001: /*
002: * Copyright 2002 (C) TJDO.
003: * All rights reserved.
004: *
005: * This software is distributed under the terms of the TJDO License version 1.0.
006: * See the terms of the TJDO License in the documentation provided with this software.
007: *
008: * $Id: BaseTable.java,v 1.8 2003/04/28 00:52:17 jackknifebarber Exp $
009: */
010:
011: package com.triactive.jdo.store;
012:
013: import com.triactive.jdo.model.ClassMetaData;
014: import java.sql.Connection;
015: import java.sql.DatabaseMetaData;
016: import java.sql.ResultSet;
017: import java.sql.SQLException;
018: import java.sql.Statement;
019: import java.util.ArrayList;
020: import java.util.Collection;
021: import java.util.HashMap;
022: import java.util.HashSet;
023: import java.util.Iterator;
024: import java.util.List;
025: import java.util.Map;
026: import java.util.Set;
027: import org.apache.log4j.Category;
028:
029: abstract class BaseTable extends AbstractTable {
030: private static final Category LOG = Category
031: .getInstance(BaseTable.class);
032:
033: public BaseTable(StoreManager storeMgr) {
034: super (storeMgr);
035: }
036:
037: public BaseTable(SQLIdentifier name, StoreManager storeMgr) {
038: super (name, storeMgr);
039: }
040:
041: public PrimaryKey getExpectedPrimaryKey() {
042: PrimaryKey pk = null;
043:
044: Iterator i = columns.iterator();
045:
046: while (i.hasNext()) {
047: Column col = (Column) i.next();
048:
049: if (col.isPrimaryKeyPart()) {
050: if (pk == null)
051: pk = new PrimaryKey(this );
052:
053: pk.addColumn(col);
054: }
055: }
056:
057: return pk;
058: }
059:
060: public void create(Connection conn) throws SQLException {
061: LOG.info("Creating table: " + this );
062:
063: super .create(conn);
064: }
065:
066: public boolean validate(int flags, Connection conn)
067: throws SQLException {
068: assertIsInitialized();
069:
070: boolean dbWasModified = false;
071:
072: if ((flags & VALIDATE) != 0) {
073: int tableType = storeMgr.getTableType(name, conn);
074:
075: if (tableType == TABLE_TYPE_MISSING) {
076: if ((flags & AUTO_CREATE) == 0)
077: throw new MissingTableException(this );
078:
079: create(conn);
080: dbWasModified = true;
081: } else {
082: LOG.info("Validating table: " + this );
083:
084: if (tableType != TABLE_TYPE_BASE_TABLE)
085: throw new NotABaseTableException(this );
086:
087: HashMap unvalidated = new HashMap(columnsByName);
088: Iterator i = storeMgr.getColumnInfo(name, conn)
089: .iterator();
090:
091: while (i.hasNext()) {
092: ColumnInfo ci = (ColumnInfo) i.next();
093: SQLIdentifier colName = new SQLIdentifier(dba,
094: ci.columnName);
095:
096: Column col = (Column) unvalidated.get(colName);
097:
098: if (col == null) {
099: if (!hasColumnName(colName))
100: throw new UnexpectedColumnException(this ,
101: colName);
102: /*
103: * Otherwise it's a duplicate column name in the
104: * metadata and we ignore it. Cloudscape is known to
105: * do this, although I think that's probably a bug.
106: */
107: } else {
108: col.validate(ci);
109: unvalidated.remove(colName);
110: }
111: }
112:
113: if (unvalidated.size() > 0)
114: throw new MissingColumnException(this , unvalidated
115: .values());
116:
117: PrimaryKey expectedPK = getExpectedPrimaryKey();
118: Map actualPKs = getExistingPrimaryKeys(conn);
119:
120: if (expectedPK == null) {
121: if (!actualPKs.isEmpty())
122: throw new WrongPrimaryKeyException(this ,
123: expectedPK, actualPKs.values());
124: } else {
125: if (actualPKs.size() != 1
126: || !actualPKs.values().contains(expectedPK))
127: throw new WrongPrimaryKeyException(this ,
128: expectedPK, actualPKs.values());
129: }
130: }
131: }
132:
133: state = TABLE_STATE_VALIDATED;
134:
135: return dbWasModified;
136: }
137:
138: public boolean validateConstraints(int flags, Connection conn)
139: throws SQLException {
140: assertIsInitialized();
141:
142: boolean dbWasModified = false;
143:
144: if ((flags & VALIDATE) != 0) {
145: boolean fksWereModified;
146: boolean idxsWereModified;
147:
148: if (dba.createIndexesBeforeForeignKeys()) {
149: idxsWereModified = validateIndices(flags, conn);
150: fksWereModified = validateForeignKeys(flags, conn);
151: } else {
152: fksWereModified = validateForeignKeys(flags, conn);
153: idxsWereModified = validateIndices(flags, conn);
154: }
155:
156: dbWasModified = fksWereModified || idxsWereModified;
157: }
158:
159: return dbWasModified;
160: }
161:
162: private boolean validateForeignKeys(int flags, Connection conn)
163: throws SQLException {
164: boolean dbWasModified = false;
165:
166: /*
167: * Validate and/or create all foreign keys.
168: */
169: Map actualForeignKeysByName = getExistingForeignKeys(conn);
170: int numActualFKs = actualForeignKeysByName.size();
171:
172: Map stmtsByFKName = getSQLAddFKStatements(actualForeignKeysByName);
173:
174: if (stmtsByFKName.isEmpty()) {
175: if (numActualFKs > 0)
176: LOG.info("Validated " + numActualFKs
177: + " foreign key(s) for table: " + this );
178: } else {
179: if ((flags & AUTO_CREATE) == 0)
180: throw new MissingForeignKeysException(this ,
181: stmtsByFKName.values());
182:
183: Statement stmt = conn.createStatement();
184:
185: try {
186: Iterator i = stmtsByFKName.entrySet().iterator();
187:
188: while (i.hasNext()) {
189: Map.Entry e = (Map.Entry) i.next();
190: String fkName = (String) e.getKey();
191: String stmtText = (String) e.getValue();
192:
193: LOG.info("Creating foreign key constraint: "
194: + getSchemaName() + '.' + fkName);
195:
196: long startTime = System.currentTimeMillis();
197:
198: stmt.execute(stmtText);
199:
200: if (LOG.isDebugEnabled())
201: LOG
202: .debug("Time = "
203: + (System.currentTimeMillis() - startTime)
204: + " ms: " + stmtText);
205:
206: storeMgr.logSQLWarnings(stmt);
207: }
208: } finally {
209: stmt.close();
210: }
211:
212: dbWasModified = true;
213: }
214:
215: return dbWasModified;
216: }
217:
218: private boolean validateIndices(int flags, Connection conn)
219: throws SQLException {
220: boolean dbWasModified = false;
221:
222: /*
223: * Validate and/or create all indices.
224: */
225: Map actualIndicesByName = getExistingIndices(conn);
226:
227: /*
228: * Compute the number of existing indices *created by us*, which are
229: * recognized by having a name that starts with the table name. This
230: * number is only used to control what gets logged below. There are
231: * often other system-generated indices.
232: */
233: int numActualIdxs = 0;
234: Iterator names = actualIndicesByName.keySet().iterator();
235:
236: while (names.hasNext()) {
237: SQLIdentifier idxName = (SQLIdentifier) names.next();
238:
239: if (idxName.toString().startsWith(name.toString()))
240: ++numActualIdxs;
241: }
242:
243: Map stmtsByIdxName = getSQLCreateIndexStatements(actualIndicesByName);
244:
245: if (stmtsByIdxName.isEmpty()) {
246: if (numActualIdxs > 0)
247: LOG.info("Validated " + numActualIdxs
248: + " index(s) for table: " + this );
249: } else {
250: if ((flags & AUTO_CREATE) == 0)
251: throw new MissingIndicesException(this , stmtsByIdxName
252: .values());
253:
254: Statement stmt = conn.createStatement();
255:
256: try {
257: Iterator i = stmtsByIdxName.entrySet().iterator();
258:
259: while (i.hasNext()) {
260: Map.Entry e = (Map.Entry) i.next();
261: String idxName = (String) e.getKey();
262: String stmtText = (String) e.getValue();
263:
264: LOG.info("Creating index: " + getSchemaName() + '.'
265: + idxName);
266:
267: long startTime = System.currentTimeMillis();
268:
269: stmt.execute(stmtText);
270:
271: if (LOG.isDebugEnabled())
272: LOG
273: .debug("Time = "
274: + (System.currentTimeMillis() - startTime)
275: + " ms: " + stmtText);
276:
277: storeMgr.logSQLWarnings(stmt);
278: }
279: } finally {
280: stmt.close();
281: }
282:
283: dbWasModified = true;
284: }
285:
286: return dbWasModified;
287: }
288:
289: public void drop(Connection conn) throws SQLException {
290: LOG.info("Dropping table: " + this );
291:
292: super .drop(conn);
293: }
294:
295: public void dropConstraints(Connection conn) throws SQLException {
296: assertIsInitialized();
297:
298: if (!dba.supportsAlterTableDropConstraint())
299: return;
300:
301: /*
302: * There's no need to drop indices; we assume they'll go away quietly
303: * when the table is dropped.
304: */
305: DatabaseMetaData dmd = conn.getMetaData();
306:
307: HashSet fkNames = new HashSet();
308: Iterator i = storeMgr.getForeignKeyInfo(name, conn).iterator();
309:
310: while (i.hasNext()) {
311: ForeignKeyInfo fki = (ForeignKeyInfo) i.next();
312:
313: /*
314: * Legally, JDBC drivers are allowed to return null names for
315: * foreign keys. If they do, we simply have to skip DROP
316: * CONSTRAINT.
317: */
318: if (fki.fkName != null)
319: fkNames.add(fki.fkName);
320: }
321:
322: int numFKs = fkNames.size();
323:
324: if (numFKs > 0) {
325: LOG.info("Dropping " + numFKs
326: + " foreign key(s) for table: " + this );
327:
328: i = fkNames.iterator();
329: Statement stmt = conn.createStatement();
330:
331: try {
332: while (i.hasNext()) {
333: String constraintName = (String) i.next();
334: String stmtText = "ALTER TABLE " + name
335: + " DROP CONSTRAINT " + constraintName;
336:
337: long startTime = System.currentTimeMillis();
338:
339: stmt.execute(stmtText);
340:
341: if (LOG.isDebugEnabled())
342: LOG
343: .debug("Time = "
344: + (System.currentTimeMillis() - startTime)
345: + " ms: " + stmtText);
346:
347: storeMgr.logSQLWarnings(stmt);
348: }
349: } finally {
350: stmt.close();
351: }
352: }
353: }
354:
355: protected List getExpectedForeignKeys() {
356: assertIsInitialized();
357:
358: ArrayList foreignKeys = new ArrayList();
359: Iterator i = columns.iterator();
360:
361: while (i.hasNext()) {
362: Column col = (Column) i.next();
363:
364: ClassMetaData cmd = ClassMetaData.forClass(col.getType());
365:
366: if (cmd != null) {
367: ClassBaseTable referencedTable = (ClassBaseTable) storeMgr
368: .getTable(cmd);
369:
370: if (referencedTable != null)
371: foreignKeys.add(new ForeignKey(col,
372: referencedTable, true));
373: }
374: }
375:
376: return foreignKeys;
377: }
378:
379: protected Set getExpectedIndices() {
380: assertIsInitialized();
381:
382: HashSet indices = new HashSet();
383: PrimaryKey pk = getExpectedPrimaryKey();
384: Iterator i = getExpectedForeignKeys().iterator();
385:
386: /*
387: * For each foreign key, add to the list an index made up of the "from"
388: * column(s) of the key, *unless* those columns also happen to be the
389: * leading columns of the primary key. If they are, we're assuming that
390: * they're going to be indexed anyway, regardless of whether that fact
391: * gets reflected by getIndexInfo().
392: */
393: while (i.hasNext()) {
394: ForeignKey fk = (ForeignKey) i.next();
395:
396: if (!pk.startsWith(fk))
397: indices.add(new Index(fk));
398: }
399:
400: return indices;
401: }
402:
403: private Map getExistingPrimaryKeys(Connection conn)
404: throws SQLException {
405: DatabaseMetaData dmd = conn.getMetaData();
406:
407: HashMap primaryKeysByName = new HashMap();
408: ResultSet rs = dmd.getPrimaryKeys(null, getSchemaName(), name
409: .getSQLIdentifier());
410:
411: try {
412: while (rs.next()) {
413: SQLIdentifier pkName;
414: String s = rs.getString(6);
415:
416: if (s == null)
417: pkName = new PrimaryKeyIdentifier(this );
418: else
419: pkName = new SQLIdentifier(dba, s);
420:
421: PrimaryKey pk = (PrimaryKey) primaryKeysByName
422: .get(pkName);
423:
424: if (pk == null) {
425: pk = new PrimaryKey(this );
426: primaryKeysByName.put(pkName, pk);
427: }
428:
429: int keySeq = rs.getInt(5) - 1;
430: SQLIdentifier colName = new SQLIdentifier(dba, rs
431: .getString(4));
432:
433: Column col = (Column) columnsByName.get(colName);
434:
435: if (col == null)
436: throw new UnexpectedColumnException(this , colName);
437:
438: pk.setColumn(keySeq, col);
439: }
440: } finally {
441: rs.close();
442: }
443:
444: return primaryKeysByName;
445: }
446:
447: private Map getExistingForeignKeys(Connection conn)
448: throws SQLException {
449: DatabaseMetaData dmd = conn.getMetaData();
450:
451: HashMap foreignKeysByName = new HashMap();
452: Iterator i = storeMgr.getForeignKeyInfo(name, conn).iterator();
453:
454: while (i.hasNext()) {
455: ForeignKeyInfo fki = (ForeignKeyInfo) i.next();
456: SQLIdentifier fkName;
457:
458: if (fki.fkName == null)
459: fkName = new ForeignKeyIdentifier(this ,
460: foreignKeysByName.size());
461: else
462: fkName = new SQLIdentifier(dba, fki.fkName);
463:
464: boolean initiallyDeferred = fki.deferrability == DatabaseMetaData.importedKeyInitiallyDeferred;
465:
466: ForeignKey fk = (ForeignKey) foreignKeysByName.get(fkName);
467:
468: if (fk == null) {
469: fk = new ForeignKey(initiallyDeferred);
470: foreignKeysByName.put(fkName, fk);
471: }
472:
473: BaseTable refTable = (BaseTable) storeMgr
474: .getTable(new SQLIdentifier(dba, fki.pkTableName));
475:
476: if (refTable != null) {
477: SQLIdentifier colName = new SQLIdentifier(dba,
478: fki.fkColumnName);
479: SQLIdentifier refColName = new SQLIdentifier(dba,
480: fki.pkColumnName);
481:
482: Column col = (Column) columnsByName.get(colName);
483: Column refCol = (Column) refTable.columnsByName
484: .get(refColName);
485:
486: if (col == null)
487: throw new UnexpectedColumnException(this , colName);
488: if (refCol == null)
489: throw new UnexpectedColumnException(this ,
490: refColName);
491:
492: fk.addColumn(col, refCol);
493: }
494: }
495:
496: return foreignKeysByName;
497: }
498:
499: private Map getExistingIndices(Connection conn) throws SQLException {
500: DatabaseMetaData dmd = conn.getMetaData();
501:
502: HashMap indicesByName = new HashMap();
503: ResultSet rs = dmd.getIndexInfo(null, getSchemaName(), name
504: .getSQLIdentifier(), false, true);
505:
506: try {
507: while (rs.next()) {
508: short idxType = rs.getShort(7);
509:
510: if (idxType == DatabaseMetaData.tableIndexStatistic)
511: continue;
512:
513: SQLIdentifier idxName = new SQLIdentifier(dba, rs
514: .getString(6));
515: Index idx = (Index) indicesByName.get(idxName);
516:
517: if (idx == null) {
518: boolean isUnique = !rs.getBoolean(4);
519: idx = new Index(this , isUnique);
520: indicesByName.put(idxName, idx);
521: }
522:
523: int colSeq = rs.getShort(8) - 1;
524: SQLIdentifier colName = new SQLIdentifier(dba, rs
525: .getString(9));
526:
527: Column col = (Column) columnsByName.get(colName);
528: if (col == null)
529: throw new UnexpectedColumnException(this , colName);
530:
531: idx.setColumn(colSeq, col);
532: }
533: } finally {
534: rs.close();
535: }
536:
537: return indicesByName;
538: }
539:
540: protected List getSQLCreateStatements() {
541: assertIsInitialized();
542:
543: ArrayList stmts = new ArrayList();
544:
545: stmts.add(dba.getCreateTableStatement(this , (Column[]) columns
546: .toArray(new Column[columns.size()])));
547:
548: PrimaryKey pk = getExpectedPrimaryKey();
549:
550: if (pk != null)
551: stmts.add(dba.getAddPrimaryKeyStatement(
552: new PrimaryKeyIdentifier(this ), pk));
553:
554: return stmts;
555: }
556:
557: protected Map getSQLAddFKStatements(Map actualForeignKeysByName) {
558: assertIsInitialized();
559:
560: HashMap stmtsByFKName = new HashMap();
561:
562: List expectedForeignKeys = getExpectedForeignKeys();
563:
564: Iterator i = expectedForeignKeys.iterator();
565: int n = 1;
566:
567: while (i.hasNext()) {
568: ForeignKey fk = (ForeignKey) i.next();
569:
570: if (!actualForeignKeysByName.containsValue(fk)) {
571: ForeignKeyIdentifier fkName;
572:
573: do {
574: fkName = new ForeignKeyIdentifier(this , n++);
575: } while (actualForeignKeysByName.containsKey(fkName));
576:
577: String stmtText = dba.getAddForeignKeyStatement(fkName,
578: fk);
579:
580: stmtsByFKName.put(fkName.getSQLIdentifier(), stmtText);
581: }
582: }
583:
584: return stmtsByFKName;
585: }
586:
587: private boolean isIndexReallyNeeded(Index requiredIdx,
588: Collection actualIndices) {
589: Iterator i = actualIndices.iterator();
590:
591: while (i.hasNext()) {
592: Index actualIdx = (Index) i.next();
593:
594: if (actualIdx.startsWith(requiredIdx))
595: return false;
596: }
597:
598: return true;
599: }
600:
601: protected Map getSQLCreateIndexStatements(Map actualIndicesByName) {
602: assertIsInitialized();
603:
604: HashMap stmtsByIdxName = new HashMap();
605:
606: Set expectedIndices = getExpectedIndices();
607:
608: Iterator i = expectedIndices.iterator();
609: int n = 1;
610:
611: while (i.hasNext()) {
612: Index idx = (Index) i.next();
613:
614: if (isIndexReallyNeeded(idx, actualIndicesByName.values())) {
615: IndexIdentifier idxName;
616:
617: do {
618: idxName = new IndexIdentifier(this ,
619: idx.getUnique(), n++);
620: } while (actualIndicesByName.containsKey(idxName));
621:
622: String stmtText = dba.getCreateIndexStatement(idxName,
623: idx);
624:
625: stmtsByIdxName
626: .put(idxName.getSQLIdentifier(), stmtText);
627: }
628: }
629:
630: return stmtsByIdxName;
631: }
632:
633: protected List getSQLDropStatements() {
634: assertIsInitialized();
635:
636: ArrayList stmts = new ArrayList();
637: stmts.add(dba.getDropTableStatement(this));
638:
639: return stmts;
640: }
641: }
|