From 38144d035048124776fdc2e78dc0f4f2395d5e91 Mon Sep 17 00:00:00 2001 From: Ravi Chodavarapu Date: Fri, 5 May 2017 14:09:29 +0100 Subject: [PATCH 1/3] Support multiple database types for the database client Add support for H2 databases --- core/pom.xml | 4 +- .../stack/datamill/db/DatabaseClient.java | 87 ++++---- .../stack/datamill/db/DatabaseType.java | 9 + .../datamill/db/impl/DatabaseTypeAdapter.java | 20 ++ .../datamill/db/impl/FunctionsMySQL.java | 197 ++++++++++++++++++ .../db/impl/H2DatabaseTypeAdapter.java | 83 ++++++++ .../datamill/db/test/TestDatabaseClient.java | 7 +- .../datamill/db/impl/DatabaseClientTest.java | 3 +- .../db/impl/H2UrlTransformerTest.java | 24 +++ parent/pom.xml | 6 +- 10 files changed, 393 insertions(+), 47 deletions(-) create mode 100644 core/src/main/java/foundation/stack/datamill/db/DatabaseType.java create mode 100644 core/src/main/java/foundation/stack/datamill/db/impl/DatabaseTypeAdapter.java create mode 100644 core/src/main/java/foundation/stack/datamill/db/impl/FunctionsMySQL.java create mode 100644 core/src/main/java/foundation/stack/datamill/db/impl/H2DatabaseTypeAdapter.java create mode 100644 core/src/test/java/foundation/stack/datamill/db/impl/H2UrlTransformerTest.java diff --git a/core/pom.xml b/core/pom.xml index b50faf7..da9d81d 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -113,8 +113,8 @@ test - org.hsqldb - hsqldb + com.h2database + h2 test diff --git a/core/src/main/java/foundation/stack/datamill/db/DatabaseClient.java b/core/src/main/java/foundation/stack/datamill/db/DatabaseClient.java index 1a9de62..d056a90 100644 --- a/core/src/main/java/foundation/stack/datamill/db/DatabaseClient.java +++ b/core/src/main/java/foundation/stack/datamill/db/DatabaseClient.java @@ -1,10 +1,9 @@ package foundation.stack.datamill.db; import com.github.davidmoten.rx.jdbc.*; +import com.zaxxer.hikari.HikariDataSource; import foundation.stack.datamill.configuration.Named; -import foundation.stack.datamill.db.impl.QueryBuilderImpl; -import foundation.stack.datamill.db.impl.RowImpl; -import foundation.stack.datamill.db.impl.UnsubscribeOnNextOperator; +import foundation.stack.datamill.db.impl.*; import org.flywaydb.core.Flyway; import org.flywaydb.core.api.MigrationInfo; import org.flywaydb.core.api.callback.FlywayCallback; @@ -25,41 +24,49 @@ public class DatabaseClient extends QueryBuilderImpl implements QueryRunner { private static final Logger logger = LoggerFactory.getLogger(DatabaseClient.class); + private static String adaptUrl(DatabaseTypeAdapter typeAdapter, String url) { + if (typeAdapter != null) { + DatabaseTypeAdapter.UrlTransformer urlTransformer = typeAdapter.createUrlTransformer(); + if (urlTransformer != null) { + url = urlTransformer.transform(url); + } + } + + return url; + } + private DelegatingConnectionProvider connectionProvider; - private final DataSource dataSource; + private DataSource dataSource; private Database database; - private final String password; - private final String url; - private final String username; + private DatabaseTypeAdapter typeAdapter; - public DatabaseClient(DataSource dataSource) { + public DatabaseClient(DatabaseType type, DataSource dataSource) { + this.typeAdapter = type == DatabaseType.H2 ? new H2DatabaseTypeAdapter() : null; this.dataSource = dataSource; - - this.url = null; - this.username = null; - this.password = null; } - public DatabaseClient(String url) { - this(url, null, null); + public DatabaseClient(DatabaseType type, String url) { + this(type, url, null, null); } - public DatabaseClient(@Named("url") String url, @Named("username") String username, @Named("password") String password) { - this.dataSource = null; + public DatabaseClient( + DatabaseType type, + @Named("url") String url, + @Named("username") String username, + @Named("password") String password) { + this.typeAdapter = type == DatabaseType.H2 ? new H2DatabaseTypeAdapter() : null; + + HikariDataSource dataSource = new HikariDataSource(); + dataSource.setUsername(username); + dataSource.setPassword(password); + dataSource.setJdbcUrl(adaptUrl(typeAdapter, url)); - this.url = url; - this.username = username; - this.password = password; + this.dataSource = dataSource; } private void setupConnectionProvider() { - if (dataSource != null) { - connectionProvider = new DelegatingConnectionProvider(new ConnectionProviderFromDataSource(dataSource)); - database = Database.from(connectionProvider); - } else if (url != null) { - connectionProvider = new DelegatingConnectionProvider(new ConnectionProviderPooled(url, username, password, 0, 10)); - database = Database.from(connectionProvider); - } + connectionProvider = new DelegatingConnectionProvider(new ConnectionProviderFromDataSource(dataSource)); + database = Database.from(connectionProvider); } private DelegatingConnectionProvider getConnectionProvider() { @@ -102,11 +109,7 @@ public String getURL() { private Flyway getFlyway() { Flyway flyway = new Flyway(); - if (dataSource != null) { - flyway.setDataSource(dataSource); - } else { - flyway.setDataSource(url, username, password); - } + flyway.setDataSource(dataSource); return flyway; } @@ -116,9 +119,9 @@ public void clean() { public void migrate(Action1 migrationPreparation) { Flyway flyway = getFlyway(); - if (migrationPreparation != null) { - flyway.setCallbacks(new MigrationCallback(migrationPreparation)); - } + flyway.setCallbacks(new MigrationCallback( + typeAdapter != null ? typeAdapter.createConnectionPreparer() : null, + migrationPreparation)); flyway.migrate(); } @@ -226,9 +229,11 @@ public Observable stream() { } private static class MigrationCallback implements FlywayCallback { + private final DatabaseTypeAdapter.ConnectionPreparer connectionPreparer; private final Action1 migrationAction; - public MigrationCallback(Action1 migrationAction) { + public MigrationCallback(DatabaseTypeAdapter.ConnectionPreparer connectionPreparer, Action1 migrationAction) { + this.connectionPreparer = connectionPreparer; this.migrationAction = migrationAction; } @@ -244,7 +249,17 @@ public void afterClean(Connection connection) { @Override public void beforeMigrate(Connection connection) { - migrationAction.call(connection); + if (connectionPreparer != null) { + try { + connectionPreparer.prepare(connection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + if (migrationAction != null) { + migrationAction.call(connection); + } } @Override diff --git a/core/src/main/java/foundation/stack/datamill/db/DatabaseType.java b/core/src/main/java/foundation/stack/datamill/db/DatabaseType.java new file mode 100644 index 0000000..5507e08 --- /dev/null +++ b/core/src/main/java/foundation/stack/datamill/db/DatabaseType.java @@ -0,0 +1,9 @@ +package foundation.stack.datamill.db; + +/** + * @author Ravi Chodavarapu (rchodava@gmail.com) + */ +public enum DatabaseType { + MYSQL, + H2 +} diff --git a/core/src/main/java/foundation/stack/datamill/db/impl/DatabaseTypeAdapter.java b/core/src/main/java/foundation/stack/datamill/db/impl/DatabaseTypeAdapter.java new file mode 100644 index 0000000..719484c --- /dev/null +++ b/core/src/main/java/foundation/stack/datamill/db/impl/DatabaseTypeAdapter.java @@ -0,0 +1,20 @@ +package foundation.stack.datamill.db.impl; + +import java.sql.Connection; +import java.sql.SQLException; + +/** + * @author Ravi Chodavarapu (rchodava@gmail.com) + */ +public interface DatabaseTypeAdapter { + interface ConnectionPreparer { + void prepare(Connection connection) throws SQLException; + } + + interface UrlTransformer { + String transform(String url); + } + + ConnectionPreparer createConnectionPreparer(); + UrlTransformer createUrlTransformer(); +} diff --git a/core/src/main/java/foundation/stack/datamill/db/impl/FunctionsMySQL.java b/core/src/main/java/foundation/stack/datamill/db/impl/FunctionsMySQL.java new file mode 100644 index 0000000..d6bb8bc --- /dev/null +++ b/core/src/main/java/foundation/stack/datamill/db/impl/FunctionsMySQL.java @@ -0,0 +1,197 @@ +/* + * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (http://h2database.com/html/license.html). + * Initial Developer: Jason Brittain (jason.brittain at gmail.com) + */ +package foundation.stack.datamill.db.impl; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Timestamp; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +/** + * From: https://github.com/h2database/h2database/blob/193ce9105e2de773c0ab62855a000cb61e9ee0e0/h2/src/main/org/h2/mode/FunctionsMySQL.java + * + * This class implements some MySQL-specific functions. + * + * @author Jason Brittain + * @author Thomas Mueller + */ +public class FunctionsMySQL { + + /** + * The date format of a MySQL formatted date/time. + * Example: 2008-09-25 08:40:59 + */ + private static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; + + /** + * Format replacements for MySQL date formats. + * See + * http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_date-format + */ + private static final String[] FORMAT_REPLACE = { + "%a", "EEE", + "%b", "MMM", + "%c", "MM", + "%d", "dd", + "%e", "d", + "%H", "HH", + "%h", "hh", + "%I", "hh", + "%i", "mm", + "%j", "DDD", + "%k", "H", + "%l", "h", + "%M", "MMMM", + "%m", "MM", + "%p", "a", + "%r", "hh:mm:ss a", + "%S", "ss", + "%s", "ss", + "%T", "HH:mm:ss", + "%W", "EEEE", + "%w", "F", + "%Y", "yyyy", + "%y", "yy", + "%%", "%", + }; + + /** + * Register the functionality in the database. + * Nothing happens if the functions are already registered. + * + * @param conn the connection + */ + public static void register(Connection conn) throws SQLException { + String[] init = { + "UNIX_TIMESTAMP", "unixTimestamp", + "LOCALTIMESTAMP", "localTimestamp", + "FROM_UNIXTIME", "fromUnixTime", + "DATE", "date", + }; + Statement stat = conn.createStatement(); + for (int i = 0; i < init.length; i += 2) { + String alias = init[i], method = init[i + 1]; + stat.execute( + "CREATE ALIAS IF NOT EXISTS " + alias + + " FOR \"" + FunctionsMySQL.class.getName() + "." + method + "\""); + } + } + + /** + * Replace all occurrences of the before string with the after string. + * + * @param s the string + * @param before the old text + * @param after the new text + * @return the string with the before string replaced + */ + private static String replaceAll(String s, String before, String after) { + int next = s.indexOf(before); + if (next < 0) { + return s; + } + StringBuilder buff = new StringBuilder( + s.length() - before.length() + after.length()); + int index = 0; + while (true) { + buff.append(s.substring(index, next)).append(after); + index = next + before.length(); + next = s.indexOf(before, index); + if (next < 0) { + buff.append(s.substring(index)); + break; + } + } + return buff.toString(); + } + + /** + * Get the seconds since 1970-01-01 00:00:00 UTC. + * See + * http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_unix-timestamp + * + * @return the current timestamp in seconds (not milliseconds). + */ + public static int unixTimestamp() { + return (int) (System.currentTimeMillis() / 1000L); + } + + public static Timestamp localTimestamp() { + return new Timestamp(System.currentTimeMillis()); + } + + /** + * Get the seconds since 1970-01-01 00:00:00 UTC of the given timestamp. + * See + * http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_unix-timestamp + * + * @param timestamp the timestamp + * @return the current timestamp in seconds (not milliseconds). + */ + public static int unixTimestamp(Timestamp timestamp) { + return (int) (timestamp.getTime() / 1000L); + } + + /** + * See + * http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_from-unixtime + * + * @param seconds The current timestamp in seconds. + * @return a formatted date/time String in the format "yyyy-MM-dd HH:mm:ss". + */ + public static String fromUnixTime(int seconds) { + SimpleDateFormat formatter = new SimpleDateFormat(DATE_TIME_FORMAT, + Locale.ENGLISH); + return formatter.format(new Date(seconds * 1000L)); + } + + /** + * See + * http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_from-unixtime + * + * @param seconds The current timestamp in seconds. + * @param format The format of the date/time String to return. + * @return a formatted date/time String in the given format. + */ + public static String fromUnixTime(int seconds, String format) { + format = convertToSimpleDateFormat(format); + SimpleDateFormat formatter = new SimpleDateFormat(format, Locale.ENGLISH); + return formatter.format(new Date(seconds * 1000L)); + } + + private static String convertToSimpleDateFormat(String format) { + String[] replace = FORMAT_REPLACE; + for (int i = 0; i < replace.length; i += 2) { + format = replaceAll(format, replace[i], replace[i + 1]); + } + return format; + } + + /** + * See + * http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_date + * This function is dependent on the exact formatting of the MySQL date/time + * string. + * + * @param dateTime The date/time String from which to extract just the date + * part. + * @return the date part of the given date/time String argument. + */ + public static String date(String dateTime) { + if (dateTime == null) { + return null; + } + int index = dateTime.indexOf(' '); + if (index != -1) { + return dateTime.substring(0, index); + } + return dateTime; + } + +} \ No newline at end of file diff --git a/core/src/main/java/foundation/stack/datamill/db/impl/H2DatabaseTypeAdapter.java b/core/src/main/java/foundation/stack/datamill/db/impl/H2DatabaseTypeAdapter.java new file mode 100644 index 0000000..e2a52df --- /dev/null +++ b/core/src/main/java/foundation/stack/datamill/db/impl/H2DatabaseTypeAdapter.java @@ -0,0 +1,83 @@ +package foundation.stack.datamill.db.impl; + +import java.net.URI; +import java.sql.Connection; +import java.sql.SQLException; + +/** + * @author Ravi Chodavarapu (rchodava@gmail.com) + */ +public class H2DatabaseTypeAdapter implements DatabaseTypeAdapter { + @Override + public ConnectionPreparer createConnectionPreparer() { + return new H2ConnectionPreparer(); + } + + @Override + public UrlTransformer createUrlTransformer() { + return new H2UrlTransformer(); + } + + static class H2ConnectionPreparer implements ConnectionPreparer { + @Override + public void prepare(Connection connection) throws SQLException { + connection.prepareStatement("CREATE SCHEMA IF NOT EXISTS \"public\"").execute(); + FunctionsMySQL.register(connection); + } + } + + static class H2UrlTransformer implements UrlTransformer { + private static boolean isH2Url(URI uri) { + String scheme = uri.getScheme(); + if (scheme != null) { + if (scheme.contains("h2")) { + return true; + } else if (scheme.contains("jdbc")) { + String schemeSpecificPart = uri.getSchemeSpecificPart(); + return schemeSpecificPart != null && schemeSpecificPart.startsWith("h2"); + } + } + + return false; + } + + private static boolean isMySqlModeSpecified(String[] options) { + for (String option : options) { + int keySeparator = option.indexOf('='); + if (keySeparator > 0) { + String key = option.substring(0, keySeparator); + if ("mode".equalsIgnoreCase(key.trim())) { + String value = option.substring(keySeparator + 1); + if ("mysql".equalsIgnoreCase(value.trim())) { + return true; + } + } + } + } + + return false; + } + + @Override + public String transform(String uri) { + if (uri != null) { + try { + URI parsed = URI.create(uri); + if (isH2Url(parsed)) { + String schemeSpecificPart = parsed.getSchemeSpecificPart(); + String[] options = schemeSpecificPart.split(";"); + if (options != null) { + if (!isMySqlModeSpecified(options)) { + return uri + ";MODE=MySQL"; + } + } + } + + } catch (IllegalArgumentException e) { + } + } + + return uri; + } + } +} diff --git a/core/src/main/java/foundation/stack/datamill/db/test/TestDatabaseClient.java b/core/src/main/java/foundation/stack/datamill/db/test/TestDatabaseClient.java index f31a937..30c3ff2 100644 --- a/core/src/main/java/foundation/stack/datamill/db/test/TestDatabaseClient.java +++ b/core/src/main/java/foundation/stack/datamill/db/test/TestDatabaseClient.java @@ -1,9 +1,6 @@ package foundation.stack.datamill.db.test; -import foundation.stack.datamill.db.DatabaseClient; -import foundation.stack.datamill.db.ResultBuilder; -import foundation.stack.datamill.db.Row; -import foundation.stack.datamill.db.UpdateQueryExecution; +import foundation.stack.datamill.db.*; import foundation.stack.datamill.db.impl.UnsubscribeOnNextOperator; import rx.Observable; import rx.functions.Func1; @@ -19,7 +16,7 @@ public class TestDatabaseClient extends DatabaseClient { private final Database database; public TestDatabaseClient(Database database) { - super((String) null); + super(DatabaseType.H2, (String) null); this.database = database; } diff --git a/core/src/test/java/foundation/stack/datamill/db/impl/DatabaseClientTest.java b/core/src/test/java/foundation/stack/datamill/db/impl/DatabaseClientTest.java index 5169f2e..523e0eb 100644 --- a/core/src/test/java/foundation/stack/datamill/db/impl/DatabaseClientTest.java +++ b/core/src/test/java/foundation/stack/datamill/db/impl/DatabaseClientTest.java @@ -1,6 +1,7 @@ package foundation.stack.datamill.db.impl; import foundation.stack.datamill.db.DatabaseClient; +import foundation.stack.datamill.db.DatabaseType; import foundation.stack.datamill.reflection.Outline; import foundation.stack.datamill.reflection.OutlineBuilder; import org.junit.Test; @@ -38,7 +39,7 @@ public Quark setSpin(int spin) { @Test public void queries() { - DatabaseClient client = new DatabaseClient("jdbc:hsqldb:mem:test"); + DatabaseClient client = new DatabaseClient(DatabaseType.H2, "jdbc:h2:mem:test"); Outline outline = OutlineBuilder.DEFAULT.build(Quark.class); client.update("create table quarks(name varchar(64), spin integer)", 0) diff --git a/core/src/test/java/foundation/stack/datamill/db/impl/H2UrlTransformerTest.java b/core/src/test/java/foundation/stack/datamill/db/impl/H2UrlTransformerTest.java new file mode 100644 index 0000000..ed6fae9 --- /dev/null +++ b/core/src/test/java/foundation/stack/datamill/db/impl/H2UrlTransformerTest.java @@ -0,0 +1,24 @@ +package foundation.stack.datamill.db.impl; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * @author Ravi Chodavarapu (rchodava@gmail.com) + */ +public class H2UrlTransformerTest { + @Test + public void transform() { + assertEquals("jdbc:mysql://localhost", + new H2DatabaseTypeAdapter.H2UrlTransformer().transform("jdbc:mysql://localhost")); + assertEquals("jdbc:h2:mem:db1;MODE=MySQL", + new H2DatabaseTypeAdapter.H2UrlTransformer().transform("jdbc:h2:mem:db1")); + assertEquals("jdbc:h2:mem:db1;AUTO_SERVER=true;MODE=MySQL", + new H2DatabaseTypeAdapter.H2UrlTransformer().transform("jdbc:h2:mem:db1;AUTO_SERVER=true")); + assertEquals("jdbc:h2:mem:db1;MODE=MySQL", + new H2DatabaseTypeAdapter.H2UrlTransformer().transform("jdbc:h2:mem:db1;MODE=MySQL")); + assertEquals("jdbc:h2:mem:db1;MODE=MySQL;AUTO_SERVER=true", + new H2DatabaseTypeAdapter.H2UrlTransformer().transform("jdbc:h2:mem:db1;MODE=MySQL;AUTO_SERVER=true")); + } +} diff --git a/parent/pom.xml b/parent/pom.xml index f89d91e..7c57539 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -194,9 +194,9 @@ 2.7.3 - org.hsqldb - hsqldb - 2.3.3 + com.h2database + h2 + 1.4.194 test From 2e289b7c61398cb92f714ba0d72bcb1eebbf9916 Mon Sep 17 00:00:00 2001 From: Ravi Chodavarapu Date: Fri, 5 May 2017 14:24:46 +0100 Subject: [PATCH 2/3] Auto-guess database type based on URL Fix cucumber tests --- .../stack/datamill/db/DatabaseClient.java | 13 ++++++- .../stack/datamill/db/DatabaseType.java | 38 ++++++++++++++++++- .../db/impl/H2DatabaseTypeAdapter.java | 25 +++--------- .../db/{impl => }/DatabaseClientTest.java | 4 +- .../stack/datamill/db/DatabaseTypeTest.java | 17 +++++++++ cucumber/pom.xml | 4 +- .../datamill/cucumber/DatabaseStepsTest.java | 17 +++++---- 7 files changed, 84 insertions(+), 34 deletions(-) rename core/src/test/java/foundation/stack/datamill/db/{impl => }/DatabaseClientTest.java (94%) create mode 100644 core/src/test/java/foundation/stack/datamill/db/DatabaseTypeTest.java diff --git a/core/src/main/java/foundation/stack/datamill/db/DatabaseClient.java b/core/src/main/java/foundation/stack/datamill/db/DatabaseClient.java index d056a90..fc81d28 100644 --- a/core/src/main/java/foundation/stack/datamill/db/DatabaseClient.java +++ b/core/src/main/java/foundation/stack/datamill/db/DatabaseClient.java @@ -45,10 +45,14 @@ public DatabaseClient(DatabaseType type, DataSource dataSource) { this.dataSource = dataSource; } - public DatabaseClient(DatabaseType type, String url) { + public DatabaseClient(DatabaseType type, @Named("url") String url) { this(type, url, null, null); } + public DatabaseClient(@Named("url") String url) { + this(DatabaseType.guess(url), url); + } + public DatabaseClient( DatabaseType type, @Named("url") String url, @@ -64,6 +68,13 @@ public DatabaseClient( this.dataSource = dataSource; } + public DatabaseClient( + @Named("url") String url, + @Named("username") String username, + @Named("password") String password) { + this(DatabaseType.guess(url), url, username, password); + } + private void setupConnectionProvider() { connectionProvider = new DelegatingConnectionProvider(new ConnectionProviderFromDataSource(dataSource)); database = Database.from(connectionProvider); diff --git a/core/src/main/java/foundation/stack/datamill/db/DatabaseType.java b/core/src/main/java/foundation/stack/datamill/db/DatabaseType.java index 5507e08..522b42e 100644 --- a/core/src/main/java/foundation/stack/datamill/db/DatabaseType.java +++ b/core/src/main/java/foundation/stack/datamill/db/DatabaseType.java @@ -1,9 +1,45 @@ package foundation.stack.datamill.db; +import java.net.URI; + /** * @author Ravi Chodavarapu (rchodava@gmail.com) */ public enum DatabaseType { MYSQL, - H2 + H2; + + private static boolean isH2Url(URI uri) { + String scheme = uri.getScheme(); + if (scheme != null) { + if (scheme.contains("h2")) { + return true; + } else if (scheme.contains("jdbc")) { + String schemeSpecificPart = uri.getSchemeSpecificPart(); + return schemeSpecificPart != null && schemeSpecificPart.startsWith("h2"); + } + } + + return false; + } + + private static boolean isH2Url(String uri) { + if (uri != null) { + try { + URI parsed = URI.create(uri); + return isH2Url(parsed); + } catch (IllegalArgumentException e) { + } + } + + return false; + } + + public static DatabaseType guess(String uri) { + if (isH2Url(uri)) { + return H2; + } + + return MYSQL; + } } diff --git a/core/src/main/java/foundation/stack/datamill/db/impl/H2DatabaseTypeAdapter.java b/core/src/main/java/foundation/stack/datamill/db/impl/H2DatabaseTypeAdapter.java index e2a52df..114aca7 100644 --- a/core/src/main/java/foundation/stack/datamill/db/impl/H2DatabaseTypeAdapter.java +++ b/core/src/main/java/foundation/stack/datamill/db/impl/H2DatabaseTypeAdapter.java @@ -1,5 +1,7 @@ package foundation.stack.datamill.db.impl; +import foundation.stack.datamill.db.DatabaseType; + import java.net.URI; import java.sql.Connection; import java.sql.SQLException; @@ -27,20 +29,6 @@ public void prepare(Connection connection) throws SQLException { } static class H2UrlTransformer implements UrlTransformer { - private static boolean isH2Url(URI uri) { - String scheme = uri.getScheme(); - if (scheme != null) { - if (scheme.contains("h2")) { - return true; - } else if (scheme.contains("jdbc")) { - String schemeSpecificPart = uri.getSchemeSpecificPart(); - return schemeSpecificPart != null && schemeSpecificPart.startsWith("h2"); - } - } - - return false; - } - private static boolean isMySqlModeSpecified(String[] options) { for (String option : options) { int keySeparator = option.indexOf('='); @@ -61,9 +49,9 @@ private static boolean isMySqlModeSpecified(String[] options) { @Override public String transform(String uri) { if (uri != null) { - try { - URI parsed = URI.create(uri); - if (isH2Url(parsed)) { + if (DatabaseType.guess(uri) == DatabaseType.H2) { + try { + URI parsed = URI.create(uri); String schemeSpecificPart = parsed.getSchemeSpecificPart(); String[] options = schemeSpecificPart.split(";"); if (options != null) { @@ -71,9 +59,8 @@ public String transform(String uri) { return uri + ";MODE=MySQL"; } } + } catch (IllegalArgumentException e) { } - - } catch (IllegalArgumentException e) { } } diff --git a/core/src/test/java/foundation/stack/datamill/db/impl/DatabaseClientTest.java b/core/src/test/java/foundation/stack/datamill/db/DatabaseClientTest.java similarity index 94% rename from core/src/test/java/foundation/stack/datamill/db/impl/DatabaseClientTest.java rename to core/src/test/java/foundation/stack/datamill/db/DatabaseClientTest.java index 523e0eb..066b9c3 100644 --- a/core/src/test/java/foundation/stack/datamill/db/impl/DatabaseClientTest.java +++ b/core/src/test/java/foundation/stack/datamill/db/DatabaseClientTest.java @@ -1,7 +1,5 @@ -package foundation.stack.datamill.db.impl; +package foundation.stack.datamill.db; -import foundation.stack.datamill.db.DatabaseClient; -import foundation.stack.datamill.db.DatabaseType; import foundation.stack.datamill.reflection.Outline; import foundation.stack.datamill.reflection.OutlineBuilder; import org.junit.Test; diff --git a/core/src/test/java/foundation/stack/datamill/db/DatabaseTypeTest.java b/core/src/test/java/foundation/stack/datamill/db/DatabaseTypeTest.java new file mode 100644 index 0000000..af8af81 --- /dev/null +++ b/core/src/test/java/foundation/stack/datamill/db/DatabaseTypeTest.java @@ -0,0 +1,17 @@ +package foundation.stack.datamill.db; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * @author Ravi Chodavarapu (rchodava@gmail.com) + */ +public class DatabaseTypeTest { + @Test + public void guess() { + assertEquals(DatabaseType.H2, DatabaseType.guess("jdbc:h2:mem")); + assertEquals(DatabaseType.H2, DatabaseType.guess("jdbc:h2:tcp://localhost/~/test")); + assertEquals(DatabaseType.MYSQL, DatabaseType.guess("jdbc:mysql:tcp://localhost:3306")); + } +} diff --git a/cucumber/pom.xml b/cucumber/pom.xml index a6b065e..57381ff 100644 --- a/cucumber/pom.xml +++ b/cucumber/pom.xml @@ -47,8 +47,8 @@ subethasmtp - org.hsqldb - hsqldb + com.h2database + h2 test diff --git a/cucumber/src/test/java/foundation/stack/datamill/cucumber/DatabaseStepsTest.java b/cucumber/src/test/java/foundation/stack/datamill/cucumber/DatabaseStepsTest.java index 89eb816..cf2e2ab 100644 --- a/cucumber/src/test/java/foundation/stack/datamill/cucumber/DatabaseStepsTest.java +++ b/cucumber/src/test/java/foundation/stack/datamill/cucumber/DatabaseStepsTest.java @@ -1,6 +1,7 @@ package foundation.stack.datamill.cucumber; import foundation.stack.datamill.db.DatabaseClient; +import foundation.stack.datamill.db.DatabaseType; import org.junit.Test; /** @@ -10,7 +11,7 @@ public class DatabaseStepsTest { @Test public void storeAndCheckRows() throws Exception { PropertyStore store = new PropertyStore(); - store.put("db", "jdbc:hsqldb:mem:test"); + store.put("db", "jdbc:h2:mem:test"); store.put("quark", "charm"); store.put("quark2", "strange"); store.put("spin", "50"); @@ -18,21 +19,21 @@ public void storeAndCheckRows() throws Exception { PlaceholderResolver resolver = new PlaceholderResolver(store); DatabaseSteps steps = new DatabaseSteps(resolver); - new DatabaseClient("jdbc:hsqldb:mem:test") + new DatabaseClient(DatabaseType.H2, "jdbc:h2:mem:test") .update("create table quarks(name varchar(64), spin integer)", 0) .count() .toBlocking() .last(); - steps.storeDatabaseRow("quarks", "jdbc:hsqldb:mem:test", "{\"name\":\"up\",\"spin\":100}"); - steps.checkDatabaseRowExists("quarks", "jdbc:hsqldb:mem:test", "name = 'up'"); + steps.storeDatabaseRow("quarks", "jdbc:h2:mem:test", "{\"name\":\"up\",\"spin\":100}"); + steps.checkDatabaseRowExists("quarks", "jdbc:h2:mem:test", "name = 'up'"); - steps.storeDatabaseRow("quarks", "jdbc:hsqldb:mem:test", "{\"name\": \"down\",\"spin\":null}"); - steps.checkDatabaseRowExists("quarks", "jdbc:hsqldb:mem:test", "name = 'down' and spin is null"); - steps.checkDatabaseRowExists("quarks", "jdbc:hsqldb:mem:test", "name = 'down'", "spin is null"); + steps.storeDatabaseRow("quarks", "jdbc:h2:mem:test", "{\"name\": \"down\",\"spin\":null}"); + steps.checkDatabaseRowExists("quarks", "jdbc:h2:mem:test", "name = 'down' and spin is null"); + steps.checkDatabaseRowExists("quarks", "jdbc:h2:mem:test", "name = 'down'", "spin is null"); steps.storeDatabaseRow("quarks", "{db}", "{\"name\":\"{quark}\",\"spin\":100}"); - steps.checkDatabaseRowExists("quarks", "jdbc:hsqldb:mem:test", "name = 'charm' and spin = 100"); + steps.checkDatabaseRowExists("quarks", "jdbc:h2:mem:test", "name = 'charm' and spin = 100"); steps.storeDatabaseRow("quarks", "{db}", "{\"name\":\"strange\",\"spin\":50}"); steps.checkDatabaseRowExists("quarks", "{db}", "name = '{quark2}' and spin = 50"); From 715f34bf163dd7ca93c0f7a95815998489c6a092 Mon Sep 17 00:00:00 2001 From: Ravi Chodavarapu Date: Fri, 5 May 2017 16:26:37 +0100 Subject: [PATCH 3/3] Add a few additional tests for db client --- .../stack/datamill/db/DatabaseClientTest.java | 39 +++++++++++++++++++ .../resources/db/migration/V0_1__Init.sql | 0 2 files changed, 39 insertions(+) create mode 100644 core/src/test/resources/db/migration/V0_1__Init.sql diff --git a/core/src/test/java/foundation/stack/datamill/db/DatabaseClientTest.java b/core/src/test/java/foundation/stack/datamill/db/DatabaseClientTest.java index 066b9c3..e09a415 100644 --- a/core/src/test/java/foundation/stack/datamill/db/DatabaseClientTest.java +++ b/core/src/test/java/foundation/stack/datamill/db/DatabaseClientTest.java @@ -4,9 +4,11 @@ import foundation.stack.datamill.reflection.OutlineBuilder; import org.junit.Test; +import java.sql.SQLException; import java.util.List; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; /** * @author Ravi Chodavarapu (rchodava@gmail.com) @@ -69,8 +71,45 @@ public void queries() { .get()) .toBlocking().last(); + assertEquals("up", quark.getName()); + assertEquals(1, quark.getSpin()); + } + + @Test + public void migration() { + DatabaseClient client = new DatabaseClient(DatabaseType.H2, "jdbc:h2:mem:test2"); + client.migrate(connection -> { + try { + connection.prepareStatement("create table quarks(name varchar(64), spin integer)").execute(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + }); + + Outline outline = OutlineBuilder.DEFAULT.build(Quark.class); + client.insertInto(outline).row(rb -> rb + .put(outline.member(m -> m.getName()), "up") + .put(outline.member(m -> m.getSpin()), 1) + .build()) + .count() + .toBlocking() + .last(); + + List quarks = client.selectAll().from(outline).all().getAs(r -> outline.wrap(new Quark()) + .set(p -> p.getName(), r.get(outline.member(m -> m.getName()))) + .set(p -> p.getSpin(), r.get(outline.member(m -> m.getSpin()))) + .get()) + .toBlocking().last(); + assertEquals(1, quarks.size()); assertEquals("up", quarks.get(0).getName()); assertEquals(1, quarks.get(0).getSpin()); } + + @Test + public void utilities() { + DatabaseClient client = new DatabaseClient(DatabaseType.H2, "jdbc:h2:mem:test"); + assertEquals("jdbc:h2:mem:test", client.getURL()); + assertTrue(client.getVersion().contains("H2")); + } } diff --git a/core/src/test/resources/db/migration/V0_1__Init.sql b/core/src/test/resources/db/migration/V0_1__Init.sql new file mode 100644 index 0000000..e69de29