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

com.mysql.cj.jdbc.CallableStatement Maven / Gradle / Ivy

/*
 * Copyright (c) 2002, 2019, Oracle and/or its affiliates. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, version 2.0, as published by the
 * Free Software Foundation.
 *
 * This program is also distributed with certain software (including but not
 * limited to OpenSSL) that is licensed under separate terms, as designated in a
 * particular file or component or in included license documentation. The
 * authors of MySQL hereby grant you an additional permission to link the
 * program and your derivative works with the separately licensed software that
 * they have included with MySQL.
 *
 * Without limiting anything contained in the foregoing, this file, which is
 * part of MySQL Connector/J, is also subject to the Universal FOSS Exception,
 * version 1.0, a copy of which can be found at
 * http://oss.oracle.com/licenses/universal-foss-exception.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0,
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
 */

package com.mysql.cj.jdbc;

import java.io.InputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.net.URL;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Date;
import java.sql.JDBCType;
import java.sql.NClob;
import java.sql.ParameterMetaData;
import java.sql.Ref;
import java.sql.ResultSet;
import java.sql.RowId;
import java.sql.SQLException;
import java.sql.SQLType;
import java.sql.SQLXML;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Wrapper;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.mysql.cj.Messages;
import com.mysql.cj.MysqlType;
import com.mysql.cj.PreparedQuery;
import com.mysql.cj.conf.PropertyDefinitions.DatabaseTerm;
import com.mysql.cj.conf.PropertyKey;
import com.mysql.cj.exceptions.FeatureNotAvailableException;
import com.mysql.cj.exceptions.MysqlErrorNumbers;
import com.mysql.cj.jdbc.exceptions.SQLError;
import com.mysql.cj.jdbc.result.ResultSetImpl;
import com.mysql.cj.jdbc.result.ResultSetInternalMethods;
import com.mysql.cj.protocol.a.result.ByteArrayRow;
import com.mysql.cj.protocol.a.result.ResultsetRowsStatic;
import com.mysql.cj.result.DefaultColumnDefinition;
import com.mysql.cj.result.Field;
import com.mysql.cj.result.Row;
import com.mysql.cj.util.StringUtils;
import com.mysql.cj.util.Util;

/**
 * Representation of stored procedures for JDBC
 */
public class CallableStatement extends ClientPreparedStatement implements java.sql.CallableStatement {

	protected static class CallableStatementParam {

		int index;

		int inOutModifier;

		boolean isIn;

		boolean isOut;

		int jdbcType;

		short nullability;

		String paramName;

		int precision;

		int scale;

		String typeName;

		MysqlType desiredMysqlType = MysqlType.UNKNOWN;

		CallableStatementParam(String name, int idx, boolean in, boolean out, int jdbcType, String typeName,
				int precision, int scale, short nullability, int inOutModifier) {
			this.paramName = name;
			this.isIn = in;
			this.isOut = out;
			this.index = idx;

			this.jdbcType = jdbcType;
			this.typeName = typeName;
			this.precision = precision;
			this.scale = scale;
			this.nullability = nullability;
			this.inOutModifier = inOutModifier;
		}

		@Override
		protected Object clone() throws CloneNotSupportedException {
			return super.clone();
		}
	}

	public class CallableStatementParamInfo implements ParameterMetaData {
		String dbInUse;

		boolean isFunctionCall;

		String nativeSql;

		int numParameters;

		List parameterList;

		Map parameterMap;

		/**
		 * synchronized externally in checkReadOnlyProcedure()
		 */
		boolean isReadOnlySafeProcedure = false;

		/**
		 * synchronized externally in checkReadOnlyProcedure()
		 */
		boolean isReadOnlySafeChecked = false;

		/**
		 * Constructor that converts a full list of parameter metadata into one that only represents the placeholders
		 * present in the {CALL ()}.
		 * 
		 * @param fullParamInfo the metadata for all parameters for this stored procedure or function.
		 */
		CallableStatementParamInfo(CallableStatementParamInfo fullParamInfo) {
			this.nativeSql = ((PreparedQuery) CallableStatement.this.query).getOriginalSql();
			this.dbInUse = CallableStatement.this.getCurrentDatabase();
			this.isFunctionCall = fullParamInfo.isFunctionCall;

			int[] localParameterMap = CallableStatement.this.placeholderToParameterIndexMap;
			int parameterMapLength = localParameterMap.length;

			this.isReadOnlySafeProcedure = fullParamInfo.isReadOnlySafeProcedure;
			this.isReadOnlySafeChecked = fullParamInfo.isReadOnlySafeChecked;
			this.parameterList = new ArrayList<>(fullParamInfo.numParameters);
			this.parameterMap = new HashMap<>(fullParamInfo.numParameters);

			if (this.isFunctionCall) {
				// Take the return value
				this.parameterList.add(fullParamInfo.parameterList.get(0));
			}

			int offset = this.isFunctionCall ? 1 : 0;

			for (int i = 0; i < parameterMapLength; i++) {
				if (localParameterMap[i] != 0) {
					CallableStatementParam param = fullParamInfo.parameterList.get(localParameterMap[i] + offset);

					this.parameterList.add(param);
					this.parameterMap.put(param.paramName, param);
				}
			}

			this.numParameters = this.parameterList.size();
		}

		CallableStatementParamInfo(java.sql.ResultSet paramTypesRs) throws SQLException {
			boolean hadRows = paramTypesRs.last();

			this.nativeSql = ((PreparedQuery) CallableStatement.this.query).getOriginalSql();
			this.dbInUse = CallableStatement.this.getCurrentDatabase();
			this.isFunctionCall = CallableStatement.this.callingStoredFunction;

			if (hadRows) {
				this.numParameters = paramTypesRs.getRow();

				this.parameterList = new ArrayList<>(this.numParameters);
				this.parameterMap = new HashMap<>(this.numParameters);

				paramTypesRs.beforeFirst();

				addParametersFromDBMD(paramTypesRs);
			} else {
				this.numParameters = 0;
			}

			if (this.isFunctionCall) {
				this.numParameters += 1;
			}
		}

		private void addParametersFromDBMD(java.sql.ResultSet paramTypesRs) throws SQLException {
			int i = 0;

			while (paramTypesRs.next()) {
				String paramName = paramTypesRs.getString(4);
				int inOutModifier;
				switch (paramTypesRs.getInt(5)) {
					case DatabaseMetaData.procedureColumnIn:
						inOutModifier = ParameterMetaData.parameterModeIn;
						break;
					case DatabaseMetaData.procedureColumnInOut:
						inOutModifier = ParameterMetaData.parameterModeInOut;
						break;
					case DatabaseMetaData.procedureColumnOut:
					case DatabaseMetaData.procedureColumnReturn:
						inOutModifier = ParameterMetaData.parameterModeOut;
						break;
					default:
						inOutModifier = ParameterMetaData.parameterModeUnknown;
				}

				boolean isOutParameter = false;
				boolean isInParameter = false;

				if (i == 0 && this.isFunctionCall) {
					isOutParameter = true;
					isInParameter = false;
				} else if (inOutModifier == java.sql.DatabaseMetaData.procedureColumnInOut) {
					isOutParameter = true;
					isInParameter = true;
				} else if (inOutModifier == java.sql.DatabaseMetaData.procedureColumnIn) {
					isOutParameter = false;
					isInParameter = true;
				} else if (inOutModifier == java.sql.DatabaseMetaData.procedureColumnOut) {
					isOutParameter = true;
					isInParameter = false;
				}

				int jdbcType = paramTypesRs.getInt(6);
				String typeName = paramTypesRs.getString(7);
				int precision = paramTypesRs.getInt(8);
				int scale = paramTypesRs.getInt(10);
				short nullability = paramTypesRs.getShort(12);

				CallableStatementParam paramInfoToAdd = new CallableStatementParam(paramName, i++, isInParameter,
						isOutParameter, jdbcType, typeName, precision, scale, nullability, inOutModifier);

				this.parameterList.add(paramInfoToAdd);
				this.parameterMap.put(paramName, paramInfoToAdd);
			}
		}

		protected void checkBounds(int paramIndex) throws SQLException {
			int localParamIndex = paramIndex - 1;

			if ((paramIndex < 0) || (localParamIndex >= this.numParameters)) {
				throw SQLError.createSQLException(
						Messages.getString("CallableStatement.11", new Object[]{paramIndex, this.numParameters}),
						MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
			}
		}

		@Override
		protected Object clone() throws CloneNotSupportedException {
			return super.clone();
		}

		CallableStatementParam getParameter(int index) {
			return this.parameterList.get(index);
		}

		CallableStatementParam getParameter(String name) {
			return this.parameterMap.get(name);
		}

		@Override
		public String getParameterClassName(int arg0) throws SQLException {
			String mysqlTypeName = getParameterTypeName(arg0);

			MysqlType mysqlType = MysqlType.getByName(mysqlTypeName);
			switch (mysqlType) {
				case YEAR:
					if (!CallableStatement.this.session.getPropertySet().getBooleanProperty(PropertyKey.yearIsDateType)
							.getValue()) {
						return Short.class.getName();
					}
					//
					// if (length == 1) {
					// if (propertySet.getBooleanReadableProperty(PropertyKey.transformedBitIsBoolean).getValue()) {
					// return MysqlType.BOOLEAN;
					// } else if (propertySet.getBooleanReadableProperty(PropertyKey.tinyInt1isBit).getValue()) {
					// return MysqlType.BIT;
					// }
					// }
					return mysqlType.getClassName();

				default:
					return mysqlType.getClassName();
			}

		}

		@Override
		public int getParameterCount() throws SQLException {
			if (this.parameterList == null) {
				return 0;
			}

			return this.parameterList.size();
		}

		@Override
		public int getParameterMode(int arg0) throws SQLException {
			checkBounds(arg0);

			return getParameter(arg0 - 1).inOutModifier;
		}

		@Override
		public int getParameterType(int arg0) throws SQLException {
			checkBounds(arg0);

			return getParameter(arg0 - 1).jdbcType;
		}

		@Override
		public String getParameterTypeName(int arg0) throws SQLException {
			checkBounds(arg0);

			return getParameter(arg0 - 1).typeName;
		}

		@Override
		public int getPrecision(int arg0) throws SQLException {
			checkBounds(arg0);

			return getParameter(arg0 - 1).precision;
		}

		@Override
		public int getScale(int arg0) throws SQLException {
			checkBounds(arg0);

			return getParameter(arg0 - 1).scale;
		}

		@Override
		public int isNullable(int arg0) throws SQLException {
			checkBounds(arg0);

			return getParameter(arg0 - 1).nullability;
		}

		@Override
		public boolean isSigned(int arg0) throws SQLException {
			checkBounds(arg0);

			return false;
		}

		Iterator iterator() {
			return this.parameterList.iterator();
		}

		int numberOfParameters() {
			return this.numParameters;
		}

		@Override
		public boolean isWrapperFor(Class iface) throws SQLException {
			checkClosed();

			// This works for classes that aren't actually wrapping anything
			return iface.isInstance(this);
		}

		@Override
		public  T unwrap(Class iface) throws java.sql.SQLException {
			try {
				// This works for classes that aren't actually wrapping anything
				return iface.cast(this);
			} catch (ClassCastException cce) {
				throw SQLError.createSQLException(
						Messages.getString("Common.UnableToUnwrap", new Object[]{iface.toString()}),
						MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
			}
		}
	}

	private final static int NOT_OUTPUT_PARAMETER_INDICATOR = Integer.MIN_VALUE;

	private final static String PARAMETER_NAMESPACE_PREFIX = "@com_mysql_jdbc_outparam_";

	private static String mangleParameterName(String origParameterName) {
		if (origParameterName == null) {
			return null;
		}

		int offset = 0;

		if (origParameterName.length() > 0 && origParameterName.charAt(0) == '@') {
			offset = 1;
		}

		StringBuilder paramNameBuf = new StringBuilder(
				PARAMETER_NAMESPACE_PREFIX.length() + origParameterName.length());
		paramNameBuf.append(PARAMETER_NAMESPACE_PREFIX);
		paramNameBuf.append(origParameterName.substring(offset));

		return paramNameBuf.toString();
	}

	private boolean callingStoredFunction = false;

	private ResultSetInternalMethods functionReturnValueResults;

	private boolean hasOutputParams = false;

	private ResultSetInternalMethods outputParameterResults;

	protected boolean outputParamWasNull = false;

	private int[] parameterIndexToRsIndex;

	protected CallableStatementParamInfo paramInfo;

	private CallableStatementParam returnValueParam;

	private boolean noAccessToProcedureBodies;

	/**
	 * Creates a new CallableStatement
	 * 
	 * @param  conn         the connection creating this statement
	 * @param  paramInfo    the SQL to prepare
	 * @throws SQLException if an error occurs
	 */
	public CallableStatement(JdbcConnection conn, CallableStatementParamInfo paramInfo) throws SQLException {
		super(conn, paramInfo.nativeSql, paramInfo.dbInUse);

		this.paramInfo = paramInfo;
		this.callingStoredFunction = this.paramInfo.isFunctionCall;

		if (this.callingStoredFunction) {
			((PreparedQuery) this.query).setParameterCount(((PreparedQuery) this.query).getParameterCount() + 1);
		}

		this.retrieveGeneratedKeys = true; // not provided for in the JDBC spec

		this.noAccessToProcedureBodies = conn.getPropertySet().getBooleanProperty(PropertyKey.noAccessToProcedureBodies)
				.getValue();
	}

	/**
	 * Creates a callable statement instance
	 * 
	 * @param  conn           the connection creating this statement
	 * @param  sql            the SQL to prepare
	 * @param  db             the current database
	 * @param  isFunctionCall is it a function call or a procedure call?
	 * @return                CallableStatement
	 * @throws SQLException   if an error occurs
	 */

	protected static CallableStatement getInstance(JdbcConnection conn, String sql, String db, boolean isFunctionCall)
			throws SQLException {
		return new CallableStatement(conn, sql, db, isFunctionCall);
	}

	/**
	 * Creates a callable statement instance
	 * 
	 * @param  conn         the connection creating this statement
	 * @param  paramInfo    the SQL to prepare
	 * @return              CallableStatement
	 * @throws SQLException if an error occurs
	 */

	protected static CallableStatement getInstance(JdbcConnection conn, CallableStatementParamInfo paramInfo)
			throws SQLException {
		return new CallableStatement(conn, paramInfo);
	}

	private int[] placeholderToParameterIndexMap;

	private void generateParameterMap() throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			if (this.paramInfo == null) {
				return;
			}

			// if the user specified some parameters as literals, we need to provide a map from the specified
			// placeholders to the actual parameter numbers

			int parameterCountFromMetaData = this.paramInfo.getParameterCount();

			// Ignore the first ? if this is a stored function, it doesn't count

			if (this.callingStoredFunction) {
				parameterCountFromMetaData--;
			}

			PreparedQuery q = ((PreparedQuery) this.query);
			if (this.paramInfo != null && q.getParameterCount() != parameterCountFromMetaData) {
				this.placeholderToParameterIndexMap = new int[q.getParameterCount()];

				int startPos = this.callingStoredFunction ? StringUtils.indexOfIgnoreCase(q.getOriginalSql(), "SELECT")
						: StringUtils.indexOfIgnoreCase(q.getOriginalSql(), "CALL");

				if (startPos != -1) {
					int parenOpenPos = q.getOriginalSql().indexOf('(', startPos + 4);

					if (parenOpenPos != -1) {
						int parenClosePos = StringUtils.indexOfIgnoreCase(parenOpenPos, q.getOriginalSql(), ")", "'",
								"'", StringUtils.SEARCH_MODE__ALL);

						if (parenClosePos != -1) {
							List parsedParameters = StringUtils.split(
									q.getOriginalSql().substring(parenOpenPos + 1, parenClosePos), ",", "'\"", "'\"",
									true);

							int numParsedParameters = parsedParameters.size();

							// sanity check

							if (numParsedParameters != q.getParameterCount()) {
								// bail?
							}

							int placeholderCount = 0;

							for (int i = 0; i < numParsedParameters; i++) {
								if (((String) parsedParameters.get(i)).equals("?")) {
									this.placeholderToParameterIndexMap[placeholderCount++] = i;
								}
							}
						}
					}
				}
			}
		}
	}

	/**
	 * Creates a new CallableStatement
	 * 
	 * @param  conn           the connection creating this statement
	 * @param  sql            the SQL to prepare
	 * @param  db             the current database
	 * @param  isFunctionCall is it a function call or a procedure call?
	 * @throws SQLException   if an error occurs
	 */
	public CallableStatement(JdbcConnection conn, String sql, String db, boolean isFunctionCall) throws SQLException {
		super(conn, sql, db);

		this.callingStoredFunction = isFunctionCall;

		if (!this.callingStoredFunction) {
			if (!StringUtils.startsWithIgnoreCaseAndWs(sql, "CALL")) {
				// not really a stored procedure call
				fakeParameterTypes(false);
			} else {
				determineParameterTypes();
			}

			generateParameterMap();
		} else {
			determineParameterTypes();
			generateParameterMap();

			((PreparedQuery) this.query).setParameterCount(((PreparedQuery) this.query).getParameterCount() + 1);
		}

		this.retrieveGeneratedKeys = true; // not provided for in the JDBC spec
		this.noAccessToProcedureBodies = conn.getPropertySet().getBooleanProperty(PropertyKey.noAccessToProcedureBodies)
				.getValue();
	}

	@Override
	public void addBatch() throws SQLException {
		setOutParams();

		super.addBatch();
	}

	private CallableStatementParam checkIsOutputParam(int paramIndex) throws SQLException {

		synchronized (checkClosed().getConnectionMutex()) {
			if (this.callingStoredFunction) {
				if (paramIndex == 1) {

					if (this.returnValueParam == null) {
						this.returnValueParam = new CallableStatementParam("", 0, false, true,
								MysqlType.VARCHAR.getJdbcType(), "VARCHAR", 0, 0,
								java.sql.DatabaseMetaData.attributeNullableUnknown,
								java.sql.DatabaseMetaData.procedureColumnReturn);
					}

					return this.returnValueParam;
				}

				// Move to position in output result set
				paramIndex--;
			}

			checkParameterIndexBounds(paramIndex);

			int localParamIndex = paramIndex - 1;

			if (this.placeholderToParameterIndexMap != null) {
				localParamIndex = this.placeholderToParameterIndexMap[localParamIndex];
			}

			CallableStatementParam paramDescriptor = this.paramInfo.getParameter(localParamIndex);

			// We don't have reliable metadata in this case, trust the caller

			if (this.noAccessToProcedureBodies) {
				paramDescriptor.isOut = true;
				paramDescriptor.isIn = true;
				paramDescriptor.inOutModifier = java.sql.DatabaseMetaData.procedureColumnInOut;
			} else if (!paramDescriptor.isOut) {
				throw SQLError.createSQLException(Messages.getString("CallableStatement.9", new Object[]{paramIndex}),
						MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
			}

			this.hasOutputParams = true;

			return paramDescriptor;
		}
	}

	/**
	 * @param  paramIndex   parameter index
	 * @throws SQLException if a database access error occurs or this method is called on a closed PreparedStatement
	 */
	private void checkParameterIndexBounds(int paramIndex) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			this.paramInfo.checkBounds(paramIndex);
		}
	}

	/**
	 * Checks whether or not this statement is supposed to be providing streamable result sets...If output parameters
	 * are registered, the driver can not stream the results.
	 * 
	 * @throws SQLException if a database access error occurs
	 */
	private void checkStreamability() throws SQLException {
		if (this.hasOutputParams && createStreamingResultSet()) {
			throw SQLError.createSQLException(Messages.getString("CallableStatement.14"),
					MysqlErrorNumbers.SQL_STATE_DRIVER_NOT_CAPABLE, getExceptionInterceptor());
		}
	}

	@Override
	public void clearParameters() throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			super.clearParameters();

			try {
				if (this.outputParameterResults != null) {
					this.outputParameterResults.close();
				}
			} finally {
				this.outputParameterResults = null;
			}
		}
	}

	/**
	 * Used to fake up some metadata when we don't have access to SHOW CREATE PROCEDURE or mysql.proc.
	 * 
	 * @param  isReallyProcedure is it a procedure or function
	 * @throws SQLException      if we can't build the metadata.
	 */
	private void fakeParameterTypes(boolean isReallyProcedure) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			String encoding = this.connection.getSession().getServerSession().getCharacterSetMetadata();
			int collationIndex = this.connection.getSession().getServerSession().getMetadataCollationIndex();
			Field[] fields = new Field[13];

			fields[0] = new Field("", "PROCEDURE_CAT", collationIndex, encoding, MysqlType.CHAR, 0);
			fields[1] = new Field("", "PROCEDURE_SCHEM", collationIndex, encoding, MysqlType.CHAR, 0);
			fields[2] = new Field("", "PROCEDURE_NAME", collationIndex, encoding, MysqlType.CHAR, 0);
			fields[3] = new Field("", "COLUMN_NAME", collationIndex, encoding, MysqlType.CHAR, 0);
			fields[4] = new Field("", "COLUMN_TYPE", collationIndex, encoding, MysqlType.CHAR, 0);
			fields[5] = new Field("", "DATA_TYPE", collationIndex, encoding, MysqlType.SMALLINT, 0);
			fields[6] = new Field("", "TYPE_NAME", collationIndex, encoding, MysqlType.CHAR, 0);
			fields[7] = new Field("", "PRECISION", collationIndex, encoding, MysqlType.INT, 0);
			fields[8] = new Field("", "LENGTH", collationIndex, encoding, MysqlType.INT, 0);
			fields[9] = new Field("", "SCALE", collationIndex, encoding, MysqlType.SMALLINT, 0);
			fields[10] = new Field("", "RADIX", collationIndex, encoding, MysqlType.SMALLINT, 0);
			fields[11] = new Field("", "NULLABLE", collationIndex, encoding, MysqlType.SMALLINT, 0);
			fields[12] = new Field("", "REMARKS", collationIndex, encoding, MysqlType.CHAR, 0);

			String procName = isReallyProcedure ? extractProcedureName() : null;

			byte[] procNameAsBytes = null;

			procNameAsBytes = procName == null ? null : StringUtils.getBytes(procName, "UTF-8");

			ArrayList resultRows = new ArrayList<>();

			for (int i = 0; i < ((PreparedQuery) this.query).getParameterCount(); i++) {
				byte[][] row = new byte[13][];
				row[0] = null; // PROCEDURE_CAT
				row[1] = null; // PROCEDURE_SCHEM
				row[2] = procNameAsBytes; // PROCEDURE/NAME
				row[3] = s2b(String.valueOf(i)); // COLUMN_NAME

				row[4] = s2b(String.valueOf(java.sql.DatabaseMetaData.procedureColumnIn));

				row[5] = s2b(String.valueOf(MysqlType.VARCHAR.getJdbcType())); // DATA_TYPE
				row[6] = s2b(MysqlType.VARCHAR.getName()); // TYPE_NAME
				row[7] = s2b(Integer.toString(65535)); // PRECISION
				row[8] = s2b(Integer.toString(65535)); // LENGTH
				row[9] = s2b(Integer.toString(0)); // SCALE
				row[10] = s2b(Integer.toString(10)); // RADIX

				row[11] = s2b(Integer.toString(java.sql.DatabaseMetaData.procedureNullableUnknown)); // nullable

				row[12] = null;

				resultRows.add(new ByteArrayRow(row, getExceptionInterceptor()));
			}

			java.sql.ResultSet paramTypesRs = this.resultSetFactory.createFromResultsetRows(ResultSet.CONCUR_READ_ONLY,
					ResultSet.TYPE_SCROLL_INSENSITIVE,
					new ResultsetRowsStatic(resultRows, new DefaultColumnDefinition(fields)));

			convertGetProcedureColumnsToInternalDescriptors(paramTypesRs);
		}
	}

	private void determineParameterTypes() throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			java.sql.ResultSet paramTypesRs = null;

			try {
				// Bug#57022, we need to check for db.SPname notation first and pass on only SPname
				String procName = extractProcedureName();
				String quotedId = this.session.getIdentifierQuoteString();

				List parseList = StringUtils.splitDBdotName(procName, "", quotedId,
						this.session.getServerSession().isNoBackslashEscapesSet());
				String tmpDb = "";
				// There *should* be 2 rows, if any.
				if (parseList.size() == 2) {
					tmpDb = (String) parseList.get(0);
					procName = (String) parseList.get(1);
				} else {
					// keep values as they are
				}

				java.sql.DatabaseMetaData dbmd = this.connection.getMetaData();

				boolean useDb = false;

				if (tmpDb.length() <= 0) {
					useDb = true;
				}

				paramTypesRs = this.session.getPropertySet().getEnumProperty(PropertyKey.databaseTerm)
						.getValue() == DatabaseTerm.SCHEMA
								? dbmd.getProcedureColumns(null, useDb ? this.getCurrentDatabase() : tmpDb/* null */,
										procName, "%")
								: dbmd.getProcedureColumns(useDb ? this.getCurrentDatabase() : tmpDb/* null */, null,
										procName, "%");

				boolean hasResults = false;
				try {
					if (paramTypesRs.next()) {
						paramTypesRs.previous();
						hasResults = true;
					}
				} catch (Exception e) {
					// paramTypesRs is empty, proceed with fake params. swallow, was expected
				}
				if (hasResults) {
					convertGetProcedureColumnsToInternalDescriptors(paramTypesRs);
				} else {
					fakeParameterTypes(true);
				}
			} finally {
				SQLException sqlExRethrow = null;

				if (paramTypesRs != null) {
					try {
						paramTypesRs.close();
					} catch (SQLException sqlEx) {
						sqlExRethrow = sqlEx;
					}

					paramTypesRs = null;
				}

				if (sqlExRethrow != null) {
					throw sqlExRethrow;
				}
			}
		}
	}

	private void convertGetProcedureColumnsToInternalDescriptors(java.sql.ResultSet paramTypesRs) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			this.paramInfo = new CallableStatementParamInfo(paramTypesRs);
		}
	}

	@Override
	public boolean execute() throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			boolean returnVal = false;

			checkStreamability();

			setInOutParamsOnServer();
			setOutParams();

			returnVal = super.execute();

			if (this.callingStoredFunction) {
				this.functionReturnValueResults = this.results;
				this.functionReturnValueResults.next();
				this.results = null;
			}

			//
			// From ResultSetRegressionTest.testBug14562():
			//
			// $ CREATE TABLE testBug14562 (row_order INT, signed_field MEDIUMINT, unsigned_field MEDIUMINT UNSIGNED)
			// $ INSERT INTO testBug14562 VALUES (1, -8388608, 0), (2, 8388607, 16777215)
			// $ CREATE PROCEDURE sp_testBug14562_1 (OUT param_1 MEDIUMINT, OUT param_2 MEDIUMINT UNSIGNED)
			// BEGIN
			// SELECT signed_field, unsigned_field INTO param_1, param_2 FROM testBug14562 WHERE row_order=1;
			// END
			// $ CALL sp_testBug14562_1(@com_mysql_jdbc_outparam_param_1, @com_mysql_jdbc_outparam_param_2)
			// $ SELECT @com_mysql_jdbc_outparam_param_1,@com_mysql_jdbc_outparam_param_2
			//
			// ResultSet metadata returns BIGINT for @com_mysql_jdbc_outparam_param_1 and
			// @com_mysql_jdbc_outparam_param_2
			// instead of expected MEDIUMINT. I wonder what happens to other types...
			retrieveOutParams();

			if (!this.callingStoredFunction) {
				return returnVal;
			}

			// Functions can't return results
			return false;
		}
	}

	@Override
	public java.sql.ResultSet executeQuery() throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {

			checkStreamability();

			java.sql.ResultSet execResults = null;

			setInOutParamsOnServer();
			setOutParams();

			execResults = super.executeQuery();

			retrieveOutParams();

			return execResults;
		}
	}

	@Override
	public int executeUpdate() throws SQLException {
		return Util.truncateAndConvertToInt(executeLargeUpdate());
	}

	private String extractProcedureName() throws SQLException {
		String sanitizedSql = StringUtils.stripComments(((PreparedQuery) this.query).getOriginalSql(), "`\"'",
				"`\"'", true, false, true, true);

		//
		int endCallIndex = StringUtils.indexOfIgnoreCase(sanitizedSql, "CALL ");
		int offset = 5;

		if (endCallIndex == -1) {
			endCallIndex = StringUtils.indexOfIgnoreCase(sanitizedSql, "SELECT ");
			offset = 7;
		}

		if (endCallIndex != -1) {
			StringBuilder nameBuf = new StringBuilder();

			String trimmedStatement = sanitizedSql.substring(endCallIndex + offset).trim();

			int statementLength = trimmedStatement.length();

			for (int i = 0; i < statementLength; i++) {
				char c = trimmedStatement.charAt(i);

				if (Character.isWhitespace(c) || (c == '(') || (c == '?')) {
					break;
				}
				nameBuf.append(c);

			}

			return nameBuf.toString();
		}

		throw SQLError.createSQLException(Messages.getString("CallableStatement.1"),
				MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
	}

	/**
	 * Adds 'at' symbol to beginning of parameter names if needed.
	 * 
	 * @param  paramNameIn  the parameter name to 'fix'
	 * @return              the parameter name with an 'a' prepended, if needed
	 * @throws SQLException if the parameter name is null or empty.
	 */
	protected String fixParameterName(String paramNameIn) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			if (paramNameIn == null) {
				paramNameIn = "nullpn";
			}

			if (this.noAccessToProcedureBodies) {
				throw SQLError.createSQLException(Messages.getString("CallableStatement.23"),
						MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
			}

			return mangleParameterName(paramNameIn);
		}
	}

	@Override
	public Array getArray(int i) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(i);

			Array retValue = rs.getArray(mapOutputParameterIndexToRsIndex(i));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public Array getArray(String parameterName) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(0); // definitely not going to be from ?=

			Array retValue = rs.getArray(fixParameterName(parameterName));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public BigDecimal getBigDecimal(int parameterIndex) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(parameterIndex);

			BigDecimal retValue = rs.getBigDecimal(mapOutputParameterIndexToRsIndex(parameterIndex));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	@Deprecated
	public BigDecimal getBigDecimal(int parameterIndex, int scale) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(parameterIndex);

			BigDecimal retValue = rs.getBigDecimal(mapOutputParameterIndexToRsIndex(parameterIndex), scale);

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public BigDecimal getBigDecimal(String parameterName) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(0); // definitely not going to be from ?=

			BigDecimal retValue = rs.getBigDecimal(fixParameterName(parameterName));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public Blob getBlob(int parameterIndex) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(parameterIndex);

			Blob retValue = rs.getBlob(mapOutputParameterIndexToRsIndex(parameterIndex));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public Blob getBlob(String parameterName) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(0); // definitely not going to be from ?=

			Blob retValue = rs.getBlob(fixParameterName(parameterName));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public boolean getBoolean(int parameterIndex) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(parameterIndex);

			boolean retValue = rs.getBoolean(mapOutputParameterIndexToRsIndex(parameterIndex));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public boolean getBoolean(String parameterName) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(0); // definitely not going to be from ?=

			boolean retValue = rs.getBoolean(fixParameterName(parameterName));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public byte getByte(int parameterIndex) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(parameterIndex);

			byte retValue = rs.getByte(mapOutputParameterIndexToRsIndex(parameterIndex));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public byte getByte(String parameterName) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(0); // definitely not going to be from ?=

			byte retValue = rs.getByte(fixParameterName(parameterName));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public byte[] getBytes(int parameterIndex) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(parameterIndex);

			byte[] retValue = rs.getBytes(mapOutputParameterIndexToRsIndex(parameterIndex));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public byte[] getBytes(String parameterName) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(0); // definitely not going to be from ?=

			byte[] retValue = rs.getBytes(fixParameterName(parameterName));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public Clob getClob(int parameterIndex) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(parameterIndex);

			Clob retValue = rs.getClob(mapOutputParameterIndexToRsIndex(parameterIndex));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public Clob getClob(String parameterName) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(0); // definitely not going to be from ?=

			Clob retValue = rs.getClob(fixParameterName(parameterName));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public Date getDate(int parameterIndex) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(parameterIndex);

			Date retValue = rs.getDate(mapOutputParameterIndexToRsIndex(parameterIndex));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public Date getDate(int parameterIndex, Calendar cal) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(parameterIndex);

			Date retValue = rs.getDate(mapOutputParameterIndexToRsIndex(parameterIndex), cal);

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public Date getDate(String parameterName) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(0); // definitely not going to be from ?=

			Date retValue = rs.getDate(fixParameterName(parameterName));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public Date getDate(String parameterName, Calendar cal) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(0); // definitely not going to be from ?=

			Date retValue = rs.getDate(fixParameterName(parameterName), cal);

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public double getDouble(int parameterIndex) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(parameterIndex);

			double retValue = rs.getDouble(mapOutputParameterIndexToRsIndex(parameterIndex));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public double getDouble(String parameterName) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(0); // definitely not going to be from ?=

			double retValue = rs.getDouble(fixParameterName(parameterName));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public float getFloat(int parameterIndex) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(parameterIndex);

			float retValue = rs.getFloat(mapOutputParameterIndexToRsIndex(parameterIndex));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public float getFloat(String parameterName) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(0); // definitely not going to be from ?=

			float retValue = rs.getFloat(fixParameterName(parameterName));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public int getInt(int parameterIndex) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(parameterIndex);

			int retValue = rs.getInt(mapOutputParameterIndexToRsIndex(parameterIndex));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public int getInt(String parameterName) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(0); // definitely not going to be from ?=

			int retValue = rs.getInt(fixParameterName(parameterName));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public long getLong(int parameterIndex) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(parameterIndex);

			long retValue = rs.getLong(mapOutputParameterIndexToRsIndex(parameterIndex));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public long getLong(String parameterName) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(0); // definitely not going to be from ?=

			long retValue = rs.getLong(fixParameterName(parameterName));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	protected int getNamedParamIndex(String paramName, boolean forOut) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			if (this.noAccessToProcedureBodies) {
				throw SQLError.createSQLException(
						"No access to parameters by name when connection has been configured not to access procedure bodies",
						MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
			}

			if ((paramName == null) || (paramName.length() == 0)) {
				throw SQLError.createSQLException(Messages.getString("CallableStatement.2"),
						MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
			}

			CallableStatementParam namedParamInfo;
			if (this.paramInfo == null || (namedParamInfo = this.paramInfo.getParameter(paramName)) == null) {
				throw SQLError.createSQLException(Messages.getString("CallableStatement.3", new Object[]{paramName}),
						MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
			}

			if (forOut && !namedParamInfo.isOut) {
				throw SQLError.createSQLException(Messages.getString("CallableStatement.5", new Object[]{paramName}),
						MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
			}

			if (this.placeholderToParameterIndexMap == null) {
				return namedParamInfo.index + 1; // JDBC indices are 1-based
			}

			for (int i = 0; i < this.placeholderToParameterIndexMap.length; i++) {
				if (this.placeholderToParameterIndexMap[i] == namedParamInfo.index) {
					return i + 1;
				}
			}

			throw SQLError.createSQLException(Messages.getString("CallableStatement.6", new Object[]{paramName}),
					MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
		}
	}

	@Override
	public Object getObject(int parameterIndex) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			CallableStatementParam paramDescriptor = checkIsOutputParam(parameterIndex);

			ResultSetInternalMethods rs = getOutputParameters(parameterIndex);

			Object retVal = rs.getObjectStoredProc(mapOutputParameterIndexToRsIndex(parameterIndex),
					paramDescriptor.desiredMysqlType.getJdbcType());

			this.outputParamWasNull = rs.wasNull();

			return retVal;
		}
	}

	@Override
	public Object getObject(int parameterIndex, Map> map) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(parameterIndex);

			Object retVal = rs.getObject(mapOutputParameterIndexToRsIndex(parameterIndex), map);

			this.outputParamWasNull = rs.wasNull();

			return retVal;
		}
	}

	@Override
	public Object getObject(String parameterName) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(0); // definitely not going to be from ?=

			Object retValue = rs.getObject(fixParameterName(parameterName));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public Object getObject(String parameterName, Map> map) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(0); // definitely not going to be from ?=

			Object retValue = rs.getObject(fixParameterName(parameterName), map);

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public  T getObject(int parameterIndex, Class type) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(parameterIndex);

			// remove cast once 1.5, 1.6 EOL'd
			T retVal = ((ResultSetImpl) rs).getObject(mapOutputParameterIndexToRsIndex(parameterIndex), type);

			this.outputParamWasNull = rs.wasNull();

			return retVal;
		}
	}

	@Override
	public  T getObject(String parameterName, Class type) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(0); // definitely not going to be from ?=

			T retValue = ((ResultSetImpl) rs).getObject(fixParameterName(parameterName), type);

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	/**
	 * Returns the ResultSet that holds the output parameters, or throws an appropriate exception if none exist, or they
	 * weren't returned.
	 * 
	 * @param  paramIndex   parameter index
	 * @return              the ResultSet that holds the output parameters
	 * @throws SQLException if no output parameters were defined, or if no output parameters were returned.
	 */
	protected ResultSetInternalMethods getOutputParameters(int paramIndex) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			this.outputParamWasNull = false;

			if (paramIndex == 1 && this.callingStoredFunction && this.returnValueParam != null) {
				return this.functionReturnValueResults;
			}

			if (this.outputParameterResults == null) {
				if (this.paramInfo.numberOfParameters() == 0) {
					throw SQLError.createSQLException(Messages.getString("CallableStatement.7"),
							MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
				}
				throw SQLError.createSQLException(Messages.getString("CallableStatement.8"),
						MysqlErrorNumbers.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
			}

			return this.outputParameterResults;
		}
	}

	@Override
	public ParameterMetaData getParameterMetaData() throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			if (this.placeholderToParameterIndexMap == null) {
				return this.paramInfo;
			}

			return new CallableStatementParamInfo(this.paramInfo);
		}
	}

	@Override
	public Ref getRef(int parameterIndex) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(parameterIndex);

			Ref retValue = rs.getRef(mapOutputParameterIndexToRsIndex(parameterIndex));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public Ref getRef(String parameterName) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(0); // definitely not going to be from ?=

			Ref retValue = rs.getRef(fixParameterName(parameterName));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public short getShort(int parameterIndex) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(parameterIndex);

			short retValue = rs.getShort(mapOutputParameterIndexToRsIndex(parameterIndex));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public short getShort(String parameterName) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(0); // definitely not going to be from ?=

			short retValue = rs.getShort(fixParameterName(parameterName));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public String getString(int parameterIndex) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(parameterIndex);

			String retValue = rs.getString(mapOutputParameterIndexToRsIndex(parameterIndex));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public String getString(String parameterName) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(0); // definitely not going to be from ?=

			String retValue = rs.getString(fixParameterName(parameterName));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public Time getTime(int parameterIndex) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(parameterIndex);

			Time retValue = rs.getTime(mapOutputParameterIndexToRsIndex(parameterIndex));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public Time getTime(int parameterIndex, Calendar cal) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(parameterIndex);

			Time retValue = rs.getTime(mapOutputParameterIndexToRsIndex(parameterIndex), cal);

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public Time getTime(String parameterName) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(0); // definitely not going to be from ?=

			Time retValue = rs.getTime(fixParameterName(parameterName));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public Time getTime(String parameterName, Calendar cal) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(0); // definitely not going to be from ?=

			Time retValue = rs.getTime(fixParameterName(parameterName), cal);

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public Timestamp getTimestamp(int parameterIndex) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(parameterIndex);

			Timestamp retValue = rs.getTimestamp(mapOutputParameterIndexToRsIndex(parameterIndex));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public Timestamp getTimestamp(int parameterIndex, Calendar cal) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(parameterIndex);

			Timestamp retValue = rs.getTimestamp(mapOutputParameterIndexToRsIndex(parameterIndex), cal);

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public Timestamp getTimestamp(String parameterName) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(0); // definitely not going to be from ?=

			Timestamp retValue = rs.getTimestamp(fixParameterName(parameterName));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public Timestamp getTimestamp(String parameterName, Calendar cal) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(0); // definitely not going to be from ?=

			Timestamp retValue = rs.getTimestamp(fixParameterName(parameterName), cal);

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public URL getURL(int parameterIndex) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(parameterIndex);

			URL retValue = rs.getURL(mapOutputParameterIndexToRsIndex(parameterIndex));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	@Override
	public URL getURL(String parameterName) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			ResultSetInternalMethods rs = getOutputParameters(0); // definitely not going to be from ?=

			URL retValue = rs.getURL(fixParameterName(parameterName));

			this.outputParamWasNull = rs.wasNull();

			return retValue;
		}
	}

	protected int mapOutputParameterIndexToRsIndex(int paramIndex) throws SQLException {

		synchronized (checkClosed().getConnectionMutex()) {
			if (this.returnValueParam != null && paramIndex == 1) {
				return 1;
			}

			checkParameterIndexBounds(paramIndex);

			int localParamIndex = paramIndex - 1;

			if (this.placeholderToParameterIndexMap != null) {
				localParamIndex = this.placeholderToParameterIndexMap[localParamIndex];
			}

			int rsIndex = this.parameterIndexToRsIndex[localParamIndex];

			if (rsIndex == NOT_OUTPUT_PARAMETER_INDICATOR) {
				throw SQLError.createSQLException(Messages.getString("CallableStatement.21", new Object[]{paramIndex}),
						MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
			}

			return rsIndex + 1;
		}
	}

	protected void registerOutParameter(int parameterIndex, MysqlType mysqlType) throws SQLException {
		CallableStatementParam paramDescriptor = checkIsOutputParam(parameterIndex);
		paramDescriptor.desiredMysqlType = mysqlType;
	}

	@Override
	public void registerOutParameter(int parameterIndex, int sqlType) throws SQLException {
		try {
			MysqlType mt = MysqlType.getByJdbcType(sqlType);
			registerOutParameter(parameterIndex, mt);
		} catch (FeatureNotAvailableException nae) {
			throw SQLError.createSQLFeatureNotSupportedException(
					Messages.getString("Statement.UnsupportedSQLType") + JDBCType.valueOf(sqlType),
					MysqlErrorNumbers.SQL_STATE_DRIVER_NOT_CAPABLE, getExceptionInterceptor());
		}
	}

	@Override
	public void registerOutParameter(int parameterIndex, SQLType sqlType) throws SQLException {
		if (sqlType instanceof MysqlType) {
			registerOutParameter(parameterIndex, (MysqlType) sqlType);
		} else {
			registerOutParameter(parameterIndex, sqlType.getVendorTypeNumber());
		}
	}

	protected void registerOutParameter(int parameterIndex, MysqlType mysqlType, int scale) throws SQLException {
		registerOutParameter(parameterIndex, mysqlType); //
	}

	@Override
	public void registerOutParameter(int parameterIndex, int sqlType, int scale) throws SQLException {
		registerOutParameter(parameterIndex, sqlType); //
	}

	@Override
	public void registerOutParameter(int parameterIndex, SQLType sqlType, int scale) throws SQLException {
		if (sqlType instanceof MysqlType) {
			registerOutParameter(parameterIndex, (MysqlType) sqlType, scale);
		} else {
			registerOutParameter(parameterIndex, sqlType.getVendorTypeNumber(), scale);
		}
	}

	protected void registerOutParameter(int parameterIndex, MysqlType mysqlType,
			 String typeName) throws SQLException {
		registerOutParameter(parameterIndex, mysqlType);
	}

	@Override
	public void registerOutParameter(int parameterIndex, int sqlType, String typeName) throws SQLException {
		try {
			MysqlType mt = MysqlType.getByJdbcType(sqlType);
			registerOutParameter(parameterIndex, mt, typeName);
		} catch (FeatureNotAvailableException nae) {
			throw SQLError.createSQLFeatureNotSupportedException(
					Messages.getString("Statement.UnsupportedSQLType") + JDBCType.valueOf(sqlType),
					MysqlErrorNumbers.SQL_STATE_DRIVER_NOT_CAPABLE, getExceptionInterceptor());
		}
	}

	@Override
	public void registerOutParameter(int parameterIndex, SQLType sqlType, String typeName) throws SQLException {
		if (sqlType instanceof MysqlType) {
			registerOutParameter(parameterIndex, (MysqlType) sqlType, typeName);
		} else {
			registerOutParameter(parameterIndex, sqlType.getVendorTypeNumber(), typeName);
		}
	}

	@Override
	public void registerOutParameter(String parameterName, int sqlType) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			registerOutParameter(getNamedParamIndex(parameterName, true), sqlType);
		}
	}

	@Override
	public void registerOutParameter(String parameterName, SQLType sqlType) throws SQLException {
		if (sqlType instanceof MysqlType) {
			registerOutParameter(getNamedParamIndex(parameterName, true), (MysqlType) sqlType);
		} else {
			registerOutParameter(getNamedParamIndex(parameterName, true), sqlType.getVendorTypeNumber());
		}
	}

	@Override
	public void registerOutParameter(String parameterName, int sqlType, int scale) throws SQLException {
		registerOutParameter(getNamedParamIndex(parameterName, true), sqlType, scale);
	}

	@Override
	public void registerOutParameter(String parameterName, SQLType sqlType, int scale) throws SQLException {
		if (sqlType instanceof MysqlType) {
			registerOutParameter(getNamedParamIndex(parameterName, true), (MysqlType) sqlType, scale);
		} else {
			registerOutParameter(getNamedParamIndex(parameterName, true), sqlType.getVendorTypeNumber(), scale);
		}
	}

	@Override
	public void registerOutParameter(String parameterName, int sqlType, String typeName) throws SQLException {
		registerOutParameter(getNamedParamIndex(parameterName, true), sqlType, typeName);
	}

	@Override
	public void registerOutParameter(String parameterName, SQLType sqlType, String typeName) throws SQLException {
		if (sqlType instanceof MysqlType) {
			registerOutParameter(getNamedParamIndex(parameterName, true), (MysqlType) sqlType, typeName);
		} else {
			registerOutParameter(parameterName, sqlType.getVendorTypeNumber(), typeName);
		}
	}

	/**
	 * Issues a second query to retrieve all output parameters.
	 * 
	 * @throws SQLException if an error occurs.
	 */
	private void retrieveOutParams() throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			int numParameters = this.paramInfo.numberOfParameters();

			this.parameterIndexToRsIndex = new int[numParameters];

			for (int i = 0; i < numParameters; i++) {
				this.parameterIndexToRsIndex[i] = NOT_OUTPUT_PARAMETER_INDICATOR;
			}

			int localParamIndex = 0;

			if (numParameters > 0) {
				StringBuilder outParameterQuery = new StringBuilder("SELECT ");

				boolean firstParam = true;
				boolean hadOutputParams = false;

				for (Iterator paramIter = this.paramInfo.iterator(); paramIter.hasNext();) {
					CallableStatementParam retrParamInfo = paramIter.next();

					if (retrParamInfo.isOut) {
						hadOutputParams = true;

						this.parameterIndexToRsIndex[retrParamInfo.index] = localParamIndex++;

						if (retrParamInfo.paramName == null) {
							retrParamInfo.paramName = "nullnp" + retrParamInfo.index;
						}

						String outParameterName = mangleParameterName(retrParamInfo.paramName);

						if (!firstParam) {
							outParameterQuery.append(",");
						} else {
							firstParam = false;
						}

						if (!outParameterName.startsWith("@")) {
							outParameterQuery.append('@');
						}

						outParameterQuery.append(outParameterName);
					}
				}

				if (hadOutputParams) {
					// We can't use 'ourself' to execute this query, or any pending result sets would be overwritten
					java.sql.Statement outParameterStmt = null;
					java.sql.ResultSet outParamRs = null;

					try {
						outParameterStmt = this.connection.createStatement();
						outParamRs = outParameterStmt.executeQuery(outParameterQuery.toString());
						this.outputParameterResults = this.resultSetFactory.createFromResultsetRows(
								outParamRs.getConcurrency(), outParamRs.getType(),
								((com.mysql.cj.jdbc.result.ResultSetInternalMethods) outParamRs).getRows()); // note,
																												// doesn't
																												// work
																												// for
																												// updatable
																												// result
																												// sets

						if (!this.outputParameterResults.next()) {
							this.outputParameterResults.close();
							this.outputParameterResults = null;
						}
					} finally {
						if (outParameterStmt != null) {
							outParameterStmt.close();
						}
					}
				} else {
					this.outputParameterResults = null;
				}
			} else {
				this.outputParameterResults = null;
			}
		}
	}

	@Override
	public void setAsciiStream(String parameterName, InputStream x, int length) throws SQLException {
		setAsciiStream(getNamedParamIndex(parameterName, false), x, length);
	}

	@Override
	public void setBigDecimal(String parameterName, BigDecimal x) throws SQLException {
		setBigDecimal(getNamedParamIndex(parameterName, false), x);
	}

	@Override
	public void setBinaryStream(String parameterName, InputStream x, int length) throws SQLException {
		setBinaryStream(getNamedParamIndex(parameterName, false), x, length);
	}

	@Override
	public void setBoolean(String parameterName, boolean x) throws SQLException {
		setBoolean(getNamedParamIndex(parameterName, false), x);
	}

	@Override
	public void setByte(String parameterName, byte x) throws SQLException {
		setByte(getNamedParamIndex(parameterName, false), x);
	}

	@Override
	public void setBytes(String parameterName, byte[] x) throws SQLException {
		setBytes(getNamedParamIndex(parameterName, false), x);
	}

	@Override
	public void setCharacterStream(String parameterName, Reader reader, int length) throws SQLException {
		setCharacterStream(getNamedParamIndex(parameterName, false), reader, length);
	}

	@Override
	public void setDate(String parameterName, Date x) throws SQLException {
		setDate(getNamedParamIndex(parameterName, false), x);
	}

	@Override
	public void setDate(String parameterName, Date x, Calendar cal) throws SQLException {
		setDate(getNamedParamIndex(parameterName, false), x, cal);
	}

	@Override
	public void setDouble(String parameterName, double x) throws SQLException {
		setDouble(getNamedParamIndex(parameterName, false), x);
	}

	@Override
	public void setFloat(String parameterName, float x) throws SQLException {
		setFloat(getNamedParamIndex(parameterName, false), x);
	}

	private void setInOutParamsOnServer() throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			if (this.paramInfo.numParameters > 0) {
				for (Iterator paramIter = this.paramInfo.iterator(); paramIter.hasNext();) {

					CallableStatementParam inParamInfo = paramIter.next();

					if (inParamInfo.isOut && inParamInfo.isIn) {
						if (inParamInfo.paramName == null) {
							inParamInfo.paramName = "nullnp" + inParamInfo.index;
						}

						String inOutParameterName = mangleParameterName(inParamInfo.paramName);
						StringBuilder queryBuf = new StringBuilder(4 + inOutParameterName.length() + 1 + 1);
						queryBuf.append("SET ");
						queryBuf.append(inOutParameterName);
						queryBuf.append("=?");

						ClientPreparedStatement setPstmt = null;

						try {
							setPstmt = ((Wrapper) this.connection.clientPrepareStatement(queryBuf.toString()))
									.unwrap(ClientPreparedStatement.class);

							if (((PreparedQuery) this.query).getQueryBindings().getBindValues()[inParamInfo.index]
									.isNull()) {
								setPstmt.setBytesNoEscapeNoQuotes(1, "NULL".getBytes());

							} else {
								byte[] parameterAsBytes = getBytesRepresentation(inParamInfo.index + 1);

								if (parameterAsBytes != null) {
									if (parameterAsBytes.length > 8 && parameterAsBytes[0] == '_'
											&& parameterAsBytes[1] == 'b' && parameterAsBytes[2] == 'i'
											&& parameterAsBytes[3] == 'n' && parameterAsBytes[4] == 'a'
											&& parameterAsBytes[5] == 'r' && parameterAsBytes[6] == 'y'
											&& parameterAsBytes[7] == '\'') {
										setPstmt.setBytesNoEscapeNoQuotes(1, parameterAsBytes);
									} else {
										switch (inParamInfo.desiredMysqlType) {
											case BIT:
											case BINARY:
											case GEOMETRY:
											case TINYBLOB:
											case BLOB:
											case MEDIUMBLOB:
											case LONGBLOB:
											case VARBINARY:
												setPstmt.setBytes(1, parameterAsBytes);
												break;
											default:
												// the inherited PreparedStatement methods have already escaped and
												// quoted these parameters
												setPstmt.setBytesNoEscape(1, parameterAsBytes);
										}
									}
								} else {
									setPstmt.setNull(1, MysqlType.NULL);
								}
							}

							setPstmt.executeUpdate();
						} finally {
							if (setPstmt != null) {
								setPstmt.close();
							}
						}
					}
				}
			}
		}
	}

	@Override
	public void setInt(String parameterName, int x) throws SQLException {
		setInt(getNamedParamIndex(parameterName, false), x);
	}

	@Override
	public void setLong(String parameterName, long x) throws SQLException {
		setLong(getNamedParamIndex(parameterName, false), x);
	}

	@Override
	public void setNull(String parameterName, int sqlType) throws SQLException {
		setNull(getNamedParamIndex(parameterName, false), sqlType);
	}

	@Override
	public void setNull(String parameterName, int sqlType, String typeName) throws SQLException {
		setNull(getNamedParamIndex(parameterName, false), sqlType, typeName);
	}

	@Override
	public void setObject(String parameterName, Object x) throws SQLException {
		setObject(getNamedParamIndex(parameterName, false), x);
	}

	@Override
	public void setObject(String parameterName, Object x, int targetSqlType) throws SQLException {
		setObject(getNamedParamIndex(parameterName, false), x, targetSqlType);
	}

	@Override
	public void setObject(String parameterName, Object x, SQLType targetSqlType) throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			setObject(getNamedParamIndex(parameterName, false), x, targetSqlType);
		}
	}

	@Override
	public void setObject(String parameterName, Object x, int targetSqlType, int scale) throws SQLException {
		setObject(getNamedParamIndex(parameterName, false), x, targetSqlType, scale);
	}

	@Override
	public void setObject(String parameterName, Object x, SQLType targetSqlType, int scaleOrLength)
			throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			setObject(getNamedParamIndex(parameterName, false), x, targetSqlType, scaleOrLength);
		}
	}

	private void setOutParams() throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			if (this.paramInfo.numParameters > 0) {
				for (Iterator paramIter = this.paramInfo.iterator(); paramIter.hasNext();) {
					CallableStatementParam outParamInfo = paramIter.next();

					if (!this.callingStoredFunction && outParamInfo.isOut) {

						if (outParamInfo.paramName == null) {
							outParamInfo.paramName = "nullnp" + outParamInfo.index;
						}

						String outParameterName = mangleParameterName(outParamInfo.paramName);

						int outParamIndex = 0;

						if (this.placeholderToParameterIndexMap == null) {
							outParamIndex = outParamInfo.index + 1;
						} else {
							// Find it, todo: remove this linear search
							boolean found = false;

							for (int i = 0; i < this.placeholderToParameterIndexMap.length; i++) {
								if (this.placeholderToParameterIndexMap[i] == outParamInfo.index) {
									outParamIndex = i + 1; /* JDBC is 1-based */
									found = true;
									break;
								}
							}

							if (!found) {
								throw SQLError.createSQLException(
										Messages.getString("CallableStatement.21",
												new Object[]{outParamInfo.paramName}),
										MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
							}
						}

						this.setBytesNoEscapeNoQuotes(outParamIndex,
								StringUtils.getBytes(outParameterName, this.charEncoding));
					}
				}
			}
		}
	}

	@Override
	public void setShort(String parameterName, short x) throws SQLException {
		setShort(getNamedParamIndex(parameterName, false), x);
	}

	@Override
	public void setString(String parameterName, String x) throws SQLException {
		setString(getNamedParamIndex(parameterName, false), x);
	}

	@Override
	public void setTime(String parameterName, Time x) throws SQLException {
		setTime(getNamedParamIndex(parameterName, false), x);
	}

	@Override
	public void setTime(String parameterName, Time x, Calendar cal) throws SQLException {
		setTime(getNamedParamIndex(parameterName, false), x, cal);
	}

	@Override
	public void setTimestamp(String parameterName, Timestamp x) throws SQLException {
		setTimestamp(getNamedParamIndex(parameterName, false), x);
	}

	@Override
	public void setTimestamp(String parameterName, Timestamp x, Calendar cal) throws SQLException {
		setTimestamp(getNamedParamIndex(parameterName, false), x, cal);
	}

	@Override
	public void setURL(String parameterName, URL val) throws SQLException {
		setURL(getNamedParamIndex(parameterName, false), val);
	}

	@Override
	public boolean wasNull() throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			return this.outputParamWasNull;
		}
	}

	@Override
	public int[] executeBatch() throws SQLException {
		return Util.truncateAndConvertToInt(executeLargeBatch());

	}

	@Override
	protected int getParameterIndexOffset() {
		if (this.callingStoredFunction) {
			return -1;
		}

		return super.getParameterIndexOffset();
	}

	@Override
	public void setAsciiStream(String parameterName, InputStream x) throws SQLException {
		setAsciiStream(getNamedParamIndex(parameterName, false), x);

	}

	@Override
	public void setAsciiStream(String parameterName, InputStream x, long length) throws SQLException {
		setAsciiStream(getNamedParamIndex(parameterName, false), x, length);

	}

	@Override
	public void setBinaryStream(String parameterName, InputStream x) throws SQLException {
		setBinaryStream(getNamedParamIndex(parameterName, false), x);

	}

	@Override
	public void setBinaryStream(String parameterName, InputStream x, long length) throws SQLException {
		setBinaryStream(getNamedParamIndex(parameterName, false), x, length);

	}

	@Override
	public void setBlob(String parameterName, Blob x) throws SQLException {
		setBlob(getNamedParamIndex(parameterName, false), x);

	}

	@Override
	public void setBlob(String parameterName, InputStream inputStream) throws SQLException {
		setBlob(getNamedParamIndex(parameterName, false), inputStream);

	}

	@Override
	public void setBlob(String parameterName, InputStream inputStream, long length) throws SQLException {
		setBlob(getNamedParamIndex(parameterName, false), inputStream, length);

	}

	@Override
	public void setCharacterStream(String parameterName, Reader reader) throws SQLException {
		setCharacterStream(getNamedParamIndex(parameterName, false), reader);

	}

	@Override
	public void setCharacterStream(String parameterName, Reader reader, long length) throws SQLException {
		setCharacterStream(getNamedParamIndex(parameterName, false), reader, length);

	}

	@Override
	public void setClob(String parameterName, Clob x) throws SQLException {
		setClob(getNamedParamIndex(parameterName, false), x);

	}

	@Override
	public void setClob(String parameterName, Reader reader) throws SQLException {
		setClob(getNamedParamIndex(parameterName, false), reader);

	}

	@Override
	public void setClob(String parameterName, Reader reader, long length) throws SQLException {
		setClob(getNamedParamIndex(parameterName, false), reader, length);

	}

	@Override
	public void setNCharacterStream(String parameterName, Reader value) throws SQLException {
		setNCharacterStream(getNamedParamIndex(parameterName, false), value);

	}

	@Override
	public void setNCharacterStream(String parameterName, Reader value, long length) throws SQLException {
		setNCharacterStream(getNamedParamIndex(parameterName, false), value, length);

	}

	/**
	 * Check whether the stored procedure alters any data or is safe for read-only usage.
	 * 
	 * @return              true if procedure does not alter data
	 * @throws SQLException if a database access error occurs or this method is called on a closed PreparedStatement
	 */
	private boolean checkReadOnlyProcedure() throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			if (this.noAccessToProcedureBodies) {
				return false;
			}

			if (this.paramInfo.isReadOnlySafeChecked) {
				return this.paramInfo.isReadOnlySafeProcedure;
			}

			ResultSet rs = null;
			java.sql.PreparedStatement ps = null;

			try {
				String procName = extractProcedureName();

				String db = this.getCurrentDatabase();

				if (procName.indexOf(".") != -1) {
					db = procName.substring(0, procName.indexOf("."));

					if (StringUtils.startsWithIgnoreCaseAndWs(db, "`") && db.trim().endsWith("`")) {
						db = db.substring(1, db.length() - 1);
					}

					procName = procName.substring(procName.indexOf(".") + 1);
					procName = StringUtils
							.toString(StringUtils.stripEnclosure(StringUtils.getBytes(procName), "`", "`"));
				}
				ps = this.connection.prepareStatement(
						"SELECT SQL_DATA_ACCESS FROM information_schema.routines WHERE routine_schema = ? AND routine_name = ?");
				ps.setMaxRows(0);
				ps.setFetchSize(0);

				ps.setString(1, db);
				ps.setString(2, procName);
				rs = ps.executeQuery();
				if (rs.next()) {
					String sqlDataAccess = rs.getString(1);
					if ("READS SQL DATA".equalsIgnoreCase(sqlDataAccess) || "NO SQL".equalsIgnoreCase(sqlDataAccess)) {
						synchronized (this.paramInfo) {
							this.paramInfo.isReadOnlySafeChecked = true;
							this.paramInfo.isReadOnlySafeProcedure = true;
						}
						return true;
					}
				}
			} catch (SQLException e) {
				// swallow the Exception
			} finally {
				if (rs != null) {
					rs.close();
				}
				if (ps != null) {
					ps.close();
				}

			}
			this.paramInfo.isReadOnlySafeChecked = false;
			this.paramInfo.isReadOnlySafeProcedure = false;
		}
		return false;

	}

	@Override
	protected boolean checkReadOnlySafeStatement() throws SQLException {
		return (super.checkReadOnlySafeStatement() || this.checkReadOnlyProcedure());
	}

	@Override
	public RowId getRowId(int parameterIndex) throws SQLException {
		ResultSetInternalMethods rs = getOutputParameters(parameterIndex);

		RowId retValue = rs.getRowId(mapOutputParameterIndexToRsIndex(parameterIndex));

		this.outputParamWasNull = rs.wasNull();

		return retValue;
	}

	@Override
	public RowId getRowId(String parameterName) throws SQLException {
		ResultSetInternalMethods rs = getOutputParameters(0); // definitely not going to be from ?=

		RowId retValue = rs.getRowId(fixParameterName(parameterName));

		this.outputParamWasNull = rs.wasNull();

		return retValue;
	}

	@Override
	public void setRowId(String parameterName, RowId x) throws SQLException {
		setRowId(getNamedParamIndex(parameterName, false), x);
	}

	@Override
	public void setNString(String parameterName, String value) throws SQLException {
		setNString(getNamedParamIndex(parameterName, false), value);
	}

	@Override
	public void setNClob(String parameterName, NClob value) throws SQLException {
		setNClob(getNamedParamIndex(parameterName, false), value);

	}

	@Override
	public void setNClob(String parameterName, Reader reader) throws SQLException {
		setNClob(getNamedParamIndex(parameterName, false), reader);

	}

	@Override
	public void setNClob(String parameterName, Reader reader, long length) throws SQLException {
		setNClob(getNamedParamIndex(parameterName, false), reader, length);

	}

	@Override
	public void setSQLXML(String parameterName, SQLXML xmlObject) throws SQLException {
		setSQLXML(getNamedParamIndex(parameterName, false), xmlObject);

	}

	@Override
	public SQLXML getSQLXML(int parameterIndex) throws SQLException {
		ResultSetInternalMethods rs = getOutputParameters(parameterIndex);

		SQLXML retValue = rs.getSQLXML(mapOutputParameterIndexToRsIndex(parameterIndex));

		this.outputParamWasNull = rs.wasNull();

		return retValue;

	}

	@Override
	public SQLXML getSQLXML(String parameterName) throws SQLException {
		ResultSetInternalMethods rs = getOutputParameters(0); // definitely not going to be from ?=

		SQLXML retValue = rs.getSQLXML(fixParameterName(parameterName));

		this.outputParamWasNull = rs.wasNull();

		return retValue;
	}

	@Override
	public String getNString(int parameterIndex) throws SQLException {
		ResultSetInternalMethods rs = getOutputParameters(parameterIndex);

		String retValue = rs.getNString(mapOutputParameterIndexToRsIndex(parameterIndex));

		this.outputParamWasNull = rs.wasNull();

		return retValue;
	}

	@Override
	public String getNString(String parameterName) throws SQLException {
		ResultSetInternalMethods rs = getOutputParameters(0); // definitely not going to be from ?=
		String retValue = rs.getNString(fixParameterName(parameterName));

		this.outputParamWasNull = rs.wasNull();

		return retValue;
	}

	@Override
	public Reader getNCharacterStream(int parameterIndex) throws SQLException {
		ResultSetInternalMethods rs = getOutputParameters(parameterIndex);

		Reader retValue = rs.getNCharacterStream(mapOutputParameterIndexToRsIndex(parameterIndex));

		this.outputParamWasNull = rs.wasNull();

		return retValue;
	}

	@Override
	public Reader getNCharacterStream(String parameterName) throws SQLException {
		ResultSetInternalMethods rs = getOutputParameters(0); // definitely not going to be from ?=
		Reader retValue = rs.getNCharacterStream(fixParameterName(parameterName));

		this.outputParamWasNull = rs.wasNull();

		return retValue;
	}

	@Override
	public Reader getCharacterStream(int parameterIndex) throws SQLException {
		ResultSetInternalMethods rs = getOutputParameters(parameterIndex);

		Reader retValue = rs.getCharacterStream(mapOutputParameterIndexToRsIndex(parameterIndex));

		this.outputParamWasNull = rs.wasNull();

		return retValue;
	}

	@Override
	public Reader getCharacterStream(String parameterName) throws SQLException {
		ResultSetInternalMethods rs = getOutputParameters(0); // definitely not going to be from ?=

		Reader retValue = rs.getCharacterStream(fixParameterName(parameterName));

		this.outputParamWasNull = rs.wasNull();

		return retValue;
	}

	@Override
	public NClob getNClob(int parameterIndex) throws SQLException {
		ResultSetInternalMethods rs = getOutputParameters(parameterIndex);

		NClob retValue = rs.getNClob(mapOutputParameterIndexToRsIndex(parameterIndex));

		this.outputParamWasNull = rs.wasNull();

		return retValue;
	}

	@Override
	public NClob getNClob(String parameterName) throws SQLException {
		ResultSetInternalMethods rs = getOutputParameters(0); // definitely not going to be from ?=

		NClob retValue = rs.getNClob(fixParameterName(parameterName));

		this.outputParamWasNull = rs.wasNull();

		return retValue;
	}

	/**
	 * Converts the given string to bytes, using the connection's character encoding.
	 *
	 * @param  s string
	 * @return   bytes
	 */
	protected byte[] s2b(String s) {
		return s == null ? null : StringUtils.getBytes(s, this.charEncoding);
	}

	@Override
	public long executeLargeUpdate() throws SQLException {
		synchronized (checkClosed().getConnectionMutex()) {
			long returnVal = -1;

			checkStreamability();

			if (this.callingStoredFunction) {
				execute();

				return -1;
			}

			setInOutParamsOnServer();
			setOutParams();

			returnVal = super.executeLargeUpdate();

			retrieveOutParams();

			return returnVal;
		}
	}

	@Override
	public long[] executeLargeBatch() throws SQLException {
		if (this.hasOutputParams) {
			throw SQLError.createSQLException("Can't call executeBatch() on CallableStatement with OUTPUT parameters",
					MysqlErrorNumbers.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
		}

		return super.executeLargeBatch();
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy