/*
 * Copyright (c) 2004, PostgreSQL Global Development Group
 * See the LICENSE file in the project root for more information.
 */

package org.postgresql.jdbc;

import org.postgresql.Driver;
import org.postgresql.core.BaseConnection;
import org.postgresql.core.CachedQuery;
import org.postgresql.core.Oid;
import org.postgresql.core.ParameterList;
import org.postgresql.core.Query;
import org.postgresql.core.QueryExecutor;
import org.postgresql.core.ServerVersion;
import org.postgresql.core.TypeInfo;
import org.postgresql.core.v3.BatchedQuery;
import org.postgresql.largeobject.LargeObject;
import org.postgresql.largeobject.LargeObjectManager;
import org.postgresql.util.ByteConverter;
import org.postgresql.util.GT;
import org.postgresql.util.HStoreConverter;
import org.postgresql.util.PGBinaryObject;
import org.postgresql.util.PGTime;
import org.postgresql.util.PGTimestamp;
import org.postgresql.util.PGobject;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;
import org.postgresql.util.ReaderInputStream;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.nio.charset.Charset;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.NClob;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.Ref;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.RowId;
import java.sql.SQLException;
import java.sql.SQLXML;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
//JCP! if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.2"
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
//JCP! endif
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Map;
import java.util.TimeZone;
import java.util.UUID;

class PgPreparedStatement extends PgStatement implements PreparedStatement {
  protected final CachedQuery preparedQuery; // Query fragments for prepared statement.
  protected final ParameterList preparedParameters; // Parameter values for prepared statement.

  private TimeZone defaultTimeZone;

  PgPreparedStatement(PgConnection connection, String sql, int rsType, int rsConcurrency,
      int rsHoldability) throws SQLException {
    this(connection, connection.borrowQuery(sql), rsType, rsConcurrency, rsHoldability);
  }

  PgPreparedStatement(PgConnection connection, CachedQuery query, int rsType,
      int rsConcurrency, int rsHoldability) throws SQLException {
    super(connection, rsType, rsConcurrency, rsHoldability);

    this.preparedQuery = query;
    this.preparedParameters = this.preparedQuery.query.createParameterList();
    // TODO: this.wantsGeneratedKeysAlways = true;

    setPoolable(true); // As per JDBC spec: prepared and callable statements are poolable by
  }

  @Override
  public ResultSet executeQuery(String sql) throws SQLException {
    throw new PSQLException(
        GT.tr("Can''t use query methods that take a query string on a PreparedStatement."),
        PSQLState.WRONG_OBJECT_TYPE);
  }

  /*
   * A Prepared SQL query is executed and its ResultSet is returned
   *
   * @return a ResultSet that contains the data produced by the * query - never null
   *
   * @exception SQLException if a database access error occurs
   */
  @Override
  public ResultSet executeQuery() throws SQLException {
    if (!executeWithFlags(0)) {
      throw new PSQLException(GT.tr("No results were returned by the query."), PSQLState.NO_DATA);
    }

    return getSingleResultSet();
  }

  @Override
  public int executeUpdate(String sql) throws SQLException {
    throw new PSQLException(
        GT.tr("Can''t use query methods that take a query string on a PreparedStatement."),
        PSQLState.WRONG_OBJECT_TYPE);
  }

  @Override
  public int executeUpdate() throws SQLException {
    executeWithFlags(QueryExecutor.QUERY_NO_RESULTS);

    return getNoResultUpdateCount();
  }

  @Override
  public boolean execute(String sql) throws SQLException {
    throw new PSQLException(
        GT.tr("Can''t use query methods that take a query string on a PreparedStatement."),
        PSQLState.WRONG_OBJECT_TYPE);
  }

  @Override
  public boolean execute() throws SQLException {
    return executeWithFlags(0);
  }

  public boolean executeWithFlags(int flags) throws SQLException {
    try {
      checkClosed();

      if (connection.getPreferQueryMode() == PreferQueryMode.SIMPLE) {
        flags |= QueryExecutor.QUERY_EXECUTE_AS_SIMPLE;
      }

      execute(preparedQuery, preparedParameters, flags);

      synchronized (this) {
        checkClosed();
        return (result != null && result.getResultSet() != null);
      }
    } finally {
      defaultTimeZone = null;
    }
  }

  protected boolean isOneShotQuery(CachedQuery cachedQuery) {
    if (cachedQuery == null) {
      cachedQuery = preparedQuery;
    }
    return super.isOneShotQuery(cachedQuery);
  }

  @Override
  public void closeImpl() throws SQLException {
    if (preparedQuery != null) {
      ((PgConnection) connection).releaseQuery(preparedQuery);
    }
  }

  public void setNull(int parameterIndex, int sqlType) throws SQLException {
    checkClosed();

    int oid;
    switch (sqlType) {
      case Types.SQLXML:
        oid = Oid.XML;
        break;
      case Types.INTEGER:
        oid = Oid.INT4;
        break;
      case Types.TINYINT:
      case Types.SMALLINT:
        oid = Oid.INT2;
        break;
      case Types.BIGINT:
        oid = Oid.INT8;
        break;
      case Types.REAL:
        oid = Oid.FLOAT4;
        break;
      case Types.DOUBLE:
      case Types.FLOAT:
        oid = Oid.FLOAT8;
        break;
      case Types.DECIMAL:
      case Types.NUMERIC:
        oid = Oid.NUMERIC;
        break;
      case Types.CHAR:
        oid = Oid.BPCHAR;
        break;
      case Types.VARCHAR:
      case Types.LONGVARCHAR:
        oid = connection.getStringVarcharFlag() ? Oid.VARCHAR : Oid.UNSPECIFIED;
        break;
      case Types.DATE:
        oid = Oid.DATE;
        break;
      case Types.TIME:
      //JCP! if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.2"
      case Types.TIME_WITH_TIMEZONE:
      case Types.TIMESTAMP_WITH_TIMEZONE:
      //JCP! endif
      case Types.TIMESTAMP:
        oid = Oid.UNSPECIFIED;
        break;
      case Types.BOOLEAN:
      case Types.BIT:
        oid = Oid.BOOL;
        break;
      case Types.BINARY:
      case Types.VARBINARY:
      case Types.LONGVARBINARY:
        oid = Oid.BYTEA;
        break;
      case Types.BLOB:
      case Types.CLOB:
        oid = Oid.OID;
        break;
      case Types.ARRAY:
      case Types.DISTINCT:
      case Types.STRUCT:
      case Types.NULL:
      case Types.OTHER:
        oid = Oid.UNSPECIFIED;
        break;
      default:
        // Bad Types value.
        throw new PSQLException(GT.tr("Unknown Types value."), PSQLState.INVALID_PARAMETER_TYPE);
    }
    preparedParameters.setNull(parameterIndex, oid);
  }

  public void setBoolean(int parameterIndex, boolean x) throws SQLException {
    checkClosed();
    // The key words TRUE and FALSE are the preferred (SQL-compliant) usage.
    bindLiteral(parameterIndex, x ? "TRUE" : "FALSE", Oid.BOOL);
  }

  public void setByte(int parameterIndex, byte x) throws SQLException {
    setShort(parameterIndex, x);
  }

  public void setShort(int parameterIndex, short x) throws SQLException {
    checkClosed();
    if (connection.binaryTransferSend(Oid.INT2)) {
      byte[] val = new byte[2];
      ByteConverter.int2(val, 0, x);
      bindBytes(parameterIndex, val, Oid.INT2);
      return;
    }
    bindLiteral(parameterIndex, Integer.toString(x), Oid.INT2);
  }

  public void setInt(int parameterIndex, int x) throws SQLException {
    checkClosed();
    if (connection.binaryTransferSend(Oid.INT4)) {
      byte[] val = new byte[4];
      ByteConverter.int4(val, 0, x);
      bindBytes(parameterIndex, val, Oid.INT4);
      return;
    }
    bindLiteral(parameterIndex, Integer.toString(x), Oid.INT4);
  }

  public void setLong(int parameterIndex, long x) throws SQLException {
    checkClosed();
    if (connection.binaryTransferSend(Oid.INT8)) {
      byte[] val = new byte[8];
      ByteConverter.int8(val, 0, x);
      bindBytes(parameterIndex, val, Oid.INT8);
      return;
    }
    bindLiteral(parameterIndex, Long.toString(x), Oid.INT8);
  }

  public void setFloat(int parameterIndex, float x) throws SQLException {
    checkClosed();
    if (connection.binaryTransferSend(Oid.FLOAT4)) {
      byte[] val = new byte[4];
      ByteConverter.float4(val, 0, x);
      bindBytes(parameterIndex, val, Oid.FLOAT4);
      return;
    }
    bindLiteral(parameterIndex, Float.toString(x), Oid.FLOAT8);
  }

  public void setDouble(int parameterIndex, double x) throws SQLException {
    checkClosed();
    if (connection.binaryTransferSend(Oid.FLOAT8)) {
      byte[] val = new byte[8];
      ByteConverter.float8(val, 0, x);
      bindBytes(parameterIndex, val, Oid.FLOAT8);
      return;
    }
    bindLiteral(parameterIndex, Double.toString(x), Oid.FLOAT8);
  }

  public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {
    setNumber(parameterIndex, x);
  }

  public void setString(int parameterIndex, String x) throws SQLException {
    checkClosed();
    setString(parameterIndex, x, getStringType());
  }

  private int getStringType() {
    return (connection.getStringVarcharFlag() ? Oid.VARCHAR : Oid.UNSPECIFIED);
  }

  protected void setString(int parameterIndex, String x, int oid) throws SQLException {
    // if the passed string is null, then set this column to null
    checkClosed();
    if (x == null) {
      preparedParameters.setNull(parameterIndex, oid);
    } else {
      bindString(parameterIndex, x, oid);
    }
  }

  public void setBytes(int parameterIndex, byte[] x) throws SQLException {
    checkClosed();

    if (null == x) {
      setNull(parameterIndex, Types.VARBINARY);
      return;
    }

    // Version 7.2 supports the bytea datatype for byte arrays
    byte[] copy = new byte[x.length];
    System.arraycopy(x, 0, copy, 0, x.length);
    preparedParameters.setBytea(parameterIndex, copy, 0, x.length);
  }

  public void setDate(int parameterIndex, java.sql.Date x) throws SQLException {
    setDate(parameterIndex, x, null);
  }

  public void setTime(int parameterIndex, Time x) throws SQLException {
    setTime(parameterIndex, x, null);
  }

  public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException {
    setTimestamp(parameterIndex, x, null);
  }

  private void setCharacterStreamPost71(int parameterIndex, InputStream x, int length,
      String encoding) throws SQLException {

    if (x == null) {
      setNull(parameterIndex, Types.VARCHAR);
      return;
    }
    if (length < 0) {
      throw new PSQLException(GT.tr("Invalid stream length {0}.", length),
          PSQLState.INVALID_PARAMETER_VALUE);
    }

    // Version 7.2 supports AsciiStream for all PG text types (char, varchar, text)
    // As the spec/javadoc for this method indicate this is to be used for
    // large String values (i.e. LONGVARCHAR) PG doesn't have a separate
    // long varchar datatype, but with toast all text datatypes are capable of
    // handling very large values. Thus the implementation ends up calling
    // setString() since there is no current way to stream the value to the server
    try {
      InputStreamReader inStream = new InputStreamReader(x, encoding);
      char[] chars = new char[length];
      int charsRead = 0;
      while (true) {
        int n = inStream.read(chars, charsRead, length - charsRead);
        if (n == -1) {
          break;
        }

        charsRead += n;

        if (charsRead == length) {
          break;
        }
      }

      setString(parameterIndex, new String(chars, 0, charsRead), Oid.VARCHAR);
    } catch (UnsupportedEncodingException uee) {
      throw new PSQLException(GT.tr("The JVM claims not to support the {0} encoding.", encoding),
          PSQLState.UNEXPECTED_ERROR, uee);
    } catch (IOException ioe) {
      throw new PSQLException(GT.tr("Provided InputStream failed."), PSQLState.UNEXPECTED_ERROR,
          ioe);
    }
  }

  public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException {
    checkClosed();
    setCharacterStreamPost71(parameterIndex, x, length, "ASCII");
  }

  public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException {
    checkClosed();

    setCharacterStreamPost71(parameterIndex, x, length, "UTF-8");
  }

  public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException {
    checkClosed();

    if (x == null) {
      setNull(parameterIndex, Types.VARBINARY);
      return;
    }

    if (length < 0) {
      throw new PSQLException(GT.tr("Invalid stream length {0}.", length),
          PSQLState.INVALID_PARAMETER_VALUE);
    }

    // Version 7.2 supports BinaryStream for for the PG bytea type
    // As the spec/javadoc for this method indicate this is to be used for
    // large binary values (i.e. LONGVARBINARY) PG doesn't have a separate
    // long binary datatype, but with toast the bytea datatype is capable of
    // handling very large values.
    preparedParameters.setBytea(parameterIndex, x, length);
  }

  public void clearParameters() throws SQLException {
    preparedParameters.clear();
  }

  // Helper method for setting parameters to PGobject subclasses.
  private void setPGobject(int parameterIndex, PGobject x) throws SQLException {
    String typename = x.getType();
    int oid = connection.getTypeInfo().getPGType(typename);
    if (oid == Oid.UNSPECIFIED) {
      throw new PSQLException(GT.tr("Unknown type {0}.", typename),
          PSQLState.INVALID_PARAMETER_TYPE);
    }

    if ((x instanceof PGBinaryObject) && connection.binaryTransferSend(oid)) {
      PGBinaryObject binObj = (PGBinaryObject) x;
      byte[] data = new byte[binObj.lengthInBytes()];
      binObj.toBytes(data, 0);
      bindBytes(parameterIndex, data, oid);
    } else {
      setString(parameterIndex, x.getValue(), oid);
    }
  }

  private void setMap(int parameterIndex, Map<?, ?> x) throws SQLException {
    int oid = connection.getTypeInfo().getPGType("hstore");
    if (oid == Oid.UNSPECIFIED) {
      throw new PSQLException(GT.tr("No hstore extension installed."),
          PSQLState.INVALID_PARAMETER_TYPE);
    }
    if (connection.binaryTransferSend(oid)) {
      byte[] data = HStoreConverter.toBytes(x, connection.getEncoding());
      bindBytes(parameterIndex, data, oid);
    } else {
      setString(parameterIndex, HStoreConverter.toString(x), oid);
    }
  }

  private void setNumber(int parameterIndex, Number x) throws SQLException {
    checkClosed();
    if (x == null) {
      setNull(parameterIndex, Types.DECIMAL);
    } else {
      bindLiteral(parameterIndex, x.toString(), Oid.NUMERIC);
    }
  }

  @Override
  public void setObject(int parameterIndex, Object in, int targetSqlType, int scale)
      throws SQLException {
    checkClosed();

    if (in == null) {
      setNull(parameterIndex, targetSqlType);
      return;
    }

    if (targetSqlType == Types.OTHER && in instanceof UUID
        && connection.haveMinimumServerVersion(ServerVersion.v8_3)) {
      setUuid(parameterIndex, (UUID) in);
      return;
    }

    switch (targetSqlType) {
      case Types.SQLXML:
        if (in instanceof SQLXML) {
          setSQLXML(parameterIndex, (SQLXML) in);
        } else {
          setSQLXML(parameterIndex, new PgSQLXML(connection, in.toString()));
        }
        break;
      case Types.INTEGER:
        setInt(parameterIndex, castToInt(in));
        break;
      case Types.TINYINT:
      case Types.SMALLINT:
        setShort(parameterIndex, castToShort(in));
        break;
      case Types.BIGINT:
        setLong(parameterIndex, castToLong(in));
        break;
      case Types.REAL:
        setFloat(parameterIndex, castToFloat(in));
        break;
      case Types.DOUBLE:
      case Types.FLOAT:
        setDouble(parameterIndex, castToDouble(in));
        break;
      case Types.DECIMAL:
      case Types.NUMERIC:
        setBigDecimal(parameterIndex, castToBigDecimal(in, scale));
        break;
      case Types.CHAR:
        setString(parameterIndex, castToString(in), Oid.BPCHAR);
        break;
      case Types.VARCHAR:
        setString(parameterIndex, castToString(in), getStringType());
        break;
      case Types.LONGVARCHAR:
        if (in instanceof InputStream) {
          preparedParameters.setText(parameterIndex, (InputStream)in);
        } else {
          setString(parameterIndex, castToString(in), getStringType());
        }
        break;
      case Types.DATE:
        if (in instanceof java.sql.Date) {
          setDate(parameterIndex, (java.sql.Date) in);
        } else {
          java.sql.Date tmpd;
          if (in instanceof java.util.Date) {
            tmpd = new java.sql.Date(((java.util.Date) in).getTime());
            //JCP! if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.2"
          } else if (in instanceof LocalDate) {
            setDate(parameterIndex, (LocalDate) in);
            break;
            //JCP! endif
          } else {
            tmpd = connection.getTimestampUtils().toDate(getDefaultCalendar(), in.toString());
          }
          setDate(parameterIndex, tmpd);
        }
        break;
      case Types.TIME:
        if (in instanceof java.sql.Time) {
          setTime(parameterIndex, (java.sql.Time) in);
        } else {
          java.sql.Time tmpt;
          if (in instanceof java.util.Date) {
            tmpt = new java.sql.Time(((java.util.Date) in).getTime());
            //JCP! if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.2"
          } else if (in instanceof LocalTime) {
            setTime(parameterIndex, (LocalTime) in);
            break;
            //JCP! endif
          } else {
            tmpt = connection.getTimestampUtils().toTime(getDefaultCalendar(), in.toString());
          }
          setTime(parameterIndex, tmpt);
        }
        break;
      case Types.TIMESTAMP:
        if (in instanceof PGTimestamp) {
          setObject(parameterIndex, in);
        } else if (in instanceof java.sql.Timestamp) {
          setTimestamp(parameterIndex, (java.sql.Timestamp) in);
        } else {
          java.sql.Timestamp tmpts;
          if (in instanceof java.util.Date) {
            tmpts = new java.sql.Timestamp(((java.util.Date) in).getTime());
            //JCP! if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.2"
          } else if (in instanceof LocalDateTime) {
            setTimestamp(parameterIndex, (LocalDateTime) in);
            break;
            //JCP! endif
          } else {
            tmpts = connection.getTimestampUtils().toTimestamp(getDefaultCalendar(), in.toString());
          }
          setTimestamp(parameterIndex, tmpts);
        }
        break;
      //JCP! if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.2"
      case Types.TIMESTAMP_WITH_TIMEZONE:
        if (in instanceof OffsetDateTime) {
          setTimestamp(parameterIndex, (OffsetDateTime) in);
        } else if (in instanceof PGTimestamp) {
          setObject(parameterIndex, in);
        } else {
          throw new PSQLException(
              GT.tr("Cannot cast an instance of {0} to type {1}",
                  in.getClass().getName(), "Types.TIMESTAMP_WITH_TIMEZONE"),
              PSQLState.INVALID_PARAMETER_TYPE);
        }
        break;
      //JCP! endif
      case Types.BOOLEAN:
      case Types.BIT:
        setBoolean(parameterIndex, BooleanTypeUtil.castToBoolean(in));
        break;
      case Types.BINARY:
      case Types.VARBINARY:
      case Types.LONGVARBINARY:
        setObject(parameterIndex, in);
        break;
      case Types.BLOB:
        if (in instanceof Blob) {
          setBlob(parameterIndex, (Blob) in);
        } else if (in instanceof InputStream) {
          long oid = createBlob(parameterIndex, (InputStream) in, -1);
          setLong(parameterIndex, oid);
        } else {
          throw new PSQLException(
              GT.tr("Cannot cast an instance of {0} to type {1}",
                  in.getClass().getName(), "Types.BLOB"),
              PSQLState.INVALID_PARAMETER_TYPE);
        }
        break;
      case Types.CLOB:
        if (in instanceof Clob) {
          setClob(parameterIndex, (Clob) in);
        } else {
          throw new PSQLException(
              GT.tr("Cannot cast an instance of {0} to type {1}",
                  in.getClass().getName(), "Types.CLOB"),
              PSQLState.INVALID_PARAMETER_TYPE);
        }
        break;
      case Types.ARRAY:
        if (in instanceof Array) {
          setArray(parameterIndex, (Array) in);
        } else if (PrimitiveArraySupport.isSupportedPrimitiveArray(in)) {
          setPrimitiveArray(parameterIndex, in);
        } else {
          throw new PSQLException(
              GT.tr("Cannot cast an instance of {0} to type {1}",
                  in.getClass().getName(), "Types.ARRAY"),
              PSQLState.INVALID_PARAMETER_TYPE);
        }
        break;
      case Types.DISTINCT:
        bindString(parameterIndex, in.toString(), Oid.UNSPECIFIED);
        break;
      case Types.OTHER:
        if (in instanceof PGobject) {
          setPGobject(parameterIndex, (PGobject) in);
        } else if (in instanceof Map) {
          setMap(parameterIndex, (Map<?, ?>) in);
        } else {
          bindString(parameterIndex, in.toString(), Oid.UNSPECIFIED);
        }
        break;
      default:
        throw new PSQLException(GT.tr("Unsupported Types value: {0}", targetSqlType),
            PSQLState.INVALID_PARAMETER_TYPE);
    }
  }

