// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (c) 2012-2014 Monty Program Ab
// Copyright (c) 2015-2024 MariaDB Corporation Ab
package org.mariadb.jdbc;

import java.io.InputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.net.URL;
import java.sql.*;
import java.sql.Date;
import java.sql.Statement;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
import org.mariadb.jdbc.client.result.Result;
import org.mariadb.jdbc.codec.Parameter;
import org.mariadb.jdbc.export.ExceptionFactory;
import org.mariadb.jdbc.util.ParameterList;

/** Common methods for function/stored procedure */
public abstract class BaseCallableStatement extends ServerPreparedStatement
    implements CallableStatement {

  /** Database name */
  protected final String databaseName;

  /** Procedure name */
  protected final String procedureName;

  /** Declared output parameters */
  protected final Set<Integer> outputParameters = new HashSet<>();

  /** parameter metadata */
  protected CallableParameterMetaData parameterMetaData = null;

  /** output parameter result */
  private Result outputResult = null;

  /**
   * Constructor
   *
   * @param sql sql command
   * @param con connection
   * @param lock thread safe lock
   * @param databaseName database name
   * @param procedureName procedure name
   * @param canUseServerTimeout indicate if server support server timeout
   * @param canUseServerMaxRows indicate if server support server max rows
   * @param canCachePrepStmts can cache server prepared result
   * @param resultSetType resultset type
   * @param resultSetConcurrency resultset concurrency
   * @param defaultFetchSize default fetch size
   * @throws SQLException if prepare fails
   */
  public BaseCallableStatement(
      String sql,
      Connection con,
      ReentrantLock lock,
      String databaseName,
      String procedureName,
      boolean canUseServerTimeout,
      boolean canUseServerMaxRows,
      boolean canCachePrepStmts,
      int resultSetType,
      int resultSetConcurrency,
      int defaultFetchSize)
      throws SQLException {
    super(
        sql,
        con,
        lock,
        canUseServerTimeout,
        canUseServerMaxRows,
        canCachePrepStmts,
        Statement.RETURN_GENERATED_KEYS,
        resultSetType,
        resultSetConcurrency,
        defaultFetchSize);
    this.databaseName = databaseName;
    this.procedureName = procedureName;
  }

  /**
   * Indicate if callable statement is a function or a stored procedure
   *
   * @return indicate if is a function
   */
  public abstract boolean isFunction();

  /**
   * Output result without output parameters
   *
   * @param i index
   * @throws SQLException if any exception
   */
  protected void outputResultFromRes(int i) throws SQLException {
    this.outputResult = (Result) this.results.remove(this.results.size() - i);
    this.outputResult.next();
  }

  /**
   * Registers the OUT parameter in ordinal position <code>parameterIndex</code> to the JDBC type
   * <code>sqlType</code>. All OUT parameters must be registered before a stored procedure is
   * executed.
   *
   * <p>The JDBC type specified by <code>sqlType</code> for an OUT parameter determines the Java
   * type that must be used in the <code>get</code> method to read the value of that parameter.
   *
   * <p>If the JDBC type expected to be returned to this output parameter is specific to this
   * particular database, <code>sqlType</code> should be <code>java.sql.Types.OTHER</code>. The
   * method {@link #getObject} retrieves the value.
   *
   * @param parameterIndex the first parameter is 1, the second is 2, and so on
   * @param sqlType the JDBC type code defined by <code>java.sql.Types</code>. If the parameter is
   *     of JDBC type <code>NUMERIC</code> or <code>DECIMAL</code>, the version of <code>
   *     registerOutParameter</code> that accepts a scale value should be used.
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if <code>sqlType</code> is a <code>ARRAY</code>, <code>
   *     BLOB</code>, <code>CLOB</code>, <code>DATALINK</code>, <code>JAVA_OBJECT</code>, <code>
   *     NCHAR</code>, <code>NCLOB</code>, <code>NVARCHAR</code>, <code>LONGNVARCHAR</code>, <code>
   *     REF</code>, <code>ROWID</code>, <code>SQLXML</code> or <code>STRUCT</code> data type and
   *     the JDBC driver does not support this data type
   * @see Types
   */
  @Override
  public void registerOutParameter(int parameterIndex, int sqlType) throws SQLException {
    checkIndex(parameterIndex);
    outputParameters.add(parameterIndex);
    if (!parameters.containsKey(parameterIndex - 1))
      parameters.set(parameterIndex - 1, Parameter.NULL_PARAMETER);
  }

  private void checkIndex(int index) throws SQLException {
    if (index <= 0
        || (prepareResult != null
            && index > (prepareResult.getParameters().length + (isFunction() ? 1 : 0)))
        || (prepareResult == null
            && parameterMetaData != null
            && index > parameterMetaData.getParameterCount())) {
      throw exceptionFactory().create(String.format("wrong parameter index %s", index));
    }
  }

  /**
   * Registers the parameter in ordinal position <code>parameterIndex</code> to be of JDBC type
   * <code>sqlType</code>. All OUT parameters must be registered before a stored procedure is
   * executed.
   *
   * <p>The JDBC type specified by <code>sqlType</code> for an OUT parameter determines the Java
   * type that must be used in the <code>get</code> method to read the value of that parameter.
   *
   * <p>This version of <code>registerOutParameter</code> should be used when the parameter is of
   * JDBC type <code>NUMERIC</code> or <code>DECIMAL</code>.
   *
   * @param parameterIndex the first parameter is 1, the second is 2, and so on
   * @param sqlType the SQL type code defined by <code>java.sql.Types</code>.
   * @param scale the desired number of digits to the right of the decimal point. It must be greater
   *     than or equal to zero.
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if <code>sqlType</code> is a <code>ARRAY</code>, <code>
   *     BLOB</code>, <code>CLOB</code>, <code>DATALINK</code>, <code>JAVA_OBJECT</code>, <code>
   *     NCHAR</code>, <code>NCLOB</code>, <code>NVARCHAR</code>, <code>LONGNVARCHAR</code>, <code>
   *     REF</code>, <code>ROWID</code>, <code>SQLXML</code> or <code>STRUCT</code> data type and
   *     the JDBC driver does not support this data type
   * @see Types
   */
  @Override
  public void registerOutParameter(int parameterIndex, int sqlType, int scale) throws SQLException {
    registerOutParameter(parameterIndex, sqlType);
  }

  @Override
  public void clearParameters() throws SQLException {
    checkNotClosed();
    parameters = new ParameterList();
    outputParameters.stream().forEach(index -> parameters.set(index - 1, Parameter.NULL_PARAMETER));
  }

  /**
   * Retrieves whether the last OUT parameter read had the value of SQL <code>NULL</code>. Note that
   * this method should be called only after calling a getter method; otherwise, there is no value
   * to use in determining whether it is <code>null</code> or not.
   *
   * @return <code>true</code> if the last parameter read was SQL <code>NULL</code>; <code>false
   *     </code> otherwise
   * @throws SQLException if a database access error occurs or this method is called on a closed
   *     <code>CallableStatement</code>
   */
  @Override
  public boolean wasNull() throws SQLException {
    checkNotClosed();
    checkOutputResult();
    return outputResult.wasNull();
  }

  private int idxToOutIdx(int idx) throws SQLException {
    int outputIndex = 1;
    if (idx < 1) throw exceptionFactory().create(String.format("wrong index %s", idx));
    if (!outputParameters.contains(idx))
      throw exceptionFactory().create(String.format("index %s not declared as output", idx));
    for (int i = 1; i < idx; i++) {
      if (outputParameters.contains(i)) outputIndex++;
    }

    return outputIndex;
  }

  /**
   * Check if statement is closed, and throw exception if so.
   *
   * @throws SQLException if statement close
   */
  protected void checkOutputResult() throws SQLException {
    if (outputResult == null) {
      throw exceptionFactory().create("No output result");
    }
  }

  /**
   * Retrieves the value of the designated JDBC <code>CHAR</code>, <code>VARCHAR</code>, or <code>
   * LONGVARCHAR</code> parameter as a <code>String</code> in the Java programming language.
   *
   * <p>For the fixed-length type JDBC <code>CHAR</code>, the <code>String</code> object returned
   * has exactly the same value the SQL <code>CHAR</code> value had in the database, including any
   * padding added by the database.
   *
   * @param parameterIndex the first parameter is 1, the second is 2, and so on
   * @return the parameter value. If the value is SQL <code>NULL</code>, the result is <code>null
   *     </code>.
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @see #setString
   */
  @Override
  public String getString(int parameterIndex) throws SQLException {
    checkNotClosed();
    checkOutputResult();
    return outputResult.getString(idxToOutIdx(parameterIndex));
  }

  /**
   * Retrieves the value of the designated JDBC <code>BIT</code> or <code>BOOLEAN</code> parameter
   * as a <code>boolean</code> in the Java programming language.
   *
   * @param parameterIndex the first parameter is 1, the second is 2, and so on
   * @return the parameter value. If the value is SQL <code>NULL</code>, the result is <code>false
   *     </code>.
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @see #setBoolean
   */
  @Override
  public boolean getBoolean(int parameterIndex) throws SQLException {
    checkNotClosed();
    checkOutputResult();
    return outputResult.getBoolean(idxToOutIdx(parameterIndex));
  }

  /**
   * Retrieves the value of the designated JDBC <code>TINYINT</code> parameter as a <code>byte
   * </code> in the Java programming language.
   *
   * @param parameterIndex the first parameter is 1, the second is 2, and so on
   * @return the parameter value. If the value is SQL <code>NULL</code>, the result is <code>0
   *     </code>.
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @see #setByte
   */
  @Override
  public byte getByte(int parameterIndex) throws SQLException {
    checkNotClosed();
    checkOutputResult();
    return outputResult.getByte(idxToOutIdx(parameterIndex));
  }

  /**
   * Retrieves the value of the designated JDBC <code>SMALLINT</code> parameter as a <code>short
   * </code> in the Java programming language.
   *
   * @param parameterIndex the first parameter is 1, the second is 2, and so on
   * @return the parameter value. If the value is SQL <code>NULL</code>, the result is <code>0
   *     </code>.
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @see #setShort
   */
  @Override
  public short getShort(int parameterIndex) throws SQLException {
    checkNotClosed();
    checkOutputResult();
    return outputResult.getShort(idxToOutIdx(parameterIndex));
  }

  /**
   * Retrieves the value of the designated JDBC <code>INTEGER</code> parameter as an <code>int
   * </code> in the Java programming language.
   *
   * @param parameterIndex the first parameter is 1, the second is 2, and so on
   * @return the parameter value. If the value is SQL <code>NULL</code>, the result is <code>0
   *     </code>.
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @see #setInt
   */
  @Override
  public int getInt(int parameterIndex) throws SQLException {
    checkNotClosed();
    checkOutputResult();
    return outputResult.getInt(idxToOutIdx(parameterIndex));
  }

  /**
   * Retrieves the value of the designated JDBC <code>BIGINT</code> parameter as a <code>long</code>
   * in the Java programming language.
   *
   * @param parameterIndex the first parameter is 1, the second is 2, and so on
   * @return the parameter value. If the value is SQL <code>NULL</code>, the result is <code>0
   *     </code>.
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @see #setLong
   */
  @Override
  public long getLong(int parameterIndex) throws SQLException {
    checkNotClosed();
    checkOutputResult();
    return outputResult.getLong(idxToOutIdx(parameterIndex));
  }

  /**
   * Retrieves the value of the designated JDBC <code>FLOAT</code> parameter as a <code>float</code>
   * in the Java programming language.
   *
   * @param parameterIndex the first parameter is 1, the second is 2, and so on
   * @return the parameter value. If the value is SQL <code>NULL</code>, the result is <code>0
   *     </code>.
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @see #setFloat
   */
  @Override
  public float getFloat(int parameterIndex) throws SQLException {
    checkNotClosed();
    checkOutputResult();
    return outputResult.getFloat(idxToOutIdx(parameterIndex));
  }

  /**
   * Retrieves the value of the designated JDBC <code>DOUBLE</code> parameter as a <code>double
   * </code> in the Java programming language.
   *
   * @param parameterIndex the first parameter is 1, the second is 2, and so on
   * @return the parameter value. If the value is SQL <code>NULL</code>, the result is <code>0
   *     </code>.
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @see #setDouble
   */
  @Override
  public double getDouble(int parameterIndex) throws SQLException {
    checkNotClosed();
    checkOutputResult();
    return outputResult.getDouble(idxToOutIdx(parameterIndex));
  }

  /**
   * Retrieves the value of the designated JDBC <code>NUMERIC</code> parameter as a <code>
   * java.math.BigDecimal</code> object with <i>scale</i> digits to the right of the decimal point.
   *
   * @param parameterIndex the first parameter is 1, the second is 2, and so on
   * @param scale the number of digits to the right of the decimal point
   * @return the parameter value. If the value is SQL <code>NULL</code>, the result is <code>null
   *     </code>.
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #setBigDecimal
   * @deprecated use <code>getBigDecimal(int parameterIndex)</code> or <code>
   *     getBigDecimal(String parameterName)</code>
   */
  @Override
  @Deprecated
  public BigDecimal getBigDecimal(int parameterIndex, int scale) throws SQLException {
    checkNotClosed();
    checkOutputResult();
    return outputResult.getBigDecimal(idxToOutIdx(parameterIndex), scale);
  }

  /**
   * Retrieves the value of the designated JDBC <code>BINARY</code> or <code>VARBINARY</code>
   * parameter as an array of <code>byte</code> values in the Java programming language.
   *
   * @param parameterIndex the first parameter is 1, the second is 2, and so on
   * @return the parameter value. If the value is SQL <code>NULL</code>, the result is <code>null
   *     </code>.
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @see #setBytes
   */
  @Override
  public byte[] getBytes(int parameterIndex) throws SQLException {
    checkNotClosed();
    checkOutputResult();
    return outputResult.getBytes(idxToOutIdx(parameterIndex));
  }

  /**
   * Retrieves the value of the designated JDBC <code>DATE</code> parameter as a <code>java.sql.Date
   * </code> object.
   *
   * @param parameterIndex the first parameter is 1, the second is 2, and so on
   * @return the parameter value. If the value is SQL <code>NULL</code>, the result is <code>null
   *     </code>.
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @see #setDate
   */
  @Override
  public Date getDate(int parameterIndex) throws SQLException {
    checkNotClosed();
    checkOutputResult();
    return outputResult.getDate(idxToOutIdx(parameterIndex));
  }

  /**
   * Retrieves the value of the designated JDBC <code>TIME</code> parameter as a <code>java.sql.Time
   * </code> object.
   *
   * @param parameterIndex the first parameter is 1, the second is 2, and so on
   * @return the parameter value. If the value is SQL <code>NULL</code>, the result is <code>null
   *     </code>.
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @see #setTime
   */
  @Override
  public Time getTime(int parameterIndex) throws SQLException {
    checkNotClosed();
    checkOutputResult();
    return outputResult.getTime(idxToOutIdx(parameterIndex));
  }

  /**
   * Retrieves the value of the designated JDBC <code>TIMESTAMP</code> parameter as a <code>
   * java.sql.Timestamp</code> object.
   *
   * @param parameterIndex the first parameter is 1, the second is 2, and so on
   * @return the parameter value. If the value is SQL <code>NULL</code>, the result is <code>null
   *     </code>.
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @see #setTimestamp
   */
  @Override
  public Timestamp getTimestamp(int parameterIndex) throws SQLException {
    checkNotClosed();
    checkOutputResult();
    return outputResult.getTimestamp(idxToOutIdx(parameterIndex));
  }

  /**
   * Retrieves the value of the designated parameter as an <code>Object</code> in the Java
   * programming language. If the value is an SQL <code>NULL</code>, the driver returns a Java
   * <code>null</code>.
   *
   * <p>This method returns a Java object whose type corresponds to the JDBC type that was
   * registered for this parameter using the method <code>registerOutParameter</code>. By
   * registering the target JDBC types as <code>java.sql.Types.OTHER</code>, this method can be used
   * to read database-specific abstract data types.
   *
   * @param parameterIndex the first parameter is 1, the second is 2, and so on
   * @return A <code>java.lang.Object</code> holding the OUT parameter value
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @see Types
   * @see #setObject
   */
  @Override
  public Object getObject(int parameterIndex) throws SQLException {
    checkNotClosed();
    checkOutputResult();
    return outputResult.getObject(idxToOutIdx(parameterIndex));
  }

  /**
   * Retrieves the value of the designated JDBC <code>NUMERIC</code> parameter as a <code>
   * java.math.BigDecimal</code> object with as many digits to the right of the decimal point as the
   * value contains.
   *
   * @param parameterIndex the first parameter is 1, the second is 2, and so on
   * @return the parameter value in full precision. If the value is SQL <code>NULL</code>, the
   *     result is <code>null</code>.
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @see #setBigDecimal
   * @since 1.2
   */
  @Override
  public BigDecimal getBigDecimal(int parameterIndex) throws SQLException {
    checkNotClosed();
    checkOutputResult();
    return outputResult.getBigDecimal(idxToOutIdx(parameterIndex));
  }

  /**
   * Returns an object representing the value of OUT parameter <code>parameterIndex</code> and uses
   * <code>map</code> for the custom mapping of the parameter value.
   *
   * <p>This method returns a Java object whose type corresponds to the JDBC type that was
   * registered for this parameter using the method <code>registerOutParameter</code>. By
   * registering the target JDBC types as <code>java.sql.Types.OTHER</code>, this method can be used
   * to read database-specific abstract data types.
   *
   * @param parameterIndex the first parameter is 1, the second is 2, and so on
   * @param map the mapping from SQL type names to Java classes
   * @return a <code>java.lang.Object</code> holding the OUT parameter value
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #setObject
   * @since 1.2
   */
  @Override
  public Object getObject(int parameterIndex, Map<String, Class<?>> map) throws SQLException {
    checkNotClosed();
    checkOutputResult();
    return outputResult.getObject(idxToOutIdx(parameterIndex), map);
  }

  /**
   * Retrieves the value of the designated JDBC <code>REF(&lt;structured-type&gt;)</code> parameter
   * as a {@link Ref} object in the Java programming language.
   *
   * @param parameterIndex the first parameter is 1, the second is 2, and so on
   * @return the parameter value as a <code>Ref</code> object in the Java programming language. If
   *     the value was SQL <code>NULL</code>, the value <code>null</code> is returned.
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.2
   */
  @Override
  public Ref getRef(int parameterIndex) throws SQLException {
    throw exceptionFactory().notSupported("Method ResultSet.getRef not supported");
  }

  /**
   * Retrieves the value of the designated JDBC <code>BLOB</code> parameter as a {@link Blob} object
   * in the Java programming language.
   *
   * @param parameterIndex the first parameter is 1, the second is 2, and so on
   * @return the parameter value as a <code>Blob</code> object in the Java programming language. If
   *     the value was SQL <code>NULL</code>, the value <code>null</code> is returned.
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.2
   */
  @Override
  public Blob getBlob(int parameterIndex) throws SQLException {
    checkNotClosed();
    checkOutputResult();
    return outputResult.getBlob(idxToOutIdx(parameterIndex));
  }

  /**
   * Retrieves the value of the designated JDBC <code>CLOB</code> parameter as a <code>java.sql.Clob
   * </code> object in the Java programming language.
   *
   * @param parameterIndex the first parameter is 1, the second is 2, and so on
   * @return the parameter value as a <code>Clob</code> object in the Java programming language. If
   *     the value was SQL <code>NULL</code>, the value <code>null</code> is returned.
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.2
   */
  @Override
  public Clob getClob(int parameterIndex) throws SQLException {
    checkNotClosed();
    checkOutputResult();
    return outputResult.getClob(idxToOutIdx(parameterIndex));
  }

  /**
   * Retrieves the value of the designated JDBC <code>ARRAY</code> parameter as an {@link Array}
   * object in the Java programming language.
   *
   * @param parameterIndex the first parameter is 1, the second is 2, and so on
   * @return the parameter value as an <code>Array</code> object in the Java programming language.
   *     If the value was SQL <code>NULL</code>, the value <code>null</code> is returned.
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.2
   */
  @Override
  public Array getArray(int parameterIndex) throws SQLException {
    checkNotClosed();
    checkOutputResult();
    throw exceptionFactory().notSupported("Method ResultSet.getArray not supported");
  }

  /**
   * Retrieves the value of the designated JDBC <code>DATE</code> parameter as a <code>java.sql.Date
   * </code> object, using the given <code>Calendar</code> object to construct the date. With a
   * <code>Calendar</code> object, the driver can calculate the date taking into account a custom
   * timezone and locale. If no <code>Calendar</code> object is specified, the driver uses the
   * default timezone and locale.
   *
   * @param parameterIndex the first parameter is 1, the second is 2, and so on
   * @param cal the <code>Calendar</code> object the driver will use to construct the date
   * @return the parameter value. If the value is SQL <code>NULL</code>, the result is <code>null
   *     </code>.
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @see #setDate
   * @since 1.2
   */
  @Override
  public Date getDate(int parameterIndex, Calendar cal) throws SQLException {
    checkNotClosed();
    checkOutputResult();
    return outputResult.getDate(idxToOutIdx(parameterIndex));
  }

  /**
   * Retrieves the value of the designated JDBC <code>TIME</code> parameter as a <code>java.sql.Time
   * </code> object, using the given <code>Calendar</code> object to construct the time. With a
   * <code>Calendar</code> object, the driver can calculate the time taking into account a custom
   * timezone and locale. If no <code>Calendar</code> object is specified, the driver uses the
   * default timezone and locale.
   *
   * @param parameterIndex the first parameter is 1, the second is 2, and so on
   * @param cal the <code>Calendar</code> object the driver will use to construct the time
   * @return the parameter value; if the value is SQL <code>NULL</code>, the result is <code>null
   *     </code>.
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @see #setTime
   * @since 1.2
   */
  @Override
  public Time getTime(int parameterIndex, Calendar cal) throws SQLException {
    checkNotClosed();
    checkOutputResult();
    return outputResult.getTime(idxToOutIdx(parameterIndex));
  }

  /**
   * Retrieves the value of the designated JDBC <code>TIMESTAMP</code> parameter as a <code>
   * java.sql.Timestamp</code> object, using the given <code>Calendar</code> object to construct the
   * <code>Timestamp</code> object. With a <code>Calendar</code> object, the driver can calculate
   * the timestamp taking into account a custom timezone and locale. If no <code>Calendar</code>
   * object is specified, the driver uses the default timezone and locale.
   *
   * @param parameterIndex the first parameter is 1, the second is 2, and so on
   * @param cal the <code>Calendar</code> object the driver will use to construct the timestamp
   * @return the parameter value. If the value is SQL <code>NULL</code>, the result is <code>null
   *     </code>.
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @see #setTimestamp
   * @since 1.2
   */
  @Override
  public Timestamp getTimestamp(int parameterIndex, Calendar cal) throws SQLException {
    checkNotClosed();
    checkOutputResult();
    return outputResult.getTimestamp(idxToOutIdx(parameterIndex));
  }

  /**
   * Registers the designated output parameter. This version of the method <code>
   * registerOutParameter</code> should be used for a user-defined or <code>REF</code> output
   * parameter. Examples of user-defined types include: <code>STRUCT</code>, <code>DISTINCT</code>,
   * <code>JAVA_OBJECT</code>, and named array types.
   *
   * <p>All OUT parameters must be registered before a stored procedure is executed.
   *
   * <p>For a user-defined parameter, the fully-qualified SQL type name of the parameter should also
   * be given, while a <code>REF</code> parameter requires that the fully-qualified type name of the
   * referenced type be given. A JDBC driver that does not need the type code and type name
   * information may ignore it. To be portable, however, applications should always provide these
   * values for user-defined and <code>REF</code> parameters.
   *
   * <p>Although it is intended for user-defined and <code>REF</code> parameters, this method may be
   * used to register a parameter of any JDBC type. If the parameter does not have a user-defined or
   * <code>REF</code> type, the <i>typeName</i> parameter is ignored.
   *
   * <p><B>Note:</B> When reading the value of an out parameter, you must use the getter method
   * whose Java type corresponds to the parameter's registered SQL type.
   *
   * @param parameterIndex the first parameter is 1, the second is 2,...
   * @param sqlType a value from {@link Types}
   * @param typeName the fully-qualified name of an SQL structured type
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if <code>sqlType</code> is a <code>ARRAY</code>, <code>
   *     BLOB</code>, <code>CLOB</code>, <code>DATALINK</code>, <code>JAVA_OBJECT</code>, <code>
   *     NCHAR</code>, <code>NCLOB</code>, <code>NVARCHAR</code>, <code>LONGNVARCHAR</code>, <code>
   *     REF</code>, <code>ROWID</code>, <code>SQLXML</code> or <code>STRUCT</code> data type and
   *     the JDBC driver does not support this data type
   * @see Types
   * @since 1.2
   */
  @Override
  public void registerOutParameter(int parameterIndex, int sqlType, String typeName)
      throws SQLException {
    registerOutParameter(parameterIndex, sqlType);
  }

  /**
   * Registers the OUT parameter named <code>parameterName</code> to the JDBC type <code>sqlType
   * </code>. All OUT parameters must be registered before a stored procedure is executed.
   *
   * <p>The JDBC type specified by <code>sqlType</code> for an OUT parameter determines the Java
   * type that must be used in the <code>get</code> method to read the value of that parameter.
   *
   * <p>If the JDBC type expected to be returned to this output parameter is specific to this
   * particular database, <code>sqlType</code> should be <code>java.sql.Types.OTHER</code>. The
   * method {@link #getObject} retrieves the value.
   *
   * @param parameterName the name of the parameter
   * @param sqlType the JDBC type code defined by <code>java.sql.Types</code>. If the parameter is
   *     of JDBC type <code>NUMERIC</code> or <code>DECIMAL</code>, the version of <code>
   *     registerOutParameter</code> that accepts a scale value should be used.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if <code>sqlType</code> is a <code>ARRAY</code>, <code>
   *     BLOB</code>, <code>CLOB</code>, <code>DATALINK</code>, <code>JAVA_OBJECT</code>, <code>
   *     NCHAR</code>, <code>NCLOB</code>, <code>NVARCHAR</code>, <code>LONGNVARCHAR</code>, <code>
   *     REF</code>, <code>ROWID</code>, <code>SQLXML</code> or <code>STRUCT</code> data type and
   *     the JDBC driver does not support this data type or if the JDBC driver does not support this
   *     method
   * @see Types
   * @since 1.4
   */
  @Override
  public void registerOutParameter(String parameterName, int sqlType) throws SQLException {
    checkNotClosed();
    registerOutParameter(nameToIndex(parameterName), sqlType);
  }

  private int nameToIndex(String parameterName) throws SQLException {
    if (parameterName == null) throw exceptionFactory().create("parameter name cannot be null");
    if (parameterMetaData == null) parameterMetaData = getParameterMetaData();

    int count = parameterMetaData.getParameterCount();
    for (int i = 1; i <= count; i++) {
      String name = parameterMetaData.getParameterName(i);
      if (name != null && name.equalsIgnoreCase(parameterName)) {
        return i;
      }
    }
    throw exceptionFactory().create(String.format("parameter name %s not found", parameterName));
  }

  /**
   * Registers the parameter named <code>parameterName</code> to be of JDBC type <code>sqlType
   * </code>. All OUT parameters must be registered before a stored procedure is executed.
   *
   * <p>The JDBC type specified by <code>sqlType</code> for an OUT parameter determines the Java
   * type that must be used in the <code>get</code> method to read the value of that parameter.
   *
   * <p>This version of <code>registerOutParameter</code> should be used when the parameter is of
   * JDBC type <code>NUMERIC</code> or <code>DECIMAL</code>.
   *
   * @param parameterName the name of the parameter
   * @param sqlType SQL type code defined by <code>java.sql.Types</code>.
   * @param scale the desired number of digits to the right of the decimal point. It must be greater
   *     than or equal to zero.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if <code>sqlType</code> is a <code>ARRAY</code>, <code>
   *     BLOB</code>, <code>CLOB</code>, <code>DATALINK</code>, <code>JAVA_OBJECT</code>, <code>
   *     NCHAR</code>, <code>NCLOB</code>, <code>NVARCHAR</code>, <code>LONGNVARCHAR</code>, <code>
   *     REF</code>, <code>ROWID</code>, <code>SQLXML</code> or <code>STRUCT</code> data type and
   *     the JDBC driver does not support this data type or if the JDBC driver does not support this
   *     method
   * @see Types
   * @since 1.4
   */
  @Override
  public void registerOutParameter(String parameterName, int sqlType, int scale)
      throws SQLException {
    registerOutParameter(parameterName, sqlType);
  }

  /**
   * Registers the designated output parameter. This version of the method <code>
   * registerOutParameter</code> should be used for a user-named or REF output parameter. Examples
   * of user-named types include: STRUCT, DISTINCT, JAVA_OBJECT, and named array types.
   *
   * <p>All OUT parameters must be registered before a stored procedure is executed.
   *
   * <p>For a user-named parameter the fully-qualified SQL type name of the parameter should also be
   * given, while a REF parameter requires that the fully-qualified type name of the referenced type
   * be given. A JDBC driver that does not need the type code and type name information may ignore
   * it. To be portable, however, applications should always provide these values for user-named and
   * REF parameters.
   *
   * <p>Although it is intended for user-named and REF parameters, this method may be used to
   * register a parameter of any JDBC type. If the parameter does not have a user-named or REF type,
   * the typeName parameter is ignored.
   *
   * <p><B>Note:</B> When reading the value of an out parameter, you must use the <code>getXXX
   * </code> method whose Java type XXX corresponds to the parameter's registered SQL type.
   *
   * @param parameterName the name of the parameter
   * @param sqlType a value from {@link Types}
   * @param typeName the fully-qualified name of an SQL structured type
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if <code>sqlType</code> is a <code>ARRAY</code>, <code>
   *     BLOB</code>, <code>CLOB</code>, <code>DATALINK</code>, <code>JAVA_OBJECT</code>, <code>
   *     NCHAR</code>, <code>NCLOB</code>, <code>NVARCHAR</code>, <code>LONGNVARCHAR</code>, <code>
   *     REF</code>, <code>ROWID</code>, <code>SQLXML</code> or <code>STRUCT</code> data type and
   *     the JDBC driver does not support this data type or if the JDBC driver does not support this
   *     method
   * @see Types
   * @since 1.4
   */
  @Override
  public void registerOutParameter(String parameterName, int sqlType, String typeName)
      throws SQLException {
    registerOutParameter(parameterName, sqlType);
  }

  /**
   * Retrieves the value of the designated JDBC <code>DATALINK</code> parameter as a <code>
   * java.net.URL</code> object.
   *
   * @param parameterIndex the first parameter is 1, the second is 2,...
   * @return a <code>java.net.URL</code> object that represents the JDBC <code>DATALINK</code> value
   *     used as the designated parameter
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs,
   *     this method is called on a closed <code>CallableStatement</code>, or if the URL being
   *     returned is not a valid URL on the Java platform
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #setURL
   * @since 1.4
   */
  @Override
  public URL getURL(int parameterIndex) throws SQLException {
    checkNotClosed();
    checkOutputResult();
    return outputResult.getURL(idxToOutIdx(parameterIndex));
  }

  /**
   * Sets the designated parameter to the given <code>java.net.URL</code> object. The driver
   * converts this to an SQL <code>DATALINK</code> value when it sends it to the database.
   *
   * @param parameterName the name of the parameter
   * @param val the parameter value
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs; this method is called on a closed <code>CallableStatement</code> or if
   *     a URL is malformed
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #getURL
   * @since 1.4
   */
  @Override
  public void setURL(String parameterName, URL val) throws SQLException {
    setURL(nameToIndex(parameterName), val);
  }

  /**
   * Sets the designated parameter to SQL <code>NULL</code>.
   *
   * <p><B>Note:</B> You must specify the parameter's SQL type.
   *
   * @param parameterName the name of the parameter
   * @param sqlType the SQL type code defined in <code>java.sql.Types</code>
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.4
   */
  @Override
  public void setNull(String parameterName, int sqlType) throws SQLException {
    setNull(nameToIndex(parameterName), sqlType);
  }

  /**
   * Sets the designated parameter to the given Java <code>boolean</code> value. The driver converts
   * this to an SQL <code>BIT</code> or <code>BOOLEAN</code> value when it sends it to the database.
   *
   * @param parameterName the name of the parameter
   * @param x the parameter value
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #getBoolean
   * @since 1.4
   */
  @Override
  public void setBoolean(String parameterName, boolean x) throws SQLException {
    setBoolean(nameToIndex(parameterName), x);
  }

  /**
   * Sets the designated parameter to the given Java <code>byte</code> value. The driver converts
   * this to an SQL <code>TINYINT</code> value when it sends it to the database.
   *
   * @param parameterName the name of the parameter
   * @param x the parameter value
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #getByte
   * @since 1.4
   */
  @Override
  public void setByte(String parameterName, byte x) throws SQLException {
    setByte(nameToIndex(parameterName), x);
  }

  /**
   * Sets the designated parameter to the given Java <code>short</code> value. The driver converts
   * this to an SQL <code>SMALLINT</code> value when it sends it to the database.
   *
   * @param parameterName the name of the parameter
   * @param x the parameter value
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #getShort
   * @since 1.4
   */
  @Override
  public void setShort(String parameterName, short x) throws SQLException {
    setShort(nameToIndex(parameterName), x);
  }

  /**
   * Sets the designated parameter to the given Java <code>int</code> value. The driver converts
   * this to an SQL <code>INTEGER</code> value when it sends it to the database.
   *
   * @param parameterName the name of the parameter
   * @param x the parameter value
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #getInt
   * @since 1.4
   */
  @Override
  public void setInt(String parameterName, int x) throws SQLException {
    setInt(nameToIndex(parameterName), x);
  }

  /**
   * Sets the designated parameter to the given Java <code>long</code> value. The driver converts
   * this to an SQL <code>BIGINT</code> value when it sends it to the database.
   *
   * @param parameterName the name of the parameter
   * @param x the parameter value
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #getLong
   * @since 1.4
   */
  @Override
  public void setLong(String parameterName, long x) throws SQLException {
    setLong(nameToIndex(parameterName), x);
  }

  /**
   * Sets the designated parameter to the given Java <code>float</code> value. The driver converts
   * this to an SQL <code>FLOAT</code> value when it sends it to the database.
   *
   * @param parameterName the name of the parameter
   * @param x the parameter value
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #getFloat
   * @since 1.4
   */
  @Override
  public void setFloat(String parameterName, float x) throws SQLException {
    setFloat(nameToIndex(parameterName), x);
  }

  /**
   * Sets the designated parameter to the given Java <code>double</code> value. The driver converts
   * this to an SQL <code>DOUBLE</code> value when it sends it to the database.
   *
   * @param parameterName the name of the parameter
   * @param x the parameter value
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #getDouble
   * @since 1.4
   */
  @Override
  public void setDouble(String parameterName, double x) throws SQLException {
    setDouble(nameToIndex(parameterName), x);
  }

  /**
   * Sets the designated parameter to the given <code>java.math.BigDecimal</code> value. The driver
   * converts this to an SQL <code>NUMERIC</code> value when it sends it to the database.
   *
   * @param parameterName the name of the parameter
   * @param x the parameter value
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #getBigDecimal
   * @since 1.4
   */
  @Override
  public void setBigDecimal(String parameterName, BigDecimal x) throws SQLException {
    setBigDecimal(nameToIndex(parameterName), x);
  }

  /**
   * Sets the designated parameter to the given Java <code>String</code> value. The driver converts
   * this to an SQL <code>VARCHAR</code> or <code>LONGVARCHAR</code> value (depending on the
   * argument's size relative to the driver's limits on <code>VARCHAR</code> values) when it sends
   * it to the database.
   *
   * @param parameterName the name of the parameter
   * @param x the parameter value
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #getString
   * @since 1.4
   */
  @Override
  public void setString(String parameterName, String x) throws SQLException {
    setString(nameToIndex(parameterName), x);
  }

  /**
   * Sets the designated parameter to the given Java array of bytes. The driver converts this to an
   * SQL <code>VARBINARY</code> or <code>LONGVARBINARY</code> (depending on the argument's size
   * relative to the driver's limits on <code>VARBINARY</code> values) when it sends it to the
   * database.
   *
   * @param parameterName the name of the parameter
   * @param x the parameter value
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #getBytes
   * @since 1.4
   */
  @Override
  public void setBytes(String parameterName, byte[] x) throws SQLException {
    setBytes(nameToIndex(parameterName), x);
  }

  /**
   * Sets the designated parameter to the given <code>java.sql.Date</code> value using the default
   * time zone of the virtual machine that is running the application. The driver converts this to
   * an SQL <code>DATE</code> value when it sends it to the database.
   *
   * @param parameterName the name of the parameter
   * @param x the parameter value
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #getDate
   * @since 1.4
   */
  @Override
  public void setDate(String parameterName, Date x) throws SQLException {
    setDate(nameToIndex(parameterName), x);
  }

  /**
   * Sets the designated parameter to the given <code>java.sql.Time</code> value. The driver
   * converts this to an SQL <code>TIME</code> value when it sends it to the database.
   *
   * @param parameterName the name of the parameter
   * @param x the parameter value
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #getTime
   * @since 1.4
   */
  @Override
  public void setTime(String parameterName, Time x) throws SQLException {
    setTime(nameToIndex(parameterName), x);
  }

  /**
   * Sets the designated parameter to the given <code>java.sql.Timestamp</code> value. The driver
   * converts this to an SQL <code>TIMESTAMP</code> value when it sends it to the database.
   *
   * @param parameterName the name of the parameter
   * @param x the parameter value
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #getTimestamp
   * @since 1.4
   */
  @Override
  public void setTimestamp(String parameterName, Timestamp x) throws SQLException {
    setTimestamp(nameToIndex(parameterName), x);
  }

  /**
   * Sets the designated parameter to the given input stream, which will have the specified number
   * of bytes. When a very large ASCII value is input to a <code>LONGVARCHAR</code> parameter, it
   * may be more practical to send it via a <code>java.io.InputStream</code>. Data will be read from
   * the stream as needed until end-of-file is reached. The JDBC driver will do any necessary
   * conversion from ASCII to the database char format.
   *
   * <p><B>Note:</B> This stream object can either be a standard Java stream object or your own
   * subclass that implements the standard interface.
   *
   * @param parameterName the name of the parameter
   * @param x the Java input stream that contains the ASCII parameter value
   * @param length the number of bytes in the stream
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.4
   */
  @Override
  public void setAsciiStream(String parameterName, InputStream x, int length) throws SQLException {
    setAsciiStream(nameToIndex(parameterName), x);
  }

  /**
   * Sets the designated parameter to the given input stream, which will have the specified number
   * of bytes. When a very large binary value is input to a <code>LONGVARBINARY</code> parameter, it
   * may be more practical to send it via a <code>java.io.InputStream</code> object. The data will
   * be read from the stream as needed until end-of-file is reached.
   *
   * <p><B>Note:</B> This stream object can either be a standard Java stream object or your own
   * subclass that implements the standard interface.
   *
   * @param parameterName the name of the parameter
   * @param x the java input stream which contains the binary parameter value
   * @param length the number of bytes in the stream
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.4
   */
  @Override
  public void setBinaryStream(String parameterName, InputStream x, int length) throws SQLException {
    setBinaryStream(nameToIndex(parameterName), x, length);
  }

  /**
   * Sets the value of the designated parameter with the given object.
   *
   * <p>The given Java object will be converted to the given targetSqlType before being sent to the
   * database.
   *
   * <p>If the object has a custom mapping (is of a class implementing the interface <code>SQLData
   * </code>), the JDBC driver should call the method <code>SQLData.writeSQL</code> to write it to
   * the SQL data stream. If, on the other hand, the object is of a class implementing <code>Ref
   * </code>, <code>Blob</code>, <code>Clob</code>, <code>NClob</code>, <code>Struct</code>, <code>
   * java.net.URL</code>, or <code>Array</code>, the driver should pass it to the database as a
   * value of the corresponding SQL type.
   *
   * <p>Note that this method may be used to pass datatabase- specific abstract data types.
   *
   * @param parameterName the name of the parameter
   * @param x the object containing the input parameter value
   * @param targetSqlType the SQL type (as defined in java.sql.Types) to be sent to the database.
   *     The scale argument may further qualify this type.
   * @param scale for java.sql.Types.DECIMAL or java.sql.Types.NUMERIC types, this is the number of
   *     digits after the decimal point. For all other types, this value will be ignored.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support the specified
   *     targetSqlType
   * @see Types
   * @see #getObject
   * @since 1.4
   */
  @Override
  public void setObject(String parameterName, Object x, int targetSqlType, int scale)
      throws SQLException {
    setObject(nameToIndex(parameterName), x);
  }

  /**
   * Sets the value of the designated parameter with the given object.
   *
   * <p>This method is similar to {@link #setObject(String parameterName, Object x, int
   * targetSqlType, int scaleOrLength)}, except that it assumes a scale of zero.
   *
   * @param parameterName the name of the parameter
   * @param x the object containing the input parameter value
   * @param targetSqlType the SQL type (as defined in java.sql.Types) to be sent to the database
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support the specified
   *     targetSqlType
   * @see #getObject
   * @since 1.4
   */
  @Override
  public void setObject(String parameterName, Object x, int targetSqlType) throws SQLException {
    setObject(nameToIndex(parameterName), x);
  }

  /**
   * Sets the value of the designated parameter with the given object.
   *
   * <p>The JDBC specification specifies a standard mapping from Java <code>Object</code> types to
   * SQL types. The given argument will be converted to the corresponding SQL type before being sent
   * to the database.
   *
   * <p>Note that this method may be used to pass database- specific abstract data types, by using a
   * driver-specific Java type.
   *
   * <p>If the object is of a class implementing the interface <code>SQLData</code>, the JDBC driver
   * should call the method <code>SQLData.writeSQL</code> to write it to the SQL data stream. If, on
   * the other hand, the object is of a class implementing <code>Ref</code>, <code>Blob</code>,
   * <code>Clob</code>, <code>NClob</code>, <code>Struct</code>, <code>java.net.URL</code>, or
   * <code>Array</code>, the driver should pass it to the database as a value of the corresponding
   * SQL type.
   *
   * <p>This method throws an exception if there is an ambiguity, for example, if the object is of a
   * class implementing more than one of the interfaces named above.
   *
   * <p><b>Note:</b> Not all databases allow for a non-typed Null to be sent to the backend. For
   * maximum portability, the <code>setNull</code> or the <code>
   * setObject(String parameterName, Object x, int sqlType)</code> method should be used instead of
   * <code>setObject(String parameterName, Object x)</code>.
   *
   * @param parameterName the name of the parameter
   * @param x the object containing the input parameter value
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs, this method is called on a closed <code>CallableStatement</code> or if
   *     the given <code>Object</code> parameter is ambiguous
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #getObject
   * @since 1.4
   */
  @Override
  public void setObject(String parameterName, Object x) throws SQLException {
    setObject(nameToIndex(parameterName), x);
  }

  /**
   * Sets the designated parameter to the given <code>Reader</code> object, which is the given
   * number of characters long. When a very large UNICODE value is input to a <code>LONGVARCHAR
   * </code> parameter, it may be more practical to send it via a <code>java.io.Reader</code>
   * object. The data will be read from the stream as needed until end-of-file is reached. The JDBC
   * driver will do any necessary conversion from UNICODE to the database char format.
   *
   * <p><B>Note:</B> This stream object can either be a standard Java stream object or your own
   * subclass that implements the standard interface.
   *
   * @param parameterName the name of the parameter
   * @param reader the <code>java.io.Reader</code> object that contains the UNICODE data used as the
   *     designated parameter
   * @param length the number of characters in the stream
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.4
   */
  @Override
  public void setCharacterStream(String parameterName, Reader reader, int length)
      throws SQLException {
    setCharacterStream(nameToIndex(parameterName), reader, length);
  }

  /**
   * Sets the designated parameter to the given <code>java.sql.Date</code> value, using the given
   * <code>Calendar</code> object. The driver uses the <code>Calendar</code> object to construct an
   * SQL <code>DATE</code> value, which the driver then sends to the database. With a a <code>
   * Calendar</code> object, the driver can calculate the date taking into account a custom
   * timezone. If no <code>Calendar</code> object is specified, the driver uses the default
   * timezone, which is that of the virtual machine running the application.
   *
   * @param parameterName the name of the parameter
   * @param x the parameter value
   * @param cal the <code>Calendar</code> object the driver will use to construct the date
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #getDate
   * @since 1.4
   */
  @Override
  public void setDate(String parameterName, Date x, Calendar cal) throws SQLException {
    setDate(nameToIndex(parameterName), x, cal);
  }

  /**
   * Sets the designated parameter to the given <code>java.sql.Time</code> value, using the given
   * <code>Calendar</code> object. The driver uses the <code>Calendar</code> object to construct an
   * SQL <code>TIME</code> value, which the driver then sends to the database. With a a <code>
   * Calendar</code> object, the driver can calculate the time taking into account a custom
   * timezone. If no <code>Calendar</code> object is specified, the driver uses the default
   * timezone, which is that of the virtual machine running the application.
   *
   * @param parameterName the name of the parameter
   * @param x the parameter value
   * @param cal the <code>Calendar</code> object the driver will use to construct the time
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #getTime
   * @since 1.4
   */
  @Override
  public void setTime(String parameterName, Time x, Calendar cal) throws SQLException {
    setTime(nameToIndex(parameterName), x, cal);
  }

  /**
   * Sets the designated parameter to the given <code>java.sql.Timestamp</code> value, using the
   * given <code>Calendar</code> object. The driver uses the <code>Calendar</code> object to
   * construct an SQL <code>TIMESTAMP</code> value, which the driver then sends to the database.
   * With a a <code>Calendar</code> object, the driver can calculate the timestamp taking into
   * account a custom timezone. If no <code>Calendar</code> object is specified, the driver uses the
   * default timezone, which is that of the virtual machine running the application.
   *
   * @param parameterName the name of the parameter
   * @param x the parameter value
   * @param cal the <code>Calendar</code> object the driver will use to construct the timestamp
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #getTimestamp
   * @since 1.4
   */
  @Override
  public void setTimestamp(String parameterName, Timestamp x, Calendar cal) throws SQLException {
    setTimestamp(nameToIndex(parameterName), x, cal);
  }

  /**
   * Sets the designated parameter to SQL <code>NULL</code>. This version of the method <code>
   * setNull</code> should be used for user-defined types and REF type parameters. Examples of
   * user-defined types include: STRUCT, DISTINCT, JAVA_OBJECT, and named array types.
   *
   * <p><B>Note:</B> To be portable, applications must give the SQL type code and the
   * fully-qualified SQL type name when specifying a NULL user-defined or REF parameter. In the case
   * of a user-defined type the name is the type name of the parameter itself. For a REF parameter,
   * the name is the type name of the referenced type.
   *
   * <p>Although it is intended for user-defined and Ref parameters, this method may be used to set
   * a null parameter of any JDBC type. If the parameter does not have a user-defined or REF type,
   * the given typeName is ignored.
   *
   * @param parameterName the name of the parameter
   * @param sqlType a value from <code>java.sql.Types</code>
   * @param typeName the fully-qualified name of an SQL user-defined type; ignored if the parameter
   *     is not a user-defined type or SQL <code>REF</code> value
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.4
   */
  @Override
  public void setNull(String parameterName, int sqlType, String typeName) throws SQLException {
    setNull(nameToIndex(parameterName), sqlType, typeName);
  }

  /**
   * Retrieves the value of a JDBC <code>CHAR</code>, <code>VARCHAR</code>, or <code>LONGVARCHAR
   * </code> parameter as a <code>String</code> in the Java programming language.
   *
   * <p>For the fixed-length type JDBC <code>CHAR</code>, the <code>String</code> object returned
   * has exactly the same value the SQL <code>CHAR</code> value had in the database, including any
   * padding added by the database.
   *
   * @param parameterName the name of the parameter
   * @return the parameter value. If the value is SQL <code>NULL</code>, the result is <code>null
   *     </code>.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #setString
   * @since 1.4
   */
  @Override
  public String getString(String parameterName) throws SQLException {
    return outputResult.getString(idxToOutIdx(nameToIndex(parameterName)));
  }

  /**
   * Retrieves the value of a JDBC <code>BIT</code> or <code>BOOLEAN</code> parameter as a <code>
   * boolean</code> in the Java programming language.
   *
   * @param parameterName the name of the parameter
   * @return the parameter value. If the value is SQL <code>NULL</code>, the result is <code>false
   *     </code>.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #setBoolean
   * @since 1.4
   */
  @Override
  public boolean getBoolean(String parameterName) throws SQLException {
    return outputResult.getBoolean(idxToOutIdx(nameToIndex(parameterName)));
  }

  /**
   * Retrieves the value of a JDBC <code>TINYINT</code> parameter as a <code>byte</code> in the Java
   * programming language.
   *
   * @param parameterName the name of the parameter
   * @return the parameter value. If the value is SQL <code>NULL</code>, the result is <code>0
   *     </code>.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #setByte
   * @since 1.4
   */
  @Override
  public byte getByte(String parameterName) throws SQLException {
    return outputResult.getByte(idxToOutIdx(nameToIndex(parameterName)));
  }

  /**
   * Retrieves the value of a JDBC <code>SMALLINT</code> parameter as a <code>short</code> in the
   * Java programming language.
   *
   * @param parameterName the name of the parameter
   * @return the parameter value. If the value is SQL <code>NULL</code>, the result is <code>0
   *     </code>.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #setShort
   * @since 1.4
   */
  @Override
  public short getShort(String parameterName) throws SQLException {
    return outputResult.getShort(idxToOutIdx(nameToIndex(parameterName)));
  }

  /**
   * Retrieves the value of a JDBC <code>INTEGER</code> parameter as an <code>int</code> in the Java
   * programming language.
   *
   * @param parameterName the name of the parameter
   * @return the parameter value. If the value is SQL <code>NULL</code>, the result is <code>0
   *     </code>.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #setInt
   * @since 1.4
   */
  @Override
  public int getInt(String parameterName) throws SQLException {
    return outputResult.getInt(idxToOutIdx(nameToIndex(parameterName)));
  }

  /**
   * Retrieves the value of a JDBC <code>BIGINT</code> parameter as a <code>long</code> in the Java
   * programming language.
   *
   * @param parameterName the name of the parameter
   * @return the parameter value. If the value is SQL <code>NULL</code>, the result is <code>0
   *     </code>.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #setLong
   * @since 1.4
   */
  @Override
  public long getLong(String parameterName) throws SQLException {
    return outputResult.getLong(idxToOutIdx(nameToIndex(parameterName)));
  }

  /**
   * Retrieves the value of a JDBC <code>FLOAT</code> parameter as a <code>float</code> in the Java
   * programming language.
   *
   * @param parameterName the name of the parameter
   * @return the parameter value. If the value is SQL <code>NULL</code>, the result is <code>0
   *     </code>.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #setFloat
   * @since 1.4
   */
  @Override
  public float getFloat(String parameterName) throws SQLException {
    return outputResult.getFloat(idxToOutIdx(nameToIndex(parameterName)));
  }

  /**
   * Retrieves the value of a JDBC <code>DOUBLE</code> parameter as a <code>double</code> in the
   * Java programming language.
   *
   * @param parameterName the name of the parameter
   * @return the parameter value. If the value is SQL <code>NULL</code>, the result is <code>0
   *     </code>.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #setDouble
   * @since 1.4
   */
  @Override
  public double getDouble(String parameterName) throws SQLException {
    return outputResult.getDouble(idxToOutIdx(nameToIndex(parameterName)));
  }

  /**
   * Retrieves the value of a JDBC <code>BINARY</code> or <code>VARBINARY</code> parameter as an
   * array of <code>byte</code> values in the Java programming language.
   *
   * @param parameterName the name of the parameter
   * @return the parameter value. If the value is SQL <code>NULL</code>, the result is <code>null
   *     </code>.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #setBytes
   * @since 1.4
   */
  @Override
  public byte[] getBytes(String parameterName) throws SQLException {
    return outputResult.getBytes(idxToOutIdx(nameToIndex(parameterName)));
  }

  /**
   * Retrieves the value of a JDBC <code>DATE</code> parameter as a <code>java.sql.Date</code>
   * object.
   *
   * @param parameterName the name of the parameter
   * @return the parameter value. If the value is SQL <code>NULL</code>, the result is <code>null
   *     </code>.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #setDate
   * @since 1.4
   */
  @Override
  public Date getDate(String parameterName) throws SQLException {
    return outputResult.getDate(idxToOutIdx(nameToIndex(parameterName)));
  }

  /**
   * Retrieves the value of a JDBC <code>TIME</code> parameter as a <code>java.sql.Time</code>
   * object.
   *
   * @param parameterName the name of the parameter
   * @return the parameter value. If the value is SQL <code>NULL</code>, the result is <code>null
   *     </code>.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #setTime
   * @since 1.4
   */
  @Override
  public Time getTime(String parameterName) throws SQLException {
    return outputResult.getTime(idxToOutIdx(nameToIndex(parameterName)));
  }

  /**
   * Retrieves the value of a JDBC <code>TIMESTAMP</code> parameter as a <code>java.sql.Timestamp
   * </code> object.
   *
   * @param parameterName the name of the parameter
   * @return the parameter value. If the value is SQL <code>NULL</code>, the result is <code>null
   *     </code>.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #setTimestamp
   * @since 1.4
   */
  @Override
  public Timestamp getTimestamp(String parameterName) throws SQLException {
    return outputResult.getTimestamp(idxToOutIdx(nameToIndex(parameterName)));
  }

  /**
   * Retrieves the value of a parameter as an <code>Object</code> in the Java programming language.
   * If the value is an SQL <code>NULL</code>, the driver returns a Java <code>null</code>.
   *
   * <p>This method returns a Java object whose type corresponds to the JDBC type that was
   * registered for this parameter using the method <code>registerOutParameter</code>. By
   * registering the target JDBC types as <code>java.sql.Types.OTHER</code>, this method can be used
   * to read database-specific abstract data types.
   *
   * @param parameterName the name of the parameter
   * @return A <code>java.lang.Object</code> holding the OUT parameter value.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see Types
   * @see #setObject
   * @since 1.4
   */
  @Override
  public Object getObject(String parameterName) throws SQLException {
    return outputResult.getObject(idxToOutIdx(nameToIndex(parameterName)));
  }

  /**
   * Retrieves the value of a JDBC <code>NUMERIC</code> parameter as a <code>java.math.BigDecimal
   * </code> object with as many digits to the right of the decimal point as the value contains.
   *
   * @param parameterName the name of the parameter
   * @return the parameter value in full precision. If the value is SQL <code>NULL</code>, the
   *     result is <code>null</code>.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #setBigDecimal
   * @since 1.4
   */
  @Override
  public BigDecimal getBigDecimal(String parameterName) throws SQLException {
    return outputResult.getBigDecimal(idxToOutIdx(nameToIndex(parameterName)));
  }

  /**
   * Returns an object representing the value of OUT parameter <code>parameterName</code> and uses
   * <code>map</code> for the custom mapping of the parameter value.
   *
   * <p>This method returns a Java object whose type corresponds to the JDBC type that was
   * registered for this parameter using the method <code>registerOutParameter</code>. By
   * registering the target JDBC types as <code>java.sql.Types.OTHER</code>, this method can be used
   * to read database-specific abstract data types.
   *
   * @param parameterName the name of the parameter
   * @param map the mapping from SQL type names to Java classes
   * @return a <code>java.lang.Object</code> holding the OUT parameter value
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #setObject
   * @since 1.4
   */
  @Override
  public Object getObject(String parameterName, Map<String, Class<?>> map) throws SQLException {
    return outputResult.getObject(idxToOutIdx(nameToIndex(parameterName)), map);
  }

  /**
   * Retrieves the value of a JDBC <code>REF(&lt;structured-type&gt;)</code> parameter as a {@link
   * Ref} object in the Java programming language.
   *
   * @param parameterName the name of the parameter
   * @return the parameter value as a <code>Ref</code> object in the Java programming language. If
   *     the value was SQL <code>NULL</code>, the value <code>null</code> is returned.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.4
   */
  @Override
  public Ref getRef(String parameterName) throws SQLException {
    throw exceptionFactory().notSupported("Method ResultSet.getRef not supported");
  }

  /**
   * Retrieves the value of a JDBC <code>BLOB</code> parameter as a {@link Blob} object in the Java
   * programming language.
   *
   * @param parameterName the name of the parameter
   * @return the parameter value as a <code>Blob</code> object in the Java programming language. If
   *     the value was SQL <code>NULL</code>, the value <code>null</code> is returned.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.4
   */
  @Override
  public Blob getBlob(String parameterName) throws SQLException {
    return outputResult.getBlob(idxToOutIdx(nameToIndex(parameterName)));
  }

  /**
   * Retrieves the value of a JDBC <code>CLOB</code> parameter as a <code>java.sql.Clob</code>
   * object in the Java programming language.
   *
   * @param parameterName the name of the parameter
   * @return the parameter value as a <code>Clob</code> object in the Java programming language. If
   *     the value was SQL <code>NULL</code>, the value <code>null</code> is returned.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.4
   */
  @Override
  public Clob getClob(String parameterName) throws SQLException {
    return outputResult.getClob(idxToOutIdx(nameToIndex(parameterName)));
  }

  /**
   * Retrieves the value of a JDBC <code>ARRAY</code> parameter as an {@link Array} object in the
   * Java programming language.
   *
   * @param parameterName the name of the parameter
   * @return the parameter value as an <code>Array</code> object in Java programming language. If
   *     the value was SQL <code>NULL</code>, the value <code>null</code> is returned.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.4
   */
  @Override
  public Array getArray(String parameterName) throws SQLException {
    throw exceptionFactory().notSupported("Method ResultSet.getArray not supported");
  }

  /**
   * Retrieves the value of a JDBC <code>DATE</code> parameter as a <code>java.sql.Date</code>
   * object, using the given <code>Calendar</code> object to construct the date. With a <code>
   * Calendar</code> object, the driver can calculate the date taking into account a custom timezone
   * and locale. If no <code>Calendar</code> object is specified, the driver uses the default
   * timezone and locale.
   *
   * @param parameterName the name of the parameter
   * @param cal the <code>Calendar</code> object the driver will use to construct the date
   * @return the parameter value. If the value is SQL <code>NULL</code>, the result is <code>null
   *     </code>.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #setDate
   * @since 1.4
   */
  @Override
  public Date getDate(String parameterName, Calendar cal) throws SQLException {
    return outputResult.getDate(idxToOutIdx(nameToIndex(parameterName)), cal);
  }

  /**
   * Retrieves the value of a JDBC <code>TIME</code> parameter as a <code>java.sql.Time</code>
   * object, using the given <code>Calendar</code> object to construct the time. With a <code>
   * Calendar</code> object, the driver can calculate the time taking into account a custom timezone
   * and locale. If no <code>Calendar</code> object is specified, the driver uses the default
   * timezone and locale.
   *
   * @param parameterName the name of the parameter
   * @param cal the <code>Calendar</code> object the driver will use to construct the time
   * @return the parameter value; if the value is SQL <code>NULL</code>, the result is <code>null
   *     </code>.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #setTime
   * @since 1.4
   */
  @Override
  public Time getTime(String parameterName, Calendar cal) throws SQLException {
    return outputResult.getTime(idxToOutIdx(nameToIndex(parameterName)), cal);
  }

  /**
   * Retrieves the value of a JDBC <code>TIMESTAMP</code> parameter as a <code>java.sql.Timestamp
   * </code> object, using the given <code>Calendar</code> object to construct the <code>Timestamp
   * </code> object. With a <code>Calendar</code> object, the driver can calculate the timestamp
   * taking into account a custom timezone and locale. If no <code>Calendar</code> object is
   * specified, the driver uses the default timezone and locale.
   *
   * @param parameterName the name of the parameter
   * @param cal the <code>Calendar</code> object the driver will use to construct the timestamp
   * @return the parameter value. If the value is SQL <code>NULL</code>, the result is <code>null
   *     </code>.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #setTimestamp
   * @since 1.4
   */
  @Override
  public Timestamp getTimestamp(String parameterName, Calendar cal) throws SQLException {
    return outputResult.getTimestamp(idxToOutIdx(nameToIndex(parameterName)), cal);
  }

  /**
   * Retrieves the value of a JDBC <code>DATALINK</code> parameter as a <code>java.net.URL</code>
   * object.
   *
   * @param parameterName the name of the parameter
   * @return the parameter value as a <code>java.net.URL</code> object in the Java programming
   *     language. If the value was SQL <code>NULL</code>, the value <code>null</code> is returned.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs, this method is called on a closed <code>CallableStatement</code>, or
   *     if there is a problem with the URL
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #setURL
   * @since 1.4
   */
  @Override
  public URL getURL(String parameterName) throws SQLException {
    return outputResult.getURL(idxToOutIdx(nameToIndex(parameterName)));
  }

  /**
   * Retrieves the value of the designated JDBC <code>ROWID</code> parameter as a <code>
   * java.sql.RowId</code> object.
   *
   * @param parameterIndex the first parameter is 1, the second is 2,...
   * @return a <code>RowId</code> object that represents the JDBC <code>ROWID</code> value is used
   *     as the designated parameter. If the parameter contains an SQL <code>NULL</code>, then a
   *     <code>null</code> value is returned.
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.6
   */
  @Override
  public RowId getRowId(int parameterIndex) throws SQLException {
    throw exceptionFactory().notSupported("RowId are not supported");
  }

  /**
   * Retrieves the value of the designated JDBC <code>ROWID</code> parameter as a <code>
   * java.sql.RowId</code> object.
   *
   * @param parameterName the name of the parameter
   * @return a <code>RowId</code> object that represents the JDBC <code>ROWID</code> value is used
   *     as the designated parameter. If the parameter contains an SQL <code>NULL</code>, then a
   *     <code>null</code> value is returned.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.6
   */
  @Override
  public RowId getRowId(String parameterName) throws SQLException {
    throw exceptionFactory().notSupported("RowId are not supported");
  }

  /**
   * Sets the designated parameter to the given <code>java.sql.RowId</code> object. The driver
   * converts this to an SQL <code>ROWID</code> when it sends it to the database.
   *
   * @param parameterName the name of the parameter
   * @param x the parameter value
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.6
   */
  @Override
  public void setRowId(String parameterName, RowId x) throws SQLException {
    throw exceptionFactory().notSupported("RowId parameter are not supported");
  }

  /**
   * Sets the designated parameter to the given <code>String</code> object. The driver converts this
   * to an SQL <code>NCHAR</code> or <code>NVARCHAR</code> or <code>LONGNVARCHAR</code>
   *
   * @param parameterName the name of the parameter to be set
   * @param value the parameter value
   * @throws SQLException if parameterName does not correspond to a named parameter; if the driver
   *     does not support national character sets; if the driver can detect that a data conversion
   *     error could occur; if a database access error occurs or this method is called on a closed
   *     <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.6
   */
  @Override
  public void setNString(String parameterName, String value) throws SQLException {
    setNString(nameToIndex(parameterName), value);
  }

  /**
   * Sets the designated parameter to a <code>Reader</code> object. The <code>Reader</code> reads
   * the data till end-of-file is reached. The driver does the necessary conversion from Java
   * character format to the national character set in the database.
   *
   * @param parameterName the name of the parameter to be set
   * @param value the parameter value
   * @param length the number of characters in the parameter data.
   * @throws SQLException if parameterName does not correspond to a named parameter; if the driver
   *     does not support national character sets; if the driver can detect that a data conversion
   *     error could occur; if a database access error occurs or this method is called on a closed
   *     <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.6
   */
  @Override
  public void setNCharacterStream(String parameterName, Reader value, long length)
      throws SQLException {
    setNCharacterStream(nameToIndex(parameterName), value, length);
  }

  /**
   * Sets the designated parameter to a <code>java.sql.NClob</code> object. The object implements
   * the <code>java.sql.NClob</code> interface. This <code>NClob</code> object maps to an SQL <code>
   * NCLOB</code>.
   *
   * @param parameterName the name of the parameter to be set
   * @param value the parameter value
   * @throws SQLException if parameterName does not correspond to a named parameter; if the driver
   *     does not support national character sets; if the driver can detect that a data conversion
   *     error could occur; if a database access error occurs or this method is called on a closed
   *     <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.6
   */
  @Override
  public void setNClob(String parameterName, NClob value) throws SQLException {
    setNClob(nameToIndex(parameterName), value);
  }

  /**
   * Sets the designated parameter to a <code>Reader</code> object. The <code>reader</code> must
   * contain the number of characters specified by length otherwise a <code>SQLException</code> will
   * be generated when the <code>CallableStatement</code> is executed. This method differs from the
   * <code>setCharacterStream (int, Reader, int)</code> method because it informs the driver that
   * the parameter value should be sent to the server as a <code>CLOB</code>. When the <code>
   * setCharacterStream</code> method is used, the driver may have to do extra work to determine
   * whether the parameter data should be sent to the server as a <code>LONGVARCHAR</code> or a
   * <code>CLOB</code>
   *
   * @param parameterName the name of the parameter to be set
   * @param reader An object that contains the data to set the parameter value to.
   * @param length the number of characters in the parameter data.
   * @throws SQLException if parameterName does not correspond to a named parameter; if the length
   *     specified is less than zero; a database access error occurs or this method is called on a
   *     closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.6
   */
  @Override
  public void setClob(String parameterName, Reader reader, long length) throws SQLException {
    setClob(nameToIndex(parameterName), reader, length);
  }

  /**
   * Sets the designated parameter to an {@code InputStream} object. The <code>Inputstream</code>
   * must contain the number of characters specified by length, otherwise a <code>SQLException
   * </code> will be generated when the <code>CallableStatement</code> is executed. This method
   * differs from the <code>setBinaryStream (int, InputStream, int)</code> method because it informs
   * the driver that the parameter value should be sent to the server as a <code>BLOB</code>. When
   * the <code>setBinaryStream</code> method is used, the driver may have to do extra work to
   * determine whether the parameter data should be sent to the server as a <code>LONGVARBINARY
   * </code> or a <code>BLOB</code>
   *
   * @param parameterName the name of the parameter to be set the second is 2, ...
   * @param inputStream An object that contains the data to set the parameter value to.
   * @param length the number of bytes in the parameter data.
   * @throws SQLException if parameterName does not correspond to a named parameter; if the length
   *     specified is less than zero; if the number of bytes in the {@code InputStream} does not
   *     match the specified length; if a database access error occurs or this method is called on a
   *     closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.6
   */
  @Override
  public void setBlob(String parameterName, InputStream inputStream, long length)
      throws SQLException {
    setBlob(nameToIndex(parameterName), inputStream, length);
  }

  /**
   * Sets the designated parameter to a <code>Reader</code> object. The <code>reader</code> must
   * contain the number of characters specified by length otherwise a <code>SQLException</code> will
   * be generated when the <code>CallableStatement</code> is executed. This method differs from the
   * <code>setCharacterStream (int, Reader, int)</code> method because it informs the driver that
   * the parameter value should be sent to the server as a <code>NCLOB</code>. When the <code>
   * setCharacterStream</code> method is used, the driver may have to do extra work to determine
   * whether the parameter data should be sent to the server as a <code>LONGNVARCHAR</code> or a
   * <code>NCLOB</code>
   *
   * @param parameterName the name of the parameter to be set
   * @param reader An object that contains the data to set the parameter value to.
   * @param length the number of characters in the parameter data.
   * @throws SQLException if parameterName does not correspond to a named parameter; if the length
   *     specified is less than zero; if the driver does not support national character sets; if the
   *     driver can detect that a data conversion error could occur; if a database access error
   *     occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.6
   */
  @Override
  public void setNClob(String parameterName, Reader reader, long length) throws SQLException {
    setNClob(nameToIndex(parameterName), reader, length);
  }

  /**
   * Retrieves the value of the designated JDBC <code>NCLOB</code> parameter as a <code>
   * java.sql.NClob</code> object in the Java programming language.
   *
   * @param parameterIndex the first parameter is 1, the second is 2, and so on
   * @return the parameter value as a <code>NClob</code> object in the Java programming language. If
   *     the value was SQL <code>NULL</code>, the value <code>null</code> is returned.
   * @throws SQLException if the parameterIndex is not valid; if the driver does not support
   *     national character sets; if the driver can detect that a data conversion error could occur;
   *     if a database access error occurs or this method is called on a closed <code>
   *     CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.6
   */
  @Override
  public NClob getNClob(int parameterIndex) throws SQLException {
    checkNotClosed();
    checkOutputResult();
    return outputResult.getNClob(idxToOutIdx(parameterIndex));
  }

  /**
   * Retrieves the value of a JDBC <code>NCLOB</code> parameter as a <code>java.sql.NClob</code>
   * object in the Java programming language.
   *
   * @param parameterName the name of the parameter
   * @return the parameter value as a <code>NClob</code> object in the Java programming language. If
   *     the value was SQL <code>NULL</code>, the value <code>null</code> is returned.
   * @throws SQLException if parameterName does not correspond to a named parameter; if the driver
   *     does not support national character sets; if the driver can detect that a data conversion
   *     error could occur; if a database access error occurs or this method is called on a closed
   *     <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.6
   */
  @Override
  public NClob getNClob(String parameterName) throws SQLException {
    return getNClob(nameToIndex(parameterName));
  }

  /**
   * Sets the designated parameter to the given <code>java.sql.SQLXML</code> object. The driver
   * converts this to an <code>SQL XML</code> value when it sends it to the database.
   *
   * @param parameterName the name of the parameter
   * @param xmlObject a <code>SQLXML</code> object that maps an <code>SQL XML</code> value
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs; this method is called on a closed <code>CallableStatement</code> or
   *     the <code>java.xml.transform.Result</code>, <code>Writer</code> or <code>OutputStream
   *     </code> has not been closed for the <code>SQLXML</code> object
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.6
   */
  @Override
  public void setSQLXML(String parameterName, SQLXML xmlObject) throws SQLException {
    throw exceptionFactory().notSupported("SQLXML parameter are not supported");
  }

  /**
   * Retrieves the value of the designated <code>SQL XML</code> parameter as a <code>java.sql.SQLXML
   * </code> object in the Java programming language.
   *
   * @param parameterIndex index of the first parameter is 1, the second is 2, ...
   * @return a <code>SQLXML</code> object that maps an <code>SQL XML</code> value
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.6
   */
  @Override
  public SQLXML getSQLXML(int parameterIndex) throws SQLException {
    throw exceptionFactory().notSupported("SQLXML are not supported");
  }

  /**
   * Retrieves the value of the designated <code>SQL XML</code> parameter as a <code>java.sql.SQLXML
   * </code> object in the Java programming language.
   *
   * @param parameterName the name of the parameter
   * @return a <code>SQLXML</code> object that maps an <code>SQL XML</code> value
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.6
   */
  @Override
  public SQLXML getSQLXML(String parameterName) throws SQLException {
    throw exceptionFactory().notSupported("SQLXML are not supported");
  }

  private ExceptionFactory exceptionFactory() {
    return con.getExceptionFactory().of(this);
  }

  /**
   * Retrieves the value of the designated <code>NCHAR</code>, <code>NVARCHAR</code> or <code>
   * LONGNVARCHAR</code> parameter as a <code>String</code> in the Java programming language.
   *
   * <p>For the fixed-length type JDBC <code>NCHAR</code>, the <code>String</code> object returned
   * has exactly the same value the SQL <code>NCHAR</code> value had in the database, including any
   * padding added by the database.
   *
   * @param parameterIndex index of the first parameter is 1, the second is 2, ...
   * @return a <code>String</code> object that maps an <code>NCHAR</code>, <code>NVARCHAR</code> or
   *     <code>LONGNVARCHAR</code> value
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #setNString
   * @since 1.6
   */
  @Override
  public String getNString(int parameterIndex) throws SQLException {
    checkNotClosed();
    checkOutputResult();
    return outputResult.getNString(idxToOutIdx(parameterIndex));
  }

  /**
   * Retrieves the value of the designated <code>NCHAR</code>, <code>NVARCHAR</code> or <code>
   * LONGNVARCHAR</code> parameter as a <code>String</code> in the Java programming language.
   *
   * <p>For the fixed-length type JDBC <code>NCHAR</code>, the <code>String</code> object returned
   * has exactly the same value the SQL <code>NCHAR</code> value had in the database, including any
   * padding added by the database.
   *
   * @param parameterName the name of the parameter
   * @return a <code>String</code> object that maps an <code>NCHAR</code>, <code>NVARCHAR</code> or
   *     <code>LONGNVARCHAR</code> value
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @see #setNString
   * @since 1.6
   */
  @Override
  public String getNString(String parameterName) throws SQLException {
    return getNString(nameToIndex(parameterName));
  }

  /**
   * Retrieves the value of the designated parameter as a <code>java.io.Reader</code> object in the
   * Java programming language. It is intended for use when accessing <code>NCHAR</code>,<code>
   * NVARCHAR</code> and <code>LONGNVARCHAR</code> parameters.
   *
   * @param parameterIndex the first parameter is 1, the second is 2, ...
   * @return a <code>java.io.Reader</code> object that contains the parameter value; if the value is
   *     SQL <code>NULL</code>, the value returned is <code>null</code> in the Java programming
   *     language.
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.6
   */
  @Override
  public Reader getNCharacterStream(int parameterIndex) throws SQLException {
    checkNotClosed();
    checkOutputResult();
    return outputResult.getNCharacterStream(idxToOutIdx(parameterIndex));
  }

  /**
   * Retrieves the value of the designated parameter as a <code>java.io.Reader</code> object in the
   * Java programming language. It is intended for use when accessing <code>NCHAR</code>,<code>
   * NVARCHAR</code> and <code>LONGNVARCHAR</code> parameters.
   *
   * @param parameterName the name of the parameter
   * @return a <code>java.io.Reader</code> object that contains the parameter value; if the value is
   *     SQL <code>NULL</code>, the value returned is <code>null</code> in the Java programming
   *     language
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.6
   */
  @Override
  public Reader getNCharacterStream(String parameterName) throws SQLException {
    return getNCharacterStream(nameToIndex(parameterName));
  }

  /**
   * Retrieves the value of the designated parameter as a <code>java.io.Reader</code> object in the
   * Java programming language.
   *
   * @param parameterIndex the first parameter is 1, the second is 2, ...
   * @return a <code>java.io.Reader</code> object that contains the parameter value; if the value is
   *     SQL <code>NULL</code>, the value returned is <code>null</code> in the Java programming
   *     language.
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed <code>CallableStatement</code>
   * @since 1.6
   */
  @Override
  public Reader getCharacterStream(int parameterIndex) throws SQLException {
    checkNotClosed();
    checkOutputResult();
    return outputResult.getCharacterStream(idxToOutIdx(parameterIndex));
  }

  /**
   * Retrieves the value of the designated parameter as a <code>java.io.Reader</code> object in the
   * Java programming language.
   *
   * @param parameterName the name of the parameter
   * @return a <code>java.io.Reader</code> object that contains the parameter value; if the value is
   *     SQL <code>NULL</code>, the value returned is <code>null</code> in the Java programming
   *     language
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.6
   */
  @Override
  public Reader getCharacterStream(String parameterName) throws SQLException {
    return getNCharacterStream(nameToIndex(parameterName));
  }

  /**
   * Sets the designated parameter to the given <code>java.sql.Blob</code> object. The driver
   * converts this to an SQL <code>BLOB</code> value when it sends it to the database.
   *
   * @param parameterName the name of the parameter
   * @param x a <code>Blob</code> object that maps an SQL <code>BLOB</code> value
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.6
   */
  @Override
  public void setBlob(String parameterName, Blob x) throws SQLException {
    setBlob(nameToIndex(parameterName), x);
  }

  /**
   * Sets the designated parameter to the given <code>java.sql.Clob</code> object. The driver
   * converts this to an SQL <code>CLOB</code> value when it sends it to the database.
   *
   * @param parameterName the name of the parameter
   * @param x a <code>Clob</code> object that maps an SQL <code>CLOB</code> value
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.6
   */
  @Override
  public void setClob(String parameterName, Clob x) throws SQLException {
    setClob(nameToIndex(parameterName), x);
  }

  /**
   * Sets the designated parameter to the given input stream, which will have the specified number
   * of bytes. When a very large ASCII value is input to a <code>LONGVARCHAR</code> parameter, it
   * may be more practical to send it via a <code>java.io.InputStream</code>. Data will be read from
   * the stream as needed until end-of-file is reached. The JDBC driver will do any necessary
   * conversion from ASCII to the database char format.
   *
   * <p><B>Note:</B> This stream object can either be a standard Java stream object or your own
   * subclass that implements the standard interface.
   *
   * @param parameterName the name of the parameter
   * @param x the Java input stream that contains the ASCII parameter value
   * @param length the number of bytes in the stream
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.6
   */
  @Override
  public void setAsciiStream(String parameterName, InputStream x, long length) throws SQLException {
    setAsciiStream(nameToIndex(parameterName), x, length);
  }

  /**
   * Sets the designated parameter to the given input stream, which will have the specified number
   * of bytes. When a very large binary value is input to a <code>LONGVARBINARY</code> parameter, it
   * may be more practical to send it via a <code>java.io.InputStream</code> object. The data will
   * be read from the stream as needed until end-of-file is reached.
   *
   * <p><B>Note:</B> This stream object can either be a standard Java stream object or your own
   * subclass that implements the standard interface.
   *
   * @param parameterName the name of the parameter
   * @param x the java input stream which contains the binary parameter value
   * @param length the number of bytes in the stream
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.6
   */
  @Override
  public void setBinaryStream(String parameterName, InputStream x, long length)
      throws SQLException {
    setBinaryStream(nameToIndex(parameterName), x, length);
  }

  /**
   * Sets the designated parameter to the given <code>Reader</code> object, which is the given
   * number of characters long. When a very large UNICODE value is input to a <code>LONGVARCHAR
   * </code> parameter, it may be more practical to send it via a <code>java.io.Reader</code>
   * object. The data will be read from the stream as needed until end-of-file is reached. The JDBC
   * driver will do any necessary conversion from UNICODE to the database char format.
   *
   * <p><B>Note:</B> This stream object can either be a standard Java stream object or your own
   * subclass that implements the standard interface.
   *
   * @param parameterName the name of the parameter
   * @param reader the <code>java.io.Reader</code> object that contains the UNICODE data used as the
   *     designated parameter
   * @param length the number of characters in the stream
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.6
   */
  @Override
  public void setCharacterStream(String parameterName, Reader reader, long length)
      throws SQLException {
    setCharacterStream(nameToIndex(parameterName), reader, length);
  }

  /**
   * Sets the designated parameter to the given input stream. When a very large ASCII value is input
   * to a <code>LONGVARCHAR</code> parameter, it may be more practical to send it via a <code>
   * java.io.InputStream</code>. Data will be read from the stream as needed until end-of-file is
   * reached. The JDBC driver will do any necessary conversion from ASCII to the database char
   * format.
   *
   * <p><B>Note:</B> This stream object can either be a standard Java stream object or your own
   * subclass that implements the standard interface.
   *
   * <p><B>Note:</B> Consult your JDBC driver documentation to determine if it might be more
   * efficient to use a version of <code>setAsciiStream</code> which takes a length parameter.
   *
   * @param parameterName the name of the parameter
   * @param x the Java input stream that contains the ASCII parameter value
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.6
   */
  @Override
  public void setAsciiStream(String parameterName, InputStream x) throws SQLException {
    setAsciiStream(nameToIndex(parameterName), x);
  }

  /**
   * Sets the designated parameter to the given input stream. When a very large binary value is
   * input to a <code>LONGVARBINARY</code> parameter, it may be more practical to send it via a
   * <code>java.io.InputStream</code> object. The data will be read from the stream as needed until
   * end-of-file is reached.
   *
   * <p><B>Note:</B> This stream object can either be a standard Java stream object or your own
   * subclass that implements the standard interface.
   *
   * <p><B>Note:</B> Consult your JDBC driver documentation to determine if it might be more
   * efficient to use a version of <code>setBinaryStream</code> which takes a length parameter.
   *
   * @param parameterName the name of the parameter
   * @param x the java input stream which contains the binary parameter value
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.6
   */
  @Override
  public void setBinaryStream(String parameterName, InputStream x) throws SQLException {
    setBinaryStream(nameToIndex(parameterName), x);
  }

  /**
   * Sets the designated parameter to the given <code>Reader</code> object. When a very large
   * UNICODE value is input to a <code>LONGVARCHAR</code> parameter, it may be more practical to
   * send it via a <code>java.io.Reader</code> object. The data will be read from the stream as
   * needed until end-of-file is reached. The JDBC driver will do any necessary conversion from
   * UNICODE to the database char format.
   *
   * <p><B>Note:</B> This stream object can either be a standard Java stream object or your own
   * subclass that implements the standard interface.
   *
   * <p><B>Note:</B> Consult your JDBC driver documentation to determine if it might be more
   * efficient to use a version of <code>setCharacterStream</code> which takes a length parameter.
   *
   * @param parameterName the name of the parameter
   * @param reader the <code>java.io.Reader</code> object that contains the Unicode data
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.6
   */
  @Override
  public void setCharacterStream(String parameterName, Reader reader) throws SQLException {
    setCharacterStream(nameToIndex(parameterName), reader);
  }

  /**
   * Sets the designated parameter to a <code>Reader</code> object. The <code>Reader</code> reads
   * the data till end-of-file is reached. The driver does the necessary conversion from Java
   * character format to the national character set in the database.
   *
   * <p><B>Note:</B> This stream object can either be a standard Java stream object or your own
   * subclass that implements the standard interface.
   *
   * <p><B>Note:</B> Consult your JDBC driver documentation to determine if it might be more
   * efficient to use a version of <code>setNCharacterStream</code> which takes a length parameter.
   *
   * @param parameterName the name of the parameter
   * @param value the parameter value
   * @throws SQLException if parameterName does not correspond to a named parameter; if the driver
   *     does not support national character sets; if the driver can detect that a data conversion
   *     error could occur; if a database access error occurs; or this method is called on a closed
   *     <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.6
   */
  @Override
  public void setNCharacterStream(String parameterName, Reader value) throws SQLException {
    setNCharacterStream(nameToIndex(parameterName), value);
  }

  /**
   * Sets the designated parameter to a <code>Reader</code> object. This method differs from the
   * <code>setCharacterStream (int, Reader)</code> method because it informs the driver that the
   * parameter value should be sent to the server as a <code>CLOB</code>. When the <code>
   * setCharacterStream</code> method is used, the driver may have to do extra work to determine
   * whether the parameter data should be sent to the server as a <code>LONGVARCHAR</code> or a
   * <code>CLOB</code>
   *
   * <p><B>Note:</B> Consult your JDBC driver documentation to determine if it might be more
   * efficient to use a version of <code>setClob</code> which takes a length parameter.
   *
   * @param parameterName the name of the parameter
   * @param reader An object that contains the data to set the parameter value to.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.6
   */
  @Override
  public void setClob(String parameterName, Reader reader) throws SQLException {
    setClob(nameToIndex(parameterName), reader);
  }

  /**
   * Sets the designated parameter to an {@code InputStream} object. This method differs from the
   * <code>setBinaryStream (int, InputStream)</code> method because it informs the driver that the
   * parameter value should be sent to the server as a <code>BLOB</code>. When the <code>
   * setBinaryStream</code> method is used, the driver may have to do extra work to determine
   * whether the parameter data should be sent to the server as a <code>LONGVARBINARY</code> or a
   * <code>BLOB</code>
   *
   * <p><B>Note:</B> Consult your JDBC driver documentation to determine if it might be more
   * efficient to use a version of <code>setBlob</code> which takes a length parameter.
   *
   * @param parameterName the name of the parameter
   * @param inputStream An object that contains the data to set the parameter value to.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.6
   */
  @Override
  public void setBlob(String parameterName, InputStream inputStream) throws SQLException {
    setBlob(nameToIndex(parameterName), inputStream);
  }

  /**
   * Sets the designated parameter to a <code>Reader</code> object. This method differs from the
   * <code>setCharacterStream (int, Reader)</code> method because it informs the driver that the
   * parameter value should be sent to the server as a <code>NCLOB</code>. When the <code>
   * setCharacterStream</code> method is used, the driver may have to do extra work to determine
   * whether the parameter data should be sent to the server as a <code>LONGNVARCHAR</code> or a
   * <code>NCLOB</code>
   *
   * <p><B>Note:</B> Consult your JDBC driver documentation to determine if it might be more
   * efficient to use a version of <code>setNClob</code> which takes a length parameter.
   *
   * @param parameterName the name of the parameter
   * @param reader An object that contains the data to set the parameter value to.
   * @throws SQLException if parameterName does not correspond to a named parameter; if the driver
   *     does not support national character sets; if the driver can detect that a data conversion
   *     error could occur; if a database access error occurs or this method is called on a closed
   *     <code>CallableStatement</code>
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.6
   */
  @Override
  public void setNClob(String parameterName, Reader reader) throws SQLException {
    setNClob(nameToIndex(parameterName), reader);
  }

  /**
   * Returns an object representing the value of OUT parameter {@code parameterIndex} and will
   * convert from the SQL type of the parameter to the requested Java data type, if the conversion
   * is supported. If the conversion is not supported or null is specified for the type, a <code>
   * SQLException</code> is thrown.
   *
   * <p>At a minimum, an implementation must support the conversions defined in Appendix B, Table
   * B-3 and conversion of appropriate user defined SQL types to a Java type which implements {@code
   * SQLData}, or {@code Struct}. Additional conversions may be supported and are vendor defined.
   *
   * @param parameterIndex the first parameter is 1, the second is 2, and so on
   * @param type Class representing the Java data type to convert the designated parameter to.
   * @return an instance of {@code type} holding the OUT parameter value
   * @throws SQLException if conversion is not supported, type is null or another error occurs. The
   *     getCause() method of the exception may provide a more detailed exception, for example, if a
   *     conversion error occurs
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.7
   */
  @Override
  public <T> T getObject(int parameterIndex, Class<T> type) throws SQLException {
    checkNotClosed();
    checkOutputResult();
    return outputResult.getObject(idxToOutIdx(parameterIndex), type);
  }

  /**
   * Returns an object representing the value of OUT parameter {@code parameterName} and will
   * convert from the SQL type of the parameter to the requested Java data type, if the conversion
   * is supported. If the conversion is not supported or null is specified for the type, a <code>
   * SQLException</code> is thrown.
   *
   * <p>At a minimum, an implementation must support the conversions defined in Appendix B, Table
   * B-3 and conversion of appropriate user defined SQL types to a Java type which implements {@code
   * SQLData}, or {@code Struct}. Additional conversions may be supported and are vendor defined.
   *
   * @param parameterName the name of the parameter
   * @param type Class representing the Java data type to convert the designated parameter to.
   * @return an instance of {@code type} holding the OUT parameter value
   * @throws SQLException if conversion is not supported, type is null or another error occurs. The
   *     getCause() method of the exception may provide a more detailed exception, for example, if a
   *     conversion error occurs
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
   * @since 1.7
   */
  @Override
  public <T> T getObject(String parameterName, Class<T> type) throws SQLException {
    return getObject(nameToIndex(parameterName), type);
  }

  /**
   * Sets the value of the designated parameter with the given object.
   *
   * <p>If the second argument is an {@code InputStream} then the stream must contain the number of
   * bytes specified by scaleOrLength. If the second argument is a {@code Reader} then the reader
   * must contain the number of characters specified by scaleOrLength. If these conditions are not
   * true the driver will generate a {@code SQLException} when the prepared statement is executed.
   *
   * <p>The given Java object will be converted to the given targetSqlType before being sent to the
   * database.
   *
   * <p>If the object has a custom mapping (is of a class implementing the interface {@code
   * SQLData}), the JDBC driver should call the method {@code SQLData.writeSQL} to write it to the
   * SQL data stream. If, on the other hand, the object is of a class implementing {@code Ref},
   * {@code Blob}, {@code Clob}, {@code NClob}, {@code Struct}, {@code java.net.URL}, or {@code
   * Array}, the driver should pass it to the database as a value of the corresponding SQL type.
   *
   * <p>Note that this method may be used to pass database-specific abstract data types.
   *
   * <p>The default implementation will throw {@code SQLFeatureNotSupportedException}
   *
   * @param parameterName the name of the parameter
   * @param x the object containing the input parameter value
   * @param targetSqlType the SQL type to be sent to the database. The scale argument may further
   *     qualify this type.
   * @param scaleOrLength for {@code java.sql.JDBCType.DECIMAL} or {@code java.sql.JDBCType.NUMERIC
   *     types}, this is the number of digits after the decimal point. For Java Object types {@code
   *     InputStream} and {@code Reader}, this is the length of the data in the stream or reader.
   *     For all other types, this value will be ignored.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed {@code CallableStatement} or if
   *     the Java Object specified by x is an InputStream or Reader object and the value of the
   *     scale parameter is less than zero
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support the specified
   *     targetSqlType
   * @see JDBCType
   * @see SQLType
   * @since 1.8
   */
  @Override
  public void setObject(String parameterName, Object x, SQLType targetSqlType, int scaleOrLength)
      throws SQLException {
    setObject(nameToIndex(parameterName), x, targetSqlType, scaleOrLength);
  }

  /**
   * Sets the value of the designated parameter with the given object.
   *
   * <p>This method is similar to {@link #setObject(String parameterName, Object x, SQLType
   * targetSqlType, int scaleOrLength)}, except that it assumes a scale of zero.
   *
   * <p>The default implementation will throw {@code SQLFeatureNotSupportedException}
   *
   * @param parameterName the name of the parameter
   * @param x the object containing the input parameter value
   * @param targetSqlType the SQL type to be sent to the database
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed {@code CallableStatement}
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support the specified
   *     targetSqlType
   * @see JDBCType
   * @see SQLType
   * @since 1.8
   */
  @Override
  public void setObject(String parameterName, Object x, SQLType targetSqlType) throws SQLException {
    setObject(nameToIndex(parameterName), x, targetSqlType);
  }

  /**
   * Registers the OUT parameter in ordinal position {@code parameterIndex} to the JDBC type {@code
   * sqlType}. All OUT parameters must be registered before a stored procedure is executed.
   *
   * <p>The JDBC type specified by {@code sqlType} for an OUT parameter determines the Java type
   * that must be used in the {@code get} method to read the value of that parameter.
   *
   * <p>If the JDBC type expected to be returned to this output parameter is specific to this
   * particular database, {@code sqlType} may be {@code JDBCType.OTHER} or a {@code SQLType} that is
   * supported by the JDBC driver. The method {@link #getObject} retrieves the value.
   *
   * <p>The default implementation will throw {@code SQLFeatureNotSupportedException}
   *
   * @param parameterIndex the first parameter is 1, the second is 2, and so on
   * @param sqlType the JDBC type code defined by {@code SQLType} to use to register the OUT
   *     Parameter. If the parameter is of JDBC type {@code JDBCType.NUMERIC} or {@code
   *     JDBCType.DECIMAL}, the version of {@code registerOutParameter} that accepts a scale value
   *     should be used.
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed {@code CallableStatement}
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support the specified
   *     sqlType
   * @see JDBCType
   * @see SQLType
   * @since 1.8
   */
  @Override
  public void registerOutParameter(int parameterIndex, SQLType sqlType) throws SQLException {
    registerOutParameter(parameterIndex, 0);
  }

  /**
   * Registers the parameter in ordinal position {@code parameterIndex} to be of JDBC type {@code
   * sqlType}. All OUT parameters must be registered before a stored procedure is executed.
   *
   * <p>The JDBC type specified by {@code sqlType} for an OUT parameter determines the Java type
   * that must be used in the {@code get} method to read the value of that parameter.
   *
   * <p>This version of {@code registerOutParameter} should be used when the parameter is of JDBC
   * type {@code JDBCType.NUMERIC} or {@code JDBCType.DECIMAL}.
   *
   * <p>The default implementation will throw {@code SQLFeatureNotSupportedException}
   *
   * @param parameterIndex the first parameter is 1, the second is 2, and so on
   * @param sqlType the JDBC type code defined by {@code SQLType} to use to register the OUT
   *     Parameter.
   * @param scale the desired number of digits to the right of the decimal point. It must be greater
   *     than or equal to zero.
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed {@code CallableStatement}
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support the specified
   *     sqlType
   * @see JDBCType
   * @see SQLType
   * @since 1.8
   */
  @Override
  public void registerOutParameter(int parameterIndex, SQLType sqlType, int scale)
      throws SQLException {
    registerOutParameter(parameterIndex, sqlType);
  }

  /**
   * Registers the designated output parameter. This version of the method {@code
   * registerOutParameter} should be used for a user-defined or {@code REF} output parameter.
   * Examples of user-defined types include: {@code STRUCT}, {@code DISTINCT}, {@code JAVA_OBJECT},
   * and named array types.
   *
   * <p>All OUT parameters must be registered before a stored procedure is executed.
   *
   * <p>For a user-defined parameter, the fully-qualified SQL type name of the parameter should also
   * be given, while a {@code REF} parameter requires that the fully-qualified type name of the
   * referenced type be given. A JDBC driver that does not need the type code and type name
   * information may ignore it. To be portable, however, applications should always provide these
   * values for user-defined and {@code REF} parameters.
   *
   * <p>Although it is intended for user-defined and {@code REF} parameters, this method may be used
   * to register a parameter of any JDBC type. If the parameter does not have a user-defined or
   * {@code REF} type, the <i>typeName</i> parameter is ignored.
   *
   * <p><B>Note:</B> When reading the value of an out parameter, you must use the getter method
   * whose Java type corresponds to the parameter's registered SQL type.
   *
   * <p>The default implementation will throw {@code SQLFeatureNotSupportedException}
   *
   * @param parameterIndex the first parameter is 1, the second is 2,...
   * @param sqlType the JDBC type code defined by {@code SQLType} to use to register the OUT
   *     Parameter.
   * @param typeName the fully-qualified name of an SQL structured type
   * @throws SQLException if the parameterIndex is not valid; if a database access error occurs or
   *     this method is called on a closed {@code CallableStatement}
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support the specified
   *     sqlType
   * @see JDBCType
   * @see SQLType
   * @since 1.8
   */
  @Override
  public void registerOutParameter(int parameterIndex, SQLType sqlType, String typeName)
      throws SQLException {
    registerOutParameter(parameterIndex, sqlType);
  }

  /**
   * Registers the OUT parameter named <code>parameterName</code> to the JDBC type {@code sqlType}.
   * All OUT parameters must be registered before a stored procedure is executed.
   *
   * <p>The JDBC type specified by {@code sqlType} for an OUT parameter determines the Java type
   * that must be used in the {@code get} method to read the value of that parameter.
   *
   * <p>If the JDBC type expected to be returned to this output parameter is specific to this
   * particular database, {@code sqlType} should be {@code JDBCType.OTHER} or a {@code SQLType} that
   * is supported by the JDBC driver. The method {@link #getObject} retrieves the value.
   *
   * <p>The default implementation will throw {@code SQLFeatureNotSupportedException}
   *
   * @param parameterName the name of the parameter
   * @param sqlType the JDBC type code defined by {@code SQLType} to use to register the OUT
   *     Parameter. If the parameter is of JDBC type {@code JDBCType.NUMERIC} or {@code
   *     JDBCType.DECIMAL}, the version of {@code registerOutParameter} that accepts a scale value
   *     should be used.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed {@code CallableStatement}
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support the specified
   *     sqlType or if the JDBC driver does not support this method
   * @see JDBCType
   * @see SQLType
   * @since 1.8
   */
  @Override
  public void registerOutParameter(String parameterName, SQLType sqlType) throws SQLException {
    registerOutParameter(nameToIndex(parameterName), sqlType);
  }

  /**
   * Registers the parameter named <code>parameterName</code> to be of JDBC type {@code sqlType}.
   * All OUT parameters must be registered before a stored procedure is executed.
   *
   * <p>The JDBC type specified by {@code sqlType} for an OUT parameter determines the Java type
   * that must be used in the {@code get} method to read the value of that parameter.
   *
   * <p>This version of {@code registerOutParameter} should be used when the parameter is of JDBC
   * type {@code JDBCType.NUMERIC} or {@code JDBCType.DECIMAL}.
   *
   * <p>The default implementation will throw {@code SQLFeatureNotSupportedException}
   *
   * @param parameterName the name of the parameter
   * @param sqlType the JDBC type code defined by {@code SQLType} to use to register the OUT
   *     Parameter.
   * @param scale the desired number of digits to the right of the decimal point. It must be greater
   *     than or equal to zero.
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed {@code CallableStatement}
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support the specified
   *     sqlType or if the JDBC driver does not support this method
   * @see JDBCType
   * @see SQLType
   * @since 1.8
   */
  @Override
  public void registerOutParameter(String parameterName, SQLType sqlType, int scale)
      throws SQLException {
    registerOutParameter(nameToIndex(parameterName), sqlType);
  }

  /**
   * Registers the designated output parameter. This version of the method {@code
   * registerOutParameter} should be used for a user-named or REF output parameter. Examples of
   * user-named types include: STRUCT, DISTINCT, JAVA_OBJECT, and named array types.
   *
   * <p>All OUT parameters must be registered before a stored procedure is executed. For a
   * user-named parameter the fully-qualified SQL type name of the parameter should also be given,
   * while a REF parameter requires that the fully-qualified type name of the referenced type be
   * given. A JDBC driver that does not need the type code and type name information may ignore it.
   * To be portable, however, applications should always provide these values for user-named and REF
   * parameters.
   *
   * <p>Although it is intended for user-named and REF parameters, this method may be used to
   * register a parameter of any JDBC type. If the parameter does not have a user-named or REF type,
   * the typeName parameter is ignored.
   *
   * <p><B>Note:</B> When reading the value of an out parameter, you must use the {@code getXXX}
   * method whose Java type XXX corresponds to the parameter's registered SQL type.
   *
   * <p>The default implementation will throw {@code SQLFeatureNotSupportedException}
   *
   * @param parameterName the name of the parameter
   * @param sqlType the JDBC type code defined by {@code SQLType} to use to register the OUT
   *     Parameter.
   * @param typeName the fully-qualified name of an SQL structured type
   * @throws SQLException if parameterName does not correspond to a named parameter; if a database
   *     access error occurs or this method is called on a closed {@code CallableStatement}
   * @throws SQLFeatureNotSupportedException if the JDBC driver does not support the specified
   *     sqlType or if the JDBC driver does not support this method
   * @see JDBCType
   * @see SQLType
   * @since 1.8
   */
  @Override
  public void registerOutParameter(String parameterName, SQLType sqlType, String typeName)
      throws SQLException {
    registerOutParameter(nameToIndex(parameterName), sqlType);
  }

  @Override
  public CallableParameterMetaData getParameterMetaData() throws SQLException {
    PreparedStatement prep =
        new ClientPreparedStatement(
            "SELECT * from information_schema.PARAMETERS "
                + "WHERE SPECIFIC_NAME = ? "
                + "AND SPECIFIC_SCHEMA = ? "
                + "ORDER BY ORDINAL_POSITION",
            con,
            lock,
            false,
            false,
            Statement.NO_GENERATED_KEYS,
            ResultSet.TYPE_FORWARD_ONLY,
            ResultSet.CONCUR_READ_ONLY,
            0);
    prep.setString(1, procedureName);
    prep.setString(2, databaseName);
    ResultSet rs = prep.executeQuery();
    parameterMetaData = new CallableParameterMetaData(rs, isFunction());
    return parameterMetaData;
  }
}
