/// Versions of queries that accept a SqliteConnection as the last parameter
module BitBadger.Documents.Sqlite.WithConn
open System.IO
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 a JSON array of results
    /// The query to retrieve the results
    /// Parameters to use for the query
    /// The mapping function to extract the document
    /// The SqliteConnection to use to execute the query
    /// A JSON array of results for the given query
    []
    let jsonArray
            query
            (parameters: SqliteParameter seq)
            (mapFunc: SqliteDataReader -> string)
            (conn: SqliteConnection) =
        use cmd = conn.CreateCommand()
        cmd.CommandText <- query
        cmd.Parameters.AddRange parameters
        toJsonArray cmd mapFunc
    /// Execute a query that returns a JSON array of results
    /// The query to retrieve the results
    /// Parameters to use for the query
    /// The mapping function to extract the document
    /// The SqliteConnection to use to execute the query
    /// A JSON array of results for the given query
    let JsonArray(query, parameters, mapFunc: System.Func, conn) =
        jsonArray query parameters mapFunc.Invoke conn
    /// Execute a query, writing its results to the given StreamWriter
    /// The query to retrieve the results
    /// Parameters to use for the query
    /// The StreamWriter to which the results should be written
    /// The mapping function to extract the document
    /// The SqliteConnection to use to execute the query
    []
    let writeJsonArray
            query
            (parameters: SqliteParameter seq)
            writer
            (mapFunc: SqliteDataReader -> string)
            (conn: SqliteConnection) =
        use cmd = conn.CreateCommand()
        cmd.CommandText <- query
        cmd.Parameters.AddRange parameters
        writeJsonArray cmd writer mapFunc
    /// Execute a query, writing its results to the given StreamWriter
    /// The query to retrieve the results
    /// Parameters to use for the query
    /// The StreamWriter to which the results should be written
    /// The mapping function to extract the document
    /// The SqliteConnection to use to execute the query
    let WriteJsonArray(query, parameters, writer, mapFunc: System.Func, conn) =
        writeJsonArray query parameters writer mapFunc.Invoke 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 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 one or no JSON documents
    /// The query to retrieve the results
    /// Parameters to use for the query
    /// The mapping function to extract the document
    /// The SqliteConnection to use to execute the query
    /// The JSON document with the first matching result, or an empty document if not found
    []
    let jsonSingle query parameters mapFunc conn = backgroundTask {
        let! results = jsonArray $"%s{query} LIMIT 1" parameters mapFunc conn
        return if results = "[]" then "{}" else results[1..results.Length - 2]
    }
    /// Execute a query that returns one or no JSON documents
    /// The query to retrieve the results
    /// Parameters to use for the query
    /// The mapping function to extract the document
    /// The SqliteConnection to use to execute the query
    /// The JSON document with the first matching result, or an empty document if not found
    let JsonSingle(query, parameters, mapFunc: System.Func, conn) =
        jsonSingle query parameters mapFunc.Invoke conn
    /// 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 as domain items
[]
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 retrieve documents as raw JSON
[]
module Json =
    /// Retrieve all JSON 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 JSON documents from the given table
    []
    let all tableName conn =
        Custom.jsonArray (Query.find tableName) [] jsonFromData conn
    /// Retrieve all JSON 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 JSON documents from the given table, ordered by the given fields
    []
    let allOrdered tableName orderFields conn =
        Custom.jsonArray (Query.find tableName + Query.orderBy orderFields SQLite) [] jsonFromData conn
    /// Retrieve a JSON 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 JSON document if found, an empty JSON document otherwise
    []
    let byId<'TKey> tableName (docId: 'TKey) conn =
        Custom.jsonSingle (Query.byId (Query.find tableName) docId) [ idParam docId ] jsonFromData conn
    /// Retrieve JSON 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 JSON documents matching the given fields
    []
    let byFields tableName howMatched fields conn =
        Custom.jsonArray
            (Query.byFields (Query.find tableName) howMatched fields) (addFieldParams fields []) jsonFromData conn
    /// 
    /// Retrieve JSON 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 JSON documents matching the given fields, ordered by the other given fields
    []
    let byFieldsOrdered tableName howMatched queryFields orderFields conn =
        Custom.jsonArray
            (Query.byFields (Query.find tableName) howMatched queryFields + Query.orderBy orderFields SQLite)
            (addFieldParams queryFields [])
            jsonFromData
            conn
    /// Retrieve the first JSON 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 JSON document if found, an empty JSON document otherwise
    []
    let firstByFields tableName howMatched fields conn =
        Custom.jsonSingle
            (Query.byFields (Query.find tableName) howMatched fields) (addFieldParams fields []) jsonFromData conn
    /// 
    /// Retrieve the first JSON 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 JSON document (in order) if found, an empty JSON document otherwise
    []
    let firstByFieldsOrdered tableName howMatched queryFields orderFields conn =
        Custom.jsonSingle
            (Query.byFields (Query.find tableName) howMatched queryFields + Query.orderBy orderFields SQLite)
            (addFieldParams queryFields [])
            jsonFromData
            conn
    /// Write all JSON documents in the given table to the given StreamWriter
    /// The table from which documents should be retrieved (may include schema)
    /// The StreamWriter to which the results should be written
    /// The SqliteConnection to use to execute the query
    []
    let writeAll tableName writer conn =
        Custom.writeJsonArray (Query.find tableName) [] writer jsonFromData conn
    /// 
    /// Write all JSON all documents in the given table to the given StreamWriter, ordered by the given fields in
    /// the document
    /// 
    /// The table from which documents should be retrieved (may include schema)
    /// The StreamWriter to which the results should be written
    /// Fields by which the results should be ordered
    /// The SqliteConnection to use to execute the query
    []
    let writeAllOrdered tableName writer orderFields conn =
        Custom.writeJsonArray (Query.find tableName + Query.orderBy orderFields SQLite) [] writer jsonFromData conn
    /// Write a JSON document to the given StreamWriter by its ID
    /// The table from which a document should be retrieved (may include schema)
    /// The StreamWriter to which the results should be written
    /// The ID of the document to retrieve
    /// The SqliteConnection to use to execute the query
    []
    let writeById<'TKey> tableName (writer: StreamWriter) (docId: 'TKey) conn = backgroundTask {
        let! json = Custom.jsonSingle (Query.byId (Query.find tableName) docId) [ idParam docId ] jsonFromData conn
        do! writer.WriteAsync json
    }
    /// 
    /// Write JSON documents to the given StreamWriter matching JSON field comparisons (->> =, etc.)
    /// 
    /// The table from which documents should be retrieved (may include schema)
    /// The StreamWriter to which the results should be written
    /// Whether to match any or all of the field conditions
    /// The field conditions to match
    /// The SqliteConnection to use to execute the query
    []
    let writeByFields tableName writer howMatched fields conn =
        Custom.writeJsonArray
            (Query.byFields (Query.find tableName) howMatched fields)
            (addFieldParams fields [])
            writer
            jsonFromData
            conn
    /// 
    /// Write JSON documents to the given StreamWriter matching JSON field comparisons (->> =, etc.)
    /// ordered by the given fields in the document
    /// 
    /// The table from which documents should be retrieved (may include schema)
    /// The StreamWriter to which the results should be written
    /// 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
    []
    let writeByFieldsOrdered tableName writer howMatched queryFields orderFields conn =
        Custom.writeJsonArray
            (Query.byFields (Query.find tableName) howMatched queryFields + Query.orderBy orderFields SQLite)
            (addFieldParams queryFields [])
            writer
            jsonFromData
            conn
    /// 
    /// Write the first JSON document to the given StreamWriter matching JSON field comparisons
    /// (->> =, etc.)
    /// 
    /// The table from which a document should be retrieved (may include schema)
    /// The StreamWriter to which the results should be written
    /// Whether to match any or all of the field conditions
    /// The field conditions to match
    /// The SqliteConnection to use to execute the query
    []
    let writeFirstByFields tableName (writer: StreamWriter) howMatched fields conn = backgroundTask {
        let! json =
            Custom.jsonSingle
                (Query.byFields (Query.find tableName) howMatched fields) (addFieldParams fields []) jsonFromData conn
        do! writer.WriteAsync json
    }
    /// 
    /// Write the first JSON document to the given StreamWriter 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)
    /// The StreamWriter to which the results should be written
    /// 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
    []
    let writeFirstByFieldsOrdered tableName (writer: StreamWriter) howMatched queryFields orderFields conn =
        backgroundTask {
            let! json =
                Custom.jsonSingle
                    (Query.byFields (Query.find tableName) howMatched queryFields + Query.orderBy orderFields SQLite)
                    (addFieldParams queryFields [])
                    jsonFromData
                    conn
            do! writer.WriteAsync json
        }
/// 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