diff --git a/CHANGELOG.md b/CHANGELOG.md index d4274b3..c41e538 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [PR #41](https://github.com/itsallcode/simple-jdbc/pull/41): Allow direct access to `PreparedStatement` for batch insert - [PR #42](https://github.com/itsallcode/simple-jdbc/pull/42): Allow direct access to `Connection` - [PR #43](https://github.com/itsallcode/simple-jdbc/pull/43): Allow direct access to `Connection` from `DbOperations` +- [PR #44](https://github.com/itsallcode/simple-jdbc/pull/44): Add `GenericDialect` for unsupported databases ## [0.9.0] - 2024-12-23 diff --git a/src/main/java/org/itsallcode/jdbc/DbDialectFactory.java b/src/main/java/org/itsallcode/jdbc/DbDialectFactory.java index 892d0c0..c43daa5 100644 --- a/src/main/java/org/itsallcode/jdbc/DbDialectFactory.java +++ b/src/main/java/org/itsallcode/jdbc/DbDialectFactory.java @@ -2,10 +2,15 @@ import java.util.ServiceLoader; import java.util.ServiceLoader.Provider; +import java.util.logging.Logger; import org.itsallcode.jdbc.dialect.DbDialect; +import org.itsallcode.jdbc.dialect.GenericDialect; class DbDialectFactory { + + private static final Logger LOG = Logger.getLogger(DbDialectFactory.class.getName()); + public DbDialect createDialect(final String url) { final ServiceLoader serviceLoader = ServiceLoader.load(DbDialect.class, Thread.currentThread().getContextClassLoader()); @@ -13,6 +18,9 @@ public DbDialect createDialect(final String url) { .map(Provider::get) .filter(dialect -> dialect.supportsUrl(url)) .findAny() - .orElseThrow(() -> new IllegalStateException("No DB dialect registered for JDBC URL '" + url + "'")); + .orElseGet(() -> { + LOG.warning(() -> "No dialect found for URL '%s', using generic dialect.".formatted(url)); + return GenericDialect.INSTANCE; + }); } } diff --git a/src/main/java/org/itsallcode/jdbc/DbOperations.java b/src/main/java/org/itsallcode/jdbc/DbOperations.java index 32b3676..d7b305a 100644 --- a/src/main/java/org/itsallcode/jdbc/DbOperations.java +++ b/src/main/java/org/itsallcode/jdbc/DbOperations.java @@ -143,7 +143,7 @@ SimpleResultSet query(final String sql, final PreparedStatementSetter pre * * @return original wrapped connection */ - public Connection getOriginalConnection(); + Connection getOriginalConnection(); @Override void close(); diff --git a/src/main/java/org/itsallcode/jdbc/dialect/GenericDialect.java b/src/main/java/org/itsallcode/jdbc/dialect/GenericDialect.java new file mode 100644 index 0000000..89716cd --- /dev/null +++ b/src/main/java/org/itsallcode/jdbc/dialect/GenericDialect.java @@ -0,0 +1,30 @@ +package org.itsallcode.jdbc.dialect; + +import org.itsallcode.jdbc.resultset.generic.ColumnMetaData; + +/** + * A generic {@link DbDialect} without any special handling. + */ +public final class GenericDialect implements DbDialect { + /** Singleton instance of the generic DB dialect. */ + public static final DbDialect INSTANCE = new GenericDialect(); + + private GenericDialect() { + // Nothing to do + } + + @Override + public boolean supportsUrl(final String jdbcUrl) { + return true; + } + + @Override + public ColumnValueExtractor createExtractor(final ColumnMetaData column) { + return Extractors.generic(); + } + + @Override + public ColumnValueSetter createSetter(final Class type) { + return Setters.generic(); + } +} diff --git a/src/test/java/org/itsallcode/jdbc/DbDialectFactoryTest.java b/src/test/java/org/itsallcode/jdbc/DbDialectFactoryTest.java new file mode 100644 index 0000000..042b014 --- /dev/null +++ b/src/test/java/org/itsallcode/jdbc/DbDialectFactoryTest.java @@ -0,0 +1,26 @@ +package org.itsallcode.jdbc; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.itsallcode.jdbc.dialect.*; +import org.junit.jupiter.api.Test; + +class DbDialectFactoryTest { + @Test + void exasolDialect() { + assertThat(new DbDialectFactory().createDialect("jdbc:exa:localhost:8563;schema=SCHEMA")) + .isInstanceOf(ExasolDialect.class); + } + + @Test + void h2Dialect() { + assertThat(new DbDialectFactory().createDialect("jdbc:h2:mem:")) + .isInstanceOf(H2Dialect.class); + } + + @Test + void genericDialect() { + assertThat(new DbDialectFactory().createDialect("jdbc:unknown:localhost:8563;schema=SCHEMA")) + .isInstanceOf(GenericDialect.class); + } +} diff --git a/src/test/java/org/itsallcode/jdbc/dialect/GenericDialectITest.java b/src/test/java/org/itsallcode/jdbc/dialect/GenericDialectITest.java new file mode 100644 index 0000000..ba5945d --- /dev/null +++ b/src/test/java/org/itsallcode/jdbc/dialect/GenericDialectITest.java @@ -0,0 +1,46 @@ +package org.itsallcode.jdbc.dialect; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.stream.Stream; + +import org.h2.jdbcx.JdbcDataSource; +import org.itsallcode.jdbc.*; +import org.itsallcode.jdbc.resultset.ContextRowMapper; +import org.itsallcode.jdbc.resultset.generic.Row; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class GenericDialectITest { + + static Stream types() { + return Stream.of( + Arguments.of(1), + Arguments.of("a"), + Arguments.of(1.0), + Arguments.of(true), + Arguments.of((Object) null)); + } + + @ParameterizedTest + @MethodSource("types") + void setGet(final Object value) { + try (SimpleConnection connection = genericDialectConnection()) { + final List result = connection + .query("select ?", asList(value), ContextRowMapper.generic(GenericDialect.INSTANCE)).toList(); + assertThat(result) + .hasSize(1) + .first().extracting(row -> row.get(0).getValue()) + .isEqualTo(value); + } + } + + private SimpleConnection genericDialectConnection() { + final JdbcDataSource dataSource = new JdbcDataSource(); + dataSource.setURL(H2TestFixture.H2_MEM_JDBC_URL); + return DataSourceConnectionFactory.create(GenericDialect.INSTANCE, dataSource).getConnection(); + } +} diff --git a/src/test/resources/logging.properties b/src/test/resources/logging.properties index 1f2fdd1..069cb61 100644 --- a/src/test/resources/logging.properties +++ b/src/test/resources/logging.properties @@ -2,5 +2,5 @@ handlers=java.util.logging.ConsoleHandler .level=INFO java.util.logging.ConsoleHandler.level=ALL java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter -java.util.logging.SimpleFormatter.format=%1$tF %1$tT.%1$tL [%4$-7s] %5$s %n +java.util.logging.SimpleFormatter.format=%1$tF %1$tT.%1$tL [%4$-7s] %2$s %5$s %n org.itsallcode.level=ALL