From abfafb922303090b2461656342944c15939e47e6 Mon Sep 17 00:00:00 2001 From: terma Date: Fri, 9 Jun 2017 12:37:44 -0400 Subject: [PATCH] Use H2 DB and add ability to customize DB driver etc. --- README.md | 10 ++ pom.xml | 16 ++- .../com/github/terma/sqlonjson/SqlOnJson.java | 105 ++++++++++++------ .../github/terma/sqlonjson/SqlOnJsonPerf.java | 4 +- .../github/terma/sqlonjson/SqlOnJsonTest.java | 53 ++++++--- 5 files changed, 130 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index 6fac50b..c732085 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,13 @@ [![Build Status](https://travis-ci.org/terma/sql-on-json.svg?branch=master)](https://travis-ci.org/terma/sql-on-json) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.terma/sql-on-json/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.terma/sql-on-json/) +## How to use + +```java +try (Connection c = sqlOnJson.convertPlain("{a:[{id:12000,name:\"super\"},{id:90,name:\"remta\"}]}")) { + ResultSet rs = c.prepareStatement("select * from a").executeQuery(); + while (rs.next()) { + // my business logic + } +} +``` \ No newline at end of file diff --git a/pom.xml b/pom.xml index 52c77f2..f1207ff 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 com.github.terma @@ -95,9 +96,9 @@ - org.hsqldb - hsqldb - 2.3.5 + com.h2database + h2 + 1.4.195 @@ -112,6 +113,13 @@ 3.5 + + org.hsqldb + hsqldb + 2.3.5 + test + + junit junit diff --git a/src/main/java/com/github/terma/sqlonjson/SqlOnJson.java b/src/main/java/com/github/terma/sqlonjson/SqlOnJson.java index 8b0935a..2312865 100644 --- a/src/main/java/com/github/terma/sqlonjson/SqlOnJson.java +++ b/src/main/java/com/github/terma/sqlonjson/SqlOnJson.java @@ -34,7 +34,7 @@ /** * Convert JSON to in memory SQL database with tables created from JSON. *

- * Create in memory DB {@link org.hsqldb.jdbc.JDBCDriver} which will be destroyed as soon as + * Create in memory DB which will be destroyed as soon as * connection will be closed. *

* Thread safe. Each call will create new independent SQL DB. @@ -44,10 +44,69 @@ @SuppressWarnings("WeakerAccess") public class SqlOnJson { - private static final AtomicInteger COUNTER = new AtomicInteger(); - private static final String DRIVER_CLASS = "org.hsqldb.jdbc.JDBCDriver"; + public static final String INSTANCE_ID_PLACEHOLDER = ""; + + public static final String DEFAULT_DRIVER = "org.h2.Driver"; + public static final String DEFAULT_URL = "jdbc:h2:mem:"; + public static final String DEFAULT_USERNAME = ""; + public static final String DEFAULT_PASSWORD = ""; + private static final Logger LOGGER = Logger.getLogger(SqlOnJson.class.getName()); + private final AtomicInteger counter; + private final String driver; + private final String url; + private final String username; + private final String password; + + /** + * @param driver DB driver class which implement JDBC interface + * @param url regular JDBC URL string with optional {@link SqlOnJson#INSTANCE_ID_PLACEHOLDER} placeholder which will be + * replaced on instance ID during conversion to make sure that all instances are unique + */ + public SqlOnJson(String driver, String url, String username, String password) { + this.counter = new AtomicInteger(); + this.driver = driver; + this.url = url; + this.username = username; + this.password = password; + } + + /** + * With default DB H2 in in-memory private mode (DB life until connection will be closed) + */ + public SqlOnJson() { + this(DEFAULT_DRIVER, DEFAULT_URL, DEFAULT_USERNAME, DEFAULT_PASSWORD); + } + + private static LinkedHashMap getColumns(JsonTable jsonTable) { + final LinkedHashMap cls = new LinkedHashMap<>(); + + for (int i = 0; i < jsonTable.data.size(); i++) { + final JsonObject jsonObject = jsonTable.data.get(i).getAsJsonObject(); + for (Map.Entry part : jsonObject.entrySet()) { + ColumnType columnType = ColumnType.STRING; + if (part.getValue().isJsonPrimitive()) { + if (part.getValue().getAsString().matches("[-0-9]+")) { + columnType = ColumnType.BIGINT; + } else if (part.getValue().getAsString().matches("[-0-9.]+")) { + columnType = ColumnType.DOUBLE; + } + } + + cls.put(part.getKey(), columnType); + } + } + return cls; + } + + private static String nameToSqlName(final String columnName) { + String first = columnName.substring(0, 1); + if (first.matches("[^a-zA-Z]")) first = "i"; + + return first + columnName.substring(1).replaceAll("[^a-zA-Z0-9_]+", ""); + } + /** * Convert JSON to SQL and assume that root of JSON is Object properties * for which with array type could be converted to tables {@link Plain} @@ -57,17 +116,17 @@ public class SqlOnJson { * @throws SQLException * @throws ClassNotFoundException */ - public static Connection convertPlain(String json) throws SQLException, ClassNotFoundException { + public Connection convertPlain(String json) throws SQLException, ClassNotFoundException { return convert(new Plain(json)); } - public static Connection convert(JsonIterator jsonIterator) throws SQLException, ClassNotFoundException { - Class.forName(DRIVER_CLASS); + public Connection convert(JsonIterator jsonIterator) throws SQLException, ClassNotFoundException { + Class.forName(driver); - if (COUNTER.get() > Integer.MAX_VALUE - 10) COUNTER.set(0); // to avoid possible overflow, who knows =) - final int id = COUNTER.incrementAndGet(); + if (counter.get() > Integer.MAX_VALUE - 10) counter.set(0); // to avoid possible overflow, who knows =) + final int id = counter.incrementAndGet(); - final Connection c = DriverManager.getConnection("jdbc:hsqldb:mem:sql_on_json_" + id + ";shutdown=true", "SA", ""); + final Connection c = DriverManager.getConnection(url.replaceAll(INSTANCE_ID_PLACEHOLDER, String.valueOf(id)), username, password); try { final long start = System.currentTimeMillis(); @@ -120,32 +179,4 @@ public static Connection convert(JsonIterator jsonIterator) throws SQLException, return c; } - private static LinkedHashMap getColumns(JsonTable jsonTable) { - final LinkedHashMap cls = new LinkedHashMap<>(); - - for (int i = 0; i < jsonTable.data.size(); i++) { - final JsonObject jsonObject = jsonTable.data.get(i).getAsJsonObject(); - for (Map.Entry part : jsonObject.entrySet()) { - ColumnType columnType = ColumnType.STRING; - if (part.getValue().isJsonPrimitive()) { - if (part.getValue().getAsString().matches("[-0-9]+")) { - columnType = ColumnType.BIGINT; - } else if (part.getValue().getAsString().matches("[-0-9.]+")) { - columnType = ColumnType.DOUBLE; - } - } - - cls.put(part.getKey(), columnType); - } - } - return cls; - } - - private static String nameToSqlName(final String columnName) { - String first = columnName.substring(0, 1); - if (first.matches("[^a-zA-Z]")) first = "i"; - - return first + columnName.substring(1).replaceAll("[^a-zA-Z0-9_]+", ""); - } - } diff --git a/src/test/java/com/github/terma/sqlonjson/SqlOnJsonPerf.java b/src/test/java/com/github/terma/sqlonjson/SqlOnJsonPerf.java index b26e72e..56720f7 100644 --- a/src/test/java/com/github/terma/sqlonjson/SqlOnJsonPerf.java +++ b/src/test/java/com/github/terma/sqlonjson/SqlOnJsonPerf.java @@ -49,8 +49,8 @@ private static StringBuilder generateJson() { public void test() throws SQLException, ClassNotFoundException { String json = generateJson().toString(); - try (Connection c = SqlOnJson.convertPlain(json)) { - try (PreparedStatement ps = c.prepareStatement("select * as count_of_rows from sources order by id desc limit 5")) { + try (Connection c = new SqlOnJson().convertPlain(json)) { + try (PreparedStatement ps = c.prepareStatement("select as count_of_rows from sources order by id desc limit 5")) { try (ResultSet rs = ps.executeQuery()) { printResultSet(rs); } diff --git a/src/test/java/com/github/terma/sqlonjson/SqlOnJsonTest.java b/src/test/java/com/github/terma/sqlonjson/SqlOnJsonTest.java index b6e946b..87f9486 100644 --- a/src/test/java/com/github/terma/sqlonjson/SqlOnJsonTest.java +++ b/src/test/java/com/github/terma/sqlonjson/SqlOnJsonTest.java @@ -26,9 +26,11 @@ @SuppressWarnings("SqlNoDataSourceInspection") public class SqlOnJsonTest { + private final SqlOnJson sqlOnJson = new SqlOnJson(); + @Test public void representEmptyJsonAsEmptyDb() throws Exception { - try (Connection c = SqlOnJson.convertPlain("")) { + try (Connection c = sqlOnJson.convertPlain("")) { ResultSet rs = c.getMetaData().getTables(null, null, "a", null); Assert.assertFalse(rs.next()); } @@ -36,7 +38,7 @@ public void representEmptyJsonAsEmptyDb() throws Exception { @Test public void representObjectWithEmptyArrayPropertyAsEmptyDb() throws Exception { - try (Connection c = SqlOnJson.convertPlain("{a:[]}")) { + try (Connection c = sqlOnJson.convertPlain("{a:[]}")) { ResultSet rs = c.getMetaData().getTables(null, null, "a", null); Assert.assertFalse(rs.next()); } @@ -44,7 +46,7 @@ public void representObjectWithEmptyArrayPropertyAsEmptyDb() throws Exception { @Test public void representObjectWithArrayPropertyAsTableInDb() throws Exception { - try (Connection c = SqlOnJson.convertPlain("{a:[{id:12000,name:\"super\"},{id:90,name:\"remta\"}]}")) { + try (Connection c = sqlOnJson.convertPlain("{a:[{id:12000,name:\"super\"},{id:90,name:\"remta\"}]}")) { ResultSet rs = c.prepareStatement("select * from a").executeQuery(); rs.next(); Assert.assertEquals(12000, rs.getLong("id")); @@ -57,7 +59,7 @@ public void representObjectWithArrayPropertyAsTableInDb() throws Exception { @Test public void ignoreNonAlphaNumberAndNonAlphaFirstCharacters() throws Exception { - try (Connection c = SqlOnJson.convertPlain("{\"_AmO_(Nit)\":[{\"_i-d,()rumbA\":12000,_12:90}]}")) { + try (Connection c = sqlOnJson.convertPlain("{\"_AmO_(Nit)\":[{\"_i-d,()rumbA\":12000,_12:90}]}")) { ResultSet rs = c.prepareStatement("select * from iamo_nit").executeQuery(); rs.next(); Assert.assertEquals(12000, rs.getLong("iidrumba")); @@ -68,7 +70,7 @@ public void ignoreNonAlphaNumberAndNonAlphaFirstCharacters() throws Exception { @Test public void support8kOfCharactersForStringFields() throws Exception { String string8k = StringUtils.repeat('z', 8 * 1000); - try (Connection c = SqlOnJson.convertPlain("{longs:[{str:\"" + string8k + "\"}]}")) { + try (Connection c = sqlOnJson.convertPlain("{longs:[{str:\"" + string8k + "\"}]}")) { ResultSet rs = c.prepareStatement("select * from longs").executeQuery(); rs.next(); Assert.assertEquals(string8k, rs.getString("str")); @@ -77,7 +79,7 @@ public void support8kOfCharactersForStringFields() throws Exception { @Test public void supportCaseWhenNonFirstObjectHasMoreProperties() throws Exception { - try (Connection c = SqlOnJson.convertPlain("{nosql:[{id:12},{id:15,mid:90}]}")) { + try (Connection c = sqlOnJson.convertPlain("{nosql:[{id:12},{id:15,mid:90}]}")) { ResultSet rs = c.prepareStatement("select * from nosql").executeQuery(); rs.next(); Assert.assertEquals(12, rs.getLong("id")); @@ -90,8 +92,8 @@ public void supportCaseWhenNonFirstObjectHasMoreProperties() throws Exception { @Test public void supportParallelWorkWithTwoDb() throws Exception { try ( - Connection c1 = SqlOnJson.convertPlain("{nosql:[{id:12},{id:15,mid:90}]}"); - Connection c2 = SqlOnJson.convertPlain("{nosql:[{a:1}]}"); + Connection c1 = sqlOnJson.convertPlain("{nosql:[{id:12},{id:15,mid:90}]}"); + Connection c2 = sqlOnJson.convertPlain("{nosql:[{a:1}]}"); ) { ResultSet rs1 = c1.prepareStatement("select * from nosql").executeQuery(); Assert.assertTrue(rs1.next()); @@ -102,7 +104,7 @@ public void supportParallelWorkWithTwoDb() throws Exception { @Test public void representNumberPropertyAsLong() throws Exception { - try (Connection c = SqlOnJson.convertPlain("{a:[{o:" + Long.MAX_VALUE + "},{o:" + Long.MIN_VALUE + "}]}")) { + try (Connection c = sqlOnJson.convertPlain("{a:[{o:" + Long.MAX_VALUE + "},{o:" + Long.MIN_VALUE + "}]}")) { ResultSet rs = c.prepareStatement("select * from a").executeQuery(); Assert.assertEquals("BIGINT", rs.getMetaData().getColumnTypeName(1)); rs.next(); @@ -114,7 +116,7 @@ public void representNumberPropertyAsLong() throws Exception { @Test public void representNumberWithPrecisionPropertyAsDouble() throws Exception { - try (Connection c = SqlOnJson.convertPlain("{a:[{o:0.009},{o:-12.45}]}")) { + try (Connection c = sqlOnJson.convertPlain("{a:[{o:0.009},{o:-12.45}]}")) { ResultSet rs = c.prepareStatement("select * from a").executeQuery(); Assert.assertEquals("DOUBLE", rs.getMetaData().getColumnTypeName(1)); rs.next(); @@ -126,7 +128,7 @@ public void representNumberWithPrecisionPropertyAsDouble() throws Exception { @Test public void representObjectWithArrayPropertyWithMissedAttributesAsTableInDbWithNull() throws Exception { - try (Connection c = SqlOnJson.convertPlain("{a:[{id:12000,name:\"super\"},{id:90}]}")) { + try (Connection c = sqlOnJson.convertPlain("{a:[{id:12000,name:\"super\"},{id:90}]}")) { ResultSet rs = c.prepareStatement("select * from a").executeQuery(); rs.next(); Assert.assertEquals(12000, rs.getLong("id")); @@ -139,7 +141,7 @@ public void representObjectWithArrayPropertyWithMissedAttributesAsTableInDbWithN @Test public void representObjectWithArrayPropertiesAsMultipleTables() throws Exception { - try (Connection c = SqlOnJson.convertPlain("{orders:[{id:12}],history:[{orderId:12}]}")) { + try (Connection c = sqlOnJson.convertPlain("{orders:[{id:12}],history:[{orderId:12}]}")) { ResultSet rs1 = c.prepareStatement("select * from orders").executeQuery(); rs1.next(); Assert.assertEquals(12, rs1.getLong("id")); @@ -152,7 +154,7 @@ public void representObjectWithArrayPropertiesAsMultipleTables() throws Exceptio @Test public void representEmbeddedObjectAsString() throws Exception { - try (Connection c = SqlOnJson.convertPlain("{orders:[{em:{a:12}}]}")) { + try (Connection c = sqlOnJson.convertPlain("{orders:[{em:{a:12}}]}")) { ResultSet rs1 = c.prepareStatement("select * from orders").executeQuery(); rs1.next(); Assert.assertEquals("{\"a\":12}", rs1.getString("em")); @@ -162,7 +164,7 @@ public void representEmbeddedObjectAsString() throws Exception { @Test public void supportEmbeddedArrayToTable() throws Exception { - try (Connection c = SqlOnJson.convertPlain("{orders:[{em:[{a:-7}]}]}")) { + try (Connection c = sqlOnJson.convertPlain("{orders:[{em:[{a:-7}]}]}")) { ResultSet rs1 = c.prepareStatement("select * from orders").executeQuery(); rs1.next(); Assert.assertEquals("[{\"a\":-7}]", rs1.getString("em")); @@ -172,10 +174,31 @@ public void supportEmbeddedArrayToTable() throws Exception { @Test public void supportEmbeddedObjectAsTable() throws Exception { - try (Connection c = SqlOnJson.convertPlain("{orders:{em:[{a:-7}]}}")) { + try (Connection c = sqlOnJson.convertPlain("{orders:{em:[{a:-7}]}}")) { ResultSet rs1 = c.getMetaData().getTables(null, null, "orders", null); Assert.assertFalse(rs1.next()); } } + @Test + public void supportRenamingOfColumnsForDefaultDb() throws Exception { + try (Connection c = sqlOnJson.convertPlain("{orders:[{user_id:12,id:900}],users:[{id:12}]}")) { + ResultSet rs1 = c.prepareStatement("select o.id as oid, u.id as uid from orders o left join users u on user_id = u.id").executeQuery(); + rs1.next(); + Assert.assertEquals("12", rs1.getString("uid")); + Assert.assertEquals("900", rs1.getString("oid")); + } + } + + @Test + public void supportCustomDb() throws Exception { + try (Connection c = new SqlOnJson("org.hsqldb.jdbc.JDBCDriver", "jdbc:hsqldb:mem:sql_on_json_test;shutdown=true", "sa", "") + .convertPlain("{orders:[{user_id:13,id:900}],users:[{id:13}]}")) { + ResultSet rs1 = c.prepareStatement("select o.id as oid, u.id as uid from orders o left join users u on user_id = u.id").executeQuery(); + rs1.next(); + Assert.assertEquals("13", rs1.getString("uid")); + Assert.assertEquals("900", rs1.getString("oid")); + } + } + }