  private <A> void setPrimitiveArray(int parameterIndex, A in) throws SQLException {
    final PrimitiveArraySupport<A> arrayToString = PrimitiveArraySupport.getArraySupport(in);

    final TypeInfo typeInfo = connection.getTypeInfo();

    final int oid = arrayToString.getDefaultArrayTypeOid(typeInfo);

    if (arrayToString.supportBinaryRepresentation() && connection.getPreferQueryMode() != PreferQueryMode.SIMPLE) {
      bindBytes(parameterIndex, arrayToString.toBinaryRepresentation(connection, in), oid);
    } else {
      final char delim = typeInfo.getArrayDelimiter(oid);
      setString(parameterIndex, arrayToString.toArrayString(delim, in), oid);
    }
  }

  private static String asString(final Clob in) throws SQLException {
    return in.getSubString(1, (int) in.length());
  }

  private static int castToInt(final Object in) throws SQLException {
    try {
      if (in instanceof String) {
        return Integer.parseInt((String) in);
      }
      if (in instanceof Number) {
        return ((Number) in).intValue();
      }
      if (in instanceof java.util.Date) {
        return (int) ((java.util.Date) in).getTime();
      }
      if (in instanceof Boolean) {
        return (Boolean) in ? 1 : 0;
      }
      if (in instanceof Clob) {
        return Integer.parseInt(asString((Clob) in));
      }
      if (in instanceof Character) {
        return Integer.parseInt(in.toString());
      }
    } catch (final Exception e) {
      throw cannotCastException(in.getClass().getName(), "int", e);
    }
    throw cannotCastException(in.getClass().getName(), "int");
  }

  private static short castToShort(final Object in) throws SQLException {
    try {
      if (in instanceof String) {
        return Short.parseShort((String) in);
      }
      if (in instanceof Number) {
        return ((Number) in).shortValue();
      }
      if (in instanceof java.util.Date) {
        return (short) ((java.util.Date) in).getTime();
      }
      if (in instanceof Boolean) {
        return (Boolean) in ? (short) 1 : (short) 0;
      }
      if (in instanceof Clob) {
        return Short.parseShort(asString((Clob) in));
      }
      if (in instanceof Character) {
        return Short.parseShort(in.toString());
      }
    } catch (final Exception e) {
      throw cannotCastException(in.getClass().getName(), "short", e);
    }
    throw cannotCastException(in.getClass().getName(), "short");
  }

  private static long castToLong(final Object in) throws SQLException {
    try {
      if (in instanceof String) {
        return Long.parseLong((String) in);
      }
      if (in instanceof Number) {
        return ((Number) in).longValue();
      }
      if (in instanceof java.util.Date) {
        return ((java.util.Date) in).getTime();
      }
      if (in instanceof Boolean) {
        return (Boolean) in ? 1L : 0L;
      }
      if (in instanceof Clob) {
        return Long.parseLong(asString((Clob) in));
      }
      if (in instanceof Character) {
        return Long.parseLong(in.toString());
      }
    } catch (final Exception e) {
      throw cannotCastException(in.getClass().getName(), "long", e);
    }
    throw cannotCastException(in.getClass().getName(), "long");
  }

  private static float castToFloat(final Object in) throws SQLException {
    try {
      if (in instanceof String) {
        return Float.parseFloat((String) in);
      }
      if (in instanceof Number) {
        return ((Number) in).floatValue();
      }
      if (in instanceof java.util.Date) {
        return ((java.util.Date) in).getTime();
      }
      if (in instanceof Boolean) {
        return (Boolean) in ? 1f : 0f;
      }
      if (in instanceof Clob) {
        return Float.parseFloat(asString((Clob) in));
      }
      if (in instanceof Character) {
        return Float.parseFloat(in.toString());
      }
    } catch (final Exception e) {
      throw cannotCastException(in.getClass().getName(), "float", e);
    }
    throw cannotCastException(in.getClass().getName(), "float");
  }

  private static double castToDouble(final Object in) throws SQLException {
    try {
      if (in instanceof String) {
        return Double.parseDouble((String) in);
      }
      if (in instanceof Number) {
        return ((Number) in).doubleValue();
      }
      if (in instanceof java.util.Date) {
        return ((java.util.Date) in).getTime();
      }
      if (in instanceof Boolean) {
        return (Boolean) in ? 1d : 0d;
      }
      if (in instanceof Clob) {
        return Double.parseDouble(asString((Clob) in));
      }
      if (in instanceof Character) {
        return Double.parseDouble(in.toString());
      }
    } catch (final Exception e) {
      throw cannotCastException(in.getClass().getName(), "double", e);
    }
    throw cannotCastException(in.getClass().getName(), "double");
  }

