Initial Development #1

Merged
danieljsummers merged 88 commits from v1-rc into main 2025-04-16 01:29:20 +00:00
2 changed files with 262 additions and 43 deletions
Showing only changes of commit ee4f25aec0 - Show all commits

View File

@ -96,7 +96,7 @@ object Query {
* @param dataType The type of data for the column (`JSON`, `JSONB`, etc.)
* @return A query to create a document table
*/
fun ensureTableFor(tableName: String, dataType: String): String =
fun ensureTableFor(tableName: String, dataType: String) =
"CREATE TABLE IF NOT EXISTS $tableName (data $dataType NOT NULL)"
/**
@ -146,7 +146,7 @@ object Query {
* @param dialect The SQL dialect to use when creating this index
* @return A query to create the key index
*/
fun ensureKey(tableName: String, dialect: Dialect): String =
fun ensureKey(tableName: String, dialect: Dialect) =
ensureIndexOn(tableName, "key", listOf(Configuration.idField), dialect).replace("INDEX", "UNIQUE INDEX")
}
@ -184,37 +184,162 @@ object Query {
* @param tableName The table into which to save (may include schema)
* @return A query to save a document
*/
fun save(tableName: String): String =
fun save(tableName: String) =
insert(tableName, AutoId.DISABLED) +
" ON CONFLICT ((data->>'${Configuration.idField}')) DO UPDATE SET data = EXCLUDED.data"
/**
* Query to count documents in a table (this query has no `WHERE` clause)
*
* @param tableName The table in which to count documents (may include schema)
* @return A query to count documents
* Functions to count documents
*/
fun count(tableName: String): String =
"SELECT COUNT(*) AS it FROM $tableName"
object Count {
/**
* Query to count all documents in a table
*
* @param tableName The table in which to count documents (may include schema)
* @return A query to count documents
*/
fun all(tableName: String) =
"SELECT COUNT(*) AS it FROM $tableName"
/**
* Query to count documents matching the given fields
*
* @param tableName The table in which to count documents (may include schema)
* @param fields The field comparisons for the count
* @param howMatched How fields should be compared (optional, defaults to ALL)
* @return A query to count documents matching the given fields
*/
fun byFields(tableName: String, fields: Collection<Field<*>>, howMatched: FieldMatch? = null) =
statementWhere(all(tableName), Where.byFields(fields, howMatched))
/**
* Query to count documents via JSON containment (PostgreSQL only)
*
* @param tableName The table in which to count documents (may include schema)
* @return A query to count documents via JSON containment
*/
fun byContains(tableName: String) =
statementWhere(all(tableName), Where.jsonContains())
/**
* Query to count documents via a JSON path match (PostgreSQL only)
*
* @param tableName The table in which to count documents (may include schema)
* @return A query to count documents via a JSON path match
*/
fun byJsonPath(tableName: String) =
statementWhere(all(tableName), Where.jsonPathMatches())
}
/**
* Query to check for document existence in a table
*
* @param tableName The table in which existence should be checked (may include schema)
* @param where The `WHERE` clause with the existence criteria
* @return A query to check document existence
* Functions to check for document existence
*/
fun exists(tableName: String, where: String): String =
"SELECT EXISTS (SELECT 1 FROM $tableName WHERE $where) AS it"
object Exists {
/**
* Query to check for document existence in a table
*
* @param tableName The table in which existence should be checked (may include schema)
* @param where The `WHERE` clause with the existence criteria
* @return A query to check document existence
*/
private fun exists(tableName: String, where: String) =
"SELECT EXISTS (SELECT 1 FROM $tableName WHERE $where) AS it"
/**
* Query to check for document existence by ID
*
* @param tableName The table in which existence should be checked (may include schema)
* @param docId The ID of the document (optional, used for type checking)
* @return A query to determine document existence by ID
*/
fun <TKey> byId(tableName: String, docId: TKey?) =
exists(tableName, Where.byId(docId = docId))
/**
* Query to check for document existence matching the given fields
*
* @param tableName The table in which existence should be checked (may include schema)
* @param fields The field comparisons for the existence check
* @param howMatched How fields should be compared (optional, defaults to ALL)
* @return A query to determine document existence for the given fields
*/
fun byFields(tableName: String, fields: Collection<Field<*>>, howMatched: FieldMatch?) =
exists(tableName, Where.byFields(fields, howMatched))
/**
* Query to check for document existence via JSON containment (PostgreSQL only)
*
* @param tableName The table in which existence should be checked (may include schema)
* @return A query to determine document existence via JSON containment
*/
fun byContains(tableName: String) =
exists(tableName, Where.jsonContains())
/**
* Query to check for document existence via a JSON path match (PostgreSQL only)
*
* @param tableName The table in which existence should be checked (may include schema)
* @return A query to determine document existence via a JSON path match
*/
fun byJsonPath(tableName: String) =
exists(tableName, Where.jsonPathMatches())
}
/**
* Query to select documents from a table (this query has no `WHERE` clause)
*
* @param tableName The table from which documents should be found (may include schema)
* @return A query to retrieve documents
* Functions to retrieve documents
*/
fun find(tableName: String): String =
"SELECT data FROM $tableName"
object Find {
/**
* Query to retrieve all documents from a table
*
* @param tableName The table from which documents should be retrieved (may include schema)
* @return A query to retrieve documents
*/
fun all(tableName: String) =
"SELECT data FROM $tableName"
/**
* Query to retrieve a document by its ID
*
* @param tableName The table from which documents should be retrieved (may include schema)
* @param docId The ID of the document (optional, used for type checking)
* @return A query to retrieve a document by its ID
*/
fun <TKey> byId(tableName: String, docId: TKey?) =
statementWhere(all(tableName), Where.byId(docId = docId))
/**
* Query to retrieve documents matching the given fields
*
* @param tableName The table from which documents should be retrieved (may include schema)
* @param fields The field comparisons for matching documents to retrieve
* @param howMatched How fields should be compared (optional, defaults to ALL)
* @return A query to retrieve documents matching the given fields
*/
fun byFields(tableName: String, fields: Collection<Field<*>>, howMatched: FieldMatch?) =
statementWhere(all(tableName), Where.byFields(fields, howMatched))
/**
* Query to retrieve documents via JSON containment (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved (may include schema)
* @return A query to retrieve documents via JSON containment
*/
fun byContains(tableName: String) =
statementWhere(all(tableName), Where.jsonContains())
/**
* Query to retrieve documents via a JSON path match (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved (may include schema)
* @return A query to retrieve documents via a JSON path match
*/
fun byJsonPath(tableName: String) =
statementWhere(all(tableName), Where.jsonPathMatches())
}
/**
* Query to update (replace) a document (this query has no `WHERE` clause)
@ -222,7 +347,7 @@ object Query {
* @param tableName The table in which documents should be replaced (may include schema)
* @return A query to update documents
*/
fun update(tableName: String): String =
fun update(tableName: String) =
"UPDATE $tableName SET data = :data"
/**
@ -286,13 +411,59 @@ object Query {
}
/**
* Query to delete documents from a table (this query has no `WHERE` clause)
*
* @param tableName The table in which documents should be deleted (may include schema)
* @return A query to delete documents
* Functions to delete documents
*/
fun delete(tableName: String): String =
"DELETE FROM $tableName"
object Delete {
/**
* Query to delete documents from a table
*
* @param tableName The table in which documents should be deleted (may include schema)
* @param where The WHERE clause for the delete statement
* @return A query to delete documents
*/
private fun delete(tableName: String, where: String) =
statementWhere("DELETE FROM $tableName", where)
/**
* Query to delete a document by its ID
*
* @param tableName The table from which documents should be deleted (may include schema)
* @param docId The ID of the document (optional, used for type checking)
* @return A query to delete a document by its ID
*/
fun <TKey> byId(tableName: String, docId: TKey?) =
delete(tableName, Where.byId(docId = docId))
/**
* Query to delete documents matching the given fields
*
* @param tableName The table from which documents should be deleted (may include schema)
* @param fields The field comparisons for documents to be deleted
* @param howMatched How fields should be compared (optional, defaults to ALL)
* @return A query to delete documents matching for the given fields
*/
fun byFields(tableName: String, fields: Collection<Field<*>>, howMatched: FieldMatch?) =
delete(tableName, Where.byFields(fields, howMatched))
/**
* Query to delete documents via JSON containment (PostgreSQL only)
*
* @param tableName The table from which documents should be deleted (may include schema)
* @return A query to delete documents via JSON containment
*/
fun byContains(tableName: String) =
delete(tableName, Where.jsonContains())
/**
* Query to delete documents via a JSON path match (PostgreSQL only)
*
* @param tableName The table from which documents should be deleted (may include schema)
* @return A query to delete documents via a JSON path match
*/
fun byJsonPath(tableName: String) =
delete(tableName, Where.jsonPathMatches())
}
/**
* Create an `ORDER BY` clause for the given fields

View File

@ -399,30 +399,78 @@ class QueryTest {
}
@Test
@DisplayName("count generates correctly")
fun count() =
assertEquals("SELECT COUNT(*) AS it FROM $tbl", Query.count(tbl), "Count query not constructed correctly")
@DisplayName("Count.all generates correctly")
fun countAll() =
assertEquals("SELECT COUNT(*) AS it FROM $tbl", Query.Count.all(tbl), "Count query not constructed correctly")
@Test
@DisplayName("exists generates correctly")
fun exists() =
assertEquals("SELECT EXISTS (SELECT 1 FROM $tbl WHERE turkey) AS it", Query.exists(tbl, "turkey"),
"Exists query not constructed correctly")
@DisplayName("Count.byFields generates correctly (PostgreSQL)")
fun countByFieldsPostgres() {
Configuration.connectionString = pg
assertEquals("SELECT COUNT(*) AS it FROM $tbl WHERE data->>'test' = :field0",
Query.Count.byFields(tbl, listOf(Field.equal("test", "", ":field0"))),
"Count query not constructed correctly")
}
@Test
@DisplayName("find generates correctly")
fun find() =
assertEquals("SELECT data FROM $tbl", Query.find(tbl), "Find query not constructed correctly")
@DisplayName("Count.byFields generates correctly (PostgreSQL)")
fun countByFieldsSQLite() {
Configuration.connectionString = lite
assertEquals("SELECT COUNT(*) AS it FROM $tbl WHERE data->>'test' = :field0",
Query.Count.byFields(tbl, listOf(Field.equal("test", "", ":field0"))),
"Count query not constructed correctly")
}
@Test
@DisplayName("Count.byContains generates correctly (PostgreSQL)")
fun countByContainsPostgres() {
Configuration.connectionString = pg
assertEquals("SELECT COUNT(*) AS it FROM $tbl WHERE data @> :criteria", Query.Count.byContains(tbl),
"Count query not constructed correctly")
}
@Test
@DisplayName("Count.byContains fails (SQLite)")
fun countByContainsSQLite() {
Configuration.connectionString = lite
assertThrows<DocumentException> { Query.Count.byContains(tbl) }
}
@Test
@DisplayName("Count.byJsonPath generates correctly (PostgreSQL)")
fun countByJsonPathPostgres() {
Configuration.connectionString = pg
assertEquals("SELECT COUNT(*) AS it FROM $tbl WHERE jsonb_path_exists(data, :path::jsonpath)",
Query.Count.byJsonPath(tbl), "Count query not constructed correctly")
}
@Test
@DisplayName("Count.byJsonPath fails (SQLite)")
fun countByJsonPathSQLite() {
Configuration.connectionString = lite
assertThrows<DocumentException> { Query.Count.byJsonPath(tbl) }
}
// @Test
// @DisplayName("exists generates correctly")
// fun exists() =
// assertEquals("SELECT EXISTS (SELECT 1 FROM $tbl WHERE turkey) AS it", Query.exists(tbl, "turkey"),
// "Exists query not constructed correctly")
@Test
@DisplayName("Find.all generates correctly")
fun findAll() =
assertEquals("SELECT data FROM $tbl", Query.Find.all(tbl), "Find query not constructed correctly")
@Test
@DisplayName("update generates successfully")
fun update() =
assertEquals("UPDATE $tbl SET data = :data", Query.update(tbl), "Update query not constructed correctly")
@Test
@DisplayName("delete generates successfully")
fun delete() =
assertEquals("DELETE FROM $tbl", Query.delete(tbl), "Delete query not constructed correctly")
// @Test
// @DisplayName("delete generates successfully")
// fun delete() =
// assertEquals("DELETE FROM $tbl", Query.delete(tbl), "Delete query not constructed correctly")
@Test
@DisplayName("orderBy generates for no fields")