com.mysql.cj.AbstractQueryBindings Maven / Gradle / Ivy
/*
* Copyright (c) 2017, 2020, 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;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.text.ParsePosition;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Calendar;
import java.util.Locale;
import com.mysql.cj.conf.PropertyKey;
import com.mysql.cj.conf.RuntimeProperty;
import com.mysql.cj.exceptions.ExceptionFactory;
import com.mysql.cj.exceptions.WrongArgumentException;
import com.mysql.cj.protocol.ColumnDefinition;
import com.mysql.cj.protocol.a.NativeConstants.IntegerDataType;
import com.mysql.cj.protocol.a.NativePacketPayload;
import com.mysql.cj.util.StringUtils;
import com.mysql.cj.util.TimeUtil;
import com.mysql.cj.util.Util;
//TODO should not be protocol-specific
public abstract class AbstractQueryBindings implements QueryBindings {
protected final static byte[] HEX_DIGITS = new byte[] { (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7',
(byte) '8', (byte) '9', (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F' };
protected Session session;
/** Bind values for individual fields */
protected T[] bindValues;
protected String charEncoding;
protected int numberOfExecutions = 0;
protected RuntimeProperty useStreamLengthsInPrepStmts;
protected RuntimeProperty sendFractionalSeconds;
private RuntimeProperty treatUtilDateAsTimestamp;
/** Is this query a LOAD DATA query? */
protected boolean isLoadDataQuery = false;
protected ColumnDefinition columnDefinition;
public AbstractQueryBindings(int parameterCount, Session sess) {
this.session = sess;
this.charEncoding = this.session.getPropertySet().getStringProperty(PropertyKey.characterEncoding).getValue();
this.sendFractionalSeconds = this.session.getPropertySet().getBooleanProperty(PropertyKey.sendFractionalSeconds);
this.treatUtilDateAsTimestamp = this.session.getPropertySet().getBooleanProperty(PropertyKey.treatUtilDateAsTimestamp);
this.useStreamLengthsInPrepStmts = this.session.getPropertySet().getBooleanProperty(PropertyKey.useStreamLengthsInPrepStmts);
initBindValues(parameterCount);
}
protected abstract void initBindValues(int parameterCount);
@Override
public abstract AbstractQueryBindings clone();
@Override
public void setColumnDefinition(ColumnDefinition colDef) {
this.columnDefinition = colDef;
}
@Override
public boolean isLoadDataQuery() {
return this.isLoadDataQuery;
}
@Override
public void setLoadDataQuery(boolean isLoadDataQuery) {
this.isLoadDataQuery = isLoadDataQuery;
}
@Override
public T[] getBindValues() {
return this.bindValues;
}
@Override
public void setBindValues(T[] bindValues) {
this.bindValues = bindValues;
}
@Override
public boolean clearBindValues() {
boolean hadLongData = false;
if (this.bindValues != null) {
for (int i = 0; i < this.bindValues.length; i++) {
if ((this.bindValues[i] != null) && this.bindValues[i].isStream()) {
hadLongData = true;
}
this.bindValues[i].reset();
}
}
return hadLongData;
}
public abstract void checkParameterSet(int columnIndex);
public void checkAllParametersSet() {
for (int i = 0; i < this.bindValues.length; i++) {
checkParameterSet(i);
}
}
public int getNumberOfExecutions() {
return this.numberOfExecutions;
}
public void setNumberOfExecutions(int numberOfExecutions) {
this.numberOfExecutions = numberOfExecutions;
}
public synchronized final void setValue(int paramIndex, byte[] val, MysqlType type) {
this.bindValues[paramIndex].setByteValue(val);
this.bindValues[paramIndex].setMysqlType(type);
}
public synchronized final void setOrigValue(int paramIndex, byte[] val) {
this.bindValues[paramIndex].setOrigByteValue(val);
}
@Override
public synchronized byte[] getOrigBytes(int parameterIndex) {
return this.bindValues[parameterIndex].getOrigByteValue();
}
public synchronized final void setValue(int paramIndex, String val, MysqlType type) {
byte[] parameterAsBytes = StringUtils.getBytes(val, this.charEncoding);
setValue(paramIndex, parameterAsBytes, type);
}
/**
* Used to escape binary data with hex for mb charsets
*
* @param buf
* source bytes
* @param packet
* write to this packet
* @param size
* number of bytes to read
*/
public final void hexEscapeBlock(byte[] buf, NativePacketPayload packet, int size) {
for (int i = 0; i < size; i++) {
byte b = buf[i];
int lowBits = (b & 0xff) / 16;
int highBits = (b & 0xff) % 16;
packet.writeInteger(IntegerDataType.INT1, HEX_DIGITS[lowBits]);
packet.writeInteger(IntegerDataType.INT1, HEX_DIGITS[highBits]);
}
}
@Override
public void setObject(int parameterIndex, Object parameterObj) {
if (parameterObj == null) {
setNull(parameterIndex);
} else {
if (parameterObj instanceof Byte) {
setInt(parameterIndex, ((Byte) parameterObj).intValue());
} else if (parameterObj instanceof String) {
setString(parameterIndex, (String) parameterObj);
} else if (parameterObj instanceof BigDecimal) {
setBigDecimal(parameterIndex, (BigDecimal) parameterObj);
} else if (parameterObj instanceof Short) {
setShort(parameterIndex, ((Short) parameterObj).shortValue());
} else if (parameterObj instanceof Integer) {
setInt(parameterIndex, ((Integer) parameterObj).intValue());
} else if (parameterObj instanceof Long) {
setLong(parameterIndex, ((Long) parameterObj).longValue());
} else if (parameterObj instanceof Float) {
setFloat(parameterIndex, ((Float) parameterObj).floatValue());
} else if (parameterObj instanceof Double) {
setDouble(parameterIndex, ((Double) parameterObj).doubleValue());
} else if (parameterObj instanceof byte[]) {
setBytes(parameterIndex, (byte[]) parameterObj);
} else if (parameterObj instanceof java.sql.Date) {
setDate(parameterIndex, (java.sql.Date) parameterObj);
} else if (parameterObj instanceof Time) {
setTime(parameterIndex, (Time) parameterObj);
} else if (parameterObj instanceof Timestamp) {
setTimestamp(parameterIndex, (Timestamp) parameterObj);
} else if (parameterObj instanceof Boolean) {
setBoolean(parameterIndex, ((Boolean) parameterObj).booleanValue());
} else if (parameterObj instanceof InputStream) {
setBinaryStream(parameterIndex, (InputStream) parameterObj, -1);
} else if (parameterObj instanceof java.sql.Blob) {
setBlob(parameterIndex, (java.sql.Blob) parameterObj);
} else if (parameterObj instanceof java.sql.Clob) {
setClob(parameterIndex, (java.sql.Clob) parameterObj);
} else if (this.treatUtilDateAsTimestamp.getValue() && parameterObj instanceof java.util.Date) {
setTimestamp(parameterIndex, new Timestamp(((java.util.Date) parameterObj).getTime()));
} else if (parameterObj instanceof BigInteger) {
setString(parameterIndex, parameterObj.toString());
} else if (parameterObj instanceof LocalDate) {
setDate(parameterIndex, Date.valueOf((LocalDate) parameterObj));
} else if (parameterObj instanceof LocalDateTime) {
setTimestamp(parameterIndex, Timestamp.valueOf((LocalDateTime) parameterObj));
} else if (parameterObj instanceof LocalTime) {
setTime(parameterIndex, Time.valueOf((LocalTime) parameterObj));
} else {
setSerializableObject(parameterIndex, parameterObj);
}
}
}
@Override
public void setObject(int parameterIndex, Object parameterObj, MysqlType targetMysqlType) {
setObject(parameterIndex, parameterObj, targetMysqlType, parameterObj instanceof BigDecimal ? ((BigDecimal) parameterObj).scale() : 0);
}
/**
* Set the value of a parameter using an object; use the java.lang equivalent objects for integral values.
*
*
* The given Java object will be converted to the targetMysqlType before being sent to the database.
*
* @param parameterIndex
* the first parameter is 1...
* @param parameterObj
* the object containing the input parameter value
* @param targetMysqlType
* The MysqlType to be send to the database
* @param scaleOrLength
* For Types.DECIMAL or Types.NUMERIC types
* this is the number of digits after the decimal. For all other
* types this value will be ignored.
*/
public void setObject(int parameterIndex, Object parameterObj, MysqlType targetMysqlType, int scaleOrLength) {
if (parameterObj == null) {
setNull(parameterIndex);
} else {
if (parameterObj instanceof LocalDate) {
parameterObj = Date.valueOf((LocalDate) parameterObj);
} else if (parameterObj instanceof LocalDateTime) {
parameterObj = Timestamp.valueOf((LocalDateTime) parameterObj);
} else if (parameterObj instanceof LocalTime) {
parameterObj = Time.valueOf((LocalTime) parameterObj);
}
try {
/*
* From Table-B5 in the JDBC Spec
*/
switch (targetMysqlType) {
case BOOLEAN:
if (parameterObj instanceof Boolean) {
setBoolean(parameterIndex, ((Boolean) parameterObj).booleanValue());
break;
} else if (parameterObj instanceof String) {
if ("true".equalsIgnoreCase((String) parameterObj) || "Y".equalsIgnoreCase((String) parameterObj)) {
setBoolean(parameterIndex, true);
} else if ("false".equalsIgnoreCase((String) parameterObj) || "N".equalsIgnoreCase((String) parameterObj)) {
setBoolean(parameterIndex, false);
} else if (((String) parameterObj).matches("-?\\d+\\.?\\d*")) {
setBoolean(parameterIndex, !((String) parameterObj).matches("-?[0]+[.]*[0]*"));
} else {
throw ExceptionFactory.createException(WrongArgumentException.class,
Messages.getString("PreparedStatement.66", new Object[] { parameterObj }), this.session.getExceptionInterceptor());
}
break;
} else if (parameterObj instanceof Number) {
int intValue = ((Number) parameterObj).intValue();
setBoolean(parameterIndex, intValue != 0);
break;
} else {
throw ExceptionFactory.createException(WrongArgumentException.class,
Messages.getString("PreparedStatement.66", new Object[] { parameterObj.getClass().getName() }),
this.session.getExceptionInterceptor());
}
case BIT:
case TINYINT:
case TINYINT_UNSIGNED:
case SMALLINT:
case SMALLINT_UNSIGNED:
case INT:
case INT_UNSIGNED:
case MEDIUMINT:
case MEDIUMINT_UNSIGNED:
case BIGINT:
case BIGINT_UNSIGNED:
case FLOAT:
case FLOAT_UNSIGNED:
case DOUBLE:
case DOUBLE_UNSIGNED:
case DECIMAL:
case DECIMAL_UNSIGNED:
setNumericObject(parameterIndex, parameterObj, targetMysqlType, scaleOrLength);
break;
case CHAR:
case ENUM:
case SET:
case VARCHAR:
case TINYTEXT:
case TEXT:
case MEDIUMTEXT:
case LONGTEXT:
case JSON:
if (parameterObj instanceof BigDecimal) {
setString(parameterIndex, (StringUtils.fixDecimalExponent(((BigDecimal) parameterObj).toPlainString())));
} else if (parameterObj instanceof java.sql.Clob) {
setClob(parameterIndex, (java.sql.Clob) parameterObj);
} else {
setString(parameterIndex, parameterObj.toString());
}
break;
case BINARY:
case GEOMETRY:
case VARBINARY:
case TINYBLOB:
case BLOB:
case MEDIUMBLOB:
case LONGBLOB:
if (parameterObj instanceof byte[]) {
setBytes(parameterIndex, (byte[]) parameterObj);
} else if (parameterObj instanceof java.sql.Blob) {
setBlob(parameterIndex, (java.sql.Blob) parameterObj);
} else {
setBytes(parameterIndex, StringUtils.getBytes(parameterObj.toString(), this.charEncoding));
}
break;
case DATE:
case DATETIME:
case TIMESTAMP:
case YEAR:
java.util.Date parameterAsDate;
if (parameterObj instanceof String) {
ParsePosition pp = new ParsePosition(0);
java.text.DateFormat sdf = new java.text.SimpleDateFormat(TimeUtil.getDateTimePattern((String) parameterObj, false), Locale.US);
parameterAsDate = sdf.parse((String) parameterObj, pp);
} else {
parameterAsDate = (java.util.Date) parameterObj;
}
switch (targetMysqlType) {
case DATE:
if (parameterAsDate instanceof java.sql.Date) {
setDate(parameterIndex, (java.sql.Date) parameterAsDate);
} else {
setDate(parameterIndex, new java.sql.Date(parameterAsDate.getTime()));
}
break;
case DATETIME:
case TIMESTAMP:
if (parameterAsDate instanceof java.sql.Timestamp) {
setTimestamp(parameterIndex, (java.sql.Timestamp) parameterAsDate);
} else {
setTimestamp(parameterIndex, new java.sql.Timestamp(parameterAsDate.getTime()));
}
break;
case YEAR:
Calendar cal = Calendar.getInstance();
cal.setTime(parameterAsDate);
setNumericObject(parameterIndex, cal.get(Calendar.YEAR), targetMysqlType, scaleOrLength);
break;
default:
break;
}
break;
case TIME:
if (parameterObj instanceof String) {
java.text.DateFormat sdf = new java.text.SimpleDateFormat(TimeUtil.getDateTimePattern((String) parameterObj, true), Locale.US);
setTime(parameterIndex, new java.sql.Time(sdf.parse((String) parameterObj).getTime()));
} else if (parameterObj instanceof Timestamp) {
Timestamp xT = (Timestamp) parameterObj;
setTime(parameterIndex, new java.sql.Time(xT.getTime()));
} else {
setTime(parameterIndex, (java.sql.Time) parameterObj);
}
break;
case UNKNOWN:
setSerializableObject(parameterIndex, parameterObj);
break;
default:
throw ExceptionFactory.createException(Messages.getString("PreparedStatement.16"), this.session.getExceptionInterceptor());
}
} catch (Exception ex) {
throw ExceptionFactory.createException(
Messages.getString("PreparedStatement.17") + parameterObj.getClass().toString() + Messages.getString("PreparedStatement.18")
+ ex.getClass().getName() + Messages.getString("PreparedStatement.19") + ex.getMessage(),
ex, this.session.getExceptionInterceptor());
}
}
}
private void setNumericObject(int parameterIndex, Object parameterObj, MysqlType targetMysqlType, int scale) {
Number parameterAsNum;
if (parameterObj instanceof Boolean) {
parameterAsNum = ((Boolean) parameterObj).booleanValue() ? Integer.valueOf(1) : Integer.valueOf(0);
} else if (parameterObj instanceof String) {
switch (targetMysqlType) {
case BIT:
if ("1".equals(parameterObj) || "0".equals(parameterObj)) {
parameterAsNum = Integer.valueOf((String) parameterObj);
} else {
boolean parameterAsBoolean = "true".equalsIgnoreCase((String) parameterObj);
parameterAsNum = parameterAsBoolean ? Integer.valueOf(1) : Integer.valueOf(0);
}
break;
case TINYINT:
case TINYINT_UNSIGNED:
case SMALLINT:
case SMALLINT_UNSIGNED:
case MEDIUMINT:
case MEDIUMINT_UNSIGNED:
case INT:
case INT_UNSIGNED:
case YEAR:
parameterAsNum = Integer.valueOf((String) parameterObj);
break;
case BIGINT:
parameterAsNum = Long.valueOf((String) parameterObj);
break;
case BIGINT_UNSIGNED:
parameterAsNum = new BigInteger((String) parameterObj);
break;
case FLOAT:
case FLOAT_UNSIGNED:
parameterAsNum = Float.valueOf((String) parameterObj);
break;
case DOUBLE:
case DOUBLE_UNSIGNED:
parameterAsNum = Double.valueOf((String) parameterObj);
break;
case DECIMAL:
case DECIMAL_UNSIGNED:
default:
parameterAsNum = new java.math.BigDecimal((String) parameterObj);
}
} else {
parameterAsNum = (Number) parameterObj;
}
switch (targetMysqlType) {
case BIT:
case TINYINT:
case TINYINT_UNSIGNED:
case SMALLINT:
case SMALLINT_UNSIGNED:
case MEDIUMINT:
case MEDIUMINT_UNSIGNED:
case INT:
case INT_UNSIGNED:
case YEAR:
setInt(parameterIndex, parameterAsNum.intValue());
break;
case BIGINT:
case BIGINT_UNSIGNED:
setLong(parameterIndex, parameterAsNum.longValue());
break;
case FLOAT:
case FLOAT_UNSIGNED:
setFloat(parameterIndex, parameterAsNum.floatValue());
break;
case DOUBLE:
case DOUBLE_UNSIGNED:
setDouble(parameterIndex, parameterAsNum.doubleValue());
break;
case DECIMAL:
case DECIMAL_UNSIGNED:
if (parameterAsNum instanceof java.math.BigDecimal) {
BigDecimal scaledBigDecimal = null;
try {
scaledBigDecimal = ((java.math.BigDecimal) parameterAsNum).setScale(scale);
} catch (ArithmeticException ex) {
try {
scaledBigDecimal = ((java.math.BigDecimal) parameterAsNum).setScale(scale, BigDecimal.ROUND_HALF_UP);
} catch (ArithmeticException arEx) {
throw ExceptionFactory.createException(WrongArgumentException.class,
Messages.getString("PreparedStatement.65", new Object[] { scale, parameterAsNum }), this.session.getExceptionInterceptor());
}
}
setBigDecimal(parameterIndex, scaledBigDecimal);
} else if (parameterAsNum instanceof java.math.BigInteger) {
setBigDecimal(parameterIndex, new java.math.BigDecimal((java.math.BigInteger) parameterAsNum, scale));
} else {
setBigDecimal(parameterIndex, new java.math.BigDecimal(parameterAsNum.doubleValue()));
}
break;
default:
break;
}
}
/**
* Sets the value for the placeholder as a serialized Java object (used by various forms of setObject()
*
* @param parameterIndex
* parameter index
* @param parameterObj
* value
*/
protected final void setSerializableObject(int parameterIndex, Object parameterObj) {
try {
ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
ObjectOutputStream objectOut = new ObjectOutputStream(bytesOut);
objectOut.writeObject(parameterObj);
objectOut.flush();
objectOut.close();
bytesOut.flush();
bytesOut.close();
byte[] buf = bytesOut.toByteArray();
ByteArrayInputStream bytesIn = new ByteArrayInputStream(buf);
setBinaryStream(parameterIndex, bytesIn, buf.length);
this.bindValues[parameterIndex].setMysqlType(MysqlType.BINARY);
} catch (Exception ex) {
throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("PreparedStatement.54") + ex.getClass().getName(), ex,
this.session.getExceptionInterceptor());
}
}
@Override
public boolean isNull(int parameterIndex) {
return this.bindValues[parameterIndex].isNull();
}
/**
* @return bytes
*/
public byte[] getBytesRepresentation(int parameterIndex) {
if (this.bindValues[parameterIndex].isStream()) {
return streamToBytes(parameterIndex, this.session.getPropertySet().getBooleanProperty(PropertyKey.useStreamLengthsInPrepStmts).getValue());
}
byte[] parameterVal = this.bindValues[parameterIndex].getByteValue();
if (parameterVal == null) {
return null;
}
return StringUtils.unquoteBytes(parameterVal);
}
private byte[] streamConvertBuf = null;
private final byte[] streamToBytes(int parameterIndex, boolean useLength) {
InputStream in = this.bindValues[parameterIndex].getStreamValue();
in.mark(Integer.MAX_VALUE); // we may need to read this same stream several times, so we need to reset it at the end.
try {
if (this.streamConvertBuf == null) {
this.streamConvertBuf = new byte[4096];
}
if (this.bindValues[parameterIndex].getStreamLength() == -1) {
useLength = false;
}
ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
int bc = useLength ? Util.readBlock(in, this.streamConvertBuf, (int) this.bindValues[parameterIndex].getStreamLength(), null)
: Util.readBlock(in, this.streamConvertBuf, null);
int lengthLeftToRead = (int) this.bindValues[parameterIndex].getStreamLength() - bc;
while (bc > 0) {
bytesOut.write(this.streamConvertBuf, 0, bc);
if (useLength) {
bc = Util.readBlock(in, this.streamConvertBuf, lengthLeftToRead, null);
if (bc > 0) {
lengthLeftToRead -= bc;
}
} else {
bc = Util.readBlock(in, this.streamConvertBuf, null);
}
}
return bytesOut.toByteArray();
} finally {
try {
in.reset();
} catch (IOException e) {
}
if (this.session.getPropertySet().getBooleanProperty(PropertyKey.autoClosePStmtStreams).getValue()) {
try {
in.close();
} catch (IOException ioEx) {
}
in = null;
}
}
}
}