package io.ebeaninternal.server.persist;

import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.ScalarType;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.SpiLogManager;
import io.ebeaninternal.server.core.timezone.DataTimeZone;
import io.ebeaninternal.server.expression.platform.DbExpressionHandler;
import io.ebeaninternal.server.persist.platform.MultiValueBind;
import io.ebeaninternal.server.type.DataBind;
import io.ebeaninternal.server.type.GeoTypeBinder;
import io.ebeaninternal.server.type.PostgresHelper;
import io.ebeaninternal.server.type.RsetDataReader;
import io.ebeaninternal.server.type.TypeManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.persistence.PersistenceException;
import java.math.BigDecimal;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

Binds bean values to a PreparedStatement.
/** * Binds bean values to a PreparedStatement. */
public class Binder { private static final Logger logger = LoggerFactory.getLogger(Binder.class); private final TypeManager typeManager; private final int asOfBindCount; private final boolean asOfStandardsBased; private final DbExpressionHandler dbExpressionHandler; private final DataTimeZone dataTimeZone; private final MultiValueBind multiValueBind; private final boolean enableBindLog; private final GeoTypeBinder geoTypeBinder; public Binder(TypeManager typeManager, SpiLogManager logManager, int asOfBindCount, boolean asOfStandardsBased, DbExpressionHandler dbExpressionHandler, DataTimeZone dataTimeZone, MultiValueBind multiValueBind) { this.typeManager = typeManager; this.geoTypeBinder = typeManager.getGeoTypeBinder(); this.asOfBindCount = asOfBindCount; this.asOfStandardsBased = asOfStandardsBased; this.dbExpressionHandler = dbExpressionHandler; this.dataTimeZone = dataTimeZone; this.multiValueBind = multiValueBind; this.enableBindLog = logManager.sql().isDebug(); }
Return true if bind log is enabled.
/** * Return true if bind log is enabled. */
public boolean isEnableBindLog() { return enableBindLog; }
Return the bind count per predicate for 'As Of' query predicates.
/** * Return the bind count per predicate for 'As Of' query predicates. */
public int getAsOfBindCount() { return asOfBindCount; }
Return true if the 'as of' history support is SQL2011 standards based.
/** * Return true if the 'as of' history support is SQL2011 standards based. */
public boolean isAsOfStandardsBased() { return asOfStandardsBased; }
Bind the values to the Prepared Statement.
/** * Bind the values to the Prepared Statement. */
public void bind(BindValues bindValues, DataBind dataBind, StringBuilder bindBuf) throws SQLException { String logPrefix = ""; ArrayList<BindValues.Value> list = bindValues.values(); for (BindValues.Value bindValue : list) { Object val = bindValue.getValue(); int dt = bindValue.getDbType(); bindObject(dataBind, val, dt); if (bindBuf != null) { bindBuf.append(logPrefix); if (logPrefix.isEmpty()) { logPrefix = ", "; } bindBuf.append(bindValue.getName()); bindBuf.append("="); if (isLob(dt)) { bindBuf.append("[LOB]"); } else { bindBuf.append(val); } } } }
Bind the parameters to the preparedStatement returning the bind log.
/** * Bind the parameters to the preparedStatement returning the bind log. */
public String bind(BindParams bindParams, PreparedStatement statement, Connection connection) throws SQLException { return bind(bindParams, new DataBind(dataTimeZone, statement, connection)); }
Bind the list of positionedParameters in BindParams.
/** * Bind the list of positionedParameters in BindParams. */
private String bind(BindParams bindParams, DataBind dataBind) throws SQLException { StringBuilder bindLog = new StringBuilder(); bind(bindParams, dataBind, bindLog); return bindLog.toString(); }
Bind the list of positionedParameters in BindParams.
/** * Bind the list of positionedParameters in BindParams. */
public void bind(BindParams bindParams, DataBind dataBind, StringBuilder bindLog) throws SQLException { bind(bindParams.positionedParameters(), dataBind, bindLog); }
Bind the list of parameters..
/** * Bind the list of parameters.. */
private void bind(List<BindParams.Param> list, DataBind dataBind, StringBuilder bindLog) throws SQLException { CallableStatement cstmt = null; if (dataBind.getPstmt() instanceof CallableStatement) { cstmt = (CallableStatement) dataBind.getPstmt(); } // the iterator is assumed to be in the correct order Object value = null; try { for (BindParams.Param param : list) { if (param.isOutParam() && cstmt != null) { cstmt.registerOutParameter(dataBind.nextPos(), param.getType()); if (param.isInParam()) { dataBind.decrementPos(); } } if (param.isInParam()) { value = param.getInValue(); if (bindLog != null) { if (bindLog.length() > 0) { bindLog.append(", "); } if (param.isEncryptionKey()) { bindLog.append("****"); } else { bindLog.append(value); } } if (value == null) { // this doesn't work for query predicates bindObject(dataBind, null, param.getType()); } else { bindObject(dataBind, value); } } } } catch (SQLException ex) { logger.warn("error binding parameter [{}][{}]", (dataBind.currentPos() - 1), value); throw ex; } }
Return true if MultiValue binding is supported for the given type.
/** * Return true if MultiValue binding is supported for the given type. */
public boolean isMultiValueSupported(Class<?> cls) { try { ScalarType<?> scalarType = getScalarType(cls); return multiValueBind.isTypeSupported(scalarType.getJdbcType()); } catch (PersistenceException e) { return false; } } public ScalarType<?> getScalarType(Class<?> clazz) { ScalarType<?> type = typeManager.getScalarType(clazz); if (type == null) { throw new PersistenceException("No ScalarType registered for " + clazz); } return type; }
Bind an Object with unknown data type.
/** * Bind an Object with unknown data type. */
public Object bindObject(DataBind dataBind, Object value) throws SQLException { if (value == null) { // null of unknown type bindObject(dataBind, null, Types.OTHER); return null; } else if (value instanceof MultiValueWrapper) { MultiValueWrapper wrapper = (MultiValueWrapper) value; Collection<?> values = wrapper.getValues(); ScalarType<?> type = getScalarType(wrapper.getType()); int dbType = type.getJdbcType(); // let the multiValueBind decide what to do with the value multiValueBind.bindMultiValues(dataBind, values, type, one -> bindObject(dataBind, one, dbType)); return values; } else { ScalarType<?> type = getScalarType(value.getClass()); if (!type.isJdbcNative()) { // convert to a JDBC native type value = type.toJdbcType(value); } bindObject(dataBind, value, type.getJdbcType()); return value; } }
Return the SQL in clause taking into account Multi-value support.
/** * Return the SQL in clause taking into account Multi-value support. */
public String getInExpression(boolean not, List<Object> bindValues) { ScalarType<?> type = getScalarType(bindValues.get(0).getClass()); return multiValueBind.getInExpression(not, type, bindValues.size()); }
bind a single value.

Note that java.math.BigInteger is supported by converting it to a Long.

Note if we get a java.util.Date or java.util.Calendar then these have been anonymously passed in (UpdateSql etc). There is a global setting to convert then to a java.sql.Date or java.sql.Timestamp for binding. The default is that both are converted to java.sql.Timestamp.

/** * bind a single value. * <p> * Note that java.math.BigInteger is supported by converting it to a Long. * </p> * <p> * Note if we get a java.util.Date or java.util.Calendar then these have * been anonymously passed in (UpdateSql etc). There is a global setting to * convert then to a java.sql.Date or java.sql.Timestamp for binding. The * default is that both are converted to java.sql.Timestamp. * </p> */
private void bindObject(DataBind dataBind, Object data, int dbType) throws SQLException { if (data == null) { dataBind.setNull(dbType); return; } switch (dbType) { case java.sql.Types.LONGVARCHAR: bindLongVarChar(dataBind, data); break; case java.sql.Types.LONGVARBINARY: bindLongVarBinary(dataBind, data); break; case java.sql.Types.CLOB: bindClob(dataBind, data); break; case java.sql.Types.BLOB: bindBlob(dataBind, data); break; default: bindSimpleData(dataBind, dbType, data); } }
Binds the value to the statement according to the data type.
/** * Binds the value to the statement according to the data type. */
private void bindSimpleData(DataBind b, int dataType, Object data) { try { switch (dataType) { case java.sql.Types.BOOLEAN: case java.sql.Types.BIT: // Types.BIT should map to Java Boolean b.setBoolean((Boolean) data); break; case java.sql.Types.VARCHAR: b.setString((String) data); break; case java.sql.Types.CHAR: b.setString(data.toString()); break; case java.sql.Types.TINYINT: b.setByte((Byte) data); break; case java.sql.Types.SMALLINT: b.setShort((Short) data); break; case java.sql.Types.INTEGER: b.setInt((Integer) data); break; case java.sql.Types.BIGINT: b.setLong((Long) data); break; case java.sql.Types.REAL: b.setFloat((Float) data); break; case java.sql.Types.FLOAT: case java.sql.Types.DOUBLE: // DB Float in theory maps to Java Double type b.setDouble((Double) data); break; case java.sql.Types.NUMERIC: case java.sql.Types.DECIMAL: b.setBigDecimal((BigDecimal) data); break; case java.sql.Types.TIME: b.setTime((java.sql.Time) data); break; case java.sql.Types.DATE: b.setDate((java.sql.Date) data); break; case java.sql.Types.TIMESTAMP: b.setTimestamp((java.sql.Timestamp) data); break; case java.sql.Types.BINARY: case java.sql.Types.VARBINARY: b.setBytes((byte[]) data); break; case DbPlatformType.UUID: case java.sql.Types.JAVA_OBJECT: // Not too sure about this. // native UUID support in H2 and Postgres b.setObject(data); break; case DbPlatformType.INET: // data is always a String at this point b.setObject(PostgresHelper.asInet(data.toString())); break; case DbPlatformType.POINT: case DbPlatformType.POLYGON: case DbPlatformType.MULTIPOINT: case DbPlatformType.LINESTRING: case DbPlatformType.MULTILINESTRING: case DbPlatformType.MULTIPOLYGON: geoTypeBinder.bind(b, dataType, data); break; case java.sql.Types.OTHER: b.setObject(data, dataType); break; default: throw new SQLException("Unhandled data type:" + dataType + " bind number:" + b.currentPos()); } } catch (Exception e) { String dataClass = "Data is null?"; if (data != null) { dataClass = data.getClass().getName(); } String m = "Error with property[" + b.currentPos() + "] dt[" + dataType + "]"; m += "data[" + data + "][" + dataClass + "]"; throw new PersistenceException(m, e); } }
Bind String data to a LONGVARCHAR column.
/** * Bind String data to a LONGVARCHAR column. */
private void bindLongVarChar(DataBind dataBind, Object data) throws SQLException { dataBind.setClob((String) data); }
Bind byte[] data to a LONGVARBINARY column.
/** * Bind byte[] data to a LONGVARBINARY column. */
private void bindLongVarBinary(DataBind dataBind, Object data) throws SQLException { dataBind.setBlob((byte[]) data); }
Bind String data to a CLOB column.
/** * Bind String data to a CLOB column. */
private void bindClob(DataBind dataBind, Object data) throws SQLException { dataBind.setClob((String) data); }
Bind byte[] data to a BLOB column.
/** * Bind byte[] data to a BLOB column. */
private void bindBlob(DataBind dataBind, Object data) throws SQLException { dataBind.setBlob((byte[]) data); } private boolean isLob(int dbType) { switch (dbType) { case Types.CLOB: case Types.LONGVARBINARY: case Types.BLOB: case Types.LONGVARCHAR: return true; default: return false; } }
Return the DB platform specific expression handler (for JSON and ARRAY types).
/** * Return the DB platform specific expression handler (for JSON and ARRAY types). */
public DbExpressionHandler getDbExpressionHandler() { return dbExpressionHandler; }
Create and return a DataBind for the statement.
/** * Create and return a DataBind for the statement. */
public DataBind dataBind(PreparedStatement stmt, Connection connection) { return new DataBind(dataTimeZone, stmt, connection); } public DataReader createDataReader(ResultSet resultSet) { return new RsetDataReader(dataTimeZone, resultSet); } }