KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sun > jdo > spi > persistence > support > sqlstore > sql > generator > SelectQueryPlan


1 /*
2  * The contents of this file are subject to the terms
3  * of the Common Development and Distribution License
4  * (the License). You may not use this file except in
5  * compliance with the License.
6  *
7  * You can obtain a copy of the license at
8  * https://glassfish.dev.java.net/public/CDDLv1.0.html or
9  * glassfish/bootstrap/legal/CDDLv1.0.txt.
10  * See the License for the specific language governing
11  * permissions and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL
14  * Header Notice in each file and include the License file
15  * at glassfish/bootstrap/legal/CDDLv1.0.txt.
16  * If applicable, add the following below the CDDL Header,
17  * with the fields enclosed by brackets [] replaced by
18  * you own identifying information:
19  * "Portions Copyrighted [year] [name of copyright owner]"
20  *
21  * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
22  */

23
24 /*
25  * SelectQueryPlan.java
26  *
27  * Created on October 3, 2001
28  *
29  */

30
31 package com.sun.jdo.spi.persistence.support.sqlstore.sql.generator;
32
33 import org.netbeans.modules.dbschema.ColumnElement;
34 import com.sun.jdo.api.persistence.support.JDOFatalInternalException;
35 import com.sun.jdo.api.persistence.support.JDOUserException;
36 import com.sun.jdo.spi.persistence.support.sqlstore.ActionDesc;
37 import com.sun.jdo.spi.persistence.support.sqlstore.LogHelperSQLStore;
38 import com.sun.jdo.spi.persistence.support.sqlstore.RetrieveDesc;
39 import com.sun.jdo.spi.persistence.support.sqlstore.SQLStoreManager;
40 import com.sun.jdo.spi.persistence.support.sqlstore.PersistenceManager;
41 import com.sun.jdo.spi.persistence.support.sqlstore.model.*;
42 import com.sun.jdo.spi.persistence.support.sqlstore.sql.ResultDesc;
43 import com.sun.jdo.spi.persistence.support.sqlstore.sql.RetrieveDescImpl;
44 import com.sun.jdo.spi.persistence.support.sqlstore.sql.concurrency.Concurrency;
45 import com.sun.jdo.spi.persistence.support.sqlstore.sql.constraint.*;
46 import com.sun.jdo.spi.persistence.utility.FieldTypeEnumeration;
47 import com.sun.jdo.spi.persistence.utility.I18NHelper;
48 import com.sun.jdo.spi.persistence.utility.logging.Logger;
49
50 import java.util.*;
51 import java.sql.ResultSet JavaDoc;
52 import java.sql.SQLException JavaDoc;
53
54
55 /**
56  * This class prepares the generation of select statements,
57  * by joining the constaint stacks of all retrieve descriptors
58  * into one stack.
59  */

60 public class SelectQueryPlan extends QueryPlan {
61
62     /** This plan is joined with the parent plan. Used only for dependent plan. */
63     private static final int ST_JOINED = 0x2;
64
65     /** This plans's constraints are already processed. */
66     public static final int ST_C_BUILT = 0x4;
67
68     /** This plans's order by constraints are already processed. */
69     public static final int ST_OC_BUILT = 0x10;
70
71     /**
72      * Pointer to the retrieve descriptor's constraint stack.
73      * NOTE: The retrieve descriptor's stack will be modified
74      * building the query plan!
75      */

76     protected Constraint constraint;
77
78     /** Bitmask constaining OPT_* Constants defined in {@link RetrieveDescImpl } */
79     public int options;
80
81     /** Iterator for the retrieve descriptor's list of fields to be retrieved. */
82     private Iterator fieldIterator;
83
84     /**
85      * Aggregate result type from the retrieve descriptor as defined by
86      * {@link FieldTypeEnumeration}
87      */

88     private int aggregateResultType;
89
90     /**
91      * List of SelectQueryPlan. After this plan is completely built, this field
92      * contains all the foreign plans that could not be joined with this plan.
93      */

94     private ArrayList foreignPlans;
95
96     /**
97      * This foreign field joins this plan to the parent plan. The field is from
98      * config of the parent plan. Used only for dependent plan.
99      */

100     protected ForeignFieldDesc parentField;
101
102     /**
103      * This plan corresponds to prefetched values for <code>parentField</code>.
104      * Used only for dependent plan.
105      */

106     private boolean prefetched;
107
108     private Concurrency concurrency;
109
110     /** BitSet containing the hierarchical fetch groups to be retrieved for this plan. */
111     private BitSet hierarchicalGroupMask;
112
113     /** BitSet containing the independent fetch groups to be retrieved for this plan. */
114     private BitSet independentGroupMask;
115
116     /** BitSet containing the fields to be retrieved for this plan */
117     private BitSet fieldMask;
118
119     private Map foreignConstraintPlans;
120
121     private ResultDesc resultDesc;
122
123     /**
124      * Takes care of adding an "And" constraint for unbound constraints, e.g.
125      * "empid == department.deptid". As the foreign constraint stack is empty,
126      * we would not add the necessary "And" constraint otherwise.
127      */

128     // See navigation033 for an example
129
private boolean appendAndOp;
130
131     /** The logger. */
132     private final static Logger logger = LogHelperSQLStore.getLogger();
133
134     /**
135      * Creates a new instance of SelectQueryPlan depending on the retrieve
136      * descriptor options.
137      * @param desc The retrieve descriptor
138      * @param store The store
139      * @param concurrency The concurrency for the plan.
140      * @return An instance of SelectQueryPlan depending on the retrieve
141      * descriptor options.
142      */

143     public static SelectQueryPlan newInstance(RetrieveDescImpl desc,
144                                               SQLStoreManager store,
145                                               Concurrency concurrency) {
146         SelectQueryPlan plan = null;
147
148         if ( (desc.getOptions() & RetrieveDescImpl.OPT_VERIFY) > 0) {
149             plan = new VerificationSelectPlan(desc, store);
150         } else {
151             plan = new SelectQueryPlan(desc, store, concurrency);
152         }
153
154         return plan;
155     }
156
157     /**
158      * Creates a new SelectQueryPlan.
159      *
160      * @param desc Retrieve descriptor holding the query information
161      * from the query compiler. This information includes selected
162      * fields and the query constraints. <code>desc</code> must be an
163      * instance of RetrieveDescImpl.
164      * @param store Store manager executing the query.
165      * @param concurrency Query concurrency.
166      */

167     public SelectQueryPlan(ActionDesc desc,
168                            SQLStoreManager store,
169                            Concurrency concurrency) {
170
171         super(desc, store);
172         action = ACT_SELECT;
173
174         // Initialize internal fields.
175
fieldMask = new BitSet();
176         hierarchicalGroupMask = new BitSet();
177         independentGroupMask = new BitSet();
178         this.concurrency = concurrency;
179         //excludeSubclasses = true;
180

181         if (desc instanceof RetrieveDescImpl) {
182             RetrieveDescImpl retrieveDesc = (RetrieveDescImpl) desc;
183             retrieveDesc.setPlan(this);
184
185             // Get the information from the retrieve descriptor.
186
constraint = retrieveDesc.getConstraint();
187             options = retrieveDesc.getOptions();
188             fieldIterator = retrieveDesc.getFields();
189             aggregateResultType = retrieveDesc.getAggregateResultType();
190         } else {
191             throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
192                 "core.generic.notinstanceof", desc.getClass().getName(), "RetrieveDescImpl")); // NOI18N
193
}
194     }
195
196     public Constraint getConstraint() {
197         return constraint;
198     }
199
200     private void setFieldMask(int index) {
201         if (index < 0) {
202             index = config.fields.size() - index;
203         }
204
205         fieldMask.set(index);
206     }
207
208     private boolean getFieldMask(int index) {
209         if (index < 0) {
210             index = config.fields.size() - index;
211         }
212
213         return fieldMask.get(index);
214     }
215
216     /**
217      * The addTable method is used to add tables correponding to a field to the plan.
218      * No columns corresponding the field are added to the plan.
219      *
220      * @param fieldDesc The field for which we need to add table
221      */

222     protected void addTable(LocalFieldDesc fieldDesc) {
223         addColumn(fieldDesc, false, false);
224     }
225
226     /**
227      * The addColumn method is used to specify a field for which data needs
228      * to be retrieved and therefore for which we need to select a column.
229      *
230      * @param fieldDesc The field for which we need to retrieve data and therefore
231      * for which we need to select a column.
232      */

233     protected void addColumn(LocalFieldDesc fieldDesc) {
234         addColumn(fieldDesc, true, false);
235     }
236
237     /**
238      * The addColumn method is used to specify a field for which data needs to
239      * be retrieved and therefore for which we need to select a column.
240      *
241      * @param fieldDesc The field for which we need to retrieve data and therefore
242      * for which we need to select a column.
243      * @param add Specifies if the field will be added to {@link ResultDesc}.
244      * @param projection Pass the projection information for this field
245      * to {@link ResultDesc}.
246      */

247     private void addColumn(LocalFieldDesc fieldDesc, boolean add, boolean projection) {
248
249         // We first search to see if any of the tables to which the requested
250
// field is mapped is being used by this query plan. Initially
251
// there is a select statement for each table so we just append this
252
// request as a column to the found statement. If none of the tables
253
// are being used in this query plan then we create a new statement.
254
//
255
for (Iterator iter = fieldDesc.getColumnElements(); iter.hasNext(); ) {
256             ColumnElement columnElement = (ColumnElement) iter.next();
257             QueryTable table = findQueryTable(columnElement.getDeclaringTable());
258
259             if (table == null)
260                 table = addQueryTable(columnElement.getDeclaringTable(), null);
261
262             SelectStatement statement = (SelectStatement) getStatement(table);
263
264             if (statement == null)
265                 statement = (SelectStatement) addStatement(table);
266
267             if (add) {
268                 ColumnRef columnRef = statement.addColumn(columnElement, table);
269                 // initialize the resultDesc
270
if (resultDesc == null) {
271                     resultDesc = new ResultDesc(config, aggregateResultType);
272                 }
273                 resultDesc.addField(fieldDesc, columnRef, projection);
274             }
275         }
276     }
277
278     /**
279      * Create and build the query plans for foreign fields that have been
280      * added by {@link RetrieveDesc#addResult(String, RetrieveDesc, boolean)}.
281      * If there is no projection, add the foreign key fields to the list
282      * of fields to be selected.
283      *
284      * @param foreignFields List of local fields.
285      * @param localFields List of fields to be selected.
286      * @see RetrieveDescImpl
287      */

288     private void processForeignFields(ArrayList foreignFields,
289                                       ArrayList localFields) {
290         if (foreignFields.size() == 0) {
291             return;
292         }
293
294         boolean debug = logger.isLoggable(Logger.FINEST);
295
296         if (debug) {
297             logger.finest("sqlstore.sql.generator.selectqueryplan.processforeignfield", // NOI18N
298
config.getPersistenceCapableClass().getName());
299         }
300
301         foreignPlans = new ArrayList();
302
303         for (int i = 0; i < foreignFields.size(); i++) {
304             processForeignField((ConstraintFieldName) foreignFields.get(i), localFields);
305         }
306
307         if (debug) {
308             logger.finest("sqlstore.sql.generator.selectqueryplan.processforeignfield.exit"); // NOI18N
309
}
310     }
311
312     /**
313      * Process the projected foreign field at index <code>index</code>.
314      * Initializes and builds a new SelectQueryPlan for this field and
315      * adds selected columns to the result descriptor. If the field is
316      * navigated only, just adds the statement and table alias.
317      *
318      * @param cfn
319      * @param localFields
320      */

321     private void processForeignField(ConstraintFieldName cfn,
322                                      ArrayList localFields) {
323
324         SelectQueryPlan fp = new SelectQueryPlan(cfn.desc, store, concurrency);
325
326         fp.prefetched = cfn.isPrefetched();
327         if (fp.prefetched) {
328             // Add fetch groups to the foreign plan if we are prefetching.
329
fp.options |= RetrieveDescImpl.OPT_ADD_FETCHGROUPS;
330         }
331
332         fp.processParentField(config, cfn.name);
333         fp.build();
334
335         // For navigational queries, add in any additional local fields which
336
// may be needed by this foreign field (typically the foreign keys).
337
// For external (user) queries, we just make sure we include the
338
// corresponding table into the table list.
339
for (int i = 0; i < fp.parentField.localFields.size(); i++) {
340             LocalFieldDesc la = (LocalFieldDesc) fp.parentField.localFields.get(i);
341
342             if (!getFieldMask(la.absoluteID)) {
343                 if ((options & RetrieveDescImpl.OPT_ADD_FETCHGROUPS) > 0) {
344                     // Add the field to localFields only if this plan corresponds
345
// to a candidate.
346
localFields.add(la);
347                 } else {
348                     // This plan is participating in a projection.
349
// Add the table and a corresponding statement to the plan.
350
addTable(la);
351                 }
352             }
353         }
354         foreignPlans.add(fp);
355     }
356
357     private boolean getGroupMask(int groupID) {
358         if (groupID >= FieldDesc.GROUP_DEFAULT)
359             return hierarchicalGroupMask.get(groupID);
360         else if (groupID < FieldDesc.GROUP_NONE)
361             return independentGroupMask.get(-(groupID + 1));
362
363         return true;
364     }
365
366     private void setGroupMask(int groupID) {
367         if (groupID >= FieldDesc.GROUP_DEFAULT)
368             hierarchicalGroupMask.set(groupID);
369         else if (groupID < FieldDesc.GROUP_NONE)
370             independentGroupMask.set(-(groupID + 1));
371     }
372
373     /**
374      * Add the fields from the fetch group specified by <code>groupID</code>
375      * to the list of selected fields. The decision which fields are
376      * added is based on the following rules:
377      *
378      * <ol>
379      * <li>Local fields are added for all the queries and user
380      * projections, that aren't aggregates.</li>
381      * <li>Only key fields are added for aggregate queries counting persistence capable objects.</li>
382      * <li>Foreign fields are added only for the projected retrieve descriptor and for
383      * queries that do not have relationsip prefetch disabled. <li>
384      * </ol>
385      *
386      * @param groupID Fetch group id.
387      * @param localFields List of fields to be selected.
388      * @param foreignFields List of foreign fields connecting to foreign plans.
389      */

