From 8809341248c62b15b78d7e6d8e06ab2ec3793c8e Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" Date: Wed, 28 Mar 2007 22:00:24 +0000 Subject: Merged 1.2-dev to trunk. --- .../com/amazon/carbonado/repo/jdbc/JDBCBlob.java | 4 +- .../com/amazon/carbonado/repo/jdbc/JDBCClob.java | 4 +- .../com/amazon/carbonado/repo/jdbc/JDBCCursor.java | 53 +- .../amazon/carbonado/repo/jdbc/JDBCRepository.java | 325 ++++++------ .../carbonado/repo/jdbc/JDBCRepositoryBuilder.java | 89 +++- .../repo/jdbc/JDBCSequenceValueProducer.java | 7 +- .../carbonado/repo/jdbc/JDBCStorableGenerator.java | 560 +++++++++++++-------- .../carbonado/repo/jdbc/JDBCStorableInfo.java | 6 + .../repo/jdbc/JDBCStorableIntrospector.java | 67 ++- .../carbonado/repo/jdbc/JDBCStorableProperty.java | 6 + .../amazon/carbonado/repo/jdbc/JDBCStorage.java | 91 +++- .../carbonado/repo/jdbc/JDBCSupportStrategy.java | 107 ++-- .../repo/jdbc/LoggingCallableStatement.java | 2 - .../carbonado/repo/jdbc/LoggingConnection.java | 2 - .../carbonado/repo/jdbc/LoggingDataSource.java | 10 +- .../repo/jdbc/LoggingPreparedStatement.java | 2 - .../carbonado/repo/jdbc/LoggingStatement.java | 2 - .../carbonado/repo/jdbc/MysqlSupportStrategy.java | 4 + .../carbonado/repo/jdbc/OracleSupportStrategy.java | 25 +- .../carbonado/repo/jdbc/SimpleDataSource.java | 5 +- 20 files changed, 892 insertions(+), 479 deletions(-) (limited to 'src/main/java/com/amazon/carbonado/repo/jdbc') diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCBlob.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCBlob.java index d84d033..ba5cdcf 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCBlob.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCBlob.java @@ -126,7 +126,7 @@ class JDBCBlob extends AbstractBlob implements JDBCLob { throw new FetchException("Blob value is null"); } try { - JDBCTransaction txn = mRepo.openTransactionManager().getTxn(); + JDBCTransaction txn = mRepo.localTxnManager().getTxn(); if (txn != null) { txn.register(this); } @@ -143,7 +143,7 @@ class JDBCBlob extends AbstractBlob implements JDBCLob { if ((mBlob = mLoader.load(mRepo)) == null) { throw new PersistException("Blob value is null"); } - JDBCTransaction txn = mRepo.openTransactionManager().getTxn(); + JDBCTransaction txn = mRepo.localTxnManager().getTxn(); if (txn != null) { txn.register(this); } diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCClob.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCClob.java index 70ce7f9..c4e74f1 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCClob.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCClob.java @@ -126,7 +126,7 @@ class JDBCClob extends AbstractClob implements JDBCLob { throw new FetchException("Clob value is null"); } try { - JDBCTransaction txn = mRepo.openTransactionManager().getTxn(); + JDBCTransaction txn = mRepo.localTxnManager().getTxn(); if (txn != null) { txn.register(this); } @@ -143,7 +143,7 @@ class JDBCClob extends AbstractClob implements JDBCLob { if ((mClob = mLoader.load(mRepo)) == null) { throw new PersistException("Clob value is null"); } - JDBCTransaction txn = mRepo.openTransactionManager().getTxn(); + JDBCTransaction txn = mRepo.localTxnManager().getTxn(); if (txn != null) { txn.register(this); } diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCCursor.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCCursor.java index 13c7548..28b73c7 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCCursor.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCCursor.java @@ -37,22 +37,27 @@ class JDBCCursor extends AbstractCursor { private final JDBCStorage mStorage; private final Connection mConnection; private final PreparedStatement mStatement; + private final boolean mScrollInsensitiveReadOnly; private ResultSet mResultSet; private boolean mHasNext; /** + * @param scrollInsensitiveReadOnly when true, statement is + * TYPE_SCROLL_INSENSITIVE and CONCUR_READ_ONLY. * @throws SQLException from executeQuery on statement. Caller must clean * up when this happens by closing statement and connection. */ JDBCCursor(JDBCStorage storage, Connection con, - PreparedStatement statement) + PreparedStatement statement, + boolean scrollInsensitiveReadOnly) throws SQLException { mStorage = storage; mConnection = con; mStatement = statement; + mScrollInsensitiveReadOnly = scrollInsensitiveReadOnly; mResultSet = statement.executeQuery(); } @@ -121,11 +126,49 @@ class JDBCCursor extends AbstractCursor { } int actual = 0; - while (amount > 0) { + + if (amount > 1 && mScrollInsensitiveReadOnly) { + // Skip a relative amount, which is preferred. if (hasNext()) { - actual++; - amount--; - mHasNext = false; + ResultSet rs = mResultSet; + try { + int rowStart = rs.getRow(); + mHasNext = rs.relative(amount); + int rowEnd = rs.getRow(); + if (rowEnd == 0) { + // Skipped past the end. Move back to find the last row number. + if (rs.previous() && (rowEnd = rs.getRow()) != 0) { + rowEnd++; + } else if (rs.last() && (rowEnd = rs.getRow()) != 0) { + rowEnd++; + } else { + // No clue how many were skipped. It's at least one. + rowEnd = rowStart + 1; + } + // Make sure ResultSet is closed below. + mHasNext = false; + } + actual += rowEnd - rowStart; + } catch (SQLException e) { + try { + close(); + } catch (FetchException e2) { + // Don't care. + } + throw mStorage.getJDBCRepository().toFetchException(e); + } + if (!mHasNext) { + close(); + } + } + } else { + // Call next a bunch, which is likely slower than relative skipping. + while (amount > 0) { + if (hasNext()) { + actual++; + amount--; + mHasNext = false; + } } } diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepository.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepository.java index 522bb2d..105de87 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepository.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepository.java @@ -18,45 +18,45 @@ package com.amazon.carbonado.repo.jdbc; +import java.lang.reflect.Method; import java.sql.Connection; import java.sql.DatabaseMetaData; +import java.sql.ResultSet; import java.sql.SQLException; - import java.util.ArrayList; -import java.util.Map; import java.util.IdentityHashMap; - +import java.util.Map; import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cojen.util.WeakIdentityMap; - import com.amazon.carbonado.FetchException; import com.amazon.carbonado.IsolationLevel; -import com.amazon.carbonado.Storage; -import com.amazon.carbonado.Storable; -import com.amazon.carbonado.SupportException; import com.amazon.carbonado.MalformedTypeException; import com.amazon.carbonado.PersistException; import com.amazon.carbonado.Repository; import com.amazon.carbonado.RepositoryException; +import com.amazon.carbonado.Storable; +import com.amazon.carbonado.Storage; +import com.amazon.carbonado.SupportException; import com.amazon.carbonado.Transaction; import com.amazon.carbonado.TriggerFactory; import com.amazon.carbonado.UnsupportedTypeException; - -import com.amazon.carbonado.capability.Capability; import com.amazon.carbonado.capability.IndexInfo; import com.amazon.carbonado.capability.IndexInfoCapability; import com.amazon.carbonado.capability.ShutdownCapability; import com.amazon.carbonado.capability.StorableInfoCapability; - import com.amazon.carbonado.info.StorableProperty; - -import com.amazon.carbonado.spi.StorageCollection; +import com.amazon.carbonado.sequence.SequenceCapability; +import com.amazon.carbonado.sequence.SequenceValueProducer; +import com.amazon.carbonado.spi.AbstractRepository; +import com.amazon.carbonado.spi.TransactionManager; +import com.amazon.carbonado.util.ThrowUnchecked; /** * Repository implementation backed by a JDBC accessible database. @@ -66,16 +66,37 @@ import com.amazon.carbonado.spi.StorageCollection; * control precisely which tables and columns must be matched up. * * @author Brian S O'Neill + * @author bcastill * @see JDBCRepositoryBuilder */ // Note: this class must be public because auto-generated code needs access to it -public class JDBCRepository +public class JDBCRepository extends AbstractRepository implements Repository, IndexInfoCapability, ShutdownCapability, StorableInfoCapability, - JDBCConnectionCapability + JDBCConnectionCapability, + SequenceCapability { + /** + * Attempts to close a DataSource by searching for a "close" method. For + * some reason, there's no standard way to close a DataSource. + * + * @return false if DataSource doesn't have a close method. + */ + public static boolean closeDataSource(DataSource ds) throws SQLException { + try { + Method closeMethod = ds.getClass().getMethod("close"); + try { + closeMethod.invoke(ds); + } catch (Throwable e) { + ThrowUnchecked.fireFirstDeclaredCause(e, SQLException.class); + } + return true; + } catch (NoSuchMethodException e) { + return false; + } + } static IsolationLevel mapIsolationLevelFromJdbc(int jdbcLevel) { switch (jdbcLevel) { @@ -142,27 +163,26 @@ public class JDBCRepository private final Log mLog = LogFactory.getLog(getClass()); - private final String mName; final boolean mIsMaster; final Iterable mTriggerFactories; private final AtomicReference mRootRef; private final String mDatabaseProductName; private final DataSource mDataSource; + private final boolean mDataSourceClose; private final String mCatalog; private final String mSchema; - private final StorageCollection mStorages; + + // Maps Storable types which should have automatic version management. + private Map mAutoVersioningMap; // Track all open connections so that they can be closed when this // repository is closed. private Map mOpenConnections; - - private final ThreadLocal mCurrentTxnMgr; - - // Weakly tracks all JDBCTransactionManager instances for shutdown. - private final Map mAllTxnMgrs; + private final Lock mOpenConnectionsLock; private final boolean mSupportsSavepoints; private final boolean mSupportsSelectForUpdate; + private final boolean mSupportsScrollInsensitiveReadOnly; private final IsolationLevel mDefaultIsolationLevel; private final int mJdbcDefaultIsolationLevel; @@ -184,48 +204,42 @@ public class JDBCRepository * @param catalog optional catalog to search for tables -- actual meaning * is database independent * @param schema optional schema to search for tables -- actual meaning is - * database independent + * is database independent + * @param forceStoredSequence tells the repository to use a stored sequence + * even if the database supports native sequences */ @SuppressWarnings("unchecked") JDBCRepository(AtomicReference rootRef, String name, boolean isMaster, Iterable triggerFactories, - DataSource dataSource, String catalog, String schema) + DataSource dataSource, boolean dataSourceClose, + String catalog, String schema, + Map autoVersioningMap, + String sequenceSelectStatement, boolean forceStoredSequence) throws RepositoryException { - if (name == null || dataSource == null) { - throw new IllegalArgumentException(); + super(name); + if (dataSource == null) { + throw new IllegalArgumentException("DataSource cannot be null"); } - mName = name; mIsMaster = isMaster; mTriggerFactories = triggerFactories; mRootRef = rootRef; mDataSource = dataSource; + mDataSourceClose = dataSourceClose; mCatalog = catalog; mSchema = schema; - mStorages = new StorageCollection() { - protected Storage createStorage(Class type) - throws RepositoryException - { - // Lock on mAllTxnMgrs to prevent databases from being opened during shutdown. - synchronized (mAllTxnMgrs) { - JDBCStorableInfo info = examineStorable(type); - if (!info.isSupported()) { - throw new UnsupportedTypeException("Independent type not supported", type); - } - return new JDBCStorage(JDBCRepository.this, info); - } - } - }; + mAutoVersioningMap = autoVersioningMap; mOpenConnections = new IdentityHashMap(); - mCurrentTxnMgr = new ThreadLocal(); - mAllTxnMgrs = new WeakIdentityMap(); + mOpenConnectionsLock = new ReentrantLock(true); // Temporarily set to generic one, in case there's a problem during initialization. mExceptionTransformer = new JDBCExceptionTransformer(); + getLog().info("Opening repository \"" + getName() + '"'); + // Test connectivity and get some info on transaction isolation levels. Connection con = getConnection(); try { @@ -261,6 +275,8 @@ public class JDBCRepository mSupportsSavepoints = supportsSavepoints; mSupportsSelectForUpdate = md.supportsSelectForUpdate(); + mSupportsScrollInsensitiveReadOnly = md.supportsResultSetConcurrency + (ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); mJdbcDefaultIsolationLevel = md.getDefaultTransactionIsolation(); mDefaultIsolationLevel = mapIsolationLevelFromJdbc(mJdbcDefaultIsolationLevel); @@ -269,7 +285,6 @@ public class JDBCRepository mReadCommittedLevel = selectIsolationLevel(md, IsolationLevel.READ_COMMITTED); mRepeatableReadLevel = selectIsolationLevel(md, IsolationLevel.REPEATABLE_READ); mSerializableLevel = selectIsolationLevel(md, IsolationLevel.SERIALIZABLE); - } catch (SQLException e) { throw toRepositoryException(e); } finally { @@ -277,43 +292,27 @@ public class JDBCRepository } mSupportStrategy = JDBCSupportStrategy.createStrategy(this); + if (forceStoredSequence) { + mSupportStrategy.setSequenceSelectStatement(null); + } else if (sequenceSelectStatement != null && sequenceSelectStatement.length() > 0) { + mSupportStrategy.setSequenceSelectStatement(sequenceSelectStatement); + } + mSupportStrategy.setForceStoredSequence(forceStoredSequence); mExceptionTransformer = mSupportStrategy.createExceptionTransformer(); + + setAutoShutdownEnabled(true); } public DataSource getDataSource() { return mDataSource; } - public String getName() { - return mName; - } - - @SuppressWarnings("unchecked") - public Storage storageFor(Class type) throws RepositoryException { - return mStorages.storageFor(type); - } - - public Transaction enterTransaction() { - return openTransactionManager().enter(null); - } - - public Transaction enterTransaction(IsolationLevel level) { - return openTransactionManager().enter(level); - } - - public Transaction enterTopTransaction(IsolationLevel level) { - return openTransactionManager().enterTop(level); - } - - public IsolationLevel getTransactionIsolationLevel() { - return openTransactionManager().getIsolationLevel(); - } - /** * Returns true if a transaction is in progress and it is for update. */ + // Is called by auto-generated code and must be public. public boolean isTransactionForUpdate() { - return openTransactionManager().isForUpdate(); + return localTransactionManager().isForUpdate(); } /** @@ -334,14 +333,6 @@ public class JDBCRepository } } - @SuppressWarnings("unchecked") - public C getCapability(Class capabilityType) { - if (capabilityType.isInstance(this)) { - return (C) this; - } - return null; - } - public IndexInfo[] getIndexInfo(Class storableType) throws RepositoryException { @@ -352,7 +343,7 @@ public class JDBCRepository // We don't register Storable types persistently, so just return what // we know right now. ArrayList names = new ArrayList(); - for (Storage storage : mStorages.allStorage()) { + for (Storage storage : allStorage()) { names.add(storage.getStorableType().getName()); } return names.toArray(new String[names.size()]); @@ -401,72 +392,6 @@ public class JDBCRepository return jProperty; } - /** - * Returns the thread-local JDBCTransactionManager instance, creating it if - * needed. - */ - JDBCTransactionManager openTransactionManager() { - JDBCTransactionManager txnMgr = mCurrentTxnMgr.get(); - if (txnMgr == null) { - synchronized (mAllTxnMgrs) { - txnMgr = new JDBCTransactionManager(this); - mCurrentTxnMgr.set(txnMgr); - mAllTxnMgrs.put(txnMgr, null); - } - } - return txnMgr; - } - - public void close() { - shutdown(false); - } - - public boolean isAutoShutdownEnabled() { - return false; - } - - public void setAutoShutdownEnabled(boolean enabled) { - } - - public void shutdown() { - shutdown(true); - } - - private void shutdown(boolean suspendThreads) { - synchronized (mAllTxnMgrs) { - // Close transactions and cursors. - for (JDBCTransactionManager txnMgr : mAllTxnMgrs.keySet()) { - if (suspendThreads) { - // Lock transaction manager but don't release it. This - // prevents other threads from beginning work during - // shutdown, which will likely fail along the way. - txnMgr.getLock().lock(); - } - try { - txnMgr.close(); - } catch (Throwable e) { - getLog().error(null, e); - } - } - - // Now close all open connections. - if (mOpenConnections != null) { - for (Connection con : mOpenConnections.keySet()) { - try { - con.close(); - } catch (SQLException e) { - getLog().warn(null, e); - } - } - mOpenConnections = null; - } - } - } - - protected Log getLog() { - return mLog; - } - public String getDatabaseProductName() { return mDatabaseProductName; } @@ -482,22 +407,25 @@ public class JDBCRepository throw new FetchException("Repository is closed"); } - JDBCTransaction txn = openTransactionManager().getTxn(); + JDBCTransaction txn = localTransactionManager().getTxn(); if (txn != null) { // Return the connection used by the current transaction. return txn.getConnection(); } - // Get connection outside synchronized section since it may block. + // Get connection outside lock section since it may block. Connection con = mDataSource.getConnection(); con.setAutoCommit(true); - synchronized (mAllTxnMgrs) { + mOpenConnectionsLock.lock(); + try { if (mOpenConnections == null) { con.close(); throw new FetchException("Repository is closed"); } mOpenConnections.put(con, null); + } finally { + mOpenConnectionsLock.unlock(); } return con; @@ -515,7 +443,7 @@ public class JDBCRepository throw new FetchException("Repository is closed"); } - // Get connection outside synchronized section since it may block. + // Get connection outside lock section since it may block. Connection con = mDataSource.getConnection(); if (level == IsolationLevel.NONE) { @@ -527,12 +455,15 @@ public class JDBCRepository } } - synchronized (mAllTxnMgrs) { + mOpenConnectionsLock.lock(); + try { if (mOpenConnections == null) { con.close(); throw new FetchException("Repository is closed"); } mOpenConnections.put(con, null); + } finally { + mOpenConnectionsLock.unlock(); } return con; @@ -549,12 +480,15 @@ public class JDBCRepository public void yieldConnection(Connection con) throws FetchException { try { if (con.getAutoCommit()) { - synchronized (mAllTxnMgrs) { + mOpenConnectionsLock.lock(); + try { if (mOpenConnections != null) { mOpenConnections.remove(con); } + } finally { + mOpenConnectionsLock.unlock(); } - // Close connection outside synchronized section since it may block. + // Close connection outside lock section since it may block. if (con.getTransactionIsolation() != mJdbcDefaultIsolationLevel) { con.setTransactionIsolation(mJdbcDefaultIsolationLevel); } @@ -574,12 +508,15 @@ public class JDBCRepository * any exceptions too. */ private void forceYieldConnection(Connection con) { - synchronized (mAllTxnMgrs) { + mOpenConnectionsLock.lock(); + try { if (mOpenConnections != null) { mOpenConnections.remove(con); } + } finally { + mOpenConnectionsLock.unlock(); } - // Close connection outside synchronized section since it may block. + // Close connection outside lock section since it may block. try { con.close(); } catch (SQLException e) { @@ -595,6 +532,10 @@ public class JDBCRepository return mSupportsSelectForUpdate; } + boolean supportsScrollInsensitiveReadOnly() { + return mSupportsScrollInsensitiveReadOnly; + } + /** * Returns the highest supported level for the given desired level. * @@ -685,4 +626,80 @@ public class JDBCRepository JDBCExceptionTransformer getExceptionTransformer() { return mExceptionTransformer; } + + protected void shutdownHook() { + // Close all open connections. + mOpenConnectionsLock.lock(); + try { + if (mOpenConnections != null) { + for (Connection con : mOpenConnections.keySet()) { + try { + con.close(); + } catch (SQLException e) { + getLog().warn(null, e); + } + } + mOpenConnections = null; + } + } finally { + mOpenConnectionsLock.unlock(); + } + + if (mDataSourceClose) { + mLog.info("Closing DataSource: " + mDataSource); + try { + if (!closeDataSource(mDataSource)) { + mLog.info("DataSource doesn't have a close method: " + + mDataSource.getClass().getName()); + } + } catch (SQLException e) { + mLog.error("Failed to close DataSource", e); + } + } + } + + protected Log getLog() { + return mLog; + } + + protected TransactionManager createTransactionManager() { + return new JDBCTransactionManager(this); + } + + protected Storage createStorage(Class type) + throws RepositoryException + { + JDBCStorableInfo info = examineStorable(type); + if (!info.isSupported()) { + throw new UnsupportedTypeException("Independent type not supported", type); + } + + Boolean autoVersioning = false; + if (mAutoVersioningMap != null) { + autoVersioning = mAutoVersioningMap.get(type.getName()); + if (autoVersioning == null) { + // No explicit setting, so check wildcard setting. + autoVersioning = mAutoVersioningMap.get(null); + if (autoVersioning == null) { + autoVersioning = false; + } + } + } + + return new JDBCStorage(this, info, autoVersioning); + } + + protected SequenceValueProducer createSequenceValueProducer(String name) + throws RepositoryException + { + return mSupportStrategy.createSequenceValueProducer(name); + } + + /** + * Returns the thread-local JDBCTransactionManager, creating it if needed. + */ + // Provides access to transaction manager from other classes. + TransactionManager localTxnManager() { + return localTransactionManager(); + } } diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepositoryBuilder.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepositoryBuilder.java index 5b59852..b078e14 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepositoryBuilder.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepositoryBuilder.java @@ -20,6 +20,8 @@ package com.amazon.carbonado.repo.jdbc; import java.sql.SQLException; import java.util.Collection; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.atomic.AtomicReference; @@ -49,15 +51,18 @@ import com.amazon.carbonado.spi.AbstractRepositoryBuilder; *
  • {@link com.amazon.carbonado.capability.IndexInfoCapability IndexInfoCapability} *
  • {@link com.amazon.carbonado.capability.StorableInfoCapability StorableInfoCapability} *
  • {@link com.amazon.carbonado.capability.ShutdownCapability ShutdownCapability} + *
  • {@link com.amazon.carbonado.sequence.SequenceCapability SequenceCapability} *
  • {@link JDBCConnectionCapability JDBCConnectionCapability} * * * @author Brian S O'Neill + * @author bcastill */ public class JDBCRepositoryBuilder extends AbstractRepositoryBuilder { private String mName; private boolean mIsMaster = true; private DataSource mDataSource; + private boolean mDataSourceClose; private boolean mDataSourceLogging; private String mCatalog; private String mSchema; @@ -65,7 +70,10 @@ public class JDBCRepositoryBuilder extends AbstractRepositoryBuilder { private String mURL; private String mUsername; private String mPassword; - + private Map mAutoVersioningMap; + private String mSequenceSelectStatement; + private boolean mForceStoredSequence; + public JDBCRepositoryBuilder() { } @@ -73,7 +81,10 @@ public class JDBCRepositoryBuilder extends AbstractRepositoryBuilder { assertReady(); JDBCRepository repo = new JDBCRepository (rootRef, getName(), isMaster(), getTriggerFactories(), - getDataSource(), mCatalog, mSchema); + getDataSource(), getDataSourceCloseOnShutdown(), + mCatalog, mSchema, + getAutoVersioningMap(), + mSequenceSelectStatement, mForceStoredSequence); rootRef.set(repo); return repo; } @@ -137,6 +148,22 @@ public class JDBCRepositoryBuilder extends AbstractRepositoryBuilder { return ds; } + /** + * Pass true to cause the DataSource to be closed when the repository is + * closed or shutdown. By default, this option is false. + */ + public void setDataSourceCloseOnShutdown(boolean b) { + mDataSourceClose = b; + } + + /** + * Returns true if DataSource is closed when the repository is closed or + * shutdown. By default, this option is false. + */ + public boolean getDataSourceCloseOnShutdown() { + return mDataSourceClose; + } + /** * Pass true to enable debug logging. By default, it is false. * @@ -241,6 +268,64 @@ public class JDBCRepositoryBuilder extends AbstractRepositoryBuilder { return mPassword; } + /** + * By default, JDBCRepository assumes that {@link + * com.amazon.carbonado.Version version numbers} are initialized and + * incremented by triggers installed on the database. Enabling automatic + * versioning here causes the JDBCRepository to manage these operations + * itself. + * + * @param enabled true to enable, false to disable + * @param className name of Storable type to enable automatic version + * management on; pass null to enable all + */ + public void setAutoVersioningEnabled(boolean enabled, String className) { + if (mAutoVersioningMap == null) { + mAutoVersioningMap = new HashMap(); + } + mAutoVersioningMap.put(className, enabled); + } + + private Map getAutoVersioningMap() { + if (mAutoVersioningMap == null) { + return null; + } + return new HashMap(mAutoVersioningMap); + } + + /** + * Returns the native sequence select statement, which is null if the + * default is chosen. + */ + public String getSequenceSelectStatement() { + return mSequenceSelectStatement; + } + + /** + * Override the default native sequence select statement with a printf. + * For example, "SELECT %s.NEXTVAL FROM DUAL". + */ + public void setSequenceSelectStatement(String sequenceSelectStatement) { + mSequenceSelectStatement = sequenceSelectStatement; + } + + /** + * Returns true if native sequences should not be used. + */ + public boolean isForceStoredSequence() { + return mForceStoredSequence; + } + + /** + * By default, native sequences are used if supported. Otherwise, a table + * named "CARBONADO_SEQUENCE" or "CARBONADO_SEQUENCES" is used instead to + * hold sequence values. When forced, the table is always used instead of + * native sequences. + */ + public void setForceStoredSequence(boolean forceStoredSequence) { + mForceStoredSequence = forceStoredSequence; + } + public void errorCheck(Collection messages) throws ConfigurationException { super.errorCheck(messages); if (mDataSource == null) { diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCSequenceValueProducer.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCSequenceValueProducer.java index f98bd1e..e2fb9a8 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCSequenceValueProducer.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCSequenceValueProducer.java @@ -23,8 +23,7 @@ import java.sql.ResultSet; import java.sql.Statement; import com.amazon.carbonado.PersistException; - -import com.amazon.carbonado.spi.AbstractSequenceValueProducer; +import com.amazon.carbonado.sequence.AbstractSequenceValueProducer; /** * @@ -65,4 +64,8 @@ class JDBCSequenceValueProducer extends AbstractSequenceValueProducer { throw mRepo.toPersistException(e); } } + + public boolean returnReservedValues() { + return false; + } } diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableGenerator.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableGenerator.java index 7e09690..6a6a3b6 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableGenerator.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableGenerator.java @@ -40,6 +40,7 @@ import org.cojen.classfile.Modifiers; import org.cojen.classfile.Opcode; import org.cojen.classfile.TypeDesc; import org.cojen.util.ClassInjector; +import org.cojen.util.KeyFactory; import org.cojen.util.SoftValuedHashMap; import com.amazon.carbonado.FetchException; @@ -77,43 +78,57 @@ class JDBCStorableGenerator { // Initial StringBuilder capactity for update statement. private static final int INITIAL_UPDATE_BUFFER_SIZE = 100; - private static final Map, Class> cCache; + // Modes for automatic versioning when setting PreparedStatement values. + private static final int NORMAL = 0; + private static final int INITIAL_VERSION = 1; + private static final int INCREMENT_VERSION = 2; + + private static final Map> cCache; static { cCache = new SoftValuedHashMap(); } - static Class getGeneratedClass(JDBCStorableInfo info) + static Class getGeneratedClass(JDBCStorableInfo info, + boolean autoVersioning) throws SupportException { - Class type = info.getStorableType(); + Object key = KeyFactory.createKey(new Object[] {info, autoVersioning}); + synchronized (cCache) { - Class generatedClass = (Class) cCache.get(type); + Class generatedClass = (Class) cCache.get(key); if (generatedClass != null) { return generatedClass; } - generatedClass = new JDBCStorableGenerator(info).generateAndInjectClass(); - cCache.put(type, generatedClass); + generatedClass = + new JDBCStorableGenerator(info, autoVersioning).generateAndInjectClass(); + cCache.put(key, generatedClass); return generatedClass; } } private final Class mStorableType; private final JDBCStorableInfo mInfo; + private final boolean mAutoVersioning; private final Map> mAllProperties; private final ClassLoader mParentClassLoader; private final ClassInjector mClassInjector; private final ClassFile mClassFile; - private JDBCStorableGenerator(JDBCStorableInfo info) throws SupportException { + private JDBCStorableGenerator(JDBCStorableInfo info, boolean autoVersioning) + throws SupportException + { mStorableType = info.getStorableType(); mInfo = info; + mAutoVersioning = autoVersioning; mAllProperties = mInfo.getAllProperties(); EnumSet features = EnumSet .of(MasterFeature.INSERT_SEQUENCES, - MasterFeature.INSERT_TXN, MasterFeature.UPDATE_TXN); + MasterFeature.INSERT_CHECK_REQUIRED, // Must use @Automatic to override. + MasterFeature.INSERT_TXN, // Required because of reload after insert. + MasterFeature.UPDATE_TXN); // Required because of reload after update. final Class abstractClass = MasterStorableGenerator.getAbstractClass(mStorableType, features); @@ -127,7 +142,7 @@ class JDBCStorableGenerator { mClassFile.setTarget("1.5"); } - private Class generateAndInjectClass() { + private Class generateAndInjectClass() throws SupportException { // We'll need these "inner classes" which serve as Lob loading // callbacks. Lob's need to be reloaded if the original transaction has // been committed. @@ -417,58 +432,170 @@ class JDBCStorableGenerator { // Push connection in preparation for preparing a statement. b.loadLocal(conVar); - // Only insert version property if DIRTY. Create two insert - // statements, with and without the version property. - StringBuilder sb = new StringBuilder(); - createInsertStatement(sb, false); - String noVersion = sb.toString(); + String staticInsertStatement; + { + // Build the full static insert statement, even though it might + // not be used. If not used, then the length of the full static + // statement is used to determine the initial buffer size of + // the dynamically generated statement. + StringBuilder sb = new StringBuilder(); + + sb.append("INSERT INTO "); + sb.append(mInfo.getQualifiedTableName()); + sb.append(" ( "); + + int ordinal = 0; + for (JDBCStorableProperty property : mInfo.getAllProperties().values()) { + if (!property.isSelectable()) { + continue; + } + if (ordinal > 0) { + sb.append(','); + } + sb.append(property.getColumnName()); + ordinal++; + } - sb.setLength(0); - int versionPropNumber = createInsertStatement(sb, true); + sb.append(" ) VALUES ("); - LocalVariable includeVersion = null; + for (int i=0; i 0) { + sb.append(','); + } + sb.append('?'); + } + + sb.append(')'); - if (versionPropNumber < 0) { - // No version property at all, so no need to determine which - // statement to execute. - b.loadConstant(noVersion); + staticInsertStatement = sb.toString(); + } + + boolean useStaticInsertStatement = true; + for (JDBCStorableProperty property : mInfo.getAllProperties().values()) { + if (property.isVersion() || property.isAutomatic()) { + useStaticInsertStatement = false; + break; + } + } + + // Count of inserted properties when using dynamically generated statement. + LocalVariable insertCountVar = null; + + if (useStaticInsertStatement) { + // Load static insert statement to stack. + b.loadConstant(staticInsertStatement); } else { - includeVersion = b.createLocalVariable(null, TypeDesc.BOOLEAN); - - Label isDirty = b.createLabel(); - branchIfDirty(b, versionPropNumber, isDirty, true); - - // Version not dirty, so don't insert it. Assume database - // creates an initial version instead. - b.loadConstant(false); - b.storeLocal(includeVersion); - b.loadConstant(noVersion); - Label cont = b.createLabel(); - b.branch(cont); - - isDirty.setLocation(); - // Including version property in statement. - b.loadConstant(true); - b.storeLocal(includeVersion); - b.loadConstant(sb.toString()); - - cont.setLocation(); + // Dynamically build insert statement, ignoring automatic and + // version properties which are not DIRTY. + + insertCountVar = b.createLocalVariable(null, TypeDesc.INT); + int initialCount = 0; + for (JDBCStorableProperty property : mInfo.getAllProperties().values()) { + if (!property.isSelectable()) { + continue; + } + if (isAlwaysInserted(property)) { + // Don't bother dynamically counting properties which + // will always be inserted. + initialCount++; + } + } + + b.loadConstant(initialCount); + b.storeLocal(insertCountVar); + + TypeDesc stringBuilderType = TypeDesc.forClass(StringBuilder.class); + b.newObject(stringBuilderType); + b.dup(); + b.loadConstant(staticInsertStatement.length()); + b.invokeConstructor(stringBuilderType, new TypeDesc[] {TypeDesc.INT}); + + // Note extra space after left paren. This is required for case + // where no properties are explicitly inserted. The logic below + // to blindly delete the last character with a (thinking it is + // a comma) causes no harm when there are no properties. + b.loadConstant("INSERT INTO " + mInfo.getQualifiedTableName() + " ( "); + CodeBuilderUtil.callStringBuilderAppendString(b); + + int propNumber = -1; + for (JDBCStorableProperty property : mInfo.getAllProperties().values()) { + propNumber++; + if (!property.isSelectable()) { + continue; + } + + Label nextProperty = b.createLabel(); + if (!isAlwaysInserted(property)) { + // Property is set only if value manually supplied. + branchIfDirty(b, propNumber, nextProperty, false); + b.integerIncrement(insertCountVar, 1); + } + + // Append property name (with trailing comma) to StringBuilder. + b.loadConstant(property.getColumnName() + ','); + CodeBuilderUtil.callStringBuilderAppendString(b); + + nextProperty.setLocation(); + } + + // Blindly delete last character, assuming it is a trailing comma. + LocalVariable sbVar = b.createLocalVariable(null, stringBuilderType); + b.storeLocal(sbVar); + b.loadLocal(sbVar); + b.loadLocal(sbVar); + CodeBuilderUtil.callStringBuilderLength(b); + b.loadConstant(1); + b.math(Opcode.ISUB); + CodeBuilderUtil.callStringBuilderSetLength(b); + b.loadLocal(sbVar); // Load StringBuilder to stack as before. + + b.loadConstant(" ) VALUES ("); + CodeBuilderUtil.callStringBuilderAppendString(b); + + // Append all the necessary question marks. + b.loadLocal(insertCountVar); + Label finishStatement = b.createLabel(); + b.ifZeroComparisonBranch(finishStatement, "<="); + + b.loadConstant('?'); + CodeBuilderUtil.callStringBuilderAppendChar(b); + + Label loopStart = b.createLabel().setLocation(); + b.integerIncrement(insertCountVar, -1); + b.loadLocal(insertCountVar); + b.ifZeroComparisonBranch(finishStatement, "<="); + b.loadConstant(",?"); + CodeBuilderUtil.callStringBuilderAppendString(b); + b.branch(loopStart); + + finishStatement.setLocation(); + b.loadConstant(')'); + CodeBuilderUtil.callStringBuilderAppendChar(b); + CodeBuilderUtil.callStringBuilderToString(b); } - // At this point, the stack contains a connection and a SQL - // statement String. + // At this point, the stack contains a connection and a complete + // SQL insert statement String. + + // Determine if generated keys need to be retrieved. + Collection> identityProperties = + mInfo.getIdentityProperties().values(); LocalVariable psVar = b.createLocalVariable("ps", preparedStatementType); - b.invokeInterface(connectionType, "prepareStatement", preparedStatementType, - new TypeDesc[] {TypeDesc.STRING}); + if (identityProperties.size() == 0) { + b.invokeInterface(connectionType, "prepareStatement", preparedStatementType, + new TypeDesc[] {TypeDesc.STRING}); + } else { + b.loadConstant(Statement.RETURN_GENERATED_KEYS); + b.invokeInterface(connectionType, "prepareStatement", preparedStatementType, + new TypeDesc[] {TypeDesc.STRING, TypeDesc.INT}); + } b.storeLocal(psVar); Label tryAfterPs = b.createLabel().setLocation(); // Now fill in parameters with property values. - JDBCStorableProperty versionProperty = null; - // Gather all Lob properties to track if a post-insert update is required. Map, Integer> lobIndexMap = findLobs(); LocalVariable lobArrayVar = null; @@ -481,43 +608,85 @@ class JDBCStorableGenerator { } int ordinal = 0; + LocalVariable ordinalVar = null; + if (!useStaticInsertStatement) { + // Increment parameter ordinal at runtime. + ordinalVar = b.createLocalVariable(null, TypeDesc.INT); + b.loadConstant(0); + b.storeLocal(ordinalVar); + } + + int propNumber = -1; for (JDBCStorableProperty property : mAllProperties.values()) { + propNumber++; if (!property.isSelectable()) { continue; } - if (property.isVersion()) { - if (includeVersion != null) { - // Fill in version later, but check against boolean - // local variable to decide if it is was dirty. - versionProperty = property; - } - continue; - } + Label nextProperty = b.createLabel(); + if (!isAlwaysInserted(property)) { + // Property is set only if value manually supplied. + branchIfDirty(b, propNumber, nextProperty, false); + } + b.loadLocal(psVar); - b.loadConstant(++ordinal); + if (ordinalVar == null) { + b.loadConstant(++ordinal); + } else { + b.integerIncrement(ordinalVar, 1); + b.loadLocal(ordinalVar); + } - setPreparedStatementValue - (b, property, repoVar, null, lobArrayVar, lobIndexMap.get(property)); - } + Label setNormally = b.createLabel(); + if (property.isVersion() && mAutoVersioning) { + // Automatically supply initial value unless manually supplied. + branchIfDirty(b, propNumber, setNormally, true); + setPreparedStatementValue + (b, property, INITIAL_VERSION, + repoVar, null, lobArrayVar, lobIndexMap.get(property)); + b.branch(nextProperty); + } - if (versionProperty != null) { - // Fill in version property now, but only if was dirty. - b.loadLocal(includeVersion); - Label skipVersion = b.createLabel(); - b.ifZeroComparisonBranch(skipVersion, "=="); + setNormally.setLocation(); - b.loadLocal(psVar); - b.loadConstant(++ordinal); - setPreparedStatementValue(b, versionProperty, repoVar, null, null, null); + setPreparedStatementValue + (b, property, NORMAL, repoVar, null, lobArrayVar, lobIndexMap.get(property)); - skipVersion.setLocation(); + nextProperty.setLocation(); } // Execute the statement. b.loadLocal(psVar); b.invokeInterface(preparedStatementType, "executeUpdate", TypeDesc.INT, null); b.pop(); + + if (identityProperties.size() > 0) { + // Get the generated keys and set the properties. + b.loadLocal(psVar); + b.invokeInterface(preparedStatementType, "getGeneratedKeys", resultSetType, null); + + LocalVariable rsVar = b.createLocalVariable("rs", resultSetType); + b.storeLocal(rsVar); + Label tryAfterRs = b.createLabel().setLocation(); + + b.loadLocal(rsVar); + b.invokeInterface(resultSetType, "next", TypeDesc.BOOLEAN, null); + Label noResults = b.createLabel(); + b.ifZeroComparisonBranch(noResults, "=="); + + // Set property value. + LocalVariable initialOffsetVar = b.createLocalVariable(null, TypeDesc.INT); + b.loadConstant(1); + b.storeLocal(initialOffsetVar); + defineExtract(b, rsVar, initialOffsetVar, null, // no lobArrayVar + mInfo.getIdentityProperties().values(), + lobLoaderMap); + + noResults.setLocation(); + + closeResultSet(b, rsVar, tryAfterRs); + } + closeStatement(b, psVar, tryAfterPs); // Immediately reload object, to ensure that any database supplied @@ -573,18 +742,6 @@ class JDBCStorableGenerator { b.loadConstant(INITIAL_UPDATE_BUFFER_SIZE); b.invokeConstructor(stringBuilderType, new TypeDesc[] {TypeDesc.INT}); - // Methods on StringBuilder. - final Method appendStringMethod; - final Method appendCharMethod; - final Method toStringMethod; - try { - appendStringMethod = StringBuilder.class.getMethod("append", String.class); - appendCharMethod = StringBuilder.class.getMethod("append", char.class); - toStringMethod = StringBuilder.class.getMethod("toString", (Class[]) null); - } catch (NoSuchMethodException e) { - throw new UndeclaredThrowableException(e); - } - { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("UPDATE "); @@ -592,7 +749,8 @@ class JDBCStorableGenerator { sqlBuilder.append(" SET "); b.loadConstant(sqlBuilder.toString()); - b.invoke(appendStringMethod); // method leaves StringBuilder on stack + // Method leaves StringBuilder on stack. + CodeBuilderUtil.callStringBuilderAppendString(b); } // Iterate over the properties, appending a set parameter for each @@ -607,31 +765,36 @@ class JDBCStorableGenerator { propNumber++; if (property.isSelectable() && !property.isPrimaryKeyMember()) { - if (property.isVersion()) { - // TODO: Support option where version property is - // updated on the Carbonado side rather than relying on - // SQL trigger. + // Assume database trigger manages version. + if (property.isVersion() && !mAutoVersioning) { continue; } - Label isNotDirty = b.createLabel(); - branchIfDirty(b, propNumber, isNotDirty, false); + Label isNotDirty = null; + if (!property.isVersion()) { + // Version must always be updated, but all other + // properties are updated only if dirty. + isNotDirty = b.createLabel(); + branchIfDirty(b, propNumber, isNotDirty, false); + } b.loadLocal(countVar); Label isZero = b.createLabel(); b.ifZeroComparisonBranch(isZero, "=="); b.loadConstant(','); - b.invoke(appendCharMethod); + CodeBuilderUtil.callStringBuilderAppendChar(b); isZero.setLocation(); b.loadConstant(property.getColumnName()); - b.invoke(appendStringMethod); + CodeBuilderUtil.callStringBuilderAppendString(b); b.loadConstant("=?"); - b.invoke(appendStringMethod); + CodeBuilderUtil.callStringBuilderAppendString(b); b.integerIncrement(countVar, 1); - isNotDirty.setLocation(); + if (isNotDirty != null) { + isNotDirty.setLocation(); + } } } @@ -655,36 +818,36 @@ class JDBCStorableGenerator { b.ifZeroComparisonBranch(notZero, "!="); b.loadConstant(whereProperties.iterator().next().getColumnName()); - b.invoke(appendStringMethod); + CodeBuilderUtil.callStringBuilderAppendString(b); b.loadConstant("=?"); - b.invoke(appendStringMethod); + CodeBuilderUtil.callStringBuilderAppendString(b); notZero.setLocation(); } b.loadConstant(" WHERE "); - b.invoke(appendStringMethod); + CodeBuilderUtil.callStringBuilderAppendString(b); int ordinal = 0; for (JDBCStorableProperty property : whereProperties) { if (ordinal > 0) { b.loadConstant(" AND "); - b.invoke(appendStringMethod); + CodeBuilderUtil.callStringBuilderAppendString(b); } b.loadConstant(property.getColumnName()); - b.invoke(appendStringMethod); + CodeBuilderUtil.callStringBuilderAppendString(b); if (property.isNullable()) { - // FIXME + // TODO: Support null primary key or version property. Is this possible? throw new UnsupportedOperationException(); } else { b.loadConstant("=?"); - b.invoke(appendStringMethod); + CodeBuilderUtil.callStringBuilderAppendString(b); } ordinal++; } // Convert StringBuilder value to a String. - b.invoke(toStringMethod); + CodeBuilderUtil.callStringBuilderToString(b); // At this point, the stack contains a connection and a SQL // statement String. @@ -724,7 +887,7 @@ class JDBCStorableGenerator { b.loadLocal(psVar); b.loadLocal(indexVar); setPreparedStatementValue - (b, property, repoVar, null, lobArrayVar, lobIndexMap.get(property)); + (b, property, NORMAL, repoVar, null, lobArrayVar, lobIndexMap.get(property)); b.integerIncrement(indexVar, 1); @@ -736,24 +899,30 @@ class JDBCStorableGenerator { propNumber++; if (property.isSelectable() && !property.isPrimaryKeyMember()) { - if (property.isVersion()) { - // TODO: Support option where version property is - // updated on the Carbonado side rather than relying on - // SQL trigger. Just add one to the value. + // Assume database trigger manages version. + if (property.isVersion() && !mAutoVersioning) { continue; } - Label isNotDirty = b.createLabel(); - branchIfDirty(b, propNumber, isNotDirty, false); + Label isNotDirty = null; + if (!property.isVersion()) { + // Version must always be updated, but all other + // properties are updated only if dirty. + isNotDirty = b.createLabel(); + branchIfDirty(b, propNumber, isNotDirty, false); + } b.loadLocal(psVar); b.loadLocal(indexVar); setPreparedStatementValue - (b, property, repoVar, null, lobArrayVar, lobIndexMap.get(property)); + (b, property, property.isVersion() ? INCREMENT_VERSION : NORMAL, + repoVar, null, lobArrayVar, lobIndexMap.get(property)); b.integerIncrement(indexVar, 1); - isNotDirty.setLocation(); + if (isNotDirty != null) { + isNotDirty.setLocation(); + } } } @@ -762,12 +931,12 @@ class JDBCStorableGenerator { for (JDBCStorableProperty property : whereProperties) { if (property.isNullable()) { - // FIXME + // TODO: Support null primary key or version property. Is this possible? throw new UnsupportedOperationException(); } else { b.loadLocal(psVar); b.loadLocal(indexVar); - setPreparedStatementValue(b, property, repoVar, null, null, null); + setPreparedStatementValue(b, property, NORMAL, repoVar, null, null, null); } b.integerIncrement(indexVar, 1); @@ -923,6 +1092,13 @@ class JDBCStorableGenerator { return generatedClass; } + /** + * Returns true if property value is always part of insert statement. + */ + private boolean isAlwaysInserted(JDBCStorableProperty property) { + return property.isVersion() ? mAutoVersioning : !property.isAutomatic(); + } + /** * Finds all Lob properties and maps them to a zero-based index. This * information is used to update large Lobs after an insert or update. @@ -1037,6 +1213,7 @@ class JDBCStorableGenerator { LocalVariable psVar, LocalVariable jdbcRepoVar, LocalVariable instanceVar) + throws SupportException { final TypeDesc superType = TypeDesc.forClass(mClassFile.getSuperClassName()); final Iterable> properties = @@ -1108,28 +1285,19 @@ class JDBCStorableGenerator { b.loadConstant(capacity); b.invokeConstructor(stringBuilderType, new TypeDesc[] {TypeDesc.INT}); - // Methods on StringBuilder. - final Method appendStringMethod; - final Method toStringMethod; - try { - appendStringMethod = StringBuilder.class.getMethod("append", String.class); - toStringMethod = StringBuilder.class.getMethod("toString", (Class[]) null); - } catch (NoSuchMethodException e) { - throw new UndeclaredThrowableException(e); - } - b.loadConstant(sqlBuilder.toString()); - b.invoke(appendStringMethod); // method leaves StringBuilder on stack + // Method leaves StringBuilder on stack. + CodeBuilderUtil.callStringBuilderAppendString(b); ordinal = 0; for (JDBCStorableProperty property : nullableProperties) { if (ordinal > 0) { b.loadConstant(" AND "); - b.invoke(appendStringMethod); + CodeBuilderUtil.callStringBuilderAppendString(b); } b.loadConstant(property.getColumnName()); - b.invoke(appendStringMethod); + CodeBuilderUtil.callStringBuilderAppendString(b); b.loadThis(); @@ -1139,13 +1307,13 @@ class JDBCStorableGenerator { Label notNull = b.createLabel(); b.ifNullBranch(notNull, false); b.loadConstant("IS NULL"); - b.invoke(appendStringMethod); + CodeBuilderUtil.callStringBuilderAppendString(b); Label next = b.createLabel(); b.branch(next); notNull.setLocation(); b.loadConstant("=?"); - b.invoke(appendStringMethod); + CodeBuilderUtil.callStringBuilderAppendString(b); next.setLocation(); ordinal++; @@ -1160,13 +1328,13 @@ class JDBCStorableGenerator { b.ifZeroComparisonBranch(notForUpdate, "=="); b.loadConstant(" FOR UPDATE"); - b.invoke(appendStringMethod); + CodeBuilderUtil.callStringBuilderAppendString(b); notForUpdate.setLocation(); } // Convert StringBuilder to String. - b.invoke(toStringMethod); + CodeBuilderUtil.callStringBuilderToString(b); } // At this point, the stack contains a connection and a SQL statement String. @@ -1186,7 +1354,7 @@ class JDBCStorableGenerator { continue; } - Label skipProperty = b.createLabel(); + Label nextProperty = b.createLabel(); final TypeDesc propertyType = TypeDesc.forClass(property.getType()); @@ -1200,12 +1368,12 @@ class JDBCStorableGenerator { // was appended earlier with "IS NULL". b.loadThis(); b.loadField(superType, property.getName(), propertyType); - b.ifNullBranch(skipProperty, true); + b.ifNullBranch(nextProperty, true); } - setPreparedStatementValue(b, property, null, instanceVar, null, null); + setPreparedStatementValue(b, property, NORMAL, null, instanceVar, null, null); - skipProperty.setLocation(); + nextProperty.setLocation(); } return tryAfterPs; @@ -1223,6 +1391,7 @@ class JDBCStorableGenerator { * the original lob. An update statement needs to be issued after the load * to insert/update the large value. * + * @param mode one of NORMAL, INITIAL_VERSION or INCREMENT_VERSION * @param instanceVar when null, assume properties are contained in * "this". Otherwise, invoke property access methods on storable referenced * in var. @@ -1230,28 +1399,39 @@ class JDBCStorableGenerator { * @param lobIndex optional, used for lob properties */ private void setPreparedStatementValue - (CodeBuilder b, JDBCStorableProperty property, LocalVariable repoVar, + (CodeBuilder b, + JDBCStorableProperty property, + int mode, + LocalVariable repoVar, LocalVariable instanceVar, - LocalVariable lobArrayVar, Integer lobIndex) + LocalVariable lobArrayVar, + Integer lobIndex) + throws SupportException { - if (instanceVar == null) { - b.loadThis(); - } else { - b.loadLocal(instanceVar); - } - Class psClass = property.getPreparedStatementSetMethod().getParameterTypes()[1]; TypeDesc psType = TypeDesc.forClass(psClass); TypeDesc propertyType = TypeDesc.forClass(property.getType()); - StorablePropertyAdapter adapter = property.getAppliedAdapter(); - TypeDesc fromType; - if (adapter == null) { - // Get protected field directly, since no adapter. + + if (mode != INITIAL_VERSION) { + // Load storable to extract property value from. if (instanceVar == null) { - b.loadField(property.getName(), propertyType); + b.loadThis(); } else { - b.loadField(instanceVar.getType(), property.getName(), propertyType); + b.loadLocal(instanceVar); + } + } + + TypeDesc fromType; + + if (adapter == null) { + if (mode != INITIAL_VERSION) { + // Get protected field directly, since no adapter. + if (instanceVar == null) { + b.loadField(property.getName(), propertyType); + } else { + b.loadField(instanceVar.getType(), property.getName(), propertyType); + } } fromType = propertyType; } else { @@ -1263,21 +1443,27 @@ class JDBCStorableGenerator { } Method adaptMethod = adapter.findAdaptMethod(property.getType(), toClass); TypeDesc adaptType = TypeDesc.forClass(adaptMethod.getReturnType()); - // Invoke special inherited protected method that gets the field - // and invokes the adapter. Method was generated by - // StorableGenerator. - String methodName = property.getReadMethodName() + '$'; - if (instanceVar == null) { - b.invokeVirtual(methodName, adaptType, null); - } else { - b.invokeVirtual (instanceVar.getType(), methodName, adaptType, null); + if (mode != INITIAL_VERSION) { + // Invoke special inherited protected method that gets the field + // and invokes the adapter. Method was generated by + // StorableGenerator. + String methodName = property.getReadMethodName() + '$'; + if (instanceVar == null) { + b.invokeVirtual(methodName, adaptType, null); + } else { + b.invokeVirtual (instanceVar.getType(), methodName, adaptType, null); + } } fromType = adaptType; } Label done = b.createLabel(); - if (!fromType.isPrimitive()) { + if (mode == INITIAL_VERSION) { + CodeBuilderUtil.initialVersion(b, fromType, 1); + } else if (mode == INCREMENT_VERSION) { + CodeBuilderUtil.incrementVersion(b, fromType); + } else if (!fromType.isPrimitive()) { // Handle case where property value is null. b.dup(); Label notNull = b.createLabel(); @@ -1695,65 +1881,9 @@ class JDBCStorableGenerator { } } - /** - * @param b builder to receive statement - * @param withVersion when false, ignore any version property - * @return version property number, or -1 if none - */ - private int createInsertStatement(StringBuilder b, boolean withVersion) { - b.append("INSERT INTO "); - b.append(mInfo.getQualifiedTableName()); - b.append(" ("); - - JDBCStorableProperty versionProperty = null; - int versionPropNumber = -1; - - int ordinal = 0; - int propNumber = -1; - for (JDBCStorableProperty property : mInfo.getAllProperties().values()) { - propNumber++; - if (!property.isSelectable()) { - continue; - } - if (property.isVersion()) { - if (withVersion) { - versionProperty = property; - versionPropNumber = propNumber; - } - continue; - } - if (ordinal > 0) { - b.append(','); - } - b.append(property.getColumnName()); - ordinal++; - } - - // Insert version property at end, to make things easier for when the - // proper insert statement is selected. - if (versionProperty != null) { - if (ordinal > 0) { - b.append(','); - } - b.append(versionProperty.getColumnName()); - ordinal++; - } - - b.append(") VALUES ("); - - for (int i=0; i 0) { - b.append(','); - } - b.append('?'); - } - - b.append(')'); - - return versionPropNumber; - } - - private Map, Class> generateLobLoaders() { + private Map, Class> generateLobLoaders() + throws SupportException + { Map, Class> lobLoaderMap = new IdentityHashMap, Class>(); @@ -1789,7 +1919,9 @@ class JDBCStorableGenerator { * * @param loaderType either JDBCBlobLoader or JDBCClobLoader */ - private Class generateLobLoader(JDBCStorableProperty property, Class loaderType) { + private Class generateLobLoader(JDBCStorableProperty property, Class loaderType) + throws SupportException + { ClassInjector ci = ClassInjector.create (property.getEnclosingType().getName(), mParentClassLoader); diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableInfo.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableInfo.java index 6275c09..109725c 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableInfo.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableInfo.java @@ -71,5 +71,11 @@ public interface JDBCStorableInfo extends StorableInfo { Map> getDataProperties(); + /** + * Returns auto-increment properties which are primary key members. The map + * should almost always be empty or contain one property. + */ + Map> getIdentityProperties(); + JDBCStorableProperty getVersionProperty(); } diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableIntrospector.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableIntrospector.java index 987c809..aa17f11 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableIntrospector.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableIntrospector.java @@ -25,7 +25,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.LinkedHashMap; -import java.util.List; import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -56,7 +55,6 @@ import com.amazon.carbonado.RepositoryException; import com.amazon.carbonado.Storable; import com.amazon.carbonado.SupportException; -import com.amazon.carbonado.info.Direction; import com.amazon.carbonado.info.OrderedProperty; import com.amazon.carbonado.info.StorableInfo; import com.amazon.carbonado.info.StorableIntrospector; @@ -66,8 +64,6 @@ import com.amazon.carbonado.info.StorableProperty; import com.amazon.carbonado.info.StorablePropertyAdapter; import com.amazon.carbonado.info.StorablePropertyConstraint; -import com.amazon.carbonado.spi.IndexInfoImpl; - /** * Provides additional metadata for a {@link Storable} type needed by * JDBCRepository. The storable type must match to a table in an external @@ -335,7 +331,31 @@ public class JDBCStorableIntrospector extends StorableIntrospector { } } + boolean autoIncrement = mainProperty.isAutomatic(); + if (autoIncrement) { + // Need to execute a little query to check if column is + // auto-increment or not. This information is not available in + // the regular database metadata prior to jdk1.6. + + PreparedStatement ps = con.prepareStatement + ("SELECT " + columnInfo.columnName + + " FROM " + tableName + + " WHERE 1=0"); + + try { + ResultSet rs = ps.executeQuery(); + try { + autoIncrement = rs.getMetaData().isAutoIncrement(1); + } finally { + rs.close(); + } + } finally { + ps.close(); + } + } + jProperty = new JProperty(mainProperty, columnInfo, + autoIncrement, accessInfo.mResultSetGet, accessInfo.mPreparedStatementSet, accessInfo.getAdapter()); @@ -974,6 +994,7 @@ public class JDBCStorableIntrospector extends StorableIntrospector { private transient Map> mPrimaryKeyProperties; private transient Map> mDataProperties; + private transient Map> mIdentityProperties; private transient JDBCStorableProperty mVersionProperty; JInfo(StorableInfo mainInfo, @@ -1104,6 +1125,23 @@ public class JDBCStorableIntrospector extends StorableIntrospector { return mDataProperties; } + public Map> getIdentityProperties() { + if (mIdentityProperties == null) { + Map> idProps = + new LinkedHashMap>(1); + for (Map.Entry> entry : + getPrimaryKeyProperties().entrySet()) + { + JDBCStorableProperty property = entry.getValue(); + if (property.isAutoIncrement()) { + idProps.put(entry.getKey(), property); + } + } + mIdentityProperties = Collections.unmodifiableMap(idProps); + } + return mIdentityProperties; + } + public JDBCStorableProperty getVersionProperty() { if (mVersionProperty == null) { for (JDBCStorableProperty property : mAllProperties.values()) { @@ -1133,6 +1171,7 @@ public class JDBCStorableIntrospector extends StorableIntrospector { private final Integer mDecimalDigits; private final Integer mCharOctetLength; private final Integer mOrdinalPosition; + private final boolean mAutoIncrement; private JDBCStorableProperty[] mInternal; private JDBCStorableProperty[] mExternal; @@ -1140,9 +1179,13 @@ public class JDBCStorableIntrospector extends StorableIntrospector { /** * Join properties need to be filled in later. */ - JProperty(StorableProperty mainProperty, ColumnInfo columnInfo, - Method resultSetGet, Method preparedStatementSet, - StorablePropertyAdapter adapter) { + JProperty(StorableProperty mainProperty, + ColumnInfo columnInfo, + boolean autoIncrement, + Method resultSetGet, + Method preparedStatementSet, + StorablePropertyAdapter adapter) + { mMainProperty = mainProperty; mColumnName = columnInfo.columnName; mDataType = columnInfo.dataType; @@ -1154,6 +1197,7 @@ public class JDBCStorableIntrospector extends StorableIntrospector { mDecimalDigits = columnInfo.decimalDigits; mCharOctetLength = columnInfo.charOctetLength; mOrdinalPosition = columnInfo.ordinalPosition; + mAutoIncrement = autoIncrement; } JProperty(StorableProperty mainProperty) { @@ -1168,6 +1212,7 @@ public class JDBCStorableIntrospector extends StorableIntrospector { mDecimalDigits = null; mCharOctetLength = null; mOrdinalPosition = null; + mAutoIncrement = false; } public String getName() { @@ -1258,6 +1303,10 @@ public class JDBCStorableIntrospector extends StorableIntrospector { return mMainProperty.getSequenceName(); } + public boolean isAutomatic() { + return mMainProperty.isAutomatic(); + } + public boolean isVersion() { return mMainProperty.isVersion(); } @@ -1279,6 +1328,10 @@ public class JDBCStorableIntrospector extends StorableIntrospector { return mColumnName != null && !isJoin(); } + public boolean isAutoIncrement() { + return mAutoIncrement; + } + public String getColumnName() { return mColumnName; } diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableProperty.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableProperty.java index b900e69..9102c17 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableProperty.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableProperty.java @@ -45,6 +45,12 @@ public interface JDBCStorableProperty extends StorableProper */ boolean isSelectable(); + /** + * Returns true if property is declared as @Automatic and column is + * designated as auto-increment. + */ + boolean isAutoIncrement(); + /** * Returns the table column for this property. * diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java index 6034227..12d6790 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java @@ -18,20 +18,17 @@ package com.amazon.carbonado.repo.jdbc; +import java.io.IOException; +import java.lang.reflect.Method; +import java.lang.reflect.UndeclaredThrowableException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; - -import java.io.IOException; - -import java.lang.reflect.Method; -import java.lang.reflect.UndeclaredThrowableException; - import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.LinkedHashMap; import org.apache.commons.logging.LogFactory; @@ -47,7 +44,6 @@ import com.amazon.carbonado.SupportException; import com.amazon.carbonado.Transaction; import com.amazon.carbonado.Trigger; import com.amazon.carbonado.capability.IndexInfo; - import com.amazon.carbonado.filter.AndFilter; import com.amazon.carbonado.filter.Filter; import com.amazon.carbonado.filter.FilterValues; @@ -55,26 +51,22 @@ import com.amazon.carbonado.filter.OrFilter; import com.amazon.carbonado.filter.PropertyFilter; import com.amazon.carbonado.filter.RelOp; import com.amazon.carbonado.filter.Visitor; - import com.amazon.carbonado.info.ChainedProperty; import com.amazon.carbonado.info.Direction; import com.amazon.carbonado.info.OrderedProperty; import com.amazon.carbonado.info.StorableProperty; import com.amazon.carbonado.info.StorablePropertyAdapter; - -import com.amazon.carbonado.spi.SequenceValueProducer; -import com.amazon.carbonado.spi.TriggerManager; - -import com.amazon.carbonado.util.QuickConstructorGenerator; - import com.amazon.carbonado.qe.AbstractQueryExecutor; import com.amazon.carbonado.qe.OrderingList; import com.amazon.carbonado.qe.QueryExecutor; -import com.amazon.carbonado.qe.QueryExecutorFactory; import com.amazon.carbonado.qe.QueryExecutorCache; +import com.amazon.carbonado.qe.QueryExecutorFactory; import com.amazon.carbonado.qe.QueryFactory; import com.amazon.carbonado.qe.StandardQuery; import com.amazon.carbonado.qe.StandardQueryFactory; +import com.amazon.carbonado.sequence.SequenceValueProducer; +import com.amazon.carbonado.spi.TriggerManager; +import com.amazon.carbonado.util.QuickConstructorGenerator; /** * @@ -95,7 +87,7 @@ class JDBCStorage extends StandardQueryFactory final TriggerManager mTriggerManager; - JDBCStorage(JDBCRepository repository, JDBCStorableInfo info) + JDBCStorage(JDBCRepository repository, JDBCStorableInfo info, boolean autoVersioning) throws SupportException, RepositoryException { super(info.getStorableType()); @@ -103,7 +95,9 @@ class JDBCStorage extends StandardQueryFactory mSupportStrategy = repository.getSupportStrategy(); mInfo = info; - Class generatedStorableClass = JDBCStorableGenerator.getGeneratedClass(info); + Class generatedStorableClass = JDBCStorableGenerator + .getGeneratedClass(info, autoVersioning); + mInstanceFactory = QuickConstructorGenerator .getInstance(generatedStorableClass, InstanceFactory.class); @@ -134,6 +128,33 @@ class JDBCStorage extends StandardQueryFactory return property != null && property.isSupported(); } + public void truncate() throws PersistException { + String truncateFormat = mSupportStrategy.getTruncateTableStatement(); + + try { + if (truncateFormat == null || mTriggerManager.getDeleteTrigger() != null) { + query().deleteAll(); + return; + } + + Connection con = mRepository.getConnection(); + try { + java.sql.Statement st = con.createStatement(); + try { + st.execute(String.format(truncateFormat, mInfo.getQualifiedTableName())); + } finally { + st.close(); + } + } catch (SQLException e) { + throw JDBCExceptionTransformer.getInstance().toPersistException(e); + } finally { + mRepository.yieldConnection(con); + } + } catch (FetchException e) { + throw e.toPersistException(); + } + } + public boolean addTrigger(Trigger trigger) { return mTriggerManager.addTrigger(trigger); } @@ -147,7 +168,11 @@ class JDBCStorage extends StandardQueryFactory } public SequenceValueProducer getSequenceValueProducer(String name) throws PersistException { - return mSupportStrategy.getSequenceValueProducer(name); + try { + return mRepository.getSequenceValueProducer(name); + } catch (RepositoryException e) { + throw e.toPersistException(); + } } public Trigger getInsertTrigger() { @@ -172,7 +197,7 @@ class JDBCStorage extends StandardQueryFactory if (jblob != null) { try { - JDBCTransaction txn = mRepository.openTransactionManager().getTxn(); + JDBCTransaction txn = mRepository.localTxnManager().getTxn(); if (txn != null) { txn.register(jblob); } @@ -194,7 +219,7 @@ class JDBCStorage extends StandardQueryFactory if (jclob != null) { try { - JDBCTransaction txn = mRepository.openTransactionManager().getTxn(); + JDBCTransaction txn = mRepository.localTxnManager().getTxn(); if (txn != null) { txn.register(jclob); } @@ -456,13 +481,27 @@ class JDBCStorage extends StandardQueryFactory } public Cursor fetch(FilterValues values) throws FetchException { - boolean forUpdate = mRepository.openTransactionManager().isForUpdate(); + boolean forUpdate = mRepository.localTxnManager().isForUpdate(); Connection con = mRepository.getConnection(); try { - PreparedStatement ps = con.prepareStatement(prepareSelect(values, forUpdate)); + boolean scrollInsensitiveReadOnly = + mRepository.supportsScrollInsensitiveReadOnly(); + + PreparedStatement ps; + + if (scrollInsensitiveReadOnly) { + // Can support fast skipping. + ps = con.prepareStatement + (prepareSelect(values, forUpdate), + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); + } else { + // Can't support fast skipping. + ps = con.prepareStatement(prepareSelect(values, forUpdate)); + } + try { setParameters(ps, values); - return new JDBCCursor(JDBCStorage.this, con, ps); + return new JDBCCursor(JDBCStorage.this, con, ps, scrollInsensitiveReadOnly); } catch (Exception e) { // in case of exception, close statement try { @@ -519,7 +558,7 @@ class JDBCStorage extends StandardQueryFactory throws IOException { indent(app, indentLevel); - boolean forUpdate = mRepository.openTransactionManager().isForUpdate(); + boolean forUpdate = mRepository.localTxnManager().isForUpdate(); app.append(prepareSelect(values, forUpdate)); app.append('\n'); return true; @@ -529,7 +568,7 @@ class JDBCStorage extends StandardQueryFactory throws IOException { try { - boolean forUpdate = mRepository.openTransactionManager().isForUpdate(); + boolean forUpdate = mRepository.localTxnManager().isForUpdate(); String statement = prepareSelect(values, forUpdate); return mRepository.getSupportStrategy().printPlan(app, indentLevel, statement); } catch (FetchException e) { diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCSupportStrategy.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCSupportStrategy.java index f9ca221..8a27c3d 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCSupportStrategy.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCSupportStrategy.java @@ -18,8 +18,8 @@ package com.amazon.carbonado.repo.jdbc; -import java.io.InputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.io.Writer; @@ -27,15 +27,16 @@ import java.io.Writer; import java.sql.PreparedStatement; import java.sql.SQLException; -import java.util.HashMap; -import java.util.Map; - import com.amazon.carbonado.FetchException; import com.amazon.carbonado.PersistException; +import com.amazon.carbonado.RepositoryException; +import com.amazon.carbonado.UnsupportedTypeException; -import com.amazon.carbonado.util.ThrowUnchecked; +import com.amazon.carbonado.sequence.SequenceValueGenerator; +import com.amazon.carbonado.sequence.SequenceValueProducer; +import com.amazon.carbonado.sequence.StoredSequence; -import com.amazon.carbonado.spi.SequenceValueProducer; +import com.amazon.carbonado.util.ThrowUnchecked; /** * Allows database product specific features to be abstracted. @@ -77,11 +78,12 @@ class JDBCSupportStrategy { return new JDBCSupportStrategy(repo); } - + protected final JDBCRepository mRepo; - - private Map mSequences; - + private String mSequenceSelectStatement; + private boolean mForceStoredSequence = false; + private String mTruncateTableStatement; + protected JDBCSupportStrategy(JDBCRepository repo) { mRepo = repo; } @@ -89,38 +91,30 @@ class JDBCSupportStrategy { JDBCExceptionTransformer createExceptionTransformer() { return new JDBCExceptionTransformer(); } - - /** - * Utility method used by generated storables to get sequence values during - * an insert operation. - * - * @param sequenceName name of sequence - * @throws PersistException instead of FetchException since this code is - * called during an insert operation - */ - synchronized SequenceValueProducer getSequenceValueProducer(String sequenceName) - throws PersistException + + SequenceValueProducer createSequenceValueProducer(String name) + throws RepositoryException { - SequenceValueProducer sequence = mSequences == null ? null : mSequences.get(sequenceName); - - if (sequence == null) { - String sequenceQuery = createSequenceQuery(sequenceName); - sequence = new JDBCSequenceValueProducer(mRepo, sequenceQuery); - if (mSequences == null) { - mSequences = new HashMap(); + if (name == null) { + throw new IllegalArgumentException("Sequence name is null"); + } + String format = getSequenceSelectStatement(); + if (format != null && format.length() > 0 && !isForceStoredSequence()) { + String sequenceQuery = String.format(format, name); + return new JDBCSequenceValueProducer(mRepo, sequenceQuery); + } else { + try { + return new SequenceValueGenerator(mRepo, name); + } catch (UnsupportedTypeException e) { + if (e.getType() != StoredSequence.class) { + throw e; + } + throw new PersistException + ("Native sequences are not currently supported for \"" + + mRepo.getDatabaseProductName() + "\". Instead, define a table named " + + "CARBONADO_SEQUENCE as required by " + StoredSequence.class.getName() + '.'); } - mSequences.put(sequenceName, sequence); } - - return sequence; - } - - String createSequenceQuery(String sequenceName) { - throw new UnsupportedOperationException - ("Sequences are not supported by default JDBC support strategy. " + - "If \"" + mRepo.getDatabaseProductName() + "\" actually does support sequences, " + - "then a custom support strategy might be available in a separate jar. " + - "If so, simply add it to your classpath."); } /** @@ -240,4 +234,39 @@ class JDBCSupportStrategy { { return false; } + + /** + * Returns the optional sequence select statement format. The format is + * printf style with 1 string parameter which can be passed through {@link + * String#format(String, Object[])} to create a sql statement. + * @return + */ + String getSequenceSelectStatement() { + return mSequenceSelectStatement; + } + + void setSequenceSelectStatement(String sequenceSelectStatement) { + mSequenceSelectStatement = sequenceSelectStatement; + } + + boolean isForceStoredSequence() { + return mForceStoredSequence; + } + + void setForceStoredSequence(boolean forceStoredSequence) { + mForceStoredSequence = forceStoredSequence; + } + + /** + * Return the optional truncate table statement format. The format is + * printf style with 1 string parameter which can be passed through {@link + * String#format(String, Object[])} to create a sql statement. + */ + String getTruncateTableStatement() { + return mTruncateTableStatement; + } + + void setTruncateTableStatement(String truncateTableStatement) { + mTruncateTableStatement = truncateTableStatement; + } } diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingCallableStatement.java b/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingCallableStatement.java index 3e5156b..140fbd0 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingCallableStatement.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingCallableStatement.java @@ -386,7 +386,6 @@ class LoggingCallableStatement extends LoggingPreparedStatement implements Calla return cs().getURL(parameterName); } - /* JDK 1.6 features public RowId getRowId(int parameterIndex) throws SQLException { return cs().getRowId(parameterIndex); } @@ -542,7 +541,6 @@ class LoggingCallableStatement extends LoggingPreparedStatement implements Calla { cs().setNClob(parameterName, reader); } - */ private CallableStatement cs() { return (CallableStatement) mStatement; diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingConnection.java b/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingConnection.java index 47d734e..3a2a6cb 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingConnection.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingConnection.java @@ -225,7 +225,6 @@ class LoggingConnection implements Connection { mCon.releaseSavepoint(savepoint); } - /* JDK 1.6 features public Clob createClob() throws SQLException { return mCon.createClob(); } @@ -277,5 +276,4 @@ class LoggingConnection implements Connection { public boolean isWrapperFor(Class iface) throws SQLException { throw new UnsupportedOperationException(); } - */ } diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingDataSource.java b/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingDataSource.java index 74c120e..ab6a2f9 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingDataSource.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingDataSource.java @@ -91,7 +91,14 @@ public class LoggingDataSource implements DataSource { return mDataSource.getLoginTimeout(); } - /* JDK 1.6 features + public void close() throws SQLException { + mLog.debug("DataSource.close()"); + if (!JDBCRepository.closeDataSource(mDataSource)) { + mLog.debug("DataSource doesn't have a close method: " + + mDataSource.getClass().getName()); + } + } + public T unwrap(Class iface) throws SQLException { throw new UnsupportedOperationException(); } @@ -99,5 +106,4 @@ public class LoggingDataSource implements DataSource { public boolean isWrapperFor(Class iface) throws SQLException { throw new UnsupportedOperationException(); } - */ } diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingPreparedStatement.java b/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingPreparedStatement.java index f5b355f..6fbfa9b 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingPreparedStatement.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingPreparedStatement.java @@ -249,7 +249,6 @@ class LoggingPreparedStatement extends LoggingStatement implements PreparedState return ps().getParameterMetaData(); } - /* JDK 1.6 features public void setRowId(int parameterIndex, RowId x) throws SQLException { ps().setRowId(parameterIndex, x); } @@ -339,7 +338,6 @@ class LoggingPreparedStatement extends LoggingStatement implements PreparedState public void setNClob(int parameterIndex, java.io.Reader reader) throws SQLException { ps().setNClob(parameterIndex, reader); } - */ private PreparedStatement ps() { return (PreparedStatement) mStatement; diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingStatement.java b/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingStatement.java index c6b1f82..5dc8d5e 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingStatement.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/LoggingStatement.java @@ -198,7 +198,6 @@ class LoggingStatement implements Statement { return mStatement.getResultSetHoldability(); } - /* JDK 1.6 features public boolean isClosed() throws SQLException { return mStatement.isClosed(); } @@ -218,5 +217,4 @@ class LoggingStatement implements Statement { public boolean isWrapperFor(Class iface) throws SQLException { throw new UnsupportedOperationException(); } - */ } diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/MysqlSupportStrategy.java b/src/main/java/com/amazon/carbonado/repo/jdbc/MysqlSupportStrategy.java index d9b5579..27f813f 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/MysqlSupportStrategy.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/MysqlSupportStrategy.java @@ -24,8 +24,12 @@ package com.amazon.carbonado.repo.jdbc; * @author Brian S O'Neill */ class MysqlSupportStrategy extends JDBCSupportStrategy { + private static final String TRUNCATE_STATEMENT = "TRUNCATE TABLE %s"; + protected MysqlSupportStrategy(JDBCRepository repo) { super(repo); + + setTruncateTableStatement(TRUNCATE_STATEMENT); } JDBCExceptionTransformer createExceptionTransformer() { diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/OracleSupportStrategy.java b/src/main/java/com/amazon/carbonado/repo/jdbc/OracleSupportStrategy.java index 3efa40d..356d8d2 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/OracleSupportStrategy.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/OracleSupportStrategy.java @@ -18,29 +18,28 @@ package com.amazon.carbonado.repo.jdbc; -import java.io.IOException; - import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; - -import java.sql.Connection; import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.Statement; import java.sql.SQLException; import org.apache.commons.logging.LogFactory; import com.amazon.carbonado.FetchException; import com.amazon.carbonado.PersistException; -import com.amazon.carbonado.Transaction; /** * * * @author Brian S O'Neill + * @author bcastill */ class OracleSupportStrategy extends JDBCSupportStrategy { + + private static final String DEFAULT_SEQUENCE_SELECT_STATEMENT = "SELECT %s.NEXTVAL FROM DUAL"; + + private static final String TRUNCATE_STATEMENT = "TRUNCATE TABLE %s"; + private static final int LOB_CHUNK_LIMIT = 4000; private static final String PLAN_TABLE_NAME = "TEMP_CARBONADO_PLAN_TABLE"; @@ -60,6 +59,11 @@ class OracleSupportStrategy extends JDBCSupportStrategy { protected OracleSupportStrategy(JDBCRepository repo) { super(repo); + // Set printf style format to create sequence query + setSequenceSelectStatement(DEFAULT_SEQUENCE_SELECT_STATEMENT); + + setTruncateTableStatement(TRUNCATE_STATEMENT); + // Access all the custom oracle.sql.BLOB methods via reflection. { Method blob_empty_lob = null; @@ -126,13 +130,6 @@ class OracleSupportStrategy extends JDBCSupportStrategy { return new OracleExceptionTransformer(); } - @Override - String createSequenceQuery(String sequenceName) { - return new StringBuilder(25 + sequenceName.length()) - .append("SELECT ").append(sequenceName).append(".NEXTVAL FROM DUAL") - .toString(); - } - @Override JDBCBlob convertBlob(java.sql.Blob blob, JDBCBlobLoader loader) { return blob == null ? null : new OracleBlob(mRepo, blob, loader); diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/SimpleDataSource.java b/src/main/java/com/amazon/carbonado/repo/jdbc/SimpleDataSource.java index 431c820..ee355de 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/SimpleDataSource.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/SimpleDataSource.java @@ -87,7 +87,9 @@ public class SimpleDataSource implements DataSource { return 0; } - /* JDK 1.6 features + public void close() throws SQLException { + } + public T unwrap(Class iface) throws SQLException { throw new UnsupportedOperationException(); } @@ -95,5 +97,4 @@ public class SimpleDataSource implements DataSource { public boolean isWrapperFor(Class iface) throws SQLException { throw new UnsupportedOperationException(); } - */ } -- cgit v1.2.3