Home » apache-openjpa-1.1.0-source » org.apache.openjpa.jdbc » kernel » exps » [javadoc | source]
    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one
    3    * or more contributor license agreements.  See the NOTICE file
    4    * distributed with this work for additional information
    5    * regarding copyright ownership.  The ASF licenses this file
    6    * to you under the Apache License, Version 2.0 (the
    7    * "License"); you may not use this file except in compliance
    8    * with the License.  You may obtain a copy of the License at
    9    *
   10    * http://www.apache.org/licenses/LICENSE-2.0
   11    *
   12    * Unless required by applicable law or agreed to in writing,
   13    * software distributed under the License is distributed on an
   14    * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   15    * KIND, either express or implied.  See the License for the
   16    * specific language governing permissions and limitations
   17    * under the License.    
   18    */
   19   package org.apache.openjpa.jdbc.kernel.exps;
   20   
   21   import java.io.Serializable;
   22   import java.util.HashMap;
   23   import java.util.Map;
   24   
   25   import org.apache.openjpa.jdbc.meta.ClassMapping;
   26   import org.apache.openjpa.jdbc.sql.DBDictionary;
   27   import org.apache.openjpa.jdbc.sql.Joins;
   28   import org.apache.openjpa.jdbc.sql.SQLBuffer;
   29   import org.apache.openjpa.jdbc.sql.Select;
   30   import org.apache.openjpa.kernel.exps.AbstractExpressionVisitor;
   31   import org.apache.openjpa.kernel.exps.Constant;
   32   import org.apache.openjpa.kernel.exps.Expression;
   33   import org.apache.openjpa.kernel.exps.QueryExpressions;
   34   import org.apache.openjpa.kernel.exps.Value;
   35   
   36   /**
   37    * Turns parsed queries into selects.
   38    *
   39    * @author Abe White
   40    * @nojavadoc
   41    */
   42   public class SelectConstructor
   43       implements Serializable {
   44   
   45       private boolean _extent = false;
   46   
   47       /**
   48        * Return true if we know the select to have on criteria; to be an extent.
   49        * Note that even if this method returns false, {@link #evaluate} may still
   50        * return null if we haven't cached whether the query is an extent yet.
   51        */
   52       public boolean isExtent() {
   53           return _extent;
   54       }
   55   
   56       /**
   57        * Evaluate the expression, returning a new select and filling in any
   58        * associated expression state. Use {@link #select} to then select the data.
   59        * 
   60        * @param ctx fill with execution context
   61        * @param state will be filled with expression state
   62        */
   63       public Select evaluate(ExpContext ctx, Select parent, String alias, 
   64           QueryExpressions exps, QueryExpressionsState state) {
   65           // already know that this query is equivalent to an extent?
   66           Select sel;
   67           if (_extent) {
   68               sel = ctx.store.getSQLFactory().newSelect();
   69               sel.setAutoDistinct((exps.distinct & exps.DISTINCT_AUTO) != 0);
   70               return sel;
   71           }
   72   
   73           // create a new select and initialize it with the joins needed for
   74           // the criteria of this query
   75           sel = newSelect(ctx, parent, alias, exps, state);
   76   
   77           // create where clause; if there are no where conditions and
   78           // no ordering or projections, we return null to signify that this
   79           // query should be treated like an extent
   80           Select inner = sel.getFromSelect();
   81           SQLBuffer where = buildWhere((inner != null) ? inner : sel, ctx, 
   82               state.filter, exps.filter);
   83           if (where == null && exps.projections.length == 0
   84               && exps.ordering.length == 0
   85               && (sel.getJoins() == null || sel.getJoins().isEmpty())) {
   86               _extent = true;
   87               sel.setAutoDistinct((exps.distinct & exps.DISTINCT_AUTO) != 0);
   88               return sel;
   89           }
   90   
   91           // now set sql criteria; it goes on the inner select if present
   92           if (inner != null)
   93               inner.where(where);
   94           else
   95               sel.where(where);
   96   
   97           // apply grouping and having.  this does not select the grouping
   98           // columns, just builds the GROUP BY clauses.  we don't build the
   99           // ORDER BY clauses yet because if we decide to add this select
  100           // to a union, the ORDER BY values get aliased differently
  101           if (exps.having != null) {
  102               Exp havingExp = (Exp) exps.having;
  103               SQLBuffer buf = new SQLBuffer(ctx.store.getDBDictionary());
  104               havingExp.appendTo(sel, ctx, state.having, buf);
  105               sel.having(buf);
  106           }
  107           for (int i = 0; i < exps.grouping.length; i++)
  108               ((Val) exps.grouping[i]).groupBy(sel, ctx, state.grouping[i]);
  109           return sel;
  110       }
  111   
  112       /**
  113        * Return a new select with expressions initialized.
  114        */
  115       private Select newSelect(ExpContext ctx, Select parent,
  116           String alias, QueryExpressions exps, QueryExpressionsState state) {
  117           Select sel = ctx.store.getSQLFactory().newSelect();
  118           sel.setAutoDistinct((exps.distinct & exps.DISTINCT_AUTO) != 0);
  119           sel.setJoinSyntax(ctx.fetch.getJoinSyntax());
  120           sel.setParent(parent, alias);
  121           initialize(sel, ctx, exps, state);
  122   
  123           if (!sel.getAutoDistinct()) {
  124               if ((exps.distinct & exps.DISTINCT_TRUE) != 0)
  125                   sel.setDistinct(true);
  126               else if ((exps.distinct & exps.DISTINCT_FALSE) != 0)
  127                   sel.setDistinct(false);
  128           } else if (exps.projections.length > 0) {
  129               if (!sel.isDistinct() && (exps.distinct & exps.DISTINCT_TRUE) != 0){
  130                   // if the select is not distinct but the query is, force
  131                   // the select to be distinct
  132                   sel.setDistinct(true);
  133               } else if (sel.isDistinct()) {
  134                   // when aggregating data or making a non-distinct projection
  135                   // from a distinct select, we have to select from a tmp
  136                   // table formed by a distinct subselect in the from clause;
  137                   // this subselect selects the pks of the candidate (to
  138                   // get unique candidate values) and needed field values and
  139                   // applies the where conditions; the outer select applies
  140                   // ordering, grouping, etc
  141                   boolean agg = exps.isAggregate();
  142                   boolean candidate = ProjectionExpressionVisitor.
  143                       hasCandidateProjections(exps.projections);
  144                   if (agg || (candidate 
  145                       && (exps.distinct & exps.DISTINCT_TRUE) == 0)) {
  146                       DBDictionary dict = ctx.store.getDBDictionary();
  147                       dict.assertSupport(dict.supportsSubselect,
  148                           "SupportsSubselect");
  149   
  150                       Select inner = sel;
  151                       sel = ctx.store.getSQLFactory().newSelect();
  152                       sel.setParent(parent, alias);
  153                       sel.setDistinct(agg
  154                           && (exps.distinct & exps.DISTINCT_TRUE) != 0);
  155                       sel.setFromSelect(inner);
  156   
  157                   // auto-distincting happens to get unique candidate instances
  158                   // back; don't auto-distinct if the user isn't selecting 
  159                   // candidate data
  160                   } else if (!candidate 
  161                       && (exps.distinct & exps.DISTINCT_TRUE) == 0) 
  162                       sel.setDistinct(false);
  163               }
  164           }
  165           return sel;
  166       }
  167   
  168       /**
  169        * Initialize all expressions.
  170        */
  171       private void initialize(Select sel, ExpContext ctx, QueryExpressions exps, 
  172           QueryExpressionsState state) {
  173           Map contains = null;
  174           if (HasContainsExpressionVisitor.hasContains(exps.filter)
  175               || HasContainsExpressionVisitor.hasContains(exps.having))
  176               contains = new HashMap(7);
  177   
  178           // initialize filter and having expressions
  179           Exp filterExp = (Exp) exps.filter;
  180           state.filter = filterExp.initialize(sel, ctx, contains);
  181           Exp havingExp = (Exp) exps.having;
  182           if (havingExp != null)
  183               state.having = havingExp.initialize(sel, ctx, contains);
  184   
  185           // get the top-level joins and null the expression's joins
  186           // at the same time so they aren't included in the where/having SQL
  187           Joins filterJoins = state.filter.joins;
  188           Joins havingJoins = (state.having == null) ? null : state.having.joins;
  189           Joins joins = sel.and(filterJoins, havingJoins);
  190   
  191           // initialize result values
  192           if (exps.projections.length > 0) {
  193               state.projections = new ExpState[exps.projections.length];
  194               Val resultVal;
  195               for (int i = 0; i < exps.projections.length; i++) {
  196                   resultVal = (Val) exps.projections[i];
  197                   // have to join through to related type for pc object 
  198                   // projections; this ensures that we have all our joins cached
  199                   state.projections[i] = resultVal.initialize(sel, ctx, 
  200                       Val.JOIN_REL | Val.FORCE_OUTER);
  201                   joins = sel.and(joins, state.projections[i].joins);
  202               }
  203           }
  204   
  205           // initialize grouping
  206           if (exps.grouping.length > 0) {
  207               state.grouping = new ExpState[exps.grouping.length];
  208               Val groupVal;
  209               for (int i = 0; i < exps.grouping.length; i++) {
  210                   groupVal = (Val) exps.grouping[i];
  211                   // have to join through to related type for pc object groupings;
  212                   // this ensures that we have all our joins cached
  213                   state.grouping[i] = groupVal.initialize(sel, ctx, Val.JOIN_REL);
  214                   joins = sel.and(joins, state.grouping[i].joins);
  215               }
  216           }
  217   
  218           // initialize ordering
  219           if (exps.ordering.length > 0) {
  220               state.ordering = new ExpState[exps.ordering.length];
  221               Val orderVal;
  222               for (int i = 0; i < exps.ordering.length; i++) {
  223                   orderVal = (Val) exps.ordering[i];
  224                   state.ordering[i] = orderVal.initialize(sel, ctx, 0);
  225                   joins = sel.and(joins, state.ordering[i].joins);
  226               }
  227           }
  228           sel.where(joins);
  229       }
  230   
  231       /**
  232        * Create the where sql.
  233        */
  234       private SQLBuffer buildWhere(Select sel, ExpContext ctx, ExpState state, 
  235           Expression filter) {
  236           // create where buffer
  237           SQLBuffer where = new SQLBuffer(ctx.store.getDBDictionary());
  238           where.append("(");
  239           Exp filterExp = (Exp) filter;
  240           filterExp.appendTo(sel, ctx, state, where);
  241   
  242           if (where.sqlEquals("(") || where.sqlEquals("(1 = 1"))
  243               return null;
  244           return where.append(")");
  245       }
  246   
  247       /**
  248        * Select the data for this query.
  249        */
  250       public void select(Select sel, ExpContext ctx, ClassMapping mapping,
  251           boolean subclasses, QueryExpressions exps, QueryExpressionsState state,
  252           int eager) {
  253           Select inner = sel.getFromSelect();
  254           Val val;
  255           Joins joins = null;
  256           if (sel.getSubselectPath() != null)
  257               joins = sel.newJoins().setSubselect(sel.getSubselectPath());
  258   
  259           // build ordering clauses before select so that any eager join
  260           // ordering gets applied after query ordering
  261           for (int i = 0; i < exps.ordering.length; i++)
  262               ((Val) exps.ordering[i]).orderBy(sel, ctx, state.ordering[i],
  263                   exps.ascending[i]);
  264   
  265           // if no result string set, select matching objects like normal
  266           if (exps.projections.length == 0 && sel.getParent() == null) {
  267               int subs = (subclasses) ? Select.SUBS_JOINABLE : Select.SUBS_NONE;
  268               sel.selectIdentifier(mapping, subs, ctx.store, ctx.fetch, eager);
  269           } else if (exps.projections.length == 0) {
  270               // subselect for objects; we really just need the primary key values
  271               sel.select(mapping.getPrimaryKeyColumns(), joins);
  272           } else {
  273               // if we have an inner select, we need to select the candidate
  274               // class' pk columns to guarantee unique instances
  275               if (inner != null)
  276                   inner.select(mapping.getPrimaryKeyColumns(), joins);
  277   
  278               // select each result value; no need to pass on the eager mode since
  279               // under projections we always use EAGER_NONE
  280               boolean pks = sel.getParent() != null;
  281               for (int i = 0; i < exps.projections.length; i++) {
  282                   val = (Val) exps.projections[i];
  283                   if (inner != null)
  284                       val.selectColumns(inner, ctx, state.projections[i], pks);
  285                   val.select(sel, ctx, state.projections[i], pks);
  286               }
  287   
  288               // make sure having columns are selected since it is required by 
  289               // some DBs.  put them last so they don't affect result processing
  290               if (exps.having != null && inner != null)
  291                   ((Exp) exps.having).selectColumns(inner, ctx, state.having, 
  292                       true);
  293           }
  294   
  295           // select ordering columns, since it is required by some DBs.  put them
  296           // last so they don't affect result processing
  297           for (int i = 0; i < exps.ordering.length; i++) {
  298               val = (Val) exps.ordering[i];
  299               if (inner != null)
  300                   val.selectColumns(inner, ctx, state.ordering[i], true);
  301               val.select(sel, ctx, state.ordering[i], true);
  302           }
  303   
  304           // add conditions limiting the projections to the proper classes; if
  305           // this isn't a projection or a subq then they will already be added
  306           if (exps.projections.length > 0 || sel.getParent() != null) {
  307               ctx.store.loadSubclasses(mapping);
  308               mapping.getDiscriminator().addClassConditions((inner != null) 
  309                   ? inner : sel, subclasses, joins);
  310           }
  311       }
  312   
  313       /**
  314        * Used to check whether a query's result projections are on the candidate.
  315        */
  316       private static class ProjectionExpressionVisitor
  317           extends AbstractExpressionVisitor {
  318   
  319           private boolean _candidate = false;
  320           private int _level = 0;
  321   
  322           public static boolean hasCandidateProjections(Value[] projs) {
  323               ProjectionExpressionVisitor v = new ProjectionExpressionVisitor();
  324               for (int i = 0; i < projs.length; i++) {
  325                   projs[i].acceptVisit(v);
  326                   if (v._candidate)
  327                       return true;
  328               }
  329               return false;
  330           }
  331   
  332           public void enter(Value val) {
  333               if (!_candidate) {
  334                   _candidate = (_level == 0 && val instanceof Constant)
  335                       || (val instanceof PCPath 
  336                       && !((PCPath) val).isVariablePath());
  337               }
  338               _level++;
  339           }
  340   
  341           public void exit(Value val) {
  342               _level--;
  343           }
  344       }
  345   }

Save This Page
Home » apache-openjpa-1.1.0-source » org.apache.openjpa.jdbc » kernel » exps » [javadoc | source]