From f15feb2238fcba6dc6d2c10cff04f762d4463aff Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Sun, 6 Apr 2025 14:53:50 -0400 Subject: [PATCH] Split SQLite into files; add JSON extraction funcs --- src/Postgres/Library.fs | 6 +- src/Sqlite/BitBadger.Documents.Sqlite.fsproj | 2 + src/Sqlite/Functions.fs | 418 +++++++ src/Sqlite/Library.fs | 1019 ++---------------- src/Sqlite/WithConn.fs | 510 +++++++++ 5 files changed, 1014 insertions(+), 941 deletions(-) create mode 100644 src/Sqlite/Functions.fs create mode 100644 src/Sqlite/WithConn.fs diff --git a/src/Postgres/Library.fs b/src/Postgres/Library.fs index beb9542..7a5520e 100644 --- a/src/Postgres/Library.fs +++ b/src/Postgres/Library.fs @@ -2,6 +2,7 @@ open System.IO open System.Text +open System.Threading.Tasks /// The type of index to generate for the document [] @@ -342,12 +343,13 @@ module Results = /// The query from which JSON should be extracted [] let writeJsonArray (writer: StreamWriter) (mapFunc: RowReader -> string) sqlProps = backgroundTask { + let await (it: Task) = it.ConfigureAwait(false).GetAwaiter().GetResult() do! writer.WriteAsync "[" let mutable isFirst = true do! sqlProps |> Sql.iterAsync (fun it -> - if isFirst then isFirst <- false else writer.Write "," - writer.WriteAsync(mapFunc it).ConfigureAwait(false).GetAwaiter().GetResult()) + if isFirst then isFirst <- false else await (writer.WriteAsync ",") + (mapFunc >> writer.WriteAsync >> await) it) do! writer.WriteAsync "]" } diff --git a/src/Sqlite/BitBadger.Documents.Sqlite.fsproj b/src/Sqlite/BitBadger.Documents.Sqlite.fsproj index e19b49d..c830a84 100644 --- a/src/Sqlite/BitBadger.Documents.Sqlite.fsproj +++ b/src/Sqlite/BitBadger.Documents.Sqlite.fsproj @@ -8,6 +8,8 @@ + + diff --git a/src/Sqlite/Functions.fs b/src/Sqlite/Functions.fs new file mode 100644 index 0000000..24c3316 --- /dev/null +++ b/src/Sqlite/Functions.fs @@ -0,0 +1,418 @@ +namespace BitBadger.Documents.Sqlite + +open Microsoft.Data.Sqlite + +/// Commands to execute custom SQL queries +[] +module Custom = + + /// Execute a query that returns a list of results + /// The query to retrieve the results + /// Parameters to use for the query + /// The mapping function between the document and the domain item + /// A list of results for the given query + [] + let list<'TDoc> query parameters (mapFunc: SqliteDataReader -> 'TDoc) = + use conn = Configuration.dbConn () + WithConn.Custom.list<'TDoc> query parameters mapFunc conn + + /// Execute a query that returns a list of results + /// The query to retrieve the results + /// Parameters to use for the query + /// The mapping function between the document and the domain item + /// A list of results for the given query + let List<'TDoc>(query, parameters, mapFunc: System.Func) = + use conn = Configuration.dbConn () + WithConn.Custom.List<'TDoc>(query, parameters, mapFunc, conn) + + /// Execute a query that returns one or no results + /// The query to retrieve the results + /// Parameters to use for the query + /// The mapping function between the document and the domain item + /// Some with the first matching result, or None if not found + [] + let single<'TDoc> query parameters (mapFunc: SqliteDataReader -> 'TDoc) = + use conn = Configuration.dbConn () + WithConn.Custom.single<'TDoc> query parameters mapFunc conn + + /// Execute a query that returns one or no results + /// The query to retrieve the results + /// Parameters to use for the query + /// The mapping function between the document and the domain item + /// The first matching result, or null if not found + let Single<'TDoc when 'TDoc: null and 'TDoc: not struct>( + query, parameters, mapFunc: System.Func) = + use conn = Configuration.dbConn () + WithConn.Custom.Single<'TDoc>(query, parameters, mapFunc, conn) + + /// Execute a query that returns no results + /// The query to retrieve the results + /// Parameters to use for the query + [] + let nonQuery query parameters = + use conn = Configuration.dbConn () + WithConn.Custom.nonQuery query parameters conn + + /// Execute a query that returns a scalar value + /// The query to retrieve the value + /// Parameters to use for the query + /// The mapping function to obtain the value + /// The scalar value for the query + [] + let scalar<'T when 'T: struct> query parameters (mapFunc: SqliteDataReader -> 'T) = + use conn = Configuration.dbConn () + WithConn.Custom.scalar<'T> query parameters mapFunc conn + + /// Execute a query that returns a scalar value + /// The query to retrieve the value + /// Parameters to use for the query + /// The mapping function to obtain the value + /// The scalar value for the query + let Scalar<'T when 'T: struct>(query, parameters, mapFunc: System.Func) = + use conn = Configuration.dbConn () + WithConn.Custom.Scalar<'T>(query, parameters, mapFunc, conn) + + +/// Functions to create tables and indexes +[] +module Definition = + + /// Create a document table + /// The table whose existence should be ensured (may include schema) + [] + let ensureTable name = + use conn = Configuration.dbConn () + WithConn.Definition.ensureTable name conn + + /// Create an index on field(s) within documents in the specified table + /// The table to be indexed (may include schema) + /// The name of the index to create + /// One or more fields to be indexed + [] + let ensureFieldIndex tableName indexName fields = + use conn = Configuration.dbConn () + WithConn.Definition.ensureFieldIndex tableName indexName fields conn + + +/// Document insert/save functions +[] +module Document = + + /// Insert a new document + /// The table into which the document should be inserted (may include schema) + /// The document to be inserted + [] + let insert<'TDoc> tableName (document: 'TDoc) = + use conn = Configuration.dbConn () + WithConn.Document.insert tableName document conn + + /// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert") + /// The table into which the document should be saved (may include schema) + /// The document to be saved + [] + let save<'TDoc> tableName (document: 'TDoc) = + use conn = Configuration.dbConn () + WithConn.Document.save tableName document conn + + +/// Commands to count documents +[] +module Count = + + /// Count all documents in a table + /// The table in which documents should be counted (may include schema) + /// The count of the documents in the table + [] + let all tableName = + use conn = Configuration.dbConn () + WithConn.Count.all tableName conn + + /// Count matching documents using JSON field comparisons (->> =, etc.) + /// The table in which documents should be counted (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// The count of matching documents in the table + [] + let byFields tableName howMatched fields = + use conn = Configuration.dbConn () + WithConn.Count.byFields tableName howMatched fields conn + + +/// Commands to determine if documents exist +[] +module Exists = + + /// Determine if a document exists for the given ID + /// The table in which existence should be checked (may include schema) + /// The ID of the document whose existence should be checked + /// True if a document exists, false if not + [] + let byId tableName (docId: 'TKey) = + use conn = Configuration.dbConn () + WithConn.Exists.byId tableName docId conn + + /// Determine if a document exists using JSON field comparisons (->> =, etc.) + /// The table in which existence should be checked (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// True if any matching documents exist, false if not + [] + let byFields tableName howMatched fields = + use conn = Configuration.dbConn () + WithConn.Exists.byFields tableName howMatched fields conn + + +/// Commands to retrieve documents +[] +module Find = + + /// Retrieve all documents in the given table + /// The table from which documents should be retrieved (may include schema) + /// All documents from the given table + [] + let all<'TDoc> tableName = + use conn = Configuration.dbConn () + WithConn.Find.all<'TDoc> tableName conn + + /// Retrieve all documents in the given table + /// The table from which documents should be retrieved (may include schema) + /// All documents from the given table + let All<'TDoc> tableName = + use conn = Configuration.dbConn () + WithConn.Find.All<'TDoc>(tableName, conn) + + /// Retrieve all documents in the given table ordered by the given fields in the document + /// The table from which documents should be retrieved (may include schema) + /// Fields by which the results should be ordered + /// All documents from the given table, ordered by the given fields + [] + let allOrdered<'TDoc> tableName orderFields = + use conn = Configuration.dbConn () + WithConn.Find.allOrdered<'TDoc> tableName orderFields conn + + /// Retrieve all documents in the given table ordered by the given fields in the document + /// The table from which documents should be retrieved (may include schema) + /// Fields by which the results should be ordered + /// All documents from the given table, ordered by the given fields + let AllOrdered<'TDoc> tableName orderFields = + use conn = Configuration.dbConn () + WithConn.Find.AllOrdered<'TDoc>(tableName, orderFields, conn) + + /// Retrieve a document by its ID + /// The table from which a document should be retrieved (may include schema) + /// The ID of the document to retrieve + /// Some with the document if found, None otherwise + [] + let byId<'TKey, 'TDoc> tableName docId = + use conn = Configuration.dbConn () + WithConn.Find.byId<'TKey, 'TDoc> tableName docId conn + + /// Retrieve a document by its ID + /// The table from which a document should be retrieved (may include schema) + /// The ID of the document to retrieve + /// The document if found, null otherwise + let ById<'TKey, 'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, docId) = + use conn = Configuration.dbConn () + WithConn.Find.ById<'TKey, 'TDoc>(tableName, docId, conn) + + /// Retrieve documents matching JSON field comparisons (->> =, etc.) + /// The table from which documents should be retrieved (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// All documents matching the given fields + [] + let byFields<'TDoc> tableName howMatched fields = + use conn = Configuration.dbConn () + WithConn.Find.byFields<'TDoc> tableName howMatched fields conn + + /// Retrieve documents matching JSON field comparisons (->> =, etc.) + /// The table from which documents should be retrieved (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// All documents matching the given fields + let ByFields<'TDoc>(tableName, howMatched, fields) = + use conn = Configuration.dbConn () + WithConn.Find.ByFields<'TDoc>(tableName, howMatched, fields, conn) + + /// + /// Retrieve documents matching JSON field comparisons (->> =, etc.) ordered by the given fields in + /// the document + /// + /// The table from which documents should be retrieved (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// Fields by which the results should be ordered + /// All documents matching the given fields, ordered by the other given fields + [] + let byFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields = + use conn = Configuration.dbConn () + WithConn.Find.byFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields conn + + /// + /// Retrieve documents matching JSON field comparisons (->> =, etc.) ordered by the given fields in + /// the document + /// + /// The table from which documents should be retrieved (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// Fields by which the results should be ordered + /// All documents matching the given fields, ordered by the other given fields + let ByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields) = + use conn = Configuration.dbConn () + WithConn.Find.ByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, conn) + + /// Retrieve the first document matching JSON field comparisons (->> =, etc.) + /// The table from which a document should be retrieved (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// Some with the first document, or None if not found + [] + let firstByFields<'TDoc> tableName howMatched fields = + use conn = Configuration.dbConn () + WithConn.Find.firstByFields<'TDoc> tableName howMatched fields conn + + /// Retrieve the first document matching JSON field comparisons (->> =, etc.) + /// The table from which a document should be retrieved (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// The first document, or null if not found + let FirstByFields<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, howMatched, fields) = + use conn = Configuration.dbConn () + WithConn.Find.FirstByFields<'TDoc>(tableName, howMatched, fields, conn) + + /// + /// Retrieve the first document matching JSON field comparisons (->> =, etc.) ordered by the given + /// fields in the document + /// + /// The table from which a document should be retrieved (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// Fields by which the results should be ordered + /// + /// Some with the first document ordered by the given fields, or None if not found + /// + [] + let firstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields = + use conn = Configuration.dbConn () + WithConn.Find.firstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields conn + + /// + /// Retrieve the first document matching JSON field comparisons (->> =, etc.) ordered by the given + /// fields in the document + /// + /// The table from which a document should be retrieved (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// Fields by which the results should be ordered + /// The first document ordered by the given fields, or null if not found + let FirstByFieldsOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>( + tableName, howMatched, queryFields, orderFields) = + use conn = Configuration.dbConn () + WithConn.Find.FirstByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, conn) + + +/// Commands to update documents +[] +module Update = + + /// Update (replace) an entire document by its ID + /// The table in which a document should be updated (may include schema) + /// The ID of the document to be updated (replaced) + /// The new document + [] + let byId tableName (docId: 'TKey) (document: 'TDoc) = + use conn = Configuration.dbConn () + WithConn.Update.byId tableName docId document conn + + /// + /// Update (replace) an entire document by its ID, using the provided function to obtain the ID from the document + /// + /// The table in which a document should be updated (may include schema) + /// The function to obtain the ID of the document + /// The new document + [] + let byFunc tableName (idFunc: 'TDoc -> 'TKey) (document: 'TDoc) = + use conn = Configuration.dbConn () + WithConn.Update.byFunc tableName idFunc document conn + + /// + /// Update (replace) an entire document by its ID, using the provided function to obtain the ID from the document + /// + /// The table in which a document should be updated (may include schema) + /// The function to obtain the ID of the document + /// The new document + let ByFunc(tableName, idFunc: System.Func<'TDoc, 'TKey>, document: 'TDoc) = + use conn = Configuration.dbConn () + WithConn.Update.ByFunc(tableName, idFunc, document, conn) + + +/// Commands to patch (partially update) documents +[] +module Patch = + + /// Patch a document by its ID + /// The table in which a document should be patched (may include schema) + /// The ID of the document to patch + /// The partial document to patch the existing document + [] + let byId tableName (docId: 'TKey) (patch: 'TPatch) = + use conn = Configuration.dbConn () + WithConn.Patch.byId tableName docId patch conn + + /// + /// Patch documents using a JSON field comparison query in the WHERE clause (->> =, etc.) + /// + /// The table in which documents should be patched (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// The partial document to patch the existing document + [] + let byFields tableName howMatched fields (patch: 'TPatch) = + use conn = Configuration.dbConn () + WithConn.Patch.byFields tableName howMatched fields patch conn + + +/// Commands to remove fields from documents +[] +module RemoveFields = + + /// Remove fields from a document by the document's ID + /// The table in which a document should be modified (may include schema) + /// The ID of the document to modify + /// One or more field names to remove from the document + [] + let byId tableName (docId: 'TKey) fieldNames = + use conn = Configuration.dbConn () + WithConn.RemoveFields.byId tableName docId fieldNames conn + + /// Remove fields from documents via a comparison on JSON fields in the document + /// The table in which documents should be modified (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// One or more field names to remove from the matching documents + [] + let byFields tableName howMatched fields fieldNames = + use conn = Configuration.dbConn () + WithConn.RemoveFields.byFields tableName howMatched fields fieldNames conn + + +/// Commands to delete documents +[] +module Delete = + + /// Delete a document by its ID + /// The table in which a document should be deleted (may include schema) + /// The ID of the document to delete + [] + let byId tableName (docId: 'TKey) = + use conn = Configuration.dbConn () + WithConn.Delete.byId tableName docId conn + + /// Delete documents by matching a JSON field comparison query (->> =, etc.) + /// The table in which documents should be deleted (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + [] + let byFields tableName howMatched fields = + use conn = Configuration.dbConn () + WithConn.Delete.byFields tableName howMatched fields conn diff --git a/src/Sqlite/Library.fs b/src/Sqlite/Library.fs index 215a62b..8e74554 100644 --- a/src/Sqlite/Library.fs +++ b/src/Sqlite/Library.fs @@ -1,5 +1,7 @@ namespace BitBadger.Documents.Sqlite +open System.IO +open System.Text open BitBadger.Documents open Microsoft.Data.Sqlite @@ -36,12 +38,10 @@ module Configuration = [] module Query = - /// - /// Create a WHERE clause fragment to implement a comparison on fields in a JSON document - /// + /// Create a WHERE clause fragment to implement a comparison on fields in a JSON document /// How the fields should be matched /// The fields for the comparisons - /// A WHERE clause implementing the comparisons for the given fields + /// A WHERE clause implementing the comparisons for the given fields [] let whereByFields (howMatched: FieldMatch) fields = let name = ParameterName() @@ -63,21 +63,21 @@ module Query = | _ -> $"{it.Path SQLite AsSql} {it.Comparison.OpSql} {name.Derive it.ParameterName}") |> String.concat $" {howMatched} " - /// Create a WHERE clause fragment to implement an ID-based query + /// Create a WHERE clause fragment to implement an ID-based query /// The ID of the document - /// A WHERE clause fragment identifying a document by its ID + /// A WHERE clause fragment identifying a document by its ID [] let whereById (docId: 'TKey) = whereByFields Any [ { Field.Equal (Configuration.idField ()) docId with ParameterName = Some "@id" } ] - /// Create an UPDATE statement to patch documents + /// Create an UPDATE statement to patch documents /// The table to be updated /// A query to patch documents [] let patch tableName = $"UPDATE %s{tableName} SET data = json_patch(data, json(@data))" - /// Create an UPDATE statement to remove fields from documents + /// Create an UPDATE statement to remove fields from documents /// The table to be updated /// The parameters with the field names to be removed /// A query to remove fields from documents @@ -136,7 +136,7 @@ module Parameters = SqliteParameter(name, Configuration.serializer().Serialize it) /// Create JSON field parameters - /// The Fields to convert to parameters + /// The Fields to convert to parameters /// The current parameters for the query /// A unified sequence of parameter names and values [] @@ -169,7 +169,7 @@ module Parameters = /// Append JSON field name parameters for the given field names to the given parameters /// The name of the parameter to use for each field /// The names of fields to be addressed - /// The name (@name) and parameter value for the field names + /// The name (@name) and parameter value for the field names [] let fieldNameParams paramName fieldNames = fieldNames @@ -189,14 +189,14 @@ module Results = /// Create a domain item from a document, specifying the field in which the document is found /// The field name containing the JSON document - /// A SqliteDataReader set to the row with the document to be constructed + /// A SqliteDataReader set to the row with the document to be constructed /// The constructed domain item [] let fromDocument<'TDoc> field (rdr: SqliteDataReader) : 'TDoc = Configuration.serializer().Deserialize<'TDoc>(rdr.GetString(rdr.GetOrdinal field)) /// Create a domain item from a document - /// A SqliteDataReader set to the row with the document to be constructed + /// A SqliteDataReader set to the row with the document to be constructed /// The constructed domain item [] let fromData<'TDoc> rdr = @@ -232,20 +232,86 @@ module Results = } /// Extract a count from the first column - /// A SqliteDataReader set to the row with the count to retrieve + /// A SqliteDataReader set to the row with the count to retrieve /// The count from the row [] let toCount (rdr: SqliteDataReader) = rdr.GetInt64 0 /// Extract a true/false value from the first column - /// A SqliteDataReader set to the row with the true/false value to retrieve + /// A SqliteDataReader set to the row with the true/false value to retrieve /// The true/false value from the row /// SQLite implements boolean as 1 = true, 0 = false [] let toExists rdr = toCount rdr > 0L + /// Retrieve a JSON document, specifying the field in which the document is found + /// The field name containing the JSON document + /// A SqliteDataReader set to the row with the document to be constructed + /// The JSON document (an empty JSON document if not found) + [] + let jsonFromDocument field (rdr: SqliteDataReader) = + try + let idx = rdr.GetOrdinal field + if rdr.IsDBNull idx then "{}" else rdr.GetString idx + with :? System.IndexOutOfRangeException -> "{}" + + /// Retrieve a JSON document + /// A SqliteDataReader set to the row with the document to be constructed + /// The JSON document (an empty JSON document if not found) + [] + let jsonFromData rdr = + jsonFromDocument "data" rdr + + /// + /// Create a JSON array for the results of the given command, using the specified mapping function + /// + /// The command to execute + /// The mapping function to extract JSON from the query's results + /// A JSON array of items from the reader + [] + let toJsonArray (cmd: SqliteCommand) (mapFunc: SqliteDataReader -> string) = backgroundTask { + use! rdr = cmd.ExecuteReaderAsync() + let it = StringBuilder "[" + while! rdr.ReadAsync() do + if it.Length > 2 then ignore (it.Append ",") + it.Append(mapFunc rdr) |> ignore + return it.Append("]").ToString() + } + + /// + /// Create a JSON array for the results of the given command, using the specified mapping function + /// + /// The command to execute + /// The mapping function to extract JSON from the query's results + /// A JSON array of items from the reader + let ToJsonArray (cmd: SqliteCommand) (mapFunc: System.Func) = + toJsonArray cmd mapFunc.Invoke + + /// Write a JSON array of items for the results of a query to the given StreamWriter + /// The command to execute + /// The StreamWriter to which results should be written + /// The mapping function to extract JSON from the query's results + [] + let writeJsonArray (cmd: SqliteCommand) (writer: StreamWriter) (mapFunc: SqliteDataReader -> string) = + backgroundTask { + use! rdr = cmd.ExecuteReaderAsync() + do! writer.WriteAsync "[" + let mutable isFirst = true + while! rdr.ReadAsync() do + if isFirst then isFirst <- false else do! writer.WriteAsync "," + do! writer.WriteAsync(mapFunc rdr) + do! writer.WriteAsync "]" + } + + /// Write a JSON array of items for the results of a query to the given StreamWriter + /// The command to execute + /// The StreamWriter to which results should be written + /// The mapping function to extract JSON from the query's results + let WriteJsonArray (cmd: SqliteCommand) (writer: StreamWriter) (mapFunc: System.Func) = + writeJsonArray cmd writer mapFunc.Invoke + [] module internal Helpers = @@ -256,928 +322,3 @@ module internal Helpers = let! _ = cmd.ExecuteNonQueryAsync() () } - - -/// Versions of queries that accept a SqliteConnection as the last parameter -module WithConn = - - /// Commands to execute custom SQL queries - [] - module Custom = - - /// Execute a query that returns a list of results - /// The query to retrieve the results - /// Parameters to use for the query - /// The mapping function between the document and the domain item - /// The SqliteConnection to use to execute the query - /// A list of results for the given query - [] - let list<'TDoc> query (parameters: SqliteParameter seq) (mapFunc: SqliteDataReader -> 'TDoc) - (conn: SqliteConnection) = - use cmd = conn.CreateCommand() - cmd.CommandText <- query - cmd.Parameters.AddRange parameters - toCustomList<'TDoc> cmd mapFunc - - /// Execute a query that returns a list of results - /// The query to retrieve the results - /// Parameters to use for the query - /// The mapping function between the document and the domain item - /// The SqliteConnection to use to execute the query - /// A list of results for the given query - let List<'TDoc>( - query, parameters: SqliteParameter seq, mapFunc: System.Func, - conn: SqliteConnection - ) = - use cmd = conn.CreateCommand() - cmd.CommandText <- query - cmd.Parameters.AddRange parameters - ToCustomList<'TDoc>(cmd, mapFunc) - - /// Execute a query that returns one or no results - /// The query to retrieve the results - /// Parameters to use for the query - /// The mapping function between the document and the domain item - /// The SqliteConnection to use to execute the query - /// Some with the first matching result, or None if not found - [] - let single<'TDoc> query parameters (mapFunc: SqliteDataReader -> 'TDoc) conn = backgroundTask { - let! results = list query parameters mapFunc conn - return FSharp.Collections.List.tryHead results - } - - /// Execute a query that returns one or no results - /// The query to retrieve the results - /// Parameters to use for the query - /// The mapping function between the document and the domain item - /// The SqliteConnection to use to execute the query - /// The first matching result, or null if not found - let Single<'TDoc when 'TDoc: null and 'TDoc: not struct>( - query, parameters, mapFunc: System.Func, conn - ) = backgroundTask { - let! result = single<'TDoc> query parameters mapFunc.Invoke conn - return Option.toObj result - } - - /// Execute a query that returns no results - /// The query to retrieve the results - /// Parameters to use for the query - /// The SqliteConnection to use to execute the query - [] - let nonQuery query (parameters: SqliteParameter seq) (conn: SqliteConnection) = - use cmd = conn.CreateCommand() - cmd.CommandText <- query - cmd.Parameters.AddRange parameters - write cmd - - /// Execute a query that returns a scalar value - /// The query to retrieve the value - /// Parameters to use for the query - /// The mapping function to obtain the value - /// The SqliteConnection to use to execute the query - /// The scalar value for the query - [] - let scalar<'T when 'T : struct> query (parameters: SqliteParameter seq) (mapFunc: SqliteDataReader -> 'T) - (conn: SqliteConnection) = backgroundTask { - use cmd = conn.CreateCommand() - cmd.CommandText <- query - cmd.Parameters.AddRange parameters - use! rdr = cmd.ExecuteReaderAsync() - let! isFound = rdr.ReadAsync() - return if isFound then mapFunc rdr else Unchecked.defaultof<'T> - } - - /// Execute a query that returns a scalar value - /// The query to retrieve the value - /// Parameters to use for the query - /// The mapping function to obtain the value - /// The SqliteConnection to use to execute the query - /// The scalar value for the query - let Scalar<'T when 'T: struct>(query, parameters, mapFunc: System.Func, conn) = - scalar<'T> query parameters mapFunc.Invoke conn - - /// Functions to create tables and indexes - [] - module Definition = - - /// Create a document table - /// The table whose existence should be ensured (may include schema) - /// The SqliteConnection to use to execute the query - [] - let ensureTable name conn = backgroundTask { - do! Custom.nonQuery (Query.Definition.ensureTable name) [] conn - do! Custom.nonQuery (Query.Definition.ensureKey name SQLite) [] conn - } - - /// Create an index on field(s) within documents in the specified table - /// The table to be indexed (may include schema) - /// The name of the index to create - /// One or more fields to be indexed - /// The SqliteConnection to use to execute the query - [] - let ensureFieldIndex tableName indexName fields conn = - Custom.nonQuery (Query.Definition.ensureIndexOn tableName indexName fields SQLite) [] conn - - /// Commands to add documents - [] - module Document = - - /// Insert a new document - /// The table into which the document should be inserted (may include schema) - /// The document to be inserted - /// The SqliteConnection to use to execute the query - [] - let insert<'TDoc> tableName (document: 'TDoc) conn = - let query = - match Configuration.autoIdStrategy () with - | Disabled -> Query.insert tableName - | strategy -> - let idField = Configuration.idField () - let dataParam = - if AutoId.NeedsAutoId strategy document idField then - match strategy with - | Number -> $"(SELECT coalesce(max(data->>'{idField}'), 0) + 1 FROM {tableName})" - | Guid -> $"'{AutoId.GenerateGuid()}'" - | RandomString -> $"'{AutoId.GenerateRandomString(Configuration.idStringLength ())}'" - | Disabled -> "@data" - |> function it -> $"json_set(@data, '$.{idField}', {it})" - else "@data" - (Query.insert tableName).Replace("@data", dataParam) - Custom.nonQuery query [ jsonParam "@data" document ] conn - - /// - /// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert") - /// - /// The table into which the document should be saved (may include schema) - /// The document to be saved - /// The SqliteConnection to use to execute the query - [] - let save<'TDoc> tableName (document: 'TDoc) conn = - Custom.nonQuery (Query.save tableName) [ jsonParam "@data" document ] conn - - /// Commands to count documents - [] - module Count = - - /// Count all documents in a table - /// The table in which documents should be counted (may include schema) - /// The SqliteConnection to use to execute the query - /// The count of the documents in the table - [] - let all tableName conn = - Custom.scalar (Query.count tableName) [] toCount conn - - /// Count matching documents using JSON field comparisons (->> =, etc.) - /// The table in which documents should be counted (may include schema) - /// Whether to match any or all of the field conditions - /// The field conditions to match - /// The SqliteConnection to use to execute the query - /// The count of matching documents in the table - [] - let byFields tableName howMatched fields conn = - Custom.scalar - (Query.byFields (Query.count tableName) howMatched fields) (addFieldParams fields []) toCount conn - - /// Commands to determine if documents exist - [] - module Exists = - - /// Determine if a document exists for the given ID - /// The table in which existence should be checked (may include schema) - /// The ID of the document whose existence should be checked - /// The SqliteConnection to use to execute the query - /// True if a document exists, false if not - [] - let byId tableName (docId: 'TKey) conn = - Custom.scalar (Query.exists tableName (Query.whereById docId)) [ idParam docId ] toExists conn - - /// Determine if a document exists using JSON field comparisons (->> =, etc.) - /// The table in which existence should be checked (may include schema) - /// Whether to match any or all of the field conditions - /// The field conditions to match - /// The SqliteConnection to use to execute the query - /// True if any matching documents exist, false if not - [] - let byFields tableName howMatched fields conn = - Custom.scalar - (Query.exists tableName (Query.whereByFields howMatched fields)) - (addFieldParams fields []) - toExists - conn - - /// Commands to retrieve documents - [] - module Find = - - /// Retrieve all documents in the given table - /// The table from which documents should be retrieved (may include schema) - /// The SqliteConnection to use to execute the query - /// All documents from the given table - [] - let all<'TDoc> tableName conn = - Custom.list<'TDoc> (Query.find tableName) [] fromData<'TDoc> conn - - /// Retrieve all documents in the given table - /// The table from which documents should be retrieved (may include schema) - /// The SqliteConnection to use to execute the query - /// All documents from the given table - let All<'TDoc>(tableName, conn) = - Custom.List(Query.find tableName, [], fromData<'TDoc>, conn) - - /// Retrieve all documents in the given table ordered by the given fields in the document - /// The table from which documents should be retrieved (may include schema) - /// Fields by which the results should be ordered - /// The SqliteConnection to use to execute the query - /// All documents from the given table, ordered by the given fields - [] - let allOrdered<'TDoc> tableName orderFields conn = - Custom.list<'TDoc> (Query.find tableName + Query.orderBy orderFields SQLite) [] fromData<'TDoc> conn - - /// Retrieve all documents in the given table ordered by the given fields in the document - /// The table from which documents should be retrieved (may include schema) - /// Fields by which the results should be ordered - /// The SqliteConnection to use to execute the query - /// All documents from the given table, ordered by the given fields - let AllOrdered<'TDoc>(tableName, orderFields, conn) = - Custom.List(Query.find tableName + Query.orderBy orderFields SQLite, [], fromData<'TDoc>, conn) - - /// Retrieve a document by its ID - /// The table from which a document should be retrieved (may include schema) - /// The ID of the document to retrieve - /// The SqliteConnection to use to execute the query - /// Some with the document if found, None otherwise - [] - let byId<'TKey, 'TDoc> tableName (docId: 'TKey) conn = - Custom.single<'TDoc> (Query.byId (Query.find tableName) docId) [ idParam docId ] fromData<'TDoc> conn - - /// Retrieve a document by its ID - /// The table from which a document should be retrieved (may include schema) - /// The ID of the document to retrieve - /// The SqliteConnection to use to execute the query - /// The document if found, null otherwise - let ById<'TKey, 'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, docId: 'TKey, conn) = - Custom.Single<'TDoc>(Query.byId (Query.find tableName) docId, [ idParam docId ], fromData<'TDoc>, conn) - - /// Retrieve documents matching JSON field comparisons (->> =, etc.) - /// The table from which documents should be retrieved (may include schema) - /// Whether to match any or all of the field conditions - /// The field conditions to match - /// The SqliteConnection to use to execute the query - /// All documents matching the given fields - [] - let byFields<'TDoc> tableName howMatched fields conn = - Custom.list<'TDoc> - (Query.byFields (Query.find tableName) howMatched fields) - (addFieldParams fields []) - fromData<'TDoc> - conn - - /// Retrieve documents matching JSON field comparisons (->> =, etc.) - /// The table from which documents should be retrieved (may include schema) - /// Whether to match any or all of the field conditions - /// The field conditions to match - /// The SqliteConnection to use to execute the query - /// All documents matching the given fields - let ByFields<'TDoc>(tableName, howMatched, fields, conn) = - Custom.List<'TDoc>( - Query.byFields (Query.find tableName) howMatched fields, - addFieldParams fields [], - fromData<'TDoc>, - conn) - - /// - /// Retrieve documents matching JSON field comparisons (->> =, etc.) ordered by the given fields - /// in the document - /// - /// The table from which documents should be retrieved (may include schema) - /// Whether to match any or all of the field conditions - /// The field conditions to match - /// Fields by which the results should be ordered - /// The SqliteConnection to use to execute the query - /// All documents matching the given fields, ordered by the other given fields - [] - let byFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields conn = - Custom.list<'TDoc> - (Query.byFields (Query.find tableName) howMatched queryFields + Query.orderBy orderFields SQLite) - (addFieldParams queryFields []) - fromData<'TDoc> - conn - - /// - /// Retrieve documents matching JSON field comparisons (->> =, etc.) ordered by the given fields - /// in the document - /// - /// The table from which documents should be retrieved (may include schema) - /// Whether to match any or all of the field conditions - /// The field conditions to match - /// Fields by which the results should be ordered - /// The SqliteConnection to use to execute the query - /// All documents matching the given fields, ordered by the other given fields - let ByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, conn) = - Custom.List<'TDoc>( - Query.byFields (Query.find tableName) howMatched queryFields + Query.orderBy orderFields SQLite, - addFieldParams queryFields [], - fromData<'TDoc>, - conn) - - /// Retrieve the first document matching JSON field comparisons (->> =, etc.) - /// The table from which a document should be retrieved (may include schema) - /// Whether to match any or all of the field conditions - /// The field conditions to match - /// The SqliteConnection to use to execute the query - /// Some with the first document, or None if not found - [] - let firstByFields<'TDoc> tableName howMatched fields conn = - Custom.single - $"{Query.byFields (Query.find tableName) howMatched fields} LIMIT 1" - (addFieldParams fields []) - fromData<'TDoc> - conn - - /// Retrieve the first document matching JSON field comparisons (->> =, etc.) - /// The table from which a document should be retrieved (may include schema) - /// Whether to match any or all of the field conditions - /// The field conditions to match - /// The SqliteConnection to use to execute the query - /// The first document, or null if not found - let FirstByFields<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, howMatched, fields, conn) = - Custom.Single( - $"{Query.byFields (Query.find tableName) howMatched fields} LIMIT 1", - addFieldParams fields [], - fromData<'TDoc>, - conn) - - /// - /// Retrieve the first document matching JSON field comparisons (->> =, etc.) ordered by the - /// given fields in the document - /// - /// The table from which a document should be retrieved (may include schema) - /// Whether to match any or all of the field conditions - /// The field conditions to match - /// Fields by which the results should be ordered - /// The SqliteConnection to use to execute the query - /// - /// Some with the first document ordered by the given fields, or None if not found - /// - [] - let firstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields conn = - Custom.single - $"{Query.byFields (Query.find tableName) howMatched queryFields}{Query.orderBy orderFields SQLite} LIMIT 1" - (addFieldParams queryFields []) - fromData<'TDoc> - conn - - /// - /// Retrieve the first document matching JSON field comparisons (->> =, etc.) ordered by the - /// given fields in the document - /// - /// The table from which a document should be retrieved (may include schema) - /// Whether to match any or all of the field conditions - /// The field conditions to match - /// Fields by which the results should be ordered - /// The SqliteConnection to use to execute the query - /// The first document ordered by the given fields, or null if not found - let FirstByFieldsOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>( - tableName, howMatched, queryFields, orderFields, conn) = - Custom.Single( - $"{Query.byFields (Query.find tableName) howMatched queryFields}{Query.orderBy orderFields SQLite} LIMIT 1", - addFieldParams queryFields [], - fromData<'TDoc>, - conn) - - /// Commands to update documents - [] - module Update = - - /// Update (replace) an entire document by its ID - /// The table in which a document should be updated (may include schema) - /// The ID of the document to be updated (replaced) - /// The new document - /// The SqliteConnection to use to execute the query - [] - let byId tableName (docId: 'TKey) (document: 'TDoc) conn = - Custom.nonQuery - (Query.statementWhere (Query.update tableName) (Query.whereById docId)) - [ idParam docId; jsonParam "@data" document ] - conn - - /// - /// Update (replace) an entire document by its ID, using the provided function to obtain the ID from the - /// document - /// - /// The table in which a document should be updated (may include schema) - /// The function to obtain the ID of the document - /// The new document - /// The SqliteConnection to use to execute the query - [] - let byFunc tableName (idFunc: 'TDoc -> 'TKey) (document: 'TDoc) conn = - byId tableName (idFunc document) document conn - - /// - /// Update (replace) an entire document by its ID, using the provided function to obtain the ID from the - /// document - /// - /// The table in which a document should be updated (may include schema) - /// The function to obtain the ID of the document - /// The new document - /// The SqliteConnection to use to execute the query - let ByFunc(tableName, idFunc: System.Func<'TDoc, 'TKey>, document: 'TDoc, conn) = - byFunc tableName idFunc.Invoke document conn - - /// Commands to patch (partially update) documents - [] - module Patch = - - /// Patch a document by its ID - /// The table in which a document should be patched (may include schema) - /// The ID of the document to patch - /// The partial document to patch the existing document - /// The SqliteConnection to use to execute the query - [] - let byId tableName (docId: 'TKey) (patch: 'TPatch) conn = - Custom.nonQuery - (Query.byId (Query.patch tableName) docId) [ idParam docId; jsonParam "@data" patch ] conn - - /// - /// Patch documents using a JSON field comparison query in the WHERE clause (->> =, - /// etc.) - /// - /// The table in which documents should be patched (may include schema) - /// Whether to match any or all of the field conditions - /// The field conditions to match - /// The partial document to patch the existing document - /// The SqliteConnection to use to execute the query - [] - let byFields tableName howMatched fields (patch: 'TPatch) conn = - Custom.nonQuery - (Query.byFields (Query.patch tableName) howMatched fields) - (addFieldParams fields [ jsonParam "@data" patch ]) - conn - - /// Commands to remove fields from documents - [] - module RemoveFields = - - /// Remove fields from a document by the document's ID - /// The table in which a document should be modified (may include schema) - /// The ID of the document to modify - /// One or more field names to remove from the document - /// The SqliteConnection to use to execute the query - [] - let byId tableName (docId: 'TKey) fieldNames conn = - let nameParams = fieldNameParams "@name" fieldNames - Custom.nonQuery - (Query.byId (Query.removeFields tableName nameParams) docId) - (idParam docId |> Seq.singleton |> Seq.append nameParams) - conn - - /// Remove fields from documents via a comparison on JSON fields in the document - /// The table in which documents should be modified (may include schema) - /// Whether to match any or all of the field conditions - /// The field conditions to match - /// One or more field names to remove from the matching documents - /// The SqliteConnection to use to execute the query - [] - let byFields tableName howMatched fields fieldNames conn = - let nameParams = fieldNameParams "@name" fieldNames - Custom.nonQuery - (Query.byFields (Query.removeFields tableName nameParams) howMatched fields) - (addFieldParams fields nameParams) - conn - - /// Commands to delete documents - [] - module Delete = - - /// Delete a document by its ID - /// The table in which a document should be deleted (may include schema) - /// The ID of the document to delete - /// The SqliteConnection to use to execute the query - [] - let byId tableName (docId: 'TKey) conn = - Custom.nonQuery (Query.byId (Query.delete tableName) docId) [ idParam docId ] conn - - /// Delete documents by matching a JSON field comparison query (->> =, etc.) - /// The table in which documents should be deleted (may include schema) - /// Whether to match any or all of the field conditions - /// The field conditions to match - /// The SqliteConnection to use to execute the query - [] - let byFields tableName howMatched fields conn = - Custom.nonQuery (Query.byFields (Query.delete tableName) howMatched fields) (addFieldParams fields []) conn - - -/// Commands to execute custom SQL queries -[] -module Custom = - - /// Execute a query that returns a list of results - /// The query to retrieve the results - /// Parameters to use for the query - /// The mapping function between the document and the domain item - /// A list of results for the given query - [] - let list<'TDoc> query parameters (mapFunc: SqliteDataReader -> 'TDoc) = - use conn = Configuration.dbConn () - WithConn.Custom.list<'TDoc> query parameters mapFunc conn - - /// Execute a query that returns a list of results - /// The query to retrieve the results - /// Parameters to use for the query - /// The mapping function between the document and the domain item - /// A list of results for the given query - let List<'TDoc>(query, parameters, mapFunc: System.Func) = - use conn = Configuration.dbConn () - WithConn.Custom.List<'TDoc>(query, parameters, mapFunc, conn) - - /// Execute a query that returns one or no results - /// The query to retrieve the results - /// Parameters to use for the query - /// The mapping function between the document and the domain item - /// Some with the first matching result, or None if not found - [] - let single<'TDoc> query parameters (mapFunc: SqliteDataReader -> 'TDoc) = - use conn = Configuration.dbConn () - WithConn.Custom.single<'TDoc> query parameters mapFunc conn - - /// Execute a query that returns one or no results - /// The query to retrieve the results - /// Parameters to use for the query - /// The mapping function between the document and the domain item - /// The first matching result, or null if not found - let Single<'TDoc when 'TDoc: null and 'TDoc: not struct>( - query, parameters, mapFunc: System.Func) = - use conn = Configuration.dbConn () - WithConn.Custom.Single<'TDoc>(query, parameters, mapFunc, conn) - - /// Execute a query that returns no results - /// The query to retrieve the results - /// Parameters to use for the query - [] - let nonQuery query parameters = - use conn = Configuration.dbConn () - WithConn.Custom.nonQuery query parameters conn - - /// Execute a query that returns a scalar value - /// The query to retrieve the value - /// Parameters to use for the query - /// The mapping function to obtain the value - /// The scalar value for the query - [] - let scalar<'T when 'T: struct> query parameters (mapFunc: SqliteDataReader -> 'T) = - use conn = Configuration.dbConn () - WithConn.Custom.scalar<'T> query parameters mapFunc conn - - /// Execute a query that returns a scalar value - /// The query to retrieve the value - /// Parameters to use for the query - /// The mapping function to obtain the value - /// The scalar value for the query - let Scalar<'T when 'T: struct>(query, parameters, mapFunc: System.Func) = - use conn = Configuration.dbConn () - WithConn.Custom.Scalar<'T>(query, parameters, mapFunc, conn) - - -/// Functions to create tables and indexes -[] -module Definition = - - /// Create a document table - /// The table whose existence should be ensured (may include schema) - [] - let ensureTable name = - use conn = Configuration.dbConn () - WithConn.Definition.ensureTable name conn - - /// Create an index on field(s) within documents in the specified table - /// The table to be indexed (may include schema) - /// The name of the index to create - /// One or more fields to be indexed - [] - let ensureFieldIndex tableName indexName fields = - use conn = Configuration.dbConn () - WithConn.Definition.ensureFieldIndex tableName indexName fields conn - - -/// Document insert/save functions -[] -module Document = - - /// Insert a new document - /// The table into which the document should be inserted (may include schema) - /// The document to be inserted - [] - let insert<'TDoc> tableName (document: 'TDoc) = - use conn = Configuration.dbConn () - WithConn.Document.insert tableName document conn - - /// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert") - /// The table into which the document should be saved (may include schema) - /// The document to be saved - [] - let save<'TDoc> tableName (document: 'TDoc) = - use conn = Configuration.dbConn () - WithConn.Document.save tableName document conn - - -/// Commands to count documents -[] -module Count = - - /// Count all documents in a table - /// The table in which documents should be counted (may include schema) - /// The count of the documents in the table - [] - let all tableName = - use conn = Configuration.dbConn () - WithConn.Count.all tableName conn - - /// Count matching documents using JSON field comparisons (->> =, etc.) - /// The table in which documents should be counted (may include schema) - /// Whether to match any or all of the field conditions - /// The field conditions to match - /// The count of matching documents in the table - [] - let byFields tableName howMatched fields = - use conn = Configuration.dbConn () - WithConn.Count.byFields tableName howMatched fields conn - - -/// Commands to determine if documents exist -[] -module Exists = - - /// Determine if a document exists for the given ID - /// The table in which existence should be checked (may include schema) - /// The ID of the document whose existence should be checked - /// True if a document exists, false if not - [] - let byId tableName (docId: 'TKey) = - use conn = Configuration.dbConn () - WithConn.Exists.byId tableName docId conn - - /// Determine if a document exists using JSON field comparisons (->> =, etc.) - /// The table in which existence should be checked (may include schema) - /// Whether to match any or all of the field conditions - /// The field conditions to match - /// True if any matching documents exist, false if not - [] - let byFields tableName howMatched fields = - use conn = Configuration.dbConn () - WithConn.Exists.byFields tableName howMatched fields conn - - -/// Commands to retrieve documents -[] -module Find = - - /// Retrieve all documents in the given table - /// The table from which documents should be retrieved (may include schema) - /// All documents from the given table - [] - let all<'TDoc> tableName = - use conn = Configuration.dbConn () - WithConn.Find.all<'TDoc> tableName conn - - /// Retrieve all documents in the given table - /// The table from which documents should be retrieved (may include schema) - /// All documents from the given table - let All<'TDoc> tableName = - use conn = Configuration.dbConn () - WithConn.Find.All<'TDoc>(tableName, conn) - - /// Retrieve all documents in the given table ordered by the given fields in the document - /// The table from which documents should be retrieved (may include schema) - /// Fields by which the results should be ordered - /// All documents from the given table, ordered by the given fields - [] - let allOrdered<'TDoc> tableName orderFields = - use conn = Configuration.dbConn () - WithConn.Find.allOrdered<'TDoc> tableName orderFields conn - - /// Retrieve all documents in the given table ordered by the given fields in the document - /// The table from which documents should be retrieved (may include schema) - /// Fields by which the results should be ordered - /// All documents from the given table, ordered by the given fields - let AllOrdered<'TDoc> tableName orderFields = - use conn = Configuration.dbConn () - WithConn.Find.AllOrdered<'TDoc>(tableName, orderFields, conn) - - /// Retrieve a document by its ID - /// The table from which a document should be retrieved (may include schema) - /// The ID of the document to retrieve - /// Some with the document if found, None otherwise - [] - let byId<'TKey, 'TDoc> tableName docId = - use conn = Configuration.dbConn () - WithConn.Find.byId<'TKey, 'TDoc> tableName docId conn - - /// Retrieve a document by its ID - /// The table from which a document should be retrieved (may include schema) - /// The ID of the document to retrieve - /// The document if found, null otherwise - let ById<'TKey, 'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, docId) = - use conn = Configuration.dbConn () - WithConn.Find.ById<'TKey, 'TDoc>(tableName, docId, conn) - - /// Retrieve documents matching JSON field comparisons (->> =, etc.) - /// The table from which documents should be retrieved (may include schema) - /// Whether to match any or all of the field conditions - /// The field conditions to match - /// All documents matching the given fields - [] - let byFields<'TDoc> tableName howMatched fields = - use conn = Configuration.dbConn () - WithConn.Find.byFields<'TDoc> tableName howMatched fields conn - - /// Retrieve documents matching JSON field comparisons (->> =, etc.) - /// The table from which documents should be retrieved (may include schema) - /// Whether to match any or all of the field conditions - /// The field conditions to match - /// All documents matching the given fields - let ByFields<'TDoc>(tableName, howMatched, fields) = - use conn = Configuration.dbConn () - WithConn.Find.ByFields<'TDoc>(tableName, howMatched, fields, conn) - - /// - /// Retrieve documents matching JSON field comparisons (->> =, etc.) ordered by the given fields in - /// the document - /// - /// The table from which documents should be retrieved (may include schema) - /// Whether to match any or all of the field conditions - /// The field conditions to match - /// Fields by which the results should be ordered - /// All documents matching the given fields, ordered by the other given fields - [] - let byFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields = - use conn = Configuration.dbConn () - WithConn.Find.byFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields conn - - /// - /// Retrieve documents matching JSON field comparisons (->> =, etc.) ordered by the given fields in - /// the document - /// - /// The table from which documents should be retrieved (may include schema) - /// Whether to match any or all of the field conditions - /// The field conditions to match - /// Fields by which the results should be ordered - /// All documents matching the given fields, ordered by the other given fields - let ByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields) = - use conn = Configuration.dbConn () - WithConn.Find.ByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, conn) - - /// Retrieve the first document matching JSON field comparisons (->> =, etc.) - /// The table from which a document should be retrieved (may include schema) - /// Whether to match any or all of the field conditions - /// The field conditions to match - /// Some with the first document, or None if not found - [] - let firstByFields<'TDoc> tableName howMatched fields = - use conn = Configuration.dbConn () - WithConn.Find.firstByFields<'TDoc> tableName howMatched fields conn - - /// Retrieve the first document matching JSON field comparisons (->> =, etc.) - /// The table from which a document should be retrieved (may include schema) - /// Whether to match any or all of the field conditions - /// The field conditions to match - /// The first document, or null if not found - let FirstByFields<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, howMatched, fields) = - use conn = Configuration.dbConn () - WithConn.Find.FirstByFields<'TDoc>(tableName, howMatched, fields, conn) - - /// - /// Retrieve the first document matching JSON field comparisons (->> =, etc.) ordered by the given - /// fields in the document - /// - /// The table from which a document should be retrieved (may include schema) - /// Whether to match any or all of the field conditions - /// The field conditions to match - /// Fields by which the results should be ordered - /// - /// Some with the first document ordered by the given fields, or None if not found - /// - [] - let firstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields = - use conn = Configuration.dbConn () - WithConn.Find.firstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields conn - - /// - /// Retrieve the first document matching JSON field comparisons (->> =, etc.) ordered by the given - /// fields in the document - /// - /// The table from which a document should be retrieved (may include schema) - /// Whether to match any or all of the field conditions - /// The field conditions to match - /// Fields by which the results should be ordered - /// The first document ordered by the given fields, or null if not found - let FirstByFieldsOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>( - tableName, howMatched, queryFields, orderFields) = - use conn = Configuration.dbConn () - WithConn.Find.FirstByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, conn) - - -/// Commands to update documents -[] -module Update = - - /// Update (replace) an entire document by its ID - /// The table in which a document should be updated (may include schema) - /// The ID of the document to be updated (replaced) - /// The new document - [] - let byId tableName (docId: 'TKey) (document: 'TDoc) = - use conn = Configuration.dbConn () - WithConn.Update.byId tableName docId document conn - - /// - /// Update (replace) an entire document by its ID, using the provided function to obtain the ID from the document - /// - /// The table in which a document should be updated (may include schema) - /// The function to obtain the ID of the document - /// The new document - [] - let byFunc tableName (idFunc: 'TDoc -> 'TKey) (document: 'TDoc) = - use conn = Configuration.dbConn () - WithConn.Update.byFunc tableName idFunc document conn - - /// - /// Update (replace) an entire document by its ID, using the provided function to obtain the ID from the document - /// - /// The table in which a document should be updated (may include schema) - /// The function to obtain the ID of the document - /// The new document - let ByFunc(tableName, idFunc: System.Func<'TDoc, 'TKey>, document: 'TDoc) = - use conn = Configuration.dbConn () - WithConn.Update.ByFunc(tableName, idFunc, document, conn) - - -/// Commands to patch (partially update) documents -[] -module Patch = - - /// Patch a document by its ID - /// The table in which a document should be patched (may include schema) - /// The ID of the document to patch - /// The partial document to patch the existing document - [] - let byId tableName (docId: 'TKey) (patch: 'TPatch) = - use conn = Configuration.dbConn () - WithConn.Patch.byId tableName docId patch conn - - /// - /// Patch documents using a JSON field comparison query in the WHERE clause (->> =, etc.) - /// - /// The table in which documents should be patched (may include schema) - /// Whether to match any or all of the field conditions - /// The field conditions to match - /// The partial document to patch the existing document - [] - let byFields tableName howMatched fields (patch: 'TPatch) = - use conn = Configuration.dbConn () - WithConn.Patch.byFields tableName howMatched fields patch conn - - -/// Commands to remove fields from documents -[] -module RemoveFields = - - /// Remove fields from a document by the document's ID - /// The table in which a document should be modified (may include schema) - /// The ID of the document to modify - /// One or more field names to remove from the document - [] - let byId tableName (docId: 'TKey) fieldNames = - use conn = Configuration.dbConn () - WithConn.RemoveFields.byId tableName docId fieldNames conn - - /// Remove fields from documents via a comparison on JSON fields in the document - /// The table in which documents should be modified (may include schema) - /// Whether to match any or all of the field conditions - /// The field conditions to match - /// One or more field names to remove from the matching documents - [] - let byFields tableName howMatched fields fieldNames = - use conn = Configuration.dbConn () - WithConn.RemoveFields.byFields tableName howMatched fields fieldNames conn - - -/// Commands to delete documents -[] -module Delete = - - /// Delete a document by its ID - /// The table in which a document should be deleted (may include schema) - /// The ID of the document to delete - [] - let byId tableName (docId: 'TKey) = - use conn = Configuration.dbConn () - WithConn.Delete.byId tableName docId conn - - /// Delete documents by matching a JSON field comparison query (->> =, etc.) - /// The table in which documents should be deleted (may include schema) - /// Whether to match any or all of the field conditions - /// The field conditions to match - [] - let byFields tableName howMatched fields = - use conn = Configuration.dbConn () - WithConn.Delete.byFields tableName howMatched fields conn diff --git a/src/Sqlite/WithConn.fs b/src/Sqlite/WithConn.fs new file mode 100644 index 0000000..763678b --- /dev/null +++ b/src/Sqlite/WithConn.fs @@ -0,0 +1,510 @@ +/// Versions of queries that accept a SqliteConnection as the last parameter +module BitBadger.Documents.Sqlite.WithConn + +open BitBadger.Documents +open Microsoft.Data.Sqlite + +/// Commands to execute custom SQL queries +[] +module Custom = + + /// Execute a query that returns a list of results + /// The query to retrieve the results + /// Parameters to use for the query + /// The mapping function between the document and the domain item + /// The SqliteConnection to use to execute the query + /// A list of results for the given query + [] + let list<'TDoc> query (parameters: SqliteParameter seq) (mapFunc: SqliteDataReader -> 'TDoc) + (conn: SqliteConnection) = + use cmd = conn.CreateCommand() + cmd.CommandText <- query + cmd.Parameters.AddRange parameters + toCustomList<'TDoc> cmd mapFunc + + /// Execute a query that returns a list of results + /// The query to retrieve the results + /// Parameters to use for the query + /// The mapping function between the document and the domain item + /// The SqliteConnection to use to execute the query + /// A list of results for the given query + let List<'TDoc>( + query, parameters: SqliteParameter seq, mapFunc: System.Func, + conn: SqliteConnection + ) = + use cmd = conn.CreateCommand() + cmd.CommandText <- query + cmd.Parameters.AddRange parameters + ToCustomList<'TDoc>(cmd, mapFunc) + + /// Execute a query that returns one or no results + /// The query to retrieve the results + /// Parameters to use for the query + /// The mapping function between the document and the domain item + /// The SqliteConnection to use to execute the query + /// Some with the first matching result, or None if not found + [] + let single<'TDoc> query parameters (mapFunc: SqliteDataReader -> 'TDoc) conn = backgroundTask { + let! results = list query parameters mapFunc conn + return FSharp.Collections.List.tryHead results + } + + /// Execute a query that returns one or no results + /// The query to retrieve the results + /// Parameters to use for the query + /// The mapping function between the document and the domain item + /// The SqliteConnection to use to execute the query + /// The first matching result, or null if not found + let Single<'TDoc when 'TDoc: null and 'TDoc: not struct>( + query, parameters, mapFunc: System.Func, conn + ) = backgroundTask { + let! result = single<'TDoc> query parameters mapFunc.Invoke conn + return Option.toObj result + } + + /// Execute a query that returns no results + /// The query to retrieve the results + /// Parameters to use for the query + /// The SqliteConnection to use to execute the query + [] + let nonQuery query (parameters: SqliteParameter seq) (conn: SqliteConnection) = + use cmd = conn.CreateCommand() + cmd.CommandText <- query + cmd.Parameters.AddRange parameters + write cmd + + /// Execute a query that returns a scalar value + /// The query to retrieve the value + /// Parameters to use for the query + /// The mapping function to obtain the value + /// The SqliteConnection to use to execute the query + /// The scalar value for the query + [] + let scalar<'T when 'T : struct> query (parameters: SqliteParameter seq) (mapFunc: SqliteDataReader -> 'T) + (conn: SqliteConnection) = backgroundTask { + use cmd = conn.CreateCommand() + cmd.CommandText <- query + cmd.Parameters.AddRange parameters + use! rdr = cmd.ExecuteReaderAsync() + let! isFound = rdr.ReadAsync() + return if isFound then mapFunc rdr else Unchecked.defaultof<'T> + } + + /// Execute a query that returns a scalar value + /// The query to retrieve the value + /// Parameters to use for the query + /// The mapping function to obtain the value + /// The SqliteConnection to use to execute the query + /// The scalar value for the query + let Scalar<'T when 'T: struct>(query, parameters, mapFunc: System.Func, conn) = + scalar<'T> query parameters mapFunc.Invoke conn + +/// Functions to create tables and indexes +[] +module Definition = + + /// Create a document table + /// The table whose existence should be ensured (may include schema) + /// The SqliteConnection to use to execute the query + [] + let ensureTable name conn = backgroundTask { + do! Custom.nonQuery (Query.Definition.ensureTable name) [] conn + do! Custom.nonQuery (Query.Definition.ensureKey name SQLite) [] conn + } + + /// Create an index on field(s) within documents in the specified table + /// The table to be indexed (may include schema) + /// The name of the index to create + /// One or more fields to be indexed + /// The SqliteConnection to use to execute the query + [] + let ensureFieldIndex tableName indexName fields conn = + Custom.nonQuery (Query.Definition.ensureIndexOn tableName indexName fields SQLite) [] conn + +/// Commands to add documents +[] +module Document = + + /// Insert a new document + /// The table into which the document should be inserted (may include schema) + /// The document to be inserted + /// The SqliteConnection to use to execute the query + [] + let insert<'TDoc> tableName (document: 'TDoc) conn = + let query = + match Configuration.autoIdStrategy () with + | Disabled -> Query.insert tableName + | strategy -> + let idField = Configuration.idField () + let dataParam = + if AutoId.NeedsAutoId strategy document idField then + match strategy with + | Number -> $"(SELECT coalesce(max(data->>'{idField}'), 0) + 1 FROM {tableName})" + | Guid -> $"'{AutoId.GenerateGuid()}'" + | RandomString -> $"'{AutoId.GenerateRandomString(Configuration.idStringLength ())}'" + | Disabled -> "@data" + |> function it -> $"json_set(@data, '$.{idField}', {it})" + else "@data" + (Query.insert tableName).Replace("@data", dataParam) + Custom.nonQuery query [ jsonParam "@data" document ] conn + + /// + /// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert") + /// + /// The table into which the document should be saved (may include schema) + /// The document to be saved + /// The SqliteConnection to use to execute the query + [] + let save<'TDoc> tableName (document: 'TDoc) conn = + Custom.nonQuery (Query.save tableName) [ jsonParam "@data" document ] conn + +/// Commands to count documents +[] +module Count = + + /// Count all documents in a table + /// The table in which documents should be counted (may include schema) + /// The SqliteConnection to use to execute the query + /// The count of the documents in the table + [] + let all tableName conn = + Custom.scalar (Query.count tableName) [] toCount conn + + /// Count matching documents using JSON field comparisons (->> =, etc.) + /// The table in which documents should be counted (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// The SqliteConnection to use to execute the query + /// The count of matching documents in the table + [] + let byFields tableName howMatched fields conn = + Custom.scalar + (Query.byFields (Query.count tableName) howMatched fields) (addFieldParams fields []) toCount conn + +/// Commands to determine if documents exist +[] +module Exists = + + /// Determine if a document exists for the given ID + /// The table in which existence should be checked (may include schema) + /// The ID of the document whose existence should be checked + /// The SqliteConnection to use to execute the query + /// True if a document exists, false if not + [] + let byId tableName (docId: 'TKey) conn = + Custom.scalar (Query.exists tableName (Query.whereById docId)) [ idParam docId ] toExists conn + + /// Determine if a document exists using JSON field comparisons (->> =, etc.) + /// The table in which existence should be checked (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// The SqliteConnection to use to execute the query + /// True if any matching documents exist, false if not + [] + let byFields tableName howMatched fields conn = + Custom.scalar + (Query.exists tableName (Query.whereByFields howMatched fields)) + (addFieldParams fields []) + toExists + conn + +/// Commands to retrieve documents +[] +module Find = + + /// Retrieve all documents in the given table + /// The table from which documents should be retrieved (may include schema) + /// The SqliteConnection to use to execute the query + /// All documents from the given table + [] + let all<'TDoc> tableName conn = + Custom.list<'TDoc> (Query.find tableName) [] fromData<'TDoc> conn + + /// Retrieve all documents in the given table + /// The table from which documents should be retrieved (may include schema) + /// The SqliteConnection to use to execute the query + /// All documents from the given table + let All<'TDoc>(tableName, conn) = + Custom.List(Query.find tableName, [], fromData<'TDoc>, conn) + + /// Retrieve all documents in the given table ordered by the given fields in the document + /// The table from which documents should be retrieved (may include schema) + /// Fields by which the results should be ordered + /// The SqliteConnection to use to execute the query + /// All documents from the given table, ordered by the given fields + [] + let allOrdered<'TDoc> tableName orderFields conn = + Custom.list<'TDoc> (Query.find tableName + Query.orderBy orderFields SQLite) [] fromData<'TDoc> conn + + /// Retrieve all documents in the given table ordered by the given fields in the document + /// The table from which documents should be retrieved (may include schema) + /// Fields by which the results should be ordered + /// The SqliteConnection to use to execute the query + /// All documents from the given table, ordered by the given fields + let AllOrdered<'TDoc>(tableName, orderFields, conn) = + Custom.List(Query.find tableName + Query.orderBy orderFields SQLite, [], fromData<'TDoc>, conn) + + /// Retrieve a document by its ID + /// The table from which a document should be retrieved (may include schema) + /// The ID of the document to retrieve + /// The SqliteConnection to use to execute the query + /// Some with the document if found, None otherwise + [] + let byId<'TKey, 'TDoc> tableName (docId: 'TKey) conn = + Custom.single<'TDoc> (Query.byId (Query.find tableName) docId) [ idParam docId ] fromData<'TDoc> conn + + /// Retrieve a document by its ID + /// The table from which a document should be retrieved (may include schema) + /// The ID of the document to retrieve + /// The SqliteConnection to use to execute the query + /// The document if found, null otherwise + let ById<'TKey, 'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, docId: 'TKey, conn) = + Custom.Single<'TDoc>(Query.byId (Query.find tableName) docId, [ idParam docId ], fromData<'TDoc>, conn) + + /// Retrieve documents matching JSON field comparisons (->> =, etc.) + /// The table from which documents should be retrieved (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// The SqliteConnection to use to execute the query + /// All documents matching the given fields + [] + let byFields<'TDoc> tableName howMatched fields conn = + Custom.list<'TDoc> + (Query.byFields (Query.find tableName) howMatched fields) + (addFieldParams fields []) + fromData<'TDoc> + conn + + /// Retrieve documents matching JSON field comparisons (->> =, etc.) + /// The table from which documents should be retrieved (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// The SqliteConnection to use to execute the query + /// All documents matching the given fields + let ByFields<'TDoc>(tableName, howMatched, fields, conn) = + Custom.List<'TDoc>( + Query.byFields (Query.find tableName) howMatched fields, + addFieldParams fields [], + fromData<'TDoc>, + conn) + + /// + /// Retrieve documents matching JSON field comparisons (->> =, etc.) ordered by the given fields + /// in the document + /// + /// The table from which documents should be retrieved (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// Fields by which the results should be ordered + /// The SqliteConnection to use to execute the query + /// All documents matching the given fields, ordered by the other given fields + [] + let byFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields conn = + Custom.list<'TDoc> + (Query.byFields (Query.find tableName) howMatched queryFields + Query.orderBy orderFields SQLite) + (addFieldParams queryFields []) + fromData<'TDoc> + conn + + /// + /// Retrieve documents matching JSON field comparisons (->> =, etc.) ordered by the given fields + /// in the document + /// + /// The table from which documents should be retrieved (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// Fields by which the results should be ordered + /// The SqliteConnection to use to execute the query + /// All documents matching the given fields, ordered by the other given fields + let ByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, conn) = + Custom.List<'TDoc>( + Query.byFields (Query.find tableName) howMatched queryFields + Query.orderBy orderFields SQLite, + addFieldParams queryFields [], + fromData<'TDoc>, + conn) + + /// Retrieve the first document matching JSON field comparisons (->> =, etc.) + /// The table from which a document should be retrieved (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// The SqliteConnection to use to execute the query + /// Some with the first document, or None if not found + [] + let firstByFields<'TDoc> tableName howMatched fields conn = + Custom.single + $"{Query.byFields (Query.find tableName) howMatched fields} LIMIT 1" + (addFieldParams fields []) + fromData<'TDoc> + conn + + /// Retrieve the first document matching JSON field comparisons (->> =, etc.) + /// The table from which a document should be retrieved (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// The SqliteConnection to use to execute the query + /// The first document, or null if not found + let FirstByFields<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, howMatched, fields, conn) = + Custom.Single( + $"{Query.byFields (Query.find tableName) howMatched fields} LIMIT 1", + addFieldParams fields [], + fromData<'TDoc>, + conn) + + /// + /// Retrieve the first document matching JSON field comparisons (->> =, etc.) ordered by the + /// given fields in the document + /// + /// The table from which a document should be retrieved (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// Fields by which the results should be ordered + /// The SqliteConnection to use to execute the query + /// + /// Some with the first document ordered by the given fields, or None if not found + /// + [] + let firstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields conn = + Custom.single + $"{Query.byFields (Query.find tableName) howMatched queryFields}{Query.orderBy orderFields SQLite} LIMIT 1" + (addFieldParams queryFields []) + fromData<'TDoc> + conn + + /// + /// Retrieve the first document matching JSON field comparisons (->> =, etc.) ordered by the + /// given fields in the document + /// + /// The table from which a document should be retrieved (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// Fields by which the results should be ordered + /// The SqliteConnection to use to execute the query + /// The first document ordered by the given fields, or null if not found + let FirstByFieldsOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>( + tableName, howMatched, queryFields, orderFields, conn) = + Custom.Single( + $"{Query.byFields (Query.find tableName) howMatched queryFields}{Query.orderBy orderFields SQLite} LIMIT 1", + addFieldParams queryFields [], + fromData<'TDoc>, + conn) + +/// Commands to update documents +[] +module Update = + + /// Update (replace) an entire document by its ID + /// The table in which a document should be updated (may include schema) + /// The ID of the document to be updated (replaced) + /// The new document + /// The SqliteConnection to use to execute the query + [] + let byId tableName (docId: 'TKey) (document: 'TDoc) conn = + Custom.nonQuery + (Query.statementWhere (Query.update tableName) (Query.whereById docId)) + [ idParam docId; jsonParam "@data" document ] + conn + + /// + /// Update (replace) an entire document by its ID, using the provided function to obtain the ID from the + /// document + /// + /// The table in which a document should be updated (may include schema) + /// The function to obtain the ID of the document + /// The new document + /// The SqliteConnection to use to execute the query + [] + let byFunc tableName (idFunc: 'TDoc -> 'TKey) (document: 'TDoc) conn = + byId tableName (idFunc document) document conn + + /// + /// Update (replace) an entire document by its ID, using the provided function to obtain the ID from the + /// document + /// + /// The table in which a document should be updated (may include schema) + /// The function to obtain the ID of the document + /// The new document + /// The SqliteConnection to use to execute the query + let ByFunc(tableName, idFunc: System.Func<'TDoc, 'TKey>, document: 'TDoc, conn) = + byFunc tableName idFunc.Invoke document conn + +/// Commands to patch (partially update) documents +[] +module Patch = + + /// Patch a document by its ID + /// The table in which a document should be patched (may include schema) + /// The ID of the document to patch + /// The partial document to patch the existing document + /// The SqliteConnection to use to execute the query + [] + let byId tableName (docId: 'TKey) (patch: 'TPatch) conn = + Custom.nonQuery + (Query.byId (Query.patch tableName) docId) [ idParam docId; jsonParam "@data" patch ] conn + + /// + /// Patch documents using a JSON field comparison query in the WHERE clause (->> =, + /// etc.) + /// + /// The table in which documents should be patched (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// The partial document to patch the existing document + /// The SqliteConnection to use to execute the query + [] + let byFields tableName howMatched fields (patch: 'TPatch) conn = + Custom.nonQuery + (Query.byFields (Query.patch tableName) howMatched fields) + (addFieldParams fields [ jsonParam "@data" patch ]) + conn + +/// Commands to remove fields from documents +[] +module RemoveFields = + + /// Remove fields from a document by the document's ID + /// The table in which a document should be modified (may include schema) + /// The ID of the document to modify + /// One or more field names to remove from the document + /// The SqliteConnection to use to execute the query + [] + let byId tableName (docId: 'TKey) fieldNames conn = + let nameParams = fieldNameParams "@name" fieldNames + Custom.nonQuery + (Query.byId (Query.removeFields tableName nameParams) docId) + (idParam docId |> Seq.singleton |> Seq.append nameParams) + conn + + /// Remove fields from documents via a comparison on JSON fields in the document + /// The table in which documents should be modified (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// One or more field names to remove from the matching documents + /// The SqliteConnection to use to execute the query + [] + let byFields tableName howMatched fields fieldNames conn = + let nameParams = fieldNameParams "@name" fieldNames + Custom.nonQuery + (Query.byFields (Query.removeFields tableName nameParams) howMatched fields) + (addFieldParams fields nameParams) + conn + +/// Commands to delete documents +[] +module Delete = + + /// Delete a document by its ID + /// The table in which a document should be deleted (may include schema) + /// The ID of the document to delete + /// The SqliteConnection to use to execute the query + [] + let byId tableName (docId: 'TKey) conn = + Custom.nonQuery (Query.byId (Query.delete tableName) docId) [ idParam docId ] conn + + /// Delete documents by matching a JSON field comparison query (->> =, etc.) + /// The table in which documents should be deleted (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// The SqliteConnection to use to execute the query + [] + let byFields tableName howMatched fields conn = + Custom.nonQuery (Query.byFields (Query.delete tableName) howMatched fields) (addFieldParams fields []) conn