Package com.impossibl.postgres.jdbc

Source Code of com.impossibl.postgres.jdbc.PGConnectionImpl

/**
* Copyright (c) 2013, impossibl.com
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
*  * Redistributions of source code must retain the above copyright notice,
*    this list of conditions and the following disclaimer.
*  * Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
*  * Neither the name of impossibl.com nor the names of its contributors may
*    be used to endorse or promote products derived from this software
*    without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package com.impossibl.postgres.jdbc;

import com.impossibl.postgres.api.jdbc.PGConnection;
import com.impossibl.postgres.api.jdbc.PGNotificationListener;
import com.impossibl.postgres.jdbc.Housekeeper.CleanupRunnable;
import com.impossibl.postgres.jdbc.SQLTextTree.Node;
import com.impossibl.postgres.jdbc.SQLTextTree.ParameterPiece;
import com.impossibl.postgres.jdbc.SQLTextTree.Processor;
import com.impossibl.postgres.protocol.Command;
import com.impossibl.postgres.protocol.Protocol;
import com.impossibl.postgres.protocol.QueryCommand;
import com.impossibl.postgres.protocol.ResultField;
import com.impossibl.postgres.protocol.ServerObjectType;
import com.impossibl.postgres.system.BasicContext;
import com.impossibl.postgres.system.NoticeException;
import com.impossibl.postgres.types.ArrayType;
import com.impossibl.postgres.types.CompositeType;
import com.impossibl.postgres.types.Type;
import com.impossibl.postgres.utils.BlockingReadTimeoutException;

import static com.impossibl.postgres.jdbc.ErrorUtils.chainWarnings;
import static com.impossibl.postgres.jdbc.ErrorUtils.makeSQLException;
import static com.impossibl.postgres.jdbc.ErrorUtils.makeSQLWarningChain;
import static com.impossibl.postgres.jdbc.Exceptions.CLOSED_CONNECTION;
import static com.impossibl.postgres.jdbc.Exceptions.INVALID_COMMAND_FOR_GENERATED_KEYS;
import static com.impossibl.postgres.jdbc.Exceptions.NOT_IMPLEMENTED;
import static com.impossibl.postgres.jdbc.Exceptions.NOT_SUPPORTED;
import static com.impossibl.postgres.jdbc.Exceptions.UNWRAP_ERROR;
import static com.impossibl.postgres.jdbc.SQLTextUtils.appendReturningClause;
import static com.impossibl.postgres.jdbc.SQLTextUtils.getBeginText;
import static com.impossibl.postgres.jdbc.SQLTextUtils.getCommitText;
import static com.impossibl.postgres.jdbc.SQLTextUtils.getGetSessionIsolationLevelText;
import static com.impossibl.postgres.jdbc.SQLTextUtils.getGetSessionReadabilityText;
import static com.impossibl.postgres.jdbc.SQLTextUtils.getIsolationLevel;
import static com.impossibl.postgres.jdbc.SQLTextUtils.getReleaseSavepointText;
import static com.impossibl.postgres.jdbc.SQLTextUtils.getRollbackText;
import static com.impossibl.postgres.jdbc.SQLTextUtils.getRollbackToText;
import static com.impossibl.postgres.jdbc.SQLTextUtils.getSetSavepointText;
import static com.impossibl.postgres.jdbc.SQLTextUtils.getSetSessionIsolationLevelText;
import static com.impossibl.postgres.jdbc.SQLTextUtils.getSetSessionReadabilityText;
import static com.impossibl.postgres.jdbc.SQLTextUtils.isTrue;
import static com.impossibl.postgres.jdbc.SQLTextUtils.prependCursorDeclaration;
import static com.impossibl.postgres.protocol.TransactionStatus.Idle;
import static com.impossibl.postgres.system.Settings.CONNECTION_READONLY;
import static com.impossibl.postgres.system.Settings.PARSED_SQL_CACHE_SIZE;
import static com.impossibl.postgres.system.Settings.PARSED_SQL_CACHE_SIZE_DEFAULT;
import static com.impossibl.postgres.system.Settings.PREPARED_STATEMENT_CACHE_SIZE;
import static com.impossibl.postgres.system.Settings.PREPARED_STATEMENT_CACHE_SIZE_DEFAULT;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.lang.ref.WeakReference;
import java.net.SocketAddress;
import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLTimeoutException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Struct;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;

import static java.lang.Boolean.parseBoolean;
import static java.sql.ResultSet.CLOSE_CURSORS_AT_COMMIT;
import static java.sql.ResultSet.CONCUR_READ_ONLY;
import static java.sql.ResultSet.TYPE_FORWARD_ONLY;
import static java.sql.Statement.RETURN_GENERATED_KEYS;
import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableMap;
import static java.util.concurrent.TimeUnit.SECONDS;



/**
* Connection implementation
* @author <a href="mailto:kdubb@me.com">Kevin Wooten</a>
* @author <a href="mailto:jesper.pedersen@redhat.com">Jesper Pedersen</a>
*/
public class PGConnectionImpl extends BasicContext implements PGConnection {