  private static BigDecimal castToBigDecimal(final Object in, final int scale) throws SQLException {
    try {
      BigDecimal rc = null;
      if (in instanceof String) {
        rc = new BigDecimal((String) in);
      } else if (in instanceof BigDecimal) {
        rc = ((BigDecimal) in);
      } else if (in instanceof BigInteger) {
        rc = new BigDecimal((BigInteger) in);
      } else if (in instanceof Long || in instanceof Integer || in instanceof Short
          || in instanceof Byte) {
        rc = BigDecimal.valueOf(((Number) in).longValue());
      } else if (in instanceof Double || in instanceof Float) {
        rc = BigDecimal.valueOf(((Number) in).doubleValue());
      } else if (in instanceof java.util.Date) {
        rc = BigDecimal.valueOf(((java.util.Date) in).getTime());
      } else if (in instanceof Boolean) {
        rc = (Boolean) in ? BigDecimal.ONE : BigDecimal.ZERO;
      } else if (in instanceof Clob) {
        rc = new BigDecimal(asString((Clob) in));
      } else if (in instanceof Character) {
        rc = new BigDecimal(new char[]{(Character) in});
      }
      if (rc != null) {
        if (scale >= 0) {
          rc = rc.setScale(scale, RoundingMode.HALF_UP);
        }
        return rc;
      }
    } catch (final Exception e) {
      throw cannotCastException(in.getClass().getName(), "BigDecimal", e);
    }
    throw cannotCastException(in.getClass().getName(), "BigDecimal");
  }

  private static String castToString(final Object in) throws SQLException {
    try {
      if (in instanceof String) {
        return (String) in;
      }
      if (in instanceof Clob) {
        return asString((Clob) in);
      }
      // convert any unknown objects to string.
      return in.toString();

    } catch (final Exception e) {
      throw cannotCastException(in.getClass().getName(), "String", e);
    }
  }

  private static PSQLException cannotCastException(final String fromType, final String toType) {
    return cannotCastException(fromType, toType, null);
  }

  private static PSQLException cannotCastException(final String fromType, final String toType,
      final Exception cause) {
    return new PSQLException(
        GT.tr("Cannot convert an instance of {0} to type {1}", fromType, toType),
        PSQLState.INVALID_PARAMETER_TYPE, cause);
  }

  public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException {
    setObject(parameterIndex, x, targetSqlType, -1);
  }

