Initial Development #1

Merged
danieljsummers merged 88 commits from v1-rc into main 2025-04-16 01:29:20 +00:00
9 changed files with 343 additions and 8 deletions
Showing only changes of commit 4d4e1d0897 - Show all commits

View File

@ -0,0 +1,63 @@
package solutions.bitbadger.documents.common
import solutions.bitbadger.documents.*
import kotlin.test.assertFalse
import kotlin.test.assertTrue
/**
* Integration tests for the `Exists` object
*/
object Exists {
fun byIdMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertTrue("The document with ID \"three\" should exist") { db.conn.existsById(TEST_TABLE, "three") }
}
fun byIdNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertFalse("The document with ID \"seven\" should not exist") { db.conn.existsById(TEST_TABLE, "seven") }
}
fun byFieldsMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertTrue("Matching documents should have been found") {
db.conn.existsByFields(TEST_TABLE, listOf(Field.equal("numValue", 10)))
}
}
fun byFieldsNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertFalse("No matching documents should have been found") {
db.conn.existsByFields(TEST_TABLE, listOf(Field.equal("nothing", "none")))
}
}
fun byContainsMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertTrue("Matching documents should have been found") {
db.conn.existsByContains(TEST_TABLE, mapOf("value" to "purple"))
}
}
fun byContainsNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertFalse("Matching documents should not have been found") {
db.conn.existsByContains(TEST_TABLE, mapOf("value" to "violet"))
}
}
fun byJsonPathMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertTrue("Matching documents should have been found") {
db.conn.existsByJsonPath(TEST_TABLE, "$.numValue ? (@ == 10)")
}
}
fun byJsonPathNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertFalse("Matching documents should not have been found") {
db.conn.existsByJsonPath(TEST_TABLE, "$.numValue ? (@ == 10.1)")
}
}
}

View File

@ -4,6 +4,9 @@ import org.junit.jupiter.api.DisplayName
import solutions.bitbadger.documents.common.Delete import solutions.bitbadger.documents.common.Delete
import kotlin.test.Test import kotlin.test.Test
/**
* PostgreSQL integration tests for the `Delete` object / `deleteBy*` connection extension functions
*/
@DisplayName("PostgreSQL - Delete") @DisplayName("PostgreSQL - Delete")
class DeleteIT { class DeleteIT {

View File

@ -0,0 +1,52 @@
package solutions.bitbadger.documents.postgresql
import org.junit.jupiter.api.DisplayName
import solutions.bitbadger.documents.common.Exists
import kotlin.test.Test
/**
* PostgreSQL integration tests for the `Exists` object / `existsBy*` connection extension functions
*/
@DisplayName("PostgreSQL - Exists")
class ExistsIT {
@Test
@DisplayName("byId returns true when a document matches the ID")
fun byIdMatch() =
PgDB().use(Exists::byIdMatch)
@Test
@DisplayName("byId returns false when no document matches the ID")
fun byIdNoMatch() =
PgDB().use(Exists::byIdNoMatch)
@Test
@DisplayName("byFields returns true when documents match")
fun byFieldsMatch() =
PgDB().use(Exists::byFieldsMatch)
@Test
@DisplayName("byFields returns false when no documents match")
fun byFieldsNoMatch() =
PgDB().use(Exists::byFieldsNoMatch)
@Test
@DisplayName("byContains returns true when documents match")
fun byContainsMatch() =
PgDB().use(Exists::byContainsMatch)
@Test
@DisplayName("byContains returns false when no documents match")
fun byContainsNoMatch() =
PgDB().use(Exists::byContainsNoMatch)
@Test
@DisplayName("byJsonPath returns true when documents match")
fun byJsonPathMatch() =
PgDB().use(Exists::byJsonPathMatch)
@Test
@DisplayName("byJsonPath returns false when no documents match")
fun byJsonPathNoMatch() =
PgDB().use(Exists::byJsonPathNoMatch)
}

View File

@ -6,6 +6,9 @@ import solutions.bitbadger.documents.DocumentException
import solutions.bitbadger.documents.common.Delete import solutions.bitbadger.documents.common.Delete
import kotlin.test.Test import kotlin.test.Test
/**
* SQLite integration tests for the `Delete` object / `deleteBy*` connection extension functions
*/
@DisplayName("SQLite - Delete") @DisplayName("SQLite - Delete")
class DeleteIT { class DeleteIT {
@ -31,13 +34,13 @@ class DeleteIT {
@Test @Test
@DisplayName("byContains fails") @DisplayName("byContains fails")
fun byContainsMatch() { fun byContainsFails() {
assertThrows<DocumentException> { SQLiteDB().use(Delete::byContainsMatch) } assertThrows<DocumentException> { SQLiteDB().use(Delete::byContainsMatch) }
} }
@Test @Test
@DisplayName("byJsonPath fails") @DisplayName("byJsonPath fails")
fun byJsonPathMatch() { fun byJsonPathFails() {
assertThrows<DocumentException> { SQLiteDB().use(Delete::byJsonPathMatch) } assertThrows<DocumentException> { SQLiteDB().use(Delete::byJsonPathMatch) }
} }
} }

View File

@ -0,0 +1,46 @@
package solutions.bitbadger.documents.sqlite
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.assertThrows
import solutions.bitbadger.documents.DocumentException
import solutions.bitbadger.documents.common.Exists
import kotlin.test.Test
/**
* SQLite integration tests for the `Exists` object / `existsBy*` connection extension functions
*/
@DisplayName("SQLite - Exists")
class ExistsIT {
@Test
@DisplayName("byId returns true when a document matches the ID")
fun byIdMatch() =
SQLiteDB().use(Exists::byIdMatch)
@Test
@DisplayName("byId returns false when no document matches the ID")
fun byIdNoMatch() =
SQLiteDB().use(Exists::byIdNoMatch)
@Test
@DisplayName("byFields returns true when documents match")
fun byFieldsMatch() =
SQLiteDB().use(Exists::byFieldsMatch)
@Test
@DisplayName("byFields returns false when no documents match")
fun byFieldsNoMatch() =
SQLiteDB().use(Exists::byFieldsNoMatch)
@Test
@DisplayName("byContains fails")
fun byContainsFails() {
assertThrows<DocumentException> { SQLiteDB().use(Exists::byContainsMatch) }
}
@Test
@DisplayName("byJsonPath fails")
fun byJsonPathFails() {
assertThrows<DocumentException> { SQLiteDB().use(Exists::byJsonPathMatch) }
}
}

View File

@ -127,7 +127,7 @@ inline fun <reified T> Connection.countByContains(tableName: String, criteria: T
Count.byContains(tableName, criteria, this) Count.byContains(tableName, criteria, this)
/** /**
* Count documents using a JSON containment query (PostgreSQL only) * Count documents using a JSON Path match query (PostgreSQL only)
* *
* @param tableName The name of the table in which documents should be counted * @param tableName The name of the table in which documents should be counted
* @param path The JSON path comparison to match * @param path The JSON path comparison to match
@ -137,6 +137,51 @@ inline fun <reified T> Connection.countByContains(tableName: String, criteria: T
fun Connection.countByJsonPath(tableName: String, path: String) = fun Connection.countByJsonPath(tableName: String, path: String) =
Count.byJsonPath(tableName, path, this) Count.byJsonPath(tableName, path, this)
// ~~~ DOCUMENT EXISTENCE QUERIES ~~~
/**
* Determine a document's existence by its ID
*
* @param tableName The name of the table in which document existence should be checked
* @param docId The ID of the document to be checked
* @return True if the document exists, false if not
*/
fun <TKey> Connection.existsById(tableName: String, docId: TKey) =
Exists.byId(tableName, docId, this)
/**
* Determine document existence using a field comparison
*
* @param tableName The name of the table in which document existence should be checked
* @param fields The fields which should be compared
* @param howMatched How the fields should be matched
* @return True if any matching documents exist, false if not
*/
fun Connection.existsByFields(tableName: String, fields: Collection<Field<*>>, howMatched: FieldMatch? = null) =
Exists.byFields(tableName, fields, howMatched, this)
/**
* Determine document existence using a JSON containment query (PostgreSQL only)
*
* @param tableName The name of the table in which document existence should be checked
* @param criteria The object for which JSON containment should be checked
* @return True if any matching documents exist, false if not
* @throws DocumentException If called on a SQLite connection
*/
inline fun <reified T> Connection.existsByContains(tableName: String, criteria: T) =
Exists.byContains(tableName, criteria, this)
/**
* Determine document existence using a JSON Path match query (PostgreSQL only)
*
* @param tableName The name of the table in which document existence should be checked
* @param path The JSON path comparison to match
* @return True if any matching documents exist, false if not
* @throws DocumentException If called on a SQLite connection
*/
fun Connection.existsByJsonPath(tableName: String, path: String) =
Exists.byJsonPath(tableName, path, this)
// ~~~ DOCUMENT RETRIEVAL QUERIES ~~~ // ~~~ DOCUMENT RETRIEVAL QUERIES ~~~
/** /**
@ -189,7 +234,7 @@ inline fun <reified T> Connection.deleteByContains(tableName: String, criteria:
Delete.byContains(tableName, criteria, this) Delete.byContains(tableName, criteria, this)
/** /**
* Delete documents using a JSON containment query (PostgreSQL only) * Delete documents using a JSON Path match query (PostgreSQL only)
* *
* @param tableName The name of the table from which documents should be deleted * @param tableName The name of the table from which documents should be deleted
* @param path The JSON path comparison to match * @param path The JSON path comparison to match

View File

@ -85,7 +85,7 @@ object Count {
Configuration.dbConn().use { byContains(tableName, criteria, it) } Configuration.dbConn().use { byContains(tableName, criteria, it) }
/** /**
* Count documents using a JSON containment query (PostgreSQL only) * Count documents using a JSON Path match query (PostgreSQL only)
* *
* @param tableName The name of the table in which documents should be counted * @param tableName The name of the table in which documents should be counted
* @param path The JSON path comparison to match * @param path The JSON path comparison to match
@ -101,7 +101,7 @@ object Count {
) )
/** /**
* Count documents using a JSON containment query (PostgreSQL only) * Count documents using a JSON Path match query (PostgreSQL only)
* *
* @param tableName The name of the table in which documents should be counted * @param tableName The name of the table in which documents should be counted
* @param path The JSON path comparison to match * @param path The JSON path comparison to match

View File

@ -75,7 +75,7 @@ object Delete {
Configuration.dbConn().use { byContains(tableName, criteria, it) } Configuration.dbConn().use { byContains(tableName, criteria, it) }
/** /**
* Delete documents using a JSON containment query (PostgreSQL only) * Delete documents using a JSON Path match query (PostgreSQL only)
* *
* @param tableName The name of the table from which documents should be deleted * @param tableName The name of the table from which documents should be deleted
* @param path The JSON path comparison to match * @param path The JSON path comparison to match
@ -86,7 +86,7 @@ object Delete {
conn.customNonQuery(Delete.byJsonPath(tableName), listOf(Parameter(":path", ParameterType.STRING, path))) conn.customNonQuery(Delete.byJsonPath(tableName), listOf(Parameter(":path", ParameterType.STRING, path)))
/** /**
* Delete documents using a JSON containment query (PostgreSQL only) * Delete documents using a JSON Path match query (PostgreSQL only)
* *
* @param tableName The name of the table from which documents should be deleted * @param tableName The name of the table from which documents should be deleted
* @param path The JSON path comparison to match * @param path The JSON path comparison to match

123
src/main/kotlin/Exists.kt Normal file
View File

@ -0,0 +1,123 @@
package solutions.bitbadger.documents
import solutions.bitbadger.documents.query.Exists
import java.sql.Connection
/**
* Functions to determine whether documents exist
*/
object Exists {
/**
* Determine a document's existence by its ID
*
* @param tableName The name of the table in which document existence should be checked
* @param docId The ID of the document to be checked
* @param conn The connection on which the existence check should be executed
* @return True if the document exists, false if not
*/
fun <TKey> byId(tableName: String, docId: TKey, conn: Connection) =
conn.customScalar(
Exists.byId(tableName, docId),
Parameters.addFields(listOf(Field.equal(Configuration.idField, docId, ":id"))),
Results::toExists
)
/**
* Determine a document's existence by its ID
*
* @param tableName The name of the table in which document existence should be checked
* @param docId The ID of the document to be checked
* @return True if the document exists, false if not
*/
fun <TKey> byId(tableName: String, docId: TKey) =
Configuration.dbConn().use { byId(tableName, docId, it) }
/**
* Determine document existence using a field comparison
*
* @param tableName The name of the table in which document existence should be checked
* @param fields The fields which should be compared
* @param howMatched How the fields should be matched
* @param conn The connection on which the existence check should be executed
* @return True if any matching documents exist, false if not
*/
fun byFields(
tableName: String,
fields: Collection<Field<*>>,
howMatched: FieldMatch? = null,
conn: Connection
): Boolean {
val named = Parameters.nameFields(fields)
return conn.customScalar(
Exists.byFields(tableName, named, howMatched),
Parameters.addFields(named),
Results::toExists
)
}
/**
* Determine document existence using a field comparison
*
* @param tableName The name of the table in which document existence should be checked
* @param fields The fields which should be compared
* @param howMatched How the fields should be matched
* @return True if any matching documents exist, false if not
*/
fun byFields(tableName: String, fields: Collection<Field<*>>, howMatched: FieldMatch? = null) =
Configuration.dbConn().use { byFields(tableName, fields, howMatched, it) }
/**
* Determine document existence using a JSON containment query (PostgreSQL only)
*
* @param tableName The name of the table in which document existence should be checked
* @param criteria The object for which JSON containment should be checked
* @param conn The connection on which the existence check should be executed
* @return True if any matching documents exist, false if not
* @throws DocumentException If called on a SQLite connection
*/
inline fun <reified T> byContains(tableName: String, criteria: T, conn: Connection) =
conn.customScalar(
Exists.byContains(tableName),
listOf(Parameters.json(":criteria", criteria)),
Results::toExists
)
/**
* Determine document existence using a JSON containment query (PostgreSQL only)
*
* @param tableName The name of the table in which document existence should be checked
* @param criteria The object for which JSON containment should be checked
* @return True if any matching documents exist, false if not
* @throws DocumentException If called on a SQLite connection
*/
inline fun <reified T> byContains(tableName: String, criteria: T) =
Configuration.dbConn().use { byContains(tableName, criteria, it) }
/**
* Determine document existence using a JSON Path match query (PostgreSQL only)
*
* @param tableName The name of the table in which document existence should be checked
* @param path The JSON path comparison to match
* @param conn The connection on which the existence check should be executed
* @return True if any matching documents exist, false if not
* @throws DocumentException If called on a SQLite connection
*/
fun byJsonPath(tableName: String, path: String, conn: Connection) =
conn.customScalar(
Exists.byJsonPath(tableName),
listOf(Parameter(":path", ParameterType.STRING, path)),
Results::toExists
)
/**
* Determine document existence using a JSON Path match query (PostgreSQL only)
*
* @param tableName The name of the table in which document existence should be checked
* @param path The JSON path comparison to match
* @return True if any matching documents exist, false if not
* @throws DocumentException If called on a SQLite connection
*/
fun byJsonPath(tableName: String, path: String) =
Configuration.dbConn().use { byJsonPath(tableName, path, it) }
}