  /**
   * Cleans up server resources in the event of leaking connections
   *
   * @author kdubb
   *
   */
  static class Cleanup implements CleanupRunnable {

    Protocol protocol;
    List<WeakReference<PGStatement>> statements;
    Housekeeper.Ref housekeeper;
    StackTraceElement[] allocationStackTrace;

    public Cleanup(Protocol protocol, List<WeakReference<PGStatement>> statements) {
      this.protocol = protocol;
      this.statements = statements;
      this.allocationStackTrace = new Exception().getStackTrace();
    }

    @Override
    public String getKind() {
      return "connection";
    }

    @Override
    public StackTraceElement[] getAllocationStackTrace() {
      return allocationStackTrace;
    }

    @Override
    public void run() {

      protocol.shutdown();

      closeStatements(statements);

      housekeeper.release();
    }

  }


  long statementId = 0L;
  long portalId = 0L;
  int savepointId;
  private int holdability;
  boolean autoCommit = true;
  int networkTimeout;
  SQLWarning warningChain;
  List<WeakReference<PGStatement>> activeStatements;
  Map<CachedStatementKey, CachedStatement> preparedStatementCache;
  final Housekeeper.Ref housekeeper;
  final Object cleanupKey;

  static Map<String, SQLText> parsedSqlCache;

  PGConnectionImpl(SocketAddress address, Properties settings, Housekeeper.Ref housekeeper) throws IOException, NoticeException {
    super(address, settings, Collections.<String, Class<?>>emptyMap());

    this.activeStatements = new ArrayList<>();

    final int statementCacheSize = getSetting(PREPARED_STATEMENT_CACHE_SIZE, PREPARED_STATEMENT_CACHE_SIZE_DEFAULT);
    if (statementCacheSize > 0) {
      preparedStatementCache = Collections.synchronizedMap(new LinkedHashMap<CachedStatementKey, CachedStatement>(statementCacheSize + 1, 1.1f, true) {
        private static final long serialVersionUID = 1L;
        @Override
        protected boolean removeEldestEntry(Map.Entry<CachedStatementKey, CachedStatement> eldest) {
          if (size() > statementCacheSize) {
            try {
              PGStatement.dispose(PGConnectionImpl.this, ServerObjectType.Statement, eldest.getValue().name);
            }
            catch (SQLException e) {
              // Ignore...
            }
            return true;
          }
          else {
            return false;
          }
        }
      });
    }

    final int sqlCacheSize = getSetting(PARSED_SQL_CACHE_SIZE, PARSED_SQL_CACHE_SIZE_DEFAULT);
    if (sqlCacheSize > 0) {
      synchronized (PGConnectionImpl.class) {
        if (parsedSqlCache == null) {
          parsedSqlCache = Collections.synchronizedMap(new LinkedHashMap<String, SQLText>(sqlCacheSize + 1, 1.1f, true) {
            private static final long serialVersionUID = 1L;
            @Override
            protected boolean removeEldestEntry(Map.Entry<String, SQLText> eldest) {
              return size() > sqlCacheSize;
            }
          });
        }
      }
    }

    this.housekeeper = housekeeper;
    if (this.housekeeper != null)
      this.cleanupKey = this.housekeeper.add(this, new Cleanup(protocol, activeStatements));
    else
      this.cleanupKey = null;
  }

  @Override
  public void init() throws IOException, NoticeException {

    super.init();

    applySettings(settings);
  }

  void applySettings(Properties settings) throws IOException {

    if (parseBoolean(settings.getProperty(CONNECTION_READONLY, "false"))) {
      try {
        setReadOnly(true);
      }
      catch (SQLException e) {
        throw new IOException(e);
      }
    }
  }

  /**
   * Add warning to end of warning chain
   *
   * @param warning
   */
  public void addWarning(SQLWarning warning) {
    warningChain = chainWarnings(warningChain, warning);
  }

  /**
   * Ensure the connection is not closed
   *
   * @throws SQLException
   *          If the connection is closed
   */
  void checkClosed() throws SQLException {

    if (isClosed())
      throw new SQLException("connection closed");
  }

  /**
   * Ensures the connection is currently in manual-commit mode
   *
   * @throws SQLException
   *          If the connection is not in manual-commit mode
   */
  void checkManualCommit() throws SQLException {

    if (autoCommit)
      throw new SQLException("must not be in auto-commit mode");
  }

  /**
   * Ensures the connection is currently in auto-commit mode
   *
   * @throws SQLException
   *          IF the connection is not in auto-commit mode
   */
  void checkAutoCommit() throws SQLException {

    if (autoCommit)
      throw new SQLException("must be in auto-commit mode");
  }

  /**
   * Ensures that a transaction is active when in manual commit mode
   *
   * @throws SQLException
   */
  void checkTransaction() throws SQLException {

    if (!autoCommit && protocol.getTransactionStatus() == Idle) {
      try {
        query(getBeginText());
      }
      catch (IOException e) {
        throw new SQLException(e);
      }
      catch (NoticeException e) {
        throw makeSQLException(e.getNotice());
      }
    }

  }

  /**
   * Generates and returns the next unique statement name for this connection
   *
   * @return New unique statement name
   */
  String getNextStatementName() {
    return String.format("%016X", ++statementId);
  }

  /**
   * Generates and returns the next unique portal name for this connection
   *
   * @return New unique portal name
   */
  String getNextPortalName() {
    return String.format("%016X", ++portalId);
  }

  /**
   * Called by statements to notify the connection of their closure
   *
   * @param statement
   */
  void handleStatementClosure(PGStatement statement) {

    //Remove given & abandoned statements
    Iterator<WeakReference<PGStatement>> statementRefIter = activeStatements.iterator();
    while (statementRefIter.hasNext()) {

      WeakReference<PGStatement> statementRef = statementRefIter.next();

      if (statementRef.get() == null || statementRef.get() == statement) {
        statementRefIter.remove();
      }
    }

  }

  /**
   * Closes the given list of result-sets
   *
   * @throws SQLException
   */
  static void closeStatements(List<WeakReference<PGStatement>> statements) {

    for (WeakReference<PGStatement> statementRef : statements) {

      PGStatement statement = statementRef.get();
      if (statement != null) {
        try {
          statement.internalClose();
        }
        catch (SQLException e) {
          //Ignore...
        }
      }

    }

  }

  /**
   * Closes all active statements for this connection
   *
   * @throws SQLException
   */
  void closeStatements() throws SQLException {
    closeStatements(activeStatements);
  }

  SQLText parseSQL(String sqlText) throws SQLException {

    try {
      if (parsedSqlCache == null) {
        return new SQLText(sqlText);
      }

      SQLText parsedSql = parsedSqlCache.get(sqlText);
      if (parsedSql == null) {
        parsedSql = new SQLText(sqlText);
        parsedSqlCache.put(sqlText, parsedSql);
      }

      return parsedSql.copy();
    }
    catch (ParseException e) {

      throw new SQLException("Error parsing SQL at position " + e.getErrorOffset());
    }
  }

