WIP on SQLite implementation

This commit is contained in:
2025-02-14 23:42:24 -05:00
parent de4df4109c
commit c35c41bf3c
13 changed files with 351 additions and 0 deletions

99
src/sqlite/pom.xml Normal file
View File

@@ -0,0 +1,99 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>solutions.bitbadger.documents</groupId>
<artifactId>sqlite</artifactId>
<version>4.0-ALPHA</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.code.style>official</kotlin.code.style>
<kotlin.compiler.jvmTarget>1.8</kotlin.compiler.jvmTarget>
</properties>
<repositories>
<repository>
<id>mavenCentral</id>
<url>https://repo1.maven.org/maven2/</url>
</repository>
</repositories>
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>2.1.10</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<configuration>
<mainClass>MainKt</mainClass>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test-junit5</artifactId>
<version>2.1.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>2.1.10</version>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.46.1.2</version>
</dependency>
<dependency>
<groupId>solutions.bitbadger.documents</groupId>
<artifactId>common</artifactId>
<version>4.0-ALPHA</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

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

View File

@@ -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<Field<*>>): 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<String, List<*>>
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 <TKey> 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>): 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 <TKey> 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<Field<*>>): 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")
}
}