390     private void addFetchGroup(int groupID,
391                                ArrayList localFields,
392                                ArrayList foreignFields) {
393         // We should enter this method only if OPT_ADD_FETCHGROUPS is set.
394
assert (options & RetrieveDescImpl.OPT_ADD_FETCHGROUPS) > 0;
395
396         ArrayList group = config.getFetchGroup(groupID);
397         setGroupMask(groupID);
398
399         if (group != null) {
400             for (int i = 0; i < group.size() ; i++) {
401                 FieldDesc f = (FieldDesc) group.get(i);
402
403                 if (!getFieldMask(f.absoluteID)) {
404                     final boolean isLocalField = f instanceof LocalFieldDesc;
405                     // Prevent testing field again.
406
setFieldMask(f.absoluteID);
407
408                     if (isLocalField) {
409                         if ((options & RetrieveDescImpl.OPT_ADD_KEYS_ONLY) == 0 || f.isKeyField()) {
410                             // pk fields are added before any other fields already
411
// present in localFields. This is because pk fields
412
// are the first to be read when resultset is processed.
413
// Please see IN=8852 for more details.
414
// All other fields are appended to the list.
415
int indexToInsert = ( f.isKeyField() ? 0 : localFields.size() );
416                             localFields.add(indexToInsert, f);
417                         }
418                     } else {
419                         // Add foreign fields only if this plan corresponds to the
420
// projected RD and relationship prefetch is not explicitly
421
// disabled by the user.
422
if ( (options & RetrieveDescImpl.OPT_PROJECTION) > 0 &&
423                              (options & RetrieveDescImpl.OPT_ADD_KEYS_ONLY) == 0 &&
424                              (options & RetrieveDescImpl.OPT_DISABLE_RELATIONSHIP_PREFETCH) == 0 ) {
425                             // Add current field to foreignFields as ConstraintFieldName
426
ForeignFieldDesc ff = (ForeignFieldDesc) f;
427                             RetrieveDescImpl desc = (RetrieveDescImpl)
428                                     store.getRetrieveDesc(ff.foreignConfig.getPersistenceCapableClass());
429                             foreignFields.add(new ConstraintFieldName(ff.getName(), desc, true));
430                         }
431                     }
432                 }
433             }
434         }
435     }
436
437     /**
438      * Add the fields from the fetch group specified by <code>groupID</code>
439      * to the list of selected fields. For hierarchical groups, we add all the
440      * groups from GROUP_DEFAULT up to <code>groupID</code>. For independent
441      * groups, we only add the default and the group indicated by
442      * <code>groupID</code>.
443      *
444      * @param groupID Fetch group id.
445      * @param localFields List of fields to be selected.
446      * @param foreignFields List of foreign fields.
447      */

448     private void addFetchGroups(int groupID,
449                                 ArrayList localFields,
450                                 ArrayList foreignFields) {
451
452         if (groupID >= FieldDesc.GROUP_DEFAULT) {
453             //Hierachical fetch group
454
for (int i = FieldDesc.GROUP_DEFAULT; i <= groupID; i++) {
455                 if (!getGroupMask(i)) {
456                     addFetchGroup(i, localFields, foreignFields);
457                 }
458             }
459         } else if (groupID < FieldDesc.GROUP_NONE) {
460             if (!getGroupMask(FieldDesc.GROUP_DEFAULT)) {
461                 //Independent fetch group
462
addFetchGroup(FieldDesc.GROUP_DEFAULT, localFields, foreignFields);
463             }
464
465             if (!getGroupMask(groupID)) {
466                 addFetchGroup(groupID, localFields, foreignFields);
467             }
468         }
469     }
470
471     /**
472      * Add the fetch group fields to the list of selected fields.
473      * The decision if fetch groups are added is based on the following rules:
474      *
475      * <ul>
476      * <li>Always add fetch groups for internal queries.</li>
477      * <li>For external queries, add fetch groups to the projected retrieve
478      * descriptor as marked in RetrieveDescImpl#setFetchGroupOptions(int)</li>
479      * </ul>
480      *
481      * @param localFields List of fields to be selected.
482      * @param foreignFields List of foreign fields.
483      * @see RetrieveDescImpl#setFetchGroupOptions(int)
484      */

485      private void processFetchGroups(ArrayList localFields, ArrayList foreignFields) {
486
487         if ((options & RetrieveDescImpl.OPT_ADD_FETCHGROUPS) > 0) {
488             int requestedItems = localFields.size() + foreignFields.size();
489
490             // Add the default fetch group.
491
if (!getGroupMask(FieldDesc.GROUP_DEFAULT)) {
492                 addFetchGroups(FieldDesc.GROUP_DEFAULT, localFields, foreignFields);
493             }
494
495             if (requestedItems > 0) {
496                 for (int i = 0; i < localFields.size(); i++) {
497                     FieldDesc f = (FieldDesc) localFields.get(i);
498
499                     setFieldMask(f.absoluteID);
500
501                     if (f.fetchGroup != FieldDesc.GROUP_NONE) {
502                         if (!getGroupMask(f.fetchGroup)) {
503                             addFetchGroups(f.fetchGroup, localFields, foreignFields);
504                         }
505                     }
506                 }
507
508                 for (int i = 0; i < foreignFields.size(); i++) {
509                     ConstraintFieldName cfn = (ConstraintFieldName) foreignFields.get(i);
510                     FieldDesc f = config.getField(cfn.name);
511
512                     setFieldMask(f.absoluteID);
513
514                     if (f.fetchGroup != FieldDesc.GROUP_NONE) {
515                         if (!getGroupMask(f.fetchGroup)) {
516                             addFetchGroups(f.fetchGroup, localFields, foreignFields);
517                         }
518                     }
519                 }
520             }
521         }
522     }
523
524     /**
525      * Add all requested local fields to {@link ResultDesc}.
526      *
527      * @param localFields List of local fields to be selected.
528      * @param projectionField The projected field.
529      */

530     private void processLocalFields(ArrayList localFields, LocalFieldDesc projectionField) {
531         boolean debug = logger.isLoggable(Logger.FINEST);
532
533         if (debug) {
534             logger.finest("sqlstore.sql.generator.selectqueryplan.processlocalfield", // NOI18N
535
config.getPersistenceCapableClass().getName());
536         }
537
538         for (int i = 0; i < localFields.size(); i++) {
539             LocalFieldDesc lf = (LocalFieldDesc) localFields.get(i);
540             addColumn(lf, true, (projectionField == lf));
541         }
542
543         if (debug) {
544             logger.finest("sqlstore.sql.generator.selectqueryplan.processlocalfield.exit"); // NOI18N
545
}
546     }
547
548     private void joinSecondaryTableStatement(SelectStatement statement,
549                                              SelectStatement secondaryTableStatement) {
550         statement.copyColumns(secondaryTableStatement);
551
552         QueryTable secondaryTable = (QueryTable) secondaryTableStatement.getQueryTables().get(0);
553         ReferenceKeyDesc key = secondaryTable.getTableDesc().getPrimaryTableKey();
554
555         addJoinConstraint(this, this,
556                 key.getReferencedKey().getColumns(),
557                 key.getReferencingKey().getColumns(), ActionDesc.OP_LEFTJOIN);
558
559         secondaryTableStatement.markJoined();
560     }
561
562     private void processRelatedStatements(SelectStatement statement) {
563         ArrayList secondaryTableStatements = statement.getSecondaryTableStatements();
564
565         if (secondaryTableStatements != null) {
566             for (int i = 0; i < secondaryTableStatements.size(); i++) {
567                 SelectStatement secondaryTableStatement = (SelectStatement) secondaryTableStatements.get(i);
568
569                 if (!secondaryTableStatement.isJoined()) {
570                     processRelatedStatements(secondaryTableStatement);
571                     joinSecondaryTableStatement(statement, secondaryTableStatement);
572                 }
573             }
574
575             secondaryTableStatements.clear();
576         }
577     }
578
579     protected void processStatements() {
580         boolean debug = logger.isLoggable(Logger.FINEST);
581
582         if (debug) {
583              Object JavaDoc[] items = new Object JavaDoc[] {config.getPersistenceCapableClass().getName(),
584                                             new Integer JavaDoc(statements.size())};
585              logger.finest("sqlstore.sql.generator.selectqueryplan.processstmts",items); // NOI18N
586
}
587
588         if (concurrency != null) {
589             concurrency.select(this);
590         }
591
592         int size = statements.size();
593
594         if (size > 1) {
595             super.processStatements();
596
597             for (int i = 0; i < size; i++) {
598                 SelectStatement s = (SelectStatement) statements.get(i);
599
600                 if (!s.isJoined())
601                     processRelatedStatements(s);
602             }
603
604             // Remove all the statements that have been joined.
605
for (int i = 0; i < statements.size(); i++) {
606                 SelectStatement s = (SelectStatement) statements.get(i);
607
608                 if (s.isJoined()) {
609                     statements.remove(i);
610                     i--;
611                     continue;
612                 }
613             }
614         }
615
616         if (debug) {
617             logger.finest("sqlstore.sql.generator.selectqueryplan.processstmts.exit"); // NOI18N
618
}
619     }
620
621
622     /**
623      * Asociates every local constraint on the stack with it's original plan
624      * and include any table that hasn't been added to the table list of the
625      * corresponding original plan.
626      *
627      * @see ConstraintFieldName#originalPlan
628      */

