Java tutorial
/* * Copyright 2006-2012 Amazon Technologies, Inc. or its affiliates. * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks * of Amazon Technologies, Inc. or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 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.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; 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.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.Trigger; import com.amazon.carbonado.capability.IndexInfo; import com.amazon.carbonado.cursor.ControllerCursor; import com.amazon.carbonado.cursor.EmptyCursor; import com.amazon.carbonado.cursor.LimitCursor; import com.amazon.carbonado.filter.AndFilter; import com.amazon.carbonado.filter.Filter; import com.amazon.carbonado.filter.FilterValues; import com.amazon.carbonado.filter.OrFilter; import com.amazon.carbonado.filter.PropertyFilter; 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.qe.AbstractQueryExecutor; import com.amazon.carbonado.qe.FilteredQueryExecutor; import com.amazon.carbonado.qe.OrderingList; import com.amazon.carbonado.qe.QueryExecutor; import com.amazon.carbonado.qe.QueryExecutorCache; import com.amazon.carbonado.qe.QueryExecutorFactory; import com.amazon.carbonado.qe.QueryFactory; import com.amazon.carbonado.qe.QueryHints; import com.amazon.carbonado.qe.SortedQueryExecutor; 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.txn.TransactionScope; import com.amazon.carbonado.util.QuickConstructorGenerator; /** * * * @author Brian S O'Neill */ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S> implements Storage<S>, JDBCSupport<S> { private static final int FIRST_RESULT_INDEX = 1; final JDBCRepository mRepository; final JDBCSupportStrategy mSupportStrategy; final JDBCStorableInfo<S> mInfo; final InstanceFactory mInstanceFactory; final QueryExecutorFactory<S> mExecutorFactory; final TriggerManager<S> mTriggerManager; JDBCStorage(JDBCRepository repository, JDBCStorableInfo<S> info, boolean isMaster, boolean autoVersioning, boolean suppressReload) throws SupportException, RepositoryException { super(info.getStorableType()); mRepository = repository; mSupportStrategy = repository.getSupportStrategy(); mInfo = info; Class<? extends S> generatedStorableClass = JDBCStorableGenerator.getGeneratedClass(info, isMaster, autoVersioning, suppressReload); mInstanceFactory = QuickConstructorGenerator.getInstance(generatedStorableClass, InstanceFactory.class); mExecutorFactory = new QueryExecutorCache<S>(new ExecutorFactory()); mTriggerManager = new TriggerManager<S>(info.getStorableType(), repository.mTriggerFactories); } @Override public Class<S> getStorableType() { return mInfo.getStorableType(); } public S prepare() { return (S) mInstanceFactory.instantiate(this); } public JDBCRepository getJDBCRepository() { return mRepository; } public Repository getRootRepository() { return mRepository.getRootRepository(); } public boolean isPropertySupported(String propertyName) { JDBCStorableProperty<S> property = mInfo.getAllProperties().get(propertyName); return property != null && property.isSupported(); } /** * @since 1.2 */ public void truncate() throws PersistException { String truncateFormat = mSupportStrategy.getTruncateTableStatement(); try { if (truncateFormat == null || mTriggerManager.getDeleteTrigger() != null) { query().deleteAll(); return; } Connection con = getConnection(); try { java.sql.Statement st = con.createStatement(); try { st.execute(String.format(truncateFormat, mInfo.getQualifiedTableName())); } finally { st.close(); } } catch (SQLException e) { throw toPersistException(e); } finally { yieldConnection(con); } } catch (FetchException e) { throw e.toPersistException(); } } public boolean addTrigger(Trigger<? super S> trigger) { return mTriggerManager.addTrigger(trigger); } public boolean removeTrigger(Trigger<? super S> trigger) { return mTriggerManager.removeTrigger(trigger); } public IndexInfo[] getIndexInfo() { return mInfo.getIndexInfo(); } public SequenceValueProducer getSequenceValueProducer(String name) throws PersistException { try { return mRepository.getSequenceValueProducer(name); } catch (RepositoryException e) { throw e.toPersistException(); } } public Trigger<? super S> getInsertTrigger() { return mTriggerManager.getInsertTrigger(); } public Trigger<? super S> getUpdateTrigger() { return mTriggerManager.getUpdateTrigger(); } public Trigger<? super S> getDeleteTrigger() { return mTriggerManager.getDeleteTrigger(); } public Trigger<? super S> getLoadTrigger() { return mTriggerManager.getLoadTrigger(); } public void locallyDisableLoadTrigger() { mTriggerManager.locallyDisableLoad(); } public void locallyEnableLoadTrigger() { mTriggerManager.locallyEnableLoad(); } public boolean isTransactionForUpdate() { return mRepository.isTransactionForUpdate(); } public FetchException toFetchException(Throwable e) { return mRepository.toFetchException(e); } public PersistException toPersistException(Throwable e) { return mRepository.toPersistException(e); } public boolean isUniqueConstraintError(SQLException e) { return mRepository.isUniqueConstraintError(e); } public Connection getConnection() throws FetchException { return mRepository.getConnection(); } public void yieldConnection(Connection con) throws FetchException { mRepository.yieldConnection(con); } public String getDatabaseProductName() { return mRepository.getDatabaseProductName(); } /** * @param loader used to reload Blob outside original transaction */ public com.amazon.carbonado.lob.Blob convertBlob(java.sql.Blob blob, JDBCBlobLoader loader) throws FetchException { JDBCBlob jblob = mSupportStrategy.convertBlob(blob, loader); if (jblob != null) { try { JDBCTransaction txn = mRepository.localTransactionScope().getTxn(); if (txn != null) { txn.register(jblob); } } catch (Exception e) { throw toFetchException(e); } } return jblob; } /** * @param loader used to reload Clob outside original transaction */ public com.amazon.carbonado.lob.Clob convertClob(java.sql.Clob clob, JDBCClobLoader loader) throws FetchException { JDBCClob jclob = mSupportStrategy.convertClob(clob, loader); if (jclob != null) { try { JDBCTransaction txn = mRepository.localTransactionScope().getTxn(); if (txn != null) { txn.register(jclob); } } catch (Exception e) { throw toFetchException(e); } } return jclob; } /** * @return original blob if too large and post-insert update is required, null otherwise * @throws PersistException instead of FetchException since this code is * called during an insert operation */ public com.amazon.carbonado.lob.Blob setBlobValue(PreparedStatement ps, int column, com.amazon.carbonado.lob.Blob blob) throws PersistException { return mSupportStrategy.setBlobValue(ps, column, blob); } /** * @return original clob if too large and post-insert update is required, null otherwise * @throws PersistException instead of FetchException since this code is * called during an insert operation */ public com.amazon.carbonado.lob.Clob setClobValue(PreparedStatement ps, int column, com.amazon.carbonado.lob.Clob clob) throws PersistException { return mSupportStrategy.setClobValue(ps, column, clob); } public void updateBlob(com.amazon.carbonado.lob.Blob oldBlob, com.amazon.carbonado.lob.Blob newBlob) throws PersistException { mSupportStrategy.updateBlob(oldBlob, newBlob); } public void updateClob(com.amazon.carbonado.lob.Clob oldClob, com.amazon.carbonado.lob.Clob newClob) throws PersistException { mSupportStrategy.updateClob(oldClob, newClob); } protected JDBCStorableInfo<S> getStorableInfo() { return mInfo; } @Override protected StandardQuery<S> createQuery(Filter<S> filter, FilterValues<S> values, OrderingList<S> ordering, QueryHints hints) { return new JDBCQuery(filter, values, ordering, hints); } static PreparedStatement prepareStatement(Connection con, String sql, Query.Controller controller) throws SQLException { PreparedStatement ps = con.prepareStatement(sql); if (controller != null) { long timeout = controller.getTimeout(); if (timeout >= 0) { TimeUnit unit = controller.getTimeoutUnit(); if (unit != null) { long seconds = unit.toSeconds(timeout); int intSeconds = seconds <= 0 ? 1 : (seconds <= Integer.MAX_VALUE ? ((int) seconds) : 0); ps.setQueryTimeout(intSeconds); } } } return ps; } public S instantiate(ResultSet rs) throws SQLException { return (S) mInstanceFactory.instantiate(this, rs, FIRST_RESULT_INDEX); } public static interface InstanceFactory { Storable instantiate(JDBCSupport storage); Storable instantiate(JDBCSupport storage, ResultSet rs, int offset) throws SQLException; } private class ExecutorFactory implements QueryExecutorFactory<S> { ExecutorFactory() { } public Class<S> getStorableType() { return JDBCStorage.this.getStorableType(); } public QueryExecutor<S> executor(Filter<S> filter, OrderingList<S> ordering, QueryHints hints) throws RepositoryException { TableAliasGenerator aliasGenerator = new TableAliasGenerator(); JoinNode jn; try { JoinNodeBuilder<S> jnb = new JoinNodeBuilder<S>(mRepository, getStorableInfo(), aliasGenerator); if (filter != null) { filter.accept(jnb, null); } jn = jnb.getRootJoinNode(); jnb.captureOrderings(ordering); } catch (UndeclaredThrowableException e) { throw toFetchException(e); } SQLStatementBuilder<S> selectBuilder = new SQLStatementBuilder<S>(mRepository); selectBuilder.append("SELECT "); // Don't bother using a table alias for one table. With just one table, // there's no need to disambiguate. String alias = jn.isAliasRequired() ? jn.getAlias() : null; Map<String, JDBCStorableProperty<S>> properties = getStorableInfo().getAllProperties(); int ordinal = 0; for (JDBCStorableProperty<S> property : properties.values()) { if (!property.isSelectable()) { continue; } if (ordinal > 0) { selectBuilder.append(','); } if (alias != null) { selectBuilder.append(alias); selectBuilder.append('.'); } selectBuilder.append(property.getColumnName()); ordinal++; } selectBuilder.append(" FROM"); SQLStatementBuilder<S> fromWhereBuilder = new SQLStatementBuilder<S>(mRepository); fromWhereBuilder.append(" FROM"); SQLStatementBuilder<S> deleteFromWhereBuilder; if (alias == null) { // Don't bother defining a table alias for one table. jn.appendTableNameTo(selectBuilder); jn.appendTableNameTo(fromWhereBuilder); deleteFromWhereBuilder = null; } else { jn.appendFullJoinTo(selectBuilder); jn.appendFullJoinTo(fromWhereBuilder); // Since the delete is operating with joins, need to generate // a special form that uses WHERE EXISTS. deleteFromWhereBuilder = new SQLStatementBuilder<S>(mRepository); deleteFromWhereBuilder.append(" FROM"); jn.appendTableNameAndAliasTo(deleteFromWhereBuilder); } // Appending where clause. Remainder filter is required if a // derived property is used. Derived properties in exists filters // is not supported. Filter<S> remainderFilter = null; PropertyFilter<S>[] propertyFilters = null; boolean[] propertyFilterNullable = null; if (filter != null && !filter.isOpen()) { Filter<S> sqlFilter = null; List<Filter<S>> splitList = filter.conjunctiveNormalFormSplit(); for (Filter<S> split : splitList) { if (usesDerivedProperty(split)) { remainderFilter = and(remainderFilter, split); } else { sqlFilter = and(sqlFilter, split); } } if (remainderFilter == null) { // Just use original filter. sqlFilter = filter; } if (sqlFilter == null) { // Just use original filter for remainder. remainderFilter = filter; } else { // Build the WHERE clause only if anything to filter on. WhereBuilder<S> wb = new WhereBuilder<S>(selectBuilder, alias == null ? null : jn, aliasGenerator); wb.append(sqlFilter); propertyFilters = wb.getPropertyFilters(); propertyFilterNullable = wb.getPropertyFilterNullable(); wb = new WhereBuilder<S>(fromWhereBuilder, alias == null ? null : jn, aliasGenerator); wb.append(sqlFilter); if (deleteFromWhereBuilder != null) { wb = new WhereBuilder<S>(deleteFromWhereBuilder, jn, aliasGenerator); wb.appendExists(sqlFilter); } } } // Append order-by clause. Remainder ordering is required if a derived // property is used. OrderingList<S> sqlOrdering = ordering; OrderingList<S> remainderOrdering = null; if (ordering != null && ordering.size() > 0) { ordinal = 0; for (OrderedProperty<S> orderedProperty : ordering) { if (orderedProperty.getChainedProperty().isDerived()) { sqlOrdering = ordering.subList(0, ordinal); remainderOrdering = ordering.subList(ordinal, ordering.size()); break; } ordinal++; } if (sqlOrdering != null && sqlOrdering.size() > 0) { selectBuilder.append(" ORDER BY "); ordinal = 0; for (OrderedProperty<S> orderedProperty : sqlOrdering) { if (ordinal > 0) { selectBuilder.append(','); } selectBuilder.appendColumn(alias == null ? null : jn, orderedProperty.getChainedProperty()); if (orderedProperty.getDirection() == Direction.DESCENDING) { selectBuilder.append(" DESC"); } ordinal++; } } } SQLStatement<S> selectStatement, fromWhere, deleteFromWhere; selectStatement = selectBuilder.build(); fromWhere = fromWhereBuilder.build(); deleteFromWhere = deleteFromWhereBuilder == null ? null : deleteFromWhereBuilder.build(); QueryExecutor<S> executor = new Executor(filter, sqlOrdering, selectStatement, fromWhere, deleteFromWhere, propertyFilters, propertyFilterNullable); if (remainderFilter != null && !remainderFilter.isOpen()) { executor = new FilteredQueryExecutor<S>(executor, remainderFilter); } if (remainderOrdering != null && remainderOrdering.size() > 0) { executor = new SortedQueryExecutor<S>(new SortedQueryExecutor.MergeSortSupport(), executor, sqlOrdering, remainderOrdering); } return executor; } private Filter<S> and(Filter<S> left, Filter<S> right) { if (left == null) { return right; } if (right == null) { return left; } return left.and(right); } private boolean usesDerivedProperty(Filter<S> filter) { Boolean result = filter.accept(new Visitor<S, Boolean, Object>() { @Override public Boolean visit(OrFilter<S> orFilter, Object param) { Boolean result = orFilter.getLeftFilter().accept(this, param); if (result != null && result) { // Short-circuit. return result; } return orFilter.getRightFilter().accept(this, param); } @Override public Boolean visit(AndFilter<S> andFilter, Object param) { Boolean result = andFilter.getLeftFilter().accept(this, param); if (result != null && result) { // Short-circuit. return result; } return andFilter.getRightFilter().accept(this, param); } @Override public Boolean visit(PropertyFilter<S> propFilter, Object param) { return propFilter.getChainedProperty().isDerived(); } }, null); return result != null && result; } } private class Executor extends AbstractQueryExecutor<S> { private final Filter<S> mFilter; private final OrderingList<S> mOrdering; private final SQLStatement<S> mSelectStatement; private final int mMaxSelectStatementLength; private final SQLStatement<S> mFromWhere; private final int mMaxFromWhereLength; private final SQLStatement<S> mDeleteFromWhere; private final int mMaxDeleteFromWhereLength; // The following arrays all have the same length, or they may all be null. private final PropertyFilter<S>[] mPropertyFilters; private final boolean[] mPropertyFilterNullable; private final Method[] mPreparedStatementSetMethods; // Some entries may be null if no adapter required. private final Method[] mAdapterMethods; // Some entries may be null if no adapter required. private final Object[] mAdapterInstances; Executor(Filter<S> filter, OrderingList<S> ordering, SQLStatement<S> selectStatement, SQLStatement<S> fromWhere, SQLStatement<S> deleteFromWhere, PropertyFilter<S>[] propertyFilters, boolean[] propertyFilterNullable) throws RepositoryException { mFilter = filter; mOrdering = ordering; mSelectStatement = selectStatement; mMaxSelectStatementLength = selectStatement.maxLength(); mFromWhere = fromWhere; mMaxFromWhereLength = fromWhere.maxLength(); if (deleteFromWhere == null) { mDeleteFromWhere = mFromWhere; mMaxDeleteFromWhereLength = mMaxFromWhereLength; } else { mDeleteFromWhere = deleteFromWhere; mMaxDeleteFromWhereLength = deleteFromWhere.maxLength(); } if (propertyFilters == null) { mPropertyFilters = null; mPropertyFilterNullable = null; mPreparedStatementSetMethods = null; mAdapterMethods = null; mAdapterInstances = null; } else { mPropertyFilters = propertyFilters; mPropertyFilterNullable = propertyFilterNullable; int length = propertyFilters.length; mPreparedStatementSetMethods = new Method[length]; mAdapterMethods = new Method[length]; mAdapterInstances = new Object[length]; gatherAdapterMethods(propertyFilters); } } private void gatherAdapterMethods(PropertyFilter<S>[] filters) throws RepositoryException { for (int i = 0; i < filters.length; i++) { PropertyFilter<S> filter = filters[i]; ChainedProperty<S> chained = filter.getChainedProperty(); StorableProperty<?> property = chained.getLastProperty(); JDBCStorableProperty<?> jProperty = mRepository.getJDBCStorableProperty(property); Method psSetMethod = jProperty.getPreparedStatementSetMethod(); mPreparedStatementSetMethods[i] = psSetMethod; StorablePropertyAdapter adapter = jProperty.getAppliedAdapter(); if (adapter != null) { Class toType = psSetMethod.getParameterTypes()[1]; mAdapterMethods[i] = adapter.findAdaptMethod(jProperty.getType(), toType); // Special case for converting character to String. if (mAdapterMethods[i] == null) { if (toType == String.class) { mAdapterMethods[i] = adapter.findAdaptMethod(jProperty.getType(), Character.class); if (mAdapterMethods[i] == null) { mAdapterMethods[i] = adapter.findAdaptMethod(jProperty.getType(), char.class); } } } mAdapterInstances[i] = adapter.getAdapterInstance(); } } } @Override public Cursor<S> fetch(FilterValues<S> values) throws FetchException { return fetch(values, null); } @Override public Cursor<S> fetch(FilterValues<S> values, Query.Controller controller) throws FetchException { TransactionScope<JDBCTransaction> scope = mRepository.localTransactionScope(); boolean forUpdate = scope.isForUpdate(); Connection con = getConnection(); try { PreparedStatement ps = prepareStatement(con, prepareSelect(values, forUpdate), controller); Integer fetchSize = mRepository.getFetchSize(); if (fetchSize != null) { ps.setFetchSize(fetchSize); } try { setParameters(ps, values); return ControllerCursor.apply(new JDBCCursor<S>(JDBCStorage.this, scope, con, ps), controller); } catch (Exception e) { // in case of exception, close statement try { ps.close(); } catch (SQLException e2) { // ignore and allow triggering exception to propagate } throw e; } } catch (Exception e) { // in case of exception, yield connection try { yieldConnection(con); } catch (FetchException e2) { // ignore and allow triggering exception to propagate } throw toFetchException(e); } } @Override public Cursor<S> fetchSlice(FilterValues<S> values, long from, Long to) throws FetchException { return fetchSlice(values, from, to, null); } @Override public Cursor<S> fetchSlice(FilterValues<S> values, long from, Long to, Query.Controller controller) throws FetchException { if (to != null && (to - from) <= 0) { return EmptyCursor.the(); } JDBCSupportStrategy.SliceOption option = mSupportStrategy.getSliceOption(); String select; switch (option) { case NOT_SUPPORTED: default: return super.fetchSlice(values, from, to, controller); case LIMIT_ONLY: if (from > 0 || to == null) { return super.fetchSlice(values, from, to, controller); } select = prepareSelect(values, false); select = mSupportStrategy.buildSelectWithSlice(select, false, true); break; case OFFSET_ONLY: if (from <= 0) { return super.fetchSlice(values, from, to, controller); } select = prepareSelect(values, false); select = mSupportStrategy.buildSelectWithSlice(select, true, false); break; case LIMIT_AND_OFFSET: case OFFSET_AND_LIMIT: case FROM_AND_TO: select = prepareSelect(values, false); select = mSupportStrategy.buildSelectWithSlice(select, from > 0, to != null); break; } TransactionScope<JDBCTransaction> scope = mRepository.localTransactionScope(); if (scope.isForUpdate()) { select = select.concat(" FOR UPDATE"); } Connection con = getConnection(); try { PreparedStatement ps = prepareStatement(con, select, controller); Integer fetchSize = mRepository.getFetchSize(); if (fetchSize != null) { ps.setFetchSize(fetchSize); } try { int psOrdinal = setParameters(ps, values); if (from > 0) { if (to != null) { switch (option) { case OFFSET_ONLY: ps.setLong(psOrdinal, from); Cursor<S> c = ControllerCursor .apply(new JDBCCursor<S>(JDBCStorage.this, scope, con, ps), controller); return new LimitCursor<S>(c, to - from); case LIMIT_AND_OFFSET: ps.setLong(psOrdinal, to - from); ps.setLong(psOrdinal + 1, from); break; case OFFSET_AND_LIMIT: ps.setLong(psOrdinal, from); ps.setLong(psOrdinal + 1, to - from); break; case FROM_AND_TO: ps.setLong(psOrdinal, from); ps.setLong(psOrdinal + 1, to); break; } } else { ps.setLong(psOrdinal, from); } } else if (to != null) { ps.setLong(psOrdinal, to); } return ControllerCursor.apply(new JDBCCursor<S>(JDBCStorage.this, scope, con, ps), controller); } catch (Exception e) { // in case of exception, close statement try { ps.close(); } catch (SQLException e2) { // ignore and allow triggering exception to propagate } throw e; } } catch (Exception e) { // in case of exception, yield connection try { yieldConnection(con); } catch (FetchException e2) { // ignore and allow triggering exception to propagate } throw toFetchException(e); } } @Override public long count(FilterValues<S> values) throws FetchException { return count(values, null); } @Override public long count(FilterValues<S> values, Query.Controller controller) throws FetchException { Connection con = getConnection(); try { PreparedStatement ps = prepareStatement(con, prepareCount(values), controller); try { setParameters(ps, values); ResultSet rs = ps.executeQuery(); try { rs.next(); return rs.getLong(1); } finally { rs.close(); } } finally { ps.close(); } } catch (Exception e) { throw toFetchException(e); } finally { yieldConnection(con); } } @Override public Filter<S> getFilter() { return mFilter; } @Override public OrderingList<S> getOrdering() { return mOrdering; } @Override public boolean printNative(Appendable app, int indentLevel, FilterValues<S> values) throws IOException { indent(app, indentLevel); boolean forUpdate = mRepository.localTransactionScope().isForUpdate(); app.append(prepareSelect(values, forUpdate)); app.append('\n'); return true; } @Override public boolean printPlan(Appendable app, int indentLevel, FilterValues<S> values) throws IOException { try { boolean forUpdate = mRepository.localTransactionScope().isForUpdate(); String statement = prepareSelect(values, forUpdate); return mRepository.getSupportStrategy().printPlan(app, indentLevel, statement); } catch (FetchException e) { LogFactory.getLog(JDBCStorage.class).error(null, e); return false; } } /** * Delete operation is included in cursor factory for ease of implementation. */ int executeDelete(FilterValues<S> filterValues, Query.Controller controller) throws PersistException { Connection con; try { con = getConnection(); } catch (FetchException e) { throw e.toPersistException(); } try { PreparedStatement ps = prepareStatement(con, prepareDelete(filterValues), controller); try { setParameters(ps, filterValues); return ps.executeUpdate(); } finally { ps.close(); } } catch (Exception e) { throw toPersistException(e); } finally { try { yieldConnection(con); } catch (FetchException e) { throw e.toPersistException(); } } } private String prepareSelect(FilterValues<S> filterValues, boolean forUpdate) { if (!forUpdate) { return mSelectStatement.buildStatement(mMaxSelectStatementLength, filterValues); } // Allocate with extra room for " FOR UPDATE" StringBuilder b = new StringBuilder(mMaxSelectStatementLength + 11); mSelectStatement.appendTo(b, filterValues); b.append(" FOR UPDATE"); return b.toString(); } private String prepareCount(FilterValues<S> filterValues) { // Allocate with extra room for "SELECT COUNT(*)" StringBuilder b = new StringBuilder(15 + mMaxFromWhereLength); b.append("SELECT COUNT(*)"); mFromWhere.appendTo(b, filterValues); return b.toString(); } private String prepareDelete(FilterValues<S> filterValues) { // Allocate with extra room for "DELETE" StringBuilder b = new StringBuilder(6 + mMaxDeleteFromWhereLength); b.append("DELETE"); mDeleteFromWhere.appendTo(b, filterValues); return b.toString(); } /** * @return next value ordinal */ private int setParameters(PreparedStatement ps, FilterValues<S> filterValues) throws Exception { PropertyFilter<S>[] propertyFilters = mPropertyFilters; if (propertyFilters == null) { return 1; } boolean[] propertyFilterNullable = mPropertyFilterNullable; Method[] psSetMethods = mPreparedStatementSetMethods; Method[] adapterMethods = mAdapterMethods; Object[] adapterInstances = mAdapterInstances; int ordinal = 0; int psOrdinal = 1; // Start at one since JDBC ordinals are one-based. for (PropertyFilter<S> filter : propertyFilters) { setValue: { Object value = filterValues.getAssignedValue(filter); if (value == null && propertyFilterNullable[ordinal]) { // No '?' parameter to fill since value "IS NULL" or "IS NOT NULL" break setValue; } Method adapter = adapterMethods[ordinal]; if (adapter != null) { value = adapter.invoke(adapterInstances[ordinal], value); } // Special case for converting character to String. if (value != null && value instanceof Character) { value = String.valueOf((Character) value); } psSetMethods[ordinal].invoke(ps, psOrdinal, value); psOrdinal++; } ordinal++; } return psOrdinal; } } private class JDBCQuery extends StandardQuery<S> { JDBCQuery(Filter<S> filter, FilterValues<S> values, OrderingList<S> ordering, QueryHints hints) { super(filter, values, ordering, hints); } @Override public void deleteAll() throws PersistException { deleteAll(null); } @Override public void deleteAll(Controller controller) throws PersistException { if (mTriggerManager.getDeleteTrigger() != null) { // Super implementation loads one at time and calls // delete. This allows delete trigger to be invoked on each. super.deleteAll(controller); } else { try { ((Executor) executor()).executeDelete(getFilterValues(), controller); } catch (RepositoryException e) { throw e.toPersistException(); } } } @Override protected Transaction enterTransaction(IsolationLevel level) { return getRootRepository().enterTransaction(level); } @Override protected QueryFactory<S> queryFactory() { return JDBCStorage.this; } @Override protected QueryExecutorFactory<S> executorFactory() { return JDBCStorage.this.mExecutorFactory; } @Override protected StandardQuery<S> newInstance(FilterValues<S> values, OrderingList<S> ordering, QueryHints hints) { return new JDBCQuery(values.getFilter(), values, ordering, hints); } } }