com.sap.cds.jdbc.generic.AbstractValueBinder Maven / Gradle / Ivy
The newest version!
/************************************************************************
* © 2021-2024 SAP SE or an SAP affiliate company. All rights reserved. *
************************************************************************/
package com.sap.cds.jdbc.generic;
import static com.sap.cds.util.CdsTypeUtils.dateTime;
import static com.sap.cds.util.CdsTypeUtils.timestamp;
import static com.sap.cds.util.CqnStatementUtils.isMediaType;
import java.io.InputStream;
import java.io.Reader;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TimeZone;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sap.cds.CdsDataStoreException;
import com.sap.cds.CdsVector;
import com.sap.cds.impl.parser.StructDataParser;
import com.sap.cds.impl.parser.token.Jsonizer;
import com.sap.cds.jdbc.spi.ValueBinder;
import com.sap.cds.ql.cqn.CqnElementRef;
import com.sap.cds.ql.cqn.CqnSelectListValue;
import com.sap.cds.reflect.CdsArrayedType;
import com.sap.cds.reflect.CdsBaseType;
import com.sap.cds.reflect.CdsStructuredType;
import com.sap.cds.reflect.CdsType;
import com.sap.cds.util.CdsModelUtils;
import com.sap.cds.util.CqnStatementUtils;
public abstract class AbstractValueBinder implements ValueBinder {
private static final Logger logger = LoggerFactory.getLogger(AbstractValueBinder.class);
protected AbstractValueBinder(int timestampFractionalSeconds, TimeZone timeZone) {
this.timestampFractionalSeconds = timestampFractionalSeconds;
this.localCalendar = ThreadLocal.withInitial(() -> Calendar.getInstance(timeZone));
}
protected static final ThreadLocal UTC = // NOSONAR
ThreadLocal.withInitial(() -> Calendar.getInstance(TimeZone.getTimeZone("UTC")));
private final int timestampFractionalSeconds;
private final ThreadLocal localCalendar;
@Override
@SuppressWarnings("unchecked")
public Getter getter(CdsBaseType cdsType, boolean isMediaType) {
if (cdsType != null) {
return (Getter) getValueFromResult(cdsType, isMediaType);
}
return (result, i) -> (T) result.getObject(i);
}
@Override
@SuppressWarnings("unchecked")
public Getter getter(CdsStructuredType targetType, CqnSelectListValue slv) {
if (slv.value().isRef()) {
CqnElementRef ref = slv.asRef();
CdsType type = CdsModelUtils.element(targetType, ref).getType();
if (type.isArrayed()) {
CdsType itemsType = type.as(CdsArrayedType.class).getItemsType();
return (result, i) -> (T) parseListFromJson(result, i, itemsType);
}
}
Optional cdsType = CqnStatementUtils.getCdsType(targetType, slv.value());
if (cdsType.isEmpty()) {
logger.debug("Cannot determine CDS type of {}", slv.value());
}
return getter(cdsType.orElse(null), isMediaType(targetType, slv));
}
@Override
@SuppressWarnings("unchecked")
public T getValue(ResultSet result, int i, CdsBaseType cdsType, boolean isMediaType) throws SQLException {
return (T) getter(cdsType, isMediaType).get(result, i);
}
@Override
public void setValue(PreparedStatement pstmt, int i, CdsBaseType cdsType, Object value) throws SQLException {
setter(cdsType).set(pstmt, i, value);
}
@Override
public Setter setter(CdsBaseType cdsType) {
if (cdsType == null) {
return this::setValue;
}
switch (cdsType) {
case DATE:
return this::setLocalDate;
case DATETIME:
return (pstmt, i, val) -> setInstant(pstmt, i, dateTime(val));
case TIME:
return this::setLocalTime;
case TIMESTAMP:
return (pstmt, i, val) -> setInstant(pstmt, i,
timestamp(val, timestampFractionalSeconds));
case LARGE_BINARY:
return this::setLargeBinary;
case HANA_CLOB:
case LARGE_STRING:
return this::setLargeString;
case VECTOR:
return this::setRealVector;
case MAP:
return this::setMap;
default:
return PreparedStatement::setObject;
}
}
protected void setValue(PreparedStatement pstmt, int i, Object value) throws SQLException {
if (value instanceof Instant instant) {
setInstant(pstmt, i, instant);
} else if (value instanceof LocalDate date) {
setLocalDate(pstmt, i, date);
} else if (value instanceof LocalTime time) {
setLocalTime(pstmt, i, time);
} else if (value instanceof ZonedDateTime time) {
setZonedDateTime(pstmt, i, time);
} else if (value instanceof Timestamp timestamp) {
setInstant(pstmt, i, timestamp.toInstant());
} else if (value instanceof java.sql.Time time) {
setLocalTime(pstmt, i, time.toLocalTime());
} else if (value instanceof java.sql.Date date) {
setLocalDate(pstmt, i, date.toLocalDate());
} else if (value instanceof byte[] bytes) {
pstmt.setBytes(i, bytes);
} else if (value instanceof Reader reader) {
setLargeString(pstmt, i, reader);
} else if (value instanceof InputStream stream) {
setLargeBinary(pstmt, i, stream);
} else {
pstmt.setObject(i, value);
}
}
protected void setRealVector(PreparedStatement pstmt, int i, Object vector) throws SQLException {
throw new UnsupportedOperationException("cds.Vector is not supported on this data store");
}
protected CdsVector getRealVector(ResultSet result, int i) throws SQLException {
throw new UnsupportedOperationException("cds.Vector is not supported on this data store");
}
protected void setLocalTime(PreparedStatement pstmt, int i, Object localTime) throws SQLException {
if (localTime instanceof LocalTime time) {
setLocalTime(pstmt, i, time);
} else if (localTime instanceof String string) {
setLocalTime(pstmt, i, LocalTime.parse(string));
} else if (localTime instanceof java.sql.Time time) {
setLocalTime(pstmt, i, time.toLocalTime());
} else {
pstmt.setObject(i, localTime);
}
}
protected abstract void setLocalTime(PreparedStatement pstmt, int i, LocalTime localTime) throws SQLException;
protected void setLocalDate(PreparedStatement pstmt, int i, Object localDate) throws SQLException {
if (localDate instanceof LocalDate date) {
setLocalDate(pstmt, i, date);
} else if (localDate instanceof String string) {
setLocalDate(pstmt, i, LocalDate.parse(string));
} else if (localDate instanceof java.sql.Date date) {
setLocalDate(pstmt, i, date.toLocalDate());
} else {
pstmt.setObject(i, localDate);
}
}
protected abstract void setLocalDate(PreparedStatement pstmt, int i, LocalDate localDate) throws SQLException;
protected abstract void setInstant(PreparedStatement pstmt, int i, Instant instant) throws SQLException;
private void setLargeBinary(PreparedStatement pstmt, int i, Object largeBin) throws SQLException {
if (largeBin instanceof InputStream stream) {
setLargeBinary(pstmt, i, stream);
} else {
pstmt.setObject(i, largeBin);
}
}
protected abstract void setLargeBinary(PreparedStatement result, int i, InputStream stream) throws SQLException;
private void setZonedDateTime(PreparedStatement pstmt, int i, ZonedDateTime zonedDateTime) throws SQLException {
setInstant(pstmt, i, dateTime(zonedDateTime.toInstant()));
}
protected abstract LocalTime getLocalTime(ResultSet result, int i) throws SQLException;
protected abstract LocalDate getLocalDate(ResultSet result, int i) throws SQLException;
protected abstract Instant getInstant(ResultSet result, int i) throws SQLException;
private void setLargeString(PreparedStatement pstmt, int i, Object largeStr) throws SQLException {
if (largeStr instanceof Reader reader) {
setLargeString(pstmt, i, reader);
} else {
setString(pstmt, i, largeStr);
}
}
protected abstract void setLargeString(PreparedStatement result, int i, Reader reader) throws SQLException;
protected abstract Reader getLargeString(ResultSet result, int i) throws SQLException;
protected abstract InputStream getLargeBinary(ResultSet result, int i) throws SQLException;
protected Boolean getBoolean(ResultSet result, int i) throws SQLException {
boolean bool = result.getBoolean(i);
return result.wasNull() ? null : bool;
}
protected Double getDouble(ResultSet result, int i) throws SQLException {
double dbl = result.getDouble(i);
return result.wasNull() ? null : dbl;
}
protected Short getShort(ResultSet result, int i) throws SQLException {
short int16 = result.getShort(i);
return result.wasNull() ? null : int16;
}
protected Integer getInt(ResultSet result, int i) throws SQLException {
int int32 = result.getInt(i);
return result.wasNull() ? null : int32;
}
protected Long getLong(ResultSet result, int i) throws SQLException {
long int64 = result.getLong(i);
return result.wasNull() ? null : int64;
}
private void setString(PreparedStatement pstmt, int i, Object string) throws SQLException {
pstmt.setString(i, (String) string);
}
protected Float getFloat(ResultSet result, int i) throws SQLException {
float real = result.getFloat(i);
return result.wasNull() ? null : real;
}
protected void setMap(PreparedStatement pstmt, int i, Object value) throws SQLException {
if (value == null) {
setJson(pstmt, i, null);
} else if (value instanceof Map) {
byte[] bytes = Jsonizer.bytes(value);
setJson(pstmt, i, bytes);
} else {
throw new CdsDataStoreException("Unsupported data format for cds.Map type: " + value.getClass());
}
}
protected void setJson(PreparedStatement pstmt, int i, Object json) throws SQLException {
if (json == null) {
pstmt.setNull(i, java.sql.Types.OTHER);
} else if (json instanceof byte[] bytes) {
pstmt.setBytes(i, bytes);
} else if (json instanceof String str) {
pstmt.setString(i, str);
} else {
throw new CdsDataStoreException("Unsupported data format for JSON: " + json.getClass());
}
}
protected List> parseListFromJson(ResultSet result, int i, CdsType rowType) throws SQLException {
String json = getJson(result, i);
if (json == null) {
return null;
}
return StructDataParser.parseArrayOf(rowType, json);
}
protected Object parseObjectFromJson(ResultSet result, int i) throws SQLException {
String json = getJson(result, i);
if (json == null) {
return null;
}
return StructDataParser.parse(json);
}
protected String getJson(ResultSet result, int i) throws SQLException {
return result.getString(i);
}
@SuppressWarnings("deprecation")
private Getter> getValueFromResult(CdsBaseType cdsType, boolean isMediaType) {
switch (cdsType) {
case BOOLEAN:
return this::getBoolean;
case DATE:
return this::getLocalDate;
case DATETIME:
return (result, i) -> dateTime(getInstant(result, i));
case DECIMAL:
case HANA_SMALLDECIMAL:
case DECIMAL_FLOAT:
return ResultSet::getBigDecimal;
case DOUBLE:
return this::getDouble;
case INTEGER:
case INT32:
return this::getInt;
case INTEGER64:
case INT64:
return this::getLong;
case LARGE_BINARY:
if (isMediaType) {
return this::getLargeBinary;
}
case HANA_BINARY:
case BINARY:
return ResultSet::getBytes;
case HANA_CLOB:
case LARGE_STRING:
if (isMediaType) {
return this::getLargeString;
}
case STRING:
return ResultSet::getString;
case TIME:
return this::getLocalTime;
case TIMESTAMP:
return (result, i) -> timestamp(getInstant(result, i), timestampFractionalSeconds);
case UINT8:
case INT16:
case HANA_TINYINT:
case HANA_SMALLINT:
return this::getShort;
case HANA_REAL:
return this::getFloat;
case VECTOR:
return this::getRealVector;
case MAP:
return this::parseObjectFromJson;
default:
return (result, i) -> result.getObject(i);
}
}
protected Calendar getCalendarForDefaultTimeZone() {
return localCalendar.get();
}
}