Skip to content

Commit

Permalink
Use H2 DB and add ability to customize DB driver etc.
Browse files Browse the repository at this point in the history
  • Loading branch information
terma committed Jun 9, 2017
1 parent 84e2442 commit abfafb9
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 58 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
```
16 changes: 12 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.github.terma</groupId>
Expand Down Expand Up @@ -95,9 +96,9 @@

<dependencies>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.3.5</version>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.195</version>
</dependency>

<dependency>
Expand All @@ -112,6 +113,13 @@
<version>3.5</version>
</dependency>

<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.3.5</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand Down
105 changes: 68 additions & 37 deletions src/main/java/com/github/terma/sqlonjson/SqlOnJson.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
/**
* Convert JSON to in memory SQL database with tables created from JSON.
* <p>
* 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.
* <p>
* Thread safe. Each call will create new independent SQL DB.
Expand All @@ -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 = "<INSTANCE_ID>";

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<String, ColumnType> getColumns(JsonTable jsonTable) {
final LinkedHashMap<String, ColumnType> cls = new LinkedHashMap<>();

for (int i = 0; i < jsonTable.data.size(); i++) {
final JsonObject jsonObject = jsonTable.data.get(i).getAsJsonObject();
for (Map.Entry<String, JsonElement> 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}
Expand All @@ -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();

Expand Down Expand Up @@ -120,32 +179,4 @@ public static Connection convert(JsonIterator jsonIterator) throws SQLException,
return c;
}

private static LinkedHashMap<String, ColumnType> getColumns(JsonTable jsonTable) {
final LinkedHashMap<String, ColumnType> cls = new LinkedHashMap<>();

for (int i = 0; i < jsonTable.data.size(); i++) {
final JsonObject jsonObject = jsonTable.data.get(i).getAsJsonObject();
for (Map.Entry<String, JsonElement> 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_]+", "");
}

}
4 changes: 2 additions & 2 deletions src/test/java/com/github/terma/sqlonjson/SqlOnJsonPerf.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
53 changes: 38 additions & 15 deletions src/test/java/com/github/terma/sqlonjson/SqlOnJsonTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,27 @@
@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());
}
}

@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());
}
}

@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"));
Expand All @@ -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"));
Expand All @@ -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"));
Expand All @@ -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"));
Expand All @@ -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());
Expand All @@ -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();
Expand All @@ -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();
Expand All @@ -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"));
Expand All @@ -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"));
Expand All @@ -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"));
Expand All @@ -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"));
Expand All @@ -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"));
}
}

}

0 comments on commit abfafb9

Please sign in to comment.