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.MDStatics;
014: import com.versant.core.metadata.ClassMetaData;
015: import com.versant.core.metadata.ClassIdTranslator;
016: import com.versant.core.metadata.parser.JdoField;
017: import com.versant.core.common.OID;
018: import com.versant.core.jdbc.sql.JdbcNameGenerator;
019: import com.versant.core.jdbc.sql.SqlDriver;
020: import com.versant.core.jdbc.sql.exp.*;
021: import com.versant.core.jdbc.JdbcMetaDataBuilder;
022: import com.versant.core.jdbc.JdbcPolyRefMetaDataBuilder;
023: import com.versant.core.jdbc.JdbcOID;
024: import com.versant.core.util.CharBuf;
025: import com.versant.core.common.State;
026:
027: import java.util.ArrayList;
028: import java.io.PrintStream;
029: import java.sql.PreparedStatement;
030: import java.sql.SQLException;
031: import java.sql.ResultSet;
032:
033: /**
034: * A field that is a polymorphic reference to any other PC class (e.g. an
035: * Object or interface reference).
036: */
037: public class JdbcPolyRefField extends JdbcField {
038:
039: /**
040: * The column used to hold the classId value identifying the class (and
041: * hence table) that is being referenced.
042: * @see #cols
043: */
044: public JdbcColumn classIdCol;
045: /**
046: * The columns used to store the classId and the primary key of the
047: * referenced (target) tables.
048: * @see #cols
049: */
050: public JdbcColumn[] refCols;
051: /**
052: * @keep
053: * The classIdCol at index 0 and pkCols.
054: */
055: public JdbcColumn[] cols;
056:
057: private ClassIdTranslator classIdTranslator;
058: private int literalType;
059:
060: public String toString() {
061: StringBuffer s = new StringBuffer();
062: s.append(super .toString());
063: switch (useJoin) {
064: case USE_JOIN_INNER:
065: s.append(" INNER");
066: case USE_JOIN_OUTER:
067: s.append(" OUTER");
068: }
069: return s.toString();
070: }
071:
072: /**
073: * Complete our meta data except for column names.
074: */
075: public void processMetaData(JdoField context,
076: JdbcMetaDataBuilder mdb) {
077:
078: JdbcPolyRefMetaDataBuilder rdb = new JdbcPolyRefMetaDataBuilder(
079: mdb, context, fmd.name, fmd.jdoField == null ? null
080: : fmd.jdoField.extensions, this );
081:
082: classIdCol = rdb.getClassIdCol();
083: refCols = rdb.getPkCols();
084: cols = rdb.getCols();
085:
086: int nc = cols.length;
087: for (int i = 0; i < nc; i++)
088: cols[i].comment = fmd.getCommentName();
089:
090: boolean nulls = fmd.nullValue != MDStatics.NULL_VALUE_EXCEPTION;
091: for (int i = 0; i < nc; i++)
092: cols[i].nulls = nulls;
093:
094: // include this in the where clause for changed locking only if all
095: // columns support equalityTest
096: includeForChangedLocking = true;
097: for (int i = 0; i < nc; i++) {
098: if (!cols[i].equalityTest) {
099: includeForChangedLocking = false;
100: break;
101: }
102: }
103:
104: useJoin = JdbcRefField.USE_JOIN_NO;
105:
106: literalType = JdbcTypes.getLiteralType(classIdCol.jdbcType);
107: classIdTranslator = rdb.getClassIdTranslator();
108: }
109:
110: /**
111: * Set the table field on all our main table columns.
112: */
113: public void setMainTable(JdbcTable table) {
114: super .setMainTable(table);
115: for (int i = 0; i < cols.length; i++)
116: cols[i].setTable(table);
117: }
118:
119: /**
120: * Init the mainTableCols field to all our main table columns.
121: */
122: public void initMainTableCols() {
123: mainTableCols = cols;
124: super .initMainTableCols();
125: }
126:
127: /**
128: * Flatten all of this fields main table columns to a.
129: */
130: public void addMainTableCols(ArrayList a) {
131: int n = cols.length;
132: for (int i = 0; i < n; i++)
133: a.add(cols[i]);
134: }
135:
136: /**
137: * Make sure all of this fields main table columns have names.
138: */
139: public void nameColumns(String tableName, JdbcNameGenerator nameGen) {
140: // extract the current names of the columns
141: String[] names = new String[cols.length];
142: for (int i = 0; i < cols.length; i++)
143: names[i] = cols[i].name;
144:
145: // generate the names for the unnamed columns
146: nameGen.generatePolyRefFieldColumnNames(tableName, fmd.name,
147: names);
148:
149: // set our column names
150: for (int i = 0; i < cols.length; i++)
151: cols[i].name = names[i];
152: }
153:
154: public void dump(PrintStream out, String indent) {
155: super .dump(out, indent);
156: String is = indent + " ";
157: if (cols != null) {
158: out.println(is + cols.length + " cols(s)");
159: for (int i = 0; i < cols.length; i++) {
160: out.println(is + "[" + i + "] " + cols[i]);
161: }
162: }
163: }
164:
165: /**
166: * Append part of an update statement for us to s (e.g col = ?).
167: */
168: public boolean appendUpdate(CharBuf s, State state) {
169: int nc = mainTableColsForUpdate.length;
170: s.append(mainTableColsForUpdate[0].name);
171: s.append("=?");
172: for (int i = 1; i < nc; i++) {
173: s.append(", ");
174: s.append(mainTableColsForUpdate[i].name);
175: s.append("=?");
176: }
177: return false;
178: }
179:
180: /**
181: * Append part of a where clause for us to s (e.g cola = ? and colb = ?).
182: * This is used for generating the where clause for changed locking.
183: */
184: public void appendWhere(CharBuf s, SqlDriver sqlDriver) {
185: int nc = mainTableColsForUpdate.length;
186: JdbcColumn c = mainTableColsForUpdate[0];
187: s.append(c.name);
188: s.append('=');
189: sqlDriver.appendWhereParam(s, c);
190: for (int i = 1; i < nc; i++) {
191: c = mainTableColsForUpdate[i];
192: s.append(" and ");
193: s.append(c.name);
194: s.append('=');
195: sqlDriver.appendWhereParam(s, c);
196: }
197: }
198:
199: /**
200: * Append part of a is null where clause for us to s (e.g cola is null
201: * and colb is null).
202: * This is used for generating the where clause for changed locking.
203: */
204: public void appendWhereIsNull(CharBuf s, SqlDriver sqlDriver) {
205: int nc = mainTableColsForUpdate.length;
206: JdbcColumn c = mainTableColsForUpdate[0];
207: s.append(c.name);
208: s.append(" is null");
209: for (int i = 1; i < nc; i++) {
210: c = mainTableColsForUpdate[i];
211: s.append(" and ");
212: s.append(c.name);
213: s.append(" is null");
214: }
215: }
216:
217: /**
218: * Append part of the insert list for us to s (e.g. cola, colb)).
219: */
220: public void appendInsertColumnList(CharBuf s) {
221: int nc = mainTableColsForUpdate.length;
222: s.append(mainTableColsForUpdate[0].name);
223: for (int i = 1; i < nc; i++) {
224: s.append(", ");
225: s.append(mainTableColsForUpdate[i].name);
226: }
227: }
228:
229: /**
230: * Append part of the insert value list for us to s (e.g. ?, ?)). This
231: * must return true if a replacable parameter was <b>not</b> added (e.g.
232: * columns using Oracle LOBs which put in empty_clob() or whatever).
233: */
234: public boolean appendInsertValueList(CharBuf s, State state) {
235: s.append('?');
236: int nc = mainTableColsForUpdate.length;
237: for (int i = 1; i < nc; i++)
238: s.append(", ?");
239: return false;
240: }
241:
242: /**
243: * Convert this field into a list of ColumnExp's or null if this is
244: * not possible.
245: */
246: public ColumnExp toColumnExp(SelectExp se, boolean joinToSuper) {
247: if (joinToSuper)
248: se = SelectExp.createJoinToSuperTable(se, this );
249:
250: ColumnExp ans = new ColumnExp(cols[0], se, this );
251: SqlExp e = ans;
252: int nc = cols.length;
253: for (int i = 1; i < nc; i++) {
254: e = e.next = new ColumnExp(cols[i], se, this );
255: }
256: return ans;
257: }
258:
259: /**
260: * Convert this field into a list of ColumnExp's to be compared to
261: * a null literal. This should only include non-shared columns i.e.
262: * columns that are updated. If all columns are shared then all should
263: * be included.
264: */
265: public ColumnExp toColumnExpForNullLiteralCompare(SelectExp se) {
266: se = SelectExp.createJoinToSuperTable(se, this );
267:
268: if (mainTableColsForUpdate == null)
269: return toColumnExp(se, true);
270: ColumnExp ans = new ColumnExp(mainTableColsForUpdate[0], se,
271: this );
272: SqlExp e = ans;
273: int nc = mainTableColsForUpdate.length;
274: for (int i = 1; i < nc; i++) {
275: e = e.next = new ColumnExp(mainTableColsForUpdate[i], se,
276: this );
277: }
278: return ans;
279: }
280:
281: /**
282: * Set this field on a PreparedStatement. This is used to set parameters
283: * for queries.
284: * @return Index of the parameter after the last one we set in ps
285: */
286: public int setQueryParam(PreparedStatement ps, int firstParam,
287: Object value) throws SQLException {
288: OID oid = (OID) value;
289: if (oid != null) {
290: ClassMetaData target = oid.getClassMetaData();
291: if (classIdTranslator.isStringClassIds()) {
292: String id = classIdTranslator
293: .getStringClassIdForClass(target);
294: classIdCol.set(ps, firstParam, id);
295: } else {
296: int id = classIdTranslator
297: .getIntClassIdForClass(target);
298: classIdCol.set(ps, firstParam, id);
299: }
300: firstParam = ((JdbcOID) oid).setParams(ps, ++firstParam);
301: } else {
302: int nc = cols.length;
303: for (int i = 0; i < nc; i++) {
304: ps.setNull(firstParam++, cols[i].jdbcType);
305: }
306: }
307: return firstParam;
308: }
309:
310: /**
311: * Set this field on a PS for inserting or updating a row.
312: * @param firstParam Index of first parameter to set
313: * @return Index of next parameter to be set on ps
314: */
315: public int setData(PreparedStatement ps, int firstParam, OID oid)
316: throws SQLException {
317: if (oid != null) {
318: if (classIdCol.isForUpdate()) {
319: ClassMetaData target = oid.getClassMetaData();
320: if (classIdTranslator.isStringClassIds()) {
321: String id = classIdTranslator
322: .getStringClassIdForClass(target);
323: classIdCol.set(ps, firstParam++, id);
324: } else {
325: int id = classIdTranslator
326: .getIntClassIdForClass(target);
327: classIdCol.set(ps, firstParam++, id);
328: }
329: }
330: firstParam = ((JdbcOID) oid).setParams(ps, firstParam,
331: refCols);
332: } else {
333: JdbcColumn[] cols = mainTableCols;
334: int nc = cols.length;
335: for (int i = 0; i < nc; i++) {
336: JdbcColumn col = cols[i];
337: if (col.isForUpdate()) {
338: ps.setNull(firstParam++, col.jdbcType);
339: }
340: }
341: }
342: return firstParam;
343: }
344:
345: /**
346: * Get this field from a ResultSet starting at index firstCol.
347: */
348: public OID getData(ResultSet rs, int firstCol) throws SQLException {
349: ClassMetaData target;
350: if (classIdTranslator.isStringClassIds()) {
351: String id = (String) classIdCol.get(rs, firstCol);
352: if (id == null)
353: return null;
354: target = classIdTranslator.getClassForStringClassId(id);
355: } else {
356: int id = classIdCol.getInt(rs, firstCol);
357: if (id == 0)
358: return null;
359: target = classIdTranslator.getClassForIntClassId(id);
360: }
361: JdbcOID oid = (JdbcOID) target
362: .createOID(target.pcSubclasses == null);
363: if (oid.copyKeyFields(rs, firstCol + 1))
364: return oid;
365: else
366: return null;
367: }
368:
369: /**
370: * Create an expression to compare our classIdCol to the correct class-id
371: * value for target.
372: */
373: public SqlExp createClassIdMatchExp(SelectExp root,
374: ClassMetaData target) {
375: SqlExp left = classIdCol.toSqlExp(root);
376: String id = classIdTranslator.isStringClassIds() ? classIdTranslator
377: .getStringClassIdForClass(target)
378: : Integer.toString(classIdTranslator
379: .getIntClassIdForClass(target));
380: LiteralExp right = new LiteralExp(literalType, id);
381: return new BinaryOpExp(left, BinaryOpExp.EQUAL, right);
382: }
383:
384: }
|