0001: /**
0002: * com.mckoi.database.DatabaseConnection 21 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.Cache;
0027: import com.mckoi.database.global.ByteLongObject;
0028: import com.mckoi.database.global.Ref;
0029: import com.mckoi.database.jdbc.SQLQuery;
0030: import java.util.HashMap;
0031: import java.util.ArrayList;
0032: import java.math.BigDecimal;
0033:
0034: /**
0035: * An object that represents a connection to a Database. This object handles
0036: * all transactional queries and modifications to the database.
0037: *
0038: * @author Tobias Downer
0039: */
0040:
0041: public class DatabaseConnection implements TriggerListener {
0042:
0043: /**
0044: * The User that this connection has been made by.
0045: */
0046: private User user;
0047:
0048: /**
0049: * The Database object that this connection is on.
0050: */
0051: private Database database;
0052:
0053: /**
0054: * The DebugLogger object that we can use to log messages to.
0055: */
0056: private DebugLogger logger;
0057:
0058: /**
0059: * A loop-back object that is managing this connection. This typically is
0060: * the session protocol. This is notified of all connection events, such as
0061: * triggers.
0062: */
0063: private CallBack call_back;
0064:
0065: /**
0066: * The locking mechanism within this connection.
0067: */
0068: private LockingMechanism locking_mechanism;
0069:
0070: /**
0071: * The TableDataConglomerate object that is used for transactional access
0072: * to the data.
0073: */
0074: private TableDataConglomerate conglomerate;
0075:
0076: /**
0077: * The current Transaction that this connection is operating within.
0078: */
0079: private Transaction transaction;
0080:
0081: /**
0082: * The current java.sql.Connection object that can be used to access the
0083: * transaction internally.
0084: */
0085: private java.sql.Connection jdbc_connection;
0086:
0087: /**
0088: * A HashMap of DataTable objects that have been created within this
0089: * connection.
0090: */
0091: private HashMap tables_cache;
0092:
0093: /**
0094: * A buffer of triggers. This contains triggers that can't fire until
0095: * the current transaction has closed. These triggers were generated by
0096: * external actions outside of the context of this transaction.
0097: */
0098: private ArrayList trigger_event_buffer;
0099:
0100: /**
0101: * A list of triggers that are fired by actions taken on tables in this
0102: * transaction. When the transaction is successfully committed, these
0103: * trigger events need to be propogated to other connections in the database
0104: * listening for trigger events on the triggered objects.
0105: */
0106: private ArrayList trigger_event_list;
0107:
0108: /**
0109: * If this is true then the database connection is in 'auto-commit' mode.
0110: * This implies a COMMIT instruction is executed after every complete
0111: * statement in the language grammar. By default this is true.
0112: */
0113: private boolean auto_commit;
0114:
0115: /**
0116: * The current transaction isolation level this connect is operating under.
0117: * 1 = READ UNCOMMITTED, 2 = READ COMMITTED, 3 = REPEATABLE READ,
0118: * 4 = SERIALIZABLE.
0119: */
0120: private int transaction_isolation;
0121:
0122: /**
0123: * A flag which if set to true, will not allow 'commit' or 'rollback'
0124: * commands on the transaction to occur and therefore prevent any open
0125: * transaction from closing. This is useful for restricting the ability
0126: * of a stored procedure to close.
0127: */
0128: private boolean close_transaction_disabled;
0129:
0130: /**
0131: * The name of the schema that this connection is currently in. If the
0132: * schema is "" then this connection is in the default schema (effectively
0133: * no schema).
0134: */
0135: private String current_schema;
0136:
0137: /**
0138: * The GrantManager object for this connection.
0139: */
0140: private GrantManager grant_manager;
0141:
0142: /**
0143: * The procedure manager object for this connection.
0144: */
0145: private ProcedureManager procedure_manager;
0146:
0147: /**
0148: * The connection trigger manager that handles actions that cause triggers
0149: * to fire on this connection.
0150: */
0151: private ConnectionTriggerManager connection_trigger_manager;
0152:
0153: /**
0154: * The connection view manager that handles view information through this
0155: * connection.
0156: */
0157: private ViewManager view_manager;
0158:
0159: /**
0160: * The list of all TableBackedCache objects that have been attached to this
0161: * DatabaseConnection and are to be notified of transaction start/end
0162: * events.
0163: */
0164: private ArrayList table_backed_cache_list;
0165:
0166: /**
0167: * A local member that represents the static list of internal tables
0168: * that represent connection specific properties such as username,
0169: * connection, statistics, etc.
0170: */
0171: private ConnectionInternalTableInfo connection_internal_table_info;
0172:
0173: // ----- Local flags -----
0174:
0175: /**
0176: * True if transactions through this connection generate an error when
0177: * there is a dirty select on a table.
0178: */
0179: private boolean error_on_dirty_select;
0180:
0181: /**
0182: * True if this connection resolves identifiers case insensitive.
0183: */
0184: private boolean case_insensitive_identifiers;
0185:
0186: // ----- OLD and NEW table information for triggers -----
0187:
0188: /**
0189: * A local member that represents the OLD and NEW system tables that
0190: * represent the OLD and NEW data in a triggered action.
0191: */
0192: private OldAndNewInternalTableInfo old_new_table_info;
0193:
0194: /**
0195: * The current state of the OLD and NEW system tables including any cached
0196: * information about the tables.
0197: */
0198: private OldNewTableState current_old_new_state = new OldNewTableState();
0199:
0200: /**
0201: * (package protected) Constructs the connection.
0202: */
0203: DatabaseConnection(Database database, User user, CallBack call_back) {
0204: this .database = database;
0205: this .user = user;
0206: this .logger = database.Debug();
0207: this .call_back = call_back;
0208: this .conglomerate = database.getConglomerate();
0209: this .locking_mechanism = new LockingMechanism(Debug());
0210: this .trigger_event_buffer = new ArrayList();
0211: this .trigger_event_list = new ArrayList();
0212: tables_cache = new HashMap();
0213: auto_commit = true;
0214:
0215: current_schema = Database.DEFAULT_SCHEMA;
0216: this .close_transaction_disabled = false;
0217:
0218: this .table_backed_cache_list = new ArrayList();
0219:
0220: connection_internal_table_info = new ConnectionInternalTableInfo();
0221: old_new_table_info = new OldAndNewInternalTableInfo();
0222:
0223: error_on_dirty_select = database.getSystem()
0224: .transactionErrorOnDirtySelect();
0225: case_insensitive_identifiers = database.getSystem()
0226: .ignoreIdentifierCase();
0227:
0228: }
0229:
0230: /**
0231: * Initializes this DatabaseConnection (possibly by initializing state from
0232: * the database).
0233: */
0234: void init() {
0235: // Create the grant manager for this connection.
0236: grant_manager = new GrantManager(this );
0237: // Create the procedure manager for this connection.
0238: procedure_manager = new ProcedureManager(this );
0239: // Create the connection trigger manager object
0240: connection_trigger_manager = new ConnectionTriggerManager(this );
0241: // Create the view manager
0242: view_manager = new ViewManager(this );
0243: }
0244:
0245: /**
0246: * Returns the transaction. If 'transaction' is null then it opens a
0247: * new transaction within the conglomerate.
0248: */
0249: private Transaction getTransaction() {
0250: synchronized (this ) {
0251: if (transaction == null) {
0252: transaction = conglomerate.createTransaction();
0253: transaction
0254: .setErrorOnDirtySelect(error_on_dirty_select);
0255: // Internal tables (connection statistics, etc)
0256: transaction
0257: .addInternalTableInfo(connection_internal_table_info);
0258: // OLD and NEW system tables (if applicable)
0259: transaction.addInternalTableInfo(old_new_table_info);
0260: // Model views as tables (obviously)
0261: transaction.addInternalTableInfo(ViewManager
0262: .createInternalTableInfo(view_manager,
0263: transaction));
0264: // Model procedures as tables
0265: transaction.addInternalTableInfo(ProcedureManager
0266: .createInternalTableInfo(transaction));
0267: // Model sequences as tables
0268: transaction.addInternalTableInfo(SequenceManager
0269: .createInternalTableInfo(transaction));
0270: // Model triggers as tables
0271: transaction
0272: .addInternalTableInfo(ConnectionTriggerManager
0273: .createInternalTableInfo(transaction));
0274:
0275: // Notify any table backed caches that this transaction has started.
0276: int sz = table_backed_cache_list.size();
0277: for (int i = 0; i < sz; ++i) {
0278: TableBackedCache cache = (TableBackedCache) table_backed_cache_list
0279: .get(i);
0280: cache.transactionStarted();
0281: }
0282:
0283: }
0284: }
0285: return transaction;
0286: }
0287:
0288: /**
0289: * Returns a freshly deserialized QueryPlanNode object for the given view
0290: * object.
0291: */
0292: QueryPlanNode createViewQueryPlanNode(TableName table_name) {
0293: return view_manager.createViewQueryPlanNode(table_name);
0294: }
0295:
0296: /**
0297: * Returns a java.sql.Connection object that can be used as a JDBC
0298: * interface to access the current transaction of this DatabaseConnection.
0299: * <p>
0300: * There are a few important considerations when using the JDBC connection;
0301: * <ul>
0302: * <li>The returned Connection does not allow auto-commit to be set. It
0303: * is intended to be used to issue commands to this
0304: * DatabaseConnection from inside a transaction so auto-commit does
0305: * not make sense.
0306: * <li>The returned object must only be accessed from the same worker
0307: * thread that is currently accessing this DatabaseConnection. The
0308: * returned Connection is <b>NOT</b> multi-thread capable.
0309: * <li>The java.sql.Connection returned here is invalidated (disposed) when
0310: * the current transaction is closed (committed or rolled back).
0311: * <li>This method returns the same java.sql.Connection on multiple calls
0312: * to this method (while a transaction is open).
0313: * <li>The DatabaseConnection must be locked in EXCLUSIVE mode or the
0314: * queries will fail.
0315: * </ul>
0316: */
0317: public java.sql.Connection getJDBCConnection() {
0318: if (jdbc_connection == null) {
0319: jdbc_connection = InternalJDBCHelper.createJDBCConnection(
0320: getUser(), this );
0321: }
0322: return jdbc_connection;
0323: }
0324:
0325: /**
0326: * Creates an object that implements ProcedureConnection that provides access
0327: * to this connection.
0328: * <p>
0329: * Note that this connection is set to the user of the privs that the
0330: * procedure executes under when this method returns.
0331: * <p>
0332: * There must be a 100% guarentee that after this method is called, a call to
0333: * 'disposeProcedureConnection' is called which cleans up the state of this
0334: * object.
0335: */
0336: ProcedureConnection createProcedureConnection(User user) {
0337: // Create the ProcedureConnection object,
0338: DCProcedureConnection c = new DCProcedureConnection();
0339: // Record the current user
0340: c.previous_user = getUser();
0341: // Record the current 'close_transaction_disabled' flag
0342: c.transaction_disabled_flag = close_transaction_disabled;
0343: // Set the new user
0344: setUser(user);
0345: // Disable the ability to close a transaction
0346: close_transaction_disabled = true;
0347: // Return
0348: return c;
0349: }
0350:
0351: /**
0352: * Disposes of the ProcedureConnection that was previously created by the
0353: * 'createProcedure' method.
0354: */
0355: void disposeProcedureConnection(ProcedureConnection connection) {
0356: DCProcedureConnection c = (DCProcedureConnection) connection;
0357: // Revert back to the previous user.
0358: setUser(c.previous_user);
0359: // Revert back to the previous transaction disable status.
0360: close_transaction_disabled = c.transaction_disabled_flag;
0361: // Dispose of the connection
0362: c.dispose();
0363: }
0364:
0365: /**
0366: * Returns the DatabaseSystem object for this connection.
0367: */
0368: public DatabaseSystem getSystem() {
0369: return database.getSystem();
0370: }
0371:
0372: /**
0373: * Returns the Database object for this connection.
0374: */
0375: public Database getDatabase() {
0376: return database;
0377: }
0378:
0379: /**
0380: * Returns the conglomerate of this connection.
0381: */
0382: TableDataConglomerate getConglomerate() {
0383: return conglomerate;
0384: }
0385:
0386: /**
0387: * Sets the User object for this connection. This is necessary because we
0388: * may want to temporarily change the user on this connection to allow
0389: * top level queries in a different privilege space.
0390: */
0391: void setUser(User user) {
0392: this .user = user;
0393: }
0394:
0395: /**
0396: * Returns the User object for this connection.
0397: */
0398: public User getUser() {
0399: return user;
0400: }
0401:
0402: /**
0403: * Returns a DebugLogger object that we can use to log debug messages to.
0404: */
0405: public final DebugLogger Debug() {
0406: return logger;
0407: }
0408:
0409: /**
0410: * Returns the connection trigger manager for this connection.
0411: */
0412: public ConnectionTriggerManager getConnectionTriggerManager() {
0413: return connection_trigger_manager;
0414: }
0415:
0416: /**
0417: * Returns the GrantManager object that manages grants for tables in the
0418: * database for this connection/user.
0419: */
0420: public GrantManager getGrantManager() {
0421: return grant_manager;
0422: }
0423:
0424: /**
0425: * Returns the ProcedureManager object that manages database functions and
0426: * procedures in the database for this connection/user.
0427: */
0428: public ProcedureManager getProcedureManager() {
0429: return procedure_manager;
0430: }
0431:
0432: /**
0433: * Sets the auto-commit mode.
0434: */
0435: public void setAutoCommit(boolean status) {
0436: auto_commit = status;
0437: }
0438:
0439: /**
0440: * Sets the transaction isolation level from a string.
0441: */
0442: public void setTransactionIsolation(String name) {
0443: if (name.equals("serializable")) {
0444: transaction_isolation = 4;
0445: } else {
0446: throw new Error("Can not set transaction isolation to "
0447: + name);
0448: }
0449: }
0450:
0451: /**
0452: * Assigns a variable to the expression for this connection. This is a
0453: * generic way of setting properties of the connection. Currently supported
0454: * variables are;
0455: * <p>
0456: * ERROR_ON_DIRTY_SELECT - set to Boolean.TRUE for turning this transaction
0457: * conflict off on this connection.
0458: * CASE_INSENSITIVE_IDENTIFIERS - Boolean.TRUE means the grammar becomes
0459: * case insensitive for identifiers resolved by the grammar.
0460: */
0461: public void setVar(String name, Expression exp) {
0462: if (name.toUpperCase().equals("ERROR_ON_DIRTY_SELECT")) {
0463: error_on_dirty_select = toBooleanValue(exp);
0464: } else if (name.toUpperCase().equals(
0465: "CASE_INSENSITIVE_IDENTIFIERS")) {
0466: case_insensitive_identifiers = toBooleanValue(exp);
0467: }
0468: }
0469:
0470: /**
0471: * Evaluates the expression to a boolean value (true or false).
0472: */
0473: private static boolean toBooleanValue(Expression exp) {
0474: Boolean b = exp.evaluate(null, null, null).toBoolean();
0475: if (b == null) {
0476: throw new StatementException(
0477: "Expression does not evaluate to a boolean (true or false).");
0478: }
0479: return b.booleanValue();
0480: }
0481:
0482: /**
0483: * Returns the auto-commit status of this connection. If this is true then
0484: * the language layer must execute a COMMIT after every statement.
0485: */
0486: public boolean getAutoCommit() {
0487: return auto_commit;
0488: }
0489:
0490: /**
0491: * Returns the transaction isolation level of this connection.
0492: */
0493: public int getTransactionIsolation() {
0494: return transaction_isolation;
0495: }
0496:
0497: /**
0498: * Returns the transaction isolation level of this connection as a string.
0499: */
0500: public String getTransactionIsolationAsString() {
0501: int il = getTransactionIsolation();
0502: if (il == 1) {
0503: return "read uncommitted";
0504: } else if (il == 2) {
0505: return "read committed";
0506: } else if (il == 3) {
0507: return "repeatable read";
0508: } else if (il == 4) {
0509: return "serializable";
0510: } else {
0511: return "unknown isolation level";
0512: }
0513: }
0514:
0515: /**
0516: * Returns the name of the schema that this connection is within.
0517: */
0518: public String getCurrentSchema() {
0519: return current_schema;
0520: }
0521:
0522: /**
0523: * Returns true if the connection is in case insensitive mode. In case
0524: * insensitive mode the case of identifier strings is not important.
0525: */
0526: public boolean isInCaseInsensitiveMode() {
0527: return case_insensitive_identifiers;
0528: }
0529:
0530: /**
0531: * Sets the schema that this connection is within.
0532: */
0533: public void setCurrentSchema(String current_schema) {
0534: this .current_schema = current_schema;
0535: }
0536:
0537: /**
0538: * Returns the LockingMechanism object that is within the context of this
0539: * database connection. This manages read/write locking within this
0540: * connection.
0541: */
0542: public LockingMechanism getLockingMechanism() {
0543: return locking_mechanism;
0544: }
0545:
0546: /**
0547: * Attaches a TableBackedCache object to this DatabaseConnection which is
0548: * notified when a transaction is started and stopped, and when the table
0549: * being backed has changes made to it.
0550: */
0551: void attachTableBackedCache(TableBackedCache cache) {
0552: cache.attachTo(conglomerate);
0553: table_backed_cache_list.add(cache);
0554: }
0555:
0556: /**
0557: * Returns a TableName[] array that contains the list of database
0558: * tables that are visible by this transaction.
0559: * <p>
0560: * This returns the list of all objects that represent queriable tables in
0561: * the database.
0562: */
0563: public TableName[] getTableList() {
0564: return getTransaction().getTableList();
0565: }
0566:
0567: /**
0568: * Returns true if the table exists within this connection transaction.
0569: */
0570: public boolean tableExists(String table_name) {
0571: return tableExists(new TableName(current_schema, table_name));
0572: }
0573:
0574: /**
0575: * Returns true if the table exists within this connection transaction.
0576: */
0577: public boolean tableExists(TableName table_name) {
0578: table_name = substituteReservedTableName(table_name);
0579: return getTransaction().tableExists(table_name);
0580: }
0581:
0582: /**
0583: * Returns the type of the table object. Currently this is either "TABLE"
0584: * or "VIEW".
0585: */
0586: public String getTableType(TableName table_name) {
0587: table_name = substituteReservedTableName(table_name);
0588: return getTransaction().getTableType(table_name);
0589: }
0590:
0591: /**
0592: * Attempts to resolve the given table name to its correct case assuming
0593: * the table name represents a case insensitive version of the name. For
0594: * example, "aPP.CuSTOMer" may resolve to "APP.Customer". If the table
0595: * name can not resolve to a valid identifier it returns the input table
0596: * name, therefore the actual presence of the table should always be
0597: * checked by calling 'tableExists' after this method returns.
0598: */
0599: public TableName tryResolveCase(TableName table_name) {
0600: table_name = substituteReservedTableName(table_name);
0601: table_name = getTransaction().tryResolveCase(table_name);
0602: return table_name;
0603: }
0604:
0605: /**
0606: * Resolves a TableName string (eg. 'Customer' 'APP.Customer' ) to a
0607: * TableName object. If the schema part of the table name is not present
0608: * then it is set to the current schema of the database connection. If the
0609: * database is ignoring the case then this will correctly resolve the table
0610: * to the cased version of the table name.
0611: */
0612: public TableName resolveTableName(String name) {
0613: TableName table_name = TableName.resolve(getCurrentSchema(),
0614: name);
0615: table_name = substituteReservedTableName(table_name);
0616: if (isInCaseInsensitiveMode()) {
0617: // Try and resolve the case of the table name,
0618: table_name = tryResolveCase(table_name);
0619: }
0620: return table_name;
0621: }
0622:
0623: /**
0624: * Resolves the given string to a table name, throwing an exception if
0625: * the reference is ambiguous. This also generates an exception if the
0626: * table object is not found.
0627: */
0628: public TableName resolveToTableName(String name) {
0629: TableName table_name = TableName.resolve(getCurrentSchema(),
0630: name);
0631: if (table_name.getName().equalsIgnoreCase("OLD")) {
0632: return Database.OLD_TRIGGER_TABLE;
0633: } else if (table_name.getName().equalsIgnoreCase("NEW")) {
0634: return Database.NEW_TRIGGER_TABLE;
0635: }
0636:
0637: return getTransaction().resolveToTableName(getCurrentSchema(),
0638: name, isInCaseInsensitiveMode());
0639:
0640: }
0641:
0642: /**
0643: * Returns the DataTableDef for the table with the given name.
0644: */
0645: public DataTableDef getDataTableDef(TableName name) {
0646: name = substituteReservedTableName(name);
0647: return getTransaction().getDataTableDef(name);
0648: }
0649:
0650: /**
0651: * Returns a DataTable that represents the table from the given schema,
0652: * name in the database.
0653: */
0654: public DataTable getTable(TableName name) {
0655: name = substituteReservedTableName(name);
0656:
0657: try {
0658: // Special handling of NEW and OLD table, we cache the DataTable in the
0659: // OldNewTableState object,
0660: if (name.equals(Database.OLD_TRIGGER_TABLE)) {
0661: if (current_old_new_state.OLD_data_table == null) {
0662: current_old_new_state.OLD_data_table = new DataTable(
0663: this , getTransaction().getTable(name));
0664: }
0665: return current_old_new_state.OLD_data_table;
0666: } else if (name.equals(Database.NEW_TRIGGER_TABLE)) {
0667: if (current_old_new_state.NEW_data_table == null) {
0668: current_old_new_state.NEW_data_table = new DataTable(
0669: this , getTransaction().getTable(name));
0670: }
0671: return current_old_new_state.NEW_data_table;
0672: }
0673:
0674: // Ask the transaction for the table
0675: MutableTableDataSource table = getTransaction().getTable(
0676: name);
0677:
0678: // Is this table in the tables_cache?
0679: DataTable dtable = (DataTable) tables_cache.get(table);
0680: // No, so wrap it around a Datatable and put it in the cache
0681: if (dtable == null) {
0682: dtable = new DataTable(this , table);
0683: tables_cache.put(table, dtable);
0684: }
0685: // Return the DataTable
0686: return dtable;
0687:
0688: } catch (DatabaseException e) {
0689: Debug().writeException(e);
0690: throw new Error("Database Exception: " + e.getMessage());
0691: }
0692:
0693: }
0694:
0695: /**
0696: * Returns a DataTable that represents the table with the given name in the
0697: * database from the current connection schema.
0698: */
0699: public DataTable getTable(String table_name) {
0700: return getTable(new TableName(current_schema, table_name));
0701: }
0702:
0703: /**
0704: * Create a new table within the context of the current connection
0705: * transaction.
0706: */
0707: public void createTable(DataTableDef table_def) {
0708: checkAllowCreate(table_def.getTableName());
0709: getTransaction().createTable(table_def);
0710: }
0711:
0712: /**
0713: * Create a new table with a starting initial sector size. This should
0714: * only be used as very fine grain optimization for creating tables. If
0715: * in the future the underlying table model is changed so that the given
0716: * 'sector_size' value is unapplicable, then the value will be ignored.
0717: */
0718: public void createTable(DataTableDef table_def,
0719: int data_sector_size, int index_sector_size) {
0720: checkAllowCreate(table_def.getTableName());
0721: getTransaction().createTable(table_def, data_sector_size,
0722: index_sector_size);
0723: }
0724:
0725: /**
0726: * Creates a new view. This takes the information in the ViewDef object and
0727: * adds it to the system view table.
0728: * <p>
0729: * Note that this is a transactional operation. You need to commit for the
0730: * view to be visible to other transactions.
0731: */
0732: public void createView(SQLQuery query, ViewDef view) {
0733: checkAllowCreate(view.getDataTableDef().getTableName());
0734:
0735: try {
0736: view_manager.defineView(view, query, getUser());
0737: } catch (DatabaseException e) {
0738: Debug().writeException(e);
0739: throw new RuntimeException("Database Exception: "
0740: + e.getMessage());
0741: }
0742:
0743: }
0744:
0745: /**
0746: * Drops the view with the given name and returns true if the drop succeeded.
0747: * Returns false if the view was not found.
0748: * <p>
0749: * Note that this is a transactional operation. You need to commit for the
0750: * change to be visible to other transactions.
0751: */
0752: public boolean dropView(TableName view_name) {
0753:
0754: try {
0755: return view_manager.deleteView(view_name);
0756: } catch (DatabaseException e) {
0757: Debug().writeException(e);
0758: throw new RuntimeException("Database Exception: "
0759: + e.getMessage());
0760: }
0761:
0762: }
0763:
0764: /**
0765: * Updates a given table within the context of the current connection
0766: * transaction.
0767: */
0768: public void updateTable(DataTableDef table_def) {
0769: checkAllowCreate(table_def.getTableName());
0770: getTransaction()
0771: .alterTable(table_def.getTableName(), table_def);
0772: }
0773:
0774: /**
0775: * Updates a given table within the context of the current connection
0776: * transaction. This should only be used as very fine grain optimization
0777: * for creating tables.If in the future the underlying table model is
0778: * changed so that the given 'sector_size' value is unapplicable, then the
0779: * value will be ignored.
0780: */
0781: public void updateTable(DataTableDef table_def,
0782: int data_sector_size, int index_sector_size) {
0783: checkAllowCreate(table_def.getTableName());
0784: getTransaction().alterTable(table_def.getTableName(),
0785: table_def, data_sector_size, index_sector_size);
0786: }
0787:
0788: /**
0789: * Given a DataTableDef, if the table exists then it is updated otherwise
0790: * if it doesn't exist then it is created.
0791: * <p>
0792: * This should only be used as very fine grain optimization for creating/
0793: * altering tables. If in the future the underlying table model is changed
0794: * so that the given 'sector_size' value is unapplicable, then the value
0795: * will be ignored.
0796: */
0797: public void alterCreateTable(DataTableDef table_def,
0798: int data_sector_size, int index_sector_size) {
0799: if (!tableExists(table_def.getTableName())) {
0800: createTable(table_def, data_sector_size, index_sector_size);
0801: } else {
0802: updateTable(table_def, data_sector_size, index_sector_size);
0803: }
0804: }
0805:
0806: /**
0807: * Given a DataTableDef, if the table exists then it is updated otherwise
0808: * if it doesn't exist then it is created.
0809: */
0810: public void alterCreateTable(DataTableDef table_def) {
0811: if (!tableExists(table_def.getTableName())) {
0812: createTable(table_def);
0813: } else {
0814: updateTable(table_def);
0815: }
0816: }
0817:
0818: /**
0819: * Notifies this transaction that a database object with the given name has
0820: * successfully been created.
0821: */
0822: void databaseObjectCreated(TableName table_name) {
0823: getTransaction().databaseObjectCreated(table_name);
0824: }
0825:
0826: /**
0827: * Notifies this transaction that a database object with the given name has
0828: * successfully been dropped.
0829: */
0830: void databaseObjectDropped(TableName table_name) {
0831: getTransaction().databaseObjectDropped(table_name);
0832: }
0833:
0834: /**
0835: * Checks all the rows in the table for immediate constraint violations
0836: * and when the transaction is next committed check for all deferred
0837: * constraint violations. This method is used when the constraints on a
0838: * table changes and we need to determine if any constraint violations
0839: * occurred. To the constraint checking system, this is like adding all
0840: * the rows to the given table.
0841: */
0842: public void checkAllConstraints(TableName table_name) {
0843: // Assert
0844: checkExclusive();
0845: getTransaction().checkAllConstraints(table_name);
0846: }
0847:
0848: /**
0849: * Drops a table from within the context of the current connection
0850: * transaction.
0851: */
0852: public void dropTable(String table_name) {
0853: dropTable(new TableName(current_schema, table_name));
0854: }
0855:
0856: /**
0857: * Drops a table from within the context of the current connection
0858: * transaction.
0859: */
0860: public void dropTable(TableName table_name) {
0861: getTransaction().dropTable(table_name);
0862: }
0863:
0864: /**
0865: * Compacts the table with the given name. Throws an exception if the
0866: * table doesn't exist.
0867: */
0868: public void compactTable(String table_name) {
0869: compactTable(new TableName(current_schema, table_name));
0870: }
0871:
0872: /**
0873: * Compacts the table with the given name. Throws an exception if the
0874: * table doesn't exist.
0875: */
0876: public void compactTable(TableName table_name) {
0877: getTransaction().compactTable(table_name);
0878: }
0879:
0880: /**
0881: * Adds the given table name to the list of tables that are selected from
0882: * within the transaction in this connection.
0883: */
0884: public void addSelectedFromTable(String table_name) {
0885: addSelectedFromTable(new TableName(current_schema, table_name));
0886: }
0887:
0888: /**
0889: * Adds the given table name to the list of tables that are selected from
0890: * within the transaction in this connection.
0891: */
0892: public void addSelectedFromTable(TableName name) {
0893: getTransaction().addSelectedFromTable(name);
0894: }
0895:
0896: /**
0897: * Requests of the sequence generator the next value from the sequence.
0898: * <p>
0899: * NOTE: This does NOT check that the user owning this connection has the
0900: * correct privs to perform this operation.
0901: */
0902: public long nextSequenceValue(String name) {
0903: // Resolve and ambiguity test
0904: TableName seq_name = resolveToTableName(name);
0905: return getTransaction().nextSequenceValue(seq_name);
0906: }
0907:
0908: /**
0909: * Returns the current sequence value for the given sequence generator that
0910: * was last returned by a call to 'nextSequenceValue'. If a value was not
0911: * last returned by a call to 'nextSequenceValue' then a statement exception
0912: * is generated.
0913: * <p>
0914: * NOTE: This does NOT check that the user owning this connection has the
0915: * correct privs to perform this operation.
0916: */
0917: public long lastSequenceValue(String name) {
0918: // Resolve and ambiguity test
0919: TableName seq_name = resolveToTableName(name);
0920: return getTransaction().lastSequenceValue(seq_name);
0921: }
0922:
0923: /**
0924: * Sets the sequence value for the given sequence generator. If the generator
0925: * does not exist or it is not possible to set the value for the generator
0926: * then an exception is generated.
0927: * <p>
0928: * NOTE: This does NOT check that the user owning this connection has the
0929: * correct privs to perform this operation.
0930: */
0931: public void setSequenceValue(String name, long value) {
0932: // Resolve and ambiguity test
0933: TableName seq_name = resolveToTableName(name);
0934: getTransaction().setSequenceValue(seq_name, value);
0935: }
0936:
0937: /**
0938: * Returns the next unique identifier for the given table from the schema.
0939: */
0940: public long nextUniqueID(TableName name) {
0941: return getTransaction().nextUniqueID(name);
0942: }
0943:
0944: /**
0945: * Returns the next unique identifier for the given table in the connection
0946: * schema.
0947: */
0948: public long nextUniqueID(String table_name) {
0949: TableName tname = TableName.resolve(current_schema, table_name);
0950: return nextUniqueID(tname);
0951: }
0952:
0953: /**
0954: * If the given table name is a reserved name, then we must substitute it
0955: * with its correct form. For example, 'APP.NEW' becomes 'SYS_INFO.NEW',
0956: * etc.
0957: */
0958: static TableName substituteReservedTableName(TableName table_name) {
0959: // We do not allow tables to be created with a reserved name
0960: String name = table_name.getName();
0961: if (name.equalsIgnoreCase("OLD")) {
0962: return Database.OLD_TRIGGER_TABLE;
0963: }
0964: if (name.equalsIgnoreCase("NEW")) {
0965: return Database.NEW_TRIGGER_TABLE;
0966: }
0967: return table_name;
0968: }
0969:
0970: /**
0971: * Generates an exception if the name of the table is reserved and the
0972: * creation of the table should be prevented. For example, the table
0973: * names 'OLD' and 'NEW' are reserved.
0974: */
0975: static void checkAllowCreate(TableName table_name) {
0976: // We do not allow tables to be created with a reserved name
0977: String name = table_name.getName();
0978: if (name.equalsIgnoreCase("OLD")
0979: || name.equalsIgnoreCase("NEW")) {
0980: throw new StatementException("Table name '" + table_name
0981: + "' is reserved.");
0982: }
0983: }
0984:
0985: /**
0986: * Creates a new sequence generator with the given TableName and
0987: * initializes it with the given details. This does NOT check if the
0988: * given name clashes with an existing database object.
0989: */
0990: public void createSequenceGenerator(TableName name,
0991: long start_value, long increment_by, long min_value,
0992: long max_value, long cache, boolean cycle) {
0993:
0994: // Check the name of the database object isn't reserved (OLD/NEW)
0995: checkAllowCreate(name);
0996:
0997: getTransaction().createSequenceGenerator(name, start_value,
0998: increment_by, min_value, max_value, cache, cycle);
0999: }
1000:
1001: /**
1002: * Drops an existing sequence generator with the given name.
1003: */
1004: public void dropSequenceGenerator(TableName name) {
1005: getTransaction().dropSequenceGenerator(name);
1006: }
1007:
1008: /**
1009: * Adds a type of trigger for the given trigger source (usually the
1010: * name of the table).
1011: * <p>
1012: * Adds a type of trigger to the given Table. When the event is fired, the
1013: * UserCallBack method is notified of the event.
1014: */
1015: public void createTrigger(String trigger_name,
1016: String trigger_source, int type) {
1017: database.getTriggerManager().addTriggerListener(this ,
1018: trigger_name, type, trigger_source, this );
1019: }
1020:
1021: /**
1022: * Removes a type of trigger for the given trigger source (usually the
1023: * name of the table).
1024: */
1025: public void deleteTrigger(String trigger_name) {
1026: database.getTriggerManager().removeTriggerListener(this ,
1027: trigger_name);
1028: }
1029:
1030: /**
1031: * Informs the underlying transaction that a high level transaction event
1032: * has occurred and should be dispatched to any listeners occordingly.
1033: */
1034: public void notifyTriggerEvent(TriggerEvent evt) {
1035: trigger_event_list.add(evt);
1036: }
1037:
1038: /**
1039: * Allocates a new large object in the Blob store of this conglomerate of the
1040: * given type and size. The blob data must be written through the
1041: * Ref after the large object is created. Once the data has been written
1042: * the 'complete' method in Ref is called.
1043: * <p>
1044: * Once a large object is created and written to, it may be allocated in one
1045: * or more tables in the conglomerate.
1046: */
1047: public Ref createNewLargeObject(byte type, long object_size) {
1048: // Enable compression for string types (but not binary types).
1049: if (type == 3 || type == 4) {
1050: type = (byte) (type | 0x010);
1051: }
1052: return conglomerate.createNewLargeObject(type, object_size);
1053: }
1054:
1055: /**
1056: * Tells the conglomerate to flush the blob store. This should be called
1057: * after one or more blobs have been created and the data for the blob(s) are
1058: * set. It is an important step to perform AFTER blobs have been written.
1059: * <p>
1060: * If this is not called and the database closes (or crashes) before a flush
1061: * occurs then the blob may not be recoverable.
1062: */
1063: public void flushBlobStore() {
1064: conglomerate.flushBlobStore();
1065: }
1066:
1067: /**
1068: * Returns a TableQueryDef object that describes the characteristics of a
1069: * table including the name (TableName), the columns (DataTableDef) and the
1070: * query plan to produce the table (QueryPlanNode). This object can be used
1071: * to resolve information about a particular table, and to evaluate the
1072: * query plan to produce the table itself.
1073: * <p>
1074: * This produces TableQueryDef objects for all table objects in the database
1075: * including data tables and views.
1076: * <p>
1077: * The 'aliased_as' parameter is used to overwrite the default name of the
1078: * table object.
1079: */
1080: public TableQueryDef getTableQueryDef(final TableName table_name,
1081: final TableName aliased_as) {
1082:
1083: // Produce the data table def for this database object.
1084: DataTableDef dtf = getDataTableDef(table_name);
1085: // If the table is aliased, set a new DataTableDef with the given name
1086: if (aliased_as != null) {
1087: dtf = new DataTableDef(dtf);
1088: dtf.setTableName(aliased_as);
1089: dtf.setImmutable();
1090: }
1091: final DataTableDef data_table_def = dtf;
1092: // final String aliased_name =
1093: // aliased_as == null ? null : aliased_as.getName();
1094:
1095: return new TableQueryDef() {
1096: public DataTableDef getDataTableDef() {
1097: return data_table_def;
1098: }
1099:
1100: public QueryPlanNode getQueryPlanNode() {
1101: return createObjectFetchQueryPlan(table_name,
1102: aliased_as);
1103: }
1104: };
1105:
1106: }
1107:
1108: /**
1109: * Creates a QueryPlanNode to fetch the given table object from this
1110: * connection.
1111: */
1112: public QueryPlanNode createObjectFetchQueryPlan(
1113: TableName table_name, TableName aliased_name) {
1114: String table_type = getTableType(table_name);
1115: if (table_type.equals("VIEW")) {
1116: return new QueryPlan.FetchViewNode(table_name, aliased_name);
1117: } else {
1118: return new QueryPlan.FetchTableNode(table_name,
1119: aliased_name);
1120: }
1121: }
1122:
1123: // ---------- Schema management and constraint methods ----------
1124: // Methods that handle getting/setting schema information such as;
1125: // * Creating/dropping/querying schema
1126: // * Creating/dropping/querying constraint information including;
1127: // check constraints, unique constraints, primary key constraints,
1128: // foreign key constraints, etc.
1129:
1130: /**
1131: * Changes the default schema to the given schema.
1132: */
1133: public void setDefaultSchema(String schema_name) {
1134: boolean ignore_case = isInCaseInsensitiveMode();
1135: SchemaDef schema = resolveSchemaCase(schema_name, ignore_case);
1136: if (schema == null) {
1137: throw new Error("Schema '" + schema_name
1138: + "' does not exist.");
1139: } else {
1140: // Set the default schema for this connection
1141: setCurrentSchema(schema.getName());
1142: }
1143: }
1144:
1145: // NOTE: These methods are copied because they simply call through to the
1146: // Transaction implementation of the method with the same signature.
1147:
1148: private void checkExclusive() {
1149: if (!getLockingMechanism().isInExclusiveMode()) {
1150: throw new Error(
1151: "Assertion failed: Expected to be in exclusive mode.");
1152: }
1153: }
1154:
1155: /**
1156: * Same as the Transaction.createSchema method.
1157: */
1158: public void createSchema(String name, String type) {
1159: // Assert
1160: checkExclusive();
1161: getTransaction().createSchema(name, type);
1162: }
1163:
1164: /**
1165: * Same as the Transaction.dropSchema method.
1166: */
1167: public void dropSchema(String name) {
1168: // Assert
1169: checkExclusive();
1170: getTransaction().dropSchema(name);
1171: }
1172:
1173: /**
1174: * Same as the Transaction.schemaExists method.
1175: */
1176: public boolean schemaExists(String name) {
1177: return getTransaction().schemaExists(name);
1178: }
1179:
1180: /**
1181: * Same as the Transaction.resolveSchemaCase method.
1182: */
1183: public SchemaDef resolveSchemaCase(String name, boolean ignore_case) {
1184: return getTransaction().resolveSchemaCase(name, ignore_case);
1185: }
1186:
1187: /**
1188: * Convenience - returns the SchemaDef object given the name of the schema.
1189: * If identifiers are case insensitive, we resolve the case of the schema
1190: * name also.
1191: */
1192: public SchemaDef resolveSchemaName(String name) {
1193: boolean ignore_case = isInCaseInsensitiveMode();
1194: return resolveSchemaCase(name, ignore_case);
1195: }
1196:
1197: /**
1198: * Same as the Transaction.getSchemaList method.
1199: */
1200: public SchemaDef[] getSchemaList() {
1201: return getTransaction().getSchemaList();
1202: }
1203:
1204: /**
1205: * Same as the Transaction.setPersistentVar method.
1206: */
1207: public void setPersistentVar(String variable, String value) {
1208: // Assert
1209: checkExclusive();
1210: getTransaction().setPersistentVar(variable, value);
1211: }
1212:
1213: /**
1214: * Same as the Transaction.getPersistentVar method.
1215: */
1216: public String getPersistentVar(String variable) {
1217: return getTransaction().getPersistantVar(variable);
1218: }
1219:
1220: /**
1221: * Same as the Transaction.addUniqueConstraint method.
1222: */
1223: public void addUniqueConstraint(TableName table_name,
1224: String[] cols, short deferred, String constraint_name) {
1225: // Assert
1226: checkExclusive();
1227: getTransaction().addUniqueConstraint(table_name, cols,
1228: deferred, constraint_name);
1229: }
1230:
1231: /**
1232: * Same as the Transaction.addForeignKeyConstraint method.
1233: */
1234: public void addForeignKeyConstraint(TableName table, String[] cols,
1235: TableName ref_table, String[] ref_cols, String delete_rule,
1236: String update_rule, short deferred, String constraint_name) {
1237: // Assert
1238: checkExclusive();
1239: getTransaction().addForeignKeyConstraint(table, cols,
1240: ref_table, ref_cols, delete_rule, update_rule,
1241: deferred, constraint_name);
1242: }
1243:
1244: /**
1245: * Same as the Transaction.addPrimaryKeyConstraint method.
1246: */
1247: public void addPrimaryKeyConstraint(TableName table_name,
1248: String[] cols, short deferred, String constraint_name) {
1249: // Assert
1250: checkExclusive();
1251: getTransaction().addPrimaryKeyConstraint(table_name, cols,
1252: deferred, constraint_name);
1253: }
1254:
1255: /**
1256: * Same as the Transaction.addCheckConstraint method.
1257: */
1258: public void addCheckConstraint(TableName table_name,
1259: Expression expression, short deferred,
1260: String constraint_name) {
1261: // Assert
1262: checkExclusive();
1263: getTransaction().addCheckConstraint(table_name, expression,
1264: deferred, constraint_name);
1265: }
1266:
1267: /**
1268: * Same as the Transaction.dropAllConstraintsForTable method.
1269: */
1270: public void dropAllConstraintsForTable(TableName table_name) {
1271: // Assert
1272: checkExclusive();
1273: getTransaction().dropAllConstraintsForTable(table_name);
1274: }
1275:
1276: /**
1277: * Same as the Transaction.dropNamedConstraint method.
1278: */
1279: public int dropNamedConstraint(TableName table_name,
1280: String constraint_name) {
1281: // Assert
1282: checkExclusive();
1283: return getTransaction().dropNamedConstraint(table_name,
1284: constraint_name);
1285: }
1286:
1287: /**
1288: * Same as the Transaction.dropPrimaryKeyConstraintForTable method.
1289: */
1290: public boolean dropPrimaryKeyConstraintForTable(
1291: TableName table_name, String constraint_name) {
1292: // Assert
1293: checkExclusive();
1294: return getTransaction().dropPrimaryKeyConstraintForTable(
1295: table_name, constraint_name);
1296: }
1297:
1298: /**
1299: * Same as the Transaction.queryTablesRelationallyLinkedTo method.
1300: */
1301: public TableName[] queryTablesRelationallyLinkedTo(TableName table) {
1302: return Transaction.queryTablesRelationallyLinkedTo(
1303: getTransaction(), table);
1304: }
1305:
1306: /**
1307: * Same as the Transaction.queryTableUniqueGroups method.
1308: */
1309: public Transaction.ColumnGroup[] queryTableUniqueGroups(
1310: TableName table_name) {
1311: return Transaction.queryTableUniqueGroups(getTransaction(),
1312: table_name);
1313: }
1314:
1315: /**
1316: * Same as the Transaction.queryTablePrimaryKeyGroup method.
1317: */
1318: public Transaction.ColumnGroup queryTablePrimaryKeyGroup(
1319: TableName table_name) {
1320: return Transaction.queryTablePrimaryKeyGroup(getTransaction(),
1321: table_name);
1322: }
1323:
1324: /**
1325: * Same as the Transaction.queryTableCheckExpression method.
1326: */
1327: public Transaction.CheckExpression[] queryTableCheckExpressions(
1328: TableName table_name) {
1329: return Transaction.queryTableCheckExpressions(getTransaction(),
1330: table_name);
1331: }
1332:
1333: /**
1334: * Same as the Transaction.queryTableForeignKeyReferences method.
1335: */
1336: public Transaction.ColumnGroupReference[] queryTableForeignKeyReferences(
1337: TableName table_name) {
1338: return Transaction.queryTableForeignKeyReferences(
1339: getTransaction(), table_name);
1340: }
1341:
1342: /**
1343: * Same as the Transaction.queryTableImportedForeignKeyReferences method.
1344: */
1345: public Transaction.ColumnGroupReference[] queryTableImportedForeignKeyReferences(
1346: TableName table_name) {
1347: return Transaction.queryTableImportedForeignKeyReferences(
1348: getTransaction(), table_name);
1349: }
1350:
1351: // ---------- Triggered OLD/NEW table handling ----------
1352: // These methods are used by the ConnectionTriggerManager object to
1353: // temporarily create OLD and NEW tables in this connection from inside a
1354: // triggered action. In some cases (before the operation) the OLD table
1355: // is mutable.
1356:
1357: /**
1358: * Returns the current state of the old/new tables.
1359: */
1360: OldNewTableState getOldNewTableState() {
1361: return current_old_new_state;
1362: }
1363:
1364: /**
1365: * Sets the current state of the old/new tables. When nesting OLD/NEW
1366: * tables for nested stored procedures, the current state should be first
1367: * recorded and reverted back when the nested procedure finishes.
1368: */
1369: void setOldNewTableState(OldNewTableState state) {
1370: current_old_new_state = state;
1371: }
1372:
1373: // ---------- Trigger methods ----------
1374:
1375: /**
1376: * Notifies this connection that an insert/delete or update operation has
1377: * occurred on some table of this DatabaseConnection. This should notify
1378: * the trigger connection manager of this event so that it may perform any
1379: * action that may have been set up to occur on this event.
1380: */
1381: void fireTableEvent(TableModificationEvent evt) {
1382: connection_trigger_manager.performTriggerAction(evt);
1383: }
1384:
1385: // ---------- Implemented from TriggerListener ----------
1386:
1387: /**
1388: * Notifies when a trigger has fired for this user. If there are no open
1389: * transactions on this connection then we do a straight call back trigger
1390: * notify. If there is a transaction open then trigger events are added
1391: * to the 'trigger_event_buffer' which fires when the connection transaction
1392: * is committed or rolled back.
1393: */
1394: public void fireTrigger(DatabaseConnection database,
1395: String trigger_name, TriggerEvent evt) {
1396:
1397: if (this != database) {
1398: throw new Error("User object mismatch.");
1399: }
1400:
1401: try {
1402: // Did we pass in a call back interface?
1403: if (call_back != null) {
1404: synchronized (trigger_event_buffer) {
1405: // If there is no active transaction then fire trigger immediately.
1406: if (transaction == null) {
1407: call_back.triggerNotify(trigger_name, evt
1408: .getType(), evt.getSource(), evt
1409: .getCount());
1410: }
1411: // Otherwise add to buffer
1412: else {
1413: trigger_event_buffer.add(trigger_name);
1414: trigger_event_buffer.add(evt);
1415: }
1416: }
1417: }
1418: } catch (Throwable e) {
1419: Debug().write(Lvl.ERROR, this ,
1420: "TRIGGER Exception: " + e.getMessage());
1421: }
1422: }
1423:
1424: /**
1425: * Fires any triggers that are pending in the trigger buffer.
1426: */
1427: private void firePendingTriggerEvents() {
1428: int sz;
1429: synchronized (trigger_event_buffer) {
1430: sz = trigger_event_buffer.size();
1431: }
1432: if (sz > 0) {
1433: // Post an event that fires the triggers for each listener.
1434: Runnable runner = new Runnable() {
1435: public void run() {
1436: synchronized (trigger_event_buffer) {
1437: // Fire all pending trigger events in buffer
1438: for (int i = 0; i < trigger_event_buffer.size(); i += 2) {
1439: String trigger_name = (String) trigger_event_buffer
1440: .get(i);
1441: TriggerEvent evt = (TriggerEvent) trigger_event_buffer
1442: .get(i + 1);
1443: call_back.triggerNotify(trigger_name, evt
1444: .getType(), evt.getSource(), evt
1445: .getCount());
1446: }
1447: // Clear the buffer
1448: trigger_event_buffer.clear();
1449: }
1450: }
1451: };
1452:
1453: // Post the event to go off approx 3ms from now.
1454: database.postEvent(3, database.createEvent(runner));
1455: }
1456:
1457: }
1458:
1459: /**
1460: * Private method that disposes the current transaction.
1461: */
1462: private void disposeTransaction() {
1463: // Set the transaction to null
1464: transaction = null;
1465: // Fire any pending trigger events in the trigger buffer.
1466: firePendingTriggerEvents();
1467: // Clear the trigger events in this object
1468: trigger_event_list.clear();
1469:
1470: // Notify any table backed caches that this transaction has finished.
1471: int sz = table_backed_cache_list.size();
1472: for (int i = 0; i < sz; ++i) {
1473: TableBackedCache cache = (TableBackedCache) table_backed_cache_list
1474: .get(i);
1475: cache.transactionFinished();
1476: }
1477: }
1478:
1479: /**
1480: * Tries to commit the current transaction. If the transaction can not be
1481: * committed because there were concurrent changes that interfered with
1482: * each other then a TransactionError is thrown and the transaction is
1483: * rolled back.
1484: * <p>
1485: * NOTE: It's guarenteed that the transaction will be closed even if a
1486: * transaction exception occurs.
1487: * <p>
1488: * Synchronization is implied on this method, because the locking mechanism
1489: * should be exclusive when this is called.
1490: */
1491: public void commit() throws TransactionException {
1492: // Are we currently allowed to commit/rollback?
1493: if (close_transaction_disabled) {
1494: throw new RuntimeException("Commit is not allowed.");
1495: }
1496:
1497: if (user != null) {
1498: user.refreshLastCommandTime();
1499: }
1500:
1501: // NOTE, always connection exclusive op.
1502: getLockingMechanism().reset();
1503: tables_cache.clear();
1504:
1505: if (transaction != null) {
1506: try {
1507:
1508: // Close and commit the transaction
1509: transaction.closeAndCommit();
1510:
1511: // Fire all SQL action level triggers that were generated on actions.
1512: database.getTriggerManager().flushTriggerEvents(
1513: trigger_event_list);
1514:
1515: } finally {
1516: // Dispose the current transaction
1517: disposeTransaction();
1518: }
1519: }
1520: }
1521:
1522: /**
1523: * Rolls back the current transaction operating within this connection.
1524: * <p>
1525: * NOTE: It's guarenteed that the transaction will be closed even if an
1526: * exception occurs.
1527: * <p>
1528: * Synchronization is implied on this method, because the locking mechanism
1529: * should be exclusive when this is called.
1530: */
1531: public void rollback() {
1532: // Are we currently allowed to commit/rollback?
1533: if (close_transaction_disabled) {
1534: throw new RuntimeException("Rollback is not allowed.");
1535: }
1536:
1537: if (user != null) {
1538: user.refreshLastCommandTime();
1539: }
1540:
1541: // NOTE, always connection exclusive op.
1542: tables_cache.clear();
1543:
1544: if (transaction != null) {
1545: getLockingMechanism().reset();
1546: try {
1547: transaction.closeAndRollback();
1548: } finally {
1549: // Dispose the current transaction
1550: disposeTransaction();
1551: // Dispose the jdbc connection
1552: if (jdbc_connection != null) {
1553: try {
1554: InternalJDBCHelper
1555: .disposeJDBCConnection(jdbc_connection);
1556: } catch (Throwable e) {
1557: Debug()
1558: .write(Lvl.ERROR, this ,
1559: "Error disposing internal JDBC connection.");
1560: Debug().writeException(Lvl.ERROR, e);
1561: // We don't wrap this exception
1562: }
1563: jdbc_connection = null;
1564: }
1565: }
1566: }
1567: }
1568:
1569: /**
1570: * Closes this database connection.
1571: */
1572: public void close() {
1573: try {
1574: rollback();
1575: } catch (Throwable e) {
1576: e.printStackTrace(System.err);
1577: } finally {
1578: if (table_backed_cache_list != null) {
1579: try {
1580: int sz = table_backed_cache_list.size();
1581: for (int i = 0; i < sz; ++i) {
1582: TableBackedCache cache = (TableBackedCache) table_backed_cache_list
1583: .get(i);
1584: cache.detatchFrom(conglomerate);
1585: }
1586: table_backed_cache_list = null;
1587: } catch (Throwable e) {
1588: e.printStackTrace(System.err);
1589: }
1590: }
1591: // Remove any trigger listeners set for this connection,
1592: database.getTriggerManager()
1593: .clearAllDatabaseConnectionTriggers(this );
1594: }
1595: }
1596:
1597: public void finalize() throws Throwable {
1598: super .finalize();
1599: close();
1600: }
1601:
1602: // ---------- Inner classes ----------
1603:
1604: /**
1605: * An implementation of ProcedureConnection generated from this object.
1606: */
1607: private class DCProcedureConnection implements ProcedureConnection {
1608:
1609: /**
1610: * The User of this connection before this procedure was started.
1611: */
1612: private User previous_user;
1613:
1614: /**
1615: * The 'close_transaction_disabled' flag when this connection was created.
1616: */
1617: private boolean transaction_disabled_flag;
1618:
1619: /**
1620: * The JDBCConnection created by this object.
1621: */
1622: private java.sql.Connection jdbc_connection;
1623:
1624: public java.sql.Connection getJDBCConnection() {
1625: if (jdbc_connection == null) {
1626: jdbc_connection = InternalJDBCHelper
1627: .createJDBCConnection(getUser(),
1628: DatabaseConnection.this );
1629: }
1630: return jdbc_connection;
1631: }
1632:
1633: public Database getDatabase() {
1634: return DatabaseConnection.this .getDatabase();
1635: }
1636:
1637: void dispose() {
1638: previous_user = null;
1639: if (jdbc_connection != null) {
1640: try {
1641: InternalJDBCHelper
1642: .disposeJDBCConnection(jdbc_connection);
1643: } catch (Throwable e) {
1644: Debug()
1645: .write(Lvl.ERROR, this ,
1646: "Error disposing internal JDBC connection.");
1647: Debug().writeException(Lvl.ERROR, e);
1648: // We don't wrap this exception
1649: }
1650: }
1651: }
1652:
1653: }
1654:
1655: /**
1656: * An internal table info object that handles OLD and NEW tables for
1657: * triggered actions.
1658: */
1659: private class OldAndNewInternalTableInfo implements
1660: InternalTableInfo {
1661:
1662: private boolean hasOLDTable() {
1663: return current_old_new_state.OLD_row_index != -1;
1664: }
1665:
1666: private boolean hasNEWTable() {
1667: return current_old_new_state.NEW_row_data != null;
1668: }
1669:
1670: public int getTableCount() {
1671: int count = 0;
1672: if (hasOLDTable()) {
1673: ++count;
1674: }
1675: if (hasNEWTable()) {
1676: ++count;
1677: }
1678: return count;
1679: }
1680:
1681: public int findTableName(TableName name) {
1682: if (hasOLDTable()
1683: && name.equals(Database.OLD_TRIGGER_TABLE)) {
1684: return 0;
1685: }
1686: if (hasNEWTable()
1687: && name.equals(Database.NEW_TRIGGER_TABLE)) {
1688: if (hasOLDTable()) {
1689: return 1;
1690: } else {
1691: return 0;
1692: }
1693: }
1694: return -1;
1695: }
1696:
1697: public TableName getTableName(int i) {
1698: if (hasOLDTable()) {
1699: if (i == 0) {
1700: return Database.OLD_TRIGGER_TABLE;
1701: }
1702: }
1703: return Database.NEW_TRIGGER_TABLE;
1704: }
1705:
1706: public boolean containsTableName(TableName name) {
1707: return findTableName(name) != -1;
1708: }
1709:
1710: public String getTableType(int i) {
1711: return "SYSTEM TABLE";
1712: }
1713:
1714: public DataTableDef getDataTableDef(int i) {
1715: DataTableDef table_def = DatabaseConnection.this
1716: .getDataTableDef(current_old_new_state.trigger_source);
1717: DataTableDef new_table_def = new DataTableDef(table_def);
1718: new_table_def.setTableName(getTableName(i));
1719: return new_table_def;
1720: }
1721:
1722: public MutableTableDataSource createInternalTable(int index) {
1723: DataTableDef t_def = getDataTableDef(index);
1724:
1725: TriggeredOldNewDataSource table = new TriggeredOldNewDataSource(
1726: getSystem(), t_def);
1727:
1728: if (hasOLDTable()) {
1729: if (index == 0) {
1730:
1731: // Copy data from the table to the new table
1732: DataTable dtable = DatabaseConnection.this
1733: .getTable(current_old_new_state.trigger_source);
1734: RowData old_row_data = new RowData(table);
1735: int row_index = current_old_new_state.OLD_row_index;
1736: for (int i = 0; i < t_def.columnCount(); ++i) {
1737: old_row_data.setColumnDataFromTObject(i, dtable
1738: .getCellContents(i, row_index));
1739: }
1740: // All OLD tables are immutable
1741: table.setImmutable(true);
1742: table.setRowData(old_row_data);
1743:
1744: return table;
1745: }
1746: }
1747:
1748: table.setImmutable(!current_old_new_state.mutable_NEW);
1749: table.setRowData(current_old_new_state.NEW_row_data);
1750:
1751: return table;
1752: }
1753:
1754: }
1755:
1756: /**
1757: * A MutableTableDataSource implementation that is used for trigger actions
1758: * to represent the data in the OLD and NEW tables.
1759: */
1760: private static class TriggeredOldNewDataSource extends GTDataSource {
1761:
1762: private DataTableDef table_def;
1763:
1764: private RowData content;
1765:
1766: private boolean immutable;
1767:
1768: /**
1769: * Constructor.
1770: */
1771: public TriggeredOldNewDataSource(TransactionSystem system,
1772: DataTableDef table_def) {
1773: super (system);
1774: this .table_def = table_def;
1775: }
1776:
1777: void setImmutable(boolean im) {
1778: this .immutable = im;
1779: }
1780:
1781: void setRowData(RowData row_data) {
1782: this .content = row_data;
1783: }
1784:
1785: public DataTableDef getDataTableDef() {
1786: return table_def;
1787: }
1788:
1789: public int getRowCount() {
1790: return 1;
1791: }
1792:
1793: public TObject getCellContents(final int column, final int row) {
1794: if (row < 0 || row > 0) {
1795: throw new RuntimeException("Row index out of bounds.");
1796: }
1797: return content.getCellData(column);
1798: }
1799:
1800: public int addRow(RowData row_data) {
1801: throw new RuntimeException("Inserting into table '"
1802: + getDataTableDef().getTableName()
1803: + "' is not permitted.");
1804: }
1805:
1806: public void removeRow(int row_index) {
1807: throw new RuntimeException("Deleting from table '"
1808: + getDataTableDef().getTableName()
1809: + "' is not permitted.");
1810: }
1811:
1812: public int updateRow(int row_index, RowData row_data) {
1813: if (immutable) {
1814: throw new RuntimeException("Updating table '"
1815: + getDataTableDef().getTableName()
1816: + "' is not permitted.");
1817: }
1818: if (row_index < 0 || row_index > 0) {
1819: throw new RuntimeException("Row index out of bounds.");
1820: }
1821:
1822: int sz = getDataTableDef().columnCount();
1823: for (int i = 0; i < sz; ++i) {
1824: content.setColumnDataFromTObject(i, row_data
1825: .getCellData(i));
1826: }
1827:
1828: return 0;
1829: }
1830:
1831: public MasterTableJournal getJournal() {
1832: // Shouldn't be used...
1833: throw new RuntimeException("Invalid method used.");
1834: }
1835:
1836: public void flushIndexChanges() {
1837: // Shouldn't be used...
1838: throw new RuntimeException("Invalid method used.");
1839: }
1840:
1841: public void constraintIntegrityCheck() {
1842: // Should always pass (not integrity check needed for OLD/NEW table.
1843: }
1844:
1845: }
1846:
1847: /**
1848: * A list of DataTableDef system table definitions for tables internal to
1849: * the database connection.
1850: */
1851: private final static DataTableDef[] INTERNAL_DEF_LIST;
1852:
1853: static {
1854: INTERNAL_DEF_LIST = new DataTableDef[5];
1855: INTERNAL_DEF_LIST[0] = GTStatisticsDataSource.DEF_DATA_TABLE_DEF;
1856: INTERNAL_DEF_LIST[1] = GTConnectionInfoDataSource.DEF_DATA_TABLE_DEF;
1857: INTERNAL_DEF_LIST[2] = GTCurrentConnectionsDataSource.DEF_DATA_TABLE_DEF;
1858: INTERNAL_DEF_LIST[3] = GTSQLTypeInfoDataSource.DEF_DATA_TABLE_DEF;
1859: INTERNAL_DEF_LIST[4] = GTPrivMapDataSource.DEF_DATA_TABLE_DEF;
1860: }
1861:
1862: /**
1863: * An internal table info object that handles tables internal to a
1864: * DatabaseConnection object.
1865: */
1866: private class ConnectionInternalTableInfo extends
1867: AbstractInternalTableInfo {
1868:
1869: /**
1870: * Constructor.
1871: */
1872: public ConnectionInternalTableInfo() {
1873: super ("SYSTEM TABLE", INTERNAL_DEF_LIST);
1874: }
1875:
1876: // ---------- Implemented ----------
1877:
1878: public MutableTableDataSource createInternalTable(int index) {
1879: if (index == 0) {
1880: return new GTStatisticsDataSource(
1881: DatabaseConnection.this ).init();
1882: } else if (index == 1) {
1883: return new GTConnectionInfoDataSource(
1884: DatabaseConnection.this ).init();
1885: } else if (index == 2) {
1886: return new GTCurrentConnectionsDataSource(
1887: DatabaseConnection.this ).init();
1888: } else if (index == 3) {
1889: return new GTSQLTypeInfoDataSource(
1890: DatabaseConnection.this ).init();
1891: } else if (index == 4) {
1892: return new GTPrivMapDataSource(DatabaseConnection.this );
1893: } else {
1894: throw new RuntimeException();
1895: }
1896: }
1897:
1898: }
1899:
1900: /**
1901: * Call back interface for events that occur within the connection instance.
1902: */
1903: public static interface CallBack {
1904:
1905: /**
1906: * Notifies the callee that a trigger event was fired that this user
1907: * is listening for.
1908: */
1909: void triggerNotify(String trigger_name, int trigger_event,
1910: String trigger_source, int fire_count);
1911:
1912: }
1913:
1914: /**
1915: * An object that stores state about the trigger table OLD and NEW when
1916: * the connection is set up to execute a stored procedure.
1917: */
1918: static class OldNewTableState {
1919:
1920: /**
1921: * The name of the table that is the trigger source.
1922: */
1923: TableName trigger_source;
1924:
1925: /**
1926: * The row index of the OLD data that is being updated or deleted in the
1927: * trigger source table.
1928: */
1929: int OLD_row_index = -1;
1930:
1931: /**
1932: * The RowData of the new data that is being inserted/updated in the trigger
1933: * source table.
1934: */
1935: RowData NEW_row_data;
1936:
1937: /**
1938: * If true then the 'new_data' information is mutable which would be true for
1939: * a BEFORE trigger. For example, we would want to change the data in the
1940: * row that caused the trigger to fire.
1941: */
1942: boolean mutable_NEW;
1943:
1944: /**
1945: * The DataTable object that represents the OLD table, if set.
1946: */
1947: DataTable OLD_data_table;
1948:
1949: /**
1950: * The DataTable object that represents the NEW table, if set.
1951: */
1952: DataTable NEW_data_table;
1953:
1954: /**
1955: * Constructor.
1956: */
1957: OldNewTableState(TableName table_source, int old_d,
1958: RowData new_d, boolean is_mutable) {
1959: this .trigger_source = table_source;
1960: this .OLD_row_index = old_d;
1961: this .NEW_row_data = new_d;
1962: this .mutable_NEW = is_mutable;
1963: }
1964:
1965: /**
1966: * Default constructor.
1967: */
1968: OldNewTableState() {
1969: }
1970:
1971: }
1972:
1973: }
|