629     private void processLocalConstraints() {
630         List stack = constraint.getConstraints();
631
632         for (int i = 0; i < stack.size(); i++) {
633             ConstraintNode node = (ConstraintNode) stack.get(i);
634
635             if (node instanceof ConstraintFieldName) {
636                 ConstraintFieldName fieldNode = (ConstraintFieldName) node;
637
638                 if (fieldNode.originalPlan == null) {
639                     // The field has not been processed before
640
SelectQueryPlan thePlan = null;
641
642                     if (fieldNode.desc == null) {
643                         thePlan = this;
644                     } else {
645                         // If the field belongs to a different RetrieveDesc, we need
646
// to use the query plan associated with that RetrieveDesc.
647
RetrieveDescImpl rd = (RetrieveDescImpl) fieldNode.desc;
648                         thePlan = newForeignConstraintPlan(rd, fieldNode.name);
649                     }
650
651                     fieldNode.originalPlan = thePlan;
652
653                     // The name field is null for unrelated constraints.
654
if (fieldNode.name != null) {
655                         FieldDesc field = thePlan.config.getField(fieldNode.name);
656
657                         if (field instanceof LocalFieldDesc) {
658                             // Adds the statement and table for the field.
659
// This is only required to process plans corresponding
660
// to query filters containing unbound variables
661
// e.g. setFilter("empid == d.deptid") on an employee query.
662

663                             thePlan.addTable((LocalFieldDesc) field);
664                         }
665                     }
666                 }
667             }
668         }
669     }
670
671     /**
672      * Returns the plan for {@link RetrieveDesc} <code>rd</code>. If there is no
673      * plan associated with <code>rd</code>, a new plan is created.
674      * If <code>fieldName</code> is not null, the returned plan will be matched
675      * with the first plan that was registered for a navigation on the field
676      * or the plan's navigational id. The navigational id is used to discriminate
677      * several navigations on the same field.
678      *
679      * @param rd Foreign retrieve descriptor.
680      * @param fieldName Parent field name.
681      * @return The plan for {@link RetrieveDesc} <code>rd</code>.
682      * @see RetrieveDescImpl
683      */

684     private SelectQueryPlan newForeignConstraintPlan(RetrieveDescImpl rd,
685                                                      String JavaDoc fieldName) {
686
687         SelectQueryPlan fcp = rd.getPlan();
688
689         if (fcp == null) {
690             fcp = new SelectQueryPlan(rd, store, null);
691         }
692         // If fieldName is null, it means that we don't know what the relationship
693
// field name is yet and this query plan is a place holder.
694
if (fieldName == null) {
695             return fcp;
696         }
697
698         if (foreignConstraintPlans == null) {
699             foreignConstraintPlans = new HashMap();
700         }
701
702         SelectQueryPlan masterPlan = null;
703
704         Object JavaDoc tag = (rd.getNavigationalId() != null) ? rd.getNavigationalId() : fieldName;
705
706         if ((masterPlan = (SelectQueryPlan) foreignConstraintPlans.get(tag)) != null) {
707             // Share the tables with the master plan.
708
fcp.tables = masterPlan.tables;
709             fcp.foreignConstraintPlans = masterPlan.foreignConstraintPlans;
710         } else {
711             foreignConstraintPlans.put(tag, fcp);
712             fcp.foreignConstraintPlans = new HashMap();
713         }
714
715         return fcp;
716     }
717
718     /**
719      * Adds a subquery constraint for a correlated exists query to the
720      * constraint stack. Also add the table alias from the subquery
721      * to the local table aliases.
722      *
723      * @param ff The relationship field for the subquery
724      * @param operation {@link ActionDesc#OP_NOTEXISTS}
725      * or {@link ActionDesc#OP_EXISTS}.
726      */

727     private void addCorrelatedExistsQuery(ForeignFieldDesc ff, int operation) {
728
729         Class JavaDoc classType = (ff.cardinalityUPB > 1) ? ff.getComponentType() : ff.getType();
730         RetrieveDescImpl rd = (RetrieveDescImpl) store.getRetrieveDesc(classType);
731
732         SelectQueryPlan subqueryPlan = new CorrelatedExistSelectPlan(rd, store, ff, this);
733         subqueryPlan.build();
734
735         // Make the tables involved in the subquery known to the parent query.
736
addQueryTables(subqueryPlan.tables);
737
738         ConstraintSubquery subqueryConstraint = new ConstraintSubquery();
739         subqueryConstraint.operation = operation;
740         subqueryConstraint.plan = subqueryPlan;
741
742         constraint.stack.add(subqueryConstraint);
743     }
744
745     /**
746      * Builds the constraint plan for foreign constraints on the constraint
747      * stack. This method joins the current plan with all plans
748      * related by foreign constraints found in the plan hierarchy.
749      *
750      * @see RetrieveDescImpl
751      */

752     private void processForeignConstraints() {
753         List currentStack = constraint.getConstraints();
754         constraint.stack = new ArrayList();
755         int index = 0;
756
757         while (index < currentStack.size()) {
758             ConstraintNode node = (ConstraintNode) currentStack.get(index);
759
760             if (node instanceof ConstraintForeignFieldName) {
761                 processForeignFieldConstraint((ConstraintForeignFieldName) node);
762
763             } else if (node instanceof ConstraintFieldName) {
764                 index = processLocalFieldConstraint((ConstraintFieldName) node, currentStack, index);
765
766             } else if (node instanceof ConstraintFieldNameSubQuery) {
767                 addCorrelatedInQuery((ConstraintFieldNameSubQuery) node);
768
769             } else {
770                 constraint.stack.add(node);
771             }
772
773             index++;
774         }
775     }
776
777     /**
778      * Joins the current plan with the constraint <code>node</code>. The constraint
779      * includes the name of the parent field and the retrieve descriptor for the
780      * related class. The plans will be joined with <code>OP_EQUIJOIN</code>.
781      * The constraints processed here have been added by
782      * {@link RetrieveDesc#addConstraint(String, RetrieveDesc)}.
783      *
784      * @param node Join constraint.
785      */

