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. --- .../carbonado/repo/indexed/IndexEntryAccessor.java | 1 - .../repo/indexed/IndexEntryGenerator.java | 1 - .../carbonado/repo/indexed/IndexedRepository.java | 17 +- .../repo/indexed/IndexedRepositoryBuilder.java | 21 +- .../carbonado/repo/indexed/IndexedStorage.java | 92 +++- .../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 +- .../carbonado/repo/logging/LoggingRepository.java | 8 +- .../carbonado/repo/logging/LoggingStorage.java | 8 + .../repo/replicated/ReplicatedRepository.java | 8 +- .../repo/replicated/ReplicatedStorage.java | 6 + .../amazon/carbonado/repo/sleepycat/BDBCursor.java | 6 +- .../carbonado/repo/sleepycat/BDBRepository.java | 451 ++++------------- .../repo/sleepycat/BDBRepositoryBuilder.java | 25 +- .../carbonado/repo/sleepycat/BDBStorage.java | 100 +++- 33 files changed, 1193 insertions(+), 922 deletions(-) (limited to 'src/main/java/com/amazon/carbonado/repo') diff --git a/src/main/java/com/amazon/carbonado/repo/indexed/IndexEntryAccessor.java b/src/main/java/com/amazon/carbonado/repo/indexed/IndexEntryAccessor.java index b107a6e..0c1e6b8 100644 --- a/src/main/java/com/amazon/carbonado/repo/indexed/IndexEntryAccessor.java +++ b/src/main/java/com/amazon/carbonado/repo/indexed/IndexEntryAccessor.java @@ -20,7 +20,6 @@ package com.amazon.carbonado.repo.indexed; import java.util.Comparator; -import com.amazon.carbonado.FetchException; import com.amazon.carbonado.Storable; import com.amazon.carbonado.Storage; diff --git a/src/main/java/com/amazon/carbonado/repo/indexed/IndexEntryGenerator.java b/src/main/java/com/amazon/carbonado/repo/indexed/IndexEntryGenerator.java index 4059ac3..a126224 100644 --- a/src/main/java/com/amazon/carbonado/repo/indexed/IndexEntryGenerator.java +++ b/src/main/java/com/amazon/carbonado/repo/indexed/IndexEntryGenerator.java @@ -24,7 +24,6 @@ import java.util.WeakHashMap; import java.lang.ref.Reference; import java.lang.ref.SoftReference; -import com.amazon.carbonado.FetchException; import com.amazon.carbonado.Storable; import com.amazon.carbonado.SupportException; import com.amazon.carbonado.info.StorableIndex; diff --git a/src/main/java/com/amazon/carbonado/repo/indexed/IndexedRepository.java b/src/main/java/com/amazon/carbonado/repo/indexed/IndexedRepository.java index d558950..097185a 100644 --- a/src/main/java/com/amazon/carbonado/repo/indexed/IndexedRepository.java +++ b/src/main/java/com/amazon/carbonado/repo/indexed/IndexedRepository.java @@ -43,7 +43,7 @@ import com.amazon.carbonado.info.StorableIntrospector; import com.amazon.carbonado.qe.RepositoryAccess; import com.amazon.carbonado.qe.StorageAccess; -import com.amazon.carbonado.spi.StorageCollection; +import com.amazon.carbonado.spi.StoragePool; /** * Wraps another repository in order to make it support indexes. The wrapped @@ -62,20 +62,23 @@ class IndexedRepository implements Repository, private final String mName; private final boolean mIndexRepairEnabled; private final double mIndexThrottle; - private final StorageCollection mStorages; + private final boolean mAllClustered; + private final StoragePool mStoragePool; IndexedRepository(AtomicReference rootRef, String name, Repository repository, boolean indexRepairEnabled, - double indexThrottle) + double indexThrottle, + boolean allClustered) { mRootRef = rootRef; mRepository = repository; mName = name; mIndexRepairEnabled = indexRepairEnabled; mIndexThrottle = indexThrottle; + mAllClustered = allClustered; - mStorages = new StorageCollection() { + mStoragePool = new StoragePool() { protected Storage createStorage(Class type) throws RepositoryException { @@ -112,7 +115,7 @@ class IndexedRepository implements Repository, public Storage storageFor(Class type) throws MalformedTypeException, SupportException, RepositoryException { - return mStorages.storageFor(type); + return mStoragePool.get(type); } public Transaction enterTransaction() { @@ -221,4 +224,8 @@ class IndexedRepository implements Repository, double getIndexRepairThrottle() { return mIndexThrottle; } + + boolean isAllClustered() { + return mAllClustered; + } } diff --git a/src/main/java/com/amazon/carbonado/repo/indexed/IndexedRepositoryBuilder.java b/src/main/java/com/amazon/carbonado/repo/indexed/IndexedRepositoryBuilder.java index 4cd4afb..e7ad8ed 100644 --- a/src/main/java/com/amazon/carbonado/repo/indexed/IndexedRepositoryBuilder.java +++ b/src/main/java/com/amazon/carbonado/repo/indexed/IndexedRepositoryBuilder.java @@ -49,6 +49,7 @@ public class IndexedRepositoryBuilder extends AbstractRepositoryBuilder { private RepositoryBuilder mRepoBuilder; private boolean mIndexRepairEnabled = true; private double mIndexThrottle = 1.0; + private boolean mAllClustered; public IndexedRepositoryBuilder() { } @@ -75,7 +76,8 @@ public class IndexedRepositoryBuilder extends AbstractRepositoryBuilder { Repository repo = new IndexedRepository(rootRef, getName(), wrapped, isIndexRepairEnabled(), - getIndexRepairThrottle()); + getIndexRepairThrottle(), + isAllClustered()); rootRef.set(repo); return repo; } @@ -167,6 +169,23 @@ public class IndexedRepositoryBuilder extends AbstractRepositoryBuilder { mIndexThrottle = desiredSpeed; } + /** + * Returns true if all indexes should be identified as clustered. This + * affects how indexes are selected by the query analyzer. + */ + public boolean isAllClustered() { + return mAllClustered; + } + + /** + * When all indexes are identified as clustered, the query analyzer treats + * all indexes as performing equally well. This is suitable for indexing + * repositories that never read from a slow storage medium. + */ + public void setAllClustered(boolean clustered) { + mAllClustered = clustered; + } + public void errorCheck(Collection messages) throws ConfigurationException { super.errorCheck(messages); if (null == getWrappedRepository()) { diff --git a/src/main/java/com/amazon/carbonado/repo/indexed/IndexedStorage.java b/src/main/java/com/amazon/carbonado/repo/indexed/IndexedStorage.java index 7b66f50..fe0cfe8 100644 --- a/src/main/java/com/amazon/carbonado/repo/indexed/IndexedStorage.java +++ b/src/main/java/com/amazon/carbonado/repo/indexed/IndexedStorage.java @@ -30,6 +30,7 @@ import org.apache.commons.logging.LogFactory; import com.amazon.carbonado.Cursor; import com.amazon.carbonado.FetchException; import com.amazon.carbonado.IsolationLevel; +import com.amazon.carbonado.PersistException; import com.amazon.carbonado.Query; import com.amazon.carbonado.RepositoryException; import com.amazon.carbonado.Storable; @@ -188,6 +189,9 @@ class IndexedStorage implements Storage, StorageAccess // Assume index is malformed, so ignore it. continue; } + if (mRepository.isAllClustered()) { + freeIndex = freeIndex.clustered(true); + } mAllIndexInfoMap.put(freeIndex, ii); freeIndexSet.add(freeIndex); } @@ -210,6 +214,10 @@ class IndexedStorage implements Storage, StorageAccess // Add the indexes we get for free. queryableIndexSet.addAll(freeIndexSet); + + if (mRepository.isAllClustered()) { + queryableIndexSet.markClustered(true); + } } // The set of indexes that should be kept up-to-date. If index repair @@ -313,6 +321,36 @@ class IndexedStorage implements Storage, StorageAccess return mQueryEngine.query(filter); } + public void truncate() throws PersistException { + hasManagedIndexes: { + for (IndexInfo info : mAllIndexInfoMap.values()) { + if (info instanceof ManagedIndex) { + break hasManagedIndexes; + } + } + + // No managed indexes, so nothing special to do. + mMasterStorage.truncate(); + return; + } + + Transaction txn = mRepository.enterTransaction(); + try { + mMasterStorage.truncate(); + + // Now truncate the indexes. + for (IndexInfo info : mAllIndexInfoMap.values()) { + if (info instanceof ManagedIndex) { + ((ManagedIndex) info).getIndexEntryStorage().truncate(); + } + } + + txn.commit(); + } finally { + txn.exit(); + } + } + public boolean addTrigger(Trigger trigger) { return mMasterStorage.addTrigger(trigger); } @@ -372,9 +410,6 @@ class IndexedStorage implements Storage, StorageAccess } } - // FIXME: sort buffer should be on repository access. Also, create abstract - // repository access that creates the correct merge sort buffer. And more: - // create capability for managing merge sort buffers. return new MergeSortBuffer(mRootStorage); } @@ -521,20 +556,24 @@ class IndexedStorage implements Storage, StorageAccess { // Doesn't completely remove the index, but it should free up space. + double desiredSpeed = mRepository.getIndexRepairThrottle(); Throttle throttle = desiredSpeed < 1.0 ? new Throttle(POPULATE_THROTTLE_WINDOW) : null; - long totalDropped = 0; - while (true) { - Transaction txn = mRepository.getWrappedRepository() - .enterTopTransaction(IsolationLevel.READ_COMMITTED); - txn.setForUpdate(true); - try { - Cursor cursor = indexEntryStorage.query().fetch(); - if (!cursor.hasNext()) { - break; - } - int count = 0; + if (throttle == null) { + indexEntryStorage.truncate(); + } else { + long totalDropped = 0; + while (true) { + Transaction txn = mRepository.getWrappedRepository() + .enterTopTransaction(IsolationLevel.READ_COMMITTED); + txn.setForUpdate(true); + try { + Cursor cursor = indexEntryStorage.query().fetch(); + if (!cursor.hasNext()) { + break; + } + int count = 0; final long savedTotal = totalDropped; boolean anyFailure = false; try { @@ -544,26 +583,25 @@ class IndexedStorage implements Storage, StorageAccess } else { anyFailure = true; } + } + } finally { + cursor.close(); + } + txn.commit(); + if (log.isInfoEnabled()) { + log.info("Removed " + totalDropped + " index entries"); } - } finally { - cursor.close(); - } - txn.commit(); - if (log.isInfoEnabled()) { - log.info("Removed " + totalDropped + " index entries"); - } if (anyFailure && totalDropped <= savedTotal) { log.warn("No indexes removed in last batch. " + "Aborting index removal cleanup"); break; } - } catch (FetchException e) { - throw e.toPersistException(); - } finally { - txn.exit(); - } + } catch (FetchException e) { + throw e.toPersistException(); + } finally { + txn.exit(); + } - if (throttle != null) { try { throttle.throttle(desiredSpeed, POPULATE_THROTTLE_SLEEP_PRECISION); } catch (InterruptedException e) { 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(); } - */ } diff --git a/src/main/java/com/amazon/carbonado/repo/logging/LoggingRepository.java b/src/main/java/com/amazon/carbonado/repo/logging/LoggingRepository.java index 611df4f..89e6e21 100644 --- a/src/main/java/com/amazon/carbonado/repo/logging/LoggingRepository.java +++ b/src/main/java/com/amazon/carbonado/repo/logging/LoggingRepository.java @@ -31,7 +31,7 @@ import com.amazon.carbonado.TriggerFactory; import com.amazon.carbonado.capability.Capability; -import com.amazon.carbonado.spi.StorageCollection; +import com.amazon.carbonado.spi.StoragePool; /** * @@ -44,7 +44,7 @@ class LoggingRepository implements Repository, LogAccessCapability { private final Repository mRepo; private final Log mLog; - private final StorageCollection mStorages; + private final StoragePool mStoragePool; LoggingRepository(AtomicReference rootRef, Iterable triggerFactories, @@ -55,7 +55,7 @@ class LoggingRepository implements Repository, LogAccessCapability { mRepo = actual; mLog = log; - mStorages = new StorageCollection() { + mStoragePool = new StoragePool() { protected Storage createStorage(Class type) throws RepositoryException { @@ -71,7 +71,7 @@ class LoggingRepository implements Repository, LogAccessCapability { public Storage storageFor(Class type) throws SupportException, RepositoryException { - return mStorages.storageFor(type); + return mStoragePool.get(type); } public Transaction enterTransaction() { diff --git a/src/main/java/com/amazon/carbonado/repo/logging/LoggingStorage.java b/src/main/java/com/amazon/carbonado/repo/logging/LoggingStorage.java index 3f807c2..de1118f 100644 --- a/src/main/java/com/amazon/carbonado/repo/logging/LoggingStorage.java +++ b/src/main/java/com/amazon/carbonado/repo/logging/LoggingStorage.java @@ -41,6 +41,14 @@ class LoggingStorage extends WrappedStorage { mRepo = repo; } + public void truncate() throws PersistException { + Log log = mRepo.getLog(); + if (log.isEnabled()) { + log.write("Storage.truncate() on " + getStorableType().getClass()); + } + super.truncate(); + } + protected S wrap(S storable) { return super.wrap(storable); } diff --git a/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedRepository.java b/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedRepository.java index 3b215bc..2c73200 100644 --- a/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedRepository.java +++ b/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedRepository.java @@ -53,7 +53,7 @@ import com.amazon.carbonado.info.Direction; import com.amazon.carbonado.info.StorableInfo; import com.amazon.carbonado.info.StorableIntrospector; -import com.amazon.carbonado.spi.StorageCollection; +import com.amazon.carbonado.spi.StoragePool; import com.amazon.carbonado.spi.TransactionPair; import com.amazon.carbonado.util.Throttle; @@ -140,7 +140,7 @@ class ReplicatedRepository private Repository mReplicaRepository; private Repository mMasterRepository; - private final StorageCollection mStorages; + private final StoragePool mStoragePool; ReplicatedRepository(String aName, Repository aReplicaRepository, @@ -149,7 +149,7 @@ class ReplicatedRepository mReplicaRepository = aReplicaRepository; mMasterRepository = aMasterRepository; - mStorages = new StorageCollection() { + mStoragePool = new StoragePool() { protected Storage createStorage(Class type) throws SupportException, RepositoryException { @@ -199,7 +199,7 @@ class ReplicatedRepository public Storage storageFor(Class type) throws MalformedTypeException, SupportException, RepositoryException { - return mStorages.storageFor(type); + return mStoragePool.get(type); } public Transaction enterTransaction() { diff --git a/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedStorage.java b/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedStorage.java index 9711784..5d3f47b 100644 --- a/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedStorage.java +++ b/src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedStorage.java @@ -21,6 +21,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.amazon.carbonado.FetchException; +import com.amazon.carbonado.PersistException; import com.amazon.carbonado.Query; import com.amazon.carbonado.Storage; import com.amazon.carbonado.Storable; @@ -105,6 +106,11 @@ class ReplicatedStorage implements Storage { return mReplicaStorage.query(filter); } + public void truncate() throws PersistException { + mMasterStorage.truncate(); + mReplicaStorage.truncate(); + } + public boolean addTrigger(Trigger trigger) { return mReplicationTrigger.addTrigger(trigger); } diff --git a/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBCursor.java b/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBCursor.java index 5d21dad..f16d2f7 100644 --- a/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBCursor.java +++ b/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBCursor.java @@ -25,6 +25,8 @@ import com.amazon.carbonado.Storable; import com.amazon.carbonado.raw.RawCursor; import com.amazon.carbonado.raw.RawUtil; +import com.amazon.carbonado.spi.TransactionManager; + /** * * @@ -33,7 +35,7 @@ import com.amazon.carbonado.raw.RawUtil; abstract class BDBCursor extends RawCursor { private static final byte[] NO_DATA = new byte[0]; - private final BDBTransactionManager mTxnMgr; + private final TransactionManager mTxnMgr; private final BDBStorage mStorage; /** * @param txnMgr @@ -48,7 +50,7 @@ abstract class BDBCursor extends RawCursor { * @throws ClassCastException if lock is not an object passed by * {@link BDBStorage#openCursor BDBStorage.openCursor} */ - protected BDBCursor(BDBTransactionManager txnMgr, + protected BDBCursor(TransactionManager txnMgr, byte[] startBound, boolean inclusiveStart, byte[] endBound, boolean inclusiveEnd, int maxPrefix, diff --git a/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBRepository.java b/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBRepository.java index 6fc7cdd..0e2ed96 100644 --- a/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBRepository.java +++ b/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBRepository.java @@ -22,23 +22,15 @@ import java.io.File; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cojen.util.WeakIdentityMap; - import com.amazon.carbonado.ConfigurationException; import com.amazon.carbonado.Cursor; import com.amazon.carbonado.FetchException; import com.amazon.carbonado.IsolationLevel; -import com.amazon.carbonado.MalformedTypeException; import com.amazon.carbonado.PersistException; import com.amazon.carbonado.Repository; import com.amazon.carbonado.RepositoryException; @@ -63,11 +55,14 @@ import com.amazon.carbonado.qe.StorageAccess; import com.amazon.carbonado.raw.StorableCodecFactory; +import com.amazon.carbonado.sequence.SequenceCapability; +import com.amazon.carbonado.sequence.SequenceValueGenerator; +import com.amazon.carbonado.sequence.SequenceValueProducer; + +import com.amazon.carbonado.spi.AbstractRepository; import com.amazon.carbonado.spi.ExceptionTransformer; import com.amazon.carbonado.spi.LobEngine; -import com.amazon.carbonado.spi.SequenceValueGenerator; -import com.amazon.carbonado.spi.SequenceValueProducer; -import com.amazon.carbonado.spi.StorageCollection; +import com.amazon.carbonado.spi.TransactionManager; /** * Repository implementation backed by a Berkeley DB. Data is encoded in the @@ -78,42 +73,31 @@ import com.amazon.carbonado.spi.StorageCollection; * @author Brian S O'Neill * @author Vidya Iyer * @author Nicole Deflaux + * @author bcastill */ -abstract class BDBRepository +abstract class BDBRepository extends AbstractRepository implements Repository, RepositoryAccess, IndexInfoCapability, CheckpointCapability, EnvironmentCapability, ShutdownCapability, - StorableInfoCapability + StorableInfoCapability, + SequenceCapability { private final Log mLog = LogFactory.getLog(getClass()); - private final String mName; private final boolean mIsMaster; final Iterable mTriggerFactories; private final AtomicReference mRootRef; private final StorableCodecFactory mStorableCodecFactory; private final ExceptionTransformer mExTransformer; - private final StorageCollection mStorages; - private final Map mSequences; - private final ThreadLocal> mCurrentTxnMgr; - - private final Lock mShutdownLock; - private final Condition mShutdownCondition; - private int mShutdownBlockerCount; - - // Weakly tracks all BDBTransactionManager instances for shutdown hook. - private final Map, ?> mAllTxnMgrs; Checkpointer mCheckpointer; DeadlockDetector mDeadlockDetector; - private ShutdownHook mShutdownHook; - final Runnable mPreShutdownHook; - final Runnable mPostShutdownHook; - volatile boolean mHasShutdown; + private final Runnable mPreShutdownHook; + private final Runnable mPostShutdownHook; private final Object mInitialDBConfig; private final BDBRepositoryBuilder.DatabaseHook mDatabaseHook; @@ -126,8 +110,6 @@ abstract class BDBRepository final File mEnvHome; final String mSingleFileName; - private final String mMergeSortTempDir; - private LayoutFactory mLayoutFactory; private LobEngine mLobEngine; @@ -146,40 +128,19 @@ abstract class BDBRepository ExceptionTransformer exTransformer) throws ConfigurationException { + super(builder.getName()); + builder.assertReady(); if (exTransformer == null) { throw new IllegalArgumentException("Exception transformer must not be null"); } - mName = builder.getName(); mIsMaster = builder.isMaster(); mTriggerFactories = builder.getTriggerFactories(); mRootRef = rootRef; mExTransformer = exTransformer; - mStorages = new StorageCollection() { - protected Storage createStorage(Class type) - throws RepositoryException - { - lockoutShutdown(); - try { - try { - return BDBRepository.this.createStorage(type); - } catch (Exception e) { - throw toRepositoryException(e); - } - } finally { - unlockoutShutdown(); - } - } - }; - - mSequences = new ConcurrentHashMap(); - mCurrentTxnMgr = new ThreadLocal>(); - mShutdownLock = new ReentrantLock(); - mShutdownCondition = mShutdownLock.newCondition(); - mAllTxnMgrs = new WeakIdentityMap(); mRunCheckpointer = !builder.getReadOnly() && builder.getRunCheckpointer(); mRunDeadlockDetector = builder.getRunDeadlockDetector(); mStorableCodecFactory = builder.getStorableCodecFactory(); @@ -191,40 +152,13 @@ abstract class BDBRepository mDataHome = builder.getDataHomeFile(); mEnvHome = builder.getEnvironmentHomeFile(); mSingleFileName = builder.getSingleFileName(); - // FIXME: see comments in builder - mMergeSortTempDir = null; //builder.getMergeSortTempDirectory(); - } - - public String getName() { - return mName; - } - - public BDBStorage storageFor(Class type) - throws MalformedTypeException, RepositoryException - { - return (BDBStorage) 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(); } @SuppressWarnings("unchecked") public C getCapability(Class capabilityType) { - if (capabilityType.isInstance(this)) { - return (C) this; + C cap = super.getCapability(capabilityType); + if (cap != null) { + return cap; } if (capabilityType == LayoutCapability.class) { return (C) mLayoutFactory; @@ -276,65 +210,6 @@ abstract class BDBRepository return StorableIntrospector.examine(type).getAllProperties().get(name) != null; } - public void close() { - shutdown(false); - } - - public boolean isAutoShutdownEnabled() { - return mShutdownHook != null; - } - - public void setAutoShutdownEnabled(boolean enabled) { - if (mShutdownHook == null) { - if (enabled) { - mShutdownHook = new ShutdownHook(this); - try { - Runtime.getRuntime().addShutdownHook(mShutdownHook); - } catch (IllegalStateException e) { - // Shutdown in progress, so immediately run hook. - mShutdownHook.run(); - } - } - } else { - if (!enabled) { - try { - Runtime.getRuntime().removeShutdownHook(mShutdownHook); - } catch (IllegalStateException e) { - // Shutdown in progress, hook is running. - } - mShutdownHook = null; - } - } - } - - public void shutdown() { - shutdown(true); - } - - private void shutdown(boolean suspendThreads) { - if (!mHasShutdown) { - // Since this repository is being closed before system shutdown, - // remove shutdown hook and run it now. - ShutdownHook hook = mShutdownHook; - if (hook != null) { - try { - Runtime.getRuntime().removeShutdownHook(hook); - } catch (IllegalStateException e) { - // Shutdown in progress, hook is running. - hook = null; - } - } else { - // If hook is null, auto-shutdown was disabled. Make a new - // instance to use, but don't register it. - hook = new ShutdownHook(this); - } - if (hook != null) { - hook.run(suspendThreads); - } - mHasShutdown = true; - } - } - /** * Suspend the checkpointer until the suspension time has expired or until * manually resumed. If a checkpoint is in progress, this method will block @@ -390,7 +265,7 @@ abstract class BDBRepository public StorageAccess storageAccessFor(Class type) throws RepositoryException { - return storageFor(type); + return (BDBStorage) storageFor(type); } @Override @@ -398,6 +273,78 @@ abstract class BDBRepository close(); } + protected void shutdownHook() { + // Run any external shutdown logic that needs to happen before the + // databases and the environment are actually closed + if (mPreShutdownHook != null) { + mPreShutdownHook.run(); + } + + // Close database handles. + for (Storage storage : allStorage()) { + try { + if (storage instanceof BDBStorage) { + ((BDBStorage) storage).close(); + } + } catch (Throwable e) { + getLog().error(null, e); + } + } + + // Wait for checkpointer to finish. + if (mCheckpointer != null) { + mCheckpointer.interrupt(); + try { + mCheckpointer.join(); + } catch (InterruptedException e) { + } + } + + // Wait for deadlock detector to finish. + if (mDeadlockDetector != null) { + mDeadlockDetector.interrupt(); + try { + mDeadlockDetector.join(); + } catch (InterruptedException e) { + } + } + + // Close environment. + try { + env_close(); + } catch (Throwable e) { + getLog().error(null, e); + } + + if (mPostShutdownHook != null) { + mPostShutdownHook.run(); + } + } + + protected Log getLog() { + return mLog; + } + + protected TransactionManager createTransactionManager() { + return new BDBTransactionManager(mExTransformer, this); + } + + protected Storage createStorage(Class type) + throws RepositoryException + { + try { + return createBDBStorage(type); + } catch (Exception e) { + throw toRepositoryException(e); + } + } + + protected SequenceValueProducer createSequenceValueProducer(String name) + throws RepositoryException + { + return new SequenceValueGenerator(BDBRepository.this, name); + } + /** * @see com.amazon.carbonado.spi.RepositoryBuilder#isMaster */ @@ -417,40 +364,6 @@ abstract class BDBRepository return dbName; } - String getMergeSortTempDirectory() { - if (mMergeSortTempDir != null) { - new File(mMergeSortTempDir).mkdirs(); - } - return mMergeSortTempDir; - } - - SequenceValueProducer getSequenceValueProducer(String name) throws PersistException { - SequenceValueGenerator producer = mSequences.get(name); - if (producer == null) { - lockoutShutdown(); - try { - producer = mSequences.get(name); - if (producer == null) { - Repository metaRepo = getRootRepository(); - try { - producer = new SequenceValueGenerator(metaRepo, name); - } catch (RepositoryException e) { - throw toPersistException(e); - } - mSequences.put(name, producer); - } - return producer; - } finally { - unlockoutShutdown(); - } - } - return producer; - } - - Log getLog() { - return mLog; - } - StorableCodecFactory getStorableCodecFactory() { return mStorableCodecFactory; } @@ -464,7 +377,7 @@ abstract class BDBRepository LobEngine getLobEngine() throws RepositoryException { if (mLobEngine == null) { - mLobEngine = new LobEngine(getRootRepository()); + mLobEngine = new LobEngine(this, getRootRepository()); } return mLobEngine; } @@ -555,7 +468,7 @@ abstract class BDBRepository */ abstract void env_close() throws Exception; - abstract BDBStorage createStorage(Class type) + abstract BDBStorage createBDBStorage(Class type) throws Exception; FetchException toFetchException(Throwable e) { @@ -571,70 +484,11 @@ abstract class BDBRepository } /** - * Returns the thread-local BDBTransactionManager instance, creating it if - * needed. - */ - BDBTransactionManager openTransactionManager() { - BDBTransactionManager txnMgr = mCurrentTxnMgr.get(); - if (txnMgr == null) { - lockoutShutdown(); - try { - txnMgr = new BDBTransactionManager(mExTransformer, this); - mCurrentTxnMgr.set(txnMgr); - mAllTxnMgrs.put(txnMgr, null); - } finally { - unlockoutShutdown(); - } - } - return txnMgr; - } - - /** - * Call to prevent shutdown hook from running. Be sure to call - * unlockoutShutdown afterwards. - */ - private void lockoutShutdown() { - mShutdownLock.lock(); - try { - mShutdownBlockerCount++; - } finally { - mShutdownLock.unlock(); - } - } - - /** - * Only call this to release lockoutShutdown. + * Returns the thread-local BDBTransactionManager, creating it if needed. */ - private void unlockoutShutdown() { - mShutdownLock.lock(); - try { - if (--mShutdownBlockerCount == 0) { - mShutdownCondition.signalAll(); - } - } finally { - mShutdownLock.unlock(); - } - } - - /** - * Only to be called by shutdown hook itself. - */ - void lockForShutdown() { - mShutdownLock.lock(); - while (mShutdownBlockerCount > 0) { - try { - mShutdownCondition.await(); - } catch (InterruptedException e) { - mLog.warn("Ignoring interruption for shutdown"); - } - } - } - - /** - * Only to be called by shutdown hook itself. - */ - void unlockForShutdown() { - mShutdownLock.unlock(); + // Provides access to transaction manager from other classes. + TransactionManager localTxnManager() { + return localTransactionManager(); } /** @@ -667,7 +521,8 @@ abstract class BDBRepository * since last checkpoint */ Checkpointer(BDBRepository repository, long sleepInterval, int kBytes, int minutes) { - super("BDBRepository checkpointer (" + repository.getName() + ')'); + super(repository.getClass().getSimpleName() + " checkpointer (" + + repository.getName() + ')'); setDaemon(true); mRepository = new WeakReference(repository); mSleepInterval = sleepInterval; @@ -800,7 +655,8 @@ abstract class BDBRepository * @param sleepInterval milliseconds to sleep before running deadlock detection */ DeadlockDetector(BDBRepository repository, long sleepInterval) { - super("BDBRepository deadlock detector (" + repository.getName() + ')'); + super(repository.getClass().getSimpleName() + " deadlock detector (" + + repository.getName() + ')'); setDaemon(true); mRepository = new WeakReference(repository); mSleepInterval = sleepInterval; @@ -831,111 +687,4 @@ abstract class BDBRepository } } } - - private static class ShutdownHook extends Thread { - private final WeakReference> mRepository; - - ShutdownHook(BDBRepository repository) { - super("BDBRepository shutdown (" + repository.getName() + ')'); - mRepository = new WeakReference>(repository); - } - - public void run() { - run(true); - } - - public void run(boolean suspendThreads) { - BDBRepository repository = mRepository.get(); - if (repository == null) { - return; - } - - repository.getLog().info("Closing repository \"" + repository.getName() + '"'); - - try { - doShutdown(repository, suspendThreads); - } finally { - repository.mHasShutdown = true; - mRepository.clear(); - repository.getLog().info - ("Finished closing repository \"" + repository.getName() + '"'); - } - } - - private void doShutdown(BDBRepository repository, boolean suspendThreads) { - repository.lockForShutdown(); - try { - // Return unused sequence values. - for (SequenceValueGenerator generator : repository.mSequences.values()) { - try { - generator.returnReservedValues(); - } catch (RepositoryException e) { - repository.getLog().warn(null, e); - } - } - - // Close transactions and cursors. - for (BDBTransactionManager txnMgr : repository.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) { - repository.getLog().error(null, e); - } - } - - // Run any external shutdown logic that needs to - // happen before the databases and the environment are - // actually closed - if (repository.mPreShutdownHook != null) { - repository.mPreShutdownHook.run(); - } - - // Close database handles. - for (Storage storage : repository.mStorages.allStorage()) { - try { - ((BDBStorage) storage).close(); - } catch (Throwable e) { - repository.getLog().error(null, e); - } - } - - // Wait for checkpointer to finish. - if (repository.mCheckpointer != null) { - repository.mCheckpointer.interrupt(); - try { - repository.mCheckpointer.join(); - } catch (InterruptedException e) { - } - } - - // Wait for deadlock detector to finish. - if (repository.mDeadlockDetector != null) { - repository.mDeadlockDetector.interrupt(); - try { - repository.mDeadlockDetector.join(); - } catch (InterruptedException e) { - } - } - - // Close environment. - try { - repository.env_close(); - } catch (Throwable e) { - repository.getLog().error(null, e); - } - - if (repository.mPostShutdownHook != null) { - repository.mPostShutdownHook.run(); - } - } finally { - repository.unlockForShutdown(); - } - } - } } diff --git a/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBRepositoryBuilder.java b/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBRepositoryBuilder.java index 1f60bfd..fa24624 100644 --- a/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBRepositoryBuilder.java +++ b/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBRepositoryBuilder.java @@ -64,6 +64,7 @@ import com.amazon.carbonado.ConfigurationException; *
  • {@link com.amazon.carbonado.capability.StorableInfoCapability StorableInfoCapability} *
  • {@link com.amazon.carbonado.capability.ShutdownCapability ShutdownCapability} *
  • {@link com.amazon.carbonado.layout.LayoutCapability LayoutCapability} + *
  • {@link com.amazon.carbonado.sequence.SequenceCapability SequenceCapability} *
  • {@link CheckpointCapability CheckpointCapability} *
  • {@link EnvironmentCapability EnvironmentCapability} * @@ -82,7 +83,6 @@ public class BDBRepositoryBuilder extends AbstractRepositoryBuilder { private boolean mIsMaster = true; private BDBProduct mProduct = DEFAULT_PRODUCT; private File mEnvHome; - private String mMergeSortTempDir; private File mDataHome; private String mSingleFileName; private boolean mIndexSupport = true; @@ -282,29 +282,6 @@ public class BDBRepositoryBuilder extends AbstractRepositoryBuilder { return getDataHomeFile().getPath(); } - /** - * Sets the directory to use for creating temporary files needed for merge - * sorting. If null or not specified, the default temporary file directory is used. - * - * @param tempDir directory to store temp files for merge sorting, or null - * for default - */ - /* FIXME: use common config somehow, since indexed repo needs this too - public void setMergeSortTempDirectory(String tempDir) { - mMergeSortTempDir = tempDir; - } - */ - - /** - * Returns the directory to use for creating temporary files needed for - * merge sorting. If null, the default temporary file directory is used. - */ - /* FIXME: use common config somehow, since indexed repo needs this too - public String getMergeSortTempDirectory() { - return mMergeSortTempDir; - } - */ - /** * Specify that all BDB databases should reside in one file, except for log * files and caches. The filename is relative to the environment home, diff --git a/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBStorage.java b/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBStorage.java index d8a3066..82ba789 100644 --- a/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBStorage.java +++ b/src/main/java/com/amazon/carbonado/repo/sleepycat/BDBStorage.java @@ -71,10 +71,12 @@ import com.amazon.carbonado.raw.StorableCodecFactory; import com.amazon.carbonado.raw.RawSupport; import com.amazon.carbonado.raw.RawUtil; +import com.amazon.carbonado.sequence.SequenceValueProducer; + import com.amazon.carbonado.spi.IndexInfoImpl; import com.amazon.carbonado.spi.LobEngine; -import com.amazon.carbonado.spi.SequenceValueProducer; import com.amazon.carbonado.spi.StorableIndexSet; +import com.amazon.carbonado.spi.TransactionManager; import com.amazon.carbonado.spi.TriggerManager; /** @@ -154,7 +156,7 @@ abstract class BDBStorage implements Storage, Storag } public S prepare() { - return mStorableCodec.instantiate(mRawSupport); + return mStorableCodec.instantiate(); } public Query query() throws FetchException { @@ -169,6 +171,48 @@ abstract class BDBStorage implements Storage, Storag return mQueryEngine.query(filter); } + public void truncate() throws PersistException { + if (mTriggerManager.getDeleteTrigger() != null || mRepository.mSingleFileName != null) { + final int batchSize = 100; + + while (true) { + Transaction txn = mRepository.enterTransaction(IsolationLevel.READ_COMMITTED); + txn.setForUpdate(true); + try { + Cursor cursor = query().fetch(); + if (!cursor.hasNext()) { + break; + } + int count = 0; + do { + cursor.next().tryDelete(); + } while (count++ < batchSize && cursor.hasNext()); + txn.commit(); + } catch (FetchException e) { + throw e.toPersistException(); + } finally { + txn.exit(); + } + + return; + } + } + + TransactionManager txnMgr = localTxnManager(); + + // Lock out shutdown task. + txnMgr.getLock().lock(); + try { + try { + db_truncate(txnMgr.getTxn()); + } catch (Exception e) { + throw toPersistException(e); + } + } finally { + txnMgr.getLock().unlock(); + } + } + public boolean addTrigger(Trigger trigger) { return mTriggerManager.addTrigger(trigger); } @@ -222,9 +266,6 @@ abstract class BDBStorage implements Storage, Storag } } - // FIXME: sort buffer should be on repository access. Also, create abstract - // repository access that creates the correct merge sort buffer. And more: - // create capability for managing merge sort buffers. return new MergeSortBuffer(mRootStorage); } @@ -262,7 +303,7 @@ abstract class BDBStorage implements Storage, Storag boolean reverseOrder) throws FetchException { - BDBTransactionManager txnMgr = openTransactionManager(); + TransactionManager txnMgr = localTxnManager(); if (reverseRange) { { @@ -367,6 +408,12 @@ abstract class BDBStorage implements Storage, Storag * differences between the current index set and the desired index set. */ protected void open(boolean readOnly) throws RepositoryException { + open(readOnly, null, true); + } + + protected void open(boolean readOnly, Txn openTxn, boolean installTriggers) + throws RepositoryException + { final Layout layout = getLayout(); StorableInfo info = StorableIntrospector.examine(getStorableType()); @@ -386,11 +433,11 @@ abstract class BDBStorage implements Storage, Storag boolean isPrimaryEmpty; try { - BDBTransactionManager txnMgr = mRepository.openTransactionManager(); + TransactionManager txnMgr = mRepository.localTxnManager(); // Lock out shutdown task. txnMgr.getLock().lock(); try { - primaryDatabase = env_openPrimaryDatabase(null, databaseName); + primaryDatabase = env_openPrimaryDatabase(openTxn, databaseName); primaryInfo = registerPrimaryDatabase(readOnly, layout); isPrimaryEmpty = db_isEmpty(null, primaryDatabase, txnMgr.isForUpdate()); } finally { @@ -446,7 +493,8 @@ abstract class BDBStorage implements Storage, Storag try { mStorableCodec = codecFactory - .createCodec(getStorableType(), pkIndex, mRepository.isMaster(), layout); + .createCodec(getStorableType(), pkIndex, mRepository.isMaster(), layout, + mRawSupport); } catch (SupportException e) { // We've opened the database prematurely, since type isn't // supported by encoding strategy. Close it down and unregister. @@ -468,12 +516,14 @@ abstract class BDBStorage implements Storage, Storag mQueryEngine = new QueryEngine(getStorableType(), mRepository); - // Don't install automatic triggers until we're completely ready. - mTriggerManager.addTriggers(getStorableType(), mRepository.mTriggerFactories); + if (installTriggers) { + // Don't install automatic triggers until we're completely ready. + mTriggerManager.addTriggers(getStorableType(), mRepository.mTriggerFactories); + } } protected S instantiate(byte[] key, byte[] value) throws FetchException { - return mStorableCodec.instantiate(mRawSupport, key, value); + return mStorableCodec.instantiate(key, value); } protected CompactionCapability.Result compact() throws RepositoryException { @@ -493,7 +543,7 @@ abstract class BDBStorage implements Storage, Storag } try { - Txn txn = mRepository.openTransactionManager().getTxn(); + Txn txn = mRepository.localTxnManager().getTxn(); return db_compact(txn, mPrimaryDatabase, start, end); } catch (Exception e) { throw mRepository.toRepositoryException(e); @@ -568,7 +618,7 @@ abstract class BDBStorage implements Storage, Storag * @param database database to use */ protected abstract BDBCursor openCursor - (BDBTransactionManager txnMgr, + (TransactionManager txnMgr, byte[] startBound, boolean inclusiveStart, byte[] endBound, boolean inclusiveEnd, int maxPrefix, @@ -588,8 +638,8 @@ abstract class BDBStorage implements Storage, Storag return mRepository.toRepositoryException(e); } - BDBTransactionManager openTransactionManager() { - return mRepository.openTransactionManager(); + TransactionManager localTxnManager() { + return mRepository.localTxnManager(); } /** @@ -647,7 +697,7 @@ abstract class BDBStorage implements Storage, Storag * prevent threads from starting work that will likely fail along the way. */ void checkClosed() throws FetchException { - BDBTransactionManager txnMgr = openTransactionManager(); + TransactionManager txnMgr = localTxnManager(); // Lock out shutdown task. txnMgr.getLock().lock(); @@ -668,7 +718,7 @@ abstract class BDBStorage implements Storage, Storag } void close() throws Exception { - BDBTransactionManager txnMgr = mRepository.openTransactionManager(); + TransactionManager txnMgr = mRepository.localTxnManager(); txnMgr.getLock().lock(); try { if (mPrimaryDatabase != null) { @@ -811,7 +861,7 @@ abstract class BDBStorage implements Storage, Storag } public byte[] tryLoad(byte[] key) throws FetchException { - BDBTransactionManager txnMgr = mStorage.openTransactionManager(); + TransactionManager txnMgr = mStorage.localTxnManager(); byte[] result; // Lock out shutdown task. txnMgr.getLock().lock(); @@ -834,7 +884,7 @@ abstract class BDBStorage implements Storage, Storag } public boolean tryInsert(S storable, byte[] key, byte[] value) throws PersistException { - BDBTransactionManager txnMgr = mStorage.openTransactionManager(); + TransactionManager txnMgr = mStorage.localTxnManager(); Object result; // Lock out shutdown task. txnMgr.getLock().lock(); @@ -857,7 +907,7 @@ abstract class BDBStorage implements Storage, Storag } public void store(S storable, byte[] key, byte[] value) throws PersistException { - BDBTransactionManager txnMgr = mStorage.openTransactionManager(); + TransactionManager txnMgr = mStorage.localTxnManager(); // Lock out shutdown task. txnMgr.getLock().lock(); try { @@ -874,7 +924,7 @@ abstract class BDBStorage implements Storage, Storag } public boolean tryDelete(byte[] key) throws PersistException { - BDBTransactionManager txnMgr = mStorage.openTransactionManager(); + TransactionManager txnMgr = mStorage.localTxnManager(); // Lock out shutdown task. txnMgr.getLock().lock(); try { @@ -907,7 +957,11 @@ abstract class BDBStorage implements Storage, Storag public SequenceValueProducer getSequenceValueProducer(String name) throws PersistException { - return mStorage.mRepository.getSequenceValueProducer(name); + try { + return mStorage.mRepository.getSequenceValueProducer(name); + } catch (RepositoryException e) { + throw e.toPersistException(); + } } public Trigger getInsertTrigger() { -- cgit v1.2.3