  /*
   * This stores an Object into a parameter.
   */
  public void setObject(int parameterIndex, Object x) throws SQLException {
    checkClosed();
    if (x == null) {
      setNull(parameterIndex, Types.OTHER);
    } else if (x instanceof UUID && connection.haveMinimumServerVersion(ServerVersion.v8_3)) {
      setUuid(parameterIndex, (UUID) x);
    } else if (x instanceof SQLXML) {
      setSQLXML(parameterIndex, (SQLXML) x);
    } else if (x instanceof String) {
      setString(parameterIndex, (String) x);
    } else if (x instanceof BigDecimal) {
      setBigDecimal(parameterIndex, (BigDecimal) x);
    } else if (x instanceof Short) {
      setShort(parameterIndex, (Short) x);
    } else if (x instanceof Integer) {
      setInt(parameterIndex, (Integer) x);
    } else if (x instanceof Long) {
      setLong(parameterIndex, (Long) x);
    } else if (x instanceof Float) {
      setFloat(parameterIndex, (Float) x);
    } else if (x instanceof Double) {
      setDouble(parameterIndex, (Double) x);
    } else if (x instanceof byte[]) {
      setBytes(parameterIndex, (byte[]) x);
    } else if (x instanceof java.sql.Date) {
      setDate(parameterIndex, (java.sql.Date) x);
    } else if (x instanceof Time) {
      setTime(parameterIndex, (Time) x);
    } else if (x instanceof Timestamp) {
      setTimestamp(parameterIndex, (Timestamp) x);
    } else if (x instanceof Boolean) {
      setBoolean(parameterIndex, (Boolean) x);
    } else if (x instanceof Byte) {
      setByte(parameterIndex, (Byte) x);
    } else if (x instanceof Blob) {
      setBlob(parameterIndex, (Blob) x);
    } else if (x instanceof Clob) {
      setClob(parameterIndex, (Clob) x);
    } else if (x instanceof Array) {
      setArray(parameterIndex, (Array) x);
    } else if (x instanceof PGobject) {
      setPGobject(parameterIndex, (PGobject) x);
    } else if (x instanceof Character) {
      setString(parameterIndex, ((Character) x).toString());
      //JCP! if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.2"
    } else if (x instanceof LocalDate) {
      setDate(parameterIndex, (LocalDate) x);
    } else if (x instanceof LocalTime) {
      setTime(parameterIndex, (LocalTime) x);
    } else if (x instanceof LocalDateTime) {
      setTimestamp(parameterIndex, (LocalDateTime) x);
    } else if (x instanceof OffsetDateTime) {
      setTimestamp(parameterIndex, (OffsetDateTime) x);
      //JCP! endif
    } else if (x instanceof Map) {
      setMap(parameterIndex, (Map<?, ?>) x);
    } else if (x instanceof Number) {
      setNumber(parameterIndex, (Number) x);
    } else if (PrimitiveArraySupport.isSupportedPrimitiveArray(x)) {
      setPrimitiveArray(parameterIndex, x);
    } else {
      // Can't infer a type.
      throw new PSQLException(GT.tr(
          "Can''t infer the SQL type to use for an instance of {0}. Use setObject() with an explicit Types value to specify the type to use.",
          x.getClass().getName()), PSQLState.INVALID_PARAMETER_TYPE);
    }
  }

  
Returns the SQL statement with the current template values substituted.
Returns:SQL statement with the current template values substituted
/** * Returns the SQL statement with the current template values substituted. * * @return SQL statement with the current template values substituted */
public String toString() { if (preparedQuery == null) { return super.toString(); } return preparedQuery.query.toString(preparedParameters); }
Note if s is a String it should be escaped by the caller to avoid SQL injection attacks. It is not done here for efficiency reasons as most calls to this method do not require escaping as the source of the string is known safe (i.e. Integer.toString())
Params:
  • paramIndex – parameter index
  • s – value (the value should already be escaped)
  • oid – type oid
Throws:
/** * Note if s is a String it should be escaped by the caller to avoid SQL injection attacks. It is * not done here for efficiency reasons as most calls to this method do not require escaping as * the source of the string is known safe (i.e. {@code Integer.toString()}) * * @param paramIndex parameter index * @param s value (the value should already be escaped) * @param oid type oid * @throws SQLException if something goes wrong */
protected void bindLiteral(int paramIndex, String s, int oid) throws SQLException { preparedParameters.setLiteralParameter(paramIndex, s, oid); } protected void bindBytes(int paramIndex, byte[] b, int oid) throws SQLException { preparedParameters.setBinaryParameter(paramIndex, b, oid); }
This version is for values that should turn into strings e.g. setString directly calls bindString with no escaping; the per-protocol ParameterList does escaping as needed.
Params:
  • paramIndex – parameter index
  • s – value
  • oid – type oid
Throws:
/** * This version is for values that should turn into strings e.g. setString directly calls * bindString with no escaping; the per-protocol ParameterList does escaping as needed. * * @param paramIndex parameter index * @param s value * @param oid type oid * @throws SQLException if something goes wrong */
private void bindString(int paramIndex, String s, int oid) throws SQLException { preparedParameters.setStringParameter(paramIndex, s, oid); } @Override public boolean isUseServerPrepare() { return (preparedQuery != null && mPrepareThreshold != 0 && preparedQuery.getExecuteCount() + 1 >= mPrepareThreshold); } @Override public void addBatch(String sql) throws SQLException { checkClosed(); throw new PSQLException( GT.tr("Can''t use query methods that take a query string on a PreparedStatement."), PSQLState.WRONG_OBJECT_TYPE); } @Override public void addBatch() throws SQLException { checkClosed(); if (batchStatements == null) { batchStatements = new ArrayList<Query>(); batchParameters = new ArrayList<ParameterList>(); } // we need to create copies of our parameters, otherwise the values can be changed batchParameters.add(preparedParameters.copy()); Query query = preparedQuery.query; if (!(query instanceof BatchedQuery) || batchStatements.isEmpty()) { batchStatements.add(query); } } public ResultSetMetaData getMetaData() throws SQLException { checkClosed(); ResultSet rs = getResultSet(); if (rs == null || ((PgResultSet) rs).isResultSetClosed()) { // OK, we haven't executed it yet, or it was closed // we've got to go to the backend // for more info. We send the full query, but just don't // execute it. int flags = QueryExecutor.QUERY_ONESHOT | QueryExecutor.QUERY_DESCRIBE_ONLY | QueryExecutor.QUERY_SUPPRESS_BEGIN; StatementResultHandler handler = new StatementResultHandler(); connection.getQueryExecutor().execute(preparedQuery.query, preparedParameters, handler, 0, 0, flags); ResultWrapper wrapper = handler.getResults(); if (wrapper != null) { rs = wrapper.getResultSet(); } } if (rs != null) { return rs.getMetaData(); } return null; } public void setArray(int i, java.sql.Array x) throws SQLException { checkClosed(); if (null == x) { setNull(i, Types.ARRAY); return; } // This only works for Array implementations that return a valid array // literal from Array.toString(), such as the implementation we return // from ResultSet.getArray(). Eventually we need a proper implementation // here that works for any Array implementation. String typename = x.getBaseTypeName(); int oid = connection.getTypeInfo().getPGArrayType(typename); if (oid == Oid.UNSPECIFIED) { throw new PSQLException(GT.tr("Unknown type {0}.", typename), PSQLState.INVALID_PARAMETER_TYPE); } if (x instanceof PgArray) { PgArray arr = (PgArray) x; if (arr.isBinary()) { bindBytes(i, arr.toBytes(), oid); return; } } setString(i, x.toString(), oid); } protected long createBlob(int i, InputStream inputStream, long length) throws SQLException { LargeObjectManager lom = connection.getLargeObjectAPI(); long oid = lom.createLO(); LargeObject lob = lom.open(oid); OutputStream outputStream = lob.getOutputStream(); byte[] buf = new byte[4096]; try { long remaining; if (length > 0) { remaining = length; } else { remaining = Long.MAX_VALUE; } int numRead = inputStream.read(buf, 0, (length > 0 && remaining < buf.length ? (int) remaining : buf.length)); while (numRead != -1 && remaining > 0) { remaining -= numRead; outputStream.write(buf, 0, numRead); numRead = inputStream.read(buf, 0, (length > 0 && remaining < buf.length ? (int) remaining : buf.length)); } } catch (IOException se) { throw new PSQLException(GT.tr("Unexpected error writing large object to database."), PSQLState.UNEXPECTED_ERROR, se); } finally { try { outputStream.close(); } catch (Exception e) { } } return oid; } public void setBlob(int i, Blob x) throws SQLException { checkClosed(); if (x == null) { setNull(i, Types.BLOB); return; } InputStream inStream = x.getBinaryStream(); try { long oid = createBlob(i, inStream, x.length()); setLong(i, oid); } finally { try { inStream.close(); } catch (Exception e) { } } } private String readerToString(Reader value, int maxLength) throws SQLException { try { int bufferSize = Math.min(maxLength, 1024); StringBuilder v = new StringBuilder(bufferSize); char[] buf = new char[bufferSize]; int nRead = 0; while (nRead > -1 && v.length() < maxLength) { nRead = value.read(buf, 0, Math.min(bufferSize, maxLength - v.length())); if (nRead > 0) { v.append(buf, 0, nRead); } } return v.toString(); } catch (IOException ioe) { throw new PSQLException(GT.tr("Provided Reader failed."), PSQLState.UNEXPECTED_ERROR, ioe); } } public void setCharacterStream(int i, java.io.Reader x, int length) throws SQLException { checkClosed(); if (x == null) { setNull(i, Types.VARCHAR); return; } if (length < 0) { throw new PSQLException(GT.tr("Invalid stream length {0}.", length), PSQLState.INVALID_PARAMETER_VALUE); } // Version 7.2 supports CharacterStream for for the PG text types // As the spec/javadoc for this method indicate this is to be used for // large text values (i.e. LONGVARCHAR) PG doesn't have a separate // long varchar datatype, but with toast all the text datatypes are capable of // handling very large values. Thus the implementation ends up calling // setString() since there is no current way to stream the value to the server setString(i, readerToString(x, length)); } @Override public void setClob(int i, Clob x) throws SQLException { checkClosed(); if (x == null) { setNull(i, Types.CLOB); return; } Reader inStream = x.getCharacterStream(); int length = (int) x.length(); LargeObjectManager lom = connection.getLargeObjectAPI(); long oid = lom.createLO(); LargeObject lob = lom.open(oid); Charset connectionCharset = Charset.forName(connection.getEncoding().name()); OutputStream los = lob.getOutputStream(); Writer lw = new OutputStreamWriter(los, connectionCharset); try { // could be buffered, but then the OutputStream returned by LargeObject // is buffered internally anyhow, so there would be no performance // boost gained, if anything it would be worse! int c = inStream.read(); int p = 0; while (c > -1 && p < length) { lw.write(c); c = inStream.read(); p++; } lw.close(); } catch (IOException se) { throw new PSQLException(GT.tr("Unexpected error writing large object to database."), PSQLState.UNEXPECTED_ERROR, se); } // lob is closed by the stream so don't call lob.close() setLong(i, oid); } public void setNull(int parameterIndex, int t, String typeName) throws SQLException { if (typeName == null) { setNull(parameterIndex, t); return; } checkClosed(); TypeInfo typeInfo = connection.getTypeInfo(); int oid = typeInfo.getPGType(typeName); preparedParameters.setNull(parameterIndex, oid); } public void setRef(int i, Ref x) throws SQLException { throw Driver.notImplemented(this.getClass(), "setRef(int,Ref)"); } public void setDate(int i, java.sql.Date d, java.util.Calendar cal) throws SQLException { checkClosed(); if (d == null) { setNull(i, Types.DATE); return; } if (connection.binaryTransferSend(Oid.DATE)) { byte[] val = new byte[4]; TimeZone tz = cal != null ? cal.getTimeZone() : null; connection.getTimestampUtils().toBinDate(tz, val, d); preparedParameters.setBinaryParameter(i, val, Oid.DATE); return; } // We must use UNSPECIFIED here, or inserting a Date-with-timezone into a // timestamptz field does an unexpected rotation by the server's TimeZone: // // We want to interpret 2005/01/01 with calendar +0100 as // "local midnight in +0100", but if we go via date it interprets it // as local midnight in the server's timezone: // template1=# select '2005-01-01+0100'::timestamptz; // timestamptz // ------------------------ // 2005-01-01 02:00:00+03 // (1 row) // template1=# select '2005-01-01+0100'::date::timestamptz; // timestamptz // ------------------------ // 2005-01-01 00:00:00+03 // (1 row) if (cal == null) { cal = getDefaultCalendar(); } bindString(i, connection.getTimestampUtils().toString(cal, d), Oid.UNSPECIFIED); } public void setTime(int i, Time t, java.util.Calendar cal) throws SQLException { checkClosed(); if (t == null) { setNull(i, Types.TIME); return; } int oid = Oid.UNSPECIFIED; // If a PGTime is used, we can define the OID explicitly. if (t instanceof PGTime) { PGTime pgTime = (PGTime) t; if (pgTime.getCalendar() == null) { oid = Oid.TIME; } else { oid = Oid.TIMETZ; cal = pgTime.getCalendar(); } } if (cal == null) { cal = getDefaultCalendar(); } bindString(i, connection.getTimestampUtils().toString(cal, t), oid); } public void setTimestamp(int i, Timestamp t, java.util.Calendar cal) throws SQLException { checkClosed(); if (t == null) { setNull(i, Types.TIMESTAMP); return; } int oid = Oid.UNSPECIFIED; // Use UNSPECIFIED as a compromise to get both TIMESTAMP and TIMESTAMPTZ working. // This is because you get this in a +1300 timezone: // // template1=# select '2005-01-01 15:00:00 +1000'::timestamptz; // timestamptz // ------------------------ // 2005-01-01 18:00:00+13 // (1 row) // template1=# select '2005-01-01 15:00:00 +1000'::timestamp; // timestamp // --------------------- // 2005-01-01 15:00:00 // (1 row) // template1=# select '2005-01-01 15:00:00 +1000'::timestamptz::timestamp; // timestamp // --------------------- // 2005-01-01 18:00:00 // (1 row) // So we want to avoid doing a timestamptz -> timestamp conversion, as that // will first convert the timestamptz to an equivalent time in the server's // timezone (+1300, above), then turn it into a timestamp with the "wrong" // time compared to the string we originally provided. But going straight // to timestamp is OK as the input parser for timestamp just throws away // the timezone part entirely. Since we don't know ahead of time what type // we're actually dealing with, UNSPECIFIED seems the lesser evil, even if it // does give more scope for type-mismatch errors being silently hidden. // If a PGTimestamp is used, we can define the OID explicitly. if (t instanceof PGTimestamp) { PGTimestamp pgTimestamp = (PGTimestamp) t; if (pgTimestamp.getCalendar() == null) { oid = Oid.TIMESTAMP; } else { oid = Oid.TIMESTAMPTZ; cal = pgTimestamp.getCalendar(); } } if (cal == null) { cal = getDefaultCalendar(); } bindString(i, connection.getTimestampUtils().toString(cal, t), oid); } //JCP! if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.2" private void setDate(int i, LocalDate localDate) throws SQLException { int oid = Oid.DATE; bindString(i, connection.getTimestampUtils().toString(localDate), oid); } private void setTime(int i, LocalTime localTime) throws SQLException { int oid = Oid.TIME; bindString(i, connection.getTimestampUtils().toString(localTime), oid); } private void setTimestamp(int i, LocalDateTime localDateTime) throws SQLException { int oid = Oid.TIMESTAMP; bindString(i, connection.getTimestampUtils().toString(localDateTime), oid); } private void setTimestamp(int i, OffsetDateTime offsetDateTime) throws SQLException { int oid = Oid.TIMESTAMPTZ; bindString(i, connection.getTimestampUtils().toString(offsetDateTime), oid); } //JCP! endif public ParameterMetaData createParameterMetaData(BaseConnection conn, int[] oids) throws SQLException { return new PgParameterMetaData(conn, oids); } //JCP! if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.2" public void setObject(int parameterIndex, Object x, java.sql.SQLType targetSqlType, int scaleOrLength) throws SQLException { throw Driver.notImplemented(this.getClass(), "setObject"); } public void setObject(int parameterIndex, Object x, java.sql.SQLType targetSqlType) throws SQLException { throw Driver.notImplemented(this.getClass(), "setObject"); } //JCP! endif public void setRowId(int parameterIndex, RowId x) throws SQLException { throw Driver.notImplemented(this.getClass(), "setRowId(int, RowId)"); } public void setNString(int parameterIndex, String value) throws SQLException { throw Driver.notImplemented(this.getClass(), "setNString(int, String)"); } public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException { throw Driver.notImplemented(this.getClass(), "setNCharacterStream(int, Reader, long)"); } public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException { throw Driver.notImplemented(this.getClass(), "setNCharacterStream(int, Reader)"); } public void setCharacterStream(int parameterIndex, Reader value, long length) throws SQLException { throw Driver.notImplemented(this.getClass(), "setCharacterStream(int, Reader, long)"); } public void setCharacterStream(int parameterIndex, Reader value) throws SQLException { if (connection.getPreferQueryMode() == PreferQueryMode.SIMPLE) { String s = (value != null) ? readerToString(value, Integer.MAX_VALUE) : null; setString(parameterIndex, s); return; } InputStream is = (value != null) ? new ReaderInputStream(value) : null; setObject(parameterIndex, is, Types.LONGVARCHAR); } public void setBinaryStream(int parameterIndex, InputStream value, long length) throws SQLException { if (length > Integer.MAX_VALUE) { throw new PSQLException(GT.tr("Object is too large to send over the protocol."), PSQLState.NUMERIC_CONSTANT_OUT_OF_RANGE); } preparedParameters.setBytea(parameterIndex, value, (int) length); } public void setBinaryStream(int parameterIndex, InputStream value) throws SQLException { preparedParameters.setBytea(parameterIndex, value); } public void setAsciiStream(int parameterIndex, InputStream value, long length) throws SQLException { throw Driver.notImplemented(this.getClass(), "setAsciiStream(int, InputStream, long)"); } public void setAsciiStream(int parameterIndex, InputStream value) throws SQLException { throw Driver.notImplemented(this.getClass(), "setAsciiStream(int, InputStream)"); } public void setNClob(int parameterIndex, NClob value) throws SQLException { throw Driver.notImplemented(this.getClass(), "setNClob(int, NClob)"); } public void setClob(int parameterIndex, Reader reader, long length) throws SQLException { throw Driver.notImplemented(this.getClass(), "setClob(int, Reader, long)"); } public void setClob(int parameterIndex, Reader reader) throws SQLException { throw Driver.notImplemented(this.getClass(), "setClob(int, Reader)"); } public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException { checkClosed(); if (inputStream == null) { setNull(parameterIndex, Types.BLOB); return; } if (length < 0) { throw new PSQLException(GT.tr("Invalid stream length {0}.", length), PSQLState.INVALID_PARAMETER_VALUE); } long oid = createBlob(parameterIndex, inputStream, length); setLong(parameterIndex, oid); } public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException { checkClosed(); if (inputStream == null) { setNull(parameterIndex, Types.BLOB); return; } long oid = createBlob(parameterIndex, inputStream, -1); setLong(parameterIndex, oid); } public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException { throw Driver.notImplemented(this.getClass(), "setNClob(int, Reader, long)"); } public void setNClob(int parameterIndex, Reader reader) throws SQLException { throw Driver.notImplemented(this.getClass(), "setNClob(int, Reader)"); } public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException { checkClosed(); String stringValue = xmlObject == null ? null : xmlObject.getString(); if (stringValue == null) { setNull(parameterIndex, Types.SQLXML); } else { setString(parameterIndex, stringValue, Oid.XML); } } private void setUuid(int parameterIndex, UUID uuid) throws SQLException { if (connection.binaryTransferSend(Oid.UUID)) { byte[] val = new byte[16]; ByteConverter.int8(val, 0, uuid.getMostSignificantBits()); ByteConverter.int8(val, 8, uuid.getLeastSignificantBits()); bindBytes(parameterIndex, val, Oid.UUID); } else { bindLiteral(parameterIndex, uuid.toString(), Oid.UUID); } } public void setURL(int parameterIndex, java.net.URL x) throws SQLException { throw Driver.notImplemented(this.getClass(), "setURL(int,URL)"); } @Override public int[] executeBatch() throws SQLException { try { // Note: in batch prepared statements batchStatements == 1, and batchParameters is equal // to the number of addBatch calls // batchParameters might be empty in case of empty batch if (batchParameters != null && batchParameters.size() > 1 && mPrepareThreshold > 0) { // Use server-prepared statements when there's more than one statement in a batch // Technically speaking, it might cause to create a server-prepared statement // just for 2 executions even for prepareThreshold=5. That however should be // acceptable since prepareThreshold is a optimization kind of parameter. this.preparedQuery.increaseExecuteCount(mPrepareThreshold); } return super.executeBatch(); } finally { defaultTimeZone = null; } } private Calendar getDefaultCalendar() { TimestampUtils timestampUtils = connection.getTimestampUtils(); if (timestampUtils.hasFastDefaultTimeZone()) { return timestampUtils.getSharedCalendar(null); } Calendar sharedCalendar = timestampUtils.getSharedCalendar(defaultTimeZone); if (defaultTimeZone == null) { defaultTimeZone = sharedCalendar.getTimeZone(); } return sharedCalendar; } public ParameterMetaData getParameterMetaData() throws SQLException { int flags = QueryExecutor.QUERY_ONESHOT | QueryExecutor.QUERY_DESCRIBE_ONLY | QueryExecutor.QUERY_SUPPRESS_BEGIN; StatementResultHandler handler = new StatementResultHandler(); connection.getQueryExecutor().execute(preparedQuery.query, preparedParameters, handler, 0, 0, flags); int[] oids = preparedParameters.getTypeOIDs(); if (oids != null) { return createParameterMetaData(connection, oids); } return null; } @Override protected void transformQueriesAndParameters() throws SQLException { if (batchParameters.size() <= 1 || !(preparedQuery.query instanceof BatchedQuery)) { return; } BatchedQuery originalQuery = (BatchedQuery) preparedQuery.query; // Single query cannot have more than {@link Short#MAX_VALUE} binds, thus // the number of multi-values blocks should be capped. // Typically, it does not make much sense to batch more than 128 rows: performance // does not improve much after updating 128 statements with 1 multi-valued one, thus // we cap maximum batch size and split there. final int bindCount = originalQuery.getBindCount(); final int highestBlockCount = 128; final int maxValueBlocks = bindCount == 0 ? 1024 /* if no binds, use 1024 rows */ : Integer.highestOneBit( // deriveForMultiBatch supports powers of two only Math.min(Math.max(1, (Short.MAX_VALUE - 1) / bindCount), highestBlockCount)); int unprocessedBatchCount = batchParameters.size(); final int fullValueBlocksCount = unprocessedBatchCount / maxValueBlocks; final int partialValueBlocksCount = Integer.bitCount(unprocessedBatchCount % maxValueBlocks); final int count = fullValueBlocksCount + partialValueBlocksCount; ArrayList<Query> newBatchStatements = new ArrayList<Query>(count); ArrayList<ParameterList> newBatchParameters = new ArrayList<ParameterList>(count); int offset = 0; for (int i = 0; i < count; i++) { int valueBlock; if (unprocessedBatchCount >= maxValueBlocks) { valueBlock = maxValueBlocks; } else { valueBlock = Integer.highestOneBit(unprocessedBatchCount); } // Find appropriate batch for block count. BatchedQuery bq = originalQuery.deriveForMultiBatch(valueBlock); ParameterList newPl = bq.createParameterList(); for (int j = 0; j < valueBlock; j++) { ParameterList pl = batchParameters.get(offset++); newPl.appendAll(pl); } newBatchStatements.add(bq); newBatchParameters.add(newPl); unprocessedBatchCount -= valueBlock; } batchStatements = newBatchStatements; batchParameters = newBatchParameters; } }