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.metadata;
012:
013: import com.versant.core.metadata.parser.JdoElement;
014: import com.versant.core.metadata.parser.JdoExtension;
015: import com.versant.core.metadata.parser.JdoExtensionKeys;
016: import com.versant.core.metadata.*;
017: import com.versant.core.common.OID;
018: import com.versant.core.common.State;
019: import com.versant.core.jdbc.*;
020: import com.versant.core.jdbc.query.JdbcJDOQLCompiler;
021: import com.versant.core.jdbc.sql.exp.*;
022: import com.versant.core.server.PersistGraph;
023: import com.versant.core.server.StateContainer;
024: import com.versant.core.common.*;
025: import com.versant.core.util.CharBuf;
026: import com.versant.core.jdo.query.Node;
027: import com.versant.core.jdo.query.VarNode;
028: import com.versant.core.jdo.query.VarNodeIF;
029: import com.versant.core.common.Debug;
030:
031: import java.io.PrintStream;
032: import java.sql.Connection;
033: import java.sql.SQLException;
034: import java.sql.PreparedStatement;
035: import java.sql.ResultSet;
036:
037: import com.versant.core.common.BindingSupportImpl;
038:
039: /**
040: * A field that is a Collection or array of a PC class stored using a
041: * foreign key in the value class.
042: */
043: public class JdbcFKCollectionField extends JdbcCollectionField {
044:
045: /**
046: * This is the 'foreign key' field in the value class.
047: */
048: public JdbcRefField fkField;
049:
050: /**
051: * This is the JdbcClass for the elements.
052: */
053: private JdbcClass elementJdbcClass;
054:
055: // Size of the moving window used to calculate avgRowCount.
056: private static final int WINDOW_SIZE = 20;
057: // The initial array size is avgRowCount multiplied by this.
058: private static final float FUDGE_FACTOR = 1.5f;
059: // This is the minimum initial array size.
060: private static final int MIN_LEN = 4;
061:
062: // Total number of fetches done so far.
063: protected transient int fetchCount;
064: // Average number of rows retrieved with each fetch.
065: protected transient float avgRowCount;
066: // Total number of times the values array for a fetch has had to be
067: // expanded (i.e. we guessed the size incorrectly. This is the number
068: // of extra objects and array copies we have had to do.
069: protected transient int expansionCount;
070:
071: public void dump(PrintStream out, String indent) {
072: super .dump(out, indent);
073: String is = indent + " ";
074: out.println(is + "fkField " + fkField);
075: }
076:
077: /**
078: * Complete the meta data for this collection. This must use info
079: * already supplied in the .jdo file and add anything else needed.
080: */
081: public void processMetaData(JdoElement context,
082: JdbcMetaDataBuilder mdb, boolean quiet) {
083: ClassMetaData ecmd = fmd.elementTypeMetaData;
084: elementJdbcClass = (JdbcClass) ecmd.storeClass;
085: if (elementJdbcClass == null) {
086: throw BindingSupportImpl
087: .getInstance()
088: .runtime(
089: "The inverse extension may only be used for "
090: + "collections of PC instances stored by JDBC\n"
091: + context.getContext());
092: }
093: ClassMetaData cmd = fmd.classMetaData;
094:
095: super .processMetaData(context, mdb, quiet);
096:
097: useJoin = JdbcField.USE_JOIN_INNER;
098: if (fmd.category != MDStatics.CATEGORY_ARRAY) {
099: fmd.managed = mdb.getJdbcConfig().managedOneToMany;
100: } else {
101: fmd.managed = false;
102: }
103:
104: JdoExtension[] a;
105: if (fmd.category == MDStatics.CATEGORY_ARRAY) {
106: a = fmd.jdoArray.extensions;
107: } else if (fmd.category == MDStatics.CATEGORY_COLLECTION) {
108: a = fmd.jdoCollection.extensions;
109: } else {
110: throw BindingSupportImpl.getInstance().internal(
111: "Category '"
112: + MDStaticUtils
113: .toCategoryString(fmd.category)
114: + "' is not supported for FK Collections");
115: }
116: int len = a.length;
117: for (int i = 0; i < len; i++) {
118: JdoExtension e = a[i];
119: switch (e.key) {
120: case JdoExtensionKeys.MANAGED:
121: if (fmd.category == MDStatics.CATEGORY_ARRAY
122: && e.getBoolean()) {
123: throw BindingSupportImpl.getInstance()
124: .invalidOperation(
125: "The managed option is not supported for arrays: "
126: + fmd.name);
127: }
128: fmd.managed = e.getBoolean();
129: break;
130: case JdoExtensionKeys.INVERSE:
131: case JdoExtensionKeys.JDBC_LINK_FOREIGN_KEY:
132: String fname = e.getString();
133: FieldMetaData f = ecmd.getFieldMetaData(fname);
134: if (f == null) {
135: f = createFakeFKBackRef(cmd, ecmd, mdb, e, quiet);
136: }
137: if (f.isEmbeddedRef()) {
138: throw BindingSupportImpl
139: .getInstance()
140: .invalidOperation(
141: "an Inverse field may not be Embedded");
142: }
143: if (f.storeField == null) {
144: throw BindingSupportImpl.getInstance().runtime(
145: "Field '" + fname + "' is not persistent\n"
146: + context.getContext());
147: }
148: if (!(f.storeField instanceof JdbcRefField)) {
149: throw BindingSupportImpl.getInstance().runtime(
150: "Field '" + fname
151: + "' is not a reference\n"
152: + context.getContext());
153: }
154: fkField = (JdbcRefField) f.storeField;
155: if (!cmd.isAncestorOrSelf(fkField.targetClass)) {
156: throw BindingSupportImpl.getInstance().runtime(
157: "Field '" + fname + "' references "
158: + fkField.targetClass
159: + " and not our class\n"
160: + context.getContext());
161: }
162: fmd.ordered = false;
163: createIndex(mdb, ecmd, e.nested);
164: break;
165:
166: default:
167: if (e.isJdbc()) {
168: throw BindingSupportImpl.getInstance().runtime(
169: "Unexpected extension: " + e + "\n"
170: + e.getContext());
171: }
172: }
173: }
174: if (fkField == null) {
175: throw BindingSupportImpl.getInstance().internal(
176: "fkField is null");
177: }
178:
179: fkField.masterCollectionField = this ;
180: ourPkColumns = fkField.cols;
181: }
182:
183: private FieldMetaData createFakeFKBackRef(ClassMetaData cmd,
184: ClassMetaData ecmd, JdbcMetaDataBuilder mdb,
185: JdoExtension e, boolean quiet) {
186: fmd.managed = false;
187:
188: FieldMetaData f = new FieldMetaData();
189: f.fake = true;
190: f.typeMetaData = cmd;
191: f.name = cmd.getShortName() + "_" + fmd.name;
192: f.category = MDStatics.CATEGORY_REF;
193: f.ordered = false;
194: f.managed = false;
195: f.primaryField = true;
196: JdbcRefField jdbcRefField = new JdbcRefField();
197: jdbcRefField.targetClass = cmd;
198: fkField = jdbcRefField;
199: f.classMetaData = ecmd;
200: jdbcRefField.fmd = f;
201: f.storeField = jdbcRefField;
202: jdbcRefField.fake = true;
203: f.type = cmd.cls;
204: f.inverseFieldMetaData = fmd;
205: mdb.processRefFieldImpl(elementJdbcClass, jdbcRefField, f, e,
206: e.nested, quiet);
207:
208: // JdbcRefMetaDataBuilder rmdb = new JdbcRefMetaDataBuilder(
209: // fmd.classMetaData, mdb, cmd,
210: // cmd.jdbcClass.store, e, fname, e.nested, quiet);
211: // JdbcColumn[] cols = rmdb.getCols();
212: // jdbcRefField.cols = cols;
213: // if (cols != null) {
214: // for (int j = 0; j < cols.length; j++) {
215: // JdbcColumn col = cols[j];
216: // col.nulls = true;
217: // col.comment = "inverse FK for " + cmd.getShortName() + "-" + fmd.name;
218: // }
219: // }
220: mdb.getClassInfo(ecmd).elements.add(f.storeField);
221: return f;
222: }
223:
224: /**
225: * Create an index on our pkField unless this has been disabled.
226: */
227: private void createIndex(JdbcMetaDataBuilder mdb,
228: ClassMetaData refCmd, JdoExtension[] nested) {
229: JdbcClass refJdbcClass = (JdbcClass) refCmd.storeClass;
230: JdbcIndex idx = null;
231: boolean doNotCreateIndex = false;
232:
233: int n = nested == null ? 0 : nested.length;
234: for (int i = 0; i < n; i++) {
235: JdoExtension e = nested[i];
236: switch (e.key) {
237: case JdoExtensionKeys.JDBC_INDEX:
238: if (idx != null) {
239: throw BindingSupportImpl.getInstance().runtime(
240: "Only one jdbc-index extension is allowed here\n"
241: + e.getContext());
242: }
243: if (e.isNoValue()) {
244: doNotCreateIndex = true;
245: break;
246: }
247: idx = new JdbcIndex();
248: idx.name = e.value;
249: break;
250: case JdoExtensionKeys.JDBC_COLUMN:
251: case JdoExtensionKeys.JDBC_USE_JOIN:
252: case JdoExtensionKeys.JDBC_CONSTRAINT:
253: case JdoExtensionKeys.JDBC_REF:
254: // Handled
255: break;
256: default:
257: if (e.isJdbc()) {
258: MetaDataBuilder.throwUnexpectedExtension(e);
259: }
260: }
261: }
262:
263: if (doNotCreateIndex)
264: return;
265: if (idx == null)
266: idx = new JdbcIndex();
267: if (fkField.cols != null) {
268: idx.setCols(fkField.cols);
269: }
270:
271: // register the name of the index if one was specified otherwise one
272: // will be generated later along with user specified indexes
273: if (idx.name != null) {
274: try {
275: mdb.getNameGenerator().addIndexName(
276: refJdbcClass.table.name, idx.name);
277: } catch (IllegalArgumentException x) {
278: throw BindingSupportImpl.getInstance().runtime(
279: x.getMessage(), x);
280: }
281: }
282:
283: mdb.getClassInfo(refCmd).autoIndexes.add(idx);
284: }
285:
286: /**
287: * Persist pass 2 field for a block of graph entries all with
288: * the same class. The same ps'es can be used for all entries in the block.
289: */
290: public void persistPass2Block(PersistGraph graph, int blockStart,
291: int blockEnd, CharBuf s, Connection con,
292: boolean batchInserts, boolean batchUpdates)
293: throws SQLException {
294: // nothing to do
295: }
296:
297: /**
298: * Fetch the values for this field.
299: */
300: public int fetch(JdbcStorageManager sm, OID oid, State state,
301: FetchGroupField field, boolean forUpdate,
302: StateContainer container, boolean fetchPass2Fields,
303: ColFieldHolder colFHolder) throws SQLException {
304:
305: String sql = forUpdate ? field.jdbcSelectSqlForUpdate
306: : field.jdbcSelectSql;
307: final boolean joined = field.jdbcUseJoin != JdbcField.USE_JOIN_NO;
308:
309: FetchGroup nextFetchGroup = field.nextFetchGroup;
310: FgDs[] fgDses = new FgDs[1];
311:
312: if (sql == null) {
313: SelectExp se = getSelectExp(field, sm, fgDses);
314: CharBuf s = sm.generateSql(se);
315: sql = s.toString();
316: if (forUpdate) {
317: field.jdbcSelectSqlForUpdate = sql;
318: } else {
319: field.jdbcSelectSql = sql;
320: }
321: } else {
322: fgDses[0] = ((JdbcFetchGroup) nextFetchGroup.storeFetchGroup)
323: .getExistingFgDs(true, false);
324: }
325:
326: if (colFHolder != null && fgDses[0] != null) {
327: colFHolder.valueJs = fgDses[0].getJoinStruct();
328: }
329:
330: PreparedStatement ps = null;
331: ResultSet rs = null;
332: Struct s = new Struct();
333: try {
334: ps = sm.con().prepareStatement(sql);
335: ((JdbcOID) oid).setParams(ps, 1);
336: try {
337: rs = ps.executeQuery();
338: } catch (Exception e) {
339: throw mapException(e,
340: "Fetch inverse foreign key collection failed: "
341: + JdbcUtils.toString(e)
342: + "\n"
343: + "Field: "
344: + fmd.getTypeQName()
345: + "\n"
346: + "Instance: "
347: + oid.toSString()
348: + "\n"
349: + JdbcUtils.getPreparedStatementInfo(
350: sql, ps));
351: }
352:
353: s.init();
354:
355: ClassMetaData valueCmd = fmd.elementTypeMetaData;
356:
357: int valuePkLen = 0;
358: if (joined)
359: valuePkLen = elementJdbcClass.table.pkSimpleColumnCount;
360: for (; rs.next();) {
361: OID valueOid = valueCmd.createOID(false);
362: boolean isNull = !((JdbcOID) valueOid).copyKeyFields(
363: rs, 1);
364: if (isNull) {
365: s.add(null);
366: } else {
367: s.add(valueOid);
368: }
369:
370: if (!isNull
371: && joined
372: && container.isStateRequired(valueOid,
373: nextFetchGroup)) {
374: State valueState = sm.createStateImp(rs, valueOid,
375: nextFetchGroup, forUpdate, 1 + valuePkLen,
376: null, true, container, fgDses[0],
377: fetchPass2Fields, false, null);
378: container.addState(valueOid, valueState);
379: }
380: }
381:
382: // this can come out when the bugs are removed from the rest
383: // of the code
384: updateState(s, state);
385: updateStats(s.size);
386: } finally {
387: cleanup(rs);
388: cleanup(ps);
389: }
390: return s.size;
391: }
392:
393: public int fetchFrom(ResultSet rs, OID oid, State state,
394: FetchGroupField field, boolean forUpdate,
395: StateContainer container, boolean fetchPass2Fields,
396: int colIndex, FetchInfo fetchInfo, JdbcStorageManager sm)
397: throws SQLException {
398:
399: Struct s = new Struct();
400: final boolean joined = field.jdbcUseJoin != JdbcField.USE_JOIN_NO;
401: boolean first = true;
402: s.init();
403: ClassMetaData valueCmd = fmd.elementTypeMetaData;
404:
405: FetchGroup nextFetchGroup = field.nextFetchGroup;
406:
407: int valuePkLen = 0;
408: if (joined)
409: valuePkLen = elementJdbcClass.table.pkSimpleColumnCount;
410: for (;;) {
411: boolean mustBreak = false;
412: if (first) {
413: first = false;
414: mustBreak = updateForFirstRow(fetchInfo, mustBreak, rs,
415: colIndex, oid);
416: } else {
417: if (rs.next()) {
418: mustBreak = checkKeyOid(rs, colIndex, fetchInfo,
419: mustBreak, oid);
420: fetchInfo.onNextRow = true;
421: } else {
422: fetchInfo.onNextRow = false;
423: fetchInfo.finished = true;
424: mustBreak = true;
425: }
426: }
427: if (mustBreak)
428: break;
429:
430: OID valueOid = valueCmd.createOID(false);
431: boolean isNull = !((JdbcOID) valueOid).copyKeyFields(rs,
432: colIndex + ourPkColumns.length);
433: if (!isNull) {
434: s.add(valueOid);
435: if (joined
436: && container.isStateRequired(valueOid,
437: nextFetchGroup)) {
438: State valueState = sm
439: .createStateImp(
440: rs,
441: valueOid,
442: nextFetchGroup,
443: forUpdate,
444: colIndex + ourPkColumns.length
445: + valuePkLen,
446: null,
447: true,
448: container,
449: ((JdbcFetchGroup) nextFetchGroup.storeFetchGroup)
450: .getExistingFgDs(true, true),
451: fetchPass2Fields, false, null);
452: container.addState(valueOid, valueState);
453: }
454: } else {
455: //no elements
456: break;
457: }
458: }
459:
460: updateState(s, state);
461: updateStats(s.size);
462: return s.size;
463: }
464:
465: private void updateStats(int size) {
466: // Update statistics. This is not thread safe but it is not a
467: // problem if the avgRowCount is a bit out sometimes.
468: int fc = ++fetchCount;
469: if (fc > WINDOW_SIZE) {
470: fc = WINDOW_SIZE;
471: } else if (fc == 1) {
472: avgRowCount = size;
473: } else if (fc < 0) {
474: fc = fetchCount = WINDOW_SIZE;
475: } else {
476: avgRowCount = (avgRowCount * (fc - 1) + size) / fc;
477: }
478: }
479:
480: public int fetchWithFilter(JdbcStorageManager sm,
481: StateContainer oidStates, FetchGroupField field,
482: ResultSet rs, boolean forUpdate, OID oidToCheckOn,
483: OID[] lastReadStateOID, ClassMetaData cmd,
484: ColFieldHolder colFHolder) throws SQLException {
485:
486: ClassMetaData valueCmd = fmd.elementTypeMetaData;
487: final boolean joined = field.jdbcUseJoin != JdbcField.USE_JOIN_NO;
488: FetchGroup nextFetchGroup = field.nextFetchGroup;
489:
490: if (colFHolder != null) {
491: colFHolder.valueJs = new JoinStructure(nextFetchGroup);
492: }
493:
494: int valuePkLen = 0;
495: if (joined)
496: valuePkLen = elementJdbcClass.table.pkSimpleColumnCount;
497:
498: int rootOIDLenght = ((JdbcClass) cmd.storeClass).table.pkSimpleColumnCount;
499: int stateOIDPKLen = ((JdbcClass) fmd.classMetaData.storeClass).table.pkSimpleColumnCount;
500:
501: //the oid just read from the rs row
502: OID rootOid = cmd.createOID(false);
503: //the oid read from the previous rs row
504: OID prevRootOid = cmd.createOID(false);
505: OID tmpOID = null;
506:
507: OID stateOID = fmd.classMetaData.createOID(false);
508: OID prevStateOID = fmd.classMetaData.createOID(false);
509: OID tmpStateOID = null;
510:
511: Struct s = new Struct();
512: s.init();
513:
514: boolean currentRowValid = false;
515: boolean prevRowValid = false;
516: int returnState = 0;
517:
518: FgDs fgDs = ((JdbcFetchGroup) nextFetchGroup.storeFetchGroup)
519: .getExistingFgDs(true,
520: field.jdbcUseJoin == JdbcField.USE_JOIN_OUTER);
521: if (colFHolder != null) {
522: colFHolder.valueJs = fgDs.getJoinStruct();
523: }
524:
525: //This oid was read previously so we have to read the rest of the row now.
526: if (lastReadStateOID[0] != null) {
527: int index = 1;
528:
529: prevRootOid = lastReadStateOID[0];
530: index += rootOIDLenght;
531:
532: stateOID = lastReadStateOID[1];
533: index += stateOIDPKLen;
534:
535: currentRowValid = oidStates.containsKey(stateOID);
536:
537: if (currentRowValid) {
538: returnState |= STATUS_VALID_ROWS;
539: OID valueOid = valueCmd.createOID(false);
540: boolean isNull = !((JdbcOID) valueOid).copyKeyFields(
541: rs, index);
542: index += valuePkLen;
543:
544: if (!isNull) {
545: s.add(valueOid);
546: } else {
547: s.add(null);
548: }
549:
550: if (!isNull
551: && joined
552: && oidStates.isStateRequired(valueOid,
553: nextFetchGroup)) {
554:
555: State valueState = sm.createStateImp(rs, valueOid,
556: nextFetchGroup, forUpdate, index, null,
557: true, oidStates, fgDs, false, false, null);
558: oidStates.addState(valueOid, valueState);
559: }
560: }
561:
562: //preserve the prevRootOid
563: tmpOID = rootOid;
564: rootOid = prevRootOid;
565: prevRootOid = tmpOID;
566:
567: //preserve the prevStateOID
568: tmpStateOID = stateOID;
569: stateOID = prevStateOID;
570: prevStateOID = tmpStateOID;
571:
572: prevRowValid = currentRowValid;
573: }
574:
575: //go through the rs until we reach the end or oidToCheckOn
576: for (; rs.next();) {
577: int index = 1;
578: ((JdbcOID) rootOid).copyKeyFields(rs, index);
579: index += rootOIDLenght;
580:
581: ((JdbcOID) stateOID).copyKeyFields(rs, index);
582: index += stateOIDPKLen;
583:
584: currentRowValid = oidStates.containsKey(stateOID);
585:
586: //detected a change in stateOid. Only update if the previous
587: // row was for a valid oid.
588: if (!stateOID.equals(prevStateOID) && prevRowValid) {
589: if (updateStateFilter(s, oidStates.get(prevStateOID))) {
590: returnState |= STATUS_DATA_ADDED;
591: }
592: updateStatistics(s.size);
593: //if lastRootOid is the oid to check on then return do no process further.
594: if (oidToCheckOn.equals(prevRootOid)
595: && !oidToCheckOn.equals(rootOid)) {
596: lastReadStateOID[0] = rootOid;
597: lastReadStateOID[1] = stateOID;
598: returnState |= STATUS_VALID_ROWS;
599: return returnState;
600: }
601: //reset the struct for next usage.
602: s.init();
603: }
604:
605: if (currentRowValid) {
606: returnState |= STATUS_VALID_ROWS;
607: OID valueOid = valueCmd.createOID(false);
608: boolean isNull = !((JdbcOID) valueOid).copyKeyFields(
609: rs, index);
610: if (!isNull) {
611: s.add(valueOid);
612: } else {
613: s.add(null);
614: }
615:
616: index += valuePkLen;
617: if (!isNull
618: && joined
619: && oidStates.isStateRequired(valueOid,
620: nextFetchGroup)) {
621: State valueState = sm
622: .createStateImp(
623: rs,
624: valueOid,
625: nextFetchGroup,
626: forUpdate,
627: index,
628: null,
629: true,
630: oidStates,
631: ((JdbcFetchGroup) nextFetchGroup.storeFetchGroup)
632: .getFgDs(
633: true,
634: field.jdbcUseJoin == JdbcField.USE_JOIN_OUTER),
635: false, false, null);
636: oidStates.addState(valueOid, valueState);
637: }
638: }
639:
640: //preserve the prevRootOid
641: tmpOID = rootOid;
642: rootOid = prevRootOid;
643: prevRootOid = tmpOID;
644:
645: //preserve the prevStateOID
646: tmpStateOID = stateOID;
647: stateOID = prevStateOID;
648: prevStateOID = tmpStateOID;
649:
650: prevRowValid = currentRowValid;
651: }
652: rs.close();
653: if ((returnState & STATUS_VALID_ROWS) == STATUS_VALID_ROWS) {
654: if (updateStateFilter(s, oidStates.get(prevStateOID))) {
655: returnState |= STATUS_DATA_ADDED;
656: }
657: }
658: returnState |= STATUS_CLOSED;
659: return returnState;
660: }
661:
662: public void fillStateWithEmpty(FetchGroupField field, State state) {
663: if (!state.containsField(fmd.stateFieldNo)) {
664: state.setInternalObjectField(fmd.stateFieldNo,
665: PRE_GEN_EMPTY_OBJECT_ARRAY);
666: }
667: }
668:
669: private boolean updateState(Struct s, State state) {
670: if (state == null)
671: return false;
672: if (s.values == null)
673: s.values = EMPTY_OID_ARRAY;
674: s.trim();
675: state.setInternalObjectField(fmd.stateFieldNo, s.values);
676: return true;
677: }
678:
679: private boolean updateStateFilter(Struct s, State state) {
680: if (state == null)
681: return false;
682: if (s.values == null)
683: s.values = EMPTY_OID_ARRAY;
684: if (state.getInternalObjectField(fmd.stateFieldNo) == PRE_GEN_EMPTY_OBJECT_ARRAY) {
685: s.trim();
686: state.setInternalObjectField(fmd.stateFieldNo, s.values);
687: return true;
688: }
689: return false;
690: }
691:
692: public void appendOrderExpForFilterExp(SelectExp se, SelectExp root) {
693: if (fmd.elementTypeMetaData != null) {
694: root
695: .appendOrderByForColumns(
696: ((JdbcClass) fmd.elementTypeMetaData.storeClass).table.pk,
697: se);
698: }
699: }
700:
701: private class Struct {
702:
703: public int len;
704: public OID[] values;
705: public int size;
706:
707: public OID prevRootOID;
708: public OID currentRootOID;
709:
710: public OID prevStateOID;
711: public OID currentStateOID;
712:
713: public void init() {
714: len = (int) (avgRowCount * FUDGE_FACTOR);
715: if (len < MIN_LEN)
716: len = 0;
717: values = len == 0 ? null : new OID[len];
718: if (Debug.DEBUG) {
719: if (((fetchCount + 1) % 10) == 0) {
720: System.out.println("JdbcFkCollectionField.fetch"
721: + " avgRowCount = " + avgRowCount + " "
722: + " len = " + len + " "
723: + " expansionCount = " + expansionCount
724: + " " + " fetchCount = " + fetchCount);
725: }
726: }
727: size = 0;
728: }
729:
730: private void add(OID value) {
731: //grow if nec.
732: if (size == len) {
733: if (len == 0) {
734: values = new OID[len = MIN_LEN];
735: } else {
736: len = len * 3 / 2 + 1;
737: OID[] a = new OID[len];
738: System.arraycopy(values, 0, a, 0, size);
739: values = a;
740: expansionCount++;
741: }
742: }
743: values[size++] = value;
744: }
745:
746: /**
747: * Trim values down to size elements.
748: */
749: public void trim() {
750: if (values.length == size)
751: return;
752: OID[] a = new OID[size];
753: System.arraycopy(values, 0, a, 0, size);
754: values = a;
755: }
756: }
757:
758: /**
759: * Update statistics. This is not thread safe but it is not a
760: * problem if the avgRowCount is a bit out sometimes.
761: */
762: private void updateStatistics(int size) {
763: int fc = ++fetchCount;
764: if (fc > WINDOW_SIZE) {
765: fc = WINDOW_SIZE;
766: } else if (fc == 1) {
767: avgRowCount = size;
768: } else if (fc < 0) {
769: fc = fetchCount = WINDOW_SIZE;
770: } else {
771: avgRowCount = (avgRowCount * (fc - 1) + size) / fc;
772: }
773: }
774:
775: /**
776: * Get a SelectExp to select all the rows in this collection using the
777: * supplied fetch group field to control joins and so on.
778: */
779: private SelectExp getSelectExp(FetchGroupField field,
780: JdbcStorageManager sm, FgDs[] fgDses) {
781: SelectExp root = new SelectExp();
782: root.table = elementJdbcClass.table;
783: root.selectList = JdbcColumn.toSqlExp(root.table.pk, root);
784:
785: if (field.jdbcUseJoin != JdbcField.USE_JOIN_NO) {
786: sm
787: .addSelectFetchGroup(
788: root,
789: field.nextFetchGroup,
790: true,
791: fgDses[0] = ((JdbcFetchGroup) field.nextFetchGroup.storeFetchGroup)
792: .getFgDs(true, false), root,
793: root.table.pk, this );
794: }
795:
796: // add ourPkColumns to the where clause list creating a new
797: // join to the base table if necessary
798: SelectExp se = root.findTable(ourPkColumns[0].table);
799: if (se == null) {
800: se = new SelectExp();
801: se.table = ourPkColumns[0].table;
802: root.addJoin(root.table.pk, se.table.pk, se);
803: }
804:
805: // put the expression at the start of the where clause list
806: root.whereExp = JdbcColumn.createEqualsParamExp(ourPkColumns,
807: se);
808: if (((JdbcClass) fmd.elementTypeMetaData.storeClass).classIdCol != null) {
809: root.whereExp.next = ((JdbcClass) fmd.elementTypeMetaData.storeClass)
810: .getCheckClassIdExp(root);
811: AndExp andExp = new AndExp(root.whereExp);
812: root.whereExp = andExp;
813: }
814:
815: // add order by if ordering extension has been used
816: if (fmd.ordering != null) {
817: root.addOrderBy(fmd.ordering, false);
818: }
819:
820: if (Debug.DEBUG) {
821: System.out
822: .println("%%% JdbcFKCollectionField.getSelectExp: "
823: + fmd.getQName());
824: root.dump(" ");
825: System.out.println("%%%");
826: }
827:
828: return root;
829: }
830:
831: /**
832: * Get a SelectExp to select all the rows in this collection using the
833: * supplied fetch group field to control joins and so on.
834: */
835: public SelectExp getSelectExpFrom(JdbcStorageManager sm,
836: SelectExp joinToExp, FetchGroupField field, FgDs owningFgDs) {
837: SelectExp root = new SelectExp();
838: root.outer = true;
839: root.table = elementJdbcClass.table;
840: root.selectList = JdbcColumn.toSqlExp(ourPkColumns, root,
841: JdbcColumn.toSqlExp(root.table.pk, root));
842:
843: if (field.jdbcUseJoin != JdbcField.USE_JOIN_NO) {
844: FgDs fgDs = ((JdbcFetchGroup) field.nextFetchGroup.storeFetchGroup)
845: .getFgDs(true, true);
846: sm.addSelectFetchGroup(root, field.nextFetchGroup, true,
847: fgDs, root, root.table.pk, this );
848: owningFgDs.valueJs = fgDs.getJoinStruct();
849: }
850:
851: // add order by if ordering extension has been used
852: if (fmd.ordering != null) {
853: root.addOrderBy(fmd.ordering, false);
854: }
855:
856: if (Debug.DEBUG) {
857: System.out
858: .println("%%% JdbcFKCollectionField.getSelectExp: "
859: + fmd.getQName());
860: root.dump(" ");
861: System.out.println("%%%");
862: }
863:
864: joinToExp.addJoin(joinToExp.table.pk, ourPkColumns, root);
865: joinToExp.appendOrderByExp(root.orderByList);
866: root.orderByList = null;
867: return root;
868: }
869:
870: public SelectExp getSelectFilterExp(JdbcStorageManager sm,
871: FetchGroupField field, ColFieldHolder colFHolder) {
872: SelectExp root = new SelectExp();
873: root.table = elementJdbcClass.table;
874: root.selectList = JdbcColumn.toSqlExp(root.table.pk, root);
875:
876: root.whereExp = ((JdbcClass) fmd.elementTypeMetaData.storeClass)
877: .getCheckClassIdExp(root);
878:
879: // prepend our pk columns to the select list
880: SqlExp e = JdbcColumn.toSqlExp(ourPkColumns, root,
881: root.selectList);
882: root.selectList = e;
883: root.appendOrderByForColumns(ourPkColumns);
884:
885: if (field.jdbcUseJoin != JdbcField.USE_JOIN_NO) {
886: FgDs fgDs = ((JdbcFetchGroup) field.nextFetchGroup.storeFetchGroup)
887: .getFgDs(
888: true,
889: field.jdbcUseJoin == JdbcField.USE_JOIN_OUTER);
890: colFHolder.valueJs = fgDs.getJoinStruct();
891: sm.addSelectFetchGroup(root, field.nextFetchGroup, true,
892: fgDs, false);
893: }
894:
895: // add order by if ordering extension has been used
896: if (fmd.ordering != null) {
897: root.addOrderBy(fmd.ordering, true);
898: }
899:
900: return root;
901: }
902:
903: public SelectExp getSelectFilterJoinExp(boolean value,
904: SelectExp lhSe, SelectExp rootSe, boolean addRootJoin) {
905: SelectExp root = new SelectExp();
906: root.table = elementJdbcClass.table;
907: lhSe.addJoin(lhSe.table.pk, ourPkColumns, root);
908: return root;
909: }
910:
911: /**
912: * Convert this field into an isEmpty expression.
913: */
914: public SqlExp toIsEmptySqlExp(JdbcJDOQLCompiler comp, SelectExp root) {
915: SelectExp se = new SelectExp();
916: se.table = elementJdbcClass.table;
917: se.jdbcField = this ;
918: se.subSelectJoinExp = root.createJoinExp(root.table.pk,
919: ourPkColumns, se);
920: // @todo what about empty/null checking?
921: return new UnaryOpExp(new ExistsExp(se, true),
922: UnaryOpExp.OP_NOT);
923: }
924:
925: /**
926: * Convert this field into a contains expression.
927: */
928: public SqlExp toContainsSqlExp(JdbcJDOQLCompiler comp,
929: SelectExp root, Node args) {
930: if (args instanceof VarNodeIF) {
931: VarNode v = ((VarNodeIF) args).getVarNode();
932: SelectExp vse = (SelectExp) v.getStoreExtent();
933:
934: //same table
935: if (vse.table == ourPkColumns[0].table) {
936: vse.subSelectJoinExp = root.createJoinExp(
937: root.table.pk, ourPkColumns, vse);
938: } else {
939: SelectExp se = new SelectExp();
940: se.table = ourPkColumns[0].table;
941: se.outer = vse.outer;
942: vse.addJoin(vse.table.pk, ourPkColumns[0].table.pk, se);
943: }
944:
945: if (v.getCmd() != fmd.elementTypeMetaData) {
946: //should be a subclass
947: vse.whereExp = SelectExp
948: .appendWithAnd(
949: vse.whereExp,
950: ((JdbcClass) fmd.elementTypeMetaData.storeClass)
951: .getCheckClassIdExp(vse));
952: }
953:
954: return new ExistsExp(vse, true, v);
955: } else {
956: SelectExp se = new SelectExp();
957: se.table = elementJdbcClass.table;
958: se.jdbcField = this ;
959: se.subSelectJoinExp = root.createJoinExp(root.table.pk,
960: ourPkColumns, se);
961: SqlExp left = JdbcColumn
962: .toSqlExp(se.table.pkSimpleCols, se);
963: for (SqlExp e = left; e != null; e = e.next) {
964: ((ColumnExp) e).cmd = fmd.elementTypeMetaData;
965: }
966: SqlExp right = comp.getVisitor().toSqlExp(args, root, left,
967: 0, null);
968: if (left.next == null && right.next == null) {
969: BinaryOpExp ans = new BinaryOpExp(left,
970: BinaryOpExp.EQUAL, right);
971: if (right instanceof ParamExp) {
972: ParamExp p = (ParamExp) right;
973: p.usage.expList = ans;
974: p.usage.expCount = 1;
975: }
976: se.whereExp = ans;
977: } else {
978: throw BindingSupportImpl.getInstance().internal(
979: "not implemented");
980: }
981: return new ExistsExp(se, false);
982: }
983: }
984:
985: }
|