From c35c41bf3cb3d56e999dfbe69398c55df2c2456d Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Fri, 14 Feb 2025 23:42:24 -0500 Subject: [PATCH] WIP on SQLite implementation --- .idea/.gitignore | 8 ++ .idea/compiler.xml | 14 +++ .idea/encodings.xml | 9 ++ .idea/jarRepositories.xml | 25 +++++ .idea/kotlinc.xml | 6 ++ .idea/libraries/KotlinJavaRuntime.xml | 23 +++++ .idea/misc.xml | 15 +++ .idea/modules.xml | 8 ++ .idea/solutions.bitbadger.documents.iml | 10 ++ .idea/vcs.xml | 6 ++ src/sqlite/pom.xml | 99 +++++++++++++++++++ src/sqlite/src/main/kotlin/Configuration.kt | 26 +++++ src/sqlite/src/main/kotlin/Query.kt | 102 ++++++++++++++++++++ 13 files changed, 351 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/compiler.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/kotlinc.xml create mode 100644 .idea/libraries/KotlinJavaRuntime.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/solutions.bitbadger.documents.iml create mode 100644 .idea/vcs.xml create mode 100644 src/sqlite/pom.xml create mode 100644 src/sqlite/src/main/kotlin/Configuration.kt create mode 100644 src/sqlite/src/main/kotlin/Query.kt diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..0cf8482 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..e98b3a0 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..4d27ef0 --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..c22b6fa --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/libraries/KotlinJavaRuntime.xml b/.idea/libraries/KotlinJavaRuntime.xml new file mode 100644 index 0000000..cf8a559 --- /dev/null +++ b/.idea/libraries/KotlinJavaRuntime.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..161ad45 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..cf07591 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/solutions.bitbadger.documents.iml b/.idea/solutions.bitbadger.documents.iml new file mode 100644 index 0000000..6ec89fa --- /dev/null +++ b/.idea/solutions.bitbadger.documents.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/sqlite/pom.xml b/src/sqlite/pom.xml new file mode 100644 index 0000000..4b69624 --- /dev/null +++ b/src/sqlite/pom.xml @@ -0,0 +1,99 @@ + + + 4.0.0 + + solutions.bitbadger.documents + sqlite + 4.0-ALPHA + + + UTF-8 + official + 1.8 + + + + + mavenCentral + https://repo1.maven.org/maven2/ + + + + + src/main/kotlin + src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + 2.1.10 + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + + + maven-surefire-plugin + 2.22.2 + + + maven-failsafe-plugin + 2.22.2 + + + org.codehaus.mojo + exec-maven-plugin + 1.6.0 + + MainKt + + + + + + + + org.jetbrains.kotlin + kotlin-test-junit5 + 2.1.10 + test + + + org.junit.jupiter + junit-jupiter + 5.10.0 + test + + + org.jetbrains.kotlin + kotlin-stdlib + 2.1.10 + + + org.xerial + sqlite-jdbc + 3.46.1.2 + + + solutions.bitbadger.documents + common + 4.0-ALPHA + compile + + + + \ No newline at end of file diff --git a/src/sqlite/src/main/kotlin/Configuration.kt b/src/sqlite/src/main/kotlin/Configuration.kt new file mode 100644 index 0000000..b944d0d --- /dev/null +++ b/src/sqlite/src/main/kotlin/Configuration.kt @@ -0,0 +1,26 @@ +package solutions.bitbadger.documents.sqlite + +import java.sql.Connection +import java.sql.DriverManager + +/** + * Configuration for SQLite + */ +object Configuration { + + /** The connection string for the SQLite database */ + var connectionString: String? = null + + /** + * Retrieve a new connection to the SQLite database + * + * @return A new connection to the SQLite database + * @throws IllegalArgumentException If the connection string is not set before calling this + */ + fun dbConn(): Connection { + if (connectionString == null) { + throw IllegalArgumentException("Please provide a connection string before attempting data access") + } + return DriverManager.getConnection(connectionString) + } +} diff --git a/src/sqlite/src/main/kotlin/Query.kt b/src/sqlite/src/main/kotlin/Query.kt new file mode 100644 index 0000000..18863df --- /dev/null +++ b/src/sqlite/src/main/kotlin/Query.kt @@ -0,0 +1,102 @@ +package solutions.bitbadger.documents.sqlite + +import solutions.bitbadger.documents.common.* +import solutions.bitbadger.documents.common.Configuration +import solutions.bitbadger.documents.common.Query + +/** + * Queries with specific syntax in SQLite + */ +object Query { + + /** + * Create a `WHERE` clause fragment to implement a comparison on fields in a JSON document + * + * @param howMatched How the fields should be matched + * @param fields The fields for the comparisons + * @return A `WHERE` clause implementing the comparisons for the given fields + */ + fun whereByFields(howMatched: FieldMatch, fields: Collection>): String { + val name = ParameterName() + return fields.joinToString(" ${howMatched.sql} ") { + val comp = it.comparison + when (comp.op) { + Op.EXISTS, Op.NOT_EXISTS -> { + "${it.path(Dialect.SQLITE, FieldFormat.SQL)} ${it.comparison.op.sql}" + } + Op.BETWEEN -> { + val p = name.derive(it.parameterName) + "${it.path(Dialect.SQLITE, FieldFormat.SQL)} ${comp.op.sql} ${p}min AND ${p}max" + } + Op.IN -> { + val p = name.derive(it.parameterName) + val values = comp.value as List<*> + val paramNames = values.indices.joinToString(", ") { idx -> "${p}_$idx" } + "${it.path(Dialect.SQLITE, FieldFormat.SQL)} ${comp.op.sql} ($paramNames)" + } + Op.IN_ARRAY -> { + val p = name.derive(it.parameterName) + val (table, values) = comp.value as Pair> + val paramNames = values.indices.joinToString(", ") { idx -> "${p}_$idx" } + "EXISTS (SELECT 1 FROM json_each($table.data, '$.${it.name}') WHERE value IN ($paramNames)" + } + else -> { + "${it.path(Dialect.SQLITE, FieldFormat.SQL)} ${comp.op.sql} ${name.derive(it.parameterName)}" + } + } + } + } + + /** + * Create a `WHERE` clause fragment to implement an ID-based query + * + * @param docId The ID of the document + * @return A `WHERE` clause fragment identifying a document by its ID + */ + fun whereById(docId: TKey): String = + whereByFields(FieldMatch.ANY, listOf(Field.equal(Configuration.idField, docId).withParameterName(":id"))) + + /** + * Create an `UPDATE` statement to patch documents + * + * @param tableName The table to be updated + * @return A query to patch documents + */ + fun patch(tableName: String): String = + "UPDATE $tableName SET data = json_patch(data, json(:data))" + + // TODO: fun removeFields(tableName: String, fields: Collection): String + + /** + * Create a query by a document's ID + * + * @param statement The SQL statement to be run against a document by its ID + * @param docId The ID of the document targeted + * @returns A query addressing a document by its ID + */ + fun byId(statement: String, docId: TKey): String = + Query.statementWhere(statement, whereById(docId)) + + /** + * Create a query on JSON fields + * + * @param statement The SQL statement to be run against matching fields + * @param howMatched Whether to match any or all of the field conditions + * @param fields The field conditions to be matched + * @return A query addressing documents by field matching conditions + */ + fun byFields(statement: String, howMatched: FieldMatch, fields: Collection>): String = + Query.statementWhere(statement, whereByFields(howMatched, fields)) + + object Definition { + + /** + * SQL statement to create a document table + * + * @param tableName The name of the table (may include schema) + * @return A query to create the table if it does not exist + */ + fun ensureTable(tableName: String): String = + Query.Definition.ensureTableFor(tableName, "TEXT") + } +}