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.ClassMetaData;
014: import com.versant.core.metadata.FieldMetaData;
015: import com.versant.core.jdbc.JdbcKeyGenerator;
016: import com.versant.core.jdbc.sql.exp.*;
017: import com.versant.core.jdbc.sql.SqlDriver;
018: import com.versant.core.common.Debug;
019: import com.versant.core.util.CharBuf;
020:
021: import java.io.Serializable;
022: import java.io.PrintStream;
023: import java.util.*;
024:
025: /**
026: * Extra meta data for a class stored in JDBC. Some of the fields are
027: * also present in the normal ClassMetaData and are duplicated here for
028: * performance reasons.
029: */
030: public final class JdbcClass implements Serializable {
031:
032: /**
033: * Do not detect concurrent updates to this class.
034: */
035: public static final int OPTIMISTIC_LOCKING_NONE = 1;
036: /**
037: * Detect concurrent updates to this class by using a version column.
038: */
039: public static final int OPTIMISTIC_LOCKING_VERSION = 2;
040: /**
041: * Detect concurrent updates to this class by using a timestamp column.
042: */
043: public static final int OPTIMISTIC_LOCKING_TIMESTAMP = 3;
044: /**
045: * Detect concurrent updates to this class by including the previous
046: * values of all changed fields in the where clause.
047: */
048: public static final int OPTIMISTIC_LOCKING_CHANGED = 4;
049:
050: /**
051: * Subclass fields stored in the table of its immediate superclass.
052: */
053: public static final int INHERITANCE_FLAT = 1;
054: /**
055: * Subclass fields stored in its own table.
056: */
057: public static final int INHERITANCE_VERTICAL = 2;
058: /**
059: * Fields stored in subclass table.
060: */
061: public static final int INHERITANCE_HORIZONTAL = 3;
062:
063: /**
064: * The normal meta data for our class.
065: */
066: public ClassMetaData cmd;
067: /**
068: * Our SqlDriver.
069: */
070: public final SqlDriver sqlDriver;
071: /**
072: * The inheritance strategy for this class. This will be INHERITANCE_FLAT
073: * if the class is not in a heirachy or is a base class.
074: *
075: * @see #INHERITANCE_FLAT
076: * @see #INHERITANCE_VERTICAL
077: */
078: public int inheritance;
079: /**
080: * Our table. This may be the table of the pcSuperClass if this is a
081: * subclass with fields stored in the base table.
082: */
083: public JdbcTable table;
084: /**
085: * The name of our table.
086: */
087: public String tableName;
088: /**
089: * All the tables for this class including superclass tables. The entry
090: * at 0 is the table for the topmost superclass. Also
091: * table == allTables[allTables.length - 1].
092: */
093: public JdbcTable[] allTables;
094: /**
095: * The fields in fieldNo order. Note that if a fieldNo is transactional
096: * then its entry here will be null.
097: */
098: public JdbcField[] fields;
099: /**
100: * The fields in State fieldNo order. Note that if a fieldNo is
101: * transactional then its entry here will be null. This includes all
102: * fields from superclasses.
103: */
104: public JdbcField[] stateFields;
105: /**
106: * mapping from column name to the jdbc fields
107: */
108: private transient Map colNamesToJdbcField;
109: /**
110: * The JDBC key generator for this class (null if no key generator is
111: * required).
112: */
113: public JdbcKeyGenerator jdbcKeyGenerator;
114: /**
115: * This becomes the default value for the useJoin field for references
116: * to this class.
117: *
118: * @see JdbcRefField#useJoin
119: */
120: public int useJoin;
121: /**
122: * Our classId for this class. This does not have to be the same as that
123: * in ClassMetaData. It only has to be unique within an inheritance
124: * heirachy. This will default to the classId of the class. If the
125: * classIdCol column is a number (INT etc) then this must be a Number.
126: * Otherwise it must be a String.
127: *
128: * @see ClassMetaData#classId
129: */
130: public Object jdbcClassId;
131: /**
132: * The column used to hold the classId value for each row. This is used
133: * to implement inheritance. It may be null if this class has no persistent
134: * subclasses or if all of the subclasses use vertical inheritance. This
135: * field is set to the same value for all the classes in a heirachy.
136: */
137: public JdbcColumn classIdCol;
138: /**
139: * Treat rows in the database that would be instances of us as instances
140: * of readAsClass instead. This is used to implement flat inheritance
141: * with no descriminator where rows are instances of the leaf class.
142: */
143: public ClassMetaData readAsClass;
144: /**
145: * How optimistic locking is done for this class (one of the
146: * OPTIMISTIC_LOCKING_xxx constants).
147: */
148: public int optimisticLocking;
149: /**
150: * The field used to store the row version or timestamp value for
151: * this table for optimistic locking. It may be null. This may be
152: * a fake field.
153: */
154: public JdbcSimpleField optimisticLockingField;
155: /**
156: * If this flag is set the the table for this class will not be created
157: * by the create schema task or the workbench.
158: */
159: public boolean doNotCreateTable;
160: /**
161: * Cache for SQL required to delete a main table row for this class.
162: */
163: public String deleteRowSql;
164: /**
165: * Cache for SQL required to lock the main table row for this class.
166: */
167: public String lockRowSql;
168: /**
169: * Do not use statement batching with this class.
170: */
171: public boolean noBatching;
172:
173: private String lockRowColumnName; // column used for update locking
174:
175: public JdbcClass(SqlDriver sqlDriver) {
176: this .sqlDriver = sqlDriver;
177: }
178:
179: public String toString() {
180: return cmd.qname + " (" + table.name + ")";
181: }
182:
183: /**
184: * Add all tables that belong to this class to the set.
185: */
186: public void getTables(HashSet tables) {
187: if (table != null) {
188: tables.add(table);
189: }
190: int n = 0;
191: if (fields != null) {
192: n = fields.length;
193: }
194: for (int i = 0; i < n; i++) {
195: JdbcField f = fields[i];
196: if (f != null)
197: f.getTables(tables);
198: }
199: }
200:
201: /**
202: * Build the stateFields array for this class.
203: */
204: public void buildStateFields() {
205: if (cmd.pcSuperMetaData == null)
206: buildStateFieldsImp();
207: }
208:
209: private void buildStateFieldsImp() {
210: if (cmd.pcSuperMetaData != null) {
211: JdbcField[] super StateFields = ((JdbcClass) cmd.pcSuperMetaData.storeClass).stateFields;
212: if (super StateFields != null) {
213: int n = super StateFields.length;
214: stateFields = new JdbcField[n + fields.length];
215: System
216: .arraycopy(super StateFields, 0, stateFields, 0,
217: n);
218: System.arraycopy(fields, 0, stateFields, n,
219: fields.length);
220: }
221: } else {
222: stateFields = fields;
223: }
224: if (cmd.pcSubclasses != null) {
225: for (int i = cmd.pcSubclasses.length - 1; i >= 0; i--) {
226: ((JdbcClass) cmd.pcSubclasses[i].storeClass)
227: .buildStateFieldsImp();
228: }
229: }
230: }
231:
232: /**
233: * Set the key generator for this class and recursively all its subclasses.
234: */
235: public void setJdbcKeyGenerator(JdbcKeyGenerator jdbcKeyGenerator) {
236: this .jdbcKeyGenerator = jdbcKeyGenerator;
237: cmd.useKeyGen = true;
238: cmd.postInsertKeyGenerator = jdbcKeyGenerator
239: .isPostInsertGenerator();
240: ClassMetaData[] pcSubclasses = cmd.pcSubclasses;
241: if (pcSubclasses == null)
242: return;
243: for (int i = 0; i < pcSubclasses.length; i++) {
244: ((JdbcClass) pcSubclasses[i].storeClass)
245: .setJdbcKeyGenerator(jdbcKeyGenerator);
246: }
247: }
248:
249: /**
250: * Copy our optimistic locking settings to all of our subclasses.
251: */
252: public void copyOptimisticLockingToSubs() {
253: ClassMetaData[] pcSubclasses = cmd.pcSubclasses;
254: if (pcSubclasses == null)
255: return;
256: for (int i = 0; i < pcSubclasses.length; i++) {
257: JdbcClass sc = (JdbcClass) pcSubclasses[i].storeClass;
258: sc.optimisticLocking = optimisticLocking;
259: sc.cmd.changedOptimisticLocking = optimisticLocking == JdbcClass.OPTIMISTIC_LOCKING_CHANGED;
260: sc.optimisticLockingField = optimisticLockingField;
261: if (optimisticLockingField != null) {
262: sc.cmd.optimisticLockingField = optimisticLockingField.fmd;
263: }
264: sc.copyOptimisticLockingToSubs();
265: }
266: }
267:
268: /**
269: * Set the classIdField for this class and recursively all its subclasses.
270: */
271: public void setClassIdCol(JdbcColumn classIdCol) {
272: this .classIdCol = classIdCol;
273: ClassMetaData[] pcSubclasses = cmd.pcSubclasses;
274: if (pcSubclasses == null)
275: return;
276: for (int i = 0; i < pcSubclasses.length; i++) {
277: ((JdbcClass) pcSubclasses[i].storeClass)
278: .setClassIdCol(classIdCol);
279: }
280: }
281:
282: /**
283: * Find the class with the given JDBC classId. This might be ourselves or
284: * one of our subclasses. Returns null if not found.
285: */
286: public ClassMetaData findClass(Object jdbcClassId) {
287: if (this .jdbcClassId.equals(jdbcClassId))
288: return cmd;
289: ClassMetaData[] subs = cmd.pcSubclasses;
290: if (subs != null) {
291: for (int i = subs.length - 1; i >= 0; i--) {
292: ClassMetaData ans = ((JdbcClass) subs[i].storeClass)
293: .findClass(jdbcClassId);
294: if (ans != null)
295: return ans;
296: }
297: }
298: return null;
299: }
300:
301: /**
302: * Is this class stored in a different table to the base class in the
303: * heirachy?
304: */
305: public boolean isMultiTableHeirachy() {
306: return table != ((JdbcClass) cmd.pcHeirachy[0].storeClass).table;
307: }
308:
309: public void dump() {
310: dump(Debug.OUT, "");
311: }
312:
313: public void dump(PrintStream out, String indent) {
314: out.println(indent + this );
315: String is = indent + " ";
316: out.println(is + "cmd = " + cmd);
317: out.println(is + "jdbcKeyGenerator = " + jdbcKeyGenerator);
318: out.println(is + "useJoin = "
319: + JdbcRefField.toUseJoinString(useJoin));
320: out.println(is + "classId = " + jdbcClassId);
321: out.println(is + "classIdCol = " + classIdCol);
322: out.println(is + "optimisticLocking = "
323: + toOptimisticLockingString(optimisticLocking));
324: out.println(is + "optLockFieldStateFieldNo = "
325: + optimisticLockingField);
326: out.println(is
327: + "fields = "
328: + (fields == null ? "null" : Integer
329: .toString(fields.length)));
330: out.println(is
331: + "stateFields = "
332: + (stateFields == null ? "null" : Integer
333: .toString(stateFields.length)));
334: }
335:
336: public static String toOptimisticLockingString(int o) {
337: switch (o) {
338: case OPTIMISTIC_LOCKING_NONE:
339: return "none";
340: case OPTIMISTIC_LOCKING_CHANGED:
341: return "changed";
342: case OPTIMISTIC_LOCKING_VERSION:
343: return "version";
344: case OPTIMISTIC_LOCKING_TIMESTAMP:
345: return "timestamp";
346: }
347: return "unknown(" + o + ")";
348: }
349:
350: /**
351: * Find a primary key field of this class or the topmost superclass in
352: * the heirachy by name or null if none.
353: */
354: public JdbcSimpleField findPkField(String fname) {
355: FieldMetaData f = cmd.findPkField(fname);
356: if (f == null)
357: return null;
358: return (JdbcSimpleField) f.storeField;
359: }
360:
361: /**
362: * Find the index of the primary key field f or -1 if none.
363: */
364: public int findPkFieldIndex(JdbcSimpleField f) {
365: for (int i = cmd.pkFields.length - 1; i >= 0; i--) {
366: if (cmd.pkFields[i].storeField == f)
367: return i;
368: }
369: return -1;
370: }
371:
372: /**
373: * Get the maximum number of OIDs for this class that can be included
374: * in an IN (?, .., ?) statement. This depends on the database and the
375: * number of columns in the primary key. The return value is zero if
376: * this class has a composite primary key.
377: */
378: public int getMaxOIDsForIN(SqlDriver sqlDriver) {
379: if (table.pkSimpleColumnCount > 1)
380: return 0;
381: return sqlDriver.getMaxInOperands();
382: }
383:
384: /**
385: * Find all fields in this class that have columnName in their main table
386: * columns and add them to list.
387: */
388: public void findFieldsForColumn(ClassMetaData cmd,
389: String columnName, List list) {
390: for (int i = 0; i < fields.length; i++) {
391: JdbcField f = fields[i];
392: if (f != null && f.findMainTableColumn(columnName) != null) {
393: ClassMetaData fcmd = f.fmd.classMetaData;
394: if ((cmd.isAncestorOrSelf(fcmd) || fcmd
395: .isAncestorOrSelf(cmd))) {
396: list.add(f);
397: }
398: }
399: }
400: }
401:
402: /**
403: * Find any columns in any of our subclasses (and recursively their
404: * subclasses) with columnName and set shared = true for them.
405: */
406: public void markSubclassColumnsShared(String columnName) {
407: if (cmd.pcSubclasses == null)
408: return;
409: for (int i = cmd.pcSubclasses.length - 1; i >= 0; i--) {
410: JdbcClass jc = (JdbcClass) cmd.pcSubclasses[i].storeClass;
411: jc.markColumnsShared(columnName, table);
412: jc.markSubclassColumnsShared(columnName);
413: }
414: }
415:
416: /**
417: * Mark any columns for fields in this class with name columnName
418: * shared = true unless they are primary key fields.
419: */
420: public void markColumnsShared(String columnName, JdbcTable cTable) {
421: for (int i = 0; i < fields.length; i++) {
422: JdbcField f = fields[i];
423: if (f == null || f.fmd.primaryKey)
424: continue;
425: if (f.mainTable != cTable)
426: continue;
427: JdbcColumn c = f.findMainTableColumn(columnName);
428: if (c != null)
429: c.setShared(true);
430: }
431: }
432:
433: /**
434: * Get the column that should be used for update locking.
435: */
436: public String getLockRowColumnName() {
437: if (lockRowColumnName == null) {
438: lockRowColumnName = table.getLockRowColumn().name;
439: }
440: return lockRowColumnName;
441: }
442:
443: /**
444: * Set the table for this class. This will also set the tablename and
445: * allTables fields. This method must only be invoked one a subclass
446: * if it has been called on its superclass.
447: */
448: public void setTable(JdbcTable table) {
449: this .table = table;
450: tableName = table.name;
451: ArrayList a = new ArrayList();
452: JdbcTable prev = null;
453: for (int i = 0; i < cmd.pcHeirachy.length; i++) {
454: JdbcTable t = ((JdbcClass) cmd.pcHeirachy[i].storeClass).table;
455: if (t != prev)
456: a.add(t);
457: prev = t;
458: }
459: allTables = new JdbcTable[a.size()];
460: a.toArray(allTables);
461: }
462:
463: /**
464: * Get a LiteralExp for our jdbc-class-id. If subclasses is true then
465: * a list of LiteralExp's are returned including all the
466: */
467: public LiteralExp getClassIdExp(boolean subclasses) {
468: LiteralExp root = classIdCol
469: .createClassIdLiteralExp(jdbcClassId);
470:
471: if (subclasses && cmd.pcSubclasses != null) {
472: ClassMetaData[] a = cmd.pcSubclasses;
473: SqlExp pos = root;
474: for (int i = a.length - 1; i >= 0; i--) {
475: pos = pos.next = ((JdbcClass) a[i].storeClass)
476: .getClassIdExp(true);
477: for (; pos.next != null; pos = pos.next)
478: ;
479: }
480: }
481: return root;
482: }
483:
484: /**
485: * Get an SqlExp that will only return instances of this class or one
486: * of its subclasses from this table.
487: *
488: * @param se A select against our table
489: */
490: public SqlExp getCheckClassIdExp(SelectExp se) {
491: if (classIdCol == null)
492: return null;
493: SqlExp colExp = classIdCol.toSqlExp(se);
494: LiteralExp idExp = getClassIdExp(true);
495: if (idExp.next == null) {
496: return new BinaryOpExp(colExp, BinaryOpExp.EQUAL, idExp);
497: } else {
498: colExp.next = idExp;
499: return new InExp(colExp);
500: }
501: }
502:
503: /**
504: * See if our jdbcClassId that those of all of our subclasses are ints.
505: * This is used to default the type of the descriminator column to
506: * INTEGER if possible.
507: */
508: public boolean isIntJdbcClassIdHeirachy() {
509: if (jdbcClassId == null)
510: return false;
511: if (jdbcClassId instanceof Integer)
512: return true;
513: try {
514: Integer.parseInt((String) jdbcClassId);
515: } catch (NumberFormatException e) {
516: return false;
517: }
518: if (cmd.pcSubclasses == null)
519: return true;
520: for (int i = cmd.pcSubclasses.length - 1; i >= 0; i--) {
521: if (!((JdbcClass) cmd.pcSubclasses[i].storeClass)
522: .isIntJdbcClassIdHeirachy()) {
523: return false;
524: }
525: }
526: return true;
527: }
528:
529: /**
530: * Convert our jdbcClassId to an Integer if it is not already an Integer.
531: * Recursively process subclasses.
532: */
533: public void convertJdbcClassIdToInteger() {
534: if (jdbcClassId instanceof String) {
535: jdbcClassId = new Integer((String) jdbcClassId);
536: }
537: if (cmd.pcSubclasses != null) {
538: for (int i = cmd.pcSubclasses.length - 1; i >= 0; i--) {
539: ((JdbcClass) cmd.pcSubclasses[i].storeClass)
540: .convertJdbcClassIdToInteger();
541: }
542: }
543: }
544:
545: public Map getColNamesToJdbcField() {
546: if (colNamesToJdbcField == null) {
547: colNamesToJdbcField = new HashMap(stateFields.length);
548: JdbcField[] fs = stateFields;
549: for (int i = 0; i < fs.length; i++) {
550: JdbcField f = fs[i];
551: if (f.mainTableCols != null) {
552: JdbcColumn[] cols = f.mainTableCols;
553: for (int j = 0; j < cols.length; j++) {
554: JdbcColumn col = cols[j];
555: colNamesToJdbcField.put(col.name.toUpperCase(),
556: f);
557: }
558: }
559: }
560: }
561: return colNamesToJdbcField;
562: }
563:
564: /**
565: * Get SQL to lock a row in our table.
566: */
567: public String getLockRowSql() {
568: if (lockRowSql == null) {
569: CharBuf s = new CharBuf();
570: s.append("UPDATE ");
571: s.append(table.name);
572: s.append(" SET ");
573: String c = getLockRowColumnName();
574: s.append(c);
575: s.append('=');
576: s.append(c);
577: s.append(" WHERE ");
578: table.appendWherePK(s);
579: lockRowSql = s.toString();
580: }
581: return lockRowSql;
582: }
583:
584: }
|