summaryrefslogtreecommitdiff
path: root/src/main/java/com/amazon/carbonado/repo/jdbc
diff options
context:
space:
mode:
authorBrian S. O'Neill <bronee@gmail.com>2007-03-28 22:00:24 +0000
committerBrian S. O'Neill <bronee@gmail.com>2007-03-28 22:00:24 +0000
commit8809341248c62b15b78d7e6d8e06ab2ec3793c8e (patch)
treef39d7353987e025758e0a3abe1ffb49a48e9be9e /src/main/java/com/amazon/carbonado/repo/jdbc
parent65478f17ada9df04c4c2afa734378bb50ec2bd13 (diff)
Merged 1.2-dev to trunk.
Diffstat (limited to 'src/main/java/com/amazon/carbonado/repo/jdbc')
-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
20 files changed, 892 insertions, 479 deletions
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();
}
- */
}