786     private void processForeignFieldConstraint(ConstraintForeignFieldName node) {
787         RetrieveDescImpl rd = (RetrieveDescImpl) node.desc;
788
789         if (rd == null) {
790             throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
791                     "sqlstore.constraint.noretrievedesc", // NOI18N
792
node.name, config.getPersistenceCapableClass().getName()));
793         }
794
795         SelectQueryPlan fcp = newForeignConstraintPlan(rd, node.name);
796
797         if ((fcp.status & ST_JOINED) == 0) {
798             fcp.processParentField(config, node.name);
799             // Joins on constraints always join as equijoin
800
processJoin(fcp, ActionDesc.OP_EQUIJOIN);
801             fcp.appendAndOp = true;
802         } else {
803             fcp.appendAndOp = false;
804         }
805     }
806
807     /**
808      * Joins unrelated constraints that have been added by
809      * {@link RetrieveDesc#addConstraint(String, RetrieveDesc)} where the
810      * name of the foreign field is null. Other constraints have been added by
811      * {@link RetrieveDesc#addConstraint(String, int, RetrieveDesc, String)}
812      *
813      * @param node Join constraint.
814      * @param currentStack Current (old) constraint stack.
815      * @param index Index in current stack.
816      */

817     private int processLocalFieldConstraint(ConstraintFieldName node,
818                                             List currentStack,
819                                             int index) {
820         if (node.desc != null) {
821             SelectQueryPlan fcp = ((RetrieveDescImpl) node.desc).getPlan();
822
823             constraint.stack.add(node);
824
825             if ((fcp.status & ST_JOINED) == 0) {
826                 fcp.appendAndOp = true;
827                 // Local fields connecting to foreign plans
828
// (non relationship constraints) are not processed here
829
// because we want to join on all foreign fields first.
830
} else {
831                 // The foreign plan has already been joined. We need
832
// to add another And-constraint for
833
// FieldName-constraints using the same retrieve
834
// descriptor. This is only required for query filters
835
// comparing local fields from different tables,
836
// e.g. setFilter("empid == department.deptid") on an
837
// employee query.
838

839                 // Push the remaining operand and the operator onto the stack.
840
constraint.stack.add(currentStack.get(++index));
841                 constraint.stack.add(currentStack.get(++index));
842                 if (fcp.appendAndOp) {
843                     constraint.addOperation(ActionDesc.OP_AND);
844                     fcp.appendAndOp = false;
845                 }
846              }
847         } else {
848             index = processForeignFieldNullComparision(node, currentStack, index);
849         }
850         return index;
851     }
852
853     /**
854      * Processes a null comparision on a foreign field.
855      *
856      * @param node Current node..
857      * @param currentStack Current (old) constraint stack.
858      * @param index Index in current stack.
859      */

860     private int processForeignFieldNullComparision(ConstraintFieldName node,
861                                                    List currentStack,
862                                                    int index) {
863         boolean addCurrentNode = true;
864         if (node.name != null) {
865             // The name entry is null for unbound constraints.
866
FieldDesc f = config.getField(node.name);
867
868             if (f instanceof ForeignFieldDesc && (index + 1 < currentStack.size())) {
869                 ConstraintNode nextNode = (ConstraintNode) currentStack.get(++index);
870                 if ((nextNode instanceof ConstraintOperation) &&
871                         ((((ConstraintOperation) nextNode).operation == ActionDesc.OP_NULL) ||
872                         (((ConstraintOperation) nextNode).operation == ActionDesc.OP_NOTNULL))) {
873
874                     processNullConstraint((ForeignFieldDesc) f, nextNode);
875                 } else {
876                     constraint.stack.add(node);
877                     constraint.stack.add(nextNode);
878                 }
879                 // Current node has been processed above.
880
addCurrentNode = false;
881             }
882         }
883         if (addCurrentNode) {
884             constraint.stack.add(node);
885         }
886         return index;
887     }
888
889     /**
890      * Handles the comparison of a relationship field with (non-) null.
891      * Comparisons for non-collection relationships not mapped to jointables can be
892      * optimized to comparing the foreign key columns being (non-) null. All other
893      * cases lead to a nested (NOT-) EXISTS query.
894      *
895      * @param ff Relationship field.
896      * @param nextNode Constraint operation, either for null or non-null comparison.
897      */

898     private void processNullConstraint(ForeignFieldDesc ff, ConstraintNode nextNode) {
899
900         if (ff.hasForeignKey()) {
901             // Optimize the query to compare the foreign key fields with null.
902
ArrayList localFields = ff.getLocalFields();
903
904             for (int j = 0; j < localFields.size(); j++) {
905                 constraint.stack.add(new ConstraintFieldDesc((LocalFieldDesc) localFields.get(j)));
906                 constraint.stack.add(nextNode);
907             }
908         } else {
909             // Otherwise, generate a nested (NOT-) EXISTS sub query.
910
int subOp = ActionDesc.OP_NOTEXISTS;
911
912             if (((ConstraintOperation) nextNode).operation == ActionDesc.OP_NOTNULL) {
913                 subOp = ActionDesc.OP_EXISTS;
914             }
915
916             // Add a subquery constraint for this field
917
addCorrelatedExistsQuery(ff, subOp);
918         }
919     }
920
921     /**
922      * Creates and builds a correlated "In" subquery.
923      * Merges tables from the subquery plan to the current plan and adds
924      * the local fields corresponding to the subquery to the constaints.
925      * The subquery is added to the constraint stack.
926      *
927      * @param node subquery constraint.
928      */

929     private void addCorrelatedInQuery(ConstraintFieldNameSubQuery node) {
930         FieldDesc field = config.getField(node.fieldName);
931         RetrieveDescImpl rd = (RetrieveDescImpl) node.desc;
932
933         if (field != null && field instanceof ForeignFieldDesc) {
934             ForeignFieldDesc ff = (ForeignFieldDesc) field;
935
936             if (ff.getComponentType() != rd.getPersistenceCapableClass() ) {
937                 throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
938                         "core.constraint.unknownfield", // NOI18N
939
node.fieldName, rd.getPersistenceCapableClass().getName()));
940             }
941
942             SelectQueryPlan subqueryPlan = new CorrelatedInSelectPlan(rd, store, ff, this);
943             subqueryPlan.build();
944
945             // Make the tables involved in the subquery known to the parent query.
946
addQueryTables(subqueryPlan.tables);
947
948             // Push a new subquery constraint on the stack
949
ConstraintSubquery subqueryConstraint = new ConstraintSubquery();
950             subqueryConstraint.plan = subqueryPlan;
951             constraint.stack.add(subqueryConstraint);
952
953             ArrayList localFields = ff.getLocalFields();
954             // Add the local fields corresponding to the subquery to the stack.
955
for (int i = 0; i < localFields.size(); i++) {
956                 constraint.addField((LocalFieldDesc) localFields.get(i), this);
957             }
958         } else {
959             // We didn't get a ForeignFieldDesc from config,
960
// or the field is not present in the config.
961
throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
962                     "core.constraint.unknownfield", // NOI18N
963
node.fieldName, rd.getPersistenceCapableClass().getName()));
964         }
965     }
966
967     /**
968      * Builds the constraint plan for unbound contraints between
969      * different retrieve descriptors. <em>Unbound</em> constraints
970      * do not navigate a relationship, i.e. there isn't a {@link
971      * ConstraintForeignFieldName} connecting the two retrieve
972      * descriptors. This method handles filters like <code>setFilter("empid ==
973      * d.deptid")</code> on an employee query, where <code>d</code> is
974      * the unbound variable. These constraints have been added by
975      * {@link RetrieveDesc#addConstraint(String, int, RetrieveDesc, String)}.
976      */

