0001: /*
0002: * Copyright (c) 1998 - 2005 Versant Corporation
0003: * All rights reserved. This program and the accompanying materials
0004: * are made available under the terms of the Eclipse Public License v1.0
0005: * which accompanies this distribution, and is available at
0006: * http://www.eclipse.org/legal/epl-v10.html
0007: *
0008: * Contributors:
0009: * Versant Corporation - initial API and implementation
0010: */
0011: package com.versant.core.jdbc.metadata;
0012:
0013: import com.versant.core.common.Debug;
0014: import com.versant.core.common.*;
0015: import com.versant.core.metadata.*;
0016: import com.versant.core.metadata.parser.JdoElement;
0017: import com.versant.core.metadata.parser.JdoExtension;
0018: import com.versant.core.metadata.parser.JdoExtensionKeys;
0019: import com.versant.core.jdo.query.*;
0020: import com.versant.core.server.StateContainer;
0021: import com.versant.core.server.PersistGraph;
0022: import com.versant.core.jdbc.*;
0023: import com.versant.core.jdbc.query.JdbcJDOQLCompiler;
0024: import com.versant.core.jdbc.sql.JdbcNameGenerator;
0025: import com.versant.core.jdbc.sql.SqlDriver;
0026: import com.versant.core.jdbc.sql.exp.*;
0027: import com.versant.core.util.CharBuf;
0028:
0029: import java.io.PrintStream;
0030: import java.sql.Connection;
0031: import java.sql.PreparedStatement;
0032: import java.sql.ResultSet;
0033: import java.sql.SQLException;
0034: import java.util.ArrayList;
0035: import java.util.HashSet;
0036: import java.util.List;
0037: import java.lang.reflect.Array;
0038:
0039: /**
0040: * A field that is a Collection, Map or array stored in a link table.
0041: */
0042: public class JdbcLinkCollectionField extends JdbcCollectionField {
0043:
0044: /**
0045: * If this field is in a many-to-many relationship then this is the
0046: * other side.
0047: */
0048: public JdbcLinkCollectionField inverse;
0049: /**
0050: * Is this the read-only half of a many-to-many?
0051: */
0052: public boolean readOnly;
0053: /**
0054: * The link table.
0055: */
0056: public JdbcTable linkTable;
0057: /**
0058: * The column(s) holding the values. This array will have length 1
0059: * unless the values are of a PC class with a composite primary key.
0060: */
0061: public JdbcColumn[] valueColumns;
0062: /**
0063: * Are the values OID's?
0064: */
0065: public boolean valuesAreOIDs;
0066:
0067: private transient boolean createValueConstraint;
0068: private transient String valueConstraintName;
0069: private transient boolean doNotCreateTable;
0070:
0071: // Cache for SQL to delete a value from the collection.
0072: private transient String deleteRowSql;
0073: // Cache for SQL to delete all values from the collection.
0074: private transient String deleteAllRowsSql;
0075: // Cache for SQL to insert a value into the collection.
0076: private transient String insertRowSql;
0077:
0078: // Size of the moving window used to calculate avgRowCount.
0079: private static final int WINDOW_SIZE = 20;
0080: // The initial array size is avgRowCount multiplied by this.
0081: private static final float FUDGE_FACTOR = 1.5f;
0082: // This is the minimum initial array size.
0083: private static final int MIN_LEN = 4;
0084:
0085: // Total number of fetches done so far.
0086: protected transient int fetchCount;
0087: // Average number of rows retrieved with each fetch.
0088: protected transient float avgRowCount;
0089: // Total number of times the values array for a fetch has had to be
0090: // expanded (i.e. we guessed the size incorrectly. This is the number
0091: // of extra objects and array copies we have had to do.
0092: protected transient int expansionCount;
0093:
0094: /**
0095: * Add all tables that belong to this field to the set.
0096: */
0097: public void getTables(HashSet tables) {
0098: if (!readOnly && !doNotCreateTable && linkTable != null) {
0099: tables.add(linkTable);
0100: }
0101: }
0102:
0103: public void dump(PrintStream out, String indent) {
0104: super .dump(out, indent);
0105: String is = indent + " ";
0106: out.println(is + "inverse " + inverse);
0107: out.println(is + "readOnly " + readOnly);
0108: out.println(is + "valuesAreOIDs " + valuesAreOIDs);
0109: out.println(is + "linkTable " + linkTable);
0110: if (valueColumns == null) {
0111: out.println(is + "valueColumns null");
0112: } else {
0113: for (int i = 0; i < valueColumns.length; i++) {
0114: out.println(is + "valueColumns[" + i + "] "
0115: + valueColumns[i]);
0116: }
0117: }
0118: }
0119:
0120: /**
0121: * Complete the meta data for this collection. This must use info
0122: * already supplied in the .jdo file and add anything else needed.
0123: */
0124: public void processMetaData(JdoElement context,
0125: JdbcMetaDataBuilder mdb, boolean quiet) {
0126: super .processMetaData(context, mdb, quiet);
0127:
0128: JdoExtension[] extensions = getExtensions();
0129:
0130: // see if this is the inverse side of a many-to-many
0131: JdoExtension ext = JdoExtension.find(JdoExtensionKeys.INVERSE,
0132: extensions);
0133: if (ext != null) {
0134: processInverse(mdb, ext);
0135: return;
0136: }
0137:
0138: valuesAreOIDs = fmd.elementTypeMetaData != null;
0139: if (valuesAreOIDs) {
0140: if (fmd.ordered || allowNulls()) {
0141: useJoin = JdbcField.USE_JOIN_OUTER;
0142: } else {
0143: useJoin = JdbcField.USE_JOIN_INNER;
0144: }
0145: } else {
0146: useJoin = JdbcField.USE_JOIN_NO;
0147: }
0148:
0149: ext = JdoExtension.find(JdoExtensionKeys.MANAGED, extensions);
0150: if (ext != null) {
0151: if (fmd.category == MDStatics.CATEGORY_ARRAY
0152: && ext.getBoolean()) {
0153: throw BindingSupportImpl.getInstance()
0154: .invalidOperation(
0155: "The managed option is not supported for arrays: "
0156: + fmd.name);
0157: }
0158: fmd.managed = ext.getBoolean();
0159: } else {
0160: if (fmd.category != MDStatics.CATEGORY_ARRAY) {
0161: fmd.managed = mdb.getJdbcConfig().managedManyToMany;
0162: } else {
0163: fmd.managed = false;
0164: }
0165: }
0166:
0167: ClassMetaData cmd = fmd.classMetaData;
0168: JdbcClass jdbcClass = (JdbcClass) cmd.storeClass;
0169: ArrayList cols = new ArrayList();
0170: JdoExtension link = JdoExtension.find(
0171: JdoExtensionKeys.JDBC_LINK_TABLE, extensions);
0172: JdoExtension[] linkNested = link == null ? null : link.nested;
0173:
0174: // create our pk columns
0175: ext = JdoExtension.find(JdoExtensionKeys.JDBC_OWNER_REF,
0176: linkNested);
0177: JdbcRefMetaDataBuilder rdb = new JdbcRefMetaDataBuilder(cmd,
0178: mdb, fmd.classMetaData, context,
0179: JdbcMetaDataBuilder.OWNER_REF_FIELDNAME,
0180: ext == null ? null : ext.nested, quiet);
0181: ourPkColumns = rdb.getCols();
0182: cols.addAll(rdb.getColsList());
0183: boolean pkConstraint = !rdb.isDoNotCreateConstraint();
0184: String pkConstraintName = rdb.getConstraintName();
0185:
0186: // create the sequence column if required
0187: if (fmd.ordered) {
0188: ext = JdoExtension.find(JdoExtensionKeys.JDBC_SEQUENCE,
0189: linkNested);
0190: sequenceColumn = mdb.createColumn(ext == null ? null
0191: : ext.nested,
0192: JdbcMetaDataBuilder.SEQUENCE_FIELDNAME,
0193: Integer.TYPE);
0194: cols.add(sequenceColumn);
0195: }
0196:
0197: completeKeyAndValueColumnMetaData(jdbcClass, cols, context,
0198: linkNested, mdb, quiet);
0199:
0200: // create the link table
0201: linkTable = new JdbcTable();
0202: linkTable.comment = fmd.getTypeQName();
0203: createLinkTablePK();
0204: linkTable.sqlDriver = mdb.getSqlDriver();
0205: linkTable.cols = new JdbcColumn[cols.size()];
0206: cols.toArray(linkTable.cols);
0207: linkTable.setTableOnCols();
0208:
0209: // Create a constraint for the main table pk columns and for the value
0210: // (and key for map) columns if the value (or key) is an OID
0211: List constraints = createConstraints(pkConstraint,
0212: pkConstraintName);
0213: linkTable.constraints = new JdbcConstraint[constraints.size()];
0214: constraints.toArray(linkTable.constraints);
0215:
0216: // see if the link table must be left out of the schema or not
0217: ext = JdoExtension.find(
0218: JdoExtensionKeys.JDBC_DO_NOT_CREATE_TABLE, linkNested);
0219: doNotCreateTable = ext != null && ext.getBoolean();
0220: String linkTableNameForNamegen = null;
0221:
0222: // Make sure the link table has a name. A dummy name is used for
0223: // name generation purposes if the table is being left out of the
0224: // schema and there is already a table with the same name so that
0225: // any generated column names will match those of the table included
0226: // in the schema (if any).
0227: JdbcNameGenerator namegen = mdb.getNameGenerator();
0228: ext = JdoExtension.find(JdoExtensionKeys.JDBC_TABLE_NAME,
0229: linkNested);
0230: if (ext == null) {
0231: nameLinkTable(namegen, jdbcClass);
0232: } else {
0233: try {
0234: namegen.addTableName(linkTable.name = ext.getString());
0235: } catch (IllegalArgumentException e) {
0236: if (!doNotCreateTable) {
0237: throw BindingSupportImpl.getInstance().runtime(
0238: e.getMessage() + "\n"
0239: + context.getContext(), e);
0240: }
0241: // Duplicate names are ok if the table is not being created.
0242: // Use a dummy name so generated column names are the same as
0243: // the original. This dummy name is removed at the end of this
0244: // method.
0245: linkTableNameForNamegen = linkTable.name
0246: + System.currentTimeMillis();
0247: namegen.addTableName(linkTableNameForNamegen);
0248: }
0249: }
0250: String linkTableName = linkTable.name;
0251: if (linkTableNameForNamegen == null)
0252: linkTableNameForNamegen = linkTableName;
0253:
0254: // name its pk constraint
0255: if (!doNotCreateTable) {
0256: if (linkTable.pkConstraintName == null) {
0257: linkTable.pkConstraintName = namegen
0258: .generatePkConstraintName(linkTableName);
0259: } else {
0260: namegen.addPkConstraintName(linkTableName,
0261: linkTable.pkConstraintName);
0262: }
0263: }
0264:
0265: // register the names of all columns that already have names
0266: for (int i = 0; i < linkTable.cols.length; i++) {
0267: try {
0268: linkTable.cols[i].addColumnNames(
0269: linkTableNameForNamegen, namegen);
0270: } catch (IllegalArgumentException e) {
0271: if (!doNotCreateTable) {
0272: throw BindingSupportImpl.getInstance().runtime(
0273: e.getMessage() + "\n"
0274: + context.getContext(), e);
0275: }
0276: // duplicates are ok if table is not being created
0277: }
0278: }
0279:
0280: // name our pk columns
0281: String[] pkNames = jdbcClass.table.getPkNames();
0282: String[] ourPkNames = JdbcColumn.getColumnNames(ourPkColumns);
0283: namegen.generateLinkTableMainRefNames(linkTableNameForNamegen,
0284: pkNames, ourPkNames);
0285: JdbcColumn.setColumnNames(ourPkColumns, ourPkNames);
0286:
0287: // name the sequence column (if any)
0288: if (sequenceColumn != null && sequenceColumn.name == null) {
0289: sequenceColumn.name = namegen
0290: .generateLinkTableSequenceName(linkTableNameForNamegen);
0291: }
0292:
0293: // name the key and value columns
0294: nameKeyAndValueColumns(namegen, linkTableNameForNamegen);
0295:
0296: // name all the constraints
0297: if (!doNotCreateTable) {
0298: for (int i = 0; i < linkTable.constraints.length; i++) {
0299: JdbcConstraint c = linkTable.constraints[i];
0300: if (c.name != null) {
0301: namegen.addRefConstraintName(linkTableName, c.name);
0302: } else {
0303: String[] fkNames = JdbcColumn
0304: .getColumnNames(c.srcCols);
0305: String[] refPkNames = JdbcColumn
0306: .getColumnNames(c.dest.pk);
0307: c.name = namegen.generateRefConstraintName(
0308: linkTableName, c.dest.name, fkNames,
0309: refPkNames);
0310: }
0311: }
0312: }
0313:
0314: // remove the table if it is not going to be in the schema so other
0315: // fields can be mapped to the same name and get the same generated
0316: // column names
0317: if (doNotCreateTable)
0318: namegen.removeTableName(linkTableNameForNamegen);
0319:
0320: // sycn with our inverse (if any)
0321: syncWithInverse(mdb);
0322: }
0323:
0324: /**
0325: * Create all the constraints for our link table.
0326: */
0327: protected List createConstraints(boolean pkConstraint,
0328: String pkConstraintName) {
0329: ArrayList constraints = new ArrayList();
0330:
0331: if (pkConstraint) {
0332: JdbcConstraint mainCon = new JdbcConstraint();
0333: mainCon.src = linkTable;
0334: mainCon.srcCols = ourPkColumns;
0335: mainCon.dest = ((JdbcClass) fmd.classMetaData.storeClass).table;
0336: mainCon.name = pkConstraintName;
0337: constraints.add(mainCon);
0338: }
0339:
0340: if (createValueConstraint && valuesAreOIDs
0341: && fmd.elementTypeMetaData.storeClass != null) {
0342: JdbcConstraint valueCon = new JdbcConstraint();
0343: valueCon.src = linkTable;
0344: valueCon.srcCols = valueColumns;
0345: valueCon.dest = ((JdbcClass) fmd.elementTypeMetaData.storeClass).table;
0346: valueCon.name = valueConstraintName;
0347: constraints.add(valueCon);
0348: }
0349:
0350: return constraints;
0351: }
0352:
0353: /**
0354: * Get our extensions or null if none.
0355: */
0356: private JdoExtension[] getExtensions() {
0357: switch (fmd.category) {
0358: case MDStatics.CATEGORY_ARRAY:
0359: if (fmd.jdoArray != null)
0360: return fmd.jdoArray.extensions;
0361: return null;
0362: case MDStatics.CATEGORY_COLLECTION:
0363: if (fmd.jdoCollection != null)
0364: return fmd.jdoCollection.extensions;
0365: return null;
0366: case MDStatics.CATEGORY_MAP:
0367: if (fmd.jdoMap != null)
0368: return fmd.jdoMap.extensions;
0369: return null;
0370: }
0371: throw BindingSupportImpl.getInstance().internal(
0372: "invalid category: " + fmd.category);
0373: }
0374:
0375: /**
0376: * Set the PK of the link table.
0377: */
0378: protected void createLinkTablePK() {
0379: if (sequenceColumn != null) {
0380: linkTable.setPk(JdbcColumn.concat(ourPkColumns,
0381: sequenceColumn));
0382: } else {
0383: linkTable.setPk(JdbcColumn.concat(ourPkColumns,
0384: valueColumns));
0385: }
0386: }
0387:
0388: /**
0389: * Name the key and value columns.
0390: */
0391: protected void nameKeyAndValueColumns(JdbcNameGenerator namegen,
0392: String linkTableNameForNamegen) {
0393: // name the value column(s)
0394: if (valuesAreOIDs) {
0395: String[] valuePkNames = JdbcColumn
0396: .getColumnNames(((JdbcClass) fmd.elementTypeMetaData.storeClass).table.pk);
0397: String[] linkValueRefNames = JdbcColumn
0398: .getColumnNames(valueColumns);
0399: namegen
0400: .generateLinkTableValueRefNames(
0401: linkTableNameForNamegen, valuePkNames,
0402: fmd.elementType.getName(),
0403: linkValueRefNames, false);
0404: JdbcColumn.setColumnNames(valueColumns, linkValueRefNames);
0405: } else {
0406: JdbcColumn c = valueColumns[0];
0407: if (c.name == null) {
0408: c.name = namegen
0409: .generateLinkTableValueName(
0410: linkTableNameForNamegen,
0411: fmd.elementType, false);
0412: }
0413: }
0414: }
0415:
0416: /**
0417: * Complete the key and value column related meta data.
0418: */
0419: protected void completeKeyAndValueColumnMetaData(
0420: JdbcClass jdbcClass, ArrayList cols, JdoElement context,
0421: JdoExtension[] linkNested, JdbcMetaDataBuilder mdb,
0422: boolean quiet) {
0423:
0424: // create the value column(s)
0425: JdoExtension ext = JdoExtension.find(
0426: JdoExtensionKeys.JDBC_VALUE, linkNested);
0427: if (fmd.elementTypeMetaData != null) { // values are OIDs
0428: JdbcRefMetaDataBuilder rdb = new JdbcRefMetaDataBuilder(
0429: fmd.classMetaData, mdb, fmd.elementTypeMetaData,
0430: context, JdbcMetaDataBuilder.VALUE_FIELDNAME,
0431: ext == null ? null : ext.nested, quiet);
0432: createValueConstraint = !rdb.isDoNotCreateConstraint();
0433: valueConstraintName = rdb.getConstraintName();
0434: valueColumns = rdb.getCols();
0435: cols.addAll(rdb.getColsList());
0436: } else {
0437: if (fmd.elementType == Object.class) {
0438: if (fmd.classMetaData.jmd.testing) {
0439: fmd.setElementType(String.class); // fudge so things work
0440: } else {
0441: throw BindingSupportImpl
0442: .getInstance()
0443: .runtime(
0444: "You must specify the element-type (or value-type for maps) "
0445: + "for collections (and maps)\n"
0446: + fmd + "\n"
0447: + context.getContext());
0448: }
0449: }
0450:
0451: JdbcColumn vc = null;
0452: if (fmd.category == MDStatics.CATEGORY_COLLECTION) {
0453: vc = mdb.createColumn(ext == null ? null : ext.nested,
0454: JdbcMetaDataBuilder.VALUE_FIELDNAME,
0455: fmd.elementType);
0456: } else if (fmd.category == MDStatics.CATEGORY_ARRAY) {
0457: vc = mdb.createColumn(ext == null ? null : ext.nested,
0458: JdbcMetaDataBuilder.VALUE_FIELDNAME,
0459: fmd.elementType);
0460: if (Debug.DEBUG) {
0461: if (fmd.componentType != fmd.elementType) {
0462: throw new RuntimeException();
0463: }
0464: }
0465: } else if (fmd.category == MDStatics.CATEGORY_MAP) {
0466: vc = mdb.createColumn(ext == null ? null : ext.nested,
0467: JdbcMetaDataBuilder.VALUE_FIELDNAME,
0468: fmd.elementType);
0469: } else {
0470: throw BindingSupportImpl.getInstance().internal("");
0471: }
0472:
0473: valueColumns = new JdbcColumn[] { vc };
0474: cols.add(vc);
0475: }
0476:
0477: //update allowNulls
0478: boolean nulls = allowNulls();
0479: for (int i = 0; i < valueColumns.length; i++) {
0480: JdbcColumn valueColumn = valueColumns[i];
0481: valueColumn.setNulls(nulls);
0482: }
0483: }
0484:
0485: /**
0486: * Name our linkTable.
0487: */
0488: protected void nameLinkTable(JdbcNameGenerator namegen,
0489: JdbcClass jdbcClass) {
0490: JdbcClass valueTarget = null;
0491: if (valuesAreOIDs)
0492: valueTarget = (JdbcClass) fmd.elementTypeMetaData.storeClass;
0493: linkTable.name = namegen.generateLinkTableName(
0494: jdbcClass.table.name, fmd.name,
0495: valueTarget == null ? null : valueTarget.table.name);
0496: }
0497:
0498: /**
0499: * Persist pass 2 field for a block of graph entries all with
0500: * the same class. The same ps'es can be used for all entries in the block.
0501: */
0502: public void persistPass2Block(PersistGraph graph, int blockStart,
0503: int blockEnd, CharBuf s, Connection con,
0504: boolean batchInserts, boolean batchUpdates)
0505: throws SQLException {
0506: if (Debug.DEBUG) {
0507: if (readOnly) {
0508: for (int pos = blockStart; pos < blockEnd; pos++) {
0509: State ns = graph.getNewState(pos);
0510: if (ns.getInternalObjectField(stateFieldNo) != null) {
0511: throw BindingSupportImpl.getInstance()
0512: .internal("readOnly field in ns");
0513: }
0514: }
0515: }
0516: }
0517: if (readOnly)
0518: return;
0519: if (fmd.ordered) {
0520: persistPass2BlockOrdered(graph, blockStart, blockEnd, s,
0521: con, batchInserts, batchUpdates);
0522: } else {
0523: persistPass2BlockUnordered(graph, blockStart, blockEnd, s,
0524: con, batchInserts, batchUpdates);
0525: }
0526: }
0527:
0528: private void persistPass2BlockOrdered(PersistGraph graph,
0529: int blockStart, int blockEnd, CharBuf s, Connection con,
0530: boolean batchInserts, boolean batchUpdates)
0531: throws SQLException {
0532: PreparedStatement psdel = null;
0533: PreparedStatement psdelAll = null;
0534: PreparedStatement psins = null;
0535: int delCount = 0;
0536: try {
0537: String psdelSql = null;
0538: String psdelAllSql = null;
0539: String psinsSql = null;
0540: for (int pos = blockStart; pos < blockEnd; pos++) {
0541: State ns = graph.getNewState(pos);
0542: if (!ns.containsField(stateFieldNo))
0543: continue;
0544:
0545: OID oid = graph.getOID(pos);
0546: if (fmd.category == MDStatics.CATEGORY_ARRAY) {
0547: if (!oid.isNew()) {
0548: //delete the current entries
0549: if (psdelAll == null) {
0550: psdelAllSql = getDeleteAllLinkTableRowsSql(s);
0551: psdelAll = con
0552: .prepareStatement(psdelAllSql);
0553: }
0554: ((JdbcOID) oid).setParams(psdelAll, 1);
0555: if (batchUpdates) {
0556: psdelAll.addBatch();
0557: } else {
0558: try {
0559: psdelAll.execute();
0560: } catch (Exception e) {
0561: throw mapException(
0562: e,
0563: "Delete all link table rows failed: "
0564: + JdbcUtils.toString(e)
0565: + "\n"
0566: + "Field: "
0567: + fmd.getTypeQName()
0568: + "\n"
0569: + "Instance: "
0570: + oid.toSString()
0571: + "\n"
0572: + JdbcUtils
0573: .getPreparedStatementInfo(
0574: psdelAllSql,
0575: psdelAll));
0576: }
0577: }
0578: }
0579:
0580: Object toInsert = ns
0581: .getInternalObjectField(stateFieldNo);
0582: if (toInsert != null) {
0583: if (psins == null) {
0584: psinsSql = getInsertLinkTableRowSql(s);
0585: psins = con.prepareStatement(psinsSql);
0586: }
0587:
0588: if (fmd.componentType.isPrimitive()) {
0589: //throw not supported
0590: throw BindingSupportImpl.getInstance()
0591: .unsupported();
0592: } else {
0593: insertOrderedLinkTableRows(oid, null,
0594: toInsert, psins, batchInserts,
0595: psinsSql);
0596: }
0597: }
0598:
0599: } else {
0600: OrderedCollectionDiff diff = (OrderedCollectionDiff) ns
0601: .getInternalObjectField(stateFieldNo);
0602: if (diff == null
0603: || diff.status == CollectionDiff.STATUS_NEW) {
0604: if (!oid.isNew()) {
0605: if (psdelAll == null) {
0606: psdelAllSql = getDeleteAllLinkTableRowsSql(s);
0607: psdelAll = con
0608: .prepareStatement(psdelAllSql);
0609: }
0610: ((JdbcOID) oid).setParams(psdelAll, 1);
0611: if (batchUpdates) {
0612: psdelAll.addBatch();
0613: } else {
0614: try {
0615: psdelAll.execute();
0616: } catch (Exception e) {
0617: throw mapException(
0618: e,
0619: "Delete all link table rows failed: "
0620: + JdbcUtils
0621: .toString(e)
0622: + "\n"
0623: + "Field: "
0624: + fmd
0625: .getTypeQName()
0626: + "\n"
0627: + "Instance: "
0628: + oid.toSString()
0629: + "\n"
0630: + JdbcUtils
0631: .getPreparedStatementInfo(
0632: psdelAllSql,
0633: psdelAll));
0634: }
0635: }
0636: }
0637: } else {
0638: int[] deleted = diff.deletedIndexes;
0639: if (deleted != null && deleted.length > 0) {
0640: if (psdel == null) {
0641: psdelSql = getDeleteLinkTableRowSql(s);
0642: psdel = con.prepareStatement(psdelSql);
0643: }
0644: deletedOrderedLinkTableRows(oid, deleted,
0645: psdel, batchUpdates, psdelSql);
0646: delCount += deleted.length;
0647: }
0648: }
0649:
0650: if (diff != null) {
0651: Object[] insertedValues = diff.insertedValues;
0652: if (insertedValues != null
0653: && insertedValues.length > 0) {
0654: if (psins == null) {
0655: psinsSql = getInsertLinkTableRowSql(s);
0656: psins = con.prepareStatement(psinsSql);
0657: }
0658: insertOrderedLinkTableRows(oid,
0659: diff.insertedIndexes,
0660: insertedValues, psins,
0661: batchInserts, psinsSql);
0662: }
0663: }
0664: }
0665: }
0666: if (batchUpdates) {
0667: execLinkTableBatchDeletes(delCount, psdel, psdelSql,
0668: psdelAll, psdelAllSql);
0669: }
0670: if (batchInserts && psins != null) {
0671: execLinkTableBatchInserts(psins, psinsSql);
0672: }
0673: } finally {
0674: cleanup(psdel);
0675: cleanup(psdelAll);
0676: cleanup(psins);
0677: }
0678: }
0679:
0680: protected void execLinkTableBatchInserts(PreparedStatement psins,
0681: String psinsSql) {
0682: try {
0683: psins.executeBatch();
0684: } catch (SQLException e) {
0685: throw mapException(e, "Link table batch insert failed: "
0686: + JdbcUtils.toString(e)
0687: + "\n"
0688: + "Field: "
0689: + fmd.getTypeQName()
0690: + "\n"
0691: + JdbcUtils.getPreparedStatementInfo(psinsSql,
0692: psins));
0693: }
0694: }
0695:
0696: protected void execLinkTableBatchDeletes(int delCount,
0697: PreparedStatement psdel, String psdelSql,
0698: PreparedStatement psdelAll, String psdelAllSql) {
0699: if (delCount > 0) {
0700: int[] a;
0701: try {
0702: a = psdel.executeBatch();
0703: } catch (Exception e) {
0704: throw mapException(e,
0705: "Link table batch delete failed: "
0706: + JdbcUtils.toString(e)
0707: + "\n"
0708: + "Field: "
0709: + fmd.getTypeQName()
0710: + "\n"
0711: + JdbcUtils.getPreparedStatementInfo(
0712: psdelSql, psdel));
0713: }
0714: for (int i = 0; i < delCount; i++) {
0715: int c = a[i];
0716: if (c <= 0) {
0717: String psi = JdbcUtils.getPreparedStatementInfo(
0718: psdelSql, psdel, i);
0719: if (c == 0) {
0720: throw BindingSupportImpl.getInstance()
0721: .concurrentUpdate(
0722: "Link table row not found on batch delete: "
0723: + "Field: "
0724: + fmd.getTypeQName()
0725: + "\n" + psi, null);
0726: }
0727: throw BindingSupportImpl.getInstance().datastore(
0728: "Unexpected update count for link table row batch delete: "
0729: + c + "\nField: "
0730: + fmd.getTypeQName() + "\n" + psi);
0731: }
0732: }
0733: }
0734: if (psdelAll != null) {
0735: try {
0736: psdelAll.executeBatch();
0737: } catch (Exception e) {
0738: throw mapException(e,
0739: "Link table batch delete all failed: "
0740: + JdbcUtils.toString(e)
0741: + "\n"
0742: + "Field: "
0743: + fmd.getTypeQName()
0744: + "\n"
0745: + JdbcUtils.getPreparedStatementInfo(
0746: psdelAllSql, psdelAll));
0747: }
0748: }
0749: }
0750:
0751: /**
0752: * Delete rows from an ordered link table.
0753: */
0754: private void deletedOrderedLinkTableRows(OID oid, int[] deleted,
0755: PreparedStatement psdel, boolean batch, String sql)
0756: throws SQLException {
0757: JdbcColumn sc = sequenceColumn;
0758: for (int j = deleted.length - 1; j >= 0; j--) {
0759: int pp = ((JdbcOID) oid).setParams(psdel, 1);
0760: sc.set(psdel, pp, deleted[j]);
0761: if (batch) {
0762: psdel.addBatch();
0763: } else {
0764: int uc;
0765: try {
0766: uc = psdel.executeUpdate();
0767: } catch (Exception e) {
0768: throw mapException(e,
0769: "Delete link table row failed: "
0770: + JdbcUtils.toString(e)
0771: + "\n"
0772: + "Field: "
0773: + fmd.getTypeQName()
0774: + "\n"
0775: + "Sequence: "
0776: + deleted[j]
0777: + "\n"
0778: + "Instance: "
0779: + oid.toSString()
0780: + "\n"
0781: + JdbcUtils
0782: .getPreparedStatementInfo(
0783: sql, psdel));
0784: }
0785: if (uc == 0) {
0786: throw BindingSupportImpl
0787: .getInstance()
0788: .concurrentUpdate(
0789: "Link table row not found: "
0790: + "Field: "
0791: + fmd.getTypeQName()
0792: + "\n"
0793: + "Sequence: "
0794: + deleted[j]
0795: + "\n"
0796: + "Instance: "
0797: + oid.toSString()
0798: + "\n"
0799: + JdbcUtils
0800: .getPreparedStatementInfo(
0801: sql, psdel),
0802: oid);
0803: }
0804: }
0805: }
0806: }
0807:
0808: /**
0809: * Insert values into an ordered link table.
0810: */
0811: private void insertOrderedLinkTableRows(OID oid,
0812: int[] insertedIndexes, Object _insertedValues,
0813: PreparedStatement psins, boolean batch, String sql)
0814: throws SQLException {
0815:
0816: Object[] insertedValues = (Object[]) _insertedValues;
0817: int ilen = insertedValues.length;
0818:
0819: if (valuesAreOIDs) {
0820: for (int j = 0; j < ilen; j++) {
0821: int pp = ((JdbcOID) oid).setParams(psins, 1);
0822: sequenceColumn.set(psins, pp++,
0823: insertedIndexes == null ? j
0824: : insertedIndexes[j]);
0825:
0826: OID _oid = (OID) insertedValues[j];
0827:
0828: if (_oid != null) {
0829: ((JdbcOID) _oid).setParams(psins, pp);
0830: } else {
0831: JdbcGenericOID.setNullParams(psins, pp,
0832: fmd.elementTypeMetaData);
0833: }
0834: if (batch) {
0835: psins.addBatch();
0836: } else {
0837: try {
0838: psins.execute();
0839: } catch (Exception e) {
0840: throw mapException(
0841: e,
0842: "Insert link table row failed: "
0843: + JdbcUtils.toString(e)
0844: + "\n"
0845: + "Field: "
0846: + fmd.getTypeQName()
0847: + "\n"
0848: + "Instance: "
0849: + oid.toSString()
0850: + "\n"
0851: + "Link table value[sequence "
0852: + insertedIndexes[j]
0853: + "]: "
0854: + _oid.toSString()
0855: + "\n"
0856: + JdbcUtils
0857: .getPreparedStatementInfo(
0858: sql, psins));
0859: }
0860: }
0861: }
0862: } else {
0863: JdbcColumn vc = valueColumns[0];
0864: for (int j = 0; j < ilen; j++) {
0865: int pp = ((JdbcOID) oid).setParams(psins, 1);
0866: sequenceColumn.set(psins, pp++,
0867: insertedIndexes == null ? j
0868: : insertedIndexes[j]);
0869:
0870: vc.set(psins, pp, insertedValues[j]);
0871:
0872: if (batch) {
0873: psins.addBatch();
0874: } else {
0875: try {
0876: psins.execute();
0877: } catch (Exception e) {
0878: throw mapException(
0879: e,
0880: "Insert link table row failed: "
0881: + JdbcUtils.toString(e)
0882: + "\n"
0883: + "Field: "
0884: + fmd.getTypeQName()
0885: + "\n"
0886: + "Instance: "
0887: + oid.toSString()
0888: + "\n"
0889: + "Link table value[sequence "
0890: + insertedIndexes[j]
0891: + "]: "
0892: +
0893:
0894: Utils
0895: .toString(insertedValues[j])
0896: + "\n"
0897: +
0898:
0899: JdbcUtils
0900: .getPreparedStatementInfo(
0901: sql, psins));
0902: }
0903: }
0904: }
0905: }
0906: }
0907:
0908: private void persistPass2BlockUnordered(PersistGraph graph,
0909: int blockStart, int blockEnd, CharBuf s, Connection con,
0910: boolean batchInserts, boolean batchUpdates)
0911: throws SQLException {
0912: PreparedStatement psdel = null;
0913: PreparedStatement psdelAll = null;
0914: PreparedStatement psins = null;
0915: int delCount = 0;
0916: try {
0917: String psdelSql = null;
0918: String psdelAllSql = null;
0919: String psinsSql = null;
0920: for (int pos = blockStart; pos < blockEnd; pos++) {
0921: State ns = graph.getNewState(pos);
0922: if (!ns.containsField(stateFieldNo))
0923: continue;
0924:
0925: UnorderedCollectionDiff diff = (UnorderedCollectionDiff) ns
0926: .getInternalObjectField(stateFieldNo);
0927:
0928: OID oid = graph.getOID(pos);
0929:
0930: if (diff == null
0931: || diff.status == CollectionDiff.STATUS_NEW) {
0932: if (!oid.isNew()) {
0933: if (psdelAll == null) {
0934: psdelAllSql = getDeleteAllLinkTableRowsSql(s);
0935: psdelAll = con
0936: .prepareStatement(psdelAllSql);
0937: }
0938: ((JdbcOID) oid).setParams(psdelAll, 1);
0939: if (batchUpdates) {
0940: psdelAll.addBatch();
0941: } else {
0942: try {
0943: psdelAll.execute();
0944: } catch (Exception e) {
0945: throw mapException(
0946: e,
0947: "Delete all link table rows failed: "
0948: + JdbcUtils.toString(e)
0949: + "\n"
0950: + "Field: "
0951: + fmd.getTypeQName()
0952: + "\n"
0953: + "Instance: "
0954: + oid.toSString()
0955: + "\n"
0956: + JdbcUtils
0957: .getPreparedStatementInfo(
0958: psdelAllSql,
0959: psdelAll));
0960: }
0961: }
0962: }
0963: } else {
0964: Object[] deleted = diff.deletedValues;
0965: if (deleted != null && deleted.length > 0) {
0966: if (psdel == null) {
0967: psdelSql = getDeleteLinkTableRowSql(s);
0968: psdel = con.prepareStatement(psdelSql);
0969: }
0970: deleteUnorderedLinkTableRows(oid, deleted,
0971: psdel, batchUpdates, psdelSql);
0972: delCount += deleted.length;
0973: }
0974: }
0975:
0976: if (diff != null) {
0977: Object[] inserted = diff.insertedValues;
0978: if (inserted != null && inserted.length > 0) {
0979: if (psins == null) {
0980: psinsSql = getInsertLinkTableRowSql(s);
0981: psins = con.prepareStatement(psinsSql);
0982: }
0983: insertUnorderedLinkTableRows(oid, inserted,
0984: psins, batchInserts, psinsSql);
0985: }
0986: }
0987: }
0988: if (batchUpdates) {
0989: execLinkTableBatchDeletes(delCount, psdel, psdelSql,
0990: psdelAll, psdelAllSql);
0991: }
0992: if (batchInserts && psins != null) {
0993: execLinkTableBatchInserts(psins, psinsSql);
0994: }
0995: } finally {
0996: cleanup(psdel);
0997: cleanup(psdelAll);
0998: cleanup(psins);
0999: }
1000: }
1001:
1002: /**
1003: * Delete values from an unordered link table.
1004: */
1005: private void deleteUnorderedLinkTableRows(OID oid,
1006: Object[] deleted, PreparedStatement psdel, boolean batch,
1007: String sql) throws SQLException {
1008: if (valuesAreOIDs) {
1009: for (int j = deleted.length - 1; j >= 0; j--) {
1010: int pp = ((JdbcOID) oid).setParams(psdel, 1);
1011: ((JdbcOID) deleted[j]).setParams(psdel, pp);
1012: if (batch) {
1013: psdel.addBatch();
1014: } else {
1015: int uc;
1016: try {
1017: uc = psdel.executeUpdate();
1018: } catch (Exception e) {
1019: throw mapException(
1020: e,
1021: "Delete link table row failed: "
1022: + JdbcUtils.toString(e)
1023: + "\n"
1024: + "Field: "
1025: + fmd.getTypeQName()
1026: + "\n"
1027: + "Value: "
1028: + ((OID) deleted[j])
1029: .toSString()
1030: + "\n"
1031: + "Instance: "
1032: + oid.toSString()
1033: + "\n"
1034: + JdbcUtils
1035: .getPreparedStatementInfo(
1036: sql, psdel));
1037: }
1038: if (uc == 0) {
1039: throw BindingSupportImpl
1040: .getInstance()
1041: .concurrentUpdate(
1042: "Link table row not found: "
1043: + "Field: "
1044: + fmd.getTypeQName()
1045: + "\n"
1046: + "Value: "
1047: + ((OID) deleted[j])
1048: .toSString()
1049: + "\n"
1050: + "Instance: "
1051: + oid.toSString()
1052: + "\n"
1053: + JdbcUtils
1054: .getPreparedStatementInfo(
1055: sql,
1056: psdel),
1057: deleted[j]);
1058: }
1059: }
1060: }
1061: } else {
1062: JdbcColumn vc = valueColumns[0];
1063: for (int j = deleted.length - 1; j >= 0; j--) {
1064: int pp = ((JdbcOID) oid).setParams(psdel, 1);
1065: vc.set(psdel, pp, deleted[j]);
1066: if (batch) {
1067: psdel.addBatch();
1068: } else {
1069: int uc;
1070: try {
1071: uc = psdel.executeUpdate();
1072: } catch (Exception e) {
1073: throw mapException(
1074: e,
1075: "Delete link table row failed: "
1076: + JdbcUtils.toString(e)
1077: + "\n"
1078: + "Field: "
1079: + fmd.getTypeQName()
1080: + "\n"
1081: + "Value: "
1082: + Utils.toString(deleted[j])
1083: + "\n"
1084: + "Instance: "
1085: + oid.toSString()
1086: + "\n"
1087: + JdbcUtils
1088: .getPreparedStatementInfo(
1089: sql, psdel));
1090: }
1091: if (uc == 0) {
1092: throw BindingSupportImpl
1093: .getInstance()
1094: .concurrentUpdate(
1095: "Link table row not found: "
1096: + "Field: "
1097: + fmd.getTypeQName()
1098: + "\n"
1099: + "Value: "
1100: + Utils
1101: .toString(deleted[j])
1102: + "\n"
1103: + "Instance: "
1104: + oid.toSString()
1105: + "\n"
1106: + JdbcUtils
1107: .getPreparedStatementInfo(
1108: sql,
1109: psdel),
1110: oid);
1111: }
1112: }
1113: }
1114: }
1115: }
1116:
1117: /**
1118: * Insert values into an unordered link table.
1119: */
1120: private void insertUnorderedLinkTableRows(OID oid,
1121: Object[] inserted, PreparedStatement psins, boolean batch,
1122: String sql) throws SQLException {
1123: int ilen = inserted.length;
1124: if (valuesAreOIDs) {
1125: for (int j = 0; j < ilen; j++) {
1126: int pp = ((JdbcOID) oid).setParams(psins, 1);
1127: ((JdbcOID) inserted[j]).setParams(psins, pp);
1128: if (batch) {
1129: psins.addBatch();
1130: } else {
1131: try {
1132: psins.execute();
1133: } catch (Exception e) {
1134: throw mapException(
1135: e,
1136: "Insert link table row failed: "
1137: + JdbcUtils.toString(e)
1138: + "\n"
1139: + "Field: "
1140: + fmd.getTypeQName()
1141: + "\n"
1142: + "Instance: "
1143: + oid.toSString()
1144: + "\n"
1145: + "Value: "
1146: + ((OID) inserted[j])
1147: .toSString()
1148: + "\n"
1149: + JdbcUtils
1150: .getPreparedStatementInfo(
1151: sql, psins));
1152: }
1153: }
1154: }
1155: } else {
1156: JdbcColumn vc = valueColumns[0];
1157: for (int j = 0; j < ilen; j++) {
1158: int pp = ((JdbcOID) oid).setParams(psins, 1);
1159: vc.set(psins, pp, inserted[j]);
1160: if (batch) {
1161: psins.addBatch();
1162: } else {
1163: try {
1164: psins.execute();
1165: } catch (Exception e) {
1166: throw mapException(
1167: e,
1168: "Insert link table row failed: "
1169: + JdbcUtils.toString(e)
1170: + "\n"
1171: + "Field: "
1172: + fmd.getTypeQName()
1173: + "\n"
1174: + "Instance: "
1175: + oid.toSString()
1176: + "\n"
1177: + "Value: "
1178: + Utils.toString(inserted[j])
1179: + "\n"
1180: + JdbcUtils
1181: .getPreparedStatementInfo(
1182: sql, psins));
1183: }
1184: }
1185: }
1186: }
1187: }
1188:
1189: /**
1190: * Get SQL to delete a row from our link table.
1191: */
1192: protected String getDeleteLinkTableRowSql(CharBuf s) {
1193: if (deleteRowSql == null) {
1194: s.clear();
1195: s.append("delete from ");
1196: s.append(linkTable.name);
1197: s.append(" where ");
1198: linkTable.appendWherePK(s);
1199: deleteRowSql = s.toString();
1200: }
1201: return deleteRowSql;
1202: }
1203:
1204: /**
1205: * Get SQL to delete all rows from our link table.
1206: */
1207: protected String getDeleteAllLinkTableRowsSql(CharBuf s) {
1208: if (deleteAllRowsSql == null) {
1209: s.clear();
1210: s.append("DELETE FROM ");
1211: s.append(linkTable.name);
1212: s.append(" WHERE ");
1213: SqlDriver driver = linkTable.sqlDriver;
1214: int nc = ourPkColumns.length;
1215: JdbcColumn sc = ourPkColumns[0];
1216: s.append(sc.name);
1217: s.append(' ');
1218: s.append('=');
1219: s.append(' ');
1220: driver.appendWhereParam(s, sc);
1221: for (int i = 1; i < nc; i++) {
1222: s.append(" AND ");
1223: sc = ourPkColumns[i];
1224: s.append(sc.name);
1225: s.append(' ');
1226: s.append('=');
1227: s.append(' ');
1228: driver.appendWhereParam(s, sc);
1229: }
1230: deleteAllRowsSql = s.toString();
1231: }
1232: return deleteAllRowsSql;
1233: }
1234:
1235: /**
1236: * Get SQL to delete all rows from our link table with a 'IN' List. This is only
1237: * supported if there is a single pk column.
1238: */
1239: protected void getDeleteAllLinkTableRowsSqlWithInList(CharBuf s) {
1240: s.clear();
1241: s.append("delete from ");
1242: s.append(linkTable.name);
1243: s.append(" where ");
1244: JdbcColumn sc = ourPkColumns[0];
1245: s.append(sc.name);
1246: s.append(" IN (");
1247: }
1248:
1249: /**
1250: * Get SQL to insert a row into our link table.
1251: */
1252: public String getInsertLinkTableRowSql(CharBuf s) {
1253: if (insertRowSql == null) {
1254: s.clear();
1255: s.append("INSERT INTO ");
1256: s.append(linkTable.name);
1257: s.append('(');
1258: linkTable.appendInsertColumnList(s);
1259: s.append(") VALUES (");
1260: linkTable.appendInsertValueList(s);
1261: s.append(')');
1262: insertRowSql = s.toString();
1263: }
1264: return insertRowSql;
1265: }
1266:
1267: /**
1268: * if null is allowed by default for this collection.
1269: */
1270: private boolean allowNulls() {
1271: return fmd.ordered || fmd.category == MDStatics.CATEGORY_MAP;
1272: }
1273:
1274: /**
1275: * Get a SelectExp to select all the rows in this collection using the
1276: * supplied fetch group field to control joins and so on.
1277: */
1278: protected SelectExp getSelectExp(JdbcStorageManager sm,
1279: FetchGroupField field, FgDs[] fgDses) {
1280: SelectExp root = new SelectExp();
1281: root.table = linkTable;
1282: root.selectList = JdbcColumn.toSqlExp(valueColumns, root);
1283: root.whereExp = JdbcColumn.createEqualsParamExp(ourPkColumns,
1284: root);
1285:
1286: if (valuesAreOIDs) {
1287: if (field.jdbcUseJoin != JdbcField.USE_JOIN_NO
1288: || fmd.ordering != null) {
1289: SelectExp se = new SelectExp();
1290: JdbcClass valueJdbcClass = (JdbcClass) fmd.elementTypeMetaData.storeClass;
1291: se.table = valueJdbcClass.table;
1292: se.outer = field.jdbcUseJoin != JdbcField.USE_JOIN_INNER;
1293:
1294: root.addJoin(valueColumns, se.table.pk, se);
1295: sm
1296: .addSelectFetchGroup(
1297: se,
1298: field.nextFetchGroup,
1299: true,
1300: fgDses[0] = ((JdbcFetchGroup) field.nextFetchGroup.storeFetchGroup)
1301: .getFgDs(
1302: true,
1303: field.jdbcUseJoin != JdbcField.USE_JOIN_INNER),
1304: root, valueColumns, this );
1305:
1306: if (fmd.ordering != null) {
1307: se.addOrderBy(fmd.ordering, false);
1308: root.orderByList = se.orderByList;
1309: se.orderByList = null;
1310: }
1311: }
1312: } else {
1313: if (fmd.ordering != null) {
1314: // the ordering can only be 'this ascending' or 'this descending'
1315: boolean desc = fmd.ordering[0].order == OrderNode.ORDER_DESCENDING;
1316: root.orderByList = new OrderExp(valueColumns[0]
1317: .toSqlExp(root), desc);
1318: // there will be only one entry in valueColumns as this is
1319: // not a collection of PC instances
1320: }
1321: }
1322: if (fmd.ordered) {
1323: if (fmd.ordering != null) {
1324: throw BindingSupportImpl.getInstance().internal(
1325: "ordered == true && ordering != null, "
1326: + fmd.getTypeQName());
1327: }
1328: root.orderByList = linkTable.createOrderByPKList(root);
1329: }
1330: return root;
1331: }
1332:
1333: public SelectExp getSelectExpFrom(JdbcStorageManager sm,
1334: SelectExp joinToExp, FetchGroupField field, FgDs owningFgDs) {
1335: SelectExp root = getSelectExpFromImp(joinToExp, field, sm,
1336: owningFgDs);
1337: root.selectList = JdbcColumn.toSqlExp(ourPkColumns, root,
1338: root.selectList);
1339: return root;
1340: }
1341:
1342: public SelectExp getSelectExpFromImp(SelectExp joinToExp,
1343: FetchGroupField field, JdbcStorageManager sm,
1344: FgDs owningFgDs) {
1345: SelectExp root = new SelectExp();
1346: root.table = linkTable;
1347: root.selectList = JdbcColumn.toSqlExp(valueColumns, root);
1348:
1349: if (valuesAreOIDs) {
1350: if (field.jdbcUseJoin != JdbcField.USE_JOIN_NO
1351: || fmd.ordering != null) {
1352: SelectExp se = new SelectExp();
1353: JdbcClass valueJdbcClass = (JdbcClass) fmd.elementTypeMetaData.storeClass;
1354: se.table = valueJdbcClass.table;
1355: se.outer = true;
1356:
1357: root.addJoin(valueColumns, se.table.pk, se);
1358: FgDs fgDs = ((JdbcFetchGroup) field.nextFetchGroup.storeFetchGroup)
1359: .getFgDs(true, se.outer);
1360: sm.addSelectFetchGroup(se, field.nextFetchGroup, true,
1361: fgDs, root, valueColumns, this );
1362: owningFgDs.valueJs = fgDs.getJoinStruct();
1363:
1364: if (fmd.ordering != null) {
1365: se.addOrderBy(fmd.ordering, false);
1366: root.orderByList = se.orderByList;
1367: se.orderByList = null;
1368: }
1369: }
1370: } else {
1371: if (fmd.ordering != null) {
1372: // the ordering can only be 'this ascending' or 'this descending'
1373: boolean desc = fmd.ordering[0].order == OrderNode.ORDER_DESCENDING;
1374: root.orderByList = new OrderExp(valueColumns[0]
1375: .toSqlExp(root), desc);
1376: // there will be only one entry in valueColumns as this is
1377: // not a collection of PC instances
1378: }
1379: }
1380: if (fmd.ordered) {
1381: if (fmd.ordering != null) {
1382: throw BindingSupportImpl.getInstance().internal(
1383: "ordered == true && ordering != null, "
1384: + fmd.getTypeQName());
1385: }
1386: root.orderByList = linkTable.createOrderByPKList(root);
1387: }
1388:
1389: root.outer = true;
1390: joinToExp.addJoin(joinToExp.table.pk, ourPkColumns, root);
1391: joinToExp.appendOrderByExp(root.orderByList);
1392: root.orderByList = null;
1393: return root;
1394: }
1395:
1396: /**
1397: * Get a SelectExp to select all the rows in this collection using the
1398: * supplied fetch group field to control joins and so on.
1399: */
1400: public SelectExp getSelectFilterExp(JdbcStorageManager sm,
1401: FetchGroupField field, ColFieldHolder colFHolder) {
1402: SelectExp root = new SelectExp();
1403: root.table = linkTable;
1404: root.selectList = JdbcColumn.toSqlExp(valueColumns, root);
1405:
1406: // prepend our pk columns to the select list
1407: SqlExp e = JdbcColumn.toSqlExp(ourPkColumns, root,
1408: root.selectList);
1409: root.selectList = e;
1410: root.appendOrderByForColumns(ourPkColumns);
1411:
1412: if (valuesAreOIDs) {
1413: if (field.jdbcUseJoin != JdbcField.USE_JOIN_NO
1414: || fmd.ordering != null) {
1415: SelectExp se = new SelectExp();
1416: JdbcClass valueJdbcClass = (JdbcClass) fmd.elementTypeMetaData.storeClass;
1417: se.table = valueJdbcClass.table;
1418: se.outer = field.jdbcUseJoin == JdbcField.USE_JOIN_OUTER;
1419:
1420: FgDs fgDs = ((JdbcFetchGroup) field.nextFetchGroup.storeFetchGroup)
1421: .getFgDs(true, se.outer);
1422: sm.addSelectFetchGroup(se, field.nextFetchGroup, true,
1423: fgDs, root, valueColumns, this );
1424: colFHolder.valueJs = fgDs.getJoinStruct();
1425:
1426: root.addJoin(valueColumns, se.table.pk, se);
1427: if (fmd.ordering != null) {
1428: se.addOrderBy(fmd.ordering, false);
1429: root.appendOrderByExp(se.orderByList);
1430: se.orderByList = null;
1431: }
1432: }
1433: } else {
1434: if (fmd.ordering != null) {
1435: // the ordering can only be 'this ascending' or 'this descending'
1436: boolean desc = fmd.ordering[0].order == OrderNode.ORDER_DESCENDING;
1437: root.appendOrderByExp(new OrderExp(valueColumns[0]
1438: .toSqlExp(root), desc));
1439: // there will be only one entry in valueColumns as this is
1440: // not a collection of PC instances
1441: }
1442: }
1443: if (fmd.ordered) {
1444: if (fmd.ordering != null) {
1445: throw BindingSupportImpl.getInstance().internal(
1446: "ordered == true && ordering != null, "
1447: + fmd.getTypeQName());
1448: }
1449: root.appendOrderByForColumns(linkTable.pkSimpleCols);
1450: }
1451: return root;
1452: }
1453:
1454: public SelectExp getSelectFilterJoinExp(boolean value,
1455: SelectExp lhSe, SelectExp rootSe, boolean addRootJoin) {
1456: SelectExp root = new SelectExp();
1457: root.table = linkTable;
1458: if (valuesAreOIDs) {
1459: SelectExp se = new SelectExp();
1460: se.table = ((JdbcClass) fmd.elementTypeMetaData.storeClass).table;
1461: root.addJoin(valueColumns, se.table.pk, se);
1462:
1463: lhSe.addJoin(lhSe.table.pk, ourPkColumns, root);
1464:
1465: return se;
1466: } else {
1467: throw BindingSupportImpl.getInstance().internal(
1468: "This must only be called for pc collections");
1469: }
1470: }
1471:
1472: /**
1473: * Fetch the values for this field using parallel query processing.
1474: */
1475: public int fetchWithFilter(JdbcStorageManager sm,
1476: StateContainer oidStates, FetchGroupField field,
1477: ResultSet rs, boolean forUpdate, OID oidToCheckOn,
1478: OID[] lastReadStateOID, ClassMetaData cmd,
1479: ColFieldHolder colFHolder) throws SQLException {
1480:
1481: ClassMetaData valueCmd = fmd.elementTypeMetaData;
1482: boolean joined = field.jdbcUseJoin != JdbcField.USE_JOIN_NO;
1483: FetchGroup nextFetchGroup = field.nextFetchGroup;
1484: int valuePkLen = 0;
1485: int keyPkLen = ((JdbcClass) cmd.storeClass).table.pkSimpleColumnCount;
1486: if (joined)
1487: valuePkLen = ((JdbcClass) valueCmd.storeClass).table.pkSimpleColumnCount;
1488: int stateOIDPKLen = ((JdbcClass) fmd.classMetaData.storeClass).table.pkSimpleColumnCount;
1489:
1490: JdbcColumn vc = valueColumns[0];
1491:
1492: //the oid just read from the rs row
1493: OID rootOid = cmd.createOID(false);
1494: //the oid read from the previous rs row
1495: OID prevRootOid = cmd.createOID(false);
1496: OID tmpOID = null;
1497:
1498: OID stateOID = fmd.classMetaData.createOID(false);
1499: OID prevStateOID = fmd.classMetaData.createOID(false);
1500: OID tmpStateOID = null;
1501:
1502: boolean currentRowValid = false;
1503: boolean prevRowValid = false;
1504:
1505: Struct struct = new Struct();
1506: struct.init();
1507: int returnState = 0;
1508:
1509: FgDs fgDs = null;
1510: if (valuesAreOIDs) {
1511: fgDs = ((JdbcFetchGroup) nextFetchGroup.storeFetchGroup)
1512: .getExistingFgDs(
1513: true,
1514: field.jdbcUseJoin == JdbcField.USE_JOIN_OUTER);
1515: if (colFHolder != null) {
1516: colFHolder.valueJs = fgDs.getJoinStruct();
1517: }
1518: }
1519:
1520: if (lastReadStateOID[0] != null) {
1521: prevRootOid = lastReadStateOID[0];
1522: stateOID = lastReadStateOID[1];
1523: currentRowValid = oidStates.containsKey(stateOID);
1524:
1525: if (currentRowValid) {
1526: returnState |= STATUS_VALID_ROWS;
1527: if (valuesAreOIDs) {
1528: OID valueOid = valueCmd.createOID(false);
1529: boolean isNull = !((JdbcOID) valueOid)
1530: .copyKeyFields(rs, 1 + keyPkLen
1531: + stateOIDPKLen);
1532:
1533: if (!isNull) {
1534: struct.add(valueOid);
1535: } else {
1536: struct.add(null);
1537: }
1538:
1539: if (!isNull
1540: && joined
1541: && oidStates.isStateRequired(valueOid,
1542: nextFetchGroup)) {
1543: returnState |= STATUS_DATA_ADDED;
1544: State valueState = sm.createStateImp(rs,
1545: valueOid, nextFetchGroup, forUpdate, 1
1546: + valuePkLen + keyPkLen
1547: + valuePkLen, null, true,
1548: oidStates, fgDs, false, false, null);
1549: oidStates.addState(valueOid, valueState);
1550: }
1551:
1552: } else {
1553: struct
1554: .add(vc.get(rs, 1 + keyPkLen
1555: + stateOIDPKLen));
1556: }
1557: }
1558:
1559: //preserve the prevRootOid
1560: tmpOID = rootOid;
1561: rootOid = prevRootOid;
1562: prevRootOid = tmpOID;
1563:
1564: //preserve the prevStateOID
1565: tmpStateOID = stateOID;
1566: stateOID = prevStateOID;
1567: prevStateOID = tmpStateOID;
1568:
1569: prevRowValid = currentRowValid;
1570: }
1571:
1572: for (; rs.next();) {
1573: int index = 1;
1574: ((JdbcOID) rootOid).copyKeyFields(rs, index);
1575: index += keyPkLen;
1576:
1577: ((JdbcOID) stateOID).copyKeyFields(rs, index);
1578: index += stateOIDPKLen;
1579:
1580: currentRowValid = oidStates.containsKey(stateOID);
1581:
1582: //detected a change in stateOid. Only update if the previous
1583: // row was for a valid oid.
1584: if (!stateOID.equals(prevStateOID) && prevRowValid) {
1585: if (updateStateFilter(struct, oidStates
1586: .get(prevStateOID))) {
1587: returnState |= STATUS_DATA_ADDED;
1588: }
1589:
1590: updateStatistics(struct.size);
1591: if (oidToCheckOn.equals(prevRootOid)
1592: && !oidToCheckOn.equals(rootOid)) {
1593: lastReadStateOID[0] = rootOid;
1594: lastReadStateOID[1] = stateOID;
1595: returnState |= STATUS_VALID_ROWS;
1596: return returnState;
1597: }
1598:
1599: struct.init();
1600: }
1601:
1602: if (currentRowValid) {
1603: returnState |= STATUS_VALID_ROWS;
1604: if (valuesAreOIDs) {
1605: OID valueOid = valueCmd.createOID(false);
1606: boolean isNull = !((JdbcOID) valueOid)
1607: .copyKeyFields(rs, index);
1608: index += valuePkLen;
1609:
1610: if (!isNull) {
1611: struct.add(valueOid);
1612: } else {
1613: struct.add(null);
1614: }
1615:
1616: if (!isNull
1617: && joined
1618: && oidStates.isStateRequired(valueOid,
1619: nextFetchGroup)) {
1620: State valueState = sm
1621: .createStateImp(
1622: rs,
1623: valueOid,
1624: nextFetchGroup,
1625: forUpdate,
1626: index,
1627: null,
1628: true,
1629: oidStates,
1630: ((JdbcFetchGroup) nextFetchGroup.storeFetchGroup)
1631: .getFgDs(
1632: true,
1633: field.jdbcUseJoin == JdbcField.USE_JOIN_OUTER),
1634: false, false, null);
1635: oidStates.addState(valueOid, valueState);
1636: }
1637: } else {
1638: struct.add(vc.get(rs, index));
1639: }
1640: }
1641:
1642: //preserve the prevRootOid
1643: tmpOID = rootOid;
1644: rootOid = prevRootOid;
1645: prevRootOid = tmpOID;
1646:
1647: //preserve the prevStateOID
1648: tmpStateOID = stateOID;
1649: stateOID = prevStateOID;
1650: prevStateOID = tmpStateOID;
1651:
1652: prevRowValid = currentRowValid;
1653: }
1654:
1655: rs.close();
1656: returnState |= STATUS_CLOSED;
1657: if ((returnState & STATUS_VALID_ROWS) == STATUS_VALID_ROWS) {
1658: if (updateStateFilter(struct, oidStates.get(prevStateOID))) {
1659: returnState |= STATUS_DATA_ADDED;
1660: }
1661: }
1662: return returnState;
1663: }
1664:
1665: private boolean updateStateFilter(Struct struct, State state) {
1666: if (state == null)
1667: return false;
1668: if (struct.values == null)
1669: struct.values = EMPTY_OBJECT_ARRAY;
1670:
1671: if (Debug.DEBUG) {
1672: if (!state.containsField(fmd.stateFieldNo)
1673: || state.getInternalObjectField(fmd.stateFieldNo) == null) {
1674: throw BindingSupportImpl.getInstance().internal("");
1675: }
1676: }
1677:
1678: if (state.getInternalObjectField(fmd.stateFieldNo) == PRE_GEN_EMPTY_OBJECT_ARRAY) {
1679: state.setInternalObjectField(fmd.stateFieldNo, struct
1680: .asArray(getStructArrayType()));
1681: return true;
1682: }
1683: return false;
1684: }
1685:
1686: private Class getStructArrayType() {
1687: if (valuesAreOIDs) {
1688: return OID.class;
1689: } else if (fmd.category == MDStatics.CATEGORY_ARRAY) {
1690: return fmd.componentType;
1691: } else {
1692: return Object.class;
1693: }
1694: }
1695:
1696: private class Struct {
1697:
1698: public int len;
1699:
1700: public Object[] values;
1701:
1702: public int size;
1703:
1704: public void init() {
1705: len = (int) (avgRowCount * FUDGE_FACTOR);
1706: if (len < MIN_LEN)
1707: len = 0;
1708: values = len == 0 ? null : (Object[])
1709:
1710: Array.newInstance(getStructArrayType(), len);
1711: if (Debug.DEBUG) {
1712: if (((fetchCount + 1) % 10) == 0) {
1713: System.out.println("JdbcLinkCollectionField.fetch"
1714: + " avgRowCount = " + avgRowCount + " "
1715: + " len = " + len + " "
1716: + " expansionCount = " + expansionCount
1717: + " " + " fetchCount = " + fetchCount);
1718: }
1719: }
1720: size = 0;
1721: }
1722:
1723: public void add(Object value) {
1724: if (!fmd.ordered && value == null) {
1725: //ignore null for unordered cols.
1726: return;
1727: }
1728: //grow if nec.
1729: if (size == len) {
1730: if (len == 0) {
1731: values = (Object[])
1732:
1733: Array.newInstance(getStructArrayType(),
1734: len = MIN_LEN);
1735: } else {
1736: len = len * 3 / 2 + 1;
1737:
1738: Object[] a = (Object[]) Array.newInstance(
1739: getStructArrayType(), len);
1740: System.arraycopy(values, 0, a, 0, size);
1741:
1742: values = a;
1743: expansionCount++;
1744: }
1745: }
1746:
1747: values[size++] = value;
1748:
1749: }
1750:
1751: /**
1752: * Return the values as an array of componentType.
1753: */
1754: public Object asArray(Class componentType) {
1755:
1756: Object[] a = (Object[]) Array.newInstance(componentType,
1757: size);
1758: System.arraycopy(values, 0, a, 0, size);
1759:
1760: return a;
1761: }
1762: }
1763:
1764: public void fillStateWithEmpty(FetchGroupField field, State state) {
1765: if (!state.containsField(fmd.stateFieldNo)) {
1766: state.setInternalObjectField(fmd.stateFieldNo,
1767: PRE_GEN_EMPTY_OBJECT_ARRAY);
1768: }
1769: }
1770:
1771: /**
1772: * Fetch the values for this field.
1773: */
1774: public int fetch(JdbcStorageManager sm, OID oid, State state,
1775: FetchGroupField field, boolean forUpdate,
1776: StateContainer container, boolean fetchPass2Fields,
1777: ColFieldHolder colFHolder) throws SQLException {
1778: String sql = forUpdate ? field.jdbcSelectSqlForUpdate
1779: : field.jdbcSelectSql;
1780:
1781: FgDs[] fgDses = new FgDs[1];
1782:
1783: if (sql == null) {
1784: SelectExp se = getSelectExp(sm, field, fgDses);
1785:
1786: CharBuf s = sm.generateSql(se);
1787: sql = s.toString();
1788: if (forUpdate) {
1789: field.jdbcSelectSqlForUpdate = sql;
1790: } else {
1791: field.jdbcSelectSql = sql;
1792: }
1793: }
1794:
1795: if (valuesAreOIDs) {
1796: fgDses[0] = ((JdbcFetchGroup) field.nextFetchGroup.storeFetchGroup)
1797: .getExistingFgDs(
1798: true,
1799: field.jdbcUseJoin != JdbcField.USE_JOIN_INNER);
1800: if (colFHolder != null) {
1801: colFHolder.valueJs = fgDses[0].getJoinStruct();
1802: }
1803: }
1804:
1805: final Struct struct = new Struct();
1806:
1807: PreparedStatement ps = null;
1808: ResultSet rs = null;
1809: try {
1810: ps = sm.con().prepareStatement(sql);
1811: ((JdbcOID) oid).setParams(ps, 1);
1812: try {
1813: rs = ps.executeQuery();
1814: } catch (Exception e) {
1815: throw mapException(e, "Fetch link table rows failed: "
1816: + JdbcUtils.toString(e) + "\n" + "Field: "
1817: + fmd.getTypeQName() + "\n" + "Instance: "
1818: + oid.toSString() + "\n"
1819: + JdbcUtils.getPreparedStatementInfo(sql, ps));
1820: }
1821:
1822: if (valuesAreOIDs) {
1823: if (colFHolder != null)
1824: colFHolder.valueJs = fgDses[0].getJoinStruct();
1825: final boolean joined = field.jdbcUseJoin != JdbcField.USE_JOIN_NO
1826: || fmd.ordering != null;
1827: final int startIndex = 1 + ((JdbcClass) fmd.elementTypeMetaData.storeClass).table.pkSimpleColumnCount;
1828:
1829: for (; rs.next();) {
1830: OID valueOid = fmd.elementTypeMetaData
1831: .createOID(false);
1832: if (((JdbcOID) valueOid).copyKeyFields(rs, 1)) {
1833: struct.add(valueOid);
1834: if (joined
1835: && container.isStateRequired(valueOid,
1836: field.nextFetchGroup)) {
1837: container.addState(valueOid, sm
1838: .createStateImp(rs, valueOid,
1839: field.nextFetchGroup,
1840: forUpdate, startIndex,
1841: null, true, container,
1842: fgDses[0],
1843: fetchPass2Fields, false,
1844: null));
1845: }
1846: } else {
1847: struct.add(null);
1848: }
1849: }
1850: } else {
1851: for (; rs.next();) {
1852: struct.add(valueColumns[0].get(rs, 1));
1853: }
1854: }
1855:
1856: // this can come out when the bugs are removed from the rest
1857: // of the code
1858: Object data;
1859: if (struct.values == null) {
1860:
1861: data = EMPTY_OBJECT_ARRAY;
1862: } else {
1863: data = struct.asArray(getStructArrayType());
1864: }
1865: state.setInternalObjectField(fmd.stateFieldNo, data);
1866: updateStatistics(struct.size);
1867: return struct.size;
1868: } finally {
1869: cleanup(rs);
1870: cleanup(ps);
1871: }
1872: }
1873:
1874: /**
1875: * Fetch the values for this field.
1876: */
1877: public int fetchFrom(ResultSet rs, OID oid, State state,
1878: FetchGroupField field, boolean forUpdate,
1879: StateContainer container, boolean fetchPass2Fields,
1880: int colIndex, FetchInfo fetchInfo, JdbcStorageManager sm)
1881: throws SQLException {
1882: final Struct struct = new Struct();
1883: if (valuesAreOIDs) {
1884: ClassMetaData valueCmd = fmd.elementTypeMetaData;
1885: boolean joined = field.jdbcUseJoin != JdbcField.USE_JOIN_NO;
1886: FetchGroup nextFetchGroup = field.nextFetchGroup;
1887: int valuePkLen = 0;
1888: if (joined)
1889: valuePkLen = ((JdbcClass) valueCmd.storeClass).table.pkSimpleColumnCount;
1890: boolean first = true;
1891: for (;;) {
1892: boolean mustBreak = false;
1893: if (first) {
1894: first = false;
1895: mustBreak = updateForFirstRow(fetchInfo, mustBreak,
1896: rs, colIndex, oid);
1897: } else {
1898: if (!rs.next()) {
1899: fetchInfo.onNextRow = false;
1900: fetchInfo.finished = true;
1901: mustBreak = true;
1902: } else {
1903: fetchInfo.onNextRow = true;
1904: mustBreak = checkKeyOid(rs, colIndex,
1905: fetchInfo, mustBreak, oid);
1906: }
1907: }
1908: if (mustBreak)
1909: break;
1910:
1911: OID valueOid = valueCmd.createOID(false);
1912: if (((JdbcOID) valueOid).copyKeyFields(rs, colIndex
1913: + ourPkColumns.length)) {
1914: struct.add(valueOid);
1915: if (joined
1916: && container.isStateRequired(valueOid,
1917: nextFetchGroup)) {
1918: State valueState = sm
1919: .createStateImp(
1920: rs,
1921: valueOid,
1922: nextFetchGroup,
1923: forUpdate,
1924: colIndex + valuePkLen
1925: + ourPkColumns.length,
1926: null,
1927: true,
1928: container,
1929: ((JdbcFetchGroup) nextFetchGroup.storeFetchGroup)
1930: .getFgDs(true, true),
1931: fetchPass2Fields, false, null);
1932: container.addState(valueOid, valueState);
1933: }
1934: } else {
1935: struct.add(null);
1936: }
1937: }
1938: } else {
1939: boolean mustBreak = false;
1940: boolean first = true;
1941: for (;;) {
1942: if (first) {
1943: first = false;
1944: mustBreak = updateForFirstRow(fetchInfo, mustBreak,
1945: rs, colIndex, oid);
1946: } else {
1947: if (!rs.next())
1948: break;
1949: fetchInfo.onNextRow = true;
1950: mustBreak = checkKeyOid(rs, colIndex, fetchInfo,
1951: mustBreak, oid);
1952: }
1953: if (mustBreak)
1954: break;
1955: struct.add(valueColumns[0].get(rs, colIndex
1956: + ourPkColumns.length));
1957: }
1958: }
1959: // this can come out when the bugs are removed from the rest
1960: // of the code
1961: Object data;
1962: if (struct.values == null) {
1963:
1964: data = EMPTY_OBJECT_ARRAY;
1965: } else {
1966: data = struct.asArray(getStructArrayType());
1967: }
1968: state.setInternalObjectField(fmd.stateFieldNo, data);
1969: updateStatistics(struct.size);
1970: return struct.size;
1971: }
1972:
1973: private void updateStatistics(int size) {
1974: // Update statistics. This is not thread safe but it is not a
1975: // problem if the avgRowCount is a bit out sometimes.
1976: int fc = ++fetchCount;
1977: if (fc > WINDOW_SIZE) {
1978: fc = WINDOW_SIZE;
1979: } else if (fc == 1) {
1980: avgRowCount = size;
1981: } else if (fc < 0) {
1982: fc = fetchCount = WINDOW_SIZE;
1983: } else {
1984: avgRowCount = (avgRowCount * (fc - 1) + size) / fc;
1985: }
1986: }
1987:
1988: /**
1989: * Delete a pass 2 field for a block of graph entries all with
1990: * the same class. The same ps'es can be used for all entries in the block.
1991: */
1992: public void deletePass2Block(DeletePacket graph, int blockStart,
1993: int blockEnd, CharBuf s, Connection con, boolean batch)
1994: throws SQLException {
1995: if (readOnly)
1996: return;
1997: PreparedStatement ps = null;
1998: try {
1999: final int count = blockEnd - blockStart;
2000: boolean useInList = (ourPkColumns.length == 1)
2001: && (count > 1);
2002: if (!batch && !useInList) {
2003: //delete one-by-one
2004: ps = deleteOneByOne(s, con, ps, blockStart, blockEnd,
2005: graph);
2006: } else {
2007: if (useInList) {
2008: ps = deleteWithInList(s, count, con, ps,
2009: blockStart, blockEnd, graph);
2010: } else if (batch) {
2011: ps = deleteWithBatch(s, con, ps, blockStart,
2012: blockEnd, graph);
2013: }
2014: }
2015: } finally {
2016: cleanup(ps);
2017: }
2018: }
2019:
2020: private PreparedStatement deleteWithBatch(CharBuf s,
2021: Connection con, PreparedStatement ps, int blockStart,
2022: int blockEnd, DeletePacket graph) throws SQLException {
2023: String sql = getDeleteAllLinkTableRowsSql(s);
2024: ps = con.prepareStatement(sql);
2025: for (int pos = blockStart; pos < blockEnd; pos++) {
2026: ((JdbcOID) graph.oids[pos]).setParams(ps, 1);
2027: ps.addBatch();
2028: }
2029: try {
2030: ps.executeBatch();
2031: } catch (Exception e) {
2032: throw mapException(e,
2033: "Batch delete link table rows failed: "
2034: + JdbcUtils.toString(e)
2035: + "\n"
2036: + "Field: "
2037: + fmd.getTypeQName()
2038: + "\n"
2039: + JdbcUtils.getPreparedStatementInfo(sql,
2040: ps));
2041: }
2042: return ps;
2043: }
2044:
2045: private PreparedStatement deleteWithInList(CharBuf s, int count,
2046: Connection con, PreparedStatement ps, int blockStart,
2047: int blockEnd, DeletePacket graph) throws SQLException {
2048: SqlDriver driver = getSqlDriver();
2049:
2050: if (count > driver.getMaxInOperands()) {
2051: //must break it up
2052: final int maxInOps = driver.getMaxInOperands();
2053: int amountOfFullRuns = count / driver.getMaxInOperands();
2054:
2055: final int amountLeft = count % driver.getMaxInOperands();
2056: ;
2057: String sql = null;
2058: int pos = blockStart;
2059: final String param = driver
2060: .getSqlParamString(ourPkColumns[0].jdbcType);
2061: getDeleteAllLinkTableRowsSqlWithInList(s);
2062: if (amountLeft > 0) {
2063: for (int i = 0; i < amountLeft; i++) {
2064: if (i != 0)
2065: s.append(",");
2066: s.append(param);
2067: }
2068: s.append(")");
2069:
2070: sql = s.toString();
2071: ps = con.prepareStatement(sql);
2072: for (int curIndex = 0; curIndex < amountLeft; curIndex++, pos++) {
2073: ((JdbcOID) graph.oids[pos]).setParams(ps,
2074: curIndex + 1);
2075: }
2076: try {
2077: ps.execute();
2078: } catch (Exception e) {
2079: throw mapException(e,
2080: "Batch delete link table rows failed: "
2081: + JdbcUtils.toString(e)
2082: + "\n"
2083: + "Field: "
2084: + fmd.getTypeQName()
2085: + "\n"
2086: + JdbcUtils
2087: .getPreparedStatementInfo(
2088: sql, ps));
2089: }
2090: s.set(s.size() - 1, ',');
2091: }
2092:
2093: for (int i = 0; i < maxInOps - amountLeft; i++) {
2094: if (i != 0)
2095: s.append(",");
2096: s.append(param);
2097: }
2098: s.append(")");
2099:
2100: sql = s.toString();
2101: ps = con.prepareStatement(sql);
2102: for (int i = 0; i < amountOfFullRuns; i++) {
2103: for (int curIndex = 0; curIndex < maxInOps; curIndex++, pos++) {
2104: ((JdbcOID) graph.oids[pos]).setParams(ps,
2105: curIndex + 1);
2106: }
2107: try {
2108: ps.execute();
2109: } catch (Exception e) {
2110: throw mapException(e,
2111: "Batch delete link table rows failed: "
2112: + JdbcUtils.toString(e)
2113: + "\n"
2114: + "Field: "
2115: + fmd.getTypeQName()
2116: + "\n"
2117: + JdbcUtils
2118: .getPreparedStatementInfo(
2119: sql, ps));
2120: }
2121: }
2122: } else {
2123: getDeleteAllLinkTableRowsSqlWithInList(s);
2124: for (int i = 0; i < count; i++) {
2125: if (i != 0)
2126: s.append(",");
2127: driver.appendWhereParam(s, ourPkColumns[0]);
2128: }
2129: s.append(")");
2130: String sql = s.toString();
2131: ps = con.prepareStatement(sql);
2132:
2133: int index = 1;
2134: for (int pos = blockStart; pos < blockEnd; pos++) {
2135: ((JdbcOID) graph.oids[pos]).setParams(ps, index++);
2136: }
2137: try {
2138: ps.execute();
2139: } catch (Exception e) {
2140: throw mapException(e,
2141: "Batch delete link table rows failed: "
2142: + JdbcUtils.toString(e)
2143: + "\n"
2144: + "Field: "
2145: + fmd.getTypeQName()
2146: + "\n"
2147: + JdbcUtils.getPreparedStatementInfo(
2148: sql, ps));
2149: }
2150: }
2151: return ps;
2152: }
2153:
2154: private PreparedStatement deleteOneByOne(CharBuf s, Connection con,
2155: PreparedStatement ps, int blockStart, int blockEnd,
2156: DeletePacket graph) throws SQLException {
2157: String sql = getDeleteAllLinkTableRowsSql(s);
2158: ps = con.prepareStatement(sql);
2159: for (int pos = blockStart; pos < blockEnd; pos++) {
2160: ((JdbcOID) graph.oids[pos]).setParams(ps, 1);
2161: try {
2162: ps.execute();
2163: } catch (Exception e) {
2164: throw mapException(e, "Delete link table rows failed: "
2165: + JdbcUtils.toString(e) + "\n" + "Field: "
2166: + fmd.getTypeQName() + "\n" + "Instance: "
2167: + graph.oids[pos].toSString() + "\n"
2168: + JdbcUtils.getPreparedStatementInfo(sql, ps));
2169: }
2170: }
2171: return ps;
2172: }
2173:
2174: /**
2175: * Convert this field into an isEmpty expression.
2176: */
2177: public SqlExp toIsEmptySqlExp(JdbcJDOQLCompiler comp, SelectExp root) {
2178: SelectExp se = new SelectExp();
2179: se.table = linkTable;
2180: se.jdbcField = this ;
2181: se.subSelectJoinExp = root.createJoinExp(root.table.pk,
2182: ourPkColumns, se);
2183: return new UnaryOpExp(new ExistsExp(se, true),
2184: UnaryOpExp.OP_NOT);
2185: }
2186:
2187: /**
2188: * Convert this field into a contains expression.
2189: */
2190: public SqlExp toContainsSqlExp(JdbcJDOQLCompiler comp,
2191: SelectExp root, Node args) {
2192: return toContainsSqlExp(valueColumns, fmd.elementTypeMetaData,
2193: comp, root, args);
2194: }
2195:
2196: protected SqlExp toContainsSqlExp(JdbcColumn[] cols,
2197: ClassMetaData colsCmd, JdbcJDOQLCompiler comp,
2198: SelectExp root, Node args) {
2199: SelectExp se = new SelectExp();
2200: se.table = linkTable;
2201: se.jdbcField = this ;
2202: se.subSelectJoinExp = root.createJoinExp(root.table.pk,
2203: ourPkColumns, se);
2204:
2205: if (args instanceof VarNodeIF) {
2206: VarNode v = ((VarNodeIF) args).getVarNode();
2207: if (v.getCmd() == null) {
2208: SelectExp storeExtent = new SelectExp();
2209: storeExtent.table = linkTable;
2210: storeExtent.var = v;
2211: v.setStoreExtent(storeExtent);
2212: v.setFieldExtent(se);
2213: v.setFmd(fmd);
2214: se.var = v;
2215: } else {
2216: SelectExp vse = (SelectExp) v.getStoreExtent();
2217: se.addJoin(cols, vse.table.pk, vse);
2218: se.var = v;
2219: }
2220: } else {
2221: SqlExp left = JdbcColumn.toSqlExp(cols, se);
2222: if (colsCmd != null) {
2223: for (SqlExp e = left; e != null; e = e.next) {
2224: ((ColumnExp) e).cmd = colsCmd;
2225: }
2226: }
2227: SqlExp right = comp.getVisitor().toSqlExp(args, root, left,
2228: 0, null);
2229: se.whereExp = SqlExp.createBinaryOpExp(left,
2230: BinaryOpExp.EQUAL, right);
2231: }
2232: return new ExistsExp(se, true,
2233: (args instanceof VarNodeIF ? (VarNodeIF) args : null));
2234: }
2235:
2236: /**
2237: * Process meta data for an inverse many-to-many collection.
2238: */
2239: private void processInverse(JdbcMetaDataBuilder mdb, JdoExtension e) {
2240: ClassMetaData ecmd = fmd.elementTypeMetaData;
2241: if (ecmd.storeClass == null) {
2242: throw BindingSupportImpl
2243: .getInstance()
2244: .runtime(
2245: "The inverse extension may only be used for "
2246: + "collections of PC instances stored by JDBC\n"
2247: + e.getContext());
2248: }
2249: String fname = e.getString();
2250: FieldMetaData f = ecmd.getFieldMetaData(fname);
2251: if (f.storeField == null) {
2252: throw BindingSupportImpl.getInstance().runtime(
2253: "Field '" + fname + "' is not persistent\n"
2254: + e.getContext());
2255: }
2256: if (!(f.storeField instanceof JdbcLinkCollectionField)) {
2257: throw BindingSupportImpl.getInstance().runtime(
2258: "Field '" + fname
2259: + "' is not a collection or array mapped "
2260: + "using a link table\n" + e.getContext());
2261: }
2262: inverse = (JdbcLinkCollectionField) f.storeField;
2263: if (f.elementTypeMetaData != fmd.classMetaData) {
2264: throw BindingSupportImpl.getInstance().runtime(
2265: "Field '" + fname + "' contains "
2266: + f.elementTypeMetaData
2267: + " and not our class\n" + e.getContext());
2268: }
2269: if (inverse == this ) {
2270: throw BindingSupportImpl
2271: .getInstance()
2272: .runtime(
2273: "Field '"
2274: + fname
2275: + "' may not be in a many-to-many with itself\n"
2276: + e.getContext());
2277: }
2278: inverse.inverse = this ;
2279: readOnly = true;
2280: valuesAreOIDs = true;
2281: fmd.ordered = false;
2282: fmd.isManyToMany = true;
2283: fmd.isReadOnly = true;
2284: fmd.inverseFieldMetaData = inverse.fmd;
2285: inverse.fmd.isManyToMany = true;
2286: inverse.fmd.inverseFieldMetaData = fmd;
2287: syncWithInverse(mdb);
2288: }
2289:
2290: /**
2291: * Sync our mapping info with our inverse. This will get called twice:
2292: * once for each side of the many-to-many.
2293: * @param mdb
2294: */
2295: private void syncWithInverse(JdbcMetaDataBuilder mdb) {
2296: if (inverse == null)
2297: return; // not inverse or inverse side not done
2298: if (readOnly) {
2299: linkTable = inverse.linkTable;
2300: if (linkTable != null) { // main side has been done
2301: valueColumns = inverse.ourPkColumns;
2302: ourPkColumns = inverse.valueColumns;
2303: sequenceColumn = inverse.sequenceColumn;
2304: createInverseIndex(mdb);
2305: }
2306: fmd.managed = inverse.fmd.managed;
2307: } else {
2308: inverse.syncWithInverse(mdb);
2309: }
2310: }
2311:
2312: /**
2313: * For inverse collections create an index on our primary key fields
2314: * in the link table of the main collection unless this has been disabled.
2315: * @param mdb
2316: */
2317: private void createInverseIndex(JdbcMetaDataBuilder mdb) {
2318: JdoExtension ext = JdoExtension.find(JdoExtensionKeys.INVERSE,
2319: getExtensions());
2320: JdoExtension[] nested = ext.nested;
2321:
2322: JdbcIndex idx = null;
2323: boolean doNotCreateIndex = false;
2324:
2325: int n = nested == null ? 0 : nested.length;
2326: for (int i = 0; i < n; i++) {
2327: JdoExtension e = nested[i];
2328: switch (e.key) {
2329: case JdoExtensionKeys.JDBC_INDEX:
2330: if (idx != null) {
2331: throw BindingSupportImpl.getInstance().runtime(
2332: "Only one jdbc-index extension is allowed here\n"
2333: + e.getContext());
2334: }
2335: if (e.isNoValue()) {
2336: doNotCreateIndex = true;
2337: break;
2338: }
2339: idx = new JdbcIndex();
2340: idx.name = e.value;
2341: break;
2342: default:
2343: if (e.isJdbc()) {
2344: MetaDataBuilder.throwUnexpectedExtension(e);
2345: }
2346: }
2347: }
2348:
2349: if (doNotCreateIndex)
2350: return;
2351: if (idx == null)
2352: idx = new JdbcIndex();
2353: idx.setCols(ourPkColumns);
2354:
2355: // register the name of the index if one was specified otherwise one
2356: // will be generated later along with user specified indexes
2357: if (idx.name != null) {
2358: try {
2359: mdb.getNameGenerator().addIndexName(linkTable.name,
2360: idx.name);
2361: } catch (IllegalArgumentException x) {
2362: throw BindingSupportImpl.getInstance().runtime(
2363: x.getMessage(), x);
2364: }
2365: }
2366:
2367: if (linkTable.indexes != null) {
2368: throw BindingSupportImpl.getInstance().internal(
2369: "Link table already has index: " + linkTable + ", "
2370: + fmd.getTypeQName());
2371: }
2372: linkTable.indexes = new JdbcIndex[] { idx };
2373: }
2374:
2375: /**
2376: * Make sure all the indexes on our link tables (if any) have names,
2377: */
2378: public void nameLinkTableIndexes(JdbcNameGenerator namegen) {
2379: if (readOnly)
2380: return;
2381: int n = linkTable == null ? 0 : linkTable.indexes == null ? 0
2382: : linkTable.indexes.length;
2383: for (int i = 0; i < n; i++) {
2384: JdbcIndex idx = linkTable.indexes[i];
2385: if (idx.name == null) {
2386: JdbcMetaDataBuilder.generateNameForIndex(namegen,
2387: linkTable.name, idx);
2388: }
2389: }
2390: }
2391:
2392: /**
2393: * Return SQL that will fetch all the rows in the link table.
2394: * This is used when bulk copying one database to another. The OID of
2395: * the owning table must be first followed by the other columns in the
2396: * order expected by readRow.
2397: *
2398: * @see #readRow
2399: */
2400: public String getFetchAllRowsSql(JdbcStorageManager sm)
2401: throws SQLException {
2402: SelectExp root = new SelectExp();
2403: root.table = linkTable;
2404: SqlExp e = root.selectList = JdbcColumn.toSqlExp(ourPkColumns,
2405: root);
2406: for (; e.next != null; e = e.next)
2407: ;
2408: if (fmd.ordered)
2409: e = e.next = sequenceColumn.toSqlExp(root);
2410: e = e.next = JdbcColumn.toSqlExp(valueColumns, root);
2411: addFetchAllRowsKey(e, root);
2412: return sm.generateSql(root).toString();
2413: }
2414:
2415: /**
2416: * Hook for JdbcMapField to add its key columns to the row. The returned
2417: * SqlExp must be the last one in the list.
2418: */
2419: protected void addFetchAllRowsKey(SqlExp e, SelectExp se) {
2420: }
2421:
2422: /**
2423: * Fetch a row of values for this field. This is used when bulk copying
2424: * one database to another to read all the rows in a given link table.
2425: * Return the index of the last column read + 1.
2426: */
2427: public int readRow(ResultSet rs, LinkRow row) throws SQLException {
2428: row.owner = (JdbcGenericOID) fmd.classMetaData.createOID(false);
2429: row.owner.copyKeyFields(rs, 1);
2430: int pos = ourPkColumns.length + 1;
2431: if (fmd.ordered) {
2432: row.seq = ((Integer) sequenceColumn.get(rs, pos++))
2433: .intValue();
2434: }
2435: if (valuesAreOIDs) {
2436: OID valueOid = fmd.elementTypeMetaData.createOID(false);
2437: ((JdbcOID) valueOid).copyKeyFields(rs, pos);
2438: row.value = valueOid;
2439: pos += valueColumns.length;
2440: } else {
2441: row.value = valueColumns[0].get(rs, pos++);
2442: }
2443: return pos;
2444: }
2445:
2446: /**
2447: * Set a row of values for this field on a PreparedStatement.
2448: * This is used when bulk copying one database to another.
2449: */
2450: public void writeRow(PreparedStatement ps, LinkRow row)
2451: throws SQLException {
2452: row.owner.setCmd(fmd.classMetaData);
2453: int pos = row.owner.setParams(ps, 1);
2454: if (fmd.ordered)
2455: sequenceColumn.set(ps, pos++, row.seq);
2456: if (valuesAreOIDs) {
2457: JdbcGenericOID v = (JdbcGenericOID) row.value;
2458: v.setCmd(fmd.classMetaData);
2459: v.setParams(ps, pos);
2460: } else {
2461: valueColumns[0].set(ps, pos, row.value);
2462: }
2463: }
2464:
2465: /**
2466: * A row from our link table. This is used by the bulk database copying
2467: * operations.
2468: */
2469: public static class LinkRow {
2470:
2471: public JdbcGenericOID owner;
2472: public int seq;
2473: public Object key;
2474: public Object value;
2475: }
2476:
2477: }
|