Skip to content

Commit

Permalink
Simplify row mapper (#15)
Browse files Browse the repository at this point in the history
* Upgrade dependencies

* Add value converter

* Add delegating result set

* Add tests

* Add tests

* add tests

* Implement delegating result set

* Add mockito-junit

* Remove deprecated method

* Add converting result setup

* Refactoring

* Add missing javadoc

* Fix build for Java 21

* Remove "modern type" option

* Move generic row to separate package

* Add javadoc to deprecated methods

* Remove unused value converter

* Add deprecation comments

* Remove unused import

* Rename test case

* Add test for converting ResultSetu

* Enable testcontainer reuse

* Use new sonar task

* Fix sonar warning

* Update settings for new vscode version

* Add enum for jdbc types

* Fix tests

* Convert Row to record

* Convert to record

* Add DB Dialects

* Use extractor for generic rows

* Remove unused code

* Add javadoc comments

* Remove unnecesarry methods from records

* Code cleanup

* Automatically determine dialect from JDBC URL

* Fix sonar plugin warning

* Code cleanup

* Update changelog

* Simplify connection interface

* Update release date

* Update example in readme

---------

Co-authored-by: kaklakariada <[email protected]>
Co-authored-by: kaklakariada <[email protected]>
  • Loading branch information
3 people authored Dec 16, 2023
1 parent a645e6d commit c5d0f3e
Show file tree
Hide file tree
Showing 56 changed files with 3,455 additions and 816 deletions.
7 changes: 5 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,11 @@ jobs:
java-version: ${{ matrix.java }}
cache: 'gradle'

- name: Enable testcontainer reuse
run: echo 'testcontainers.reuse.enable=true' > "$HOME/.testcontainers.properties"

- name: Build with Java ${{ matrix.java }}
run: ./gradlew clean build --info -PjavaVersion=${{matrix.java}}
run: ./gradlew clean build --info -PjavaVersion=${{matrix.java}} -Dsonar.gradle.skipCompile=true

- name: Publish Test Report
uses: scacap/action-surefire-report@v1
Expand All @@ -52,7 +55,7 @@ jobs:

- name: Sonar analysis
if: ${{ env.DEFAULT_JAVA == matrix.java && env.SONAR_TOKEN != null }}
run: ./gradlew sonarqube -Dsonar.token=$SONAR_TOKEN
run: ./gradlew sonar -Dsonar.token=$SONAR_TOKEN -Dsonar.gradle.skipCompile=true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
Expand Down
8 changes: 4 additions & 4 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
"editor.formatOnSave": true,
"java.configuration.updateBuildConfiguration": "automatic",
"editor.codeActionsOnSave": {
"source.organizeImports": true,
"source.generate.finalModifiers": true,
"source.fixAll": true
"source.organizeImports": "explicit",
"source.generate.finalModifiers": "explicit",
"source.fixAll": "explicit"
},
"java.codeGeneration.useBlocks": true,
"java.saveActions.organizeImports": true,
Expand All @@ -19,4 +19,4 @@
"connectionId": "itsallcode",
"projectKey": "org.itsallcode:simple-jdbc"
}
}
}
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.6.0] - unreleased
## [0.7.0] - unreleased

## [0.6.0] - 2023-12-16

- [PR #15](https://github.com/itsallcode/simple-jdbc/pull/15): Refactor row mapper, add DB dialect detection

## [0.5.0] - 2023-11-26

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Add dependency to your gradle project:

```groovy
dependencies {
implementation 'org.itsallcode:simple-jdbc:0.5.0'
implementation 'org.itsallcode:simple-jdbc:0.6.0'
}
```

Expand All @@ -36,13 +36,13 @@ record Name(int id, String name) {

ConnectionFactory connectionFactory = ConnectionFactory.create();
try (SimpleConnection connection = connectionFactory.create("jdbc:h2:mem:", "user", "password")) {
connection.executeScriptFromResource("/schema.sql");
connection.executeScript(readResource("/schema.sql"));
connection.insert("NAMES", List.of("ID", "NAME"), Name::toRow,
Stream.of(new Name(1, "a"), new Name(2, "b"), new Name(3, "c")));
try (SimpleResultSet<Row> rs = connection.query("select * from names order by id")) {
List<Row> result = rs.stream().toList();
assertEquals(3, result.size());
assertEquals(1, result.get(0).getColumnValue(0).getValue());
assertEquals(1, result.get(0).get(0).value());
}
}
```
Expand Down
7 changes: 5 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ plugins {
}

group 'org.itsallcode'
version = '0.5.0'
version = '0.6.0'

dependencies {
}
Expand Down Expand Up @@ -60,6 +60,9 @@ testing {
test {
dependencies {
implementation libs.h2
implementation libs.mockitoJunit
implementation libs.tostringverifier
implementation libs.equalsverifier
}
}
integrationTest(JvmTestSuite) {
Expand Down Expand Up @@ -96,7 +99,7 @@ sonar {
}
}

rootProject.tasks['sonarqube'].dependsOn(tasks['testCodeCoverageReport'], tasks['integrationTestCodeCoverageReport'])
rootProject.tasks['sonar'].dependsOn(tasks['testCodeCoverageReport'], tasks['integrationTestCodeCoverageReport'])

def getPropertyWithDefault(String name, String defaultValue) {
if(project.hasProperty(name)) {
Expand Down
Binary file modified gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
5 changes: 3 additions & 2 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ dependencyResolutionManagement {
library('assertj', 'org.assertj:assertj-core:3.24.2')
library('h2', 'com.h2database:h2:2.2.224')
library('junitPioneer', 'org.junit-pioneer:junit-pioneer:2.2.0')
library('equalsverifier', 'nl.jqno.equalsverifier:equalsverifier:3.15.3')
library('equalsverifier', 'nl.jqno.equalsverifier:equalsverifier:3.15.4')
library('tostringverifier', 'com.jparams:to-string-verifier:1.4.8')
library('hamcrest', 'org.hamcrest:hamcrest-all:1.3')
library('hamcrestResultSetMatcher', 'com.exasol:hamcrest-resultset-matcher:1.6.3')
library('mockito', 'org.mockito:mockito-core:5.7.0')
library('mockito', 'org.mockito:mockito-core:5.8.0')
library('mockitoJunit', 'org.mockito:mockito-junit-jupiter:5.8.0')
library('slf4jLogger', 'org.slf4j:slf4j-jdk14:2.0.9')
library('exasolJdbc', 'com.exasol:exasol-jdbc:7.1.20')
library('exasolTestcontainers', 'com.exasol:exasol-testcontainers:7.0.0')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
import java.time.LocalDate;
import java.util.stream.Stream;

import org.itsallcode.jdbc.resultset.Row;
import org.itsallcode.jdbc.resultset.SimpleResultSet;
import org.itsallcode.jdbc.resultset.generic.Row;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
Expand All @@ -19,7 +19,7 @@
import com.exasol.containers.ExasolContainer;
import com.exasol.containers.ExasolService;

class LegacyTypeITest {
class ExasolTypeTest {

private static final ExasolContainer<?> container = new ExasolContainer<>("8.23.1")
.withRequiredServices(ExasolService.JDBC).withReuse(true);
Expand All @@ -35,16 +35,16 @@ static void stopDb() {
}

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

@ParameterizedTest
@MethodSource("testTypes")
void type(final TypeTest test) {
void genericRowType(final TypeTest test) {
try (SimpleResultSet<Row> result = connect()
.query("select cast('" + test.value() + "' as " + test.type() + ")")) {
final Object value = result.toList().get(0).getColumnValue(0).getValue();
final Object value = result.toList().get(0).get(0).value();
assertAll(
() -> assertThat(value.getClass()).isEqualTo(test.expectedValue().getClass()),
() -> assertThat(value).isEqualTo(test.expectedValue()));
Expand All @@ -53,14 +53,29 @@ void type(final TypeTest test) {

@ParameterizedTest
@MethodSource("testTypes")
void nullValue(final TypeTest test) {
void genericRowNullValue(final TypeTest test) {
try (SimpleResultSet<Row> result = connect()
.query("select cast(NULL as " + test.type() + ")")) {
assertThat(result.toList().get(0).getColumnValue(0).getValue())
assertThat(result.toList().get(0).get(0).value())
.isNull();
}
}

@ParameterizedTest
@MethodSource("testTypes")
void resultSetValueTypes(final TypeTest test) {
try (SimpleConnection connection = connect();
SimpleResultSet<Object> result = connection
.query("select cast('" + test.value() + "' as " + test.type() + ")",
(resultSet, rowNum) -> resultSet.getObject(1,
test.expectedValue().getClass()))) {
final Object value = result.toList().get(0);
assertAll(
() -> assertThat(value.getClass()).isEqualTo(test.expectedValue().getClass()),
() -> assertThat(value).isEqualTo(test.expectedValue()));
}
}

static Stream<Arguments> testTypes() {
return Stream.of(
typeTest("2023-11-25 16:18:46", "timestamp", Instant.parse("2023-11-25T16:18:46.0Z")),
Expand Down
33 changes: 6 additions & 27 deletions src/main/java/org/itsallcode/jdbc/ConnectionFactory.java
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
package org.itsallcode.jdbc;

import java.sql.*;
import java.util.List;
import java.util.Properties;

import org.itsallcode.jdbc.resultset.Row;
import org.itsallcode.jdbc.resultset.RowMapper;

/**
* This class connects to a database and returns new {@link SimpleConnection}s.
*/
public class ConnectionFactory {
private final Context context;
private final DbDialectFactory dialectFactory;

private ConnectionFactory(final Context context) {
private ConnectionFactory(final Context context, final DbDialectFactory dialectFactory) {
this.context = context;
this.dialectFactory = dialectFactory;
}

/**
* Create a new connection factory with a default context.
* Create a new connection factory.
*
* @return a new instance
*/
Expand All @@ -33,7 +31,7 @@ public static ConnectionFactory create() {
* @return a new instance
*/
public static ConnectionFactory create(final Context context) {
return new ConnectionFactory(context);
return new ConnectionFactory(context, new DbDialectFactory());
}

/**
Expand Down Expand Up @@ -69,7 +67,7 @@ public SimpleConnection create(final String url, final String user, final String
* @return a new connection
*/
public SimpleConnection create(final String url, final Properties info) {
return new SimpleConnection(createConnection(url, info), context);
return new SimpleConnection(createConnection(url, info), context, dialectFactory.createDialect(url));
}

private Connection createConnection(final String url, final Properties info) {
Expand All @@ -79,23 +77,4 @@ private Connection createConnection(final String url, final Properties info) {
throw new UncheckedSQLException("Error connecting to '" + url + "'", e);
}
}

/**
* Create a {@link RowMapper} that creates generic {@link Row} objects.
*
* @return a new row mapper
*/
public RowMapper<Row> createGenericRowMapper() {
return RowMapper.createGenericRowMapper(context);
}

/**
* Create a {@link RowMapper} that creates {@link List}s of simple column
* objects.
*
* @return a new row mapper
*/
public RowMapper<List<Object>> createListRowMapper() {
return RowMapper.createListRowMapper(context);
}
}
35 changes: 2 additions & 33 deletions src/main/java/org/itsallcode/jdbc/Context.java
Original file line number Diff line number Diff line change
@@ -1,29 +1,11 @@
package org.itsallcode.jdbc;

import org.itsallcode.jdbc.resultset.ValueExtractorFactory;

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

private final boolean useModernTypes;

private Context(final ContextBuilder builder) {
this.useModernTypes = builder.useModernTypes;
}

/**
* Get the configured {@link ValueExtractorFactory}.
*
* @return value extractor factory
*/
public ValueExtractorFactory getValueExtractorFactory() {
if (useModernTypes) {
return ValueExtractorFactory.createModernType();
} else {
return ValueExtractorFactory.create();
}
private Context() {
}

/**
Expand All @@ -48,30 +30,17 @@ public static ContextBuilder builder() {
* A builder for {@link Context} objects.
*/
public static class ContextBuilder {
private boolean useModernTypes = false;

private ContextBuilder() {
}

/**
* Configure the context to convert legacy types returned by the result set to
* modern types.
*
* @param useModernTypes {@code true} to convert legacy types
* @return {@code this} for fluent programming
*/
public ContextBuilder useModernTypes(final boolean useModernTypes) {
this.useModernTypes = useModernTypes;
return this;
}

/**
* Build a new context.
*
* @return a new context
*/
public Context build() {
return new Context(this);
return new Context();
}
}
}
18 changes: 18 additions & 0 deletions src/main/java/org/itsallcode/jdbc/DbDialectFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.itsallcode.jdbc;

import java.util.ServiceLoader;
import java.util.ServiceLoader.Provider;

import org.itsallcode.jdbc.dialect.DbDialect;

class DbDialectFactory {
public DbDialect createDialect(final String url) {
final ServiceLoader<DbDialect> serviceLoader = ServiceLoader.load(DbDialect.class,
DbDialect.class.getClassLoader());
return serviceLoader.stream()
.map(Provider::get)
.filter(dialect -> dialect.supportsUrl(url))
.findAny()
.orElseThrow(() -> new IllegalStateException("No DB dialect registered for JDBC URL '" + url + "'"));
}
}
Loading

0 comments on commit c5d0f3e

Please sign in to comment.