All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.permazen.kv.sql.SQLKVTransaction Maven / Gradle / Ivy

There is a newer version: 5.1.0
Show newest version

/*
 * Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
 */

package io.permazen.kv.sql;

import com.google.common.base.Preconditions;

import io.permazen.kv.AbstractKVStore;
import io.permazen.kv.CloseableKVStore;
import io.permazen.kv.KVPair;
import io.permazen.kv.KVStore;
import io.permazen.kv.KVTransaction;
import io.permazen.kv.KVTransactionException;
import io.permazen.kv.KeyRange;
import io.permazen.kv.StaleTransactionException;
import io.permazen.kv.mvcc.MutableView;
import io.permazen.kv.mvcc.Mutations;
import io.permazen.kv.util.ForwardingKVStore;
import io.permazen.util.ByteUtil;
import io.permazen.util.CloseableIterator;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.Future;
import java.util.function.Function;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * {@link SQLKVDatabase} transaction.
 */
public class SQLKVTransaction extends ForwardingKVStore implements KVTransaction {

    private static final int MAX_DATA_PER_BATCH = 10 * 1024 * 1024;     // 10 MB
    private static final int MAX_STATEMENTS_PER_BATCH = 1000;
    private static final int BATCH_STATEMENT_OVERHEAD = 8;              // just a guess

    protected final Logger log = LoggerFactory.getLogger(this.getClass());

    protected final SQLKVDatabase database;
    protected final Connection connection;

    private long timeout;
    private boolean readOnly;
    private KVStore view;
    private volatile boolean mutated;
    private boolean closed;
    private boolean stale;

    /**
     * Constructor.
     *
     * @param database the associated database
     * @param connection the {@link Connection} for the transaction
     * @throws SQLException if an SQL error occurs
     */
    public SQLKVTransaction(SQLKVDatabase database, Connection connection) throws SQLException {
        Preconditions.checkArgument(database != null, "null database");
        Preconditions.checkArgument(connection != null, "null connection");
        this.database = database;
        this.connection = connection;
    }

    @Override
    public SQLKVDatabase getKVDatabase() {
        return this.database;
    }

    @Override
    public void setTimeout(long timeout) {
        Preconditions.checkArgument(timeout >= 0, "timeout < 0");
        this.timeout = timeout;
    }

    /**
     * Watch a key to monitor for changes in its value.
     *
     * 

* The implementation in {@link SQLKVTransaction} always throws {@link UnsupportedOperationException}. * Subclasses may add support using a database-specific notification mechanism. * * @param key {@inheritDoc} * @return {@inheritDoc} * @throws StaleTransactionException {@inheritDoc} * @throws io.permazen.kv.RetryTransactionException {@inheritDoc} * @throws io.permazen.kv.KVDatabaseException {@inheritDoc} * @throws UnsupportedOperationException {@inheritDoc} * @throws IllegalArgumentException {@inheritDoc} */ @Override public Future watchKey(byte[] key) { throw new UnsupportedOperationException(); } private synchronized byte[] getSQL(byte[] key) { if (this.stale) throw new StaleTransactionException(this); Preconditions.checkArgument(key != null, "null key"); return this.queryBytes(StmtType.GET, this.encodeKey(key)); } private synchronized KVPair getAtLeastSQL(byte[] minKey, byte[] maxKey) { if (this.stale) throw new StaleTransactionException(this); return minKey != null && minKey.length > 0 ? (maxKey != null ? this.queryKVPair(StmtType.GET_RANGE_FORWARD_SINGLE, this.encodeKey(minKey), this.encodeKey(maxKey)) : this.queryKVPair(StmtType.GET_AT_LEAST_FORWARD_SINGLE, this.encodeKey(minKey))) : (maxKey != null ? this.queryKVPair(StmtType.GET_AT_MOST_FORWARD_SINGLE, this.encodeKey(maxKey)) : this.queryKVPair(StmtType.GET_FIRST)); } private synchronized KVPair getAtMostSQL(byte[] maxKey, byte[] minKey) { if (this.stale) throw new StaleTransactionException(this); return maxKey != null ? (minKey != null && minKey.length > 0 ? this.queryKVPair(StmtType.GET_RANGE_REVERSE_SINGLE, this.encodeKey(minKey), this.encodeKey(maxKey)) : this.queryKVPair(StmtType.GET_AT_MOST_REVERSE_SINGLE, this.encodeKey(maxKey))) : (minKey != null && minKey.length > 0 ? this.queryKVPair(StmtType.GET_AT_LEAST_REVERSE_SINGLE, this.encodeKey(minKey)) : this.queryKVPair(StmtType.GET_LAST)); } private synchronized CloseableIterator getRangeSQL(byte[] minKey, byte[] maxKey, boolean reverse) { if (this.stale) throw new StaleTransactionException(this); if (minKey != null && minKey.length == 0) minKey = null; if (minKey == null && maxKey == null) return this.queryIterator(reverse ? StmtType.GET_ALL_REVERSE : StmtType.GET_ALL_FORWARD); if (minKey == null) { return this.queryIterator(reverse ? StmtType.GET_AT_MOST_REVERSE : StmtType.GET_AT_MOST_FORWARD, this.encodeKey(maxKey)); } if (maxKey == null) { return this.queryIterator(reverse ? StmtType.GET_AT_LEAST_REVERSE : StmtType.GET_AT_LEAST_FORWARD, this.encodeKey(minKey)); } else { return this.queryIterator(reverse ? StmtType.GET_RANGE_REVERSE : StmtType.GET_RANGE_FORWARD, this.encodeKey(minKey), this.encodeKey(maxKey)); } } private synchronized void putSQL(byte[] key, byte[] value) { Preconditions.checkArgument(key != null, "null key"); Preconditions.checkArgument(value != null, "null value"); if (this.stale) throw new StaleTransactionException(this); this.update(StmtType.PUT, this.encodeKey(key), value, value); } private synchronized void removeSQL(byte[] key) { Preconditions.checkArgument(key != null, "null key"); if (this.stale) throw new StaleTransactionException(this); this.update(StmtType.REMOVE, this.encodeKey(key)); } private synchronized void removeRangeSQL(byte[] minKey, byte[] maxKey) { if (this.stale) throw new StaleTransactionException(this); if (minKey != null && minKey.length == 0) minKey = null; if (minKey == null && maxKey == null) this.update(StmtType.REMOVE_ALL); else if (minKey == null) this.update(StmtType.REMOVE_AT_MOST, this.encodeKey(maxKey)); else if (maxKey == null) this.update(StmtType.REMOVE_AT_LEAST, this.encodeKey(minKey)); else this.update(StmtType.REMOVE_RANGE, this.encodeKey(minKey), this.encodeKey(maxKey)); } private synchronized void applyBatch(Mutations mutations) { Preconditions.checkArgument(mutations != null, "null mutations"); if (this.stale) throw new StaleTransactionException(this); // Do removes final EnumMap> removeBatchMap = new EnumMap<>(StmtType.class); final Function> listInit = st -> new ArrayList<>(); boolean removeAll = false; for (KeyRange remove : mutations.getRemoveRanges()) { final byte[] min = remove.getMin(); final byte[] max = remove.getMax(); assert min != null; if (min.length == 0 && max == null) { removeAll = true; break; } if (min.length == 0) removeBatchMap.computeIfAbsent(StmtType.REMOVE_AT_MOST, listInit).add(this.encodeKey(max)); else if (max == null) removeBatchMap.computeIfAbsent(StmtType.REMOVE_AT_LEAST, listInit).add(this.encodeKey(min)); else if (ByteUtil.isConsecutive(min, max)) removeBatchMap.computeIfAbsent(StmtType.REMOVE, listInit).add(this.encodeKey(min)); else { final ArrayList batch = removeBatchMap.computeIfAbsent(StmtType.REMOVE_RANGE, listInit); batch.add(this.encodeKey(min)); batch.add(this.encodeKey(max)); } } if (removeAll) this.update(StmtType.REMOVE_ALL); else { for (Map.Entry> entry : removeBatchMap.entrySet()) this.updateBatch(entry.getKey(), entry.getValue()); } // Do puts final ArrayList putBatch = new ArrayList(); for (Map.Entry entry : mutations.getPutPairs()) { putBatch.add(this.encodeKey(entry.getKey())); putBatch.add(entry.getValue()); putBatch.add(entry.getValue()); } this.updateBatch(StmtType.PUT, putBatch); // Do adjusts for (Map.Entry entry : mutations.getAdjustPairs()) this.adjustCounter(entry.getKey(), entry.getValue()); } @Override public synchronized boolean isReadOnly() { return this.readOnly; } @Override public synchronized void commit() { if (this.stale) throw new StaleTransactionException(this); this.stale = true; try { if (this.readOnly && !(this.view instanceof MutableView)) this.connection.rollback(); else this.connection.commit(); } catch (SQLException e) { throw this.handleException(e); } finally { this.closeConnection(); } } @Override public synchronized void rollback() { if (this.stale) return; this.stale = true; try { this.connection.rollback(); } catch (SQLException e) { throw this.handleException(e); } finally { this.closeConnection(); } } @Override public CloseableKVStore mutableSnapshot() { throw new UnsupportedOperationException(); } /** * Handle an unexpected SQL exception. * *

* The implementation in {@link SQLKVTransaction} rolls back the SQL transaction, closes the associated {@link Connection}, * and wraps the exception via {@link SQLKVDatabase#wrapException SQLKVDatabase.wrapException()}. * * @param e original exception * @return key/value transaction exception */ protected KVTransactionException handleException(SQLException e) { this.stale = true; try { this.connection.rollback(); } catch (SQLException e2) { // ignore } finally { this.closeConnection(); } return this.database.wrapException(this, e); } /** * Close the {@link Connection} associated with this instance, if it's not already closed. * This method is idempotent. */ protected void closeConnection() { if (this.closed) return; this.closed = true; try { this.connection.close(); } catch (SQLException e) { // ignore } } @Override protected void finalize() throws Throwable { try { if (!this.stale) this.log.warn(this + " leaked without commit() or rollback()"); this.closeConnection(); } finally { super.finalize(); } } // KVStore @Override public void put(byte[] key, byte[] value) { this.mutated = true; this.delegate().put(key, value); } @Override public void remove(byte[] key) { this.mutated = true; this.delegate().remove(key); } @Override public void removeRange(byte[] minKey, byte[] maxKey) { this.mutated = true; this.delegate().removeRange(minKey, maxKey); } @Override public void apply(Mutations mutations) { this.mutated = true; this.delegate().apply(mutations); } @Override protected synchronized KVStore delegate() { if (this.view == null) this.view = new SQLView(); return this.view; } @Override public synchronized void setReadOnly(boolean readOnly) { if (readOnly == this.readOnly) return; Preconditions.checkArgument(readOnly, "read-only transaction cannot be made writable again"); Preconditions.checkState(!this.mutated || this.database.rollbackForReadOnly, "data is already mutated"); if (!this.database.rollbackForReadOnly) this.view = new MutableView(this.view); this.readOnly = readOnly; } // Helper methods protected byte[] queryBytes(StmtType stmtType, byte[]... params) { assert params.length == stmtType.getNumParams(); final byte[] result = this.query(stmtType, (stmt, rs) -> rs.next() ? rs.getBytes(1) : null, true, params); if (this.log.isTraceEnabled()) this.log.trace("SQL query returned " + (result != null ? result.length + " bytes" : "not found")); return result; } protected KVPair queryKVPair(StmtType stmtType, byte[]... params) { assert params.length == stmtType.getNumParams(); final KVPair pair = this.query(stmtType, (stmt, rs) -> rs.next() ? new KVPair(this.decodeKey(rs.getBytes(1)), rs.getBytes(2)) : null, true, params); if (this.log.isTraceEnabled()) { this.log.trace("SQL query returned " + (pair != null ? "(" + pair.getKey().length + ", " + pair.getValue().length + ") bytes" : "not found")); } return pair; } protected CloseableIterator queryIterator(StmtType stmtType, byte[]... params) { assert params.length == stmtType.getNumParams(); final CloseableIterator i = this.query(stmtType, ResultSetIterator::new, false, params); if (this.log.isTraceEnabled()) this.log.trace("SQL query returned " + (i.hasNext() ? "non-" : "") + "empty iterator"); return i; } protected T query(StmtType stmtType, ResultSetFunction resultSetFunction, boolean close, byte[]... params) { assert params.length == stmtType.getNumParams(); try { final PreparedStatement preparedStatement = stmtType.create(this.database, this.connection, this.log); final int numParams = preparedStatement.getParameterMetaData().getParameterCount(); for (int i = 0; i < params.length && i < numParams; i++) { if (this.log.isTraceEnabled()) this.log.trace("setting ?" + (i + 1) + " = " + ByteUtil.toString(params[i])); preparedStatement.setBytes(i + 1, params[i]); } preparedStatement.setQueryTimeout((int)((this.timeout + 999) / 1000)); if (this.log.isTraceEnabled()) this.log.trace("executing SQL query"); final ResultSet resultSet = preparedStatement.executeQuery(); final T result = resultSetFunction.apply(preparedStatement, resultSet); if (close) { resultSet.close(); preparedStatement.close(); } return result; } catch (SQLException e) { throw this.handleException(e); } } protected void update(StmtType stmtType, byte[]... params) { assert params.length == stmtType.getNumParams(); try (PreparedStatement preparedStatement = stmtType.create(this.database, this.connection, this.log)) { final int numParams = preparedStatement.getParameterMetaData().getParameterCount(); for (int i = 0; i < params.length && i < numParams; i++) { if (this.log.isTraceEnabled()) this.log.trace("setting ?" + (i + 1) + " = " + ByteUtil.toString(params[i])); preparedStatement.setBytes(i + 1, params[i]); } preparedStatement.setQueryTimeout((int)((this.timeout + 999) / 1000)); if (this.log.isTraceEnabled()) this.log.trace("executing SQL update"); preparedStatement.executeUpdate(); if (this.log.isTraceEnabled()) this.log.trace("SQL update completed"); } catch (SQLException e) { throw this.handleException(e); } } protected void updateBatch(StmtType stmtType, List paramList) { // Each statement will consume this many parameters from paramList final int numStmtParams = stmtType.getNumParams(); assert numStmtParams > 0; assert paramList.size() % numStmtParams == 0; // Create statement and do batches try (PreparedStatement preparedStatement = stmtType.create(this.database, this.connection, this.log)) { final int numSqlParams = preparedStatement.getParameterMetaData().getParameterCount(); // Set query timeout preparedStatement.setQueryTimeout((int)((this.timeout + 999) / 1000)); // While there is more data, do more batches int paramIndex = 0; while (paramIndex < paramList.size()) { // While data limit per batch not reached, add more data to the batch int numStatementsInBatch = 0; int batchTotalBytes = 0; while (numStatementsInBatch < MAX_STATEMENTS_PER_BATCH && batchTotalBytes < MAX_DATA_PER_BATCH && paramIndex < paramList.size()) { for (int i = 0; i < numSqlParams; i++) { final byte[] param = paramList.get(paramIndex + i); if (this.log.isTraceEnabled()) this.log.trace("setting ?" + (i + 1) + " = " + ByteUtil.toString(param)); preparedStatement.setBytes(i + 1, param); batchTotalBytes += param.length; } preparedStatement.addBatch(); paramIndex += numStmtParams; numStatementsInBatch++; batchTotalBytes += BATCH_STATEMENT_OVERHEAD; } // Execute batch if (this.log.isTraceEnabled()) this.log.trace("executing SQL batch update"); preparedStatement.executeBatch(); if (this.log.isTraceEnabled()) this.log.trace("SQL batch update completed"); } assert paramIndex == paramList.size(); } catch (SQLException e) { throw this.handleException(e); } } /** * Encode the given key for the underlying database key column. * *

* The implementation in {@link SQLKVTransaction} just returns {@code key}. * * @param key key * @return database value for key column * @see #decodeKey decodeKey() */ protected byte[] encodeKey(byte[] key) { return key; } /** * Decode the given database key value encoded by {@link #encodeKey encodeKey()}. * *

* The implementation in {@link SQLKVTransaction} just returns {@code dbkey}. * * @param dbkey database value for key column * @return key * @see #encodeKey encodeKey() */ protected byte[] decodeKey(byte[] dbkey) { return dbkey; } // SQLView private class SQLView extends AbstractKVStore { @Override public byte[] get(byte[] key) { return SQLKVTransaction.this.getSQL(key); } @Override public KVPair getAtLeast(byte[] minKey, byte[] maxKey) { // Avoid range query when only a single key is in the range if (minKey != null && maxKey != null && ByteUtil.isConsecutive(minKey, maxKey)) { final byte[] value = this.get(minKey); return value != null ? new KVPair(minKey, value) : null; } return SQLKVTransaction.this.getAtLeastSQL(minKey, maxKey); } @Override public KVPair getAtMost(byte[] maxKey, byte[] minKey) { // Avoid range query when only a single key is in the range if (minKey != null && maxKey != null && ByteUtil.isConsecutive(minKey, maxKey)) { final byte[] value = this.get(minKey); return value != null ? new KVPair(minKey, value) : null; } return SQLKVTransaction.this.getAtMostSQL(maxKey, minKey); } @Override public CloseableIterator getRange(final byte[] minKey, final byte[] maxKey, final boolean reverse) { // Avoid range query when only a single key is in the range if (minKey != null && maxKey != null && ByteUtil.isConsecutive(minKey, maxKey)) { final byte[] value = this.get(minKey); if (value == null) { return new CloseableIterator() { @Override public boolean hasNext() { return false; } @Override public KVPair next() { throw new NoSuchElementException(); } @Override public void remove() { throw new NoSuchElementException(); } @Override public void close() { } }; } else { return new CloseableIterator() { private boolean gotten; @Override public boolean hasNext() { return !this.gotten; } @Override public KVPair next() { if (this.gotten) throw new NoSuchElementException(); this.gotten = true; return new KVPair(minKey, value); } @Override public void remove() { if (!this.gotten) throw new NoSuchElementException(); SQLView.this.remove(minKey); } @Override public void close() { } }; } } return SQLKVTransaction.this.getRangeSQL(minKey, maxKey, reverse); } @Override public void put(byte[] key, byte[] value) { SQLKVTransaction.this.putSQL(key, value); } @Override public void remove(byte[] key) { SQLKVTransaction.this.removeSQL(key); } @Override public void removeRange(byte[] minKey, byte[] maxKey) { SQLKVTransaction.this.removeRangeSQL(minKey, maxKey); } @Override public void apply(Mutations mutations) { SQLKVTransaction.this.applyBatch(mutations); } } // ResultSetFunction private interface ResultSetFunction { T apply(PreparedStatement preparedStatement, ResultSet resultSet) throws SQLException; } // ResultSetIterator private class ResultSetIterator implements CloseableIterator { private final PreparedStatement preparedStatement; private final ResultSet resultSet; private boolean ready; private boolean closed; private byte[] removeKey; ResultSetIterator(PreparedStatement preparedStatement, ResultSet resultSet) { assert preparedStatement != null; assert resultSet != null; this.resultSet = resultSet; this.preparedStatement = preparedStatement; } // Iterator @Override public synchronized boolean hasNext() { if (this.closed) return false; if (this.ready) return true; try { this.ready = this.resultSet.next(); } catch (SQLException e) { throw SQLKVTransaction.this.handleException(e); } if (!this.ready) this.close(); return this.ready; } @Override public synchronized KVPair next() { if (!this.hasNext()) throw new NoSuchElementException(); final byte[] key; final byte[] value; try { key = SQLKVTransaction.this.decodeKey(this.resultSet.getBytes(1)); value = this.resultSet.getBytes(2); } catch (SQLException e) { throw SQLKVTransaction.this.handleException(e); } this.removeKey = key.clone(); this.ready = false; return new KVPair(key, value); } @Override public synchronized void remove() { if (this.closed || this.removeKey == null) throw new IllegalStateException(); SQLKVTransaction.this.remove(this.removeKey); this.removeKey = null; } // Closeable @Override public synchronized void close() { if (this.closed) return; this.closed = true; try { this.resultSet.close(); } catch (Exception e) { // ignore } try { this.preparedStatement.close(); } catch (Exception e) { // ignore } } // Object @Override protected void finalize() throws Throwable { try { this.close(); } finally { super.finalize(); } } } // StmtType /** * Used internally to build SQL statements. */ protected enum StmtType { GET(1) { @Override protected PreparedStatement create(SQLKVDatabase db, Connection c, Logger log) throws SQLException { return this.prepare(c, db.createGetStatement(), log); }; }, GET_FIRST(0) { @Override protected PreparedStatement create(SQLKVDatabase db, Connection c, Logger log) throws SQLException { return this.prepare(c, db.limitSingleRow(db.createGetAllStatement(false)), log); }; }, GET_LAST(0) { @Override protected PreparedStatement create(SQLKVDatabase db, Connection c, Logger log) throws SQLException { return this.prepare(c, db.limitSingleRow(db.createGetAllStatement(true)), log); }; }, GET_AT_LEAST_FORWARD(1) { @Override protected PreparedStatement create(SQLKVDatabase db, Connection c, Logger log) throws SQLException { return this.prepare(c, db.createGetAtLeastStatement(false), log); }; }, GET_AT_LEAST_FORWARD_SINGLE(1) { @Override protected PreparedStatement create(SQLKVDatabase db, Connection c, Logger log) throws SQLException { return this.prepare(c, db.limitSingleRow(db.createGetAtLeastStatement(false)), log); }; }, GET_AT_LEAST_REVERSE(1) { @Override protected PreparedStatement create(SQLKVDatabase db, Connection c, Logger log) throws SQLException { return this.prepare(c, db.createGetAtLeastStatement(true), log); }; }, GET_AT_LEAST_REVERSE_SINGLE(1) { @Override protected PreparedStatement create(SQLKVDatabase db, Connection c, Logger log) throws SQLException { return this.prepare(c, db.limitSingleRow(db.createGetAtLeastStatement(true)), log); }; }, GET_AT_MOST_FORWARD(1) { @Override protected PreparedStatement create(SQLKVDatabase db, Connection c, Logger log) throws SQLException { return this.prepare(c, db.createGetAtMostStatement(false), log); }; }, GET_AT_MOST_FORWARD_SINGLE(1) { @Override protected PreparedStatement create(SQLKVDatabase db, Connection c, Logger log) throws SQLException { return this.prepare(c, db.limitSingleRow(db.createGetAtMostStatement(false)), log); }; }, GET_AT_MOST_REVERSE(1) { @Override protected PreparedStatement create(SQLKVDatabase db, Connection c, Logger log) throws SQLException { return this.prepare(c, db.createGetAtMostStatement(true), log); }; }, GET_AT_MOST_REVERSE_SINGLE(1) { @Override protected PreparedStatement create(SQLKVDatabase db, Connection c, Logger log) throws SQLException { return this.prepare(c, db.limitSingleRow(db.createGetAtMostStatement(true)), log); }; }, GET_RANGE_FORWARD(2) { @Override protected PreparedStatement create(SQLKVDatabase db, Connection c, Logger log) throws SQLException { return this.prepare(c, db.createGetRangeStatement(false), log); }; }, GET_RANGE_FORWARD_SINGLE(2) { @Override protected PreparedStatement create(SQLKVDatabase db, Connection c, Logger log) throws SQLException { return this.prepare(c, db.limitSingleRow(db.createGetRangeStatement(false)), log); }; }, GET_RANGE_REVERSE(2) { @Override protected PreparedStatement create(SQLKVDatabase db, Connection c, Logger log) throws SQLException { return this.prepare(c, db.createGetRangeStatement(true), log); }; }, GET_RANGE_REVERSE_SINGLE(2) { @Override protected PreparedStatement create(SQLKVDatabase db, Connection c, Logger log) throws SQLException { return this.prepare(c, db.limitSingleRow(db.createGetRangeStatement(true)), log); }; }, GET_ALL_FORWARD(0) { @Override protected PreparedStatement create(SQLKVDatabase db, Connection c, Logger log) throws SQLException { return this.prepare(c, db.createGetAllStatement(false), log); }; }, GET_ALL_REVERSE(0) { @Override protected PreparedStatement create(SQLKVDatabase db, Connection c, Logger log) throws SQLException { return this.prepare(c, db.createGetAllStatement(true), log); }; }, PUT(3) { @Override protected PreparedStatement create(SQLKVDatabase db, Connection c, Logger log) throws SQLException { return this.prepare(c, db.createPutStatement(), log); }; }, REMOVE(1) { @Override protected PreparedStatement create(SQLKVDatabase db, Connection c, Logger log) throws SQLException { return this.prepare(c, db.createRemoveStatement(), log); }; }, REMOVE_RANGE(2) { @Override protected PreparedStatement create(SQLKVDatabase db, Connection c, Logger log) throws SQLException { return this.prepare(c, db.createRemoveRangeStatement(), log); }; }, REMOVE_AT_LEAST(1) { @Override protected PreparedStatement create(SQLKVDatabase db, Connection c, Logger log) throws SQLException { return this.prepare(c, db.createRemoveAtLeastStatement(), log); }; }, REMOVE_AT_MOST(1) { @Override protected PreparedStatement create(SQLKVDatabase db, Connection c, Logger log) throws SQLException { return this.prepare(c, db.createRemoveAtMostStatement(), log); }; }, REMOVE_ALL(0) { @Override protected PreparedStatement create(SQLKVDatabase db, Connection c, Logger log) throws SQLException { return this.prepare(c, db.createRemoveAllStatement(), log); }; }; private final int numParams; StmtType(int numParams) { this.numParams = numParams; } public int getNumParams() { return this.numParams; } protected abstract PreparedStatement create(SQLKVDatabase db, Connection c, Logger log) throws SQLException; protected PreparedStatement prepare(Connection c, String sql, Logger log) throws SQLException { if (log.isTraceEnabled()) log.trace("preparing SQL statement: " + sql); return c.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy