diff --git a/src/Postgres/Extensions.fs b/src/Postgres/Extensions.fs index 1622cef..568b51b 100644 --- a/src/Postgres/Extensions.fs +++ b/src/Postgres/Extensions.fs @@ -4,7 +4,7 @@ open Npgsql open Npgsql.FSharp open WithProps -/// F# Extensions for the NpgsqlConnection type +/// F# Extensions for the NpgsqlConnection type [] module Extensions = @@ -377,247 +377,455 @@ module Extensions = open System.Runtime.CompilerServices -/// C# extensions on the NpgsqlConnection type +/// C# extensions on the NpgsqlConnection type type NpgsqlConnectionCSharpExtensions = - /// Execute a query that returns a list of results + /// Execute a query that returns a list of results + /// The NpgsqlConnection on which to run the query + /// 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 [] static member inline CustomList<'TDoc>(conn, query, parameters, mapFunc: System.Func) = Custom.List<'TDoc>(query, parameters, mapFunc, Sql.existingConnection conn) - /// Execute a query that returns one or no results; returns None if not found + /// Execute a query that returns one or no results + /// The NpgsqlConnection on which to run the query + /// 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 [] static member inline CustomSingle<'TDoc when 'TDoc: null and 'TDoc: not struct>( conn, query, parameters, mapFunc: System.Func) = Custom.Single<'TDoc>(query, parameters, mapFunc, Sql.existingConnection conn) - /// Execute a query that returns no results + /// Execute a query that returns no results + /// The NpgsqlConnection on which to run the query + /// The query to retrieve the results + /// Parameters to use for the query [] static member inline CustomNonQuery(conn, query, parameters) = Custom.nonQuery query parameters (Sql.existingConnection conn) - /// Execute a query that returns a scalar value + /// Execute a query that returns a scalar value + /// The NpgsqlConnection on which to run the query + /// 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 [] static member inline CustomScalar<'T when 'T: struct>( conn, query, parameters, mapFunc: System.Func) = Custom.Scalar(query, parameters, mapFunc, Sql.existingConnection conn) - /// Create a document table + /// Create a document table + /// The NpgsqlConnection on which to run the query + /// The table whose existence should be ensured (may include schema) [] static member inline EnsureTable(conn, name) = Definition.ensureTable name (Sql.existingConnection conn) - /// Create an index on documents in the specified table + /// Create an index on documents in the specified table + /// The NpgsqlConnection on which to run the query + /// The table to be indexed (may include schema) + /// The type of document index to create [] static member inline EnsureDocumentIndex(conn, name, idxType) = Definition.ensureDocumentIndex name idxType (Sql.existingConnection conn) - /// Create an index on field(s) within documents in the specified table + /// Create an index on field(s) within documents in the specified table + /// The NpgsqlConnection on which to run the query + /// The table to be indexed (may include schema) + /// The name of the index to create + /// One or more fields to be indexed [] static member inline EnsureFieldIndex(conn, tableName, indexName, fields) = Definition.ensureFieldIndex tableName indexName fields (Sql.existingConnection conn) - /// Insert a new document + /// Insert a new document + /// The NpgsqlConnection on which to run the query + /// The table into which the document should be inserted (may include schema) + /// The document to be inserted [] static member inline Insert<'TDoc>(conn, tableName, document: 'TDoc) = insert<'TDoc> tableName document (Sql.existingConnection conn) - /// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert") + /// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert") + /// The NpgsqlConnection on which to run the query + /// The table into which the document should be saved (may include schema) + /// The document to be saved [] static member inline Save<'TDoc>(conn, tableName, document: 'TDoc) = save<'TDoc> tableName document (Sql.existingConnection conn) - /// Count all documents in a table + /// Count all documents in a table + /// The NpgsqlConnection on which to run the query + /// The table in which documents should be counted (may include schema) + /// The count of the documents in the table [] static member inline CountAll(conn, tableName) = Count.all tableName (Sql.existingConnection conn) - /// Count matching documents using a JSON field comparison query (->> =) + /// Count matching documents using JSON field comparisons (->> =, etc.) + /// The NpgsqlConnection on which to run the query + /// 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 [] static member inline CountByFields(conn, tableName, howMatched, fields) = Count.byFields tableName howMatched fields (Sql.existingConnection conn) - /// Count matching documents using a JSON containment query (@>) + /// Count matching documents using a JSON containment query (@>) + /// The NpgsqlConnection on which to run the query + /// The table in which documents should be counted (may include schema) + /// The document to match with the containment query + /// The count of the documents in the table [] static member inline CountByContains(conn, tableName, criteria: 'TCriteria) = Count.byContains tableName criteria (Sql.existingConnection conn) - /// Count matching documents using a JSON Path match query (@?) + /// Count matching documents using a JSON Path match query (@?) + /// The NpgsqlConnection on which to run the query + /// The table in which documents should be counted (may include schema) + /// The JSON Path expression to be matched + /// The count of the documents in the table [] static member inline CountByJsonPath(conn, tableName, jsonPath) = Count.byJsonPath tableName jsonPath (Sql.existingConnection conn) - /// Determine if a document exists for the given ID + /// Determine if a document exists for the given ID + /// The NpgsqlConnection on which to run the query + /// 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 [] static member inline ExistsById(conn, tableName, docId) = Exists.byId tableName docId (Sql.existingConnection conn) - /// Determine if documents exist using a JSON field comparison query (->> =) + /// Determine if a document exists using JSON field comparisons (->> =, etc.) + /// The NpgsqlConnection on which to run the query + /// 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 [] static member inline ExistsByFields(conn, tableName, howMatched, fields) = Exists.byFields tableName howMatched fields (Sql.existingConnection conn) - /// Determine if documents exist using a JSON containment query (@>) + /// Determine if a document exists using a JSON containment query (@>) + /// The NpgsqlConnection on which to run the query + /// The table in which existence should be checked (may include schema) + /// The document to match with the containment query + /// True if any matching documents exist, false if not [] static member inline ExistsByContains(conn, tableName, criteria: 'TCriteria) = Exists.byContains tableName criteria (Sql.existingConnection conn) - /// Determine if documents exist using a JSON Path match query (@?) + /// Determine if a document exists using a JSON Path match query (@?) + /// The NpgsqlConnection on which to run the query + /// The table in which existence should be checked (may include schema) + /// The JSON Path expression to be matched + /// True if any matching documents exist, false if not [] static member inline ExistsByJsonPath(conn, tableName, jsonPath) = Exists.byJsonPath tableName jsonPath (Sql.existingConnection conn) - /// Retrieve all documents in the given table + /// Retrieve all documents in the given table + /// The NpgsqlConnection on which to run the query + /// The table from which documents should be retrieved (may include schema) + /// All documents from the given table [] static member inline FindAll<'TDoc>(conn, tableName) = Find.All<'TDoc>(tableName, Sql.existingConnection conn) - /// Retrieve all documents in the given table ordered by the given fields in the document + /// Retrieve all documents in the given table ordered by the given fields in the document + /// The NpgsqlConnection on which to run the query + /// 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 [] static member inline FindAllOrdered<'TDoc>(conn, tableName, orderFields) = Find.AllOrdered<'TDoc>(tableName, orderFields, Sql.existingConnection conn) - /// Retrieve a document by its ID; returns None if not found + /// Retrieve a document by its ID + /// The NpgsqlConnection on which to run the query + /// 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 [] static member inline FindById<'TKey, 'TDoc when 'TDoc: null and 'TDoc: not struct>(conn, tableName, docId: 'TKey) = Find.ById<'TKey, 'TDoc>(tableName, docId, Sql.existingConnection conn) - /// Retrieve documents matching a JSON field comparison query (->> =) + /// Retrieve documents matching JSON field comparisons (->> =, etc.) + /// The NpgsqlConnection on which to run the query + /// 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 [] static member inline FindByFields<'TDoc>(conn, tableName, howMatched, fields) = Find.ByFields<'TDoc>(tableName, howMatched, fields, Sql.existingConnection conn) - /// Retrieve documents matching a JSON field comparison query (->> =) ordered by the given fields in the document + /// + /// Retrieve documents matching JSON field comparisons (->> =, etc.) ordered by the given fields in + /// the document + /// + /// The NpgsqlConnection on which to run the query + /// 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 [] static member inline FindByFieldsOrdered<'TDoc>(conn, tableName, howMatched, queryFields, orderFields) = Find.ByFieldsOrdered<'TDoc>( tableName, howMatched, queryFields, orderFields, Sql.existingConnection conn) - /// Retrieve documents matching a JSON containment query (@>) + /// Retrieve documents matching a JSON containment query (@>) + /// The NpgsqlConnection on which to run the query + /// The table from which documents should be retrieved (may include schema) + /// The document to match with the containment query + /// All documents matching the given containment query [] static member inline FindByContains<'TDoc>(conn, tableName, criteria: obj) = Find.ByContains<'TDoc>(tableName, criteria, Sql.existingConnection conn) - /// Retrieve documents matching a JSON containment query (@>) ordered by the given fields in the document + /// + /// Retrieve documents matching a JSON containment query (@>) ordered by the given fields in the + /// document + /// + /// The NpgsqlConnection on which to run the query + /// The table from which documents should be retrieved (may include schema) + /// The document to match with the containment query + /// Fields by which the results should be ordered + /// All documents matching the given containment query, ordered by the given fields [] static member inline FindByContainsOrdered<'TDoc>(conn, tableName, criteria: obj, orderFields) = Find.ByContainsOrdered<'TDoc>(tableName, criteria, orderFields, Sql.existingConnection conn) - /// Retrieve documents matching a JSON Path match query (@?) + /// Retrieve documents matching a JSON Path match query (@?) + /// The NpgsqlConnection on which to run the query + /// The table from which documents should be retrieved (may include schema) + /// The JSON Path expression to match + /// All documents matching the given JSON Path expression [] static member inline FindByJsonPath<'TDoc>(conn, tableName, jsonPath) = Find.ByJsonPath<'TDoc>(tableName, jsonPath, Sql.existingConnection conn) - /// Retrieve documents matching a JSON Path match query (@?) ordered by the given fields in the document + /// + /// Retrieve documents matching a JSON Path match query (@?) ordered by the given fields in the document + /// + /// The NpgsqlConnection on which to run the query + /// The table from which documents should be retrieved (may include schema) + /// The JSON Path expression to match + /// Fields by which the results should be ordered + /// All documents matching the given JSON Path expression, ordered by the given fields [] static member inline FindByJsonPathOrdered<'TDoc>(conn, tableName, jsonPath, orderFields) = Find.ByJsonPathOrdered<'TDoc>(tableName, jsonPath, orderFields, Sql.existingConnection conn) - /// Retrieve the first document matching a JSON field comparison query (->> =); returns null if not found + /// Retrieve the first document matching JSON field comparisons (->> =, etc.) + /// The NpgsqlConnection on which to run the query + /// 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 [] static member inline FindFirstByFields<'TDoc when 'TDoc: null and 'TDoc: not struct>( conn, tableName, howMatched, fields) = Find.FirstByFields<'TDoc>(tableName, howMatched, fields, Sql.existingConnection conn) - /// Retrieve the first document matching a JSON field comparison query (->> =) ordered by the given fields in the - /// document; returns null if not found + /// + /// Retrieve the first document matching JSON field comparisons (->> =, etc.) ordered by the given + /// fields in the document + /// + /// The NpgsqlConnection on which to run the query + /// 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 [] static member inline FindFirstByFieldsOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>( conn, tableName, howMatched, queryFields, orderFields) = Find.FirstByFieldsOrdered<'TDoc>( tableName, howMatched, queryFields, orderFields, Sql.existingConnection conn) - /// Retrieve the first document matching a JSON containment query (@>); returns None if not found + /// Retrieve the first document matching a JSON containment query (@>) + /// The NpgsqlConnection on which to run the query + /// The table from which a document should be retrieved (may include schema) + /// The document to match with the containment query + /// The first document, or null if not found [] static member inline FindFirstByContains<'TDoc when 'TDoc: null and 'TDoc: not struct>( conn, tableName, criteria: obj) = Find.FirstByContains<'TDoc>(tableName, criteria, Sql.existingConnection conn) - /// Retrieve the first document matching a JSON containment query (@>) ordered by the given fields in the document; - /// returns None if not found + /// + /// Retrieve the first document matching a JSON containment query (@>) ordered by the given fields in + /// the document + /// + /// The NpgsqlConnection on which to run the query + /// The table from which a document should be retrieved (may include schema) + /// The document to match with the containment query + /// Fields by which the results should be ordered + /// The first document ordered by the given fields, or null if not found [] static member inline FindFirstByContainsOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>( conn, tableName, criteria: obj, orderFields) = Find.FirstByContainsOrdered<'TDoc>(tableName, criteria, orderFields, Sql.existingConnection conn) - /// Retrieve the first document matching a JSON Path match query (@?); returns None if not found + /// Retrieve the first document matching a JSON Path match query (@?) + /// The NpgsqlConnection on which to run the query + /// The table from which a document should be retrieved (may include schema) + /// The JSON Path expression to match + /// The first document, or null if not found [] static member inline FindFirstByJsonPath<'TDoc when 'TDoc: null and 'TDoc: not struct>(conn, tableName, jsonPath) = Find.FirstByJsonPath<'TDoc>(tableName, jsonPath, Sql.existingConnection conn) - /// Retrieve the first document matching a JSON Path match query (@?) ordered by the given fields in the document; - /// returns None if not found + /// + /// Retrieve the first document matching a JSON Path match query (@?) ordered by the given fields in the + /// document + /// + /// The NpgsqlConnection on which to run the query + /// The table from which a document should be retrieved (may include schema) + /// The JSON Path expression to match + /// Fields by which the results should be ordered + /// The first document ordered by the given fields, or null if not found [] static member inline FindFirstByJsonPathOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>( conn, tableName, jsonPath, orderFields) = Find.FirstByJsonPathOrdered<'TDoc>(tableName, jsonPath, orderFields, Sql.existingConnection conn) - /// Update an entire document by its ID + /// Update (replace) an entire document by its ID + /// The NpgsqlConnection on which to run the query + /// The table in which a document should be updated (may include schema) + /// The ID of the document to be updated (replaced) + /// The new document [] static member inline UpdateById(conn, tableName, docId: 'TKey, document: 'TDoc) = Update.byId tableName docId document (Sql.existingConnection conn) - /// Update an entire document by its ID, using the provided function to obtain the ID from the document + /// + /// Update (replace) an entire document by its ID, using the provided function to obtain the ID from the document + /// + /// The NpgsqlConnection on which to run the query + /// The table in which a document should be updated (may include schema) + /// The function to obtain the ID of the document + /// The new document [] static member inline UpdateByFunc(conn, tableName, idFunc: System.Func<'TDoc, 'TKey>, document: 'TDoc) = Update.ByFunc(tableName, idFunc, document, Sql.existingConnection conn) - /// Patch a document by its ID + /// Patch a document by its ID + /// The NpgsqlConnection on which to run the query + /// 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 [] static member inline PatchById(conn, tableName, docId: 'TKey, patch: 'TPatch) = Patch.byId tableName docId patch (Sql.existingConnection conn) - /// Patch documents using a JSON field comparison query in the WHERE clause (->> =) + /// + /// Patch documents using a JSON field comparison query in the WHERE clause (->> =, etc.) + /// + /// The NpgsqlConnection on which to run the query + /// 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 [] static member inline PatchByFields(conn, tableName, howMatched, fields, patch: 'TPatch) = Patch.byFields tableName howMatched fields patch (Sql.existingConnection conn) - /// Patch documents using a JSON containment query in the WHERE clause (@>) + /// Patch documents using a JSON containment query in the WHERE clause (@>) + /// The NpgsqlConnection on which to run the query + /// The table in which documents should be patched (may include schema) + /// The document to match the containment query + /// The partial document to patch the existing document [] static member inline PatchByContains(conn, tableName, criteria: 'TCriteria, patch: 'TPatch) = Patch.byContains tableName criteria patch (Sql.existingConnection conn) - /// Patch documents using a JSON Path match query in the WHERE clause (@?) + /// Patch documents using a JSON Path match query in the WHERE clause (@?) + /// The NpgsqlConnection on which to run the query + /// The table in which documents should be patched (may include schema) + /// The JSON Path expression to match + /// The partial document to patch the existing document [] static member inline PatchByJsonPath(conn, tableName, jsonPath, patch: 'TPatch) = Patch.byJsonPath tableName jsonPath patch (Sql.existingConnection conn) - /// Remove fields from a document by the document's ID + /// Remove fields from a document by the document's ID + /// The NpgsqlConnection on which to run the query + /// 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 [] static member inline RemoveFieldsById(conn, tableName, docId: 'TKey, fieldNames) = RemoveFields.byId tableName docId fieldNames (Sql.existingConnection conn) - /// Remove fields from documents via a comparison on JSON fields in the document + /// Remove fields from documents via a comparison on JSON fields in the document + /// The NpgsqlConnection on which to run the query + /// 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 [] static member inline RemoveFieldsByFields(conn, tableName, howMatched, fields, fieldNames) = RemoveFields.byFields tableName howMatched fields fieldNames (Sql.existingConnection conn) - /// Remove fields from documents via a JSON containment query (@>) + /// Remove fields from documents via a JSON containment query (@>) + /// The NpgsqlConnection on which to run the query + /// The table in which documents should be modified (may include schema) + /// The document to match the containment query + /// One or more field names to remove from the matching documents [] static member inline RemoveFieldsByContains(conn, tableName, criteria: 'TContains, fieldNames) = RemoveFields.byContains tableName criteria fieldNames (Sql.existingConnection conn) - /// Remove fields from documents via a JSON Path match query (@?) + /// Remove fields from documents via a JSON Path match query (@?) + /// The NpgsqlConnection on which to run the query + /// The table in which documents should be modified (may include schema) + /// The JSON Path expression to match + /// One or more field names to remove from the matching documents [] static member inline RemoveFieldsByJsonPath(conn, tableName, jsonPath, fieldNames) = RemoveFields.byJsonPath tableName jsonPath fieldNames (Sql.existingConnection conn) - /// Delete a document by its ID + /// Delete a document by its ID + /// The NpgsqlConnection on which to run the query + /// The table in which a document should be deleted (may include schema) + /// The ID of the document to delete [] static member inline DeleteById(conn, tableName, docId: 'TKey) = Delete.byId tableName docId (Sql.existingConnection conn) - /// Delete documents by matching a JSON field comparison query (->> =) + /// Delete documents by matching a JSON field comparison query (->> =, etc.) + /// The NpgsqlConnection on which to run the query + /// 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 [] static member inline DeleteByFields(conn, tableName, howMatched, fields) = Delete.byFields tableName howMatched fields (Sql.existingConnection conn) - /// Delete documents by matching a JSON containment query (@>) + /// Delete documents by matching a JSON contains query (@>) + /// The NpgsqlConnection on which to run the query + /// The table in which documents should be deleted (may include schema) + /// The document to match the containment query [] static member inline DeleteByContains(conn, tableName, criteria: 'TContains) = Delete.byContains tableName criteria (Sql.existingConnection conn) - /// Delete documents by matching a JSON Path match query (@?) + /// Delete documents by matching a JSON Path match query (@?) + /// The NpgsqlConnection on which to run the query + /// The table in which documents should be deleted (may include schema) + /// The JSON Path expression to match [] - static member inline DeleteByJsonPath(conn, tableName, path) = - Delete.byJsonPath tableName path (Sql.existingConnection conn) + static member inline DeleteByJsonPath(conn, tableName, jsonPath) = + Delete.byJsonPath tableName jsonPath (Sql.existingConnection conn) diff --git a/src/Postgres/WithProps.fs b/src/Postgres/WithProps.fs index 6b0d05c..da7bc86 100644 --- a/src/Postgres/WithProps.fs +++ b/src/Postgres/WithProps.fs @@ -250,7 +250,7 @@ module Exists = toExists sqlProps -/// Commands to determine if documents exist +/// Commands to retrieve documents [] module Find = diff --git a/src/Sqlite/Library.fs b/src/Sqlite/Library.fs index 520f465..6180534 100644 --- a/src/Sqlite/Library.fs +++ b/src/Sqlite/Library.fs @@ -3,20 +3,25 @@ open BitBadger.Documents open Microsoft.Data.Sqlite -/// Configuration for document handling +/// Configuration for document handling module Configuration = /// The connection string to use for query execution let mutable internal connectionString: string option = None - /// Register a connection string to use for query execution (enables foreign keys) + /// Register a connection string to use for query execution + /// The connection string to use for connections from this library + /// This also enables foreign keys [] let useConnectionString connStr = let builder = SqliteConnectionStringBuilder connStr builder.ForeignKeys <- Option.toNullable (Some true) connectionString <- Some (string builder) - - /// Retrieve the currently configured data source + + /// Retrieve a new connection using currently configured connection string + /// A new database connection + /// If no data source has been configured + /// If the connection cannot be opened [] let dbConn () = match connectionString with @@ -27,11 +32,16 @@ module Configuration = | None -> invalidOp "Please provide a connection string before attempting data access" -/// Query definitions +/// Query definitions [] 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 [] let whereByFields (howMatched: FieldMatch) fields = let name = ParameterName() @@ -53,58 +63,82 @@ 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 [] - let whereById paramName = - whereByFields Any [ { Field.Equal (Configuration.idField ()) 0 with ParameterName = Some paramName } ] - - /// Create an UPDATE statement to patch documents + let whereById (docId: 'TKey) = + whereByFields Any [ { Field.Equal (Configuration.idField ()) docId with ParameterName = Some "@id" } ] + + /// 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 [] let removeFields tableName (parameters: SqliteParameter seq) = let paramNames = parameters |> Seq.map _.ParameterName |> String.concat ", " $"UPDATE %s{tableName} SET data = json_remove(data, {paramNames})" - - /// Create a query by a document's ID + + /// Create a query by a document's ID + /// The SQL statement to be run against a document by its ID + /// The ID of the document targeted + /// A query addressing a document by its ID [] let byId<'TKey> statement (docId: 'TKey) = Query.statementWhere statement (whereByFields Any [ { Field.Equal (Configuration.idField ()) docId with ParameterName = Some "@id" } ]) - - /// Create a query on JSON fields + + /// Create a query on JSON fields + /// The SQL statement to be run against matching fields + /// Whether to match any or all of the field conditions + /// The field conditions to be matched + /// A query addressing documents by field matching conditions [] let byFields statement howMatched fields = Query.statementWhere statement (whereByFields howMatched fields) - - /// Data definition + + /// Data definition module Definition = - /// SQL statement to create a document table + /// SQL statement to create a document table + /// The name of the table (may include schema) + /// A query to create the table if it does not exist [] let ensureTable name = Query.Definition.ensureTableFor name "TEXT" -/// Parameter handling helpers +/// Parameter handling helpers [] module Parameters = - - /// Create an ID parameter (name "@id", key will be treated as a string) + + /// Create an ID parameter (name "@id") + /// The key value for the ID parameter + /// The name and parameter value for the ID [] let idParam (key: 'TKey) = SqliteParameter("@id", string key) - /// Create a parameter with a JSON value + /// Create a parameter with a JSON value + /// The name of the parameter to create + /// The criteria to provide as JSON + /// The name and parameter value for the JSON field [] let jsonParam name (it: 'TJson) = SqliteParameter(name, Configuration.serializer().Serialize it) - /// Create JSON field parameters + /// Create JSON field parameters + /// The Fields to convert to parameters + /// The current parameters for the query + /// A unified sequence of parameter names and values [] let addFieldParams fields parameters = let name = ParameterName() @@ -131,8 +165,11 @@ module Parameters = [] let addFieldParam name field parameters = addFieldParams [ { field with ParameterName = Some name } ] parameters - - /// Append JSON field name parameters for the given field names to the given 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 [] let fieldNameParams paramName fieldNames = fieldNames @@ -140,27 +177,37 @@ module Parameters = |> Seq.toList |> Seq.ofList - /// An empty parameter sequence + /// An empty parameter sequence [] let noParams = Seq.empty -/// Helper functions for handling results +/// Helper functions for handling results [] module Results = - - /// Create a domain item from a document, specifying the field in which the document is found + + /// 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 + /// 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 + /// Create a domain item from a document + /// A SqliteDataReader set to the row with the document to be constructed + /// The constructed domain item [] let fromData<'TDoc> rdr = fromDocument<'TDoc> "data" rdr + /// /// Create a list of items for the results of the given command, using the specified mapping function + /// + /// The command to execute + /// The mapping function from data reader to domain class instance + /// A list of items from the reader [] let toCustomList<'TDoc> (cmd: SqliteCommand) (mapFunc: SqliteDataReader -> 'TDoc) = backgroundTask { use! rdr = cmd.ExecuteReaderAsync() @@ -169,36 +216,61 @@ module Results = it <- Seq.append it (Seq.singleton (mapFunc rdr)) return List.ofSeq it } - - /// Extract a count from the first column + + /// + /// Create a list of items for the results of the given command, using the specified mapping function + /// + /// The command to execute + /// The mapping function from data reader to domain class instance + /// A list of items from the reader + let ToCustomList<'TDoc>(cmd: SqliteCommand, mapFunc: System.Func) = backgroundTask { + use! rdr = cmd.ExecuteReaderAsync() + let it = ResizeArray<'TDoc>() + while! rdr.ReadAsync() do + it.Add(mapFunc.Invoke rdr) + return it + } + + /// Extract a count from the first column + /// A SqliteDataReader set to the row with the count to retrieve + /// The count from the row [] - let toCount (row: SqliteDataReader) = - row.GetInt64 0 - - /// Extract a true/false value from a count in the first column + 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 + /// The true/false value from the row + /// SQLite implements boolean as 1 = true, 0 = false [] - let toExists row = - toCount(row) > 0L + let toExists rdr = + toCount rdr > 0L [] module internal Helpers = - - /// Execute a non-query command + + /// Execute a non-query command + /// The command to be executed let internal write (cmd: SqliteCommand) = backgroundTask { let! _ = cmd.ExecuteNonQueryAsync() () } -/// Versions of queries that accept a SqliteConnection as the last parameter +/// Versions of queries that accept a SqliteConnection as the last parameter module WithConn = - - /// Commands to execute custom SQL queries + + /// Commands to execute custom SQL queries [] module Custom = - /// Execute a query that returns a list of results + /// 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) = @@ -206,29 +278,51 @@ module WithConn = cmd.CommandText <- query cmd.Parameters.AddRange parameters toCustomList<'TDoc> cmd mapFunc - - /// Execute a query that returns a list of results - let List<'TDoc>(query, parameters, mapFunc: System.Func, conn) = backgroundTask { - let! results = list<'TDoc> query parameters mapFunc.Invoke conn - return ResizeArray<'TDoc> results - } - - /// Execute a query that returns one or no results (returns None if not found) + + /// 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 (returns null if not found) + + /// 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 does not return a value + + /// 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() @@ -236,7 +330,12 @@ module WithConn = cmd.Parameters.AddRange parameters write cmd - /// Execute a query that returns a scalar value + /// 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 { @@ -248,35 +347,49 @@ module WithConn = return if isFound then mapFunc rdr else Unchecked.defaultof<'T> } - /// Execute a query that returns a scalar value + /// 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 + /// Functions to create tables and indexes [] module Definition = - - /// Create a document table + + /// 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 a document table + + /// 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 + /// Commands to add documents [] module Document = - - /// Insert a new 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 + match Configuration.autoIdStrategy () with | Disabled -> Query.insert tableName | strategy -> let idField = Configuration.idField () @@ -291,37 +404,59 @@ module WithConn = 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 + /// Commands to count documents [] module Count = - - /// Count all documents in a table + + /// 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 a comparison on JSON fields + + /// 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 + /// Commands to determine if documents exist [] module Exists = - - /// Determine if a document exists for the given ID + + /// 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 "@id")) [ idParam docId ] toExists conn - - /// Determine if a document exists using a comparison on JSON fields + 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 @@ -329,73 +464,128 @@ module WithConn = (addFieldParams fields []) toExists conn - - /// Commands to retrieve documents + + /// Commands to retrieve documents [] module Find = - - /// Retrieve all documents in the given table + + /// 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 + /// 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 + /// 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 + /// 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 (returns None if not found) + /// 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 (returns null if not found) + /// 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 via a comparison on JSON fields + + /// 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 via a comparison on JSON 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 + /// 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 via a comparison on JSON fields ordered by the given fields in the document + 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 via a comparison on JSON fields ordered by the given fields in the document + 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 via a comparison on JSON fields, returning only the first result + 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 @@ -403,17 +593,32 @@ module WithConn = (addFieldParams fields []) fromData<'TDoc> conn - - /// Retrieve documents via a comparison on JSON fields, returning only the first result + + /// 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 documents via a comparison on JSON fields ordered by the given fields in the document, returning - /// only the first result + + /// + /// 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 @@ -421,9 +626,17 @@ module WithConn = (addFieldParams queryFields []) fromData<'TDoc> conn - - /// Retrieve documents via a comparison on JSON fields ordered by the given fields in the document, returning - /// only the first result + + /// + /// 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( @@ -431,51 +644,85 @@ module WithConn = addFieldParams queryFields [], fromData<'TDoc>, conn) - - /// Commands to update documents + + /// Commands to update documents [] module Update = - - /// Update an entire document by its ID + + /// 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 "@id")) + (Query.statementWhere (Query.update tableName) (Query.whereById docId)) [ idParam docId; jsonParam "@data" document ] conn - - /// Update an entire document by its ID, using the provided function to obtain the ID from the document + + /// + /// 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 an entire document by its ID, using the provided function to obtain the ID from the document + + /// + /// 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 + + /// Commands to patch (partially update) documents [] module Patch = - - /// Patch a document by its ID + + /// 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 comparison on JSON fields + + /// + /// 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 + + /// Commands to remove fields from documents [] module RemoveFields = - - /// Remove fields from a document by the document's ID + + /// 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 @@ -483,8 +730,13 @@ module WithConn = (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 + + /// 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 @@ -492,62 +744,95 @@ module WithConn = (Query.byFields (Query.removeFields tableName nameParams) howMatched fields) (addFieldParams fields nameParams) conn - + /// Commands to delete documents [] module Delete = - - /// Delete a document by its ID + + /// 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 comparison on JSON fields + + /// 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 +/// Commands to execute custom SQL queries [] module Custom = - /// Execute a query that returns a list of results + /// 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 + /// 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 (returns None if not found) + /// 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 (returns null if not found) + /// 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 does not return a value + /// 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 + + /// 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 + /// 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) @@ -562,7 +847,7 @@ module Definition = let ensureTable name = use conn = Configuration.dbConn () WithConn.Definition.ensureTable name conn - + /// Create an index on a document table [] let ensureFieldIndex tableName indexName fields = @@ -573,7 +858,7 @@ module Definition = /// Document insert/save functions [] module Document = - + /// Insert a new document [] let insert<'TDoc> tableName (document: 'TDoc) = @@ -590,13 +875,13 @@ module Document = /// Commands to count documents [] module Count = - + /// Count all documents in a table [] let all tableName = use conn = Configuration.dbConn () WithConn.Count.all tableName conn - + /// Count matching documents using a comparison on JSON fields [] let byFields tableName howMatched fields = @@ -613,7 +898,7 @@ module Exists = let byId tableName (docId: 'TKey) = use conn = Configuration.dbConn () WithConn.Exists.byId tableName docId conn - + /// Determine if a document exists using a comparison on JSON fields [] let byFields tableName howMatched fields = @@ -624,7 +909,7 @@ module Exists = /// Commands to determine if documents exist [] module Find = - + /// Retrieve all documents in the given table [] let all<'TDoc> tableName = @@ -663,29 +948,29 @@ module Find = let byFields<'TDoc> tableName howMatched fields = use conn = Configuration.dbConn () WithConn.Find.byFields<'TDoc> tableName howMatched fields conn - + /// Retrieve documents via a comparison on JSON fields let ByFields<'TDoc>(tableName, howMatched, fields) = use conn = Configuration.dbConn () WithConn.Find.ByFields<'TDoc>(tableName, howMatched, fields, conn) - + /// Retrieve documents via a comparison on JSON fields ordered by the given fields in the document [] let byFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields = use conn = Configuration.dbConn () WithConn.Find.byFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields conn - + /// Retrieve documents via a comparison on JSON fields ordered by the given fields in the document let ByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields) = use conn = Configuration.dbConn () WithConn.Find.ByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, conn) - + /// Retrieve documents via a comparison on JSON fields, returning only the first result [] let firstByFields<'TDoc> tableName howMatched fields = use conn = Configuration.dbConn () WithConn.Find.firstByFields<'TDoc> tableName howMatched fields conn - + /// Retrieve documents via a comparison on JSON fields, returning only the first result let FirstByFields<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, howMatched, fields) = use conn = Configuration.dbConn () @@ -697,7 +982,7 @@ module Find = let firstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields = use conn = Configuration.dbConn () WithConn.Find.firstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields conn - + /// Retrieve documents via a comparison on JSON fields ordered by the given fields in the document, returning only /// the first result let FirstByFieldsOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>( @@ -709,19 +994,19 @@ module Find = /// Commands to update documents [] module Update = - + /// Update an entire document by its ID [] let byId tableName (docId: 'TKey) (document: 'TDoc) = use conn = Configuration.dbConn () WithConn.Update.byId tableName docId document conn - + /// Update an entire document by its ID, using the provided function to obtain the ID from the document [] let byFunc tableName (idFunc: 'TDoc -> 'TKey) (document: 'TDoc) = use conn = Configuration.dbConn () WithConn.Update.byFunc tableName idFunc document conn - + /// Update an entire document by its ID, using the provided function to obtain the ID from the document let ByFunc(tableName, idFunc: System.Func<'TDoc, 'TKey>, document: 'TDoc) = use conn = Configuration.dbConn () @@ -731,13 +1016,13 @@ module Update = /// Commands to patch (partially update) documents [] module Patch = - + /// Patch a document by its ID [] let byId tableName (docId: 'TKey) (patch: 'TPatch) = use conn = Configuration.dbConn () WithConn.Patch.byId tableName docId patch conn - + /// Patch documents using a comparison on JSON fields in the WHERE clause [] let byFields tableName howMatched fields (patch: 'TPatch) = @@ -748,13 +1033,13 @@ module Patch = /// Commands to remove fields from documents [] module RemoveFields = - + /// Remove fields from a document by the document's ID [] let byId tableName (docId: 'TKey) fieldNames = use conn = Configuration.dbConn () WithConn.RemoveFields.byId tableName docId fieldNames conn - + /// Remove field from documents via a comparison on JSON fields in the document [] let byFields tableName howMatched fields fieldNames = @@ -765,13 +1050,13 @@ module RemoveFields = /// Commands to delete documents [] module Delete = - + /// Delete a document by its ID [] let byId tableName (docId: 'TKey) = use conn = Configuration.dbConn () WithConn.Delete.byId tableName docId conn - + /// Delete documents by matching a comparison on JSON fields [] let byFields tableName howMatched fields = diff --git a/src/Tests.CSharp/SqliteCSharpTests.cs b/src/Tests.CSharp/SqliteCSharpTests.cs index a9cce70..14aa617 100644 --- a/src/Tests.CSharp/SqliteCSharpTests.cs +++ b/src/Tests.CSharp/SqliteCSharpTests.cs @@ -75,7 +75,7 @@ public static class SqliteCSharpTests ]), TestCase("WhereById succeeds", () => { - Expect.equal(Sqlite.Query.WhereById("@id"), "data->>'Id' = @id", "WHERE clause not correct"); + Expect.equal(Sqlite.Query.WhereById("abc"), "data->>'Id' = @id", "WHERE clause not correct"); }), TestCase("Patch succeeds", () => { @@ -141,9 +141,9 @@ public static class SqliteCSharpTests Expect.isEmpty(Parameters.None, "The parameter list should have been empty"); }) ]); - + // Results are exhaustively executed in the context of other tests - + /// /// Add the test documents to the database /// @@ -989,7 +989,7 @@ public static class SqliteCSharpTests }) ]) ]); - + /// /// All tests for SQLite C# functions and methods /// diff --git a/src/Tests/SqliteTests.fs b/src/Tests/SqliteTests.fs index 83e1c22..c2c3f58 100644 --- a/src/Tests/SqliteTests.fs +++ b/src/Tests/SqliteTests.fs @@ -65,7 +65,7 @@ let queryTests = testList "Query" [ } ] test "whereById succeeds" { - Expect.equal (Query.whereById "@id") "data->>'Id' = @id" "WHERE clause not correct" + Expect.equal (Query.whereById "abc") "data->>'Id' = @id" "WHERE clause not correct" } test "patch succeeds" { Expect.equal @@ -235,7 +235,7 @@ let definitionTests = testList "Definition" [ $"SELECT EXISTS (SELECT 1 FROM {SqliteDb.Catalog} WHERE name = @name) AS it" [ SqliteParameter("@name", name) ] toExists - + let! exists = itExists "ensured" let! alsoExists = itExists "idx_ensured_key" Expect.isFalse exists "The table should not exist already" @@ -254,7 +254,7 @@ let definitionTests = testList "Definition" [ $"SELECT EXISTS (SELECT 1 FROM {SqliteDb.Catalog} WHERE name = 'idx_ensured_test') AS it" [] toExists - + let! exists = indexExists () Expect.isFalse exists "The index should not exist already" @@ -272,7 +272,7 @@ let documentTests = testList "Document" [ use! db = SqliteDb.BuildDb() let! before = Find.all SqliteDb.TableName Expect.equal before [] "There should be no documents in the table" - + let testDoc = { emptyDoc with Id = "turkey"; Sub = Some { Foo = "gobble"; Bar = "gobble" } } do! insert SqliteDb.TableName testDoc let! after = Find.all SqliteDb.TableName @@ -293,12 +293,12 @@ let documentTests = testList "Document" [ use! db = SqliteDb.BuildDb() let! before = Count.all SqliteDb.TableName Expect.equal before 0L "There should be no documents in the table" - + do! insert SqliteDb.TableName { Key = 0; Text = "one" } do! insert SqliteDb.TableName { Key = 0; Text = "two" } do! insert SqliteDb.TableName { Key = 77; Text = "three" } do! insert SqliteDb.TableName { Key = 0; Text = "four" } - + let! after = Find.allOrdered SqliteDb.TableName [ Field.Named "Key" ] Expect.hasLength after 4 "There should have been 4 documents returned" Expect.equal (after |> List.map _.Key) [ 1; 2; 77; 78 ] "The IDs were not generated correctly" @@ -312,12 +312,12 @@ let documentTests = testList "Document" [ use! db = SqliteDb.BuildDb() let! before = Count.all SqliteDb.TableName Expect.equal before 0L "There should be no documents in the table" - + do! insert SqliteDb.TableName { emptyDoc with Value = "one" } do! insert SqliteDb.TableName { emptyDoc with Value = "two" } do! insert SqliteDb.TableName { emptyDoc with Id = "abc123"; Value = "three" } do! insert SqliteDb.TableName { emptyDoc with Value = "four" } - + let! after = Find.all SqliteDb.TableName Expect.hasLength after 4 "There should have been 4 documents returned" Expect.hasCountOf after 3u (fun doc -> doc.Id.Length = 32) "Three of the IDs should have been GUIDs" @@ -332,12 +332,12 @@ let documentTests = testList "Document" [ use! db = SqliteDb.BuildDb() let! before = Count.all SqliteDb.TableName Expect.equal before 0L "There should be no documents in the table" - + do! insert SqliteDb.TableName { emptyDoc with Value = "one" } do! insert SqliteDb.TableName { emptyDoc with Value = "two" } do! insert SqliteDb.TableName { emptyDoc with Id = "abc123"; Value = "three" } do! insert SqliteDb.TableName { emptyDoc with Value = "four" } - + let! after = Find.all SqliteDb.TableName Expect.hasLength after 4 "There should have been 4 documents returned" Expect.hasCountOf @@ -354,7 +354,7 @@ let documentTests = testList "Document" [ use! db = SqliteDb.BuildDb() let! before = Find.all SqliteDb.TableName Expect.equal before [] "There should be no documents in the table" - + let testDoc = { emptyDoc with Id = "test"; Sub = Some { Foo = "a"; Bar = "b" } } do! save SqliteDb.TableName testDoc let! after = Find.all SqliteDb.TableName @@ -364,11 +364,11 @@ let documentTests = testList "Document" [ use! db = SqliteDb.BuildDb() let testDoc = { emptyDoc with Id = "test"; Sub = Some { Foo = "a"; Bar = "b" } } do! insert SqliteDb.TableName testDoc - + let! before = Find.byId SqliteDb.TableName "test" Expect.isSome before "There should have been a document returned" Expect.equal before.Value testDoc "The document is not correct" - + let upd8Doc = { testDoc with Sub = Some { Foo = "c"; Bar = "d" } } do! save SqliteDb.TableName upd8Doc let! after = Find.byId SqliteDb.TableName "test" @@ -391,14 +391,14 @@ let countTests = testList "Count" [ testTask "succeeds for a numeric range" { use! db = SqliteDb.BuildDb() do! loadDocs () - + let! theCount = Count.byFields SqliteDb.TableName Any [ Field.Between "NumValue" 10 20 ] Expect.equal theCount 3L "There should have been 3 matching documents" } testTask "succeeds for a non-numeric range" { use! db = SqliteDb.BuildDb() do! loadDocs () - + let! theCount = Count.byFields SqliteDb.TableName Any [ Field.Between "Value" "aardvark" "apple" ] Expect.equal theCount 1L "There should have been 1 matching document" } @@ -467,7 +467,7 @@ let findTests = testList "Find" [ testTask "succeeds when ordering numerically" { use! db = SqliteDb.BuildDb() do! loadDocs () - + let! results = Find.allOrdered SqliteDb.TableName [ Field.Named "n:NumValue" ] Expect.hasLength results 5 "There should have been 5 documents returned" Expect.equal @@ -478,7 +478,7 @@ let findTests = testList "Find" [ testTask "succeeds when ordering numerically descending" { use! db = SqliteDb.BuildDb() do! loadDocs () - + let! results = Find.allOrdered SqliteDb.TableName [ Field.Named "n:NumValue DESC" ] Expect.hasLength results 5 "There should have been 5 documents returned" Expect.equal @@ -489,7 +489,7 @@ let findTests = testList "Find" [ testTask "succeeds when ordering alphabetically" { use! db = SqliteDb.BuildDb() do! loadDocs () - + let! results = Find.allOrdered SqliteDb.TableName [ Field.Named "Id DESC" ] Expect.hasLength results 5 "There should have been 5 documents returned" Expect.equal @@ -541,7 +541,7 @@ let findTests = testList "Find" [ use! db = SqliteDb.BuildDb() do! Definition.ensureTable SqliteDb.TableName for doc in ArrayDocument.TestDocuments do do! insert SqliteDb.TableName doc - + let! docs = Find.byFields SqliteDb.TableName All [ Field.InArray "Values" SqliteDb.TableName [ "c" ] ] @@ -551,7 +551,7 @@ let findTests = testList "Find" [ use! db = SqliteDb.BuildDb() do! Definition.ensureTable SqliteDb.TableName for doc in ArrayDocument.TestDocuments do do! insert SqliteDb.TableName doc - + let! docs = Find.byFields SqliteDb.TableName All [ Field.InArray "Values" SqliteDb.TableName [ "j" ] ] @@ -671,7 +671,7 @@ let updateTests = testList "Update" [ let! before = Find.all SqliteDb.TableName Expect.isEmpty before "There should have been no documents returned" - + // This not raising an exception is the test do! Update.byId SqliteDb.TableName "test" { emptyDoc with Id = "x"; Sub = Some { Foo = "blue"; Bar = "red" } } @@ -695,7 +695,7 @@ let updateTests = testList "Update" [ let! before = Find.all SqliteDb.TableName Expect.isEmpty before "There should have been no documents returned" - + // This not raising an exception is the test do! Update.byFunc SqliteDb.TableName (_.Id) { Id = "one"; Value = "le un"; NumValue = 1; Sub = None } } @@ -708,7 +708,7 @@ let patchTests = testList "Patch" [ testTask "succeeds when a document is updated" { use! db = SqliteDb.BuildDb() do! loadDocs () - + do! Patch.byId SqliteDb.TableName "one" {| NumValue = 44 |} let! after = Find.byId SqliteDb.TableName "one" Expect.isSome after "There should have been a document returned post-update" @@ -719,7 +719,7 @@ let patchTests = testList "Patch" [ let! before = Find.all SqliteDb.TableName Expect.isEmpty before "There should have been no documents returned" - + // This not raising an exception is the test do! Patch.byId SqliteDb.TableName "test" {| Foo = "green" |} } @@ -728,7 +728,7 @@ let patchTests = testList "Patch" [ testTask "succeeds when a document is updated" { use! db = SqliteDb.BuildDb() do! loadDocs () - + do! Patch.byFields SqliteDb.TableName Any [ Field.Equal "Value" "purple" ] {| NumValue = 77 |} let! after = Count.byFields SqliteDb.TableName Any [ Field.Equal "NumValue" 77 ] Expect.equal after 2L "There should have been 2 documents returned" @@ -738,7 +738,7 @@ let patchTests = testList "Patch" [ let! before = Find.all SqliteDb.TableName Expect.isEmpty before "There should have been no documents returned" - + // This not raising an exception is the test do! Patch.byFields SqliteDb.TableName Any [ Field.Equal "Value" "burgundy" ] {| Foo = "green" |} } @@ -751,7 +751,7 @@ let removeFieldsTests = testList "RemoveFields" [ testTask "succeeds when fields is removed" { use! db = SqliteDb.BuildDb() do! loadDocs () - + do! RemoveFields.byId SqliteDb.TableName "two" [ "Sub"; "Value" ] try let! _ = Find.byId SqliteDb.TableName "two" @@ -763,13 +763,13 @@ let removeFieldsTests = testList "RemoveFields" [ testTask "succeeds when a field is not removed" { use! db = SqliteDb.BuildDb() do! loadDocs () - + // This not raising an exception is the test do! RemoveFields.byId SqliteDb.TableName "two" [ "AFieldThatIsNotThere" ] } testTask "succeeds when no document is matched" { use! db = SqliteDb.BuildDb() - + // This not raising an exception is the test do! RemoveFields.byId SqliteDb.TableName "two" [ "Value" ] } @@ -778,7 +778,7 @@ let removeFieldsTests = testList "RemoveFields" [ testTask "succeeds when a field is removed" { use! db = SqliteDb.BuildDb() do! loadDocs () - + do! RemoveFields.byFields SqliteDb.TableName Any [ Field.Equal "NumValue" 17 ] [ "Sub" ] try let! _ = Find.byId SqliteDb.TableName "four" @@ -790,13 +790,13 @@ let removeFieldsTests = testList "RemoveFields" [ testTask "succeeds when a field is not removed" { use! db = SqliteDb.BuildDb() do! loadDocs () - + // This not raising an exception is the test do! RemoveFields.byFields SqliteDb.TableName Any [ Field.Equal "NumValue" 17 ] [ "Nothing" ] } testTask "succeeds when no document is matched" { use! db = SqliteDb.BuildDb() - + // This not raising an exception is the test do! RemoveFields.byFields SqliteDb.TableName Any [ Field.NotEqual "Abracadabra" "apple" ] [ "Value" ] }