summaryrefslogtreecommitdiff
path: root/src/main/java/com/amazon/carbonado/repo
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/amazon/carbonado/repo')
-rw-r--r--src/main/java/com/amazon/carbonado/repo/indexed/IndexEntryAccessor.java1
-rw-r--r--src/main/java/com/amazon/carbonado/repo/indexed/IndexEntryGenerator.java1
-rw-r--r--src/main/java/com/amazon/carbonado/repo/indexed/IndexedRepository.java17
-rw-r--r--src/main/java/com/amazon/carbonado/repo/indexed/IndexedRepositoryBuilder.java21
-rw-r--r--src/main/java/com/amazon/carbonado/repo/indexed/IndexedStorage.java92
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/JDBCBlob.java4
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/JDBCClob.java4
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/JDBCCursor.java53
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepository.java325
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/JDBCRepositoryBuilder.java89
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/JDBCSequenceValueProducer.java7
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableGenerator.java560
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableInfo.java6
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableIntrospector.java67
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableProperty.java6
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java91
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/JDBCSupportStrategy.java107
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/LoggingCallableStatement.java2
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/LoggingConnection.java2
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/LoggingDataSource.java10
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/LoggingPreparedStatement.java2
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/LoggingStatement.java2
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/MysqlSupportStrategy.java4
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/OracleSupportStrategy.java25
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/SimpleDataSource.java5
-rw-r--r--src/main/java/com/amazon/carbonado/repo/logging/LoggingRepository.java8
-rw-r--r--src/main/java/com/amazon/carbonado/repo/logging/LoggingStorage.java8
-rw-r--r--src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedRepository.java8
-rw-r--r--src/main/java/com/amazon/carbonado/repo/replicated/ReplicatedStorage.java6
-rw-r--r--src/main/java/com/amazon/carbonado/repo/sleepycat/BDBCursor.java6
-rw-r--r--src/main/java/com/amazon/carbonado/repo/sleepycat/BDBRepository.java451
-rw-r--r--src/main/java/com/amazon/carbonado/repo/sleepycat/BDBRepositoryBuilder.java25
-rw-r--r--src/main/java/com/amazon/carbonado/repo/sleepycat/BDBStorage.java100
33 files changed, 1193 insertions, 922 deletions
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<Repository> 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 <S extends Storable> Storage<S> createStorage(Class<S> type)
throws RepositoryException
{
@@ -112,7 +115,7 @@ class IndexedRepository implements Repository,
public <S extends Storable> Storage<S> storageFor(Class<S> 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<String> 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<S extends Storable> implements Storage<S>, StorageAccess<S>
// 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<S extends Storable> implements Storage<S>, StorageAccess<S>
// 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<S extends Storable> implements Storage<S>, StorageAccess<S>
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<? super S> trigger) {
return mMasterStorage.addTrigger(trigger);
}
@@ -372,9 +410,6 @@ class IndexedStorage<S extends Storable> implements Storage<S>, StorageAccess<S>
}
}
- // 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<S>(mRootStorage);
}
@@ -521,20 +556,24 @@ class IndexedStorage<S extends Storable> implements Storage<S>, StorageAccess<S>
{
// 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<? extends Storable> 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<? extends Storable> 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<S extends Storable> implements Storage<S>, StorageAccess<S>
} 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<S extends Storable> extends AbstractCursor<S> {
private final JDBCStorage<S> 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<S> 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<S extends Storable> extends AbstractCursor<S> {
}
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<JDBCTransaction>
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<TriggerFactory> mTriggerFactories;
private final AtomicReference<Repository> 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<String, Boolean> mAutoVersioningMap;
// Track all open connections so that they can be closed when this
// repository is closed.
private Map<Connection, Object> mOpenConnections;
-
- private final ThreadLocal<JDBCTransactionManager> mCurrentTxnMgr;
-
- // Weakly tracks all JDBCTransactionManager instances for shutdown.
- private final Map<JDBCTransactionManager, ?> 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<Repository> rootRef,
String name, boolean isMaster,
Iterable<TriggerFactory> triggerFactories,
- DataSource dataSource, String catalog, String schema)
+ DataSource dataSource, boolean dataSourceClose,
+ String catalog, String schema,
+ Map<String, Boolean> 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 <S extends Storable> Storage<S> createStorage(Class<S> type)
- throws RepositoryException
- {
- // Lock on mAllTxnMgrs to prevent databases from being opened during shutdown.
- synchronized (mAllTxnMgrs) {
- JDBCStorableInfo<S> info = examineStorable(type);
- if (!info.isSupported()) {
- throw new UnsupportedTypeException("Independent type not supported", type);
- }
- return new JDBCStorage<S>(JDBCRepository.this, info);
- }
- }
- };
+ mAutoVersioningMap = autoVersioningMap;
mOpenConnections = new IdentityHashMap<Connection, Object>();
- mCurrentTxnMgr = new ThreadLocal<JDBCTransactionManager>();
- 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 <S extends Storable> Storage<S> storageFor(Class<S> 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 extends Capability> C getCapability(Class<C> capabilityType) {
- if (capabilityType.isInstance(this)) {
- return (C) this;
- }
- return null;
- }
-
public <S extends Storable> IndexInfo[] getIndexInfo(Class<S> 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<String> names = new ArrayList<String>();
- 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<JDBCTransaction> createTransactionManager() {
+ return new JDBCTransactionManager(this);
+ }
+
+ protected <S extends Storable> Storage<S> createStorage(Class<S> type)
+ throws RepositoryException
+ {
+ JDBCStorableInfo<S> 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<S>(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<JDBCTransaction> 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;
* <li>{@link com.amazon.carbonado.capability.IndexInfoCapability IndexInfoCapability}
* <li>{@link com.amazon.carbonado.capability.StorableInfoCapability StorableInfoCapability}
* <li>{@link com.amazon.carbonado.capability.ShutdownCapability ShutdownCapability}
+ * <li>{@link com.amazon.carbonado.sequence.SequenceCapability SequenceCapability}
* <li>{@link JDBCConnectionCapability JDBCConnectionCapability}
* </ul>
*
* @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<String, Boolean> 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;
}
@@ -138,6 +149,22 @@ public class JDBCRepositoryBuilder extends AbstractRepositoryBuilder {
}
/**
+ * 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.
*
* @see LoggingDataSource
@@ -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<String, Boolean>();
+ }
+ mAutoVersioningMap.put(className, enabled);
+ }
+
+ private Map<String, Boolean> getAutoVersioningMap() {
+ if (mAutoVersioningMap == null) {
+ return null;
+ }
+ return new HashMap<String, Boolean>(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<String> 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<S extends Storable> {
// Initial StringBuilder capactity for update statement.
private static final int INITIAL_UPDATE_BUFFER_SIZE = 100;
- private static final Map<Class<?>, Class<? extends Storable>> 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<Object, Class<? extends Storable>> cCache;
static {
cCache = new SoftValuedHashMap();
}
- static <S extends Storable> Class<? extends S> getGeneratedClass(JDBCStorableInfo<S> info)
+ static <S extends Storable> Class<? extends S> getGeneratedClass(JDBCStorableInfo<S> info,
+ boolean autoVersioning)
throws SupportException
{
- Class<S> type = info.getStorableType();
+ Object key = KeyFactory.createKey(new Object[] {info, autoVersioning});
+
synchronized (cCache) {
- Class<? extends S> generatedClass = (Class<? extends S>) cCache.get(type);
+ Class<? extends S> generatedClass = (Class<? extends S>) cCache.get(key);
if (generatedClass != null) {
return generatedClass;
}
- generatedClass = new JDBCStorableGenerator<S>(info).generateAndInjectClass();
- cCache.put(type, generatedClass);
+ generatedClass =
+ new JDBCStorableGenerator<S>(info, autoVersioning).generateAndInjectClass();
+ cCache.put(key, generatedClass);
return generatedClass;
}
}
private final Class<S> mStorableType;
private final JDBCStorableInfo<S> mInfo;
+ private final boolean mAutoVersioning;
private final Map<String, ? extends JDBCStorableProperty<S>> mAllProperties;
private final ClassLoader mParentClassLoader;
private final ClassInjector mClassInjector;
private final ClassFile mClassFile;
- private JDBCStorableGenerator(JDBCStorableInfo<S> info) throws SupportException {
+ private JDBCStorableGenerator(JDBCStorableInfo<S> info, boolean autoVersioning)
+ throws SupportException
+ {
mStorableType = info.getStorableType();
mInfo = info;
+ mAutoVersioning = autoVersioning;
mAllProperties = mInfo.getAllProperties();
EnumSet<MasterFeature> 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<? extends S> abstractClass =
MasterStorableGenerator.getAbstractClass(mStorableType, features);
@@ -127,7 +142,7 @@ class JDBCStorableGenerator<S extends Storable> {
mClassFile.setTarget("1.5");
}
- private Class<? extends S> generateAndInjectClass() {
+ private Class<? extends S> 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<S extends Storable> {
// 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<ordinal; i++) {
+ if (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<JDBCStorableProperty<S>> 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<S> versionProperty = null;
-
// Gather all Lob properties to track if a post-insert update is required.
Map<JDBCStorableProperty<S>, Integer> lobIndexMap = findLobs();
LocalVariable lobArrayVar = null;
@@ -481,43 +608,85 @@ class JDBCStorableGenerator<S extends Storable> {
}
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<S> 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<S extends Storable> {
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<S extends Storable> {
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<S extends Storable> {
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<S extends Storable> {
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<S> 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<S extends Storable> {
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<S extends Storable> {
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<S extends Storable> {
for (JDBCStorableProperty<S> 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);
@@ -924,6 +1093,13 @@ class JDBCStorableGenerator<S extends Storable> {
}
/**
+ * 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<S extends Storable> {
LocalVariable psVar,
LocalVariable jdbcRepoVar,
LocalVariable instanceVar)
+ throws SupportException
{
final TypeDesc superType = TypeDesc.forClass(mClassFile.getSuperClassName());
final Iterable<? extends JDBCStorableProperty<?>> properties =
@@ -1108,28 +1285,19 @@ class JDBCStorableGenerator<S extends Storable> {
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<S extends Storable> {
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<S extends Storable> {
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<S extends Storable> {
continue;
}
- Label skipProperty = b.createLabel();
+ Label nextProperty = b.createLabel();
final TypeDesc propertyType = TypeDesc.forClass(property.getType());
@@ -1200,12 +1368,12 @@ class JDBCStorableGenerator<S extends Storable> {
// 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<S extends Storable> {
* 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<S extends Storable> {
* @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<S extends Storable> {
}
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<S extends Storable> {
}
}
- /**
- * @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<ordinal; i++) {
- if (i > 0) {
- b.append(',');
- }
- b.append('?');
- }
-
- b.append(')');
-
- return versionPropNumber;
- }
-
- private Map<JDBCStorableProperty<S>, Class<?>> generateLobLoaders() {
+ private Map<JDBCStorableProperty<S>, Class<?>> generateLobLoaders()
+ throws SupportException
+ {
Map<JDBCStorableProperty<S>, Class<?>> lobLoaderMap =
new IdentityHashMap<JDBCStorableProperty<S>, Class<?>>();
@@ -1789,7 +1919,9 @@ class JDBCStorableGenerator<S extends Storable> {
*
* @param loaderType either JDBCBlobLoader or JDBCClobLoader
*/
- private Class<?> generateLobLoader(JDBCStorableProperty<S> property, Class<?> loaderType) {
+ private Class<?> generateLobLoader(JDBCStorableProperty<S> 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<S extends Storable> extends StorableInfo<S> {
Map<String, JDBCStorableProperty<S>> getDataProperties();
+ /**
+ * Returns auto-increment properties which are primary key members. The map
+ * should almost always be empty or contain one property.
+ */
+ Map<String, JDBCStorableProperty<S>> getIdentityProperties();
+
JDBCStorableProperty<S> 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<S>(mainProperty, columnInfo,
+ autoIncrement,
accessInfo.mResultSetGet,
accessInfo.mPreparedStatementSet,
accessInfo.getAdapter());
@@ -974,6 +994,7 @@ public class JDBCStorableIntrospector extends StorableIntrospector {
private transient Map<String, JDBCStorableProperty<S>> mPrimaryKeyProperties;
private transient Map<String, JDBCStorableProperty<S>> mDataProperties;
+ private transient Map<String, JDBCStorableProperty<S>> mIdentityProperties;
private transient JDBCStorableProperty<S> mVersionProperty;
JInfo(StorableInfo<S> mainInfo,
@@ -1104,6 +1125,23 @@ public class JDBCStorableIntrospector extends StorableIntrospector {
return mDataProperties;
}
+ public Map<String, JDBCStorableProperty<S>> getIdentityProperties() {
+ if (mIdentityProperties == null) {
+ Map<String, JDBCStorableProperty<S>> idProps =
+ new LinkedHashMap<String, JDBCStorableProperty<S>>(1);
+ for (Map.Entry<String, JDBCStorableProperty<S>> entry :
+ getPrimaryKeyProperties().entrySet())
+ {
+ JDBCStorableProperty<S> property = entry.getValue();
+ if (property.isAutoIncrement()) {
+ idProps.put(entry.getKey(), property);
+ }
+ }
+ mIdentityProperties = Collections.unmodifiableMap(idProps);
+ }
+ return mIdentityProperties;
+ }
+
public JDBCStorableProperty<S> getVersionProperty() {
if (mVersionProperty == null) {
for (JDBCStorableProperty<S> 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<S>[] mInternal;
private JDBCStorableProperty<?>[] mExternal;
@@ -1140,9 +1179,13 @@ public class JDBCStorableIntrospector extends StorableIntrospector {
/**
* Join properties need to be filled in later.
*/
- JProperty(StorableProperty<S> mainProperty, ColumnInfo columnInfo,
- Method resultSetGet, Method preparedStatementSet,
- StorablePropertyAdapter adapter) {
+ JProperty(StorableProperty<S> 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<S> 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
@@ -46,6 +46,12 @@ public interface JDBCStorableProperty<S extends Storable> 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.
*
* @return null if property is unsupported
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<S extends Storable> extends StandardQueryFactory<S>
final TriggerManager<S> mTriggerManager;
- JDBCStorage(JDBCRepository repository, JDBCStorableInfo<S> info)
+ JDBCStorage(JDBCRepository repository, JDBCStorableInfo<S> info, boolean autoVersioning)
throws SupportException, RepositoryException
{
super(info.getStorableType());
@@ -103,7 +95,9 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S>
mSupportStrategy = repository.getSupportStrategy();
mInfo = info;
- Class<? extends S> generatedStorableClass = JDBCStorableGenerator.getGeneratedClass(info);
+ Class<? extends S> generatedStorableClass = JDBCStorableGenerator
+ .getGeneratedClass(info, autoVersioning);
+
mInstanceFactory = QuickConstructorGenerator
.getInstance(generatedStorableClass, InstanceFactory.class);
@@ -134,6 +128,33 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S>
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<? super S> trigger) {
return mTriggerManager.addTrigger(trigger);
}
@@ -147,7 +168,11 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S>
}
public SequenceValueProducer getSequenceValueProducer(String name) throws PersistException {
- return mSupportStrategy.getSequenceValueProducer(name);
+ try {
+ return mRepository.getSequenceValueProducer(name);
+ } catch (RepositoryException e) {
+ throw e.toPersistException();
+ }
}
public Trigger<? super S> getInsertTrigger() {
@@ -172,7 +197,7 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S>
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<S extends Storable> extends StandardQueryFactory<S>
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<S extends Storable> extends StandardQueryFactory<S>
}
public Cursor<S> fetch(FilterValues<S> 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<S>(JDBCStorage.this, con, ps);
+ return new JDBCCursor<S>(JDBCStorage.this, con, ps, scrollInsensitiveReadOnly);
} catch (Exception e) {
// in case of exception, close statement
try {
@@ -519,7 +558,7 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S>
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<S extends Storable> extends StandardQueryFactory<S>
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<String, SequenceValueProducer> 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<String, SequenceValueProducer>();
+ 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> T unwrap(Class<T> 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;
@@ -127,13 +131,6 @@ class OracleSupportStrategy extends JDBCSupportStrategy {
}
@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> T unwrap(Class<T> 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<Repository> rootRef,
Iterable<TriggerFactory> triggerFactories,
@@ -55,7 +55,7 @@ class LoggingRepository implements Repository, LogAccessCapability {
mRepo = actual;
mLog = log;
- mStorages = new StorageCollection() {
+ mStoragePool = new StoragePool() {
protected <S extends Storable> Storage<S> createStorage(Class<S> type)
throws RepositoryException
{
@@ -71,7 +71,7 @@ class LoggingRepository implements Repository, LogAccessCapability {
public <S extends Storable> Storage<S> storageFor(Class<S> 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<S extends Storable> extends WrappedStorage<S> {
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 <S extends Storable> Storage<S> createStorage(Class<S> type)
throws SupportException, RepositoryException
{
@@ -199,7 +199,7 @@ class ReplicatedRepository
public <S extends Storable> Storage<S> storageFor(Class<S> 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<S extends Storable> implements Storage<S> {
return mReplicaStorage.query(filter);
}
+ public void truncate() throws PersistException {
+ mMasterStorage.truncate();
+ mReplicaStorage.truncate();
+ }
+
public boolean addTrigger(Trigger<? super S> 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<Txn, S extends Storable> extends RawCursor<S> {
private static final byte[] NO_DATA = new byte[0];
- private final BDBTransactionManager<Txn> mTxnMgr;
+ private final TransactionManager<Txn> mTxnMgr;
private final BDBStorage<Txn, S> mStorage;
/**
* @param txnMgr
@@ -48,7 +50,7 @@ abstract class BDBCursor<Txn, S extends Storable> extends RawCursor<S> {
* @throws ClassCastException if lock is not an object passed by
* {@link BDBStorage#openCursor BDBStorage.openCursor}
*/
- protected BDBCursor(BDBTransactionManager<Txn> txnMgr,
+ protected BDBCursor(TransactionManager<Txn> 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<Txn>
+abstract class BDBRepository<Txn> extends AbstractRepository<Txn>
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<TriggerFactory> mTriggerFactories;
private final AtomicReference<Repository> mRootRef;
private final StorableCodecFactory mStorableCodecFactory;
private final ExceptionTransformer mExTransformer;
- private final StorageCollection mStorages;
- private final Map<String, SequenceValueGenerator> mSequences;
- private final ThreadLocal<BDBTransactionManager<Txn>> mCurrentTxnMgr;
-
- private final Lock mShutdownLock;
- private final Condition mShutdownCondition;
- private int mShutdownBlockerCount;
-
- // Weakly tracks all BDBTransactionManager instances for shutdown hook.
- private final Map<BDBTransactionManager<Txn>, ?> 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<Txn>
final File mEnvHome;
final String mSingleFileName;
- private final String mMergeSortTempDir;
-
private LayoutFactory mLayoutFactory;
private LobEngine mLobEngine;
@@ -146,40 +128,19 @@ abstract class BDBRepository<Txn>
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 <S extends Storable> Storage<S> createStorage(Class<S> type)
- throws RepositoryException
- {
- lockoutShutdown();
- try {
- try {
- return BDBRepository.this.createStorage(type);
- } catch (Exception e) {
- throw toRepositoryException(e);
- }
- } finally {
- unlockoutShutdown();
- }
- }
- };
-
- mSequences = new ConcurrentHashMap<String, SequenceValueGenerator>();
- mCurrentTxnMgr = new ThreadLocal<BDBTransactionManager<Txn>>();
- 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<Txn>
mDataHome = builder.getDataHomeFile();
mEnvHome = builder.getEnvironmentHomeFile();
mSingleFileName = builder.getSingleFileName();
- // FIXME: see comments in builder
- mMergeSortTempDir = null; //builder.getMergeSortTempDirectory();
- }
-
- public String getName() {
- return mName;
- }
-
- public <S extends Storable> BDBStorage<Txn, S> storageFor(Class<S> type)
- throws MalformedTypeException, RepositoryException
- {
- return (BDBStorage<Txn, S>) 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 extends Capability> C getCapability(Class<C> 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<Txn>
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<Txn>
public <S extends Storable> StorageAccess<S> storageAccessFor(Class<S> type)
throws RepositoryException
{
- return storageFor(type);
+ return (BDBStorage<Txn, S>) storageFor(type);
}
@Override
@@ -398,6 +273,78 @@ abstract class BDBRepository<Txn>
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 <S extends Storable> Storage createStorage(Class<S> 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<Txn>
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<Txn>
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<Txn>
*/
abstract void env_close() throws Exception;
- abstract <S extends Storable> BDBStorage<Txn, S> createStorage(Class<S> type)
+ abstract <S extends Storable> BDBStorage<Txn, S> createBDBStorage(Class<S> type)
throws Exception;
FetchException toFetchException(Throwable e) {
@@ -571,70 +484,11 @@ abstract class BDBRepository<Txn>
}
/**
- * Returns the thread-local BDBTransactionManager instance, creating it if
- * needed.
- */
- BDBTransactionManager<Txn> openTransactionManager() {
- BDBTransactionManager<Txn> txnMgr = mCurrentTxnMgr.get();
- if (txnMgr == null) {
- lockoutShutdown();
- try {
- txnMgr = new BDBTransactionManager<Txn>(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<Txn> localTxnManager() {
+ return localTransactionManager();
}
/**
@@ -667,7 +521,8 @@ abstract class BDBRepository<Txn>
* 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<BDBRepository>(repository);
mSleepInterval = sleepInterval;
@@ -800,7 +655,8 @@ abstract class BDBRepository<Txn>
* @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<BDBRepository>(repository);
mSleepInterval = sleepInterval;
@@ -831,111 +687,4 @@ abstract class BDBRepository<Txn>
}
}
}
-
- private static class ShutdownHook extends Thread {
- private final WeakReference<BDBRepository<?>> mRepository;
-
- ShutdownHook(BDBRepository repository) {
- super("BDBRepository shutdown (" + repository.getName() + ')');
- mRepository = new WeakReference<BDBRepository<?>>(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;
* <li>{@link com.amazon.carbonado.capability.StorableInfoCapability StorableInfoCapability}
* <li>{@link com.amazon.carbonado.capability.ShutdownCapability ShutdownCapability}
* <li>{@link com.amazon.carbonado.layout.LayoutCapability LayoutCapability}
+ * <li>{@link com.amazon.carbonado.sequence.SequenceCapability SequenceCapability}
* <li>{@link CheckpointCapability CheckpointCapability}
* <li>{@link EnvironmentCapability EnvironmentCapability}
* </ul>
@@ -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;
@@ -283,29 +283,6 @@ public class BDBRepositoryBuilder extends AbstractRepositoryBuilder {
}
/**
- * 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,
* unless data directories have been specified. For BDBRepositories that
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<Txn, S extends Storable> implements Storage<S>, Storag
}
public S prepare() {
- return mStorableCodec.instantiate(mRawSupport);
+ return mStorableCodec.instantiate();
}
public Query<S> query() throws FetchException {
@@ -169,6 +171,48 @@ abstract class BDBStorage<Txn, S extends Storable> implements Storage<S>, 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<S> 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<Txn> 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<? super S> trigger) {
return mTriggerManager.addTrigger(trigger);
}
@@ -222,9 +266,6 @@ abstract class BDBStorage<Txn, S extends Storable> implements Storage<S>, 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<S>(mRootStorage);
}
@@ -262,7 +303,7 @@ abstract class BDBStorage<Txn, S extends Storable> implements Storage<S>, Storag
boolean reverseOrder)
throws FetchException
{
- BDBTransactionManager<Txn> txnMgr = openTransactionManager();
+ TransactionManager<Txn> txnMgr = localTxnManager();
if (reverseRange) {
{
@@ -367,6 +408,12 @@ abstract class BDBStorage<Txn, S extends Storable> implements Storage<S>, 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<S> info = StorableIntrospector.examine(getStorableType());
@@ -386,11 +433,11 @@ abstract class BDBStorage<Txn, S extends Storable> implements Storage<S>, Storag
boolean isPrimaryEmpty;
try {
- BDBTransactionManager<Txn> txnMgr = mRepository.openTransactionManager();
+ TransactionManager<Txn> 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<Txn, S extends Storable> implements Storage<S>, 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<Txn, S extends Storable> implements Storage<S>, Storag
mQueryEngine = new QueryEngine<S>(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<S> compact() throws RepositoryException {
@@ -493,7 +543,7 @@ abstract class BDBStorage<Txn, S extends Storable> implements Storage<S>, 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<Txn, S extends Storable> implements Storage<S>, Storag
* @param database database to use
*/
protected abstract BDBCursor<Txn, S> openCursor
- (BDBTransactionManager<Txn> txnMgr,
+ (TransactionManager<Txn> txnMgr,
byte[] startBound, boolean inclusiveStart,
byte[] endBound, boolean inclusiveEnd,
int maxPrefix,
@@ -588,8 +638,8 @@ abstract class BDBStorage<Txn, S extends Storable> implements Storage<S>, Storag
return mRepository.toRepositoryException(e);
}
- BDBTransactionManager<Txn> openTransactionManager() {
- return mRepository.openTransactionManager();
+ TransactionManager<Txn> localTxnManager() {
+ return mRepository.localTxnManager();
}
/**
@@ -647,7 +697,7 @@ abstract class BDBStorage<Txn, S extends Storable> implements Storage<S>, Storag
* prevent threads from starting work that will likely fail along the way.
*/
void checkClosed() throws FetchException {
- BDBTransactionManager<Txn> txnMgr = openTransactionManager();
+ TransactionManager<Txn> txnMgr = localTxnManager();
// Lock out shutdown task.
txnMgr.getLock().lock();
@@ -668,7 +718,7 @@ abstract class BDBStorage<Txn, S extends Storable> implements Storage<S>, Storag
}
void close() throws Exception {
- BDBTransactionManager<Txn> txnMgr = mRepository.openTransactionManager();
+ TransactionManager<Txn> txnMgr = mRepository.localTxnManager();
txnMgr.getLock().lock();
try {
if (mPrimaryDatabase != null) {
@@ -811,7 +861,7 @@ abstract class BDBStorage<Txn, S extends Storable> implements Storage<S>, Storag
}
public byte[] tryLoad(byte[] key) throws FetchException {
- BDBTransactionManager<Txn> txnMgr = mStorage.openTransactionManager();
+ TransactionManager<Txn> txnMgr = mStorage.localTxnManager();
byte[] result;
// Lock out shutdown task.
txnMgr.getLock().lock();
@@ -834,7 +884,7 @@ abstract class BDBStorage<Txn, S extends Storable> implements Storage<S>, Storag
}
public boolean tryInsert(S storable, byte[] key, byte[] value) throws PersistException {
- BDBTransactionManager<Txn> txnMgr = mStorage.openTransactionManager();
+ TransactionManager<Txn> txnMgr = mStorage.localTxnManager();
Object result;
// Lock out shutdown task.
txnMgr.getLock().lock();
@@ -857,7 +907,7 @@ abstract class BDBStorage<Txn, S extends Storable> implements Storage<S>, Storag
}
public void store(S storable, byte[] key, byte[] value) throws PersistException {
- BDBTransactionManager<Txn> txnMgr = mStorage.openTransactionManager();
+ TransactionManager<Txn> txnMgr = mStorage.localTxnManager();
// Lock out shutdown task.
txnMgr.getLock().lock();
try {
@@ -874,7 +924,7 @@ abstract class BDBStorage<Txn, S extends Storable> implements Storage<S>, Storag
}
public boolean tryDelete(byte[] key) throws PersistException {
- BDBTransactionManager<Txn> txnMgr = mStorage.openTransactionManager();
+ TransactionManager<Txn> txnMgr = mStorage.localTxnManager();
// Lock out shutdown task.
txnMgr.getLock().lock();
try {
@@ -907,7 +957,11 @@ abstract class BDBStorage<Txn, S extends Storable> implements Storage<S>, 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<? super S> getInsertTrigger() {