977     private void processUnboundConstraints() {
978         List currentStack = constraint.getConstraints();
979         constraint.stack = new ArrayList();
980
981         for (int i = 0; i < currentStack.size(); i++) {
982             ConstraintNode node = (ConstraintNode) currentStack.get(i);
983
984             if (node instanceof ConstraintFieldName) {
985                 ConstraintFieldName fieldNode = (ConstraintFieldName) node;
986
987                 if (fieldNode.name != null) {
988                     constraint.stack.add(fieldNode);
989                 } else if (fieldNode.desc != null) {
990                     SelectQueryPlan fcp = ((RetrieveDescImpl) fieldNode.desc).getPlan();
991
992                     // Do the join.
993
if ((fcp.status & ST_JOINED) == 0) {
994                         // As this is a "real" non-relationship join,
995
// do not force the addition of an and constraint.
996
fcp.appendAndOp = false;
997
998                         processJoin(fcp, ActionDesc.OP_NONREL_JOIN);
999                     }
1000                }
1001            } else {
1002                constraint.stack.add(node);
1003            }
1004        }
1005    }
1006
1007    /**
1008     * Builds the foreign constraint plan <code>fcp</code> without
1009     * adding any new fields to {@link ResultDesc} and joins
1010     * the plans with the join operation <code>joinOp</code>.
1011     *
1012     * @param fcp Foreign constraint plan.
1013     * @param joinOp Join operation.
1014     */

1015    private void processJoin(SelectQueryPlan fcp, int joinOp) {
1016        fcp.processConstraints();
1017        doJoin(fcp, joinOp);
1018    }
1019
1020    /**
1021     * Sets the plan's parent field and adds the tables for the join columns
1022     * to the table list. The parent field is identified by <code>fieldName</code>
1023     * and defined in the model information of the parent class
1024     * <code>parentConfig</code>.
1025     *
1026     * @see ClassDesc
1027     */

1028    private void processParentField(ClassDesc parentConfig, String JavaDoc fieldName) {
1029        if (parentField == null) {
1030            // The plan has not been processed before
1031
FieldDesc f = parentConfig.getField(fieldName);
1032
1033            if (f == null || !(f instanceof ForeignFieldDesc)) {
1034                throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
1035                        "core.constraint.unknownfield", // NOI18N
1036
fieldName, parentConfig.getPersistenceCapableClass().getName()));
1037            }
1038            parentField = (ForeignFieldDesc) f;
1039
1040            // Add the join table, if neccessary.
1041
if (parentField.useJoinTable()) {
1042                // It is important to add the join table here so that this table does not
1043
// get lost behind the same join table in parentPlan's table list
1044
// See collection38
1045
//
1046
for (int i = 0; i < parentField.assocLocalColumns.size(); i++) {
1047                    ColumnElement col = (ColumnElement) parentField.assocLocalColumns.get(i);
1048                    addQueryTable(col.getDeclaringTable(), config);
1049                }
1050            }
1051
1052            // Add the joined tables.
1053
// This is required for cases where no fields from this plan are selected
1054
// The side-effect for this is to create statements with no columns.
1055
for (int i = 0; i < parentField.foreignColumns.size(); i++) {
1056                ColumnElement col = (ColumnElement) parentField.foreignColumns.get(i);
1057                addQueryTable(col.getDeclaringTable(), config);
1058            }
1059        }
1060    }
1061
1062    /**
1063     * Builds the query plan for a select type
1064     * {@link ActionDesc} (i.e. a {@link RetrieveDesc}).
1065     */

1066    public void build() {
1067        // Plan must be build only once.
1068
if ((status & ST_BUILT) > 0) {
1069            return;
1070        }
1071
1072        processFields();
1073
1074        processConstraints();
1075
1076        processJoins();
1077
1078        processOrderConstraints();
1079
1080        status |= ST_BUILT;
1081   }
1082
1083    /**
1084     * Process the fields from the retrieve descriptor's field list.
1085     * <em>Must be overwritten by subquery plans!</em>
1086     */

1087    protected void processFields() {
1088        ArrayList foreignFields = new ArrayList();
1089        ArrayList localFields = new ArrayList();
1090
1091        LocalFieldDesc projectionField = separateFieldList(localFields, foreignFields);
1092
1093        // Because of a problem with BLOB columns on SQLServer
1094
// fetch group fields are added to the beginning of localFields.
1095
processFetchGroups(localFields, foreignFields);
1096
1097        // For internal queries, processForeignFields might add additional
1098
// fields to localFields, so we call it first.
1099
processForeignFields(foreignFields, localFields);
1100        processLocalFields(localFields, projectionField);
1101    }
1102
1103    /**
1104     * Separates the retrieve descriptor's field list. Cull out the
1105     * foreign field constraints into <code>foreignFields</code>. Get
1106     * the field descriptors of local fields and put them into
1107     * <code>localFields</code>.
1108     *
1109     * @param localFields List of LocalFieldDesc.
1110     * @param foreignFields List of ConstraintFieldName.
1111     * @return LocalFieldDesc of the projected field.
1112     */

1113    private LocalFieldDesc separateFieldList(ArrayList localFields,
1114                                             ArrayList foreignFields) {
1115
1116        LocalFieldDesc projectionField = null;
1117
1118        while (fieldIterator.hasNext()) {
1119            ConstraintFieldName cfn = (ConstraintFieldName) fieldIterator.next();
1120            FieldDesc f = config.getField(cfn.name);
1121
1122            if (f == null) {
1123                throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
1124                        "core.constraint.unknownfield", // NOI18N
1125
cfn.name, config.getPersistenceCapableClass().getName()));
1126            }
1127
1128            setFieldMask(f.absoluteID);
1129
1130            if (cfn.desc != null) {
1131                foreignFields.add(cfn);
1132            } else {
1133                localFields.add(f);
1134                if (cfn.isProjection()) {
1135                    projectionField = (LocalFieldDesc) f;
1136                }
1137            }
1138        }
1139
1140        return projectionField;
1141    }
1142
1143    protected void processConstraints() {
1144        // Constraints must be build only once.
1145
if ((status & ST_BUILT) > 0 || (status & ST_C_BUILT) > 0) {
1146            return;
1147        }
1148
1149        processLocalConstraints();
1150
1151        // Join all the statements.
1152
processStatements();
1153
1154        processForeignConstraints();
1155
1156        // Joins over unbound variables.
1157
processUnboundConstraints();
1158
1159        status |= ST_C_BUILT;
1160    }
1161
1162    /**
1163     * Joins the current plan with <code>foreignPlan</code>.
1164     * The join operation <code>joinOperation</code>
1165     * will be added to the constraint stack.
1166     *
1167     * @param foreignPlan Query plan to be joined.
1168     * @param joinOperation Join operation. No join constaint is
1169     * added for non relationship joins.
1170     */

1171    private void doJoin(SelectQueryPlan foreignPlan, int joinOperation) {
1172        if ((foreignPlan.status & ST_JOINED) > 0) {
1173            return;
1174        }
1175
1176        mergeConstraints(foreignPlan, joinOperation);
1177
1178        mergeStatements(foreignPlan, joinOperation);
1179
1180        if (foreignPlan.tables != null) {
1181            addQueryTables(foreignPlan.tables);
1182        }
1183
1184        foreignPlan.status = foreignPlan.status | ST_JOINED;
1185    }
1186
1187    /**
1188     * Merge the foreign statement with us.<br />
1189     * If there is no foreign statement,
1190     * this method just returns.
1191     *
1192     * @param foreignPlan Foreign plan to be joined.
1193     * @param joinOperation Join operator.
1194     */

1195    private void mergeStatements(SelectQueryPlan foreignPlan, int joinOperation) {
1196        SelectStatement fromStatement;
1197        SelectStatement toStatement;
1198
1199        if (foreignPlan.statements.size() > 0) {
1200            toStatement = (SelectStatement) foreignPlan.statements.get(0);
1201
1202            // Merge the foreign query with us.
1203
if (statements.size() > 0) {
1204                fromStatement = (SelectStatement) statements.get(0);
1205
1206                // Copy projected columns
1207
fromStatement.copyColumns(toStatement);
1208
1209                // For a non relationship join, we need to add all tables from the
1210
// foreign statement. In any other case, the tables will be added
1211
// when the join operation is processed by the statement.
1212
if (joinOperation == ActionDesc.OP_NONREL_JOIN) {
1213                    fromStatement.tableList.addAll(toStatement.tableList);
1214                }
1215
1216                mergeResultDesc(foreignPlan);
1217                if (foreignPlan.prefetched) {
1218                    // If the foreign plan is marked as a prefetched plan,
1219
// propagate this information to its resultDesc.
1220
resultDesc.setPrefetching();
1221                }
1222
1223                this.options |= foreignPlan.options;
1224            }
1225        }
1226    }
1227
1228    /**
1229     * Merge the foreign constraints with us.<br />
1230     * Adds the appropriate Join-constraint to the stack and merges
1231     * the constraint stacks. Adds an And-constraint, if neccessary,
1232     * see {@link com.sun.jdo.spi.persistence.support.sqlstore.sql.constraint.Constraint#mergeConstraint}.
1233     *
1234     * @param foreignPlan Foreign plan to be joined.
1235     * @param joinOperation Join operator.
1236     */

1237    private void mergeConstraints(SelectQueryPlan foreignPlan, int joinOperation) {
1238
1239        if (joinOperation != ActionDesc.OP_NONREL_JOIN) {
1240            // Add the join constraint.
1241
if (foreignPlan.parentField.useJoinTable()) {
1242                // The join table is added to the foreign plan while processing
1243
// parent field
1244
addJoinConstraint(this, foreignPlan,
1245                        foreignPlan.parentField.localColumns,
1246                        foreignPlan.parentField.assocLocalColumns, joinOperation);
1247
1248                addJoinConstraint(foreignPlan, foreignPlan,
1249                        foreignPlan.parentField.assocForeignColumns,
1250                        foreignPlan.parentField.foreignColumns, joinOperation);
1251
1252                // Except for oracle, the outer join condition will end up in
1253
// from clause. Hence, add OP_AND for Equijoin only
1254
if (joinOperation == ActionDesc.OP_EQUIJOIN) {
1255                    constraint.addOperation(ActionDesc.OP_AND);
1256                }
1257            } else {
1258                addJoinConstraint(this, foreignPlan,
1259                        foreignPlan.parentField.localColumns,
1260                        foreignPlan.parentField.foreignColumns, joinOperation);
1261            }
1262        }
1263
1264        // Copy the constraints from the toStack.
1265
boolean addAnd = constraint.mergeConstraint(foreignPlan.constraint, joinOperation);
1266
1267        if (addAnd || foreignPlan.appendAndOp) {
1268            constraint.addOperation(ActionDesc.OP_AND);
1269        }
1270    }
1271
1272    /**
1273     * Joins <code>foreignPlan</code>'s result
1274     * descriptor with the current plan.
1275     *
1276     * @param foreignPlan Query plan to be joined.
1277     */

1278    private void mergeResultDesc(SelectQueryPlan foreignPlan) {
1279        ResultDesc foreignResult = foreignPlan.resultDesc;
1280
1281        if (resultDesc != null && foreignResult != null) {
1282            resultDesc.doJoin(foreignResult, foreignPlan.parentField);
1283        } else if (resultDesc == null) {
1284            resultDesc = foreignResult;
1285        }
1286    }
1287
1288    /**
1289     * Put in a join constraint to the foreign table.
1290     *
1291     * @param fromPlan The plan for fromColumns
1292     * @param toPlan The plan for toColumns
1293     * @param fromColumns List of local columns.
1294     * @param toColumns List of foreign columns.
1295     * @param joinOp Join operation. This operation is never a non relationship join.
1296     */

1297    protected void addJoinConstraint(SelectQueryPlan fromPlan,
1298                                     SelectQueryPlan toPlan,
1299                                     ArrayList fromColumns,
1300                                     ArrayList toColumns,
1301                                     int joinOp) {
1302
1303        ConstraintJoin join = new ConstraintJoin();
1304
1305        join.operation = joinOp;
1306        join.fromColumns = fromColumns;
1307        join.fromPlan = fromPlan;
1308        join.toColumns = toColumns;
1309        join.toPlan = toPlan;
1310
1311        constraint.addJoinConstraint(join);
1312    }
1313
1314    /**
1315     * Compares the statements generated for the current plan
1316     * against the statements generated for foreign query plans
1317     * and joins any statements together which it can. This method
1318     * joins query plans on foreign fields that have been added by
1319     * {@link RetrieveDesc#addResult(String,RetrieveDesc,boolean)}.
1320     *
1321     * @see RetrieveDescImpl#buildQueryPlan(SQLStoreManager, Concurrency)
1322     */

1323    private void processJoins() {
1324
1325        if (foreignPlans == null) {
1326            return;
1327        }
1328
1329        for (Iterator iter = foreignPlans.iterator(); iter.hasNext(); ) {
1330            SelectQueryPlan fp = (SelectQueryPlan) iter.next();
1331
1332            if ((fp.status & ST_JOINED) == 0) {
1333                // Recursively join foreign plans of the foreign plan.
1334
fp.processJoins();
1335
1336                // TODO: We only join to foreign query plans that involve one statement.
1337
if (statements.size() == 1 && fp.statements.size() == 1) {
1338                    doJoin(fp, getJoinOperator(fp));
1339                }
1340            }
1341
1342            if ((fp.status & ST_JOINED) > 0) {
1343                // Foreign plan has been joined
1344
iter.remove();
1345            }
1346        }
1347
1348        // Sanity check.
1349
if (foreignPlans != null && foreignPlans.size() > 0) {
1350
1351            throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
1352                    "sqlstore.sql.generator.selectqueryplan.plansnotjoined")); // NOI18N
1353
} else {
1354            foreignPlans = null;
1355        }
1356    }
1357
1358    /**
1359     * Defines the join operator based on the projection property (and
1360     * the navigated relationship). Depending on the relationship
1361     * cardinality the plans are joined with <code>OP_EQUIJOIN</code>
1362     * or <code>OP_LEFTJOIN</code>. Projection queries are always
1363     * joined with <code>OP_LEFTJOIN</code>.)
1364     *
1365     * @param dependentPlan The dependent plan
1366     * @return Join Operator.
1367     */

1368    private int getJoinOperator(SelectQueryPlan dependentPlan) {
1369        int joinOperator;
1370        ForeignFieldDesc parentField = null;
1371
1372        if (isProjection(this)) {
1373            parentField = dependentPlan.parentField;
1374        } else if (isProjection(dependentPlan)) {
1375            parentField = dependentPlan.parentField.getInverseRelationshipField();
1376        }
1377
1378        if (parentField != null) {
1379
1380            joinOperator = ActionDesc.OP_LEFTJOIN;
1381            // TODO: Check the parentField's cardinality?
1382
// if (parentField.cardinalityUPB == 1) {
1383
// // Join "to one" associations.
1384
// // There are two kinds of "to one" associations:
1385
// if (parentField.cardinalityLWB == 1) {
1386
// // 1-1 associations: (fp.parentField.cardinalityLWB == 1)
1387
// joinOperator = ActionDesc.OP_EQUIJOIN;
1388
// } else {
1389
// // Optional associations: (fp.parentField.cardinalityLWB == 0)
1390
// // The query should return an object, even if the navigated
1391
// // relationship isn't set.
1392
// joinOperator = ActionDesc.OP_LEFTJOIN;
1393
// }
1394
// } else { // parentField.cardinalityUPB > 1
1395
// // To-Many associations are always optional.
1396
// joinOperator = ActionDesc.OP_LEFTJOIN;
1397
// }
1398
} else {
1399            joinOperator = ActionDesc.OP_EQUIJOIN;
1400        }
1401
1402        return joinOperator;
1403    }
1404
1405    private boolean isProjection(SelectQueryPlan plan) {
1406        return(prefetched
1407                || (plan.options & RetrieveDescImpl.OPT_PROJECTION) > 0
1408                && (plan.options & RetrieveDescImpl.OPT_AGGREGATE) == 0);
1409    }
1410
1411    /**
1412     * Converts ConstraintFieldName used in Order by constraints into
1413     * ConstraintFieldDesc using ConstraintFieldName#originalPlan.<br />
1414     *
1415     * <em>Currently unused functionality:</em><br />
1416     * Gets all the "order by" constraints from the the current stack.
1417     * The constraints are ordered such that any "order by" constraint
1418     * with position N is placed before any "order by" constraint with
1419     * position N+m, where m > 0. Also any "order by" constraint with
1420     * no position (i.e. position < 0) are placed immediately
1421     * following the previous "order by" constraint with a position.
1422     * The order of the "order by" constraints on the constraint stack
1423     * is changed to effect this ordering.
1424     * <em>NOTE:</em> The value constraints giving the position for
1425     * the order by constraints is currently not generated by the
1426     * query compiler.
1427     */

1428    public void processOrderConstraints() {
1429
1430        if ((status & ST_BUILT) > 0 || (status & ST_OC_BUILT) > 0) {
1431            return;
1432        }
1433
1434        ArrayList orderByArray = new ArrayList();
1435
1436        int i, pos;
1437        int insertAt = 0;
1438
1439        // Now pull out all "order by" constraints and convert
1440
// ConstraintFieldName to ConstraintFieldDesc expected
1441
// by SelectStatement.
1442
if (constraint != null) {
1443            i = 0;
1444            while (i < constraint.stack.size()) {
1445                ConstraintNode opNode = (ConstraintNode) constraint.stack.get(i);
1446
1447                if ((opNode instanceof ConstraintOperation)
1448                        && ((((ConstraintOperation) opNode).operation == ActionDesc.OP_ORDERBY) ||
1449                        (((ConstraintOperation) opNode).operation == ActionDesc.OP_ORDERBY_DESC))) {
1450                    pos = -1;
1451                    if ((i > 1) && (constraint.stack.get(i - 2) instanceof ConstraintValue)) {
1452                        pos = ((Integer JavaDoc) ((ConstraintValue) constraint.stack.get(i - 2)).getValue() ).intValue();
1453                        constraint.stack.remove(i - 2);
1454                        i = i - 1;
1455                    }
1456
1457                    if (pos > 0) {
1458                        insertAt = pos;
1459                    }
1460
1461                    for (int k = orderByArray.size(); k <= insertAt; k++) {
1462                        orderByArray.add(null);
1463                    }
1464
1465                    if (orderByArray.get(insertAt) == null) {
1466                        orderByArray.set(insertAt, new ArrayList());
1467                    }
1468
1469                    ConstraintNode fieldNode = (ConstraintNode) constraint.stack.get(i - 1);
1470                    ConstraintFieldDesc consFieldDesc = null;
1471
1472                    if (fieldNode instanceof ConstraintFieldName) {
1473                        QueryPlan originalPlan = this;
1474
1475                        if (((ConstraintField) fieldNode).originalPlan != null) {
1476                            originalPlan = ((ConstraintField) fieldNode).originalPlan;
1477                        }
1478
1479                        FieldDesc fieldDesc = originalPlan.config.
1480                                getField(((ConstraintFieldName) fieldNode).name);
1481
1482                        if (!(fieldDesc instanceof LocalFieldDesc)) {
1483                            throw new JDOUserException(I18NHelper.getMessage(messages,
1484                                    "core.generic.notinstanceof", // NOI18N
1485
fieldDesc.getClass().getName(),
1486                                    "LocalFieldDesc")); // NOI18N
1487
}
1488
1489                        consFieldDesc = new ConstraintFieldDesc((LocalFieldDesc) fieldDesc,
1490                                originalPlan, 1);
1491                    } else if (fieldNode instanceof ConstraintFieldDesc) {
1492                        consFieldDesc = (ConstraintFieldDesc) fieldNode;
1493                    } else {
1494                        throw new JDOUserException(I18NHelper.getMessage(messages,
1495                                "core.generic.notinstanceof", // NOI18N
1496
fieldNode.getClass().getName(),
1497                                "ConstraintFieldName/ConstraintFieldDesc")); // NOI18N
1498
}
1499
1500                    if (((ConstraintOperation) opNode).operation == ActionDesc.OP_ORDERBY_DESC) {
1501                        consFieldDesc.ordering = -1;
1502                    }
1503
1504                    // Remember constraint in orderByArray.
1505
ArrayList temp = (ArrayList) (orderByArray.get(insertAt));
1506                    temp.add(consFieldDesc);
1507
1508                    constraint.stack.remove(i);
1509                    constraint.stack.remove(i - 1);
1510                    i = i - 2 + 1;
1511                }
1512                i = i + 1;
1513            }
1514        }
1515
1516        for (int j = 0, size = orderByArray.size(); j < size; j++) {
1517            ArrayList oa = (ArrayList) orderByArray.get(j);
1518
1519            if (constraint == null) {
1520                constraint = new Constraint();
1521            }
1522
1523            for (int k = 0, sizeK = oa.size(); k < sizeK; k++) {
1524                ConstraintFieldDesc ob = (ConstraintFieldDesc) oa.get(k);
1525
1526                if (ob.ordering < 0) {
1527                    constraint.addField(ob);
1528                    constraint.addOperation(ActionDesc.OP_ORDERBY_DESC);
1529                } else {
1530                    constraint.addField(ob);
1531                    constraint.addOperation(ActionDesc.OP_ORDERBY);
1532                }
1533            }
1534        }
1535
1536        status |= ST_OC_BUILT;
1537    }
1538
1539    protected Statement newStatement() {
1540        return new SelectStatement(store.getVendorType(), this);
1541    }
1542
1543    /**
1544     * Extract data from given <code>resultData</code>
1545     * @param pm The PersistenceManager.
1546     * @param resultData The result set from which data is to be extracted.
1547     * @return Result from the given <code>resultData</code>
1548     * @throws SQLException
1549     */

1550    public Object JavaDoc getResult(PersistenceManager pm, ResultSet JavaDoc resultData)
1551            throws SQLException JavaDoc{
1552        return resultDesc.getResult(pm, resultData);
1553    }
1554
1555}
1556
Popular Tags