
hu.webarticum.miniconnect.jdbcadapter.JdbcAdapterResultSet Maven / Gradle / Ivy
package hu.webarticum.miniconnect.jdbcadapter;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.sql.JDBCType;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import hu.webarticum.miniconnect.api.MiniColumnHeader;
import hu.webarticum.miniconnect.api.MiniResultSet;
import hu.webarticum.miniconnect.api.MiniValue;
import hu.webarticum.miniconnect.api.MiniValueDefinition;
import hu.webarticum.miniconnect.impl.result.StoredColumnHeader;
import hu.webarticum.miniconnect.lang.ByteString;
import hu.webarticum.miniconnect.lang.ImmutableList;
import hu.webarticum.miniconnect.record.converter.Converter;
import hu.webarticum.miniconnect.record.converter.DefaultConverter;
import hu.webarticum.miniconnect.record.type.StandardValueType;
import hu.webarticum.miniconnect.record.type.ValueType;
public class JdbcAdapterResultSet implements MiniResultSet {
private static final Map TYPE_MAPPING =
Collections.synchronizedMap(new EnumMap<>(JDBCType.class));
static {
TYPE_MAPPING.put(JDBCType.NULL, StandardValueType.NULL);
TYPE_MAPPING.put(JDBCType.BOOLEAN, StandardValueType.BOOL);
TYPE_MAPPING.put(JDBCType.BIT, StandardValueType.BOOL);
TYPE_MAPPING.put(JDBCType.TINYINT, StandardValueType.INT);
TYPE_MAPPING.put(JDBCType.SMALLINT, StandardValueType.INT);
TYPE_MAPPING.put(JDBCType.INTEGER, StandardValueType.INT);
TYPE_MAPPING.put(JDBCType.BIGINT, StandardValueType.LONG);
TYPE_MAPPING.put(JDBCType.NUMERIC, StandardValueType.DECIMAL);
TYPE_MAPPING.put(JDBCType.DECIMAL, StandardValueType.DECIMAL);
TYPE_MAPPING.put(JDBCType.REAL, StandardValueType.FLOAT);
TYPE_MAPPING.put(JDBCType.FLOAT, StandardValueType.DOUBLE);
TYPE_MAPPING.put(JDBCType.DOUBLE, StandardValueType.DOUBLE);
TYPE_MAPPING.put(JDBCType.BINARY, StandardValueType.BINARY);
TYPE_MAPPING.put(JDBCType.VARBINARY, StandardValueType.BINARY);
TYPE_MAPPING.put(JDBCType.LONGVARBINARY, StandardValueType.BINARY);
TYPE_MAPPING.put(JDBCType.CHAR, StandardValueType.STRING);
TYPE_MAPPING.put(JDBCType.NCHAR, StandardValueType.STRING);
TYPE_MAPPING.put(JDBCType.VARCHAR, StandardValueType.STRING);
TYPE_MAPPING.put(JDBCType.NVARCHAR, StandardValueType.STRING);
TYPE_MAPPING.put(JDBCType.LONGVARCHAR, StandardValueType.STRING);
TYPE_MAPPING.put(JDBCType.LONGNVARCHAR, StandardValueType.STRING);
TYPE_MAPPING.put(JDBCType.TIME, StandardValueType.TIME);
TYPE_MAPPING.put(JDBCType.DATE, StandardValueType.DATE);
TYPE_MAPPING.put(JDBCType.TIMESTAMP, StandardValueType.TIMESTAMP);
// FIXME: use some blob wrapper
TYPE_MAPPING.put(JDBCType.BLOB, StandardValueType.BINARY);
TYPE_MAPPING.put(JDBCType.CLOB, StandardValueType.STRING);
TYPE_MAPPING.put(JDBCType.NCLOB, StandardValueType.STRING);
/*
// TODO: support more types
TIME_WITH_TIMEZONE
TIMESTAMP_WITH_TIMEZONE
JAVA_OBJECT
SQLXML
OTHER
ROWID
DISTINCT
STRUCT
ARRAY
REF
DATALINK
REF_CURSOR
etc.
settings, encodings etc.
*/
}
private final ImmutableList valueTypes;
private final ImmutableList columnHeaders;
private final Statement jdbcStatement;
private final ResultSet jdbcResultSet;
public JdbcAdapterResultSet(Statement jdbcStatement, ResultSet jdbcResultSet) {
this.jdbcStatement = jdbcStatement;
this.jdbcResultSet = jdbcResultSet;
this.valueTypes = extractValueTypes(jdbcResultSet);
this.columnHeaders = extractColumnHeaders(jdbcResultSet, valueTypes);
}
private static ImmutableList extractValueTypes(ResultSet jdbcResultSet) {
try {
return extractValueTypesThrowing(jdbcResultSet);
} catch (SQLException e) {
throw new UncheckedSqlException(e);
}
}
private static ImmutableList extractValueTypesThrowing(
ResultSet jdbcResultSet) throws SQLException {
ResultSetMetaData jdbcMetaData = jdbcResultSet.getMetaData();
int columnCount = jdbcMetaData.getColumnCount();
List resultBuilder = new ArrayList<>(columnCount);
for (int c = 1; c <= columnCount; c++) {
resultBuilder.add(extractValueTypeThrowing(jdbcMetaData, c));
}
return ImmutableList.fromCollection(resultBuilder);
}
private static ValueType extractValueTypeThrowing(
ResultSetMetaData jdbcMetaData, int c) throws SQLException {
int jdbcTypeNumber = jdbcMetaData.getColumnType(c);
JDBCType jdbcType = JDBCType.valueOf(jdbcTypeNumber);
if (jdbcType == JDBCType.DECIMAL && jdbcMetaData.getScale(c) == 0) {
return StandardValueType.BIGINT;
}
return TYPE_MAPPING.getOrDefault(jdbcType, StandardValueType.BINARY);
}
private static ImmutableList extractColumnHeaders(
ResultSet jdbcResultSet, ImmutableList valueTypes) {
try {
return extractColumnHeadersThrowing(jdbcResultSet, valueTypes);
} catch (SQLException e) {
throw new UncheckedSqlException(e);
}
}
private static ImmutableList extractColumnHeadersThrowing(
ResultSet jdbcResultSet, ImmutableList valueTypes) throws SQLException {
ResultSetMetaData jdbcMetaData = jdbcResultSet.getMetaData();
int columnCount = jdbcMetaData.getColumnCount();
List resultBuilder = new ArrayList<>(columnCount);
for (int i = 0; i < columnCount; i++) {
int c = i + 1;
String name = jdbcMetaData.getColumnLabel(c);
boolean isNullable = (jdbcMetaData.isNullable(c) != ResultSetMetaData.columnNoNulls);
MiniValueDefinition valueDefinition =
valueTypes.get(i).defaultTranslator().definition();
resultBuilder.add(new StoredColumnHeader(name, isNullable, valueDefinition));
}
return ImmutableList.fromCollection(resultBuilder);
}
@Override
public ImmutableList columnHeaders() {
return columnHeaders;
}
@Override
public ImmutableList fetch() {
try {
return fetchThrowing();
} catch (SQLException e) {
throw new UncheckedSqlException(e);
}
}
private ImmutableList fetchThrowing() throws SQLException {
if (!jdbcResultSet.next()) {
return null;
}
return extractRowThrowing();
}
private ImmutableList extractRowThrowing() throws SQLException {
int columnCount = jdbcResultSet.getMetaData().getColumnCount();
List resultBuilder = new ArrayList<>(columnCount);
for (int i = 0; i < columnCount; i++) {
MiniValue value = extractValue(i);
resultBuilder.add(value);
}
return ImmutableList.fromCollection(resultBuilder);
}
private MiniValue extractValue(int i) {
ValueType valueType = valueTypes.get(i);
Class> mappingType = valueType.clazz();
Object value = getValue(i, mappingType);
return valueType.defaultTranslator().encodeFully(value);
}
private Object getValue(int i, Class> mappingType) {
int c = i + 1;
Class> jdbcMappingType = jdbcMappingTypeOf(mappingType);
// TODO: handle precision/scale, use LargeInteger in case of DECIMAL(-1, 0)
try {
return convertJdbcValue(mappingType, jdbcResultSet.getObject(c, jdbcMappingType));
} catch (SQLException e) {
// TODO: log?
}
Object value;
try {
value = jdbcResultSet.getObject(c);
} catch (SQLException e) {
return null;
}
if (mappingType.isInstance(value)) {
return value;
}
Converter converter = new DefaultConverter();
try {
return converter.convert(value, mappingType);
} catch (Exception e) {
return value;
}
}
private Class> jdbcMappingTypeOf(Class> mappingType) {
if (mappingType == LocalTime.class) {
return java.sql.Time.class;
} else if (mappingType == LocalDate.class) {
return java.sql.Date.class;
} else if (mappingType == Instant.class) {
return java.sql.Timestamp.class;
} else if (mappingType == ByteString.class) {
return byte[].class;
} else {
return mappingType;
}
}
private Object convertJdbcValue(Class> mappingType, Object jdbcValue) {
if (jdbcValue == null) {
return null;
} else if (mappingType == LocalTime.class) {
return ((java.sql.Time) jdbcValue).toLocalTime();
} else if (mappingType == LocalDate.class) {
return ((java.sql.Date) jdbcValue).toLocalDate();
} else if (mappingType == Instant.class) {
return ((java.sql.Timestamp) jdbcValue).toInstant();
} else if (mappingType == ByteString.class) {
return ByteString.wrap((byte[]) jdbcValue);
} else {
return jdbcValue;
}
}
@Override
public void close() {
try {
jdbcResultSet.close();
jdbcStatement.close();
} catch (SQLException e) {
throw new UncheckedIOException(new IOException("Unexpected SQLException", e));
}
}
@Override
public boolean isClosed() {
try {
return jdbcResultSet.isClosed();
} catch (SQLException e) {
throw new UncheckedIOException(new IOException("Database access error", e));
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy