From 864477f997d220424dd59321fe809d64f47ee22a Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Sat, 1 Mar 2025 14:03:39 -0500 Subject: [PATCH] Add find functions (tests pending) --- src/main/kotlin/ConnectionExtensions.kt | 120 +++++++- src/main/kotlin/Count.kt | 4 +- src/main/kotlin/Delete.kt | 4 +- src/main/kotlin/Exists.kt | 4 +- src/main/kotlin/Find.kt | 389 ++++++++++++++++++++++-- 5 files changed, 484 insertions(+), 37 deletions(-) diff --git a/src/main/kotlin/ConnectionExtensions.kt b/src/main/kotlin/ConnectionExtensions.kt index f46dde1..ec0cbb9 100644 --- a/src/main/kotlin/ConnectionExtensions.kt +++ b/src/main/kotlin/ConnectionExtensions.kt @@ -123,7 +123,7 @@ fun Connection.countByFields(tableName: String, fields: Collection>, ho * @return A count of the matching documents in the table * @throws DocumentException If called on a SQLite connection */ -inline fun Connection.countByContains(tableName: String, criteria: T) = +inline fun Connection.countByContains(tableName: String, criteria: TContains) = Count.byContains(tableName, criteria, this) /** @@ -168,7 +168,7 @@ fun Connection.existsByFields(tableName: String, fields: Collection>, h * @return True if any matching documents exist, false if not * @throws DocumentException If called on a SQLite connection */ -inline fun Connection.existsByContains(tableName: String, criteria: T) = +inline fun Connection.existsByContains(tableName: String, criteria: TContains) = Exists.byContains(tableName, criteria, this) /** @@ -185,22 +185,122 @@ fun Connection.existsByJsonPath(tableName: String, path: String) = // ~~~ DOCUMENT RETRIEVAL QUERIES ~~~ /** - * Retrieve all documents in the given table + * Retrieve all documents in the given table, ordering results by the optional given fields * * @param tableName The table from which documents should be retrieved + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) * @return A list of documents from the given table */ -inline fun Connection.findAll(tableName: String) = - Find.all(tableName, this) +inline fun Connection.findAll(tableName: String, orderBy: Collection>? = null) = + Find.all(tableName, orderBy, this) /** - * Retrieve all documents in the given table + * Retrieve a document by its ID + * + * @param tableName The table from which the document should be retrieved + * @param docId The ID of the document to retrieve + * @return The document if it is found, `null` otherwise + */ +inline fun Connection.findById(tableName: String, docId: TKey) = + Find.byId(tableName, docId, this) + +/** + * Retrieve documents using a field comparison, ordering results by the optional given fields + * + * @param tableName The table from which the document should be retrieved + * @param fields The fields which should be compared + * @param howMatched How the fields should be matched + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @return A list of documents matching the field comparison + */ +inline fun Connection.findByFields( + tableName: String, + fields: Collection>, + howMatched: FieldMatch? = null, + orderBy: Collection>? = null +) = + Find.byFields(tableName, fields, howMatched, orderBy, this) + +/** + * Retrieve documents using a JSON containment query, ordering results by the optional given fields (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 orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @return A list of documents matching the JSON containment query + * @throws DocumentException If called on a SQLite connection + */ +inline fun Connection.findByContains( + tableName: String, + criteria: TContains, + orderBy: Collection>? = null +) = + Find.byContains(tableName, criteria, orderBy, this) + +/** + * Retrieve documents using a JSON Path match query, ordering results by the optional given fields (PostgreSQL only) * * @param tableName The table from which documents should be retrieved - * @return A list of documents from the given table + * @param path The JSON path comparison to match + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @return A list of documents matching the JSON Path match query + * @throws DocumentException If called on a SQLite connection */ -inline fun Connection.findAll(tableName: String, orderBy: Collection>) = - Find.all(tableName, orderBy, this) +inline fun Connection.findByJsonPath( + tableName: String, + path: String, + orderBy: Collection>? = null +) = + Find.byJsonPath(tableName, path, orderBy, this) + +/** + * Retrieve the first document using a field comparison and optional ordering fields + * + * @param tableName The table from which documents should be retrieved + * @param fields The fields which should be compared + * @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`) + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @return The first document matching the field comparison, or `null` if no matches are found + */ +inline fun Connection.findFirstByFields( + tableName: String, + fields: Collection>, + howMatched: FieldMatch? = null, + orderBy: Collection>? = null +) = + Find.firstByFields(tableName, fields, howMatched, orderBy, this) + +/** + * Retrieve the first document using a JSON containment query and optional ordering fields (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param criteria The object for which JSON containment should be checked + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @return The first document matching the JSON containment query, or `null` if no matches are found + * @throws DocumentException If called on a SQLite connection + */ +inline fun Connection.findFirstByContains( + tableName: String, + criteria: TContains, + orderBy: Collection>? = null +) = + Find.firstByContains(tableName, criteria, orderBy, this) + +/** + * Retrieve the first document using a JSON Path match query and optional ordering fields (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param path The JSON path comparison to match + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @return The first document matching the JSON Path match query, or `null` if no matches are found + * @throws DocumentException If called on a SQLite connection + */ +inline fun Connection.findFirstByJsonPath( + tableName: String, + path: String, + orderBy: Collection>? = null +) = + Find.firstByJsonPath(tableName, path, orderBy, this) // ~~~ DOCUMENT DELETION QUERIES ~~~ @@ -230,7 +330,7 @@ fun Connection.deleteByFields(tableName: String, fields: Collection>, h * @param criteria The object for which JSON containment should be checked * @throws DocumentException If called on a SQLite connection */ -inline fun Connection.deleteByContains(tableName: String, criteria: T) = +inline fun Connection.deleteByContains(tableName: String, criteria: TContains) = Delete.byContains(tableName, criteria, this) /** diff --git a/src/main/kotlin/Count.kt b/src/main/kotlin/Count.kt index 738c5c4..970c865 100644 --- a/src/main/kotlin/Count.kt +++ b/src/main/kotlin/Count.kt @@ -70,7 +70,7 @@ object Count { * @return A count of the matching documents in the table * @throws DocumentException If called on a SQLite connection */ - inline fun byContains(tableName: String, criteria: T, conn: Connection) = + inline fun byContains(tableName: String, criteria: TContains, conn: Connection) = conn.customScalar(Count.byContains(tableName), listOf(Parameters.json(":criteria", criteria)), Results::toCount) /** @@ -81,7 +81,7 @@ object Count { * @return A count of the matching documents in the table * @throws DocumentException If called on a SQLite connection */ - inline fun byContains(tableName: String, criteria: T) = + inline fun byContains(tableName: String, criteria: TContains) = Configuration.dbConn().use { byContains(tableName, criteria, it) } /** diff --git a/src/main/kotlin/Delete.kt b/src/main/kotlin/Delete.kt index d64321b..e3ef705 100644 --- a/src/main/kotlin/Delete.kt +++ b/src/main/kotlin/Delete.kt @@ -61,7 +61,7 @@ object Delete { * @param conn The connection on which the deletion should be executed * @throws DocumentException If called on a SQLite connection */ - inline fun byContains(tableName: String, criteria: T, conn: Connection) = + inline fun byContains(tableName: String, criteria: TContains, conn: Connection) = conn.customNonQuery(Delete.byContains(tableName), listOf(Parameters.json(":criteria", criteria))) /** @@ -71,7 +71,7 @@ object Delete { * @param criteria The object for which JSON containment should be checked * @throws DocumentException If called on a SQLite connection */ - inline fun byContains(tableName: String, criteria: T) = + inline fun byContains(tableName: String, criteria: TContains) = Configuration.dbConn().use { byContains(tableName, criteria, it) } /** diff --git a/src/main/kotlin/Exists.kt b/src/main/kotlin/Exists.kt index 3978f53..0b1e1ce 100644 --- a/src/main/kotlin/Exists.kt +++ b/src/main/kotlin/Exists.kt @@ -76,7 +76,7 @@ object Exists { * @return True if any matching documents exist, false if not * @throws DocumentException If called on a SQLite connection */ - inline fun byContains(tableName: String, criteria: T, conn: Connection) = + inline fun byContains(tableName: String, criteria: TContains, conn: Connection) = conn.customScalar( Exists.byContains(tableName), listOf(Parameters.json(":criteria", criteria)), @@ -91,7 +91,7 @@ object Exists { * @return True if any matching documents exist, false if not * @throws DocumentException If called on a SQLite connection */ - inline fun byContains(tableName: String, criteria: T) = + inline fun byContains(tableName: String, criteria: TContains) = Configuration.dbConn().use { byContains(tableName, criteria, it) } /** diff --git a/src/main/kotlin/Find.kt b/src/main/kotlin/Find.kt index b9131ea..898d910 100644 --- a/src/main/kotlin/Find.kt +++ b/src/main/kotlin/Find.kt @@ -9,6 +9,27 @@ import solutions.bitbadger.documents.query.orderBy */ object Find { + /** + * Retrieve all documents in the given table, ordering results by the optional given fields + * + * @param tableName The table from which documents should be retrieved + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @param conn The connection over which documents should be retrieved + * @return A list of documents from the given table + */ + inline fun all(tableName: String, orderBy: Collection>? = null, conn: Connection) = + conn.customList(Find.all(tableName) + (orderBy?.let(::orderBy) ?: ""), mapFunc = Results::fromData) + + /** + * Retrieve all documents in the given table + * + * @param tableName The table from which documents should be retrieved + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @return A list of documents from the given table + */ + inline fun all(tableName: String, orderBy: Collection>? = null) = + Configuration.dbConn().use { all(tableName, orderBy, it) } + /** * Retrieve all documents in the given table * @@ -17,36 +38,362 @@ object Find { * @return A list of documents from the given table */ inline fun all(tableName: String, conn: Connection) = - conn.customList(Find.all(tableName), mapFunc = Results::fromData) + all(tableName, null, conn) /** - * Retrieve all documents in the given table + * Retrieve a document by its ID * - * @param tableName The table from which documents should be retrieved - * @return A list of documents from the given table - */ - inline fun all(tableName: String) = - Configuration.dbConn().use { all(tableName, it) } - - /** - * Retrieve all documents in the given table - * - * @param tableName The table from which documents should be retrieved - * @param orderBy Fields by which the query should be ordered + * @param tableName The table from which the document should be retrieved + * @param docId The ID of the document to retrieve * @param conn The connection over which documents should be retrieved - * @return A list of documents from the given table + * @return The document if it is found, `null` otherwise */ - inline fun all(tableName: String, orderBy: Collection>, conn: Connection) = - conn.customList(Find.all(tableName) + orderBy(orderBy), mapFunc = Results::fromData) + inline fun byId(tableName: String, docId: TKey, conn: Connection) = + conn.customSingle( + Find.byId(tableName, docId), + Parameters.addFields(listOf(Field.equal(Configuration.idField, docId, ":id"))), + Results::fromData + ) /** - * Retrieve all documents in the given table + * Retrieve a document by its ID + * + * @param tableName The table from which the document should be retrieved + * @param docId The ID of the document to retrieve + * @return The document if it is found, `null` otherwise + */ + inline fun byId(tableName: String, docId: TKey) = + Configuration.dbConn().use { byId(tableName, docId, it) } + + /** + * Retrieve documents using a field comparison, ordering results by the given fields * * @param tableName The table from which documents should be retrieved - * @param orderBy Fields by which the query should be ordered - * @return A list of documents from the given table + * @param fields The fields which should be compared + * @param howMatched How the fields should be matched + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @param conn The connection over which documents should be retrieved + * @return A list of documents matching the field comparison */ - inline fun all(tableName: String, orderBy: Collection>) = - Configuration.dbConn().use { all(tableName, orderBy, it) } + inline fun byFields( + tableName: String, + fields: Collection>, + howMatched: FieldMatch? = null, + orderBy: Collection>? = null, + conn: Connection + ): List { + val named = Parameters.nameFields(fields) + return conn.customList( + Find.byFields(tableName, named, howMatched) + (orderBy?.let(::orderBy) ?: ""), + Parameters.addFields(named), + Results::fromData + ) + } + /** + * Retrieve documents using a field comparison, ordering results by the given fields + * + * @param tableName The table from which documents should be retrieved + * @param fields The fields which should be compared + * @param howMatched How the fields should be matched + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @return A list of documents matching the field comparison + */ + inline fun byFields( + tableName: String, + fields: Collection>, + howMatched: FieldMatch? = null, + orderBy: Collection>? = null + ) = + Configuration.dbConn().use { byFields(tableName, fields, howMatched, orderBy, it) } + + /** + * Retrieve documents using a field comparison + * + * @param tableName The table from which documents should be retrieved + * @param fields The fields which should be compared + * @param howMatched How the fields should be matched + * @param conn The connection over which documents should be retrieved + * @return A list of documents matching the field comparison + */ + inline fun byFields( + tableName: String, + fields: Collection>, + howMatched: FieldMatch? = null, + conn: Connection + ) = + byFields(tableName, fields, howMatched, null, conn) + + /** + * Retrieve documents using a field comparison + * + * @param tableName The table from which documents should be retrieved + * @param fields The fields which should be compared + * @param howMatched How the fields should be matched + * @return A list of documents matching the field comparison + */ + inline fun byFields( + tableName: String, + fields: Collection>, + howMatched: FieldMatch? = null + ) = + Configuration.dbConn().use { byFields(tableName, fields, howMatched, null, it) } + + /** + * Retrieve documents using a JSON containment query, ordering results by the given fields (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param criteria The object for which JSON containment should be checked + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @param conn The connection over which documents should be retrieved + * @return A list of documents matching the JSON containment query + * @throws DocumentException If called on a SQLite connection + */ + inline fun byContains( + tableName: String, + criteria: TContains, + orderBy: Collection>? = null, + conn: Connection + ) = + conn.customList( + Find.byContains(tableName) + (orderBy?.let(::orderBy) ?: ""), + listOf(Parameters.json(":criteria", criteria)), + Results::fromData + ) + + /** + * Retrieve documents using a JSON containment query, ordering results by the given fields (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param criteria The object for which JSON containment should be checked + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @return A list of documents matching the JSON containment query + * @throws DocumentException If called on a SQLite connection + */ + inline fun byContains( + tableName: String, + criteria: TContains, + orderBy: Collection>? = null + ) = + Configuration.dbConn().use { byContains(tableName, criteria, orderBy, it) } + + /** + * Retrieve documents using a JSON containment query (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param criteria The object for which JSON containment should be checked + * @param conn The connection over which documents should be retrieved + * @return A list of documents matching the JSON containment query + * @throws DocumentException If called on a SQLite connection + */ + inline fun byContains(tableName: String, criteria: TContains, conn: Connection) = + byContains(tableName, criteria, null, conn) + + /** + * Retrieve documents using a JSON containment query (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param criteria The object for which JSON containment should be checked + * @return A list of documents matching the JSON containment query + * @throws DocumentException If called on a SQLite connection + */ + inline fun byContains(tableName: String, criteria: TContains) = + Configuration.dbConn().use { byContains(tableName, criteria, it) } + + /** + * Retrieve documents using a JSON Path match query, ordering results by the given fields (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param path The JSON path comparison to match + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @param conn The connection over which documents should be retrieved + * @return A list of documents matching the JSON Path match query + * @throws DocumentException If called on a SQLite connection + */ + inline fun byJsonPath( + tableName: String, + path: String, + orderBy: Collection>? = null, + conn: Connection + ) = + conn.customList( + Find.byJsonPath(tableName) + (orderBy?.let(::orderBy) ?: ""), + listOf(Parameter(":path", ParameterType.STRING, path)), + Results::fromData + ) + + /** + * Retrieve documents using a JSON Path match query, ordering results by the given fields (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param path The JSON path comparison to match + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @return A list of documents matching the JSON Path match query + * @throws DocumentException If called on a SQLite connection + */ + inline fun byJsonPath(tableName: String, path: String, orderBy: Collection>? = null) = + Configuration.dbConn().use { byJsonPath(tableName, path, orderBy, it) } + + /** + * Retrieve documents using a JSON Path match query (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param path The JSON path comparison to match + * @param conn The connection over which documents should be retrieved + * @return A list of documents matching the JSON Path match query + * @throws DocumentException If called on a SQLite connection + */ + inline fun byJsonPath(tableName: String, path: String, conn: Connection) = + byJsonPath(tableName, path, null, conn) + + /** + * Retrieve the first document using a field comparison and optional ordering fields + * + * @param tableName The table from which documents should be retrieved + * @param fields The fields which should be compared + * @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`) + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @param conn The connection over which documents should be retrieved + * @return The first document matching the field comparison, or `null` if no matches are found + */ + inline fun firstByFields( + tableName: String, + fields: Collection>, + howMatched: FieldMatch? = null, + orderBy: Collection>? = null, + conn: Connection + ): TDoc? { + val named = Parameters.nameFields(fields) + return conn.customSingle( + Find.byFields(tableName, named, howMatched) + (orderBy?.let(::orderBy) ?: ""), + Parameters.addFields(named), + Results::fromData + ) + } + + /** + * Retrieve the first document using a field comparison and optional ordering fields + * + * @param tableName The table from which documents should be retrieved + * @param fields The fields which should be compared + * @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`) + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @return The first document matching the field comparison, or `null` if no matches are found + */ + inline fun firstByFields( + tableName: String, + fields: Collection>, + howMatched: FieldMatch? = null, + orderBy: Collection>? = null + ) = + Configuration.dbConn().use { firstByFields(tableName, fields, howMatched, orderBy, it) } + + /** + * Retrieve the first document using a field comparison + * + * @param tableName The table from which documents should be retrieved + * @param fields The fields which should be compared + * @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`) + * @param conn The connection over which documents should be retrieved + * @return The first document matching the field comparison, or `null` if no matches are found + */ + inline fun firstByFields( + tableName: String, + fields: Collection>, + howMatched: FieldMatch? = null, + conn: Connection + ) = + firstByFields(tableName, fields, howMatched, null, conn) + + /** + * Retrieve the first document using a JSON containment query and optional ordering fields (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param criteria The object for which JSON containment should be checked + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @param conn The connection over which documents should be retrieved + * @return The first document matching the JSON containment query, or `null` if no matches are found + * @throws DocumentException If called on a SQLite connection + */ + inline fun firstByContains( + tableName: String, + criteria: TContains, + orderBy: Collection>? = null, + conn: Connection + ) = + conn.customSingle( + Find.byContains(tableName) + (orderBy?.let(::orderBy) ?: ""), + listOf(Parameters.json(":criteria", criteria)), + Results::fromData + ) + + /** + * Retrieve the first document using a JSON containment query (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param criteria The object for which JSON containment should be checked + * @param conn The connection over which documents should be retrieved + * @return The first document matching the JSON containment query, or `null` if no matches are found + * @throws DocumentException If called on a SQLite connection + */ + inline fun firstByContains(tableName: String, criteria: TContains, conn: Connection) = + firstByContains(tableName, criteria, null, conn) + + /** + * Retrieve the first document using a JSON containment query and optional ordering fields (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param criteria The object for which JSON containment should be checked + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @return The first document matching the JSON containment query, or `null` if no matches are found + * @throws DocumentException If called on a SQLite connection + */ + inline fun firstByContains(tableName: String, criteria: TContains, orderBy: Collection>? = null) = + Configuration.dbConn().use { firstByContains(tableName, criteria, orderBy, it) } + + /** + * Retrieve the first document using a JSON Path match query and optional ordering fields (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param path The JSON path comparison to match + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @param conn The connection over which documents should be retrieved + * @return The first document matching the JSON Path match query, or `null` if no matches are found + * @throws DocumentException If called on a SQLite connection + */ + inline fun firstByJsonPath( + tableName: String, + path: String, + orderBy: Collection>? = null, + conn: Connection + ) = + conn.customSingle( + Find.byJsonPath(tableName) + (orderBy?.let(::orderBy) ?: ""), + listOf(Parameter(":path", ParameterType.STRING, path)), + Results::fromData + ) + + /** + * Retrieve the first document using a JSON Path match query (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param path The JSON path comparison to match + * @param conn The connection over which documents should be retrieved + * @return The first document matching the JSON Path match query, or `null` if no matches are found + * @throws DocumentException If called on a SQLite connection + */ + inline fun firstByJsonPath(tableName: String, path: String, conn: Connection) = + firstByJsonPath(tableName, path, null, conn) + + /** + * Retrieve the first document using a JSON Path match query and optional ordering fields (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param path The JSON path comparison to match + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @return The first document matching the JSON Path match query, or `null` if no matches are found + * @throws DocumentException If called on a SQLite connection + */ + inline fun firstByJsonPath(tableName: String, path: String, orderBy: Collection>? = null) = + Configuration.dbConn().use { firstByJsonPath(tableName, path, orderBy, it) } }