  /**
   * Executes the given command and throws a SQLException if an error was
   * encountered and returns a chain of SQLWarnings if any were generated.
   *
   * @param cmd
   *          Command to execute
   * @return Chain of SQLWarning objects if any were encountered
   * @throws SQLException
   *           If an error was encountered
   */
  SQLWarning execute(Command cmd, boolean checkTxn) throws SQLException {

    if (checkTxn) {
      checkTransaction();
    }

    //Enable network timeout
    long networkTimeoutMS = SECONDS.toMillis(networkTimeout);
    cmd.setNetworkTimeout(networkTimeoutMS);

    try {

      protocol.execute(cmd);

      if (cmd.getError() != null) {

        throw makeSQLException(cmd.getError());
      }

      return makeSQLWarningChain(cmd.getWarnings());

    }
    catch (BlockingReadTimeoutException e) {

      close();

      throw new SQLTimeoutException(e);
    }
    catch (InterruptedIOException e) {

      close();

      throw CLOSED_CONNECTION;
    }
    catch (IOException e) {

      throw new SQLException(e);
    }

  }

  /**
   * Executes the given SQL text ignoring all result values
   *
   * @param sql
   *          SQL text to execute
   * @throws SQLException
   *           If an error was encountered during execution
   */
  void execute(String sql, boolean checkTxn) throws SQLException {

    if (checkTxn) {
      checkTransaction();
    }

    try {

      query(sql);

    }
    catch (BlockingReadTimeoutException e) {

      close();

      throw new SQLTimeoutException(e);
    }
    catch (InterruptedIOException e) {

      close();

      throw CLOSED_CONNECTION;
    }
    catch (IOException e) {

      throw new SQLException(e);

    }
    catch (NoticeException e) {

      throw makeSQLException(e.getNotice());

    }

  }

  /**
   * Executes the given SQL text returning the first column of the first row
   *
   * @param sql
   *          SQL text to execute
   * @return String String value of the 1st column of the 1st row or empty
   *         string if no results are available
   * @throws SQLException
   *           If an error was encountered during execution
   */
  String executeForString(String sql, boolean checkTxn) throws SQLException {

    if (checkTxn) {
      checkTransaction();
    }

    try {

      return queryFirstResultString(sql);

    }
    catch (BlockingReadTimeoutException e) {

      close();

      throw new SQLTimeoutException(e);
    }
    catch (InterruptedIOException e) {

      close();

      throw CLOSED_CONNECTION;
    }
    catch (IOException e) {

      throw new SQLException(e);

    }
    catch (NoticeException e) {

      throw makeSQLException(e.getNotice());

    }

  }

  QueryCommand.ResultBatch executeForFirstResultBatch(String sql, boolean checkTxn, Object... params) throws SQLException {

    if (checkTxn) {
      checkTransaction();
    }

    try {

      return queryBatch(sql, Object[].class, params);

    }
    catch (BlockingReadTimeoutException e) {

      close();

      throw new SQLTimeoutException(e);
    }
    catch (InterruptedIOException e) {

      close();

      throw CLOSED_CONNECTION;
    }
    catch (IOException e) {

      throw new SQLException(e);

    }
    catch (NoticeException e) {

      throw makeSQLException(e.getNotice());

    }

  }

  Object[] executeForFirstResult(String sql, boolean checkTxn, Object... params) throws SQLException {

    QueryCommand.ResultBatch resultBatch = executeForFirstResultBatch(sql, checkTxn, params);

    List<?> res = resultBatch.results;
    if (res == null || res.isEmpty())
      return null;

    return (Object[]) res.get(0);
  }

  <T> T executeForFirstResultValue(String sql, boolean checkTxn, Class<T> returnType, Object... params) throws SQLException {

    Object[] result = executeForFirstResult(sql, checkTxn, params);
    if (result == null || result.length == 0)
      return null;

    return returnType.cast(result[0]);

  }

  long executeForRowsAffected(String sql, boolean checkTxn, Object... params) throws SQLException {

    QueryCommand.ResultBatch resultBatch = executeForFirstResultBatch(sql, checkTxn, params);

    return resultBatch.rowsAffected;
  }

  /**
   * Closes all statemens and shuts down the protocol
   *
   * @throws SQLException If an error occurs closing any of the statements
   */
  void internalClose() throws SQLException {

    closeStatements();

    shutdown();

    if (housekeeper != null) {
      housekeeper.remove(cleanupKey);
      housekeeper.release();
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isServerMinimumVersion(int major, int minor) {
    return getServerVersion().isMinimum(major, minor);
  }

  @Override
  public boolean isValid(int timeout) throws SQLException {

    //Not valid if connection is closed
    if (isClosed())
      return false;

    return executeForString("SELECT '1'::char", false).equals("1");
  }

  @Override
  public Map<String, Class<?>> getTypeMap() throws SQLException {
    checkClosed();

    return targetTypeMap;
  }

  @Override
  public void setTypeMap(Map<String, Class<?>> typeMap) throws SQLException {
    checkClosed();

    targetTypeMap = unmodifiableMap(typeMap);
  }

  @Override
  public int getHoldability() throws SQLException {
    checkClosed();

    return holdability;
  }

  @Override
  public void setHoldability(int holdability) throws SQLException {
    checkClosed();

    if (holdability != ResultSet.CLOSE_CURSORS_AT_COMMIT &&
        holdability != ResultSet.HOLD_CURSORS_OVER_COMMIT) {
      throw new SQLException("illegal argument");
    }

    this.holdability = holdability;
  }

  @Override
  public DatabaseMetaData getMetaData() throws SQLException {
    checkClosed();
    return new PGDatabaseMetaData(this);
  }

  @Override
  public boolean getAutoCommit() throws SQLException {
    checkClosed();

    return autoCommit;
  }

  @Override
  public void setAutoCommit(boolean autoCommit) throws SQLException {
    checkClosed();

    // Do nothing if no change in state
    if (this.autoCommit == autoCommit)
      return;

    // Commit any in-flight transaction (cannot call commit as it will start a
    // new transaction since we would still be in manual commit mode)
    if (!this.autoCommit && protocol.getTransactionStatus() != Idle) {
      execute(getCommitText(), false);
    }

    this.autoCommit = autoCommit;
  }

  @Override
  public boolean isReadOnly() throws SQLException {
    checkClosed();

    String readability = executeForString(getGetSessionReadabilityText(), false);

    return isTrue(readability);
  }

  @Override
  public void setReadOnly(boolean readOnly) throws SQLException {
    checkClosed();

    if (protocol.getTransactionStatus() != Idle) {
      throw new SQLException("cannot set read only during a transaction");
    }

    execute(getSetSessionReadabilityText(readOnly), false);
  }

  @Override
  public int getTransactionIsolation() throws SQLException {
    checkClosed();

    String isolLevel = executeForString(getGetSessionIsolationLevelText(), false);

    return getIsolationLevel(isolLevel);
  }

  @Override
  public void setTransactionIsolation(int level) throws SQLException {
    checkClosed();

    if (level != Connection.TRANSACTION_NONE &&
        level != Connection.TRANSACTION_READ_UNCOMMITTED &&
        level != Connection.TRANSACTION_READ_COMMITTED &&
        level != Connection.TRANSACTION_REPEATABLE_READ &&
        level != Connection.TRANSACTION_SERIALIZABLE) {
      throw new SQLException("illegal argument");
    }

    execute(getSetSessionIsolationLevelText(level), false);
  }

  @Override
  public void commit() throws SQLException {
    checkClosed();
    checkManualCommit();

    // Commit the current transaction
    if (protocol.getTransactionStatus() != Idle) {
      execute(getCommitText(), false);
    }

  }

  @Override
  public void rollback() throws SQLException {
    checkClosed();
    checkManualCommit();

    // Roll back the current transaction
    if (protocol.getTransactionStatus() != Idle) {
      execute(getRollbackText(), false);
    }

  }

  @Override
  public Savepoint setSavepoint() throws SQLException {
    checkClosed();
    checkManualCommit();

    // Allocate new save-point name & wrapper
    PGSavepoint savepoint = new PGSavepoint(++savepointId);

    // Mark save-point
    execute(getSetSavepointText(savepoint), true);

    return savepoint;
  }

  @Override
  public Savepoint setSavepoint(String name) throws SQLException {
    checkClosed();
    checkManualCommit();

    // Allocate new save-point wrapper
    PGSavepoint savepoint = new PGSavepoint(name);

    // Mark save-point
    execute(getSetSavepointText(savepoint), true);

    return savepoint;
  }

  @Override
  public void rollback(Savepoint savepointParam) throws SQLException {
    checkClosed();
    checkManualCommit();

    PGSavepoint savepoint = (PGSavepoint) savepointParam;

    if (!savepoint.isValid()) {
      throw new SQLException("invalid savepoint");
    }

    try {
      // Rollback to save-point (if in transaction)
      if (protocol.getTransactionStatus() != Idle) {
        execute(getRollbackToText(savepoint), false);
      }
    }
    finally {
      // Mark as released
      savepoint.setReleased(true);
    }

  }

  @Override
  public void releaseSavepoint(Savepoint savepointParam) throws SQLException {
    checkClosed();
    checkManualCommit();

    PGSavepoint savepoint = (PGSavepoint) savepointParam;

    if (!savepoint.isValid()) {
      throw new SQLException("invalid savepoint");
    }

    try {
      // Release the save-point (if in a transaction)
      if (!savepoint.getReleased() && protocol.getTransactionStatus() != Idle) {
        execute(getReleaseSavepointText(savepoint), false);
      }
    }
    finally {
      // Use up the save-point
      savepoint.invalidate();
    }

  }

  @Override
  public String getCatalog() throws SQLException {
    checkClosed();
    return null;
  }

  @Override
  public void setCatalog(String catalog) throws SQLException {
    checkClosed();
  }

  @Override
  public String getSchema() throws SQLException {
    checkClosed();
    return null;
  }

  @Override
  public void setSchema(String schema) throws SQLException {
    checkClosed();
  }

  @Override
  public String nativeSQL(String sql) throws SQLException {
    checkClosed();

    SQLText sqlText = parseSQL(sql);

    SQLTextEscapes.processEscapes(sqlText, this);

    return sqlText.toString();
  }

  @Override
  public PGStatement createStatement() throws SQLException {

    return createStatement(TYPE_FORWARD_ONLY, CONCUR_READ_ONLY, CLOSE_CURSORS_AT_COMMIT);
  }

  @Override
  public PGStatement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {

    return createStatement(resultSetType, resultSetConcurrency, CLOSE_CURSORS_AT_COMMIT);
  }

  @Override
  public PGStatement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
    checkClosed();

    PGSimpleStatement statement = new PGSimpleStatement(this, resultSetType, resultSetConcurrency, resultSetHoldability);

    activeStatements.add(new WeakReference<PGStatement>(statement));

    return statement;
  }

  @Override
  public PGPreparedStatement prepareStatement(String sql) throws SQLException {

    return prepareStatement(sql, TYPE_FORWARD_ONLY, CONCUR_READ_ONLY, CLOSE_CURSORS_AT_COMMIT);
  }

  @Override
  public PGPreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {

    return prepareStatement(sql, resultSetType, resultSetConcurrency, CLOSE_CURSORS_AT_COMMIT);
  }

  @Override
  public PGPreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
    checkClosed();

    SQLText sqlText = parseSQL(sql);

    return prepareStatement(sqlText, resultSetType, resultSetConcurrency, resultSetHoldability);
  }

  public PGPreparedStatement prepareStatement(SQLText sqlText, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {

    SQLTextEscapes.processEscapes(sqlText, this);

    String statementName = getNextStatementName();

    String cursorName = null;

    if (resultSetType != ResultSet.TYPE_FORWARD_ONLY || resultSetConcurrency == ResultSet.CONCUR_UPDATABLE) {

      cursorName = "cursor" + statementName;

      if (!prependCursorDeclaration(sqlText, cursorName, resultSetType, resultSetHoldability, autoCommit)) {

        cursorName = null;

      }

    }

    final int[] parameterCount = new int[1];
    sqlText.process(new Processor() {

      @Override
      public Node process(Node node) throws SQLException {
        if (node instanceof ParameterPiece)
          parameterCount[0] += 1;
        return node;
      }

    }, true);

    PGPreparedStatement statement =
        new PGPreparedStatement(this, resultSetType, resultSetConcurrency, resultSetHoldability, statementName, sqlText.toString(), parameterCount[0], cursorName);

    activeStatements.add(new WeakReference<PGStatement>(statement));

    return statement;
  }

  @Override
  public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
    checkClosed();

    SQLText sqlText = parseSQL(sql);

    if (autoGeneratedKeys != RETURN_GENERATED_KEYS) {
      return prepareStatement(sql);
    }

    if (!appendReturningClause(sqlText)) {
      throw INVALID_COMMAND_FOR_GENERATED_KEYS;
    }

    PGPreparedStatement statement = prepareStatement(sqlText, TYPE_FORWARD_ONLY, CONCUR_READ_ONLY, CLOSE_CURSORS_AT_COMMIT);

    statement.setWantsGeneratedKeys(true);

    return statement;
  }

  @Override
  public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
    checkClosed();
    throw NOT_SUPPORTED;
  }

  @Override
  public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
    checkClosed();

    SQLText sqlText = parseSQL(sql);

    if (!appendReturningClause(sqlText, asList(columnNames))) {
      throw INVALID_COMMAND_FOR_GENERATED_KEYS;
    }

    PGPreparedStatement statement = prepareStatement(sqlText, TYPE_FORWARD_ONLY, CONCUR_READ_ONLY, CLOSE_CURSORS_AT_COMMIT);

    statement.setWantsGeneratedKeys(true);

    return statement;
  }

  @Override
  public CallableStatement prepareCall(String sql) throws SQLException {
    return prepareCall(sql, TYPE_FORWARD_ONLY, CONCUR_READ_ONLY);
  }

  @Override
  public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
    return prepareCall(sql, TYPE_FORWARD_ONLY, CONCUR_READ_ONLY, getHoldability());
  }

  @Override
  public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
    checkClosed();

    SQLText sqlText = parseSQL(sql);

    return prepareCall(sqlText, resultSetType, resultSetConcurrency, resultSetHoldability);
  }

  public PGCallableStatement prepareCall(SQLText sqlText, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {

    final int[] parameterCount = new int[1];
    Processor counter = new Processor() {

      @Override
      public Node process(Node node) throws SQLException {
        if (node instanceof ParameterPiece)
          parameterCount[0] += 1;
        return node;
      }

    };

    sqlText.process(counter, true);
    int preParameterCount = parameterCount[0];

    SQLTextEscapes.processEscapes(sqlText, this);

    parameterCount[0] = 0;
    sqlText.process(counter, true);
    int finalParameterCount = parameterCount[0];

    String statementName = getNextStatementName();

    String cursorName = null;

    if (resultSetType != ResultSet.TYPE_FORWARD_ONLY || resultSetConcurrency == ResultSet.CONCUR_UPDATABLE) {

      cursorName = "cursor" + statementName;

      if (!prependCursorDeclaration(sqlText, cursorName, resultSetType, resultSetHoldability, autoCommit)) {

        cursorName = null;

      }

    }

    boolean hasAssign = preParameterCount == (finalParameterCount + 1);

    PGCallableStatement statement =
        new PGCallableStatement(this, resultSetType, resultSetConcurrency, resultSetHoldability, statementName, sqlText.toString(), parameterCount[0], cursorName, hasAssign);

    activeStatements.add(new WeakReference<PGStatement>(statement));

    return statement;
  }

  @Override
  public Blob createBlob() throws SQLException {
    checkClosed();

    int loOid = LargeObject.creat(this, 0);

    return new PGBlob(this, loOid);
  }

  @Override
  public Clob createClob() throws SQLException {
    checkClosed();

    int loOid = LargeObject.creat(this, 0);

    return new PGClob(this, loOid);
  }

  @Override
  public SQLXML createSQLXML() throws SQLException {
    checkClosed();

    return new PGSQLXML(this);
  }

  @Override
  public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
    checkClosed();

    Type type = getRegistry().loadType(typeName + "[]");
    if (type == null) {
      throw new SQLException("Array type not found");
    }

    return new PGArray(this, (ArrayType)type, elements);
  }

  @Override
  public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
    checkClosed();

    Type type = getRegistry().loadType(typeName);
    if (!(type instanceof CompositeType)) {
      throw new SQLException("Invalid type for struct");
    }

    return new PGStruct(this, (CompositeType)type, attributes);
  }

  @Override
  public void setClientInfo(String name, String value) throws SQLClientInfoException {
    // TODO: implement
    throw new UnsupportedOperationException();
  }

  @Override
  public void setClientInfo(Properties properties) throws SQLClientInfoException {
    // TODO: implement
    throw new UnsupportedOperationException();
  }

  @Override
  public String getClientInfo(String name) throws SQLException {
    checkClosed();
    throw NOT_IMPLEMENTED;
  }

  @Override
  public Properties getClientInfo() throws SQLException {
    checkClosed();
    throw NOT_IMPLEMENTED;
  }

  @Override
  public NClob createNClob() throws SQLException {
    checkClosed();
    throw NOT_SUPPORTED;
  }

  @Override
  public boolean isClosed() throws SQLException {
    return !protocol.isConnected();
  }

  @Override
  public void close() throws SQLException {

    // Ignore multiple closes
    if (isClosed())
      return;

    internalClose();
  }

  @Override
  public void abort(Executor executor) throws SQLException {

    getProtocol().abort(executor);

    shutdown();

    if (housekeeper != null)
      housekeeper.remove(cleanupKey);
  }

  @Override
  public SQLWarning getWarnings() throws SQLException {
    checkClosed();
    return warningChain;
  }

  @Override
  public void clearWarnings() throws SQLException {
    checkClosed();
    warningChain = null;
  }

  @Override
  public int getNetworkTimeout() throws SQLException {
    checkClosed();
    return networkTimeout;
  }

  @Override
  public void setNetworkTimeout(Executor executor, int networkTimeout) throws SQLException {
    checkClosed();

    if (networkTimeout < 0) {
      throw new SQLException("invalid network timeout");
    }

    this.networkTimeout = networkTimeout;
  }

  @Override
  public <T> T unwrap(Class<T> iface) throws SQLException {
    if (!iface.isAssignableFrom(getClass())) {
      throw UNWRAP_ERROR;
    }

    return iface.cast(this);
  }

  @Override
  public boolean isWrapperFor(Class<?> iface) throws SQLException {
    return iface.isAssignableFrom(getClass());
  }

  @Override
  public void addNotificationListener(String name, String channelNameFilter, PGNotificationListener listener) {
    super.addNotificationListener(name, channelNameFilter, listener);
  }

  @Override
  public void addNotificationListener(String channelNameFilter, PGNotificationListener listener) {
    super.addNotificationListener(null, channelNameFilter, listener);
  }

  @Override
  public void addNotificationListener(PGNotificationListener listener) {
    super.addNotificationListener(null, null, listener);
  }

  @Override
  public void removeNotificationListener(PGNotificationListener listener) {
    super.removeNotificationListener(listener);
  }

  CachedStatement getCachedStatement(CachedStatementKey key, Callable<CachedStatement> loader) throws Exception {

    if (preparedStatementCache == null) {
      return loader.call();
    }

    CachedStatement cached = preparedStatementCache.get(key);
    if (cached == null) {

      cached = loader.call();

      preparedStatementCache.put(key, cached);
    }

    return cached;
  }

}

class CachedStatementKey {

  String sql;
  List<Type> parameterTypes;

  public CachedStatementKey(String sql, List<Type> parameterTypes) {
    this.sql = sql;
    this.parameterTypes = parameterTypes;
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((parameterTypes == null) ? 0 : parameterTypes.hashCode());
    result = prime * result + ((sql == null) ? 0 : sql.hashCode());
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (getClass() != obj.getClass())
      return false;
    CachedStatementKey other = (CachedStatementKey) obj;
    if (parameterTypes == null) {
      if (other.parameterTypes != null)
        return false;
    }
    else if (!parameterTypes.equals(other.parameterTypes))
      return false;
    if (sql == null) {
      if (other.sql != null)
        return false;
    }
    else if (!sql.equals(other.sql))
      return false;
    return true;
  }

}

class CachedStatement {

  String name;
  List<Type> parameterTypes;
  List<ResultField> resultFields;

  public CachedStatement(String statementName, List<Type> parameterTypes, List<ResultField> resultFields) {
    this.name = statementName;
    this.parameterTypes = parameterTypes;
    this.resultFields = resultFields;
  }

}
TOP

Related Classes of com.impossibl.postgres.jdbc.PGConnectionImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.