Skip to content

Commit

Permalink
Add DB Dialects
Browse files Browse the repository at this point in the history
  • Loading branch information
kaklakariada committed Dec 16, 2023
1 parent 75701b2 commit 3f5d9ea
Show file tree
Hide file tree
Showing 18 changed files with 269 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.time.LocalDate;
import java.util.stream.Stream;

import org.itsallcode.jdbc.dialect.ExasolDialect;
import org.itsallcode.jdbc.resultset.SimpleResultSet;
import org.itsallcode.jdbc.resultset.generic.Row;
import org.junit.jupiter.api.AfterAll;
Expand Down Expand Up @@ -35,7 +36,7 @@ static void stopDb() {
}

SimpleConnection connect() {
return ConnectionFactory.create(Context.builder().build()) //
return ConnectionFactory.create(Context.builder().dialect(new ExasolDialect()).build()) //
.create(container.getJdbcUrl(), container.getUsername(), container.getPassword());
}

Expand Down
9 changes: 0 additions & 9 deletions src/main/java/org/itsallcode/jdbc/ConnectionFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,6 @@ private ConnectionFactory(final Context context) {
this.context = context;
}

/**
* Create a new connection factory with a default context.
*
* @return a new instance
*/
public static ConnectionFactory create() {
return create(Context.builder().build());
}

/**
* Create a new connection factory with a custom context.
*
Expand Down
31 changes: 29 additions & 2 deletions src/main/java/org/itsallcode/jdbc/Context.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
package org.itsallcode.jdbc;

import static java.util.stream.Collectors.toList;

import java.sql.ResultSet;
import java.util.List;
import java.util.Objects;

import org.itsallcode.jdbc.dialect.DbDialect;
import org.itsallcode.jdbc.resultset.*;
import org.itsallcode.jdbc.resultset.generic.SimpleMetaData;
import org.itsallcode.jdbc.resultset.generic.ValueExtractorFactory;

/**
* This represents a context with configuration for the Simple JDBC framework.
*/
public class Context {

private Context() {
private final DbDialect dialect;

private Context(final ContextBuilder builder) {
this.dialect = Objects.requireNonNull(builder.dialect, "dialect");
}

/**
Expand All @@ -28,6 +40,14 @@ public ParameterMapper getParameterMapper() {
return ParameterMapper.create();
}

ResultSet convertingResultSet(final ResultSet resultSet) {
final SimpleMetaData metaData = SimpleMetaData.create(resultSet, this);
final List<ColumnValueConverter> converters = metaData.getColumns().stream()
.map(col -> ColumnValueConverter.simple(dialect.createConverter(col)))
.collect(toList());
return new ConvertingResultSet(resultSet, ResultSetValueConverter.create(metaData, converters));
}

/**
* Create a new builder for {@link Context} objects.
*
Expand All @@ -42,16 +62,23 @@ public static ContextBuilder builder() {
*/
public static class ContextBuilder {

private DbDialect dialect;

private ContextBuilder() {
}

public ContextBuilder dialect(final DbDialect dialect) {
this.dialect = dialect;
return this;
}

/**
* Build a new context.
*
* @return a new context
*/
public Context build() {
return new Context();
return new Context(this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ class SimplePreparedStatement implements AutoCloseable {

<T> SimpleResultSet<T> executeQuery(final RowMapper<T> rowMapper) {
final ResultSet resultSet = doExecute();
return new SimpleResultSet<>(context, resultSet, rowMapper);
final ResultSet convertingResultSet = context.convertingResultSet(resultSet);
return new SimpleResultSet<>(context, convertingResultSet, rowMapper);
}

private ResultSet doExecute() {
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/org/itsallcode/jdbc/dialect/DbDialect.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.itsallcode.jdbc.dialect;

import org.itsallcode.jdbc.resultset.generic.SimpleMetaData.ColumnMetaData;

public interface DbDialect {
Extractor createConverter(final ColumnMetaData column);
}
20 changes: 20 additions & 0 deletions src/main/java/org/itsallcode/jdbc/dialect/ExasolDialect.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.itsallcode.jdbc.dialect;

import java.time.LocalDate;
import java.time.LocalTime;

import org.itsallcode.jdbc.resultset.generic.SimpleMetaData.ColumnMetaData;

public class ExasolDialect implements DbDialect {

public Extractor createConverter(final ColumnMetaData column) {
return switch (column.getType().getJdbcType()) {
case TIMESTAMP -> Extractors.timestampToUTCInstant();
case CLOB -> Extractors.clobToString();
case BLOB -> Extractors.blobToBytes();
case TIME -> Extractors.forType(LocalTime.class);
case DATE -> Extractors.forType(LocalDate.class);
default -> Extractors.generic();
};
}
}
11 changes: 11 additions & 0 deletions src/main/java/org/itsallcode/jdbc/dialect/Extractor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.itsallcode.jdbc.dialect;

import java.sql.ResultSet;
import java.sql.SQLException;

@FunctionalInterface
public interface Extractor {

Object getObject(ResultSet resultSet, int columnIndex) throws SQLException;

}
48 changes: 48 additions & 0 deletions src/main/java/org/itsallcode/jdbc/dialect/Extractors.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.itsallcode.jdbc.dialect;

import java.sql.*;
import java.util.Calendar;
import java.util.TimeZone;

public class Extractors {
private static final Calendar UTC_CALENDAR = Calendar.getInstance(TimeZone.getTimeZone("UTC"));

private Extractors() {
}

static Extractor timestampToUTCInstant() {
return nonNull((resultSet, columnIndex) -> resultSet.getTimestamp(columnIndex, UTC_CALENDAR).toInstant());
}

private static Extractor nonNull(final Extractor extractor) {
return (resultSet, columnIndex) -> {
resultSet.getObject(columnIndex);
if (resultSet.wasNull()) {
return null;
}
return extractor.getObject(resultSet, columnIndex);
};
}

static Extractor clobToString() {
return nonNull((resultSet, columnIndex) -> {
final Clob clob = resultSet.getClob(columnIndex);
return clob.getSubString(1, (int) clob.length());
});
}

static Extractor blobToBytes() {
return nonNull((resultSet, columnIndex) -> {
final Blob blob = resultSet.getBlob(columnIndex);
return blob.getBytes(1, (int) blob.length());
});
}

static Extractor forType(final Class<?> type) {
return (resultSet, columnIndex) -> resultSet.getObject(columnIndex, type);
}

static Extractor generic() {
return ResultSet::getObject;
}
}
20 changes: 20 additions & 0 deletions src/main/java/org/itsallcode/jdbc/dialect/H2Dialect.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.itsallcode.jdbc.dialect;

import java.time.LocalDate;
import java.time.LocalTime;

import org.itsallcode.jdbc.resultset.generic.SimpleMetaData.ColumnMetaData;

public class H2Dialect implements DbDialect {

public Extractor createConverter(final ColumnMetaData column) {
return switch (column.getType().getJdbcType()) {
case TIMESTAMP -> Extractors.timestampToUTCInstant();
case CLOB -> Extractors.clobToString();
case BLOB -> Extractors.blobToBytes();
case TIME -> Extractors.forType(LocalTime.class);
case DATE -> Extractors.forType(LocalDate.class);
default -> Extractors.generic();
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.itsallcode.jdbc.resultset;

import java.sql.ResultSet;
import java.sql.SQLException;

import org.itsallcode.jdbc.dialect.Extractor;

@FunctionalInterface
public interface ColumnValueConverter {

<T> T getObject(ResultSet resultSet, int columnIndex, Class<T> type) throws SQLException;

static ColumnValueConverter generic() {
return simple(ResultSet::getObject);
}

static ColumnValueConverter simple(final Extractor extractor) {
return new ColumnValueConverter() {
@Override
public <T> T getObject(final ResultSet resultSet, final int columnIndex, final Class<T> type)
throws SQLException {
return type.cast(extractor.getObject(resultSet, columnIndex));
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,23 @@
import java.sql.ResultSet;
import java.sql.SQLException;

class ConvertingResultSet extends DelegatingResultSet {
public class ConvertingResultSet extends DelegatingResultSet {
private final ResultSet delegate;
private final ResultSetValueConverter converter;

ConvertingResultSet(final ResultSet delegate) {
public ConvertingResultSet(final ResultSet delegate, final ResultSetValueConverter converter) {
super(delegate);
this.delegate = delegate;
this.converter = converter;
}

@Override
public <T> T getObject(final int columnIndex, final Class<T> type) throws SQLException {
throw new UnsupportedOperationException();
return converter.getObject(delegate, columnIndex, type);
}

@Override
public <T> T getObject(final String columnLabel, final Class<T> type) throws SQLException {
throw new UnsupportedOperationException();
return converter.getObject(delegate, columnLabel, type);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package org.itsallcode.jdbc.resultset;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;

import org.itsallcode.jdbc.resultset.generic.SimpleMetaData;
import org.itsallcode.jdbc.resultset.generic.SimpleMetaData.ColumnMetaData;

public class ResultSetValueConverter {

private final Map<Integer, ColumnValueConverter> convertersByIndex;
private final Map<String, Integer> columnIndexByLabel;

private ResultSetValueConverter(final Map<Integer, ColumnValueConverter> convertersByIndex,
final Map<String, Integer> columnIndexByLabel) {
this.convertersByIndex = convertersByIndex;
this.columnIndexByLabel = columnIndexByLabel;
}

public static ResultSetValueConverter create(final SimpleMetaData resultSetMetadata,
final List<ColumnValueConverter> converters) {
final Map<Integer, ColumnValueConverter> convertersByIndex = new HashMap<>();
final Map<String, Integer> columnIndexByLabel = new HashMap<>();
for (int i = 1; i <= converters.size(); i++) {
final ColumnValueConverter converter = converters.get(i - 1);
final ColumnMetaData metaData = resultSetMetadata.getColumnByIndex(i);
convertersByIndex.put(metaData.getColumnIndex(), converter);
columnIndexByLabel.put(metaData.getLabel(), i);
}
return new ResultSetValueConverter(convertersByIndex, columnIndexByLabel);
}

public <T> T getObject(final ResultSet delegate, final int columnIndex, final Class<T> type) throws SQLException {
return getConverter(columnIndex).getObject(delegate, columnIndex, type);
}

public <T> T getObject(final ResultSet delegate, final String columnLabel, final Class<T> type)
throws SQLException {
final int columnIndex = getIndexForLabel(columnLabel);
return getConverter(columnIndex).getObject(delegate, columnIndex, type);
}

private int getIndexForLabel(final String columnLabel) {
final Integer index = columnIndexByLabel.get(columnLabel);
if (index == null) {
throw new IllegalStateException("No index found for column label '" + columnLabel + "'");
}
return index.intValue();
}

private ColumnValueConverter getConverter(final int columnIndex) {
final ColumnValueConverter converter = convertersByIndex.get(columnIndex);
if (converter == null) {
throw new IllegalStateException("No converter found for column index " + columnIndex);
}
return converter;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public GenericRowMapper(final ColumnValuesConverter<T> converter) {
@Override
public T mapRow(final Context context, final ResultSet resultSet, final int rowNum) throws SQLException {
if (rowBuilder == null) {
rowBuilder = new ResultSetRowBuilder(SimpleMetaData.create(resultSet.getMetaData(), context));
rowBuilder = new ResultSetRowBuilder(SimpleMetaData.create(resultSet, context));
}
final Row row = rowBuilder.buildRow(resultSet, rowNum);
return converter.mapRow(row);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.itsallcode.jdbc.resultset.generic;

import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

Expand All @@ -18,7 +17,19 @@ private SimpleMetaData(final List<ColumnMetaData> columns) {
this.columns = columns;
}

static SimpleMetaData create(final ResultSetMetaData metaData, final Context context) {
public static SimpleMetaData create(final ResultSet resultSet, final Context context) {
return create(getMetaData(resultSet), context);
}

private static ResultSetMetaData getMetaData(final ResultSet resultSet) {
try {
return resultSet.getMetaData();
} catch (final SQLException e) {
throw new UncheckedSQLException("Error getting meta data", e);
}
}

private static SimpleMetaData create(final ResultSetMetaData metaData, final Context context) {
try {
final List<ColumnMetaData> columns = createColumnMetaData(metaData, context);
return new SimpleMetaData(columns);
Expand Down Expand Up @@ -49,6 +60,16 @@ public List<ColumnMetaData> getColumns() {
return columns;
}

/**
* Get column metadata for a given index (one based).
*
* @param index column index (one based)
* @return column metadata
*/
public ColumnMetaData getColumnByIndex(final int index) {
return columns.get(index - 1);
}

/**
* Represents the metadata of a single column.
*/
Expand Down
Loading

0 comments on commit 3f5d9ea

Please sign in to comment.