001: /*
002: * Copyright (c) 1998 - 2005 Versant Corporation
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * Versant Corporation - initial API and implementation
010: */
011: package com.versant.core.jdbc.query;
012:
013: import com.versant.core.common.Debug;
014: import com.versant.core.jdo.QueryDetails;
015: import com.versant.core.common.CmdBitSet;
016: import com.versant.core.metadata.*;
017: import com.versant.core.jdo.query.*;
018: import com.versant.core.jdbc.FgDs;
019: import com.versant.core.jdbc.JdbcStorageManager;
020: import com.versant.core.jdbc.ProjectionQueryDecoder;
021: import com.versant.core.jdbc.metadata.JdbcClass;
022: import com.versant.core.jdbc.metadata.JdbcColumn;
023: import com.versant.core.jdbc.metadata.JdbcField;
024: import com.versant.core.jdbc.metadata.JdbcFetchGroup;
025: import com.versant.core.jdbc.sql.SqlDriver;
026: import com.versant.core.jdbc.sql.exp.*;
027:
028: import com.versant.core.common.BindingSupportImpl;
029:
030: /**
031: * This will compile a JDOQL query into a JdbcCompiledQuery.
032: *
033: * @see JdbcCompiledQuery
034: * @see #reinit
035: */
036: public final class JdbcJDOQLCompiler {
037:
038: private final JdbcStorageManager sm;
039: private final ModelMetaData jmd;
040: private final JDOQLNodeToSqlExp visitor;
041:
042: // these fields must be set to null in reinit
043: private ClassMetaData cmd;
044: private ParamNode[] params;
045: private OrderNode[] orders;
046: private UnaryNode filter;
047: private ResultNode resultNode;
048: private GroupingNode groupingNode;
049: private QueryParser qParser;
050: private SelectExp candidateSelectExp;
051:
052: public JdbcJDOQLCompiler(JdbcStorageManager sm) {
053: this .sm = sm;
054: this .jmd = sm.getJmd();
055: visitor = new JDOQLNodeToSqlExp(this );
056: }
057:
058: /**
059: * Get this compiler ready to compile more queries. This is called before
060: * it is returned to the pool.
061: */
062: public void reinit() {
063: cmd = null;
064: params = null;
065: orders = null;
066: filter = null;
067: qParser = null;
068: resultNode = null;
069: groupingNode = null;
070: candidateSelectExp = null;
071: }
072:
073: public QueryParser getQParser() {
074: return qParser;
075: }
076:
077: public JDOQLNodeToSqlExp getVisitor() {
078: return visitor;
079: }
080:
081: /**
082: * Compile a QueryDetails into a JdbcCompiledQuery ready to run.
083: */
084: public JdbcCompiledQuery compile(QueryDetails q) {
085:
086: cmd = jmd.getClassMetaData(q.getCandidateClass());
087: if (cmd == null) {
088: throw BindingSupportImpl.getInstance().invalidOperation(
089: "Class " + q.getCandidateClass().getName()
090: + " not found in meta data");
091: }
092:
093: return compileImp(q);
094: }
095:
096: /**
097: * Create a filter exp for a collection query.
098: */
099: public SqlExp compileParallelFetch(QueryDetails q) {
100: cmd = jmd.getClassMetaData(q.getCandidateClass());
101: if (cmd == null) {
102: throw BindingSupportImpl.getInstance().invalidOperation(
103: "Class " + q.getCandidateClass().getName()
104: + " not found in meta data");
105: }
106: return compileParallelFetchImp(q);
107: }
108:
109: private JdbcCompiledQuery compileImp(QueryDetails q) {
110: JdbcCompiledQuery cq = new JdbcCompiledQuery(cmd, q);
111:
112: qParser = new QueryParser(jmd);
113:
114: try {
115: qParser.parse(q);
116:
117: resolveVarNodes(qParser);
118: params = qParser.getParams();
119: orders = qParser.getOrders();
120: filter = qParser.getFilter();
121: resultNode = qParser.getResultNode();
122: groupingNode = qParser.getGroupingNode();
123: } catch (Exception e) {
124: if (BindingSupportImpl.getInstance().isOwnException(e)) {
125: throw (RuntimeException) e;
126: } else {
127: throw BindingSupportImpl.getInstance()
128: .invalidOperation(e.getMessage(), e);
129: }
130: } catch (TokenMgrError e) {
131: throw BindingSupportImpl.getInstance().invalidOperation(
132: e.getMessage(), e);
133: }
134:
135: // build the SQL query tree
136: candidateSelectExp = new SelectExp();
137: JdbcClass jdbcClass = (JdbcClass) cmd.storeClass;
138: candidateSelectExp.table = jdbcClass.table;
139:
140: FetchGroup fg = cmd.fetchGroups[q.getFetchGroupIndex()];
141:
142: //do inner joins to basetable
143: for (FetchGroup supg = fg.super FetchGroup; supg != null; supg = supg.super FetchGroup) {
144: JdbcClass sc = (JdbcClass) supg.classMetaData.storeClass;
145:
146: if (sc.table != candidateSelectExp.table) {
147: // different table so do an inner join
148: SelectExp se = candidateSelectExp.findTable(sc.table);
149: if (se == null) {
150: se = new SelectExp();
151: se.table = sc.table;
152: candidateSelectExp.addJoin(
153: candidateSelectExp.table.pk, se.table.pk,
154: se);
155: }
156: }
157: }
158:
159: if (filter != null) {
160: candidateSelectExp.whereExp = visitor.toSqlExp(filter,
161: candidateSelectExp, null, 0, null);
162: // candidateSelectExp.whereExp = filter.toSqlExp(this,
163: // candidateSelectExp, null, 0, null);
164: }
165:
166: // include only correct subclass(es) if cmd is in a heirachy
167: if (cmd.isInHeirachy())
168: addSubclassFilter(candidateSelectExp, cq);
169:
170: if (Debug.DEBUG) {
171: Debug.OUT.println("\n* SQL tree:");
172: candidateSelectExp.dump("");
173: }
174:
175: SqlDriver driver = sm.getSqlDriver();
176:
177: FgDs fgDs = cq.fgDs = ((JdbcFetchGroup) fg.storeFetchGroup)
178: .getFgDs(cq.isIncludeSubclasses(), false);
179: SqlExp orderByFromCrossJoin = null;
180: if (resultNode == null) {
181: candidateSelectExp.normalize(driver, null, driver
182: .isConvertExistsToDistinctJoin());
183: if (Debug.DEBUG) {
184: Debug.OUT.println("\n* Normalized SQL tree:");
185: candidateSelectExp.dump("");
186: }
187:
188: cq.process();
189: // add the fetch group to the query and make sure the pk is also there
190: sm.addSelectFetchGroup(candidateSelectExp, fg, cq
191: .isIncludeSubclasses(), fgDs, cq
192: .isCrossJoinAllowed());
193:
194: addPrimaryKey(candidateSelectExp);
195:
196: cq.setSelectColumnCount(candidateSelectExp
197: .getSelectListColumnCount());
198: orderByFromCrossJoin = candidateSelectExp.orderByList;
199: candidateSelectExp.orderByList = null;
200: } else {
201: processNode(resultNode, "Result");
202: processNode(groupingNode, "Grouping");
203: if (groupingNode != null && groupingNode.havingNode != null) {
204: processNode(groupingNode.havingNode, "Having");
205: }
206: ProjectionQueryDecoder decoder = new ProjectionQueryDecoder(
207: resultNode, driver);
208:
209: cq.setProjectionDecoder(decoder);
210: cq.setGroupingNode(groupingNode);
211: cq.process();
212:
213: /**
214: * remove distinct if all the existExp that was removed was for the
215: * variable that is in the result string.
216: *
217: * Check to see if the candidateSelectExp constains joins that require
218: * a distinct
219: */
220:
221: boolean convertExistBecauseOfVarInProjection = resultNode
222: .processForVarNodes();
223: candidateSelectExp.normalize(driver, null,
224: convertExistBecauseOfVarInProjection
225: || driver.isConvertExistsToDistinctJoin());
226:
227: if (resultNode.isDistinct()) {
228: candidateSelectExp.distinct = true;
229: }
230:
231: if (Debug.DEBUG) {
232: Debug.OUT.println("\n* Normalized SQL tree:");
233: candidateSelectExp.dump("");
234: }
235:
236: if (decoder.containsThis()) {
237: SqlExp tail = sm.addSelectFetchGroup(
238: candidateSelectExp, fg, cq
239: .isIncludeSubclasses(), fgDs, false);
240: addPrimaryKey(candidateSelectExp);
241: cq
242: .setSelectColumnCount(candidateSelectExp.selectListCountBeforeAggregate = candidateSelectExp
243: .getSelectListColumnCount());
244: tail.next = visitor.toSqlExp(resultNode,
245: candidateSelectExp, null, 0, null);
246: } else {
247: candidateSelectExp.selectList = visitor.toSqlExp(
248: resultNode, candidateSelectExp, null, 0, null);
249: }
250:
251: }
252:
253: candidateSelectExp.groupByList = getGroupingExp();
254: if (groupingNode != null) {
255: if (groupingNode.havingNode == null) {
256: candidateSelectExp.havingExp = null;
257: } else {
258: candidateSelectExp.havingExp = new HavingExp(visitor
259: .toSqlExp(groupingNode.havingNode,
260: candidateSelectExp, null, 0, null));
261: }
262: }
263:
264: reOrderJoinExp(cmd.fetchGroups[q.getFetchGroupIndex()],
265: candidateSelectExp);
266:
267: // add an orderby if required
268: if (orders != null) {
269: addOrderBy(candidateSelectExp);
270: }
271:
272: if (Debug.DEBUG) {
273: Debug.OUT.println("\n* Finished SQL tree:");
274: candidateSelectExp.dump("");
275: }
276:
277: /**
278: * determine if we should add a orderBy pk. This is only needed for
279: * parColFetching. If this is a aggregate only query then this is not needed.
280: */
281: if (cq.isContainsThis()) {
282: if (cq.isParColFetchEnabled() || cq.isCrossJoinAllowed()) {
283: candidateSelectExp
284: .appendOrderByForColumns(((JdbcClass) cmd.storeClass).table.pk);
285: }
286: }
287:
288: /**
289: * Add the orderby as provided by the crossjoin
290: */
291: if (orderByFromCrossJoin != null) {
292: candidateSelectExp.appendOrderByExp(orderByFromCrossJoin);
293: }
294:
295: //find all equivalent joins
296: SelectExp.mergeJoinList(candidateSelectExp.joinList);
297:
298: doFinalSql(cq.getSqlStruct(), candidateSelectExp, driver);
299:
300: // build params from ParamNode usages
301: if (params != null)
302: compileParams(qParser, cq.getSqlStruct());
303:
304: if (Debug.DEBUG) {
305: Debug.OUT.println("\nParams:");
306: dumpParams(cq, cq.getParamList());
307: }
308:
309: final CmdBitSet bits = qParser.getCmds();
310: int[] a = q.getExtraEvictClasses();
311: if (a != null) {
312: for (int i = a.length - 1; i >= 0; i--) {
313: bits.add(jmd.classes[a[i]]);
314: }
315: }
316: cq.setFilterClsIndexs(bits.toArray());
317: cq.setCacheable(bits.isCacheble() && !q.isRandomAccess());
318: cq.setEvictionClassBits(bits.getBits());
319: cq.setEvictionClassIndexes(bits.getIndexes());
320: return cq;
321: }
322:
323: /**
324: * Fill in the storeExtent of all variables.
325: */
326: private void resolveVarNodes(QueryParser qParser) {
327: VarNode[] vars = qParser.getVars();
328: if (vars == null || vars.length == 0) {
329: return;
330: }
331: for (int i = 0; i < vars.length; i++) {
332: VarNode var = vars[i];
333: ClassMetaData vcmd = var.getCmd();
334: if (vcmd == null) {
335: continue;
336: }
337: SelectExp se = new SelectExp();
338: se.table = ((JdbcClass) vcmd.storeClass).table;
339: se.var = var;
340: if (vcmd.pcSuperMetaData != null) {
341: // subclass so add test for correct class-id in jdo_class
342: // column
343: se.whereExp = ((JdbcClass) vcmd.storeClass)
344: .getCheckClassIdExp(se);
345: }
346: var.setStoreExtent(se);
347: }
348: }
349:
350: public static void reOrderJoinExp(FetchGroup fg, SelectExp se) {
351: Join current = se.joinList;
352: FetchGroupField[] fgfs = fg.fields;
353: for (int i = 0; i < fgfs.length; i++) {
354: FetchGroupField fgf = fgfs[i];
355:
356: if (fgf.fmd.category != MDStatics.CATEGORY_REF)
357: continue;
358: Join join = se.findJoin((JdbcField) fgf.fmd.storeField);
359: if (join != null) {
360: if (join == current) {
361: current = current.next;
362: } else {
363: Join beforeToMove = findJoinBefore(join,
364: se.joinList);
365: Join beforeCurrent = findJoinBefore(current,
366: se.joinList);
367:
368: if (beforeToMove.next != join) {
369: throw new RuntimeException(
370: "before.next != join");
371: }
372:
373: beforeToMove.next = join.next;
374: join.next = current;
375: if (beforeCurrent != null)
376: beforeCurrent.next = join;
377: if (current == se.joinList) {
378: se.joinList = join;
379: }
380: }
381: }
382: }
383: }
384:
385: private SqlExp getGroupingExp() {
386: SqlExp gpExp = null;
387: if (groupingNode != null) {
388: gpExp = visitor.toSqlExp(groupingNode, candidateSelectExp,
389: null, 0, null);
390: }
391: return gpExp;
392: }
393:
394: private void processNode(Node node, String info) {
395: if (node == null)
396: return;
397: if (Debug.DEBUG) {
398: Debug.OUT.println("\n* " + info + ": " + node);
399: Debug.OUT.println("\n* Parsed tree:");
400: node.dump("");
401: }
402:
403: node.normalize();
404: if (Debug.DEBUG) {
405: Debug.OUT.println("\n* Normalized tree:");
406: node.dump("");
407: }
408:
409: node.resolve(qParser, cmd, false);
410: if (Debug.DEBUG) {
411: Debug.OUT.println("\n* Resolved tree:");
412: node.dump("");
413: }
414:
415: node.normalize();
416: if (Debug.DEBUG) {
417: Debug.OUT.println("\n* Second normalized tree:");
418: node.dump("");
419: }
420: }
421:
422: private SqlExp compileParallelFetchImp(QueryDetails q) {
423: JdbcCompiledQuery cq = new JdbcCompiledQuery(cmd, q);
424: qParser = new QueryParser(jmd);
425:
426: try {
427: qParser.parse(q);
428: resolveVarNodes(qParser);
429: params = qParser.getParams();
430: orders = qParser.getOrders();
431: filter = qParser.getFilter();
432: } catch (Exception e) {
433: if (BindingSupportImpl.getInstance().isOwnException(e)) {
434: throw (RuntimeException) e;
435: } else {
436: throw BindingSupportImpl.getInstance()
437: .invalidOperation(e.getMessage(), e);
438: }
439: } catch (TokenMgrError e) {
440: throw BindingSupportImpl.getInstance().invalidOperation(
441: e.getMessage(), e);
442: }
443:
444: if (filter != null) {
445: filter.normalize();
446: if (Debug.DEBUG) {
447: Debug.OUT.println("\n* Normalized tree:");
448: filter.dump("");
449: }
450: }
451:
452: // build the SQL query tree
453: candidateSelectExp = new SelectExp();
454: JdbcClass jdbcClass = (JdbcClass) cmd.storeClass;
455: candidateSelectExp.table = jdbcClass.table;
456:
457: FetchGroup fg = cmd.fetchGroups[q.getFetchGroupIndex()];
458: //do inner joins to basetable
459: for (FetchGroup supg = fg.super FetchGroup; supg != null; supg = supg.super FetchGroup) {
460: JdbcClass sc = (JdbcClass) supg.classMetaData.storeClass;
461:
462: if (sc.table != candidateSelectExp.table) {
463: // different table so do an inner join
464: SelectExp se = candidateSelectExp.findTable(sc.table);
465: if (se == null) {
466: se = new SelectExp();
467: se.table = sc.table;
468: candidateSelectExp.addJoin(
469: candidateSelectExp.table.pk, se.table.pk,
470: se);
471: }
472: }
473: }
474:
475: if (filter != null) {
476: candidateSelectExp.whereExp = visitor.toSqlExp(filter,
477: candidateSelectExp, null, 0, null);
478: }
479:
480: // include only correct subclass(es) if cmd is in a heirachy
481: if (cmd.isInHeirachy())
482: addSubclassFilter(candidateSelectExp, cq);
483:
484: if (Debug.DEBUG) {
485: Debug.OUT.println("\n* SQL tree:");
486: candidateSelectExp.dump("");
487: }
488:
489: SqlDriver driver = sm.getSqlDriver();
490: candidateSelectExp.normalize(driver, null, driver
491: .isConvertExistsToDistinctJoin());
492: if (Debug.DEBUG) {
493: Debug.OUT.println("\n* Normalized SQL tree:");
494: candidateSelectExp.dump("");
495: }
496:
497: reOrderJoinExp(cmd.fetchGroups[q.getFetchGroupIndex()],
498: candidateSelectExp);
499:
500: // add an order by if required
501: if (orders != null) {
502: addOrderBy(candidateSelectExp);
503: }
504:
505: if (Debug.DEBUG) {
506: Debug.OUT.println("\n* Finished SQL tree:");
507: candidateSelectExp.dump("");
508: }
509:
510: candidateSelectExp
511: .appendOrderByForColumns(((JdbcClass) cmd.storeClass).table.pk);
512:
513: if (Debug.DEBUG) {
514: Debug.OUT.println("\nSQL:\n" + cq.getSqlbuf());
515: }
516: return candidateSelectExp;
517: }
518:
519: public static void doFinalSql(SqlStruct sqlStruct, SelectExp root,
520: SqlDriver driver) {
521: // get the final SQL
522: int aliasCount = root.createAlias(0);
523: if (aliasCount == 1) {
524: root.alias = null;
525: sqlStruct.setFirstTableOrAlias(root.table.name);
526: } else {
527: sqlStruct.setFirstTableOrAlias(root.alias);
528: }
529:
530: root.appendSQL(driver, sqlStruct.getSqlbuf(), null);
531: sqlStruct.setSelectListRange(root.distinct,
532: root.selectListStartIndex,
533: root.selectListFirstColEndIndex,
534: root.selectListEndIndex);
535: sqlStruct.setOrderByRange(root.orderByStartIndex,
536: root.orderByEndIndex);
537:
538: // work around bug with replace in CharBuffer class
539: sqlStruct.getSqlbuf().append(' ');
540:
541: if (Debug.DEBUG) {
542: System.out.println("\nSQL:\n" + sqlStruct.getSqlbuf());
543: }
544: }
545:
546: public static Join findJoinBefore(Join aJoin, Join root) {
547: for (Join join = root; join != null; join = join.next) {
548: if (join.next == aJoin)
549: return join;
550: }
551: return null;
552: }
553:
554: private void addSubclassFilter(SelectExp root, JdbcCompiledQuery cq) {
555: // no filter needed if we want the whole heirachy
556: if (((JdbcClass) cmd.storeClass).classIdCol != null) {
557: if (cq.isIncludeSubclasses() && cmd.pcSuperMetaData == null)
558: return;
559: if (cq.isIncludeSubclasses()
560: && ((JdbcClass) cmd.storeClass).inheritance == JdbcClass.INHERITANCE_VERTICAL) {
561: return;
562: }
563: addClassIdFilter(root, cq);
564: } else {
565: if (!cq.isIncludeSubclasses()) {
566: if (cmd.pcSubclasses == null)
567: return;
568: //must add joins to immediate sub tables and where id col is null
569: for (int i = 0; i < cmd.pcSubclasses.length; i++) {
570: ClassMetaData pcSubclass = cmd.pcSubclasses[i];
571:
572: SelectExp se = root
573: .findTable(((JdbcClass) pcSubclass.storeClass).table);
574: if (se == null) {
575: se = new SelectExp();
576: se.outer = true;
577: se.table = ((JdbcClass) pcSubclass.storeClass).table;
578:
579: SqlExp colExp = ((JdbcClass) pcSubclass.storeClass).table.pk[0]
580: .toSqlExp(se);
581:
582: root.appendToWhereExp(new IsNullExp(colExp));
583: root.addJoin(root.table.pk, se.table.pk, se);
584: }
585: }
586: }
587: }
588:
589: }
590:
591: private void addClassIdFilter(SelectExp root, JdbcCompiledQuery cq) {
592: // create expression for the class ID column
593: JdbcColumn cidcol = ((JdbcClass) cmd.storeClass).classIdCol;
594: SelectExp se = root.findTable(cidcol.table);
595: if (se == null) {
596: throw BindingSupportImpl.getInstance().invalidOperation(
597: "Table for classId column not in SelectExp");
598: }
599: SqlExp cidexp = cidcol.toSqlExp(se);
600: if (cidexp.next != null) {
601: throw BindingSupportImpl.getInstance().invalidOperation(
602: "Compound classId columns not implemented");
603: }
604:
605: SqlExp fe;
606: if (!cq.isIncludeSubclasses() || cmd.pcSubclasses == null) {
607: // classid = literal expression
608: fe = new BinaryOpExp(
609: cidexp,
610: BinaryOpExp.EQUAL,
611: cidcol
612: .createClassIdLiteralExp(((JdbcClass) cmd.storeClass).jdbcClassId));
613: } else {
614: // in expression with a literal for each class id
615: cidexp.next = createClassIdLiteralExp(cmd);
616: fe = new InExp(cidexp);
617: }
618:
619: // add this to the where clause
620: if (root.whereExp == null) {
621: root.whereExp = fe;
622: } else if (root.whereExp instanceof AndExp) {
623: root.whereExp.append(fe);
624: } else {
625: root.whereExp.next = fe;
626: root.whereExp = new AndExp(root.whereExp);
627: }
628: }
629:
630: /**
631: * Create a list of LiteralExp's for the class ID's of cmd and all of
632: * its subclasses.
633: */
634: private SqlExp createClassIdLiteralExp(ClassMetaData cmd) {
635: SqlExp e = ((JdbcClass) cmd.storeClass).classIdCol
636: .createClassIdLiteralExp(((JdbcClass) cmd.storeClass).jdbcClassId);
637: ClassMetaData[] a = cmd.pcSubclasses;
638: if (a != null) {
639: SqlExp p = e;
640: for (int i = a.length - 1; i >= 0; i--) {
641: SqlExp q = createClassIdLiteralExp(a[i]);
642: for (; p.next != null; p = p.next)
643: ;
644: p = p.next = q;
645: }
646: }
647: return e;
648: }
649:
650: private SqlExp addOrderBy(SelectExp root) {
651: int len = orders.length;
652: for (int i = 0; i < len; i++)
653: orders[i].resolve(qParser, cmd, true);
654: return root.addOrderBy(orders, false, visitor);
655: }
656:
657: private void addPrimaryKey(SelectExp root) {
658: JdbcColumn[] pk = root.table.pkSimpleCols;
659: ColumnExp list = new ColumnExp(pk[0], root, null);
660: SqlExp pos = list;
661: int n = pk.length;
662: for (int i = 1; i < n; i++) {
663: pos = pos.next = new ColumnExp(pk[i], root, null);
664: }
665: pos.next = root.selectList;
666: root.selectList = list;
667: }
668:
669: /**
670: * This nasty code has to find the bits of SQL occupied by parameter
671: * expressions that could be null. They may need to be converted into
672: * 'is null' or 'is not null' or removed completely (for shared columns)
673: * if the corresponding parameter is null.
674: */
675: public static void compileParams(QueryParser qParser,
676: SqlStruct sqlStruct) {
677: if (qParser.getParams() == null)
678: return;
679: SqlStruct.Param list = null;
680: SqlStruct.Param pos = null;
681: ParamNode[] params = qParser.getParams();
682: int np = params.length;
683: for (int i = 0; i < np; i++) {
684: ParamNode p = params[i];
685: SqlParamUsage usage = (SqlParamUsage) p.usageList;
686: if (usage == null)
687: continue;
688:
689: for (; usage != null; usage = usage.next) {
690:
691: // create new param and add it to the list
692: SqlStruct.Param param = new SqlStruct.Param(p
693: .getIdentifier());
694: if (pos == null) {
695: pos = list = param;
696: } else {
697: pos = pos.next = param;
698: }
699:
700: // fill in the param
701: param.declaredParamIndex = i;
702: JdbcField jdbcField = usage.jdbcField;
703: if (jdbcField == null) {
704: param.classIndex = usage.classIndex;
705: param.fieldNo = -1;
706: param.javaTypeCode = usage.javaTypeCode;
707: if (param.javaTypeCode == 0) {
708: p.resolve(qParser, null, false);
709: param.javaTypeCode = MDStaticUtils.toTypeCode(p
710: .getCls());
711: }
712: param.jdbcType = usage.jdbcType;
713: param.col = usage.col;
714: } else {
715: param.classIndex = jdbcField.fmd.classMetaData.index;
716: param.fieldNo = jdbcField.stateFieldNo;
717: param.col = usage.col;
718: }
719: param.mod = usage.mod;
720:
721: // make a CharSpan for each usage
722: if (usage.expCount > 0) {
723: SqlStruct.CharSpan cspos = null;
724: SqlStruct.CharSpan[] a = new SqlStruct.CharSpan[usage.expCount];
725: boolean multicol = usage.expCount > 1;
726: int j = 0;
727: int removeCount = 0;
728: for (SqlExp e = usage.expList; j < a.length; j++) {
729: SqlStruct.CharSpan cs = a[j] = new SqlStruct.CharSpan();
730: if (multicol && mustBeRemovedIfNull(e)) {
731: if (++removeCount == a.length) {
732: // all expressions are to be removed so restart
733: // the loop making them all 'is null' instead
734: multicol = false;
735: e = usage.expList;
736: j = -1;
737: cspos = null;
738: continue;
739: }
740: cs.firstCharIndex = e
741: .getPreFirstCharIndex();
742: if (e.next == null) { // last span
743: cs.lastCharIndex = e.getLastCharIndex();
744: // work back and remove trailing 'and' if any
745: for (int k = j - 1; k >= 0; k--) {
746: if (a[k].type != SqlStruct.CharSpan.TYPE_REMOVE) {
747: a[k + 1].firstCharIndex -= 4; // 'and '
748: break;
749: }
750: }
751: } else { // first or middle span
752: cs.lastCharIndex = e.next
753: .getPreFirstCharIndex();
754: }
755: cs.type = SqlStruct.CharSpan.TYPE_REMOVE;
756: } else {
757: cs.firstCharIndex = e.getFirstCharIndex();
758: cs.lastCharIndex = e.getLastCharIndex();
759: cs.type = e.isNegative() ? SqlStruct.CharSpan.TYPE_NOT_NULL
760: : SqlStruct.CharSpan.TYPE_NULL;
761: }
762:
763: if (cspos == null) {
764: cspos = param.charSpanList = cs;
765: param.firstCharIndex = cs.firstCharIndex;
766: } else {
767: cspos = cspos.next = cs;
768: }
769:
770: e = e.next;
771: }
772: } else {
773: param.firstCharIndex = usage.expList
774: .getFirstCharIndex();
775: }
776: }
777: }
778: if (list != null)
779: sqlStruct.setParamList(sortParams(list));
780: }
781:
782: /**
783: * Columns that are not updated (i.e. are shared) must be removed
784: * completely if the matching parameter is null.
785: */
786: private static boolean mustBeRemovedIfNull(SqlExp e) {
787: if (!e.isNegative() && e.childList instanceof ColumnExp) {
788: ColumnExp ce = (ColumnExp) e.childList;
789: return !ce.col.isForUpdate();
790: }
791: return false;
792: }
793:
794: /**
795: * Sort the params in the order that they appear in the query.
796: */
797: private static SqlStruct.Param sortParams(SqlStruct.Param list) {
798: if (list.next == null)
799: return list;
800: // stone sort the list (bubble sort except elements sink to the bottom)
801: for (;;) {
802: boolean changed = false;
803: SqlStruct.Param p0 = null;
804: for (SqlStruct.Param p1 = list;;) {
805: SqlStruct.Param p2 = p1.next;
806: if (p2 == null)
807: break;
808: if (p1.firstCharIndex > p2.firstCharIndex) {
809: // exchange p and p2
810: p1.next = p2.next;
811: p2.next = p1;
812: if (p0 == null) {
813: list = p2;
814: } else {
815: p0.next = p2;
816: }
817: p0 = p2;
818: changed = true;
819: } else {
820: p0 = p1;
821: p1 = p2;
822: }
823: }
824: if (!changed)
825: return list;
826: }
827: }
828:
829: private void dumpParams(JdbcCompiledQuery cq, SqlStruct.Param p) {
830: for (; p != null; p = p.next) {
831: if (Debug.DEBUG) {
832: Debug.OUT.println("Param " + p.declaredParamIndex
833: + " firstCharIndex " + p.firstCharIndex);
834: }
835: for (SqlStruct.CharSpan s = p.charSpanList; s != null; s = s.next) {
836: if (Debug.DEBUG) {
837: String ts;
838: switch (s.type) {
839: case SqlStruct.CharSpan.TYPE_NULL:
840: ts = "NULL";
841: break;
842: case SqlStruct.CharSpan.TYPE_NOT_NULL:
843: ts = "NOT_NULL";
844: break;
845: case SqlStruct.CharSpan.TYPE_REMOVE:
846: ts = "REMOVE";
847: break;
848: default:
849: ts = "Unknown(" + s.type + ")";
850: }
851: Debug.OUT.println(" CharSpan "
852: + s.firstCharIndex
853: + " to "
854: + s.lastCharIndex
855: + " "
856: + ts
857: + " = '"
858: + cq.getSqlbuf().toString(s.firstCharIndex,
859: s.lastCharIndex - s.firstCharIndex)
860: + "'");
861: }
862: }
863: }
864: }
865:
866: public ModelMetaData getJmd() {
867: return jmd;
868: }
869:
870: /**
871: * Get the select for the candidate class. All other SqlExp's for the
872: * query are reachable from this.
873: */
874: public SelectExp getCandidateSelectExp() {
875: return candidateSelectExp;
876: }
877: }
|