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")
+ }
+}