0001: /**
0002: * com.mckoi.database.Transaction 18 Nov 2000
0003: *
0004: * Mckoi SQL Database ( http://www.mckoi.com/database )
0005: * Copyright (C) 2000, 2001, 2002 Diehl and Associates, Inc.
0006: *
0007: * This program is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU General Public License
0009: * Version 2 as published by the Free Software Foundation.
0010: *
0011: * This program is distributed in the hope that it will be useful,
0012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0014: * GNU General Public License Version 2 for more details.
0015: *
0016: * You should have received a copy of the GNU General Public License
0017: * Version 2 along with this program; if not, write to the Free Software
0018: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
0019: *
0020: * Change Log:
0021: *
0022: *
0023: */package com.mckoi.database;
0024:
0025: import com.mckoi.debug.*;
0026: import com.mckoi.util.IntegerVector;
0027: import com.mckoi.util.BigNumber;
0028: import com.mckoi.database.global.ByteLongObject;
0029: import com.mckoi.database.global.ObjectTranslator;
0030: import java.io.IOException;
0031: import java.util.ArrayList;
0032: import java.util.HashMap;
0033:
0034: /**
0035: * An open transaction that manages all data access to the
0036: * TableDataConglomerate. A transaction sees a view of the data as it was when
0037: * the transaction was created. It also sees any modifications that were made
0038: * within the context of this transaction. It does not see modifications made
0039: * by other open transactions.
0040: * <p>
0041: * A transaction ends when it is committed or rollbacked. All operations
0042: * on this transaction object only occur within the context of this transaction
0043: * and are not permanent changes to the database structure. Only when the
0044: * transaction is committed are changes reflected in the master data.
0045: *
0046: * @author Tobias Downer
0047: */
0048:
0049: public class Transaction extends SimpleTransaction {
0050:
0051: // ---------- Constraint statics ----------
0052: // These statics are for managing constraints.
0053:
0054: /**
0055: * The type of deferrance.
0056: */
0057: public static final short INITIALLY_DEFERRED = java.sql.DatabaseMetaData.importedKeyInitiallyDeferred;
0058: public static final short INITIALLY_IMMEDIATE = java.sql.DatabaseMetaData.importedKeyInitiallyImmediate;
0059: public static final short NOT_DEFERRABLE = java.sql.DatabaseMetaData.importedKeyNotDeferrable;
0060:
0061: /**
0062: * Foreign key referential trigger actions.
0063: */
0064: public static final String NO_ACTION = "NO ACTION";
0065: public static final String CASCADE = "CASCADE";
0066: public static final String SET_NULL = "SET NULL";
0067: public static final String SET_DEFAULT = "SET DEFAULT";
0068:
0069: // ---------- Member variables ----------
0070:
0071: /**
0072: * The TableDataConglomerate that this transaction is within the context of.
0073: */
0074: private TableDataConglomerate conglomerate;
0075:
0076: /**
0077: * The commit_id that represents the id of the last commit that occurred
0078: * when this transaction was created.
0079: */
0080: private long commit_id;
0081:
0082: /**
0083: * All tables touched by this transaction. (MutableTableDataSource)
0084: */
0085: private ArrayList touched_tables;
0086:
0087: /**
0088: * All tables selected from in this transaction. (MasterTableDataSource)
0089: */
0090: private ArrayList selected_from_tables;
0091:
0092: /**
0093: * The name of all database objects that were created in this transaction.
0094: * This is used for a namespace collision test during commit.
0095: */
0096: private ArrayList created_database_objects;
0097:
0098: /**
0099: * The name of all database objects that were dropped in this transaction.
0100: * This is used for a namespace collision test during commit.
0101: */
0102: private ArrayList dropped_database_objects;
0103:
0104: /**
0105: * The journal for this transaction. This journal describes all changes
0106: * made to the database by this transaction.
0107: */
0108: private TransactionJournal journal;
0109:
0110: /**
0111: * The list of InternalTableInfo objects that are containers for generating
0112: * internal tables (GTDataSource).
0113: */
0114: private InternalTableInfo[] internal_tables;
0115:
0116: /**
0117: * A pointer in the internal_tables list.
0118: */
0119: private int internal_tables_i;
0120:
0121: /**
0122: * True if an error should be generated on a dirty select.
0123: */
0124: private boolean transaction_error_on_dirty_select;
0125:
0126: /**
0127: * True if this transaction is closed.
0128: */
0129: private boolean closed;
0130:
0131: /**
0132: * Constructs the transaction.
0133: */
0134: Transaction(TableDataConglomerate conglomerate, long commit_id,
0135: ArrayList visible_tables, ArrayList table_indices) {
0136:
0137: super (conglomerate.getSystem(), conglomerate
0138: .getSequenceManager());
0139:
0140: this .conglomerate = conglomerate;
0141: this .commit_id = commit_id;
0142: this .closed = false;
0143:
0144: this .created_database_objects = new ArrayList();
0145: this .dropped_database_objects = new ArrayList();
0146:
0147: this .touched_tables = new ArrayList();
0148: this .selected_from_tables = new ArrayList();
0149: journal = new TransactionJournal();
0150:
0151: // Set up all the visible tables
0152: int sz = visible_tables.size();
0153: for (int i = 0; i < sz; ++i) {
0154: addVisibleTable((MasterTableDataSource) visible_tables
0155: .get(i), (IndexSet) table_indices.get(i));
0156: }
0157:
0158: // NOTE: We currently only support 8 - internal tables to the transaction
0159: // layer, and internal tables to the database connection layer.
0160: internal_tables = new InternalTableInfo[8];
0161: internal_tables_i = 0;
0162: addInternalTableInfo(new TransactionInternalTables());
0163:
0164: getSystem().stats().increment("Transaction.count");
0165:
0166: // Defaults to true (should be changed by called 'setErrorOnDirtySelect'
0167: // method.
0168: transaction_error_on_dirty_select = true;
0169: }
0170:
0171: /**
0172: * Returns the TableDataConglomerate of this transaction.
0173: */
0174: final TableDataConglomerate getConglomerate() {
0175: return conglomerate;
0176: }
0177:
0178: /**
0179: * Adds an internal table container (InternalTableInfo) used to
0180: * resolve internal tables. This is intended as a way for the
0181: * DatabaseConnection layer to plug in 'virtual' tables, such as those
0182: * showing connection statistics, etc. It also allows modelling database
0183: * objects as tables, such as sequences, triggers, procedures, etc.
0184: */
0185: void addInternalTableInfo(InternalTableInfo info) {
0186: if (internal_tables_i >= internal_tables.length) {
0187: throw new RuntimeException(
0188: "Internal table list bounds reached.");
0189: }
0190: internal_tables[internal_tables_i] = info;
0191: ++internal_tables_i;
0192: }
0193:
0194: /**
0195: * Returns the 'commit_id' which is the last commit that occured before
0196: * this transaction was created.
0197: * <p>
0198: * NOTE: Don't make this synchronized over anything. This is accessed
0199: * by OpenTransactionList.
0200: */
0201: long getCommitID() {
0202: // REINFORCED NOTE: This absolutely must never be synchronized because
0203: // it is accessed by OpenTransactionList synchronized.
0204: return commit_id;
0205: }
0206:
0207: // ----- Operations within the context of this transaction -----
0208:
0209: /**
0210: * Overwritten from SimpleTransaction.
0211: * Returns a new MutableTableDataSource for the view of the
0212: * MasterTableDataSource at the start of this transaction. Note that this is
0213: * only ever called once per table accessed in this transaction.
0214: */
0215: public MutableTableDataSource createMutableTableDataSourceAtCommit(
0216: MasterTableDataSource master) {
0217: // Create the table for this transaction.
0218: MutableTableDataSource table = master
0219: .createTableDataSourceAtCommit(this );
0220: // Log in the journal that this table was touched by the transaction.
0221: journal.entryAddTouchedTable(master.getTableID());
0222: touched_tables.add(table);
0223: return table;
0224: }
0225:
0226: /**
0227: * Called by the query evaluation layer when information is selected
0228: * from this table as part of this transaction. When there is a select
0229: * query on a table, when the transaction is committed we should look for
0230: * any concurrently committed changes to the table. If there are any, then
0231: * any selects on the table should be considered incorrect and cause a
0232: * commit failure.
0233: */
0234: public void addSelectedFromTable(TableName table_name) {
0235: // Special handling of internal tables,
0236: if (isDynamicTable(table_name)) {
0237: return;
0238: }
0239:
0240: MasterTableDataSource master = findVisibleTable(table_name,
0241: false);
0242: if (master == null) {
0243: throw new StatementException(
0244: "Table with name not available: " + table_name);
0245: }
0246: // System.out.println("Selected from table: " + table_name);
0247: synchronized (selected_from_tables) {
0248: if (!selected_from_tables.contains(master)) {
0249: selected_from_tables.add(master);
0250: }
0251: }
0252:
0253: }
0254:
0255: /**
0256: * Copies all the tables within this transaction view to the destination
0257: * conglomerate object. Some care should be taken with security when using
0258: * this method. This is useful for generating a backup of the current
0259: * view of the database that can work without interfering with the general
0260: * operation of the database.
0261: */
0262: void liveCopyAllDataTo(TableDataConglomerate dest_conglomerate) {
0263: // Create a new TableDataConglomerate using the same settings from this
0264: // TransactionSystem but on the new StoreSystem.
0265: int sz = getVisibleTableCount();
0266:
0267: // The list to copy (in the order to copy in).
0268: // We put the 'SEQUENCE_INFO' at the very end of the table list to copy.
0269: ArrayList copy_list = new ArrayList(sz);
0270:
0271: MasterTableDataSource last_entry = null;
0272: for (int i = 0; i < sz; ++i) {
0273: MasterTableDataSource master_table = getVisibleTable(i);
0274: TableName table_name = master_table.getDataTableDef()
0275: .getTableName();
0276: if (table_name
0277: .equals(TableDataConglomerate.SYS_SEQUENCE_INFO)) {
0278: last_entry = master_table;
0279: } else {
0280: copy_list.add(master_table);
0281: }
0282: }
0283: copy_list.add(0, last_entry);
0284:
0285: try {
0286: // For each master table,
0287: for (int i = 0; i < sz; ++i) {
0288:
0289: MasterTableDataSource master_table = (MasterTableDataSource) copy_list
0290: .get(i);
0291: TableName table_name = master_table.getDataTableDef()
0292: .getTableName();
0293:
0294: // Create a destination transaction
0295: Transaction dest_transaction = dest_conglomerate
0296: .createTransaction();
0297:
0298: // The view of this table within this transaction.
0299: IndexSet index_set = getIndexSetForTable(master_table);
0300:
0301: // If the table already exists then drop it
0302: if (dest_transaction.tableExists(table_name)) {
0303: dest_transaction.dropTable(table_name);
0304: }
0305:
0306: // Copy it into the destination conglomerate.
0307: dest_transaction.copyTable(master_table, index_set);
0308:
0309: // Close and commit the transaction in the destination conglomeration.
0310: dest_transaction.closeAndCommit();
0311:
0312: // Dispose the IndexSet
0313: index_set.dispose();
0314:
0315: }
0316:
0317: } catch (TransactionException e) {
0318: Debug().writeException(e);
0319: throw new RuntimeException(
0320: "Transaction Error when copying table: "
0321: + e.getMessage());
0322: }
0323: }
0324:
0325: // ---------- Dynamically generated tables ----------
0326:
0327: /**
0328: * Returns true if the given table name represents a dynamically generated
0329: * system table.
0330: */
0331: protected boolean isDynamicTable(TableName table_name) {
0332: for (int i = 0; i < internal_tables.length; ++i) {
0333: InternalTableInfo info = internal_tables[i];
0334: if (info != null) {
0335: if (info.containsTableName(table_name)) {
0336: return true;
0337: }
0338: }
0339: }
0340: return false;
0341: }
0342:
0343: /**
0344: * Returns a list of all dynamic table names. This method returns a
0345: * reference to a static, make sure you don't change the contents of the
0346: * array!
0347: */
0348: protected TableName[] getDynamicTableList() {
0349: int sz = 0;
0350: for (int i = 0; i < internal_tables.length; ++i) {
0351: InternalTableInfo info = internal_tables[i];
0352: if (info != null) {
0353: sz += info.getTableCount();
0354: }
0355: }
0356:
0357: TableName[] list = new TableName[sz];
0358: int index = 0;
0359:
0360: for (int i = 0; i < internal_tables.length; ++i) {
0361: InternalTableInfo info = internal_tables[i];
0362: if (info != null) {
0363: sz = info.getTableCount();
0364: for (int n = 0; n < sz; ++n) {
0365: list[index] = info.getTableName(n);
0366: ++index;
0367: }
0368: }
0369: }
0370:
0371: return list;
0372: }
0373:
0374: /**
0375: * Returns the DataTableDef for the given internal table.
0376: */
0377: protected DataTableDef getDynamicDataTableDef(TableName table_name) {
0378:
0379: for (int i = 0; i < internal_tables.length; ++i) {
0380: InternalTableInfo info = internal_tables[i];
0381: if (info != null) {
0382: int index = info.findTableName(table_name);
0383: if (index != -1) {
0384: return info.getDataTableDef(index);
0385: }
0386: }
0387: }
0388:
0389: throw new RuntimeException("Not an internal table: "
0390: + table_name);
0391: }
0392:
0393: /**
0394: * Returns an instance of MutableDataTableSource that represents the
0395: * contents of the internal table with the given name.
0396: */
0397: protected MutableTableDataSource getDynamicTable(
0398: TableName table_name) {
0399:
0400: for (int i = 0; i < internal_tables.length; ++i) {
0401: InternalTableInfo info = internal_tables[i];
0402: if (info != null) {
0403: int index = info.findTableName(table_name);
0404: if (index != -1) {
0405: return info.createInternalTable(index);
0406: }
0407: }
0408: }
0409:
0410: throw new RuntimeException("Not an internal table: "
0411: + table_name);
0412: }
0413:
0414: /**
0415: * Returns a string type describing the type of the dynamic table.
0416: */
0417: public String getDynamicTableType(TableName table_name) {
0418: // Otherwise we need to look up the table in the internal table list,
0419: for (int i = 0; i < internal_tables.length; ++i) {
0420: InternalTableInfo info = internal_tables[i];
0421: if (info != null) {
0422: int index = info.findTableName(table_name);
0423: if (index != -1) {
0424: return info.getTableType(index);
0425: }
0426: }
0427: }
0428: // No internal table found, so report the error.
0429: throw new RuntimeException("No table '" + table_name
0430: + "' to report type for.");
0431: }
0432:
0433: // ---------- Transaction manipulation ----------
0434:
0435: /**
0436: * Creates a new table within this transaction with the given sector size.
0437: * If the table already exists then an exception is thrown.
0438: * <p>
0439: * This should only be called under an exclusive lock on the connection.
0440: */
0441: public void createTable(DataTableDef table_def,
0442: int data_sector_size, int index_sector_size) {
0443:
0444: TableName table_name = table_def.getTableName();
0445: MasterTableDataSource master = findVisibleTable(table_name,
0446: false);
0447: if (master != null) {
0448: throw new StatementException("Table '" + table_name
0449: + "' already exists.");
0450: }
0451:
0452: table_def.setImmutable();
0453:
0454: if (data_sector_size < 27) {
0455: data_sector_size = 27;
0456: } else if (data_sector_size > 4096) {
0457: data_sector_size = 4096;
0458: }
0459:
0460: // Create the new master table and add to list of visible tables.
0461: master = conglomerate.createMasterTable(table_def,
0462: data_sector_size, index_sector_size);
0463: // Add this table (and an index set) for this table.
0464: addVisibleTable(master, master.createIndexSet());
0465:
0466: // Log in the journal that this transaction touched the table_id.
0467: int table_id = master.getTableID();
0468: journal.entryAddTouchedTable(table_id);
0469:
0470: // Log in the journal that we created this table.
0471: journal.entryTableCreate(table_id);
0472:
0473: // Add entry to the Sequences table for the native generator for this
0474: // table.
0475: SequenceManager.addNativeTableGenerator(this , table_name);
0476:
0477: // Notify that this database object has been successfully created.
0478: databaseObjectCreated(table_name);
0479:
0480: }
0481:
0482: /**
0483: * Creates a new table within this transaction. If the table already
0484: * exists then an exception is thrown.
0485: * <p>
0486: * This should only be called under an exclusive lock on the connection.
0487: */
0488: public void createTable(DataTableDef table_def) {
0489: // data sector size defaults to 251
0490: // index sector size defaults to 1024
0491: createTable(table_def, 251, 1024);
0492: }
0493:
0494: /**
0495: * Given a DataTableDef, if the table exists then it is updated otherwise
0496: * if it doesn't exist then it is created.
0497: * <p>
0498: * This should only be used as very fine grain optimization for creating/
0499: * altering tables. If in the future the underlying table model is changed
0500: * so that the given 'sector_size' value is unapplicable, then the value
0501: * will be ignored.
0502: */
0503: public void alterCreateTable(DataTableDef table_def,
0504: int data_sector_size, int index_sector_size) {
0505: if (!tableExists(table_def.getTableName())) {
0506: createTable(table_def, data_sector_size, index_sector_size);
0507: } else {
0508: alterTable(table_def.getTableName(), table_def,
0509: data_sector_size, index_sector_size);
0510: }
0511: }
0512:
0513: /**
0514: * Drops a table within this transaction. If the table does not exist then
0515: * an exception is thrown.
0516: * <p>
0517: * This should only be called under an exclusive lock on the connection.
0518: */
0519: public void dropTable(TableName table_name) {
0520: // System.out.println(this + " DROP: " + table_name);
0521: MasterTableDataSource master = findVisibleTable(table_name,
0522: false);
0523:
0524: if (master == null) {
0525: throw new StatementException("Table '" + table_name
0526: + "' doesn't exist.");
0527: }
0528:
0529: // Removes this table from the visible table list of this transaction
0530: removeVisibleTable(master);
0531:
0532: // Log in the journal that this transaction touched the table_id.
0533: int table_id = master.getTableID();
0534: journal.entryAddTouchedTable(table_id);
0535:
0536: // Log in the journal that we dropped this table.
0537: journal.entryTableDrop(table_id);
0538:
0539: // Remove the native sequence generator (in this transaction) for this
0540: // table.
0541: SequenceManager.removeNativeTableGenerator(this , table_name);
0542:
0543: // Notify that this database object has been dropped
0544: databaseObjectDropped(table_name);
0545:
0546: }
0547:
0548: /**
0549: * Generates an exact copy of the table within this transaction. It is
0550: * recommended that the table is dropped before the copy is made. The
0551: * purpose of this method is to generate a temporary table that can be
0552: * modified without fear of another transaction changing the contents in
0553: * another transaction. This also provides a convenient way to compact
0554: * a table because any spare space is removed when the table is copied. It
0555: * also allows us to make a copy of MasterTableDataSource into a foreign
0556: * conglomerate which allows us to implement a backup procedure.
0557: * <p>
0558: * This method does NOT assume the given MasterTableDataSource is contained,
0559: * or has once been contained within this conglomerate.
0560: */
0561: public void copyTable(MasterTableDataSource src_master_table,
0562: IndexSet index_set) {
0563:
0564: DataTableDef table_def = src_master_table.getDataTableDef();
0565: TableName table_name = table_def.getTableName();
0566: MasterTableDataSource master = findVisibleTable(table_name,
0567: false);
0568: if (master != null) {
0569: throw new StatementException("Unable to copy. Table '"
0570: + table_name + "' already exists.");
0571: }
0572:
0573: // Copy the master table and add to the list of visible tables.
0574: master = conglomerate.copyMasterTable(src_master_table,
0575: index_set);
0576: // Add this visible table
0577: addVisibleTable(master, master.createIndexSet());
0578:
0579: // Log in the journal that this transaction touched the table_id.
0580: int table_id = master.getTableID();
0581: journal.entryAddTouchedTable(table_id);
0582:
0583: // Log in the journal that we created this table.
0584: journal.entryTableCreate(table_id);
0585:
0586: // Add entry to the Sequences table for the native generator for this
0587: // table.
0588: SequenceManager.addNativeTableGenerator(this , table_name);
0589:
0590: // Notify that this database object has been successfully created.
0591: databaseObjectCreated(table_name);
0592:
0593: }
0594:
0595: /**
0596: * Alter the table with the given name to the new definition and give the
0597: * copied table a new data sector size. If the table does not exist then
0598: * an exception is thrown.
0599: * <p>
0600: * This copies all columns that were in the original table to the new
0601: * altered table if the name is the same. Any names that don't exist are
0602: * set to the default value.
0603: * <p>
0604: * This should only be called under an exclusive lock on the connection.
0605: */
0606: public void alterTable(TableName table_name,
0607: DataTableDef table_def, int data_sector_size,
0608: int index_sector_size) {
0609:
0610: table_def.setImmutable();
0611:
0612: // The current schema context is the schema of the table name
0613: String current_schema = table_name.getSchema();
0614: SystemQueryContext context = new SystemQueryContext(this ,
0615: current_schema);
0616:
0617: // Get the next unique id of the unaltered table.
0618: long next_id = nextUniqueID(table_name);
0619:
0620: // Drop the current table
0621: MutableTableDataSource c_table = getTable(table_name);
0622: dropTable(table_name);
0623: // And create the table table
0624: createTable(table_def);
0625: MutableTableDataSource altered_table = getTable(table_name);
0626:
0627: // Get the new MasterTableDataSource object
0628: MasterTableDataSource new_master_table = findVisibleTable(
0629: table_name, false);
0630: // Set the sequence id of the table
0631: new_master_table.setUniqueID(next_id);
0632:
0633: // Work out which columns we have to copy to where
0634: int[] col_map = new int[table_def.columnCount()];
0635: DataTableDef orig_td = c_table.getDataTableDef();
0636: for (int i = 0; i < col_map.length; ++i) {
0637: String col_name = table_def.columnAt(i).getName();
0638: col_map[i] = orig_td.findColumnName(col_name);
0639: }
0640:
0641: try {
0642: // First move all the rows from the old table to the new table,
0643: // This does NOT update the indexes.
0644: try {
0645: RowEnumeration e = c_table.rowEnumeration();
0646: while (e.hasMoreRows()) {
0647: int row_index = e.nextRowIndex();
0648: RowData row_data = new RowData(altered_table);
0649: for (int i = 0; i < col_map.length; ++i) {
0650: int col = col_map[i];
0651: if (col != -1) {
0652: row_data.setColumnData(i, c_table
0653: .getCellContents(col, row_index));
0654: }
0655: }
0656: row_data.setDefaultForRest(context);
0657: // Note we use a low level 'addRow' method on the master table
0658: // here. This does not touch the table indexes. The indexes are
0659: // built later.
0660: int new_row_number = new_master_table
0661: .addRow(row_data);
0662: // Set the record as committed added
0663: new_master_table.writeRecordType(new_row_number,
0664: 0x010);
0665: }
0666: } catch (DatabaseException e) {
0667: Debug().writeException(e);
0668: throw new RuntimeException(e.getMessage());
0669: }
0670:
0671: // PENDING: We need to copy any existing index definitions that might
0672: // have been set on the table being altered.
0673:
0674: // Rebuild the indexes in the new master table,
0675: new_master_table.buildIndexes();
0676:
0677: // Get the snapshot index set on the new table and set it here
0678: setIndexSetForTable(new_master_table, new_master_table
0679: .createIndexSet());
0680:
0681: // Flush this out of the table cache
0682: flushTableCache(table_name);
0683:
0684: // Ensure the native sequence generator exists...
0685: SequenceManager
0686: .removeNativeTableGenerator(this , table_name);
0687: SequenceManager.addNativeTableGenerator(this , table_name);
0688:
0689: // Notify that this database object has been successfully dropped and
0690: // created.
0691: databaseObjectDropped(table_name);
0692: databaseObjectCreated(table_name);
0693:
0694: } catch (IOException e) {
0695: Debug().writeException(e);
0696: throw new RuntimeException(e.getMessage());
0697: }
0698:
0699: }
0700:
0701: /**
0702: * Alters the table with the given name within this transaction to the
0703: * specified table definition. If the table does not exist then an exception
0704: * is thrown.
0705: * <p>
0706: * This should only be called under an exclusive lock on the connection.
0707: */
0708: public void alterTable(TableName table_name, DataTableDef table_def) {
0709:
0710: // Make sure we remember the current sector size of the altered table so
0711: // we can create the new table with the original size.
0712: try {
0713:
0714: int current_data_sector_size;
0715: MasterTableDataSource master = findVisibleTable(table_name,
0716: false);
0717: if (master instanceof V1MasterTableDataSource) {
0718: current_data_sector_size = ((V1MasterTableDataSource) master)
0719: .rawDataSectorSize();
0720: } else {
0721: current_data_sector_size = -1;
0722: }
0723: // HACK: We use index sector size of 2043 for all altered tables
0724: alterTable(table_name, table_def, current_data_sector_size,
0725: 2043);
0726:
0727: } catch (IOException e) {
0728: throw new RuntimeException("IO Error: " + e.getMessage());
0729: }
0730:
0731: }
0732:
0733: /**
0734: * Checks all the rows in the table for immediate constraint violations
0735: * and when the transaction is next committed check for all deferred
0736: * constraint violations. This method is used when the constraints on a
0737: * table changes and we need to determine if any constraint violations
0738: * occurred. To the constraint checking system, this is like adding all
0739: * the rows to the given table.
0740: */
0741: public void checkAllConstraints(TableName table_name) {
0742: // Get the table
0743: TableDataSource table = getTable(table_name);
0744: // Get all the rows in the table
0745: int[] rows = new int[table.getRowCount()];
0746: RowEnumeration row_enum = table.rowEnumeration();
0747: int i = 0;
0748: while (row_enum.hasMoreRows()) {
0749: rows[i] = row_enum.nextRowIndex();
0750: ++i;
0751: }
0752: // Check the constraints of all the rows in the table.
0753: TableDataConglomerate.checkAddConstraintViolations(this , table,
0754: rows, INITIALLY_IMMEDIATE);
0755:
0756: // Add that we altered this table in the journal
0757: MasterTableDataSource master = findVisibleTable(table_name,
0758: false);
0759: if (master == null) {
0760: throw new StatementException("Table '" + table_name
0761: + "' doesn't exist.");
0762: }
0763:
0764: // Log in the journal that this transaction touched the table_id.
0765: int table_id = master.getTableID();
0766:
0767: journal.entryAddTouchedTable(table_id);
0768: // Log in the journal that we dropped this table.
0769: journal.entryTableConstraintAlter(table_id);
0770:
0771: }
0772:
0773: /**
0774: * Compacts the table with the given name within this transaction. If the
0775: * table doesn't exist then an exception is thrown.
0776: */
0777: public void compactTable(TableName table_name) {
0778:
0779: // Find the master table.
0780: MasterTableDataSource current_table = findVisibleTable(
0781: table_name, false);
0782: if (current_table == null) {
0783: throw new StatementException("Table '" + table_name
0784: + "' doesn't exist.");
0785: }
0786:
0787: // If the table is worth compacting, or the table is a
0788: // V1MasterTableDataSource
0789: if (current_table.isWorthCompacting()) {
0790: // The view of this table within this transaction.
0791: IndexSet index_set = getIndexSetForTable(current_table);
0792: // Drop the current table
0793: dropTable(table_name);
0794: // And copy to the new table
0795: copyTable(current_table, index_set);
0796: }
0797:
0798: }
0799:
0800: /**
0801: * Returns true if the conglomerate commit procedure should check for
0802: * dirty selects and produce a transaction error. A dirty select is when
0803: * a query reads information from a table that is effected by another table
0804: * during a transaction. This in itself will not cause data
0805: * consistancy problems but for strict conformance to SERIALIZABLE
0806: * isolation level this should return true.
0807: * <p>
0808: * NOTE; We MUST NOT make this method serialized because it is back called
0809: * from within a commit lock in TableDataConglomerate.
0810: */
0811: boolean transactionErrorOnDirtySelect() {
0812: return transaction_error_on_dirty_select;
0813: }
0814:
0815: /**
0816: * Sets the transaction error on dirty select for this transaction.
0817: */
0818: void setErrorOnDirtySelect(boolean status) {
0819: transaction_error_on_dirty_select = status;
0820: }
0821:
0822: // ----- Setting/Querying constraint information -----
0823: // PENDING: Is it worth implementing a pluggable constraint architecture
0824: // as described in the idea below. With the current implementation we
0825: // have tied a DataTableConglomerate to a specific constraint
0826: // architecture.
0827: //
0828: // IDEA: These methods delegate to the parent conglomerate which has a
0829: // pluggable architecture for setting/querying constraints. Some uses of
0830: // a conglomerate may not need integrity constraints or may implement the
0831: // mechanism for storing/querying in a different way. This provides a
0832: // useful abstraction of being enable to implement constraint behaviour
0833: // by only providing a way to set/query the constraint information in
0834: // different conglomerate uses.
0835:
0836: /**
0837: * Convenience, given a SimpleTableQuery object this will return a list of
0838: * column names in sequence that represent the columns in a group constraint.
0839: * <p>
0840: * 'cols' is the unsorted list of indexes in the table that represent the
0841: * group.
0842: * <p>
0843: * Assumes column 2 of dt is the sequence number and column 1 is the name
0844: * of the column.
0845: */
0846: private static String[] toColumns(SimpleTableQuery dt,
0847: IntegerVector cols) {
0848: int size = cols.size();
0849: String[] list = new String[size];
0850:
0851: // for each n of the output list
0852: for (int n = 0; n < size; ++n) {
0853: // for each i of the input list
0854: for (int i = 0; i < size; ++i) {
0855: int row_index = cols.intAt(i);
0856: int seq_no = ((BigNumber) dt.get(2, row_index)
0857: .getObject()).intValue();
0858: if (seq_no == n) {
0859: list[n] = dt.get(1, row_index).getObject()
0860: .toString();
0861: break;
0862: }
0863: }
0864: }
0865:
0866: return list;
0867: }
0868:
0869: /**
0870: * Convenience, generates a unique constraint name. If the given constraint
0871: * name is 'null' then a new one is created, otherwise the given default
0872: * one is returned.
0873: */
0874: private static String makeUniqueConstraintName(String name,
0875: BigNumber unique_id) {
0876: if (name == null) {
0877: name = "_ANONYMOUS_CONSTRAINT_" + unique_id.toString();
0878: }
0879: return name;
0880: }
0881:
0882: /**
0883: * Notifies this transaction that a database object with the given name has
0884: * successfully been created.
0885: */
0886: void databaseObjectCreated(TableName table_name) {
0887: // If this table name was dropped, then remove from the drop list
0888: boolean dropped = dropped_database_objects.remove(table_name);
0889: // If the above operation didn't remove a table name then add to the
0890: // created database objects list.
0891: if (!dropped) {
0892: created_database_objects.add(table_name);
0893: }
0894: }
0895:
0896: /**
0897: * Notifies this transaction that a database object with the given name has
0898: * successfully been dropped.
0899: */
0900: void databaseObjectDropped(TableName table_name) {
0901: // If this table name was created, then remove from the create list
0902: boolean created = created_database_objects.remove(table_name);
0903: // If the above operation didn't remove a table name then add to the
0904: // dropped database objects list.
0905: if (!created) {
0906: dropped_database_objects.add(table_name);
0907: }
0908: }
0909:
0910: /**
0911: * Returns the normalized list of database object names created in this
0912: * transaction.
0913: */
0914: ArrayList getAllNamesCreated() {
0915: return created_database_objects;
0916: }
0917:
0918: /**
0919: * Returns the normalized list of database object names dropped in this
0920: * transaction.
0921: */
0922: ArrayList getAllNamesDropped() {
0923: return dropped_database_objects;
0924: }
0925:
0926: /**
0927: * Create a new schema in this transaction. When the transaction is
0928: * committed the schema will become globally accessable. Note that any
0929: * security checks must be performed before this method is called.
0930: * <p>
0931: * NOTE: We must guarentee that the transaction be in exclusive mode before
0932: * this method is called.
0933: */
0934: public void createSchema(String name, String type) {
0935: TableName table_name = TableDataConglomerate.SCHEMA_INFO_TABLE;
0936: MutableTableDataSource t = getTable(table_name);
0937: SimpleTableQuery dt = new SimpleTableQuery(t);
0938:
0939: try {
0940: // Select entries where;
0941: // sUSRSchemaInfo.name = name
0942: if (!dt.existsSingle(1, name)) {
0943: // Add the entry to the schema info table.
0944: RowData rd = new RowData(t);
0945: BigNumber unique_id = BigNumber
0946: .fromLong(nextUniqueID(table_name));
0947: rd.setColumnDataFromObject(0, unique_id);
0948: rd.setColumnDataFromObject(1, name);
0949: rd.setColumnDataFromObject(2, type);
0950: // Third (other) column is left as null
0951: t.addRow(rd);
0952: } else {
0953: throw new StatementException("Schema already exists: "
0954: + name);
0955: }
0956: } finally {
0957: dt.dispose();
0958: }
0959: }
0960:
0961: /**
0962: * Drops a schema from this transaction. When the transaction is committed
0963: * the schema will be dropped perminently. Note that any security checks
0964: * must be performed before this method is called.
0965: * <p>
0966: * NOTE: We must guarentee that the transaction be in exclusive mode before
0967: * this method is called.
0968: */
0969: public void dropSchema(String name) {
0970: TableName table_name = TableDataConglomerate.SCHEMA_INFO_TABLE;
0971: MutableTableDataSource t = getTable(table_name);
0972: SimpleTableQuery dt = new SimpleTableQuery(t);
0973:
0974: // Drop a single entry from dt where column 1 = name
0975: boolean b = dt.deleteSingle(1, name);
0976: dt.dispose();
0977: if (!b) {
0978: throw new StatementException("Schema doesn't exists: "
0979: + name);
0980: }
0981: }
0982:
0983: /**
0984: * Returns true if the schema exists within this transaction.
0985: */
0986: public boolean schemaExists(String name) {
0987: TableName table_name = TableDataConglomerate.SCHEMA_INFO_TABLE;
0988: MutableTableDataSource t = getTable(table_name);
0989: SimpleTableQuery dt = new SimpleTableQuery(t);
0990:
0991: // Returns true if there's a single entry in dt where column 1 = name
0992: boolean b = dt.existsSingle(1, name);
0993: dt.dispose();
0994: return b;
0995: }
0996:
0997: /**
0998: * Resolves the case of the given schema name if the database is performing
0999: * case insensitive identifier matching. Returns a SchemaDef object that
1000: * identifiers the schema. Returns null if the schema name could not be
1001: * resolved.
1002: */
1003: public SchemaDef resolveSchemaCase(String name, boolean ignore_case) {
1004: // The list of schema
1005: SimpleTableQuery dt = new SimpleTableQuery(
1006: getTable(TableDataConglomerate.SCHEMA_INFO_TABLE));
1007:
1008: try {
1009: RowEnumeration e = dt.rowEnumeration();
1010: if (ignore_case) {
1011: SchemaDef result = null;
1012: while (e.hasMoreRows()) {
1013: int row_index = e.nextRowIndex();
1014: String cur_name = dt.get(1, row_index).getObject()
1015: .toString();
1016: if (name.equalsIgnoreCase(cur_name)) {
1017: if (result != null) {
1018: throw new StatementException(
1019: "Ambiguous schema name: '" + name
1020: + "'");
1021: }
1022: String type = dt.get(2, row_index).getObject()
1023: .toString();
1024: result = new SchemaDef(cur_name, type);
1025: }
1026: }
1027: return result;
1028:
1029: } else { // if (!ignore_case)
1030: while (e.hasMoreRows()) {
1031: int row_index = e.nextRowIndex();
1032: String cur_name = dt.get(1, row_index).getObject()
1033: .toString();
1034: if (name.equals(cur_name)) {
1035: String type = dt.get(2, row_index).getObject()
1036: .toString();
1037: return new SchemaDef(cur_name, type);
1038: }
1039: }
1040: // Not found
1041: return null;
1042: }
1043: }
1044:
1045: finally {
1046: dt.dispose();
1047: }
1048:
1049: }
1050:
1051: /**
1052: * Returns an array of SchemaDef objects for each schema currently setup in
1053: * the database.
1054: */
1055: public SchemaDef[] getSchemaList() {
1056: // The list of schema
1057: SimpleTableQuery dt = new SimpleTableQuery(
1058: getTable(TableDataConglomerate.SCHEMA_INFO_TABLE));
1059: RowEnumeration e = dt.rowEnumeration();
1060: SchemaDef[] arr = new SchemaDef[dt.getRowCount()];
1061: int i = 0;
1062:
1063: while (e.hasMoreRows()) {
1064: int row_index = e.nextRowIndex();
1065: String cur_name = dt.get(1, row_index).getObject()
1066: .toString();
1067: String cur_type = dt.get(2, row_index).getObject()
1068: .toString();
1069: arr[i] = new SchemaDef(cur_name, cur_type);
1070: ++i;
1071: }
1072:
1073: dt.dispose();
1074: return arr;
1075: }
1076:
1077: /**
1078: * Sets a persistent variable of the database that becomes a committed
1079: * change once this transaction is committed. The variable can later be
1080: * retrieved with a call to the 'getPersistantVar' method. A persistant
1081: * var is created if it doesn't exist in the DatabaseVars table otherwise
1082: * it is overwritten.
1083: */
1084: public void setPersistentVar(String variable, String value) {
1085: TableName table_name = TableDataConglomerate.PERSISTENT_VAR_TABLE;
1086: MutableTableDataSource t = getTable(table_name);
1087: SimpleTableQuery dt = new SimpleTableQuery(t);
1088: dt.setVar(0, new Object[] { variable, value });
1089: dt.dispose();
1090: }
1091:
1092: /**
1093: * Returns the value of the persistent variable with the given name or null
1094: * if it doesn't exist.
1095: */
1096: public String getPersistantVar(String variable) {
1097: TableName table_name = TableDataConglomerate.PERSISTENT_VAR_TABLE;
1098: MutableTableDataSource t = getTable(table_name);
1099: SimpleTableQuery dt = new SimpleTableQuery(t);
1100: String val = dt.getVar(1, 0, variable).toString();
1101: dt.dispose();
1102: return val;
1103: }
1104:
1105: /**
1106: * Creates a new sequence generator with the given TableName and
1107: * initializes it with the given details. This does NOT check if the
1108: * given name clashes with an existing database object.
1109: */
1110: public void createSequenceGenerator(TableName name,
1111: long start_value, long increment_by, long min_value,
1112: long max_value, long cache, boolean cycle) {
1113: SequenceManager.createSequenceGenerator(this , name,
1114: start_value, increment_by, min_value, max_value, cache,
1115: cycle);
1116:
1117: // Notify that this database object has been created
1118: databaseObjectCreated(name);
1119: }
1120:
1121: /**
1122: * Drops an existing sequence generator with the given name.
1123: */
1124: public void dropSequenceGenerator(TableName name) {
1125: SequenceManager.dropSequenceGenerator(this , name);
1126: // Flush the sequence manager
1127: flushSequenceManager(name);
1128:
1129: // Notify that this database object has been dropped
1130: databaseObjectDropped(name);
1131: }
1132:
1133: /**
1134: * Adds a unique constraint to the database which becomes perminant when
1135: * the transaction is committed. Columns in a table that are defined as
1136: * unique are prevented from being duplicated by the engine.
1137: * <p>
1138: * NOTE: Security checks for adding constraints must be checked for at a
1139: * higher layer.
1140: * <p>
1141: * NOTE: We must guarentee that the transaction be in exclusive mode before
1142: * this method is called.
1143: */
1144: public void addUniqueConstraint(TableName table_name,
1145: String[] cols, short deferred, String constraint_name) {
1146:
1147: TableName tn1 = TableDataConglomerate.UNIQUE_INFO_TABLE;
1148: TableName tn2 = TableDataConglomerate.UNIQUE_COLS_TABLE;
1149: MutableTableDataSource t = getTable(tn1);
1150: MutableTableDataSource tcols = getTable(tn2);
1151:
1152: try {
1153:
1154: // Insert a value into UNIQUE_INFO_TABLE
1155: RowData rd = new RowData(t);
1156: BigNumber unique_id = BigNumber.fromLong(nextUniqueID(tn1));
1157: constraint_name = makeUniqueConstraintName(constraint_name,
1158: unique_id);
1159: rd.setColumnDataFromObject(0, unique_id);
1160: rd.setColumnDataFromObject(1, constraint_name);
1161: rd.setColumnDataFromObject(2, table_name.getSchema());
1162: rd.setColumnDataFromObject(3, table_name.getName());
1163: rd.setColumnDataFromObject(4, BigNumber.fromInt(deferred));
1164: t.addRow(rd);
1165:
1166: // Insert the columns
1167: for (int i = 0; i < cols.length; ++i) {
1168: rd = new RowData(tcols);
1169: rd.setColumnDataFromObject(0, unique_id); // unique id
1170: rd.setColumnDataFromObject(1, cols[i]); // column name
1171: rd.setColumnDataFromObject(2, BigNumber.fromInt(i)); // sequence number
1172: tcols.addRow(rd);
1173: }
1174:
1175: } catch (DatabaseConstraintViolationException e) {
1176: // Constraint violation when inserting the data. Check the type and
1177: // wrap around an appropriate error message.
1178: if (e.getErrorCode() == DatabaseConstraintViolationException.UNIQUE_VIOLATION) {
1179: // This means we gave a constraint name that's already being used
1180: // for a primary key.
1181: throw new StatementException("Unique constraint name '"
1182: + constraint_name + "' is already being used.");
1183: }
1184: throw e;
1185: }
1186:
1187: }
1188:
1189: /**
1190: * Adds a foreign key constraint to the database which becomes perminent
1191: * when the transaction is committed. A foreign key represents a referential
1192: * link from one table to another (may be the same table). The 'table_name',
1193: * 'cols' args represents the object to link from. The 'ref_table',
1194: * 'ref_cols' args represents the object to link to. The update rules are
1195: * for specifying cascading delete/update rules. The deferred arg is for
1196: * IMMEDIATE/DEFERRED checking.
1197: * <p>
1198: * NOTE: Security checks for adding constraints must be checked for at a
1199: * higher layer.
1200: * <p>
1201: * NOTE: We must guarentee that the transaction be in exclusive mode before
1202: * this method is called.
1203: */
1204: public void addForeignKeyConstraint(TableName table, String[] cols,
1205: TableName ref_table, String[] ref_cols, String delete_rule,
1206: String update_rule, short deferred, String constraint_name) {
1207: TableName tn1 = TableDataConglomerate.FOREIGN_INFO_TABLE;
1208: TableName tn2 = TableDataConglomerate.FOREIGN_COLS_TABLE;
1209: MutableTableDataSource t = getTable(tn1);
1210: MutableTableDataSource tcols = getTable(tn2);
1211:
1212: try {
1213:
1214: // If 'ref_columns' empty then set to primary key for referenced table,
1215: // ISSUE: What if primary key changes after the fact?
1216: if (ref_cols.length == 0) {
1217: ColumnGroup set = queryTablePrimaryKeyGroup(this ,
1218: ref_table);
1219: if (set == null) {
1220: throw new StatementException(
1221: "No primary key defined for referenced table '"
1222: + ref_table + "'");
1223: }
1224: ref_cols = set.columns;
1225: }
1226:
1227: if (cols.length != ref_cols.length) {
1228: throw new StatementException("Foreign key reference '"
1229: + table + "' -> '" + ref_table
1230: + "' does not have an equal number of "
1231: + "column terms.");
1232: }
1233:
1234: // If delete or update rule is 'SET NULL' then check the foreign key
1235: // columns are not constrained as 'NOT NULL'
1236: if (delete_rule.equals("SET NULL")
1237: || update_rule.equals("SET NULL")) {
1238: DataTableDef table_def = getDataTableDef(table);
1239: for (int i = 0; i < cols.length; ++i) {
1240: DataTableColumnDef column_def = table_def
1241: .columnAt(table_def.findColumnName(cols[i]));
1242: if (column_def.isNotNull()) {
1243: throw new StatementException(
1244: "Foreign key reference '"
1245: + table
1246: + "' -> '"
1247: + ref_table
1248: + "' update or delete triggered "
1249: + "action is SET NULL for columns that are constrained as "
1250: + "NOT NULL.");
1251: }
1252: }
1253: }
1254:
1255: // Insert a value into FOREIGN_INFO_TABLE
1256: RowData rd = new RowData(t);
1257: BigNumber unique_id = BigNumber.fromLong(nextUniqueID(tn1));
1258: constraint_name = makeUniqueConstraintName(constraint_name,
1259: unique_id);
1260: rd.setColumnDataFromObject(0, unique_id);
1261: rd.setColumnDataFromObject(1, constraint_name);
1262: rd.setColumnDataFromObject(2, table.getSchema());
1263: rd.setColumnDataFromObject(3, table.getName());
1264: rd.setColumnDataFromObject(4, ref_table.getSchema());
1265: rd.setColumnDataFromObject(5, ref_table.getName());
1266: rd.setColumnDataFromObject(6, update_rule);
1267: rd.setColumnDataFromObject(7, delete_rule);
1268: rd.setColumnDataFromObject(8, BigNumber.fromInt(deferred));
1269: t.addRow(rd);
1270:
1271: // Insert the columns
1272: for (int i = 0; i < cols.length; ++i) {
1273: rd = new RowData(tcols);
1274: rd.setColumnDataFromObject(0, unique_id); // unique id
1275: rd.setColumnDataFromObject(1, cols[i]); // column name
1276: rd.setColumnDataFromObject(2, ref_cols[i]); // ref column name
1277: rd.setColumnDataFromObject(3, BigNumber.fromInt(i)); // sequence number
1278: tcols.addRow(rd);
1279: }
1280:
1281: } catch (DatabaseConstraintViolationException e) {
1282: // Constraint violation when inserting the data. Check the type and
1283: // wrap around an appropriate error message.
1284: if (e.getErrorCode() == DatabaseConstraintViolationException.UNIQUE_VIOLATION) {
1285: // This means we gave a constraint name that's already being used
1286: // for a primary key.
1287: throw new StatementException(
1288: "Foreign key constraint name '"
1289: + constraint_name
1290: + "' is already being used.");
1291: }
1292: throw e;
1293: }
1294:
1295: }
1296:
1297: /**
1298: * Adds a primary key constraint that becomes perminent when the transaction
1299: * is committed. A primary key represents a set of columns in a table
1300: * that are constrained to be unique and can not be null. If the
1301: * constraint name parameter is 'null' a primary key constraint is created
1302: * with a unique constraint name.
1303: * <p>
1304: * NOTE: Security checks for adding constraints must be checked for at a
1305: * higher layer.
1306: * <p>
1307: * NOTE: We must guarentee that the transaction be in exclusive mode before
1308: * this method is called.
1309: */
1310: public void addPrimaryKeyConstraint(TableName table_name,
1311: String[] cols, short deferred, String constraint_name) {
1312:
1313: TableName tn1 = TableDataConglomerate.PRIMARY_INFO_TABLE;
1314: TableName tn2 = TableDataConglomerate.PRIMARY_COLS_TABLE;
1315: MutableTableDataSource t = getTable(tn1);
1316: MutableTableDataSource tcols = getTable(tn2);
1317:
1318: try {
1319:
1320: // Insert a value into PRIMARY_INFO_TABLE
1321: RowData rd = new RowData(t);
1322: BigNumber unique_id = BigNumber.fromLong(nextUniqueID(tn1));
1323: constraint_name = makeUniqueConstraintName(constraint_name,
1324: unique_id);
1325: rd.setColumnDataFromObject(0, unique_id);
1326: rd.setColumnDataFromObject(1, constraint_name);
1327: rd.setColumnDataFromObject(2, table_name.getSchema());
1328: rd.setColumnDataFromObject(3, table_name.getName());
1329: rd.setColumnDataFromObject(4, BigNumber.fromInt(deferred));
1330: t.addRow(rd);
1331:
1332: // Insert the columns
1333: for (int i = 0; i < cols.length; ++i) {
1334: rd = new RowData(tcols);
1335: rd.setColumnDataFromObject(0, unique_id); // unique id
1336: rd.setColumnDataFromObject(1, cols[i]); // column name
1337: rd.setColumnDataFromObject(2, BigNumber.fromInt(i)); // Sequence number
1338: tcols.addRow(rd);
1339: }
1340:
1341: } catch (DatabaseConstraintViolationException e) {
1342: // Constraint violation when inserting the data. Check the type and
1343: // wrap around an appropriate error message.
1344: if (e.getErrorCode() == DatabaseConstraintViolationException.UNIQUE_VIOLATION) {
1345: // This means we gave a constraint name that's already being used
1346: // for a primary key.
1347: throw new StatementException(
1348: "Primary key constraint name '"
1349: + constraint_name
1350: + "' is already being used.");
1351: }
1352: throw e;
1353: }
1354:
1355: }
1356:
1357: /**
1358: * Adds a check expression that becomes perminent when the transaction
1359: * is committed. A check expression is an expression that must evaluate
1360: * to true for all records added/updated in the database.
1361: * <p>
1362: * NOTE: Security checks for adding constraints must be checked for at a
1363: * higher layer.
1364: * <p>
1365: * NOTE: We must guarentee that the transaction be in exclusive mode before
1366: * this method is called.
1367: */
1368: public void addCheckConstraint(TableName table_name,
1369: Expression expression, short deferred,
1370: String constraint_name) {
1371:
1372: TableName tn = TableDataConglomerate.CHECK_INFO_TABLE;
1373: MutableTableDataSource t = getTable(tn);
1374: int col_count = t.getDataTableDef().columnCount();
1375:
1376: try {
1377:
1378: // Insert check constraint data.
1379: BigNumber unique_id = BigNumber.fromLong(nextUniqueID(tn));
1380: constraint_name = makeUniqueConstraintName(constraint_name,
1381: unique_id);
1382: RowData rd = new RowData(t);
1383: rd.setColumnDataFromObject(0, unique_id);
1384: rd.setColumnDataFromObject(1, constraint_name);
1385: rd.setColumnDataFromObject(2, table_name.getSchema());
1386: rd.setColumnDataFromObject(3, table_name.getName());
1387: rd
1388: .setColumnDataFromObject(4, new String(expression
1389: .text()));
1390: rd.setColumnDataFromObject(5, BigNumber.fromInt(deferred));
1391: if (col_count > 6) {
1392: // Serialize the check expression
1393: ByteLongObject serialized_expression = ObjectTranslator
1394: .serialize(expression);
1395: rd.setColumnDataFromObject(6, serialized_expression);
1396: }
1397: t.addRow(rd);
1398:
1399: } catch (DatabaseConstraintViolationException e) {
1400: // Constraint violation when inserting the data. Check the type and
1401: // wrap around an appropriate error message.
1402: if (e.getErrorCode() == DatabaseConstraintViolationException.UNIQUE_VIOLATION) {
1403: // This means we gave a constraint name that's already being used.
1404: throw new StatementException("Check constraint name '"
1405: + constraint_name + "' is already being used.");
1406: }
1407: throw e;
1408: }
1409:
1410: }
1411:
1412: /**
1413: * Drops all the constraints defined for the given table. This is a useful
1414: * function when dropping a table from the database.
1415: * <p>
1416: * NOTE: Security checks that the user can drop constraints must be checke at
1417: * a higher layer.
1418: * <p>
1419: * NOTE: We must guarentee that the transaction be in exclusive mode before
1420: * this method is called.
1421: */
1422: public void dropAllConstraintsForTable(TableName table_name) {
1423: ColumnGroup primary = queryTablePrimaryKeyGroup(this ,
1424: table_name);
1425: ColumnGroup[] uniques = queryTableUniqueGroups(this , table_name);
1426: CheckExpression[] expressions = queryTableCheckExpressions(
1427: this , table_name);
1428: ColumnGroupReference[] refs = queryTableForeignKeyReferences(
1429: this , table_name);
1430:
1431: if (primary != null) {
1432: dropPrimaryKeyConstraintForTable(table_name, primary.name);
1433: }
1434: for (int i = 0; i < uniques.length; ++i) {
1435: dropUniqueConstraintForTable(table_name, uniques[i].name);
1436: }
1437: for (int i = 0; i < expressions.length; ++i) {
1438: dropCheckConstraintForTable(table_name, expressions[i].name);
1439: }
1440: for (int i = 0; i < refs.length; ++i) {
1441: dropForeignKeyReferenceConstraintForTable(table_name,
1442: refs[i].name);
1443: }
1444:
1445: }
1446:
1447: /**
1448: * Drops the named constraint from the transaction. Used when altering
1449: * table schema. Returns the number of constraints that were removed from
1450: * the system. If this method returns 0 then it indicates there is no
1451: * constraint with the given name in the table.
1452: * <p>
1453: * NOTE: Security checks that the user can drop constraints must be checke at
1454: * a higher layer.
1455: * <p>
1456: * NOTE: We must guarentee that the transaction be in exclusive mode before
1457: * this method is called.
1458: */
1459: public int dropNamedConstraint(TableName table_name,
1460: String constraint_name) {
1461:
1462: int drop_count = 0;
1463: if (dropPrimaryKeyConstraintForTable(table_name,
1464: constraint_name)) {
1465: ++drop_count;
1466: }
1467: if (dropUniqueConstraintForTable(table_name, constraint_name)) {
1468: ++drop_count;
1469: }
1470: if (dropCheckConstraintForTable(table_name, constraint_name)) {
1471: ++drop_count;
1472: }
1473: if (dropForeignKeyReferenceConstraintForTable(table_name,
1474: constraint_name)) {
1475: ++drop_count;
1476: }
1477: return drop_count;
1478: }
1479:
1480: /**
1481: * Drops the primary key constraint for the given table. Used when altering
1482: * table schema. If 'constraint_name' is null this method will search for
1483: * the primary key of the table name. Returns true if the primary key
1484: * constraint was dropped (the constraint existed).
1485: * <p>
1486: * NOTE: Security checks that the user can drop constraints must be checke at
1487: * a higher layer.
1488: * <p>
1489: * NOTE: We must guarentee that the transaction be in exclusive mode before
1490: * this method is called.
1491: */
1492: public boolean dropPrimaryKeyConstraintForTable(
1493: TableName table_name, String constraint_name) {
1494:
1495: MutableTableDataSource t = getTable(TableDataConglomerate.PRIMARY_INFO_TABLE);
1496: MutableTableDataSource t2 = getTable(TableDataConglomerate.PRIMARY_COLS_TABLE);
1497: SimpleTableQuery dt = new SimpleTableQuery(t); // The info table
1498: SimpleTableQuery dtcols = new SimpleTableQuery(t2); // The columns
1499:
1500: try {
1501: IntegerVector data;
1502: if (constraint_name != null) {
1503: // Returns the list of indexes where column 1 = constraint name
1504: // and column 2 = schema name
1505: data = dt.selectIndexesEqual(1, constraint_name, 2,
1506: table_name.getSchema());
1507: } else {
1508: // Returns the list of indexes where column 3 = table name
1509: // and column 2 = schema name
1510: data = dt.selectIndexesEqual(3, table_name.getName(),
1511: 2, table_name.getSchema());
1512: }
1513:
1514: if (data.size() > 1) {
1515: throw new Error(
1516: "Assertion failed: multiple primary key for: "
1517: + table_name);
1518: } else if (data.size() == 1) {
1519: int row_index = data.intAt(0);
1520: // The id
1521: TObject id = dt.get(0, row_index);
1522: // All columns with this id
1523: IntegerVector ivec = dtcols.selectIndexesEqual(0, id);
1524: // Delete from the table
1525: dtcols.deleteRows(ivec);
1526: dt.deleteRows(data);
1527: return true;
1528: }
1529: // data.size() must be 0 so no constraint was found to drop.
1530: return false;
1531:
1532: } finally {
1533: dtcols.dispose();
1534: dt.dispose();
1535: }
1536:
1537: }
1538:
1539: /**
1540: * Drops a single named unique constraint from the given table. Returns
1541: * true if the unique constraint was dropped (the constraint existed).
1542: * <p>
1543: * NOTE: Security checks that the user can drop constraints must be checke at
1544: * a higher layer.
1545: * <p>
1546: * NOTE: We must guarentee that the transaction be in exclusive mode before
1547: * this method is called.
1548: */
1549: public boolean dropUniqueConstraintForTable(TableName table,
1550: String constraint_name) {
1551:
1552: MutableTableDataSource t = getTable(TableDataConglomerate.UNIQUE_INFO_TABLE);
1553: MutableTableDataSource t2 = getTable(TableDataConglomerate.UNIQUE_COLS_TABLE);
1554: SimpleTableQuery dt = new SimpleTableQuery(t); // The info table
1555: SimpleTableQuery dtcols = new SimpleTableQuery(t2); // The columns
1556:
1557: try {
1558: // Returns the list of indexes where column 1 = constraint name
1559: // and column 2 = schema name
1560: IntegerVector data = dt.selectIndexesEqual(1,
1561: constraint_name, 2, table.getSchema());
1562:
1563: if (data.size() > 1) {
1564: throw new Error(
1565: "Assertion failed: multiple unique constraint name: "
1566: + constraint_name);
1567: } else if (data.size() == 1) {
1568: int row_index = data.intAt(0);
1569: // The id
1570: TObject id = dt.get(0, row_index);
1571: // All columns with this id
1572: IntegerVector ivec = dtcols.selectIndexesEqual(0, id);
1573: // Delete from the table
1574: dtcols.deleteRows(ivec);
1575: dt.deleteRows(data);
1576: return true;
1577: }
1578: // data.size() == 0 so the constraint wasn't found
1579: return false;
1580: } finally {
1581: dtcols.dispose();
1582: dt.dispose();
1583: }
1584:
1585: }
1586:
1587: /**
1588: * Drops a single named check constraint from the given table. Returns true
1589: * if the check constraint was dropped (the constraint existed).
1590: * <p>
1591: * NOTE: Security checks that the user can drop constraints must be checke at
1592: * a higher layer.
1593: * <p>
1594: * NOTE: We must guarentee that the transaction be in exclusive mode before
1595: * this method is called.
1596: */
1597: public boolean dropCheckConstraintForTable(TableName table,
1598: String constraint_name) {
1599:
1600: MutableTableDataSource t = getTable(TableDataConglomerate.CHECK_INFO_TABLE);
1601: SimpleTableQuery dt = new SimpleTableQuery(t); // The info table
1602:
1603: try {
1604: // Returns the list of indexes where column 1 = constraint name
1605: // and column 2 = schema name
1606: IntegerVector data = dt.selectIndexesEqual(1,
1607: constraint_name, 2, table.getSchema());
1608:
1609: if (data.size() > 1) {
1610: throw new Error(
1611: "Assertion failed: multiple check constraint name: "
1612: + constraint_name);
1613: } else if (data.size() == 1) {
1614: // Delete the check constraint
1615: dt.deleteRows(data);
1616: return true;
1617: }
1618: // data.size() == 0 so the constraint wasn't found
1619: return false;
1620: } finally {
1621: dt.dispose();
1622: }
1623:
1624: }
1625:
1626: /**
1627: * Drops a single named foreign key reference from the given table. Returns
1628: * true if the foreign key reference constraint was dropped (the constraint
1629: * existed).
1630: * <p>
1631: * NOTE: Security checks that the user can drop constraints must be checke at
1632: * a higher layer.
1633: * <p>
1634: * NOTE: We must guarentee that the transaction be in exclusive mode before
1635: * this method is called.
1636: */
1637: public boolean dropForeignKeyReferenceConstraintForTable(
1638: TableName table, String constraint_name) {
1639:
1640: MutableTableDataSource t = getTable(TableDataConglomerate.FOREIGN_INFO_TABLE);
1641: MutableTableDataSource t2 = getTable(TableDataConglomerate.FOREIGN_COLS_TABLE);
1642: SimpleTableQuery dt = new SimpleTableQuery(t); // The info table
1643: SimpleTableQuery dtcols = new SimpleTableQuery(t2); // The columns
1644:
1645: try {
1646: // Returns the list of indexes where column 1 = constraint name
1647: // and column 2 = schema name
1648: IntegerVector data = dt.selectIndexesEqual(1,
1649: constraint_name, 2, table.getSchema());
1650:
1651: if (data.size() > 1) {
1652: throw new Error(
1653: "Assertion failed: multiple foreign key constraint "
1654: + "name: " + constraint_name);
1655: } else if (data.size() == 1) {
1656: int row_index = data.intAt(0);
1657: // The id
1658: TObject id = dt.get(0, row_index);
1659: // All columns with this id
1660: IntegerVector ivec = dtcols.selectIndexesEqual(0, id);
1661: // Delete from the table
1662: dtcols.deleteRows(ivec);
1663: dt.deleteRows(data);
1664: return true;
1665: }
1666: // data.size() == 0 so the constraint wasn't found
1667: return false;
1668: } finally {
1669: dtcols.dispose();
1670: dt.dispose();
1671: }
1672:
1673: }
1674:
1675: /**
1676: * Returns the list of tables (as a TableName array) that are dependant
1677: * on the data in the given table to maintain referential consistancy. The
1678: * list includes the tables referenced as foreign keys, and the tables
1679: * that reference the table as a foreign key.
1680: * <p>
1681: * This is a useful query for determining ahead of time the tables that
1682: * require a read lock when inserting/updating a table. A table will require
1683: * a read lock if the operation needs to query it for potential referential
1684: * integrity violations.
1685: */
1686: public static TableName[] queryTablesRelationallyLinkedTo(
1687: SimpleTransaction transaction, TableName table) {
1688: ArrayList list = new ArrayList();
1689: ColumnGroupReference[] refs = queryTableForeignKeyReferences(
1690: transaction, table);
1691: for (int i = 0; i < refs.length; ++i) {
1692: TableName tname = refs[i].ref_table_name;
1693: if (!list.contains(tname)) {
1694: list.add(tname);
1695: }
1696: }
1697: refs = queryTableImportedForeignKeyReferences(transaction,
1698: table);
1699: for (int i = 0; i < refs.length; ++i) {
1700: TableName tname = refs[i].key_table_name;
1701: if (!list.contains(tname)) {
1702: list.add(tname);
1703: }
1704: }
1705: return (TableName[]) list.toArray(new TableName[list.size()]);
1706: }
1707:
1708: /**
1709: * Returns a set of unique groups that are constrained to be unique for
1710: * the given table in this transaction. For example, if columns ('name')
1711: * and ('number', 'document_rev') are defined as unique, this will return
1712: * an array of two groups that represent unique columns in the given
1713: * table.
1714: */
1715: public static ColumnGroup[] queryTableUniqueGroups(
1716: SimpleTransaction transaction, TableName table_name) {
1717: TableDataSource t = transaction
1718: .getTableDataSource(TableDataConglomerate.UNIQUE_INFO_TABLE);
1719: TableDataSource t2 = transaction
1720: .getTableDataSource(TableDataConglomerate.UNIQUE_COLS_TABLE);
1721: SimpleTableQuery dt = new SimpleTableQuery(t); // The info table
1722: SimpleTableQuery dtcols = new SimpleTableQuery(t2); // The columns
1723:
1724: ColumnGroup[] groups;
1725: try {
1726: // Returns the list indexes where column 3 = table name
1727: // and column 2 = schema name
1728: IntegerVector data = dt.selectIndexesEqual(3, table_name
1729: .getName(), 2, table_name.getSchema());
1730: groups = new ColumnGroup[data.size()];
1731:
1732: for (int i = 0; i < data.size(); ++i) {
1733: TObject id = dt.get(0, data.intAt(i));
1734:
1735: // Select all records with equal id
1736: IntegerVector cols = dtcols.selectIndexesEqual(0, id);
1737:
1738: // Put into a group.
1739: ColumnGroup group = new ColumnGroup();
1740: // constraint name
1741: group.name = dt.get(1, data.intAt(i)).getObject()
1742: .toString();
1743: group.columns = toColumns(dtcols, cols); // the list of columns
1744: group.deferred = ((BigNumber) dt.get(4, data.intAt(i))
1745: .getObject()).shortValue();
1746: groups[i] = group;
1747: }
1748: } finally {
1749: dt.dispose();
1750: dtcols.dispose();
1751: }
1752:
1753: return groups;
1754: }
1755:
1756: /**
1757: * Returns a set of primary key groups that are constrained to be unique
1758: * for the given table in this transaction (there can be only 1 primary
1759: * key defined for a table). Returns null if there is no primary key
1760: * defined for the table.
1761: */
1762: public static ColumnGroup queryTablePrimaryKeyGroup(
1763: SimpleTransaction transaction, TableName table_name) {
1764: TableDataSource t = transaction
1765: .getTableDataSource(TableDataConglomerate.PRIMARY_INFO_TABLE);
1766: TableDataSource t2 = transaction
1767: .getTableDataSource(TableDataConglomerate.PRIMARY_COLS_TABLE);
1768: SimpleTableQuery dt = new SimpleTableQuery(t); // The info table
1769: SimpleTableQuery dtcols = new SimpleTableQuery(t2); // The columns
1770:
1771: try {
1772: // Returns the list indexes where column 3 = table name
1773: // and column 2 = schema name
1774: IntegerVector data = dt.selectIndexesEqual(3, table_name
1775: .getName(), 2, table_name.getSchema());
1776:
1777: if (data.size() > 1) {
1778: throw new Error(
1779: "Assertion failed: multiple primary key for: "
1780: + table_name);
1781: } else if (data.size() == 1) {
1782: int row_index = data.intAt(0);
1783: // The id
1784: TObject id = dt.get(0, row_index);
1785: // All columns with this id
1786: IntegerVector ivec = dtcols.selectIndexesEqual(0, id);
1787: // Make it in to a columns object
1788: ColumnGroup group = new ColumnGroup();
1789: group.name = dt.get(1, row_index).getObject()
1790: .toString();
1791: group.columns = toColumns(dtcols, ivec);
1792: group.deferred = ((BigNumber) dt.get(4, row_index)
1793: .getObject()).shortValue();
1794: return group;
1795: } else {
1796: return null;
1797: }
1798: } finally {
1799: dt.dispose();
1800: dtcols.dispose();
1801: }
1802:
1803: }
1804:
1805: /**
1806: * Returns a set of check expressions that are constrained over all new
1807: * columns added to the given table in this transaction. For example,
1808: * we may want a column called 'serial_number' to be constrained as
1809: * CHECK serial_number LIKE '___-________-___'.
1810: */
1811: public static CheckExpression[] queryTableCheckExpressions(
1812: SimpleTransaction transaction, TableName table_name) {
1813: TableDataSource t = transaction
1814: .getTableDataSource(TableDataConglomerate.CHECK_INFO_TABLE);
1815: SimpleTableQuery dt = new SimpleTableQuery(t); // The info table
1816:
1817: CheckExpression[] checks;
1818: try {
1819: // Returns the list indexes where column 3 = table name
1820: // and column 2 = schema name
1821: IntegerVector data = dt.selectIndexesEqual(3, table_name
1822: .getName(), 2, table_name.getSchema());
1823: checks = new CheckExpression[data.size()];
1824:
1825: for (int i = 0; i < checks.length; ++i) {
1826: int row_index = data.intAt(i);
1827:
1828: CheckExpression check = new CheckExpression();
1829: check.name = dt.get(1, row_index).getObject()
1830: .toString();
1831: check.deferred = ((BigNumber) dt.get(5, row_index)
1832: .getObject()).shortValue();
1833: // Is the deserialized version available?
1834: if (t.getDataTableDef().columnCount() > 6) {
1835: ByteLongObject sexp = (ByteLongObject) dt.get(6,
1836: row_index).getObject();
1837: if (sexp != null) {
1838: try {
1839: // Deserialize the expression
1840: check.expression = (Expression) ObjectTranslator
1841: .deserialize(sexp);
1842: } catch (Throwable e) {
1843: // We weren't able to deserialize the expression so report the
1844: // error to the log
1845: transaction.Debug().write(
1846: Lvl.WARNING,
1847: Transaction.class,
1848: "Unable to deserialize the check expression. "
1849: + "The error is: "
1850: + e.getMessage());
1851: transaction
1852: .Debug()
1853: .write(Lvl.WARNING,
1854: Transaction.class,
1855: "Parsing the check expression instead.");
1856: check.expression = null;
1857: }
1858: }
1859: }
1860: // Otherwise we need to parse it from the string
1861: if (check.expression == null) {
1862: Expression exp = Expression.parse(dt.get(4,
1863: row_index).getObject().toString());
1864: check.expression = exp;
1865: }
1866: checks[i] = check;
1867: }
1868:
1869: } finally {
1870: dt.dispose();
1871: }
1872:
1873: return checks;
1874: }
1875:
1876: /**
1877: * Returns an array of column references in the given table that represent
1878: * foreign key references. For example, say a foreign reference has been
1879: * set up in the given table as follows;<p><pre>
1880: * FOREIGN KEY (customer_id) REFERENCES Customer (id)
1881: * </pre><p>
1882: * This method will return the column group reference
1883: * Order(customer_id) -> Customer(id).
1884: * <p>
1885: * This method is used to check that a foreign key reference actually points
1886: * to a valid record in the referenced table as expected.
1887: */
1888: public static ColumnGroupReference[] queryTableForeignKeyReferences(
1889: SimpleTransaction transaction, TableName table_name) {
1890:
1891: TableDataSource t = transaction
1892: .getTableDataSource(TableDataConglomerate.FOREIGN_INFO_TABLE);
1893: TableDataSource t2 = transaction
1894: .getTableDataSource(TableDataConglomerate.FOREIGN_COLS_TABLE);
1895: SimpleTableQuery dt = new SimpleTableQuery(t); // The info table
1896: SimpleTableQuery dtcols = new SimpleTableQuery(t2); // The columns
1897:
1898: ColumnGroupReference[] groups;
1899: try {
1900: // Returns the list indexes where column 3 = table name
1901: // and column 2 = schema name
1902: IntegerVector data = dt.selectIndexesEqual(3, table_name
1903: .getName(), 2, table_name.getSchema());
1904: groups = new ColumnGroupReference[data.size()];
1905:
1906: for (int i = 0; i < data.size(); ++i) {
1907: int row_index = data.intAt(i);
1908:
1909: // The foreign key id
1910: TObject id = dt.get(0, row_index);
1911:
1912: // The referenced table
1913: TableName ref_table_name = new TableName(dt.get(4,
1914: row_index).getObject().toString(), dt.get(5,
1915: row_index).getObject().toString());
1916:
1917: // Select all records with equal id
1918: IntegerVector cols = dtcols.selectIndexesEqual(0, id);
1919:
1920: // Put into a group.
1921: ColumnGroupReference group = new ColumnGroupReference();
1922: // constraint name
1923: group.name = dt.get(1, row_index).getObject()
1924: .toString();
1925: group.key_table_name = table_name;
1926: group.ref_table_name = ref_table_name;
1927: group.update_rule = dt.get(6, row_index).getObject()
1928: .toString();
1929: group.delete_rule = dt.get(7, row_index).getObject()
1930: .toString();
1931: group.deferred = ((BigNumber) dt.get(8, row_index)
1932: .getObject()).shortValue();
1933:
1934: int cols_size = cols.size();
1935: String[] key_cols = new String[cols_size];
1936: String[] ref_cols = new String[cols_size];
1937: for (int n = 0; n < cols_size; ++n) {
1938: for (int p = 0; p < cols_size; ++p) {
1939: int cols_index = cols.intAt(p);
1940: if (((BigNumber) dtcols.get(3, cols_index)
1941: .getObject()).intValue() == n) {
1942: key_cols[n] = dtcols.get(1, cols_index)
1943: .getObject().toString();
1944: ref_cols[n] = dtcols.get(2, cols_index)
1945: .getObject().toString();
1946: break;
1947: }
1948: }
1949: }
1950: group.key_columns = key_cols;
1951: group.ref_columns = ref_cols;
1952:
1953: groups[i] = group;
1954: }
1955: } finally {
1956: dt.dispose();
1957: dtcols.dispose();
1958: }
1959:
1960: return groups;
1961: }
1962:
1963: /**
1964: * Returns an array of column references in the given table that represent
1965: * foreign key references that reference columns in the given table. This
1966: * is a reverse mapping of the 'queryTableForeignKeyReferences' method. For
1967: * example, say a foreign reference has been set up in any table as follows;
1968: * <p><pre>
1969: * [ In table Order ]
1970: * FOREIGN KEY (customer_id) REFERENCE Customer (id)
1971: * </pre><p>
1972: * And the table name we are querying is 'Customer' then this method will
1973: * return the column group reference
1974: * Order(customer_id) -> Customer(id).
1975: * <p>
1976: * This method is used to check that a reference isn't broken when we remove
1977: * a record (for example, removing a Customer that has references to it will
1978: * break integrity).
1979: */
1980: public static ColumnGroupReference[] queryTableImportedForeignKeyReferences(
1981: SimpleTransaction transaction, TableName ref_table_name) {
1982:
1983: TableDataSource t = transaction
1984: .getTableDataSource(TableDataConglomerate.FOREIGN_INFO_TABLE);
1985: TableDataSource t2 = transaction
1986: .getTableDataSource(TableDataConglomerate.FOREIGN_COLS_TABLE);
1987: SimpleTableQuery dt = new SimpleTableQuery(t); // The info table
1988: SimpleTableQuery dtcols = new SimpleTableQuery(t2); // The columns
1989:
1990: ColumnGroupReference[] groups;
1991: try {
1992: // Returns the list indexes where column 5 = ref table name
1993: // and column 4 = ref schema name
1994: IntegerVector data = dt.selectIndexesEqual(5,
1995: ref_table_name.getName(), 4, ref_table_name
1996: .getSchema());
1997: groups = new ColumnGroupReference[data.size()];
1998:
1999: for (int i = 0; i < data.size(); ++i) {
2000: int row_index = data.intAt(i);
2001:
2002: // The foreign key id
2003: TObject id = dt.get(0, row_index);
2004:
2005: // The referencee table
2006: TableName table_name = new TableName(dt.get(2,
2007: row_index).getObject().toString(), dt.get(3,
2008: row_index).getObject().toString());
2009:
2010: // Select all records with equal id
2011: IntegerVector cols = dtcols.selectIndexesEqual(0, id);
2012:
2013: // Put into a group.
2014: ColumnGroupReference group = new ColumnGroupReference();
2015: // constraint name
2016: group.name = dt.get(1, row_index).getObject()
2017: .toString();
2018: group.key_table_name = table_name;
2019: group.ref_table_name = ref_table_name;
2020: group.update_rule = dt.get(6, row_index).getObject()
2021: .toString();
2022: group.delete_rule = dt.get(7, row_index).getObject()
2023: .toString();
2024: group.deferred = ((BigNumber) dt.get(8, row_index)
2025: .getObject()).shortValue();
2026:
2027: int cols_size = cols.size();
2028: String[] key_cols = new String[cols_size];
2029: String[] ref_cols = new String[cols_size];
2030: for (int n = 0; n < cols_size; ++n) {
2031: for (int p = 0; p < cols_size; ++p) {
2032: int cols_index = cols.intAt(p);
2033: if (((BigNumber) dtcols.get(3, cols_index)
2034: .getObject()).intValue() == n) {
2035: key_cols[n] = dtcols.get(1, cols_index)
2036: .getObject().toString();
2037: ref_cols[n] = dtcols.get(2, cols_index)
2038: .getObject().toString();
2039: break;
2040: }
2041: }
2042: }
2043: group.key_columns = key_cols;
2044: group.ref_columns = ref_cols;
2045:
2046: groups[i] = group;
2047: }
2048: } finally {
2049: dt.dispose();
2050: dtcols.dispose();
2051: }
2052:
2053: return groups;
2054: }
2055:
2056: // ----- Transaction close operations -----
2057:
2058: /**
2059: * Closes and marks a transaction as committed. Any changes made by this
2060: * transaction are seen by all transactions created after this method
2061: * returns.
2062: * <p>
2063: * This method will fail under the following circumstances:
2064: * <ol>
2065: * <li> There are any rows deleted in this transaction that were deleted
2066: * by another successfully committed transaction.
2067: * <li> There were rows added in another committed transaction that would
2068: * change the result of the search clauses committed by this transaction.
2069: * </ol>
2070: * The first check is not too difficult to check for. The second is very
2071: * difficult however we need it to ensure TRANSACTION_SERIALIZABLE isolation
2072: * is enforced. We may have to simplify this by throwing a transaction
2073: * exception if the table has had any changes made to it during this
2074: * transaction.
2075: * <p>
2076: * This should only be called under an exclusive lock on the connection.
2077: */
2078: public void closeAndCommit() throws TransactionException {
2079:
2080: if (!closed) {
2081: try {
2082: closed = true;
2083: // Get the conglomerate to do this commit.
2084: conglomerate.processCommit(this , getVisibleTables(),
2085: selected_from_tables, touched_tables, journal);
2086: } finally {
2087: cleanup();
2088: }
2089: }
2090:
2091: }
2092:
2093: /**
2094: * Closes and rolls back a transaction as if the commands the transaction ran
2095: * never happened. This will not throw a transaction exception.
2096: * <p>
2097: * This should only be called under an exclusive lock on the connection.
2098: */
2099: public void closeAndRollback() {
2100:
2101: if (!closed) {
2102: try {
2103: closed = true;
2104: // Notify the conglomerate that this transaction has closed.
2105: conglomerate.processRollback(this , touched_tables,
2106: journal);
2107: } finally {
2108: cleanup();
2109: }
2110: }
2111:
2112: }
2113:
2114: /**
2115: * Cleans up this transaction.
2116: */
2117: private void cleanup() {
2118: getSystem().stats().decrement("Transaction.count");
2119: // Dispose of all the IndexSet objects created by this transaction.
2120: disposeAllIndices();
2121:
2122: // Dispose all the table we touched
2123: try {
2124: for (int i = 0; i < touched_tables.size(); ++i) {
2125: MutableTableDataSource source = (MutableTableDataSource) touched_tables
2126: .get(i);
2127: source.dispose();
2128: }
2129: } catch (Throwable e) {
2130: Debug().writeException(e);
2131: }
2132:
2133: getSystem().stats().increment("Transaction.cleanup");
2134: conglomerate = null;
2135: touched_tables = null;
2136: journal = null;
2137: }
2138:
2139: /**
2140: * Disposes this transaction without rolling back or committing the changes.
2141: * Care should be taken when using this - it must only be used for simple
2142: * transactions that are short lived and have not modified the database.
2143: */
2144: void dispose() {
2145: if (!isReadOnly()) {
2146: throw new RuntimeException(
2147: "Assertion failed - tried to dispose a non read-only transaction.");
2148: }
2149: if (!closed) {
2150: closed = true;
2151: cleanup();
2152: }
2153: }
2154:
2155: /**
2156: * Finalize, we should close the transaction.
2157: */
2158: public void finalize() throws Throwable {
2159: super .finalize();
2160: if (!closed) {
2161: Debug().write(Lvl.ERROR, this , "Transaction not closed!");
2162: closeAndRollback();
2163: }
2164: }
2165:
2166: // ---------- Transaction inner classes ----------
2167:
2168: /**
2169: * A list of DataTableDef system table definitions for tables internal to
2170: * the transaction.
2171: */
2172: private final static DataTableDef[] INTERNAL_DEF_LIST;
2173:
2174: static {
2175: INTERNAL_DEF_LIST = new DataTableDef[3];
2176: INTERNAL_DEF_LIST[0] = GTTableColumnsDataSource.DEF_DATA_TABLE_DEF;
2177: INTERNAL_DEF_LIST[1] = GTTableInfoDataSource.DEF_DATA_TABLE_DEF;
2178: INTERNAL_DEF_LIST[2] = GTProductDataSource.DEF_DATA_TABLE_DEF;
2179: }
2180:
2181: /**
2182: * A static internal table info for internal tables to the transaction.
2183: * This implementation includes all the dynamically generated system tables
2184: * that are tied to information in a transaction.
2185: */
2186: private class TransactionInternalTables extends
2187: AbstractInternalTableInfo {
2188:
2189: /**
2190: * Constructor.
2191: */
2192: public TransactionInternalTables() {
2193: super ("SYSTEM TABLE", INTERNAL_DEF_LIST);
2194: }
2195:
2196: // ---------- Implemented ----------
2197:
2198: public MutableTableDataSource createInternalTable(int index) {
2199: if (index == 0) {
2200: return new GTTableColumnsDataSource(Transaction.this )
2201: .init();
2202: } else if (index == 1) {
2203: return new GTTableInfoDataSource(Transaction.this )
2204: .init();
2205: } else if (index == 2) {
2206: return new GTProductDataSource(Transaction.this ).init();
2207: } else {
2208: throw new RuntimeException();
2209: }
2210: }
2211:
2212: }
2213:
2214: /**
2215: * A group of columns as used by the constraint system. A ColumnGroup is
2216: * a simple list of columns in a table.
2217: */
2218: public static class ColumnGroup {
2219:
2220: /**
2221: * The name of the group (the constraint name).
2222: */
2223: public String name;
2224:
2225: /**
2226: * The list of columns that make up the group.
2227: */
2228: public String[] columns;
2229:
2230: /**
2231: * Whether this is deferred or initially immediate.
2232: */
2233: public short deferred;
2234:
2235: }
2236:
2237: /**
2238: * Represents a constraint expression to check.
2239: */
2240: public static class CheckExpression {
2241:
2242: /**
2243: * The name of the check expression (the constraint name).
2244: */
2245: public String name;
2246:
2247: /**
2248: * The expression to check.
2249: */
2250: public Expression expression;
2251:
2252: /**
2253: * Whether this is deferred or initially immediate.
2254: */
2255: public short deferred;
2256:
2257: }
2258:
2259: /**
2260: * Represents a reference from a group of columns in one table to a group of
2261: * columns in another table. The is used to represent a foreign key
2262: * reference.
2263: */
2264: public static class ColumnGroupReference {
2265:
2266: /**
2267: * The name of the group (the constraint name).
2268: */
2269: public String name;
2270:
2271: /**
2272: * The key table name.
2273: */
2274: public TableName key_table_name;
2275:
2276: /**
2277: * The list of columns that make up the key.
2278: */
2279: public String[] key_columns;
2280:
2281: /**
2282: * The referenced table name.
2283: */
2284: public TableName ref_table_name;
2285:
2286: /**
2287: * The list of columns that make up the referenced group.
2288: */
2289: public String[] ref_columns;
2290:
2291: /**
2292: * The update rule.
2293: */
2294: public String update_rule;
2295:
2296: /**
2297: * The delete rule.
2298: */
2299: public String delete_rule;
2300:
2301: /**
2302: * Whether this is deferred or initially immediate.
2303: */
2304: public short deferred;
2305:
2306: }
2307:
2308: }
|