diff --git a/src/Common/BitBadger.Documents.Common.fsproj b/src/Common/BitBadger.Documents.Common.fsproj index 8129e69..aa13f34 100644 --- a/src/Common/BitBadger.Documents.Common.fsproj +++ b/src/Common/BitBadger.Documents.Common.fsproj @@ -3,6 +3,7 @@ Common files for PostgreSQL and SQLite document database libraries JSON Document SQL + bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml diff --git a/src/Common/Library.fs b/src/Common/Library.fs index f51e81a..8bae732 100644 --- a/src/Common/Library.fs +++ b/src/Common/Library.fs @@ -2,43 +2,44 @@ open System.Security.Cryptography -/// The types of comparisons available for JSON fields +/// The types of comparisons available for JSON fields +/// type Comparison = - /// Equals (=) + /// Equals (=) | Equal of Value: obj - /// Greater Than (>) + /// Greater Than (>) | Greater of Value: obj - /// Greater Than or Equal To (>=) + /// Greater Than or Equal To (>=) | GreaterOrEqual of Value: obj - /// Less Than (<) + /// Less Than (<) | Less of Value: obj - /// Less Than or Equal To (<=) + /// Less Than or Equal To (<=) | LessOrEqual of Value: obj - /// Not Equal to (<>) + /// Not Equal to (<>) | NotEqual of Value: obj - /// Between (BETWEEN) + /// Between (BETWEEN) | Between of Min: obj * Max: obj - /// In (IN) + /// In (IN) | In of Values: obj seq - /// In Array (PostgreSQL: |?, SQLite: EXISTS / json_each / IN) + /// In Array (PostgreSQL: |?, SQLite: EXISTS / json_each / IN) | InArray of Table: string * Values: obj seq - /// Exists (IS NOT NULL) + /// Exists (IS NOT NULL) | Exists - /// Does Not Exist (IS NULL) + /// Does Not Exist (IS NULL) | NotExists - /// Get the operator SQL for this comparison + /// The operator SQL for this comparison member this.OpSql = match this with | Equal _ -> "=" @@ -54,119 +55,190 @@ type Comparison = | NotExists -> "IS NULL" -/// The dialect in which a command should be rendered +/// The dialect in which a command should be rendered [] type Dialect = | PostgreSQL | SQLite -/// The format in which an element of a JSON field should be extracted +/// The format in which an element of a JSON field should be extracted [] type FieldFormat = - /// Use ->> or #>>; extracts a text (PostgreSQL) or SQL (SQLite) value + /// + /// Use ->> or #>>; extracts a text (PostgreSQL) or SQL (SQLite) value + /// | AsSql - /// Use -> or #>; extracts a JSONB (PostgreSQL) or JSON (SQLite) value + /// Use -> or #>; extracts a JSONB (PostgreSQL) or JSON (SQLite) value | AsJson -/// Criteria for a field WHERE clause +/// Criteria for a field WHERE clause type Field = { - /// The name of the field + /// The name of the field Name: string - /// The comparison for the field + /// The comparison for the field Comparison: Comparison - /// The name of the parameter for this field + /// The name of the parameter for this field ParameterName: string option - /// The table qualifier for this field + /// The table qualifier for this field Qualifier: string option } with - /// Create a comparison against a field + /// Create a comparison against a field + /// The name of the field against which the comparison should be applied + /// The comparison for the given field + /// A new Field instance implementing the given comparison static member Where name (comparison: Comparison) = { Name = name; Comparison = comparison; ParameterName = None; Qualifier = None } - /// Create an equals (=) field criterion + /// Create an equals (=) field criterion + /// The name of the field to be compared + /// The value for the comparison + /// A field with the given comparison static member Equal<'T> name (value: 'T) = Field.Where name (Equal value) - /// Create an equals (=) field criterion (alias) + /// Create an equals (=) field criterion (alias) + /// The name of the field to be compared + /// The value for the comparison + /// A field with the given comparison static member EQ<'T> name (value: 'T) = Field.Equal name value - /// Create a greater than (>) field criterion + /// Create a greater than (>) field criterion + /// The name of the field to be compared + /// The value for the comparison + /// A field with the given comparison static member Greater<'T> name (value: 'T) = Field.Where name (Greater value) - /// Create a greater than (>) field criterion (alias) + /// Create a greater than (>) field criterion (alias) + /// The name of the field to be compared + /// The value for the comparison + /// A field with the given comparison static member GT<'T> name (value: 'T) = Field.Greater name value - /// Create a greater than or equal to (>=) field criterion + /// Create a greater than or equal to (>=) field criterion + /// The name of the field to be compared + /// The value for the comparison + /// A field with the given comparison static member GreaterOrEqual<'T> name (value: 'T) = Field.Where name (GreaterOrEqual value) - /// Create a greater than or equal to (>=) field criterion (alias) + /// Create a greater than or equal to (>=) field criterion (alias) + /// The name of the field to be compared + /// The value for the comparison + /// A field with the given comparison static member GE<'T> name (value: 'T) = Field.GreaterOrEqual name value - /// Create a less than (<) field criterion + /// Create a less than (<) field criterion + /// The name of the field to be compared + /// The value for the comparison + /// A field with the given comparison static member Less<'T> name (value: 'T) = Field.Where name (Less value) - /// Create a less than (<) field criterion (alias) + /// Create a less than (<) field criterion (alias) + /// The name of the field to be compared + /// The value for the comparison + /// A field with the given comparison static member LT<'T> name (value: 'T) = Field.Less name value - /// Create a less than or equal to (<=) field criterion + /// Create a less than or equal to (<=) field criterion + /// The name of the field to be compared + /// The value for the comparison + /// A field with the given comparison static member LessOrEqual<'T> name (value: 'T) = Field.Where name (LessOrEqual value) - /// Create a less than or equal to (<=) field criterion (alias) + /// Create a less than or equal to (<=) field criterion (alias) + /// The name of the field to be compared + /// The value for the comparison + /// A field with the given comparison static member LE<'T> name (value: 'T) = Field.LessOrEqual name value - /// Create a not equals (<>) field criterion + /// Create a not equals (<>) field criterion + /// The name of the field to be compared + /// The value for the comparison + /// A field with the given comparison static member NotEqual<'T> name (value: 'T) = Field.Where name (NotEqual value) - /// Create a not equals (<>) field criterion (alias) + /// Create a not equals (<>) field criterion (alias) + /// The name of the field to be compared + /// The value for the comparison + /// A field with the given comparison static member NE<'T> name (value: 'T) = Field.NotEqual name value - /// Create a Between field criterion + /// Create a Between field criterion + /// The name of the field to be compared + /// The minimum value for the comparison range + /// The maximum value for the comparison range + /// A field with the given comparison static member Between<'T> name (min: 'T) (max: 'T) = Field.Where name (Between(min, max)) - /// Create a Between field criterion (alias) + /// Create a Between field criterion (alias) + /// The name of the field to be compared + /// The minimum value for the comparison range + /// The maximum value for the comparison range + /// A field with the given comparison static member BT<'T> name (min: 'T) (max: 'T) = Field.Between name min max - /// Create an In field criterion + /// Create an In field criterion + /// The name of the field to be compared + /// The values for the comparison + /// A field with the given comparison static member In<'T> name (values: 'T seq) = Field.Where name (In (Seq.map box values)) - /// Create an In field criterion (alias) + /// Create an In field criterion (alias) + /// The name of the field to be compared + /// The values for the comparison + /// A field with the given comparison static member IN<'T> name (values: 'T seq) = Field.In name values - /// Create an InArray field criterion + /// Create an InArray field criterion + /// The name of the field to be compared + /// The name of the table in which the field's documents are stored + /// The values for the comparison + /// A field with the given comparison static member InArray<'T> name tableName (values: 'T seq) = Field.Where name (InArray(tableName, Seq.map box values)) - /// Create an exists (IS NOT NULL) field criterion + /// Create an exists (IS NOT NULL) field criterion + /// The name of the field to be compared + /// A field with the given comparison static member Exists name = Field.Where name Exists - /// Create an exists (IS NOT NULL) field criterion (alias) + /// Create an exists (IS NOT NULL) field criterion (alias) + /// The name of the field to be compared + /// A field with the given comparison static member EX name = Field.Exists name - /// Create a not exists (IS NULL) field criterion + /// Create a not exists (IS NULL) field criterion + /// The name of the field to be compared + /// A field with the given comparison static member NotExists name = Field.Where name NotExists - /// Create a not exists (IS NULL) field criterion (alias) + /// Create a not exists (IS NULL) field criterion (alias) + /// The name of the field to be compared + /// A field with the given comparison static member NEX name = Field.NotExists name - /// Transform a field name (a.b.c) to a path for the given SQL dialect + /// Transform a field name (a.b.c) to a path for the given SQL dialect + /// The name of the field in dotted format + /// The SQL dialect to use when converting the name to nested path format + /// Whether to reference this path as a JSON value or a SQL value + /// A string with the path required to address the nested document value static member NameToPath (name: string) dialect format = let path = if name.Contains '.' then @@ -183,46 +255,59 @@ type Field = { match format with AsJson -> $"->'{name}'" | AsSql -> $"->>'{name}'" $"data{path}" - /// Create a field with a given name, but no other properties filled (op will be EQ, value will be "") + /// Create a field with a given name, but no other properties filled + /// The field name, along with any other qualifications if used in a sorting context + /// Comparison will be Equal, value will be an empty string static member Named name = Field.Where name (Equal "") - /// Specify the name of the parameter for this field + /// Specify the name of the parameter for this field + /// The parameter name (including : or @) + /// A field with the given parameter name specified member this.WithParameterName name = { this with ParameterName = Some name } - /// Specify a qualifier (alias) for the table from which this field will be referenced + /// Specify a qualifier (alias) for the table from which this field will be referenced + /// The table alias for this field comparison + /// A field with the given qualifier specified member this.WithQualifier alias = { this with Qualifier = Some alias } - /// Get the qualified path to the field + /// Get the qualified path to the field + /// The SQL dialect to use when converting the name to nested path format + /// Whether to reference this path as a JSON value or a SQL value + /// A string with the qualified path required to address the nested document value member this.Path dialect format = (this.Qualifier |> Option.map (fun q -> $"{q}.") |> Option.defaultValue "") + Field.NameToPath this.Name dialect format -/// How fields should be matched +/// How fields should be matched [] type FieldMatch = - /// Any field matches (OR) + /// Any field matches (OR) | Any - /// All fields match (AND) + /// All fields match (AND) | All - /// The SQL value implementing each matching strategy + /// The SQL value implementing each matching strategy override this.ToString() = match this with Any -> "OR" | All -> "AND" -/// Derive parameter names (each instance wraps a counter to uniquely name anonymous fields) +/// Derive parameter names (each instance wraps a counter to uniquely name anonymous fields) type ParameterName() = /// The counter for the next field value let mutable currentIdx = -1 + /// /// Return the specified name for the parameter, or an anonymous parameter name if none is specified + /// + /// The optional name of the parameter + /// The name of the parameter, derived if no name was provided member this.Derive paramName = match paramName with | Some it -> it @@ -231,31 +316,41 @@ type ParameterName() = $"@field{currentIdx}" -/// Automatically-generated document ID strategies +/// Automatically-generated document ID strategies [] type AutoId = - /// No automatic IDs will be generated + /// No automatic IDs will be generated | Disabled - /// Generate a MAX-plus-1 numeric value for documents + /// Generate a MAX-plus-1 numeric value for documents | Number - /// Generate a GUID for each document (as a lowercase, no-dashes, 32-character string) + /// Generate a GUID for each document (as a lowercase, no-dashes, 32-character string) | Guid - /// Generate a random string of hexadecimal characters for each document + /// Generate a random string of hexadecimal characters for each document | RandomString with - /// Generate a GUID string + /// Generate a GUID string + /// A GUID string static member GenerateGuid() = System.Guid.NewGuid().ToString "N" - /// Generate a string of random hexadecimal characters + /// Generate a string of random hexadecimal characters + /// The number of characters to generate + /// A string of the given length with random hexadecimal characters static member GenerateRandomString(length: int) = RandomNumberGenerator.GetHexString(length, lowercase = true) - /// Does the given document need an automatic ID generated? + /// Does the given document need an automatic ID generated? + /// The auto-ID strategy currently in use + /// The document being inserted + /// The name of the ID property in the given document + /// True if an auto-ID strategy is implemented and the ID has no value, false otherwise + /// + /// If the ID field type and requested ID value are not compatible + /// static member NeedsAutoId<'T> strategy (document: 'T) idProp = match strategy with | Disabled -> false @@ -290,17 +385,17 @@ with | Disabled -> false -/// The required document serialization implementation +/// The required document serialization implementation type IDocumentSerializer = - /// Serialize an object to a JSON string + /// Serialize an object to a JSON string abstract Serialize<'T> : 'T -> string - /// Deserialize a JSON string into an object + /// Deserialize a JSON string into an object abstract Deserialize<'T> : string -> 'T -/// Document serializer defaults +/// Document serializer defaults module DocumentSerializer = open System.Text.Json @@ -312,7 +407,7 @@ module DocumentSerializer = o.Converters.Add(JsonFSharpConverter()) o - /// The default JSON serializer + /// The default JSON serializer [] let ``default`` = { new IDocumentSerializer with @@ -323,19 +418,21 @@ module DocumentSerializer = } -/// Configuration for document handling +/// Configuration for document handling [] module Configuration = /// The serializer to use for document manipulation let mutable private serializerValue = DocumentSerializer.``default`` - /// Register a serializer to use for translating documents to domain types + /// Register a serializer to use for translating documents to domain types + /// The serializer to use when manipulating documents [] let useSerializer ser = serializerValue <- ser - /// Retrieve the currently configured serializer + /// Retrieve the currently configured serializer + /// The currently configured serializer [] let serializer () = serializerValue @@ -343,12 +440,14 @@ module Configuration = /// The serialized name of the ID field for documents let mutable private idFieldValue = "Id" - /// Specify the name of the ID field for documents + /// Specify the name of the ID field for documents + /// The name of the ID field for documents [] let useIdField it = idFieldValue <- it - /// Retrieve the currently configured ID field for documents + /// Retrieve the currently configured ID field for documents + /// The currently configured ID field [] let idField () = idFieldValue @@ -356,12 +455,14 @@ module Configuration = /// The automatic ID strategy used by the library let mutable private autoIdValue = Disabled - /// Specify the automatic ID generation strategy used by the library + /// Specify the automatic ID generation strategy used by the library + /// The automatic ID generation strategy to use [] let useAutoIdStrategy it = autoIdValue <- it - /// Retrieve the currently configured automatic ID generation strategy + /// Retrieve the currently configured automatic ID generation strategy + /// The current automatic ID generation strategy [] let autoIdStrategy () = autoIdValue @@ -369,30 +470,38 @@ module Configuration = /// The length of automatically generated random strings let mutable private idStringLengthValue = 16 - /// Specify the length of automatically generated random strings + /// Specify the length of automatically generated random strings + /// The length of automatically generated random strings [] let useIdStringLength length = idStringLengthValue <- length - /// Retrieve the currently configured length of automatically generated random strings + /// Retrieve the currently configured length of automatically generated random strings + /// The current length of automatically generated random strings [] let idStringLength () = idStringLengthValue -/// Query construction functions +/// Query construction functions [] module Query = - /// Combine a query (select, update, etc.) and a WHERE clause + /// Combine a query (SELECT, UPDATE, etc.) and a WHERE clause + /// The first part of the statement + /// The WHERE clause for the statement + /// The two parts of the query combined with WHERE [] let statementWhere statement where = $"%s{statement} WHERE %s{where}" - /// Queries to define tables and indexes + /// Queries to define tables and indexes module Definition = - /// SQL statement to create a document table + /// SQL statement to create a document table + /// The name of the table to create (may include schema) + /// The type of data for the column (JSON, JSONB, etc.) + /// A query to create a document table [] let ensureTableFor name dataType = $"CREATE TABLE IF NOT EXISTS %s{name} (data %s{dataType} NOT NULL)" @@ -402,7 +511,12 @@ module Query = let parts = tableName.Split '.' if Array.length parts = 1 then "", tableName else parts[0], parts[1] - /// SQL statement to create an index on one or more fields in a JSON document + /// SQL statement to create an index on one or more fields in a JSON document + /// The table on which an index should be created (may include schema) + /// The name of the index to be created + /// One or more fields to include in the index + /// The SQL dialect to use when creating this index + /// A query to create the field index [] let ensureIndexOn tableName indexName (fields: string seq) dialect = let _, tbl = splitSchemaAndTable tableName @@ -416,55 +530,84 @@ module Query = |> String.concat ", " $"CREATE INDEX IF NOT EXISTS idx_{tbl}_%s{indexName} ON {tableName} ({jsonFields})" - /// SQL statement to create a key index for a document table + /// SQL statement to create a key index for a document table + /// The table on which a key index should be created (may include schema) + /// The SQL dialect to use when creating this index + /// A query to create the key index [] let ensureKey tableName dialect = (ensureIndexOn tableName "key" [ Configuration.idField () ] dialect).Replace("INDEX", "UNIQUE INDEX") - /// Query to insert a document + /// Query to insert a document + /// The table into which to insert (may include schema) + /// A query to insert a document [] let insert tableName = $"INSERT INTO %s{tableName} VALUES (@data)" + /// /// Query to save a document, inserting it if it does not exist and updating it if it does (AKA "upsert") + /// + /// The table into which to save (may include schema) + /// A query to save a document [] let save tableName = sprintf "INSERT INTO %s VALUES (@data) ON CONFLICT ((data->>'%s')) DO UPDATE SET data = EXCLUDED.data" tableName (Configuration.idField ()) - /// Query to count documents in a table (no WHERE clause) + /// Query to count documents in a table + /// The table in which to count documents (may include schema) + /// A query to count documents + /// This query has no WHERE clause [] let count tableName = $"SELECT COUNT(*) AS it FROM %s{tableName}" - /// Query to check for document existence in a table + /// Query to check for document existence in a table + /// The table in which existence should be checked (may include schema) + /// The WHERE clause with the existence criteria + /// A query to check document existence [] let exists tableName where = $"SELECT EXISTS (SELECT 1 FROM %s{tableName} WHERE %s{where}) AS it" - /// Query to select documents from a table (no WHERE clause) + /// Query to select documents from a table + /// The table from which documents should be found (may include schema) + /// A query to retrieve documents + /// This query has no WHERE clause [] let find tableName = $"SELECT data FROM %s{tableName}" - /// Query to update a document (no WHERE clause) + /// Query to update (replace) a document + /// The table in which documents should be replaced (may include schema) + /// A query to update documents + /// This query has no WHERE clause [] let update tableName = $"UPDATE %s{tableName} SET data = @data" - /// Query to delete documents from a table (no WHERE clause) + /// Query to delete documents from a table + /// The table in which documents should be deleted (may include schema) + /// A query to delete documents + /// This query has no WHERE clause [] let delete tableName = $"DELETE FROM %s{tableName}" - /// Create a SELECT clause to retrieve the document data from the given table + /// Create a SELECT clause to retrieve the document data from the given table + /// The table from which documents should be found (may include schema) + /// A query to retrieve documents [] [] let selectFromTable tableName = find tableName - /// Create an ORDER BY clause for the given fields + /// Create an ORDER BY clause for the given fields + /// One or more fields by which to order + /// The SQL dialect for the generated clause + /// An ORDER BY clause for the given fields [] let orderBy fields dialect = if Seq.isEmpty fields then "" diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 60cc6e9..932d9dd 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,18 +2,12 @@ net8.0;net9.0 embedded - false - 4.0.0.0 - 4.0.0.0 - 4.0.0 - From v3.1: (see project site for breaking changes and compatibility) -- Change ByField to ByFields -- Support dot-access to nested document fields -- Add Find*Ordered functions/methods -- Add case-insensitive ordering (as of rc2) -- Preserve additional ORDER BY qualifiers (as of rc3) -- Add In / InArray comparisons (as of rc4) -- Field construction functions are generic (as of rc5) + true + 4.0.1.0 + 4.0.1.0 + 4.0.1 + From v4.0: Add XML documention (IDE support) +From v3.1: See 4.0 release for breaking changes and compatibility danieljsummers Bit Badger Solutions README.md diff --git a/src/Postgres/BitBadger.Documents.Postgres.fsproj b/src/Postgres/BitBadger.Documents.Postgres.fsproj index 79ba4a6..d663ce9 100644 --- a/src/Postgres/BitBadger.Documents.Postgres.fsproj +++ b/src/Postgres/BitBadger.Documents.Postgres.fsproj @@ -3,10 +3,13 @@ Use PostgreSQL as a document database JSON Document PostgreSQL Npgsql + bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml + + diff --git a/src/Postgres/Extensions.fs b/src/Postgres/Extensions.fs index 17c7810..568b51b 100644 --- a/src/Postgres/Extensions.fs +++ b/src/Postgres/Extensions.fs @@ -2,446 +2,830 @@ namespace BitBadger.Documents.Postgres open Npgsql open Npgsql.FSharp +open WithProps -/// F# Extensions for the NpgsqlConnection type +/// F# Extensions for the NpgsqlConnection type [] module Extensions = - + type NpgsqlConnection with - /// 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 member conn.customList<'TDoc> query parameters (mapFunc: RowReader -> 'TDoc) = - WithProps.Custom.list<'TDoc> query parameters mapFunc (Sql.existingConnection conn) + 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 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 member conn.customSingle<'TDoc> query parameters (mapFunc: RowReader -> 'TDoc) = - WithProps.Custom.single<'TDoc> query parameters mapFunc (Sql.existingConnection conn) + Custom.single<'TDoc> query parameters mapFunc (Sql.existingConnection conn) - /// Execute a query that returns no results + /// Execute a query that returns no results + /// The query to retrieve the results + /// Parameters to use for the query member conn.customNonQuery query parameters = - WithProps.Custom.nonQuery query parameters (Sql.existingConnection conn) + Custom.nonQuery query parameters (Sql.existingConnection 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 member conn.customScalar<'T when 'T: struct> query parameters (mapFunc: RowReader -> 'T) = - WithProps.Custom.scalar query parameters mapFunc (Sql.existingConnection conn) - - /// Create a document table + Custom.scalar query parameters mapFunc (Sql.existingConnection conn) + + /// Create a document table + /// The table whose existence should be ensured (may include schema) member conn.ensureTable name = - WithProps.Definition.ensureTable name (Sql.existingConnection conn) + 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 table to be indexed (may include schema) + /// The type of document index to create member conn.ensureDocumentIndex name idxType = - WithProps.Definition.ensureDocumentIndex name idxType (Sql.existingConnection conn) - - /// Create an index on field(s) within documents in the specified table + Definition.ensureDocumentIndex name idxType (Sql.existingConnection 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 member conn.ensureFieldIndex tableName indexName fields = - WithProps.Definition.ensureFieldIndex tableName indexName fields (Sql.existingConnection conn) + Definition.ensureFieldIndex tableName indexName fields (Sql.existingConnection conn) - /// 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 member conn.insert<'TDoc> tableName (document: 'TDoc) = - WithProps.Document.insert<'TDoc> tableName document (Sql.existingConnection conn) + 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") + /// + /// The table into which the document should be saved (may include schema) + /// The document to be saved member conn.save<'TDoc> tableName (document: 'TDoc) = - WithProps.Document.save<'TDoc> tableName document (Sql.existingConnection conn) + save<'TDoc> tableName document (Sql.existingConnection conn) - /// Count all documents in a table + /// Count all documents in a table + /// The table in which documents should be counted (may include schema) + /// The count of the documents in the table member conn.countAll tableName = - WithProps.Count.all tableName (Sql.existingConnection conn) - - /// Count matching documents using a JSON field comparison query (->> =) + Count.all tableName (Sql.existingConnection conn) + + /// Count matching documents using JSON field comparisons (->> =, etc.) + /// The table in which documents should be counted (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// The count of matching documents in the table member conn.countByFields tableName howMatched fields = - WithProps.Count.byFields tableName howMatched fields (Sql.existingConnection conn) - - /// Count matching documents using a JSON containment query (@>) + Count.byFields tableName howMatched fields (Sql.existingConnection conn) + + /// Count matching documents using a JSON containment 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 member conn.countByContains tableName criteria = - WithProps.Count.byContains tableName criteria (Sql.existingConnection conn) + 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 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 member conn.countByJsonPath tableName jsonPath = - WithProps.Count.byJsonPath tableName jsonPath (Sql.existingConnection conn) + 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 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 member conn.existsById tableName docId = - WithProps.Exists.byId tableName docId (Sql.existingConnection conn) - - /// Determine if documents exist using a JSON field comparison query (->> =) + Exists.byId tableName docId (Sql.existingConnection conn) + + /// Determine if a document exists using JSON field comparisons (->> =, etc.) + /// The table in which existence should be checked (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// True if any matching documents exist, false if not member conn.existsByFields tableName howMatched fields = - WithProps.Exists.byFields tableName howMatched fields (Sql.existingConnection conn) - - /// Determine if documents exist using a JSON containment query (@>) + Exists.byFields tableName howMatched fields (Sql.existingConnection conn) + + /// Determine if a document exists using a JSON containment 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 member conn.existsByContains tableName criteria = - WithProps.Exists.byContains tableName criteria (Sql.existingConnection conn) + 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 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 member conn.existsByJsonPath tableName jsonPath = - WithProps.Exists.byJsonPath tableName jsonPath (Sql.existingConnection conn) - - /// Retrieve all documents in the given table + Exists.byJsonPath tableName jsonPath (Sql.existingConnection conn) + + /// Retrieve all documents in the given table + /// The table from which documents should be retrieved (may include schema) + /// All documents from the given table member conn.findAll<'TDoc> tableName = - WithProps.Find.all<'TDoc> tableName (Sql.existingConnection conn) + 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 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 member conn.findAllOrdered<'TDoc> tableName orderFields = - WithProps.Find.allOrdered<'TDoc> tableName orderFields (Sql.existingConnection conn) + 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 table from which a document should be retrieved (may include schema) + /// The ID of the document to retrieve + /// Some with the document if found, None otherwise member conn.findById<'TKey, 'TDoc> tableName docId = - WithProps.Find.byId<'TKey, 'TDoc> tableName docId (Sql.existingConnection conn) + 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 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 member conn.findByFields<'TDoc> tableName howMatched fields = - WithProps.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 + Find.byFields<'TDoc> tableName howMatched fields (Sql.existingConnection conn) + + /// + /// Retrieve documents matching JSON field comparisons (->> =, etc.) ordered by the given fields + /// in the document + /// + /// The table from which documents should be retrieved (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// Fields by which the results should be ordered + /// All documents matching the given fields, ordered by the other given fields member conn.findByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields = - WithProps.Find.byFieldsOrdered<'TDoc> + 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 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 member conn.findByContains<'TDoc> tableName (criteria: obj) = - WithProps.Find.byContains<'TDoc> tableName criteria (Sql.existingConnection conn) + 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 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 member conn.findByContainsOrdered<'TDoc> tableName (criteria: obj) orderFields = - WithProps.Find.byContainsOrdered<'TDoc> tableName criteria orderFields (Sql.existingConnection conn) + 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 table from which documents should be retrieved (may include schema) + /// The JSON Path expression to match + /// All documents matching the given JSON Path expression member conn.findByJsonPath<'TDoc> tableName jsonPath = - WithProps.Find.byJsonPath<'TDoc> tableName jsonPath (Sql.existingConnection conn) - - /// Retrieve documents matching a JSON Path match query (@?) ordered by the given fields in the document + Find.byJsonPath<'TDoc> tableName jsonPath (Sql.existingConnection conn) + + /// + /// Retrieve documents matching a JSON Path match query (@?) ordered by the given fields in the + /// document + /// + /// 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 member conn.findByJsonPathOrdered<'TDoc> tableName jsonPath orderFields = - WithProps.Find.byJsonPathOrdered<'TDoc> tableName jsonPath orderFields (Sql.existingConnection conn) - - /// Retrieve the first document matching a JSON field comparison query (->> =); returns None if not found + Find.byJsonPathOrdered<'TDoc> tableName jsonPath orderFields (Sql.existingConnection conn) + + /// Retrieve the first document matching JSON field comparisons (->> =, etc.) + /// The table from which a document should be retrieved (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// Some with the first document, or None if not found member conn.findFirstByFields<'TDoc> tableName howMatched fields = - WithProps.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 None if not found + Find.firstByFields<'TDoc> tableName howMatched fields (Sql.existingConnection conn) + + /// + /// Retrieve the first document matching JSON field comparisons (->> =, etc.) ordered by the + /// given fields in the document + /// + /// The table from which a document should be retrieved (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// Fields by which the results should be ordered + /// + /// Some with the first document ordered by the given fields, or None if not found + /// member conn.findFirstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields = - WithProps.Find.firstByFieldsOrdered<'TDoc> + 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 table from which a document should be retrieved (may include schema) + /// The document to match with the containment query + /// Some with the first document, or None if not found member conn.findFirstByContains<'TDoc> tableName (criteria: obj) = - WithProps.Find.firstByContains<'TDoc> tableName criteria (Sql.existingConnection conn) + 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 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 + /// + /// Some with the first document ordered by the given fields, or None if not found + /// member conn.findFirstByContainsOrdered<'TDoc> tableName (criteria: obj) orderFields = - WithProps.Find.firstByContainsOrdered<'TDoc> tableName criteria orderFields (Sql.existingConnection conn) + 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 table from which a document should be retrieved (may include schema) + /// The JSON Path expression to match + /// Some with the first document, or None if not found member conn.findFirstByJsonPath<'TDoc> tableName jsonPath = - WithProps.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 + 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 + /// + /// 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 + /// + /// Some with the first document ordered by the given fields, or None if not found + /// member conn.findFirstByJsonPathOrdered<'TDoc> tableName jsonPath orderFields = - WithProps.Find.firstByJsonPathOrdered<'TDoc> tableName jsonPath orderFields (Sql.existingConnection conn) - - /// Update an entire document by its ID + Find.firstByJsonPathOrdered<'TDoc> tableName jsonPath orderFields (Sql.existingConnection conn) + + /// 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 member conn.updateById tableName (docId: 'TKey) (document: 'TDoc) = - WithProps.Update.byId tableName docId document (Sql.existingConnection conn) + 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 table in which a document should be updated (may include schema) + /// The function to obtain the ID of the document + /// The new document member conn.updateByFunc tableName (idFunc: 'TDoc -> 'TKey) (document: 'TDoc) = - WithProps.Update.byFunc tableName idFunc document (Sql.existingConnection conn) + Update.byFunc tableName idFunc document (Sql.existingConnection conn) - /// 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 member conn.patchById tableName (docId: 'TKey) (patch: 'TPatch) = - WithProps.Patch.byId tableName docId patch (Sql.existingConnection conn) - - /// Patch documents using a JSON field comparison query in the WHERE clause (->> =) + Patch.byId tableName docId patch (Sql.existingConnection 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 member conn.patchByFields tableName howMatched fields (patch: 'TPatch) = - WithProps.Patch.byFields tableName howMatched fields patch (Sql.existingConnection conn) - - /// Patch documents using a JSON containment query in the WHERE clause (@>) + Patch.byFields tableName howMatched fields patch (Sql.existingConnection conn) + + /// + /// Patch documents using a JSON containment query in the WHERE clause (@>) + /// + /// 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 member conn.patchByContains tableName (criteria: 'TCriteria) (patch: 'TPatch) = - WithProps.Patch.byContains tableName criteria patch (Sql.existingConnection conn) - - /// Patch documents using a JSON Path match query in the WHERE clause (@?) + Patch.byContains tableName criteria patch (Sql.existingConnection conn) + + /// Patch documents using a JSON Path match query in the WHERE clause (@?) + /// 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 member conn.patchByJsonPath tableName jsonPath (patch: 'TPatch) = - WithProps.Patch.byJsonPath tableName jsonPath patch (Sql.existingConnection conn) - - /// Remove fields from a document by the document's ID + Patch.byJsonPath tableName jsonPath patch (Sql.existingConnection conn) + + /// 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 member conn.removeFieldsById tableName (docId: 'TKey) fieldNames = - WithProps.RemoveFields.byId tableName docId fieldNames (Sql.existingConnection conn) - - /// Remove fields from documents via a comparison on JSON fields in the document + RemoveFields.byId tableName docId fieldNames (Sql.existingConnection 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 member conn.removeFieldsByFields tableName howMatched fields fieldNames = - WithProps.RemoveFields.byFields tableName howMatched fields fieldNames (Sql.existingConnection conn) - - /// Remove fields from documents via a JSON containment query (@>) + RemoveFields.byFields tableName howMatched fields fieldNames (Sql.existingConnection conn) + + /// Remove fields from documents via a JSON containment 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 member conn.removeFieldsByContains tableName (criteria: 'TContains) fieldNames = - WithProps.RemoveFields.byContains tableName criteria fieldNames (Sql.existingConnection conn) - - /// Remove fields from documents via a JSON Path match query (@?) + RemoveFields.byContains tableName criteria fieldNames (Sql.existingConnection conn) + + /// Remove fields from documents via a JSON Path match 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 member conn.removeFieldsByJsonPath tableName jsonPath fieldNames = - WithProps.RemoveFields.byJsonPath tableName jsonPath fieldNames (Sql.existingConnection conn) - - /// Delete a document by its ID + RemoveFields.byJsonPath tableName jsonPath fieldNames (Sql.existingConnection conn) + + /// 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 member conn.deleteById tableName (docId: 'TKey) = - WithProps.Delete.byId tableName docId (Sql.existingConnection conn) + Delete.byId tableName docId (Sql.existingConnection 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 member conn.deleteByFields tableName howMatched fields = - WithProps.Delete.byFields tableName howMatched fields (Sql.existingConnection conn) - - /// Delete documents by matching a JSON containment query (@>) - member conn.deleteByContains tableName (criteria: 'TContains) = - WithProps.Delete.byContains tableName criteria (Sql.existingConnection conn) + Delete.byFields tableName howMatched fields (Sql.existingConnection conn) - /// Delete documents by matching a JSON Path match query (@?) - member conn.deleteByJsonPath tableName path = - WithProps.Delete.byJsonPath tableName path (Sql.existingConnection conn) + /// Delete documents by matching a JSON contains query (@>) + /// The table in which documents should be deleted (may include schema) + /// The document to match the containment query + member conn.deleteByContains tableName (criteria: 'TContains) = + Delete.byContains tableName criteria (Sql.existingConnection conn) + + /// Delete documents by matching a JSON Path match query (@?) + /// The table in which documents should be deleted (may include schema) + /// The JSON Path expression to match + member conn.deleteByJsonPath tableName jsonPath = + Delete.byJsonPath tableName jsonPath (Sql.existingConnection conn) 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) = - WithProps.Custom.List<'TDoc>(query, parameters, mapFunc, Sql.existingConnection conn) + 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) = - WithProps.Custom.Single<'TDoc>(query, parameters, mapFunc, Sql.existingConnection conn) + 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) = - WithProps.Custom.nonQuery query parameters (Sql.existingConnection conn) + 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) = - WithProps.Custom.Scalar(query, parameters, mapFunc, Sql.existingConnection conn) - - /// Create a document table + Custom.Scalar(query, parameters, mapFunc, Sql.existingConnection conn) + + /// 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) = - WithProps.Definition.ensureTable name (Sql.existingConnection conn) + 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) = - WithProps.Definition.ensureDocumentIndex name idxType (Sql.existingConnection conn) - - /// Create an index on field(s) within documents in the specified table + Definition.ensureDocumentIndex name idxType (Sql.existingConnection conn) + + /// 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) = - WithProps.Definition.ensureFieldIndex tableName indexName fields (Sql.existingConnection conn) + 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) = - WithProps.Document.insert<'TDoc> tableName document (Sql.existingConnection conn) + 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) = - WithProps.Document.save<'TDoc> tableName document (Sql.existingConnection conn) + 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) = - WithProps.Count.all tableName (Sql.existingConnection conn) - - /// Count matching documents using a JSON field comparison query (->> =) + Count.all tableName (Sql.existingConnection conn) + + /// 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) = - WithProps.Count.byFields tableName howMatched fields (Sql.existingConnection conn) - - /// Count matching documents using a JSON containment query (@>) + Count.byFields tableName howMatched fields (Sql.existingConnection conn) + + /// 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) = - WithProps.Count.byContains tableName criteria (Sql.existingConnection conn) + 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) = - WithProps.Count.byJsonPath tableName jsonPath (Sql.existingConnection conn) + 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) = - WithProps.Exists.byId tableName docId (Sql.existingConnection conn) - - /// Determine if documents exist using a JSON field comparison query (->> =) + Exists.byId tableName docId (Sql.existingConnection conn) + + /// 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) = - WithProps.Exists.byFields tableName howMatched fields (Sql.existingConnection conn) - - /// Determine if documents exist using a JSON containment query (@>) + Exists.byFields tableName howMatched fields (Sql.existingConnection conn) + + /// 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) = - WithProps.Exists.byContains tableName criteria (Sql.existingConnection conn) + 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) = - WithProps.Exists.byJsonPath tableName jsonPath (Sql.existingConnection conn) - - /// Retrieve all documents in the given table + Exists.byJsonPath tableName jsonPath (Sql.existingConnection conn) + + /// 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) = - WithProps.Find.All<'TDoc>(tableName, Sql.existingConnection conn) + 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) = - WithProps.Find.AllOrdered<'TDoc>(tableName, orderFields, Sql.existingConnection conn) + 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) = - WithProps.Find.ById<'TKey, 'TDoc>(tableName, docId, Sql.existingConnection conn) + 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) = - WithProps.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 + Find.ByFields<'TDoc>(tableName, howMatched, fields, Sql.existingConnection conn) + + /// + /// 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) = - WithProps.Find.ByFieldsOrdered<'TDoc>( + 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) = - WithProps.Find.ByContains<'TDoc>(tableName, criteria, Sql.existingConnection conn) + 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) = - WithProps.Find.ByContainsOrdered<'TDoc>(tableName, criteria, orderFields, Sql.existingConnection conn) + 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) = - WithProps.Find.ByJsonPath<'TDoc>(tableName, jsonPath, Sql.existingConnection conn) - - /// Retrieve documents matching a JSON Path match query (@?) ordered by the given fields in the document + Find.ByJsonPath<'TDoc>(tableName, jsonPath, Sql.existingConnection conn) + + /// + /// 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) = - WithProps.Find.ByJsonPathOrdered<'TDoc>(tableName, jsonPath, orderFields, Sql.existingConnection conn) - - /// Retrieve the first document matching a JSON field comparison query (->> =); returns null if not found + Find.ByJsonPathOrdered<'TDoc>(tableName, jsonPath, orderFields, Sql.existingConnection conn) + + /// 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) = - WithProps.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 + Find.FirstByFields<'TDoc>(tableName, howMatched, fields, Sql.existingConnection conn) + + /// + /// 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) = - WithProps.Find.FirstByFieldsOrdered<'TDoc>( + 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) = - WithProps.Find.FirstByContains<'TDoc>(tableName, criteria, Sql.existingConnection conn) + 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) = - WithProps.Find.FirstByContainsOrdered<'TDoc>(tableName, criteria, orderFields, Sql.existingConnection conn) + 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) = - WithProps.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 + 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 + /// + /// 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) = - WithProps.Find.FirstByJsonPathOrdered<'TDoc>(tableName, jsonPath, orderFields, Sql.existingConnection conn) - - /// Update an entire document by its ID + Find.FirstByJsonPathOrdered<'TDoc>(tableName, jsonPath, orderFields, Sql.existingConnection conn) + + /// 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) = - WithProps.Update.byId tableName docId document (Sql.existingConnection conn) + 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) = - WithProps.Update.ByFunc(tableName, idFunc, document, Sql.existingConnection conn) + 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) = - WithProps.Patch.byId tableName docId patch (Sql.existingConnection conn) - - /// Patch documents using a JSON field comparison query in the WHERE clause (->> =) + Patch.byId tableName docId patch (Sql.existingConnection conn) + + /// + /// 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) = - WithProps.Patch.byFields tableName howMatched fields patch (Sql.existingConnection conn) - - /// Patch documents using a JSON containment query in the WHERE clause (@>) + Patch.byFields tableName howMatched fields patch (Sql.existingConnection conn) + + /// 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) = - WithProps.Patch.byContains tableName criteria patch (Sql.existingConnection conn) - - /// Patch documents using a JSON Path match query in the WHERE clause (@?) + Patch.byContains tableName criteria patch (Sql.existingConnection conn) + + /// 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) = - WithProps.Patch.byJsonPath tableName jsonPath patch (Sql.existingConnection conn) - - /// Remove fields from a document by the document's ID + Patch.byJsonPath tableName jsonPath patch (Sql.existingConnection conn) + + /// 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) = - WithProps.RemoveFields.byId tableName docId fieldNames (Sql.existingConnection conn) - - /// Remove fields from documents via a comparison on JSON fields in the document + RemoveFields.byId tableName docId fieldNames (Sql.existingConnection conn) + + /// 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) = - WithProps.RemoveFields.byFields tableName howMatched fields fieldNames (Sql.existingConnection conn) - - /// Remove fields from documents via a JSON containment query (@>) + RemoveFields.byFields tableName howMatched fields fieldNames (Sql.existingConnection conn) + + /// 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) = - WithProps.RemoveFields.byContains tableName criteria fieldNames (Sql.existingConnection conn) - - /// Remove fields from documents via a JSON Path match query (@?) + RemoveFields.byContains tableName criteria fieldNames (Sql.existingConnection conn) + + /// 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) = - WithProps.RemoveFields.byJsonPath tableName jsonPath fieldNames (Sql.existingConnection conn) - - /// Delete a document by its ID + RemoveFields.byJsonPath tableName jsonPath fieldNames (Sql.existingConnection conn) + + /// 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) = - WithProps.Delete.byId tableName docId (Sql.existingConnection conn) - - /// Delete documents by matching a JSON field comparison query (->> =) + Delete.byId tableName docId (Sql.existingConnection conn) + + /// 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) = - WithProps.Delete.byFields tableName howMatched fields (Sql.existingConnection conn) - - /// Delete documents by matching a JSON containment query (@>) + Delete.byFields tableName howMatched fields (Sql.existingConnection conn) + + /// 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) = - WithProps.Delete.byContains tableName criteria (Sql.existingConnection conn) + 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) = - WithProps.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/Functions.fs b/src/Postgres/Functions.fs new file mode 100644 index 0000000..fb189e9 --- /dev/null +++ b/src/Postgres/Functions.fs @@ -0,0 +1,617 @@ +namespace BitBadger.Documents.Postgres + +/// Commands to execute custom SQL queries +[] +module Custom = + + /// Execute a query that returns a list of results + /// The query to retrieve the results + /// Parameters to use for the query + /// The mapping function between the document and the domain item + /// A list of results for the given query + [] + let list<'TDoc> query parameters (mapFunc: RowReader -> 'TDoc) = + WithProps.Custom.list<'TDoc> query parameters mapFunc (fromDataSource ()) + + /// 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) = + WithProps.Custom.List<'TDoc>(query, parameters, mapFunc, fromDataSource ()) + + /// 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: RowReader -> 'TDoc) = + WithProps.Custom.single<'TDoc> query parameters mapFunc (fromDataSource ()) + + /// 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) = + WithProps.Custom.Single<'TDoc>(query, parameters, mapFunc, fromDataSource ()) + + /// Execute a query that returns no results + /// The query to retrieve the results + /// Parameters to use for the query + [] + let nonQuery query parameters = + WithProps.Custom.nonQuery query parameters (fromDataSource ()) + + /// 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: RowReader -> 'T) = + WithProps.Custom.scalar query parameters mapFunc (fromDataSource ()) + + /// 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) = + WithProps.Custom.Scalar<'T>(query, parameters, mapFunc, fromDataSource ()) + + +/// Table and index definition commands +[] +module Definition = + + /// Create a document table + /// The table whose existence should be ensured (may include schema) + [] + let ensureTable name = + WithProps.Definition.ensureTable name (fromDataSource ()) + + /// Create an index on documents in the specified table + /// The table to be indexed (may include schema) + /// The type of document index to create + [] + let ensureDocumentIndex name idxType = + WithProps.Definition.ensureDocumentIndex name idxType (fromDataSource ()) + + /// Create an index on field(s) within documents in the specified table + /// The table to be indexed (may include schema) + /// The name of the index to create + /// One or more fields to be indexed + [] + let ensureFieldIndex tableName indexName fields = + WithProps.Definition.ensureFieldIndex tableName indexName fields (fromDataSource ()) + + +/// Document writing functions +[] +module Document = + + /// Insert a new document + /// The table into which the document should be inserted (may include schema) + /// The document to be inserted + [] + let insert<'TDoc> tableName (document: 'TDoc) = + WithProps.Document.insert<'TDoc> tableName document (fromDataSource ()) + + /// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert") + /// The table into which the document should be saved (may include schema) + /// The document to be saved + [] + let save<'TDoc> tableName (document: 'TDoc) = + WithProps.Document.save<'TDoc> tableName document (fromDataSource ()) + + +/// Queries to count documents +[] +module Count = + + /// Count all documents in a table + /// The table in which documents should be counted (may include schema) + /// The count of the documents in the table + [] + let all tableName = + WithProps.Count.all tableName (fromDataSource ()) + + /// Count matching documents using JSON field comparisons (->> =, etc.) + /// The table in which documents should be counted (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// The count of matching documents in the table + [] + let byFields tableName howMatched fields = + WithProps.Count.byFields tableName howMatched fields (fromDataSource ()) + + /// Count matching documents using a JSON containment 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 + [] + let byContains tableName criteria = + WithProps.Count.byContains tableName criteria (fromDataSource ()) + + /// Count matching documents using a JSON Path match 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 + [] + let byJsonPath tableName jsonPath = + WithProps.Count.byJsonPath tableName jsonPath (fromDataSource ()) + + +/// Queries to determine if documents exist +[] +module Exists = + + /// Determine if a document exists for the given ID + /// The table in which existence should be checked (may include schema) + /// The ID of the document whose existence should be checked + /// True if a document exists, false if not + [] + let byId tableName docId = + WithProps.Exists.byId tableName docId (fromDataSource ()) + + /// Determine if a document exists using JSON field comparisons (->> =, etc.) + /// The table in which existence should be checked (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// True if any matching documents exist, false if not + [] + let byFields tableName howMatched fields = + WithProps.Exists.byFields tableName howMatched fields (fromDataSource ()) + + /// Determine if a document exists using a JSON containment 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 + [] + let byContains tableName criteria = + WithProps.Exists.byContains tableName criteria (fromDataSource ()) + + /// Determine if a document exists using a JSON Path match 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 + [] + let byJsonPath tableName jsonPath = + WithProps.Exists.byJsonPath tableName jsonPath (fromDataSource ()) + + +/// Commands to retrieve documents +[] +module Find = + + /// Retrieve all documents in the given table + /// The table from which documents should be retrieved (may include schema) + /// All documents from the given table + [] + let all<'TDoc> tableName = + WithProps.Find.all<'TDoc> tableName (fromDataSource ()) + + /// Retrieve all documents in the given table + /// The table from which documents should be retrieved (may include schema) + /// All documents from the given table + let All<'TDoc> tableName = + WithProps.Find.All<'TDoc>(tableName, fromDataSource ()) + + /// Retrieve all documents in the given table ordered by the given fields in the document + /// The table from which documents should be retrieved (may include schema) + /// Fields by which the results should be ordered + /// All documents from the given table, ordered by the given fields + [] + let allOrdered<'TDoc> tableName orderFields = + WithProps.Find.allOrdered<'TDoc> tableName orderFields (fromDataSource ()) + + /// Retrieve all documents in the given table ordered by the given fields in the document + /// The table from which documents should be retrieved (may include schema) + /// Fields by which the results should be ordered + /// All documents from the given table, ordered by the given fields + let AllOrdered<'TDoc> tableName orderFields = + WithProps.Find.AllOrdered<'TDoc>(tableName, orderFields, fromDataSource ()) + + /// Retrieve a document by its ID + /// The table from which a document should be retrieved (may include schema) + /// The ID of the document to retrieve + /// Some with the document if found, None otherwise + [] + let byId<'TKey, 'TDoc> tableName docId = + WithProps.Find.byId<'TKey, 'TDoc> tableName docId (fromDataSource ()) + + /// Retrieve a document by its ID + /// The table from which a document should be retrieved (may include schema) + /// The ID of the document to retrieve + /// The document if found, null otherwise + let ById<'TKey, 'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, docId: 'TKey) = + WithProps.Find.ById<'TKey, 'TDoc>(tableName, docId, fromDataSource ()) + + /// Retrieve documents matching JSON field comparisons (->> =, etc.) + /// The table from which documents should be retrieved (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// All documents matching the given fields + [] + let byFields<'TDoc> tableName howMatched fields = + WithProps.Find.byFields<'TDoc> tableName howMatched fields (fromDataSource ()) + + /// Retrieve documents matching JSON field comparisons (->> =, etc.) + /// The table from which documents should be retrieved (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// All documents matching the given fields + let ByFields<'TDoc>(tableName, howMatched, fields) = + WithProps.Find.ByFields<'TDoc>(tableName, howMatched, fields, fromDataSource ()) + + /// + /// Retrieve documents matching JSON field comparisons (->> =, etc.) ordered by the given fields in + /// the document + /// + /// The table from which documents should be retrieved (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// Fields by which the results should be ordered + /// All documents matching the given fields, ordered by the other given fields + [] + let byFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields = + WithProps.Find.byFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields (fromDataSource ()) + + /// + /// Retrieve documents matching JSON field comparisons (->> =, etc.) ordered by the given fields in + /// the document + /// + /// The table from which documents should be retrieved (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// Fields by which the results should be ordered + /// All documents matching the given fields, ordered by the other given fields + let ByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields) = + WithProps.Find.ByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, fromDataSource ()) + + /// Retrieve documents matching a JSON containment 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 + [] + let byContains<'TDoc> tableName (criteria: obj) = + WithProps.Find.byContains<'TDoc> tableName criteria (fromDataSource ()) + + /// Retrieve documents matching a JSON containment 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 + let ByContains<'TDoc>(tableName, criteria: obj) = + WithProps.Find.ByContains<'TDoc>(tableName, criteria, fromDataSource ()) + + /// + /// Retrieve documents matching a JSON containment query (@>) ordered by the given fields in the + /// document + /// + /// 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 + [] + let byContainsOrdered<'TDoc> tableName (criteria: obj) orderFields = + WithProps.Find.byContainsOrdered<'TDoc> tableName criteria orderFields (fromDataSource ()) + + /// + /// Retrieve documents matching a JSON containment query (@>) ordered by the given fields in the + /// document + /// + /// 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 + let ByContainsOrdered<'TDoc>(tableName, criteria: obj, orderFields) = + WithProps.Find.ByContainsOrdered<'TDoc>(tableName, criteria, orderFields, fromDataSource ()) + + /// Retrieve documents matching a JSON Path match 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 + [] + let byJsonPath<'TDoc> tableName jsonPath = + WithProps.Find.byJsonPath<'TDoc> tableName jsonPath (fromDataSource ()) + + /// Retrieve documents matching a JSON Path match 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 + let ByJsonPath<'TDoc>(tableName, jsonPath) = + WithProps.Find.ByJsonPath<'TDoc>(tableName, jsonPath, fromDataSource ()) + + /// + /// Retrieve documents matching a JSON Path match query (@?) ordered by the given fields in the document + /// + /// 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 + [] + let byJsonPathOrdered<'TDoc> tableName jsonPath orderFields = + WithProps.Find.byJsonPathOrdered<'TDoc> tableName jsonPath orderFields (fromDataSource ()) + + /// + /// Retrieve documents matching a JSON Path match query (@?) ordered by the given fields in the document + /// + /// 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 + let ByJsonPathOrdered<'TDoc>(tableName, jsonPath, orderFields) = + WithProps.Find.ByJsonPathOrdered<'TDoc>(tableName, jsonPath, orderFields, fromDataSource ()) + + /// Retrieve the first document matching JSON field comparisons (->> =, etc.) + /// The table from which a document should be retrieved (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// Some with the first document, or None if not found + [] + let firstByFields<'TDoc> tableName howMatched fields = + WithProps.Find.firstByFields<'TDoc> tableName howMatched fields (fromDataSource ()) + + /// Retrieve the first document matching JSON field comparisons (->> =, etc.) + /// The table from which a document should be retrieved (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// The first document, or null if not found + let FirstByFields<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, howMatched, fields) = + WithProps.Find.FirstByFields<'TDoc>(tableName, howMatched, fields, fromDataSource ()) + + /// + /// Retrieve the first document matching JSON field comparisons (->> =, etc.) ordered by the given + /// fields in the document + /// + /// The table from which a document should be retrieved (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// Fields by which the results should be ordered + /// + /// Some with the first document ordered by the given fields, or None if not found + /// + [] + let firstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields = + WithProps.Find.firstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields (fromDataSource ()) + + /// + /// Retrieve the first document matching JSON field comparisons (->> =, etc.) ordered by the given + /// fields in the document + /// + /// The table from which a document should be retrieved (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// Fields by which the results should be ordered + /// The first document ordered by the given fields, or null if not found + let FirstByFieldsOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>( + tableName, howMatched, queryFields, orderFields) = + WithProps.Find.FirstByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, fromDataSource ()) + + /// Retrieve the first document matching a JSON containment query (@>) + /// The table from which a document should be retrieved (may include schema) + /// The document to match with the containment query + /// Some with the first document, or None if not found + [] + let firstByContains<'TDoc> tableName (criteria: obj) = + WithProps.Find.firstByContains<'TDoc> tableName criteria (fromDataSource ()) + + /// Retrieve the first document matching a JSON containment 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 + let FirstByContains<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, criteria: obj) = + WithProps.Find.FirstByContains<'TDoc>(tableName, criteria, fromDataSource ()) + + /// + /// Retrieve the first document matching a JSON containment query (@>) ordered by the given fields in + /// the document + /// + /// 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 + /// + /// Some with the first document ordered by the given fields, or None if not found + /// + [] + let firstByContainsOrdered<'TDoc> tableName (criteria: obj) orderFields = + WithProps.Find.firstByContainsOrdered<'TDoc> tableName criteria orderFields (fromDataSource ()) + + /// + /// Retrieve the first document matching a JSON containment query (@>) ordered by the given fields in + /// the document + /// + /// 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 + let FirstByContainsOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, criteria: obj, orderFields) = + WithProps.Find.FirstByContainsOrdered<'TDoc>(tableName, criteria, orderFields, fromDataSource ()) + + /// Retrieve the first document matching a JSON Path match query (@?) + /// The table from which a document should be retrieved (may include schema) + /// The JSON Path expression to match + /// Some with the first document, or None if not found + [] + let firstByJsonPath<'TDoc> tableName jsonPath = + WithProps.Find.firstByJsonPath<'TDoc> tableName jsonPath (fromDataSource ()) + + /// Retrieve the first document matching a JSON Path match 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 + let FirstByJsonPath<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, jsonPath) = + WithProps.Find.FirstByJsonPath<'TDoc>(tableName, jsonPath, fromDataSource ()) + + /// + /// Retrieve the first document matching a JSON Path match query (@?) ordered by the given fields in the + /// document + /// + /// 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 + /// + /// Some with the first document ordered by the given fields, or None if not found + /// + [] + let firstByJsonPathOrdered<'TDoc> tableName jsonPath orderFields = + WithProps.Find.firstByJsonPathOrdered<'TDoc> tableName jsonPath orderFields (fromDataSource ()) + + /// + /// Retrieve the first document matching a JSON Path match query (@?) ordered by the given fields in the + /// document + /// + /// 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 + let FirstByJsonPathOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, jsonPath, orderFields) = + WithProps.Find.FirstByJsonPathOrdered<'TDoc>(tableName, jsonPath, orderFields, fromDataSource ()) + + +/// Commands to update documents +[] +module Update = + + /// Update (replace) an entire document by its ID + /// The table in which a document should be updated (may include schema) + /// The ID of the document to be updated (replaced) + /// The new document + [] + let byId tableName (docId: 'TKey) (document: 'TDoc) = + WithProps.Update.byId tableName docId document (fromDataSource ()) + + /// + /// Update (replace) an entire document by its ID, using the provided function to obtain the ID from the document + /// + /// The table in which a document should be updated (may include schema) + /// The function to obtain the ID of the document + /// The new document + [] + let byFunc tableName (idFunc: 'TDoc -> 'TKey) (document: 'TDoc) = + WithProps.Update.byFunc tableName idFunc document (fromDataSource ()) + + /// + /// Update (replace) an entire document by its ID, using the provided function to obtain the ID from the document + /// + /// The table in which a document should be updated (may include schema) + /// The function to obtain the ID of the document + /// The new document + let ByFunc(tableName, idFunc: System.Func<'TDoc, 'TKey>, document: 'TDoc) = + WithProps.Update.ByFunc(tableName, idFunc, document, fromDataSource ()) + + +/// Commands to patch (partially update) documents +[] +module Patch = + + /// Patch a document by its ID + /// The table in which a document should be patched (may include schema) + /// The ID of the document to patch + /// The partial document to patch the existing document + [] + let byId tableName (docId: 'TKey) (patch: 'TPatch) = + WithProps.Patch.byId tableName docId patch (fromDataSource ()) + + /// + /// Patch documents using a JSON field comparison query in the WHERE clause (->> =, etc.) + /// + /// The table in which documents should be patched (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// The partial document to patch the existing document + [] + let byFields tableName howMatched fields (patch: 'TPatch) = + WithProps.Patch.byFields tableName howMatched fields patch (fromDataSource ()) + + /// Patch documents using a JSON containment query in the WHERE clause (@>) + /// 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 + [] + let byContains tableName (criteria: 'TCriteria) (patch: 'TPatch) = + WithProps.Patch.byContains tableName criteria patch (fromDataSource ()) + + /// Patch documents using a JSON Path match query in the WHERE clause (@?) + /// 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 + [] + let byJsonPath tableName jsonPath (patch: 'TPatch) = + WithProps.Patch.byJsonPath tableName jsonPath patch (fromDataSource ()) + + +/// Commands to remove fields from documents +[] +module RemoveFields = + + /// Remove fields from a document by the document's ID + /// The table in which a document should be modified (may include schema) + /// The ID of the document to modify + /// One or more field names to remove from the document + [] + let byId tableName (docId: 'TKey) fieldNames = + WithProps.RemoveFields.byId tableName docId fieldNames (fromDataSource ()) + + /// Remove fields from documents via a comparison on JSON fields in the document + /// The table in which documents should be modified (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// One or more field names to remove from the matching documents + [] + let byFields tableName howMatched fields fieldNames = + WithProps.RemoveFields.byFields tableName howMatched fields fieldNames (fromDataSource ()) + + /// Remove fields from documents via a JSON containment 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 + [] + let byContains tableName (criteria: 'TContains) fieldNames = + WithProps.RemoveFields.byContains tableName criteria fieldNames (fromDataSource ()) + + /// Remove fields from documents via a JSON Path match 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 + [] + let byJsonPath tableName jsonPath fieldNames = + WithProps.RemoveFields.byJsonPath tableName jsonPath fieldNames (fromDataSource ()) + + +/// Commands to delete documents +[] +module Delete = + + /// Delete a document by its ID + /// The table in which a document should be deleted (may include schema) + /// The ID of the document to delete + [] + let byId tableName (docId: 'TKey) = + WithProps.Delete.byId tableName docId (fromDataSource ()) + + /// Delete documents by matching a JSON field comparison query (->> =, etc.) + /// The table in which documents should be deleted (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + [] + let byFields tableName howMatched fields = + WithProps.Delete.byFields tableName howMatched fields (fromDataSource ()) + + /// Delete documents by matching a JSON contains query (@>) + /// The table in which documents should be deleted (may include schema) + /// The document to match the containment query + [] + let byContains tableName (criteria: 'TContains) = + WithProps.Delete.byContains tableName criteria (fromDataSource ()) + + /// Delete documents by matching a JSON Path match query (@?) + /// The table in which documents should be deleted (may include schema) + /// The JSON Path expression to match + [] + let byJsonPath tableName jsonPath = + WithProps.Delete.byJsonPath tableName jsonPath (fromDataSource ()) diff --git a/src/Postgres/Library.fs b/src/Postgres/Library.fs index 39b3882..edf03af 100644 --- a/src/Postgres/Library.fs +++ b/src/Postgres/Library.fs @@ -1,44 +1,49 @@ namespace BitBadger.Documents.Postgres -/// The type of index to generate for the document +/// The type of index to generate for the document [] type DocumentIndex = - - /// A GIN index with standard operations (all operators supported) + + /// A GIN index with standard operations (all operators supported) | Full - - /// A GIN index with JSONPath operations (optimized for @>, @?, @@ operators) + + /// + /// A GIN index with JSONPath operations (optimized for @>, @?, @@ operators) + /// | Optimized open Npgsql -/// Configuration for document handling +/// Configuration for document handling module Configuration = /// The data source to use for query execution let mutable private dataSourceValue : NpgsqlDataSource option = None - /// Register a data source to use for query execution (disposes the current one if it exists) + /// Register a data source to use for query execution (disposes the current one if it exists) + /// The data source to use [] let useDataSource source = if Option.isSome dataSourceValue then dataSourceValue.Value.Dispose() dataSourceValue <- Some source - - /// Retrieve the currently configured data source + + /// Retrieve the currently configured data source + /// The current data source + /// If no data source has been configured [] let dataSource () = match dataSourceValue with | Some source -> source | None -> invalidOp "Please provide a data source before attempting data access" - + open Npgsql.FSharp /// Helper functions [] module private Helpers = - + /// Shorthand to retrieve the data source as SqlProps let internal fromDataSource () = Configuration.dataSource () |> Sql.fromDataSource @@ -48,7 +53,7 @@ module private Helpers = let! _ = it () } - + /// Create a number or string parameter, or use the given parameter derivation function if non-(numeric or string) let internal parameterFor<'T> (value: 'T) (catchAllFunc: 'T -> SqlValue) = match box value with @@ -69,21 +74,29 @@ module private Helpers = open BitBadger.Documents -/// Functions for creating parameters +/// Functions for creating parameters [] module Parameters = - - /// Create an ID parameter (name "@id") + + /// 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) = "@id", parameterFor key (fun it -> Sql.string (string it)) - /// 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: string) (it: 'TJson) = name, Sql.jsonb (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() @@ -101,7 +114,7 @@ module Parameters = yield! values |> Seq.mapi (fun idx v -> - let paramName = $"{p}_{idx}" + let paramName = $"{p}_{idx}" paramName, Sql.parameter (NpgsqlParameter(paramName, v))) | InArray (_, values) -> let p = name.Derive it.ParameterName @@ -114,23 +127,30 @@ module Parameters = |> Seq.toList |> Seq.ofList - /// 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 names of fields to be addressed + /// The name (@name) and parameter value for the field names [] let fieldNameParams (fieldNames: string seq) = if Seq.length fieldNames = 1 then "@name", Sql.string (Seq.head fieldNames) else "@name", Sql.stringArray (Array.ofSeq fieldNames) - - /// An empty parameter sequence + + /// An empty parameter sequence [] let noParams = Seq.empty - -/// Query construction functions + +/// Query construction functions [] 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() @@ -159,972 +179,126 @@ module Query = else $"{it.Path PostgreSQL AsSql} {it.Comparison.OpSql} {param}") |> 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<'TKey> (docId: 'TKey) = whereByFields Any [ { Field.Equal (Configuration.idField ()) docId with ParameterName = Some "@id" } ] - - /// Table and index definition queries + + /// Table and index definition queries 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 "JSONB" - - /// SQL statement to create an index on JSON documents in the specified table + + /// SQL statement to create an index on JSON documents in the specified table + /// The name of the table to be indexed (may include schema) + /// The type of document index to create + /// A query to create the index if it does not exist [] let ensureDocumentIndex (name: string) idxType = let extraOps = match idxType with Full -> "" | Optimized -> " jsonb_path_ops" let tableName = name.Split '.' |> Array.last $"CREATE INDEX IF NOT EXISTS idx_{tableName}_document ON {name} USING GIN (data{extraOps})" - /// Create a WHERE clause fragment to implement a @> (JSON contains) condition + /// + /// Create a WHERE clause fragment to implement a @> (JSON contains) condition + /// + /// The parameter name for the query + /// A WHERE clause fragment for the contains condition [] let whereDataContains paramName = $"data @> %s{paramName}" - - /// Create a WHERE clause fragment to implement a @? (JSON Path match) condition + + /// + /// Create a WHERE clause fragment to implement a @? (JSON Path match) condition + /// + /// The parameter name for the query + /// A WHERE clause fragment for the JSON Path match condition [] let whereJsonPathMatches paramName = $"data @? %s{paramName}::jsonpath" - - /// Create an UPDATE statement to patch documents + + /// Create an UPDATE statement to patch documents + /// The table to be updated + /// A query to patch documents [] let patch tableName = $"UPDATE %s{tableName} SET data = data || @data" - /// Create an UPDATE statement to remove fields from documents + /// Create an UPDATE statement to remove fields from documents + /// The table to be updated + /// A query to remove fields from documents [] let removeFields tableName = $"UPDATE %s{tableName} SET data = data - @name" - /// 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 (whereById docId) - - /// 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) - - /// Create a JSON containment query + + /// Create a JSON containment query + /// The SQL statement to be run against the containment query + /// A query addressing documents by a JSON containment query [] let byContains statement = Query.statementWhere statement (whereDataContains "@criteria") - - /// Create a JSON Path match query + + /// Create a JSON Path match query + /// The SQL statement to run against the JSON Path match + /// A query addressing documents by a JSON Path match [] let byPathMatch statement = Query.statementWhere statement (whereJsonPathMatches "@path") -/// Functions for dealing with results +/// Functions for dealing with 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 row reader set to the row with the document to be constructed + /// The constructed domain item [] let fromDocument<'T> field (row: RowReader) : 'T = Configuration.serializer().Deserialize<'T>(row.string field) - - /// Create a domain item from a document + + /// Create a domain item from a document + /// A row reader set to the row with the document to be constructed + /// The constructed domain item [] let fromData<'T> row : 'T = fromDocument "data" row - - /// Extract a count from the column "it" + + /// Extract a count from the column it + /// A row reader set to the row with the count to retrieve + /// The count from the row [] let toCount (row: RowReader) = row.int "it" - - /// Extract a true/false value from the column "it" + + /// Extract a true/false value from the column it + /// A row reader set to the row with the true/false value to retrieve + /// The true/false value from the row [] let toExists (row: RowReader) = row.bool "it" - - -/// Versions of queries that accept SqlProps as the last parameter -module WithProps = - - module FSharpList = Microsoft.FSharp.Collections.List - - /// Commands to execute custom SQL queries - [] - module Custom = - - /// Execute a query that returns a list of results - [] - let list<'TDoc> query parameters (mapFunc: RowReader -> 'TDoc) sqlProps = - Sql.query query sqlProps - |> Sql.parameters (List.ofSeq parameters) - |> Sql.executeAsync mapFunc - - /// Execute a query that returns a list of results - let List<'TDoc>(query, parameters, mapFunc: System.Func, sqlProps) = backgroundTask { - let! results = list<'TDoc> query parameters mapFunc.Invoke sqlProps - return ResizeArray results - } - - /// Execute a query that returns one or no results; returns None if not found - [] - let single<'TDoc> query parameters mapFunc sqlProps = backgroundTask { - let! results = list<'TDoc> query parameters mapFunc sqlProps - return FSharp.Collections.List.tryHead results - } - - /// Execute a query that returns one or no results; returns null if not found - let Single<'TDoc when 'TDoc: null and 'TDoc: not struct>( - query, parameters, mapFunc: System.Func, sqlProps) = backgroundTask { - let! result = single<'TDoc> query parameters mapFunc.Invoke sqlProps - return Option.toObj result - } - - /// Execute a query that returns no results - [] - let nonQuery query parameters sqlProps = - Sql.query query sqlProps - |> Sql.parameters (FSharpList.ofSeq parameters) - |> Sql.executeNonQueryAsync - |> ignoreTask - - /// Execute a query that returns a scalar value - [] - let scalar<'T when 'T: struct> query parameters (mapFunc: RowReader -> 'T) sqlProps = - Sql.query query sqlProps - |> Sql.parameters (FSharpList.ofSeq parameters) - |> Sql.executeRowAsync mapFunc - - /// Execute a query that returns a scalar value - let Scalar<'T when 'T: struct>(query, parameters, mapFunc: System.Func, sqlProps) = - scalar<'T> query parameters mapFunc.Invoke sqlProps - - /// Table and index definition commands - module Definition = - - /// Create a document table - [] - let ensureTable name sqlProps = backgroundTask { - do! Custom.nonQuery (Query.Definition.ensureTable name) [] sqlProps - do! Custom.nonQuery (Query.Definition.ensureKey name PostgreSQL) [] sqlProps - } - - /// Create an index on documents in the specified table - [] - let ensureDocumentIndex name idxType sqlProps = - Custom.nonQuery (Query.Definition.ensureDocumentIndex name idxType) [] sqlProps - - /// Create an index on field(s) within documents in the specified table - [] - let ensureFieldIndex tableName indexName fields sqlProps = - Custom.nonQuery (Query.Definition.ensureIndexOn tableName indexName fields PostgreSQL) [] sqlProps - - /// Commands to add documents - [] - module Document = - - /// Insert a new document - [] - let insert<'TDoc> tableName (document: 'TDoc) sqlProps = - 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}')::numeric), 0) + 1 FROM {tableName}) || '" - | Guid -> $"\"{AutoId.GenerateGuid()}\"" - | RandomString -> $"\"{AutoId.GenerateRandomString(Configuration.idStringLength ())}\"" - | Disabled -> "@data" - |> function it -> $"""@data::jsonb || ('{{"{idField}":{it}}}')::jsonb""" - else "@data" - (Query.insert tableName).Replace("@data", dataParam) - Custom.nonQuery query [ jsonParam "@data" document ] sqlProps - - /// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert") - [] - let save<'TDoc> tableName (document: 'TDoc) sqlProps = - Custom.nonQuery (Query.save tableName) [ jsonParam "@data" document ] sqlProps - - /// Commands to count documents - [] - module Count = - - /// Count all documents in a table - [] - let all tableName sqlProps = - Custom.scalar (Query.count tableName) [] toCount sqlProps - - /// Count matching documents using JSON field comparisons (->> =) - [] - let byFields tableName howMatched fields sqlProps = - Custom.scalar - (Query.byFields (Query.count tableName) howMatched fields) (addFieldParams fields []) toCount sqlProps - - /// Count matching documents using a JSON containment query (@>) - [] - let byContains tableName (criteria: 'TContains) sqlProps = - Custom.scalar - (Query.byContains (Query.count tableName)) [ jsonParam "@criteria" criteria ] toCount sqlProps - - /// Count matching documents using a JSON Path match query (@?) - [] - let byJsonPath tableName jsonPath sqlProps = - Custom.scalar - (Query.byPathMatch (Query.count tableName)) [ "@path", Sql.string jsonPath ] toCount sqlProps - - /// Commands to determine if documents exist - [] - module Exists = - - /// Determine if a document exists for the given ID - [] - let byId tableName (docId: 'TKey) sqlProps = - Custom.scalar (Query.exists tableName (Query.whereById docId)) [ idParam docId ] toExists sqlProps - - /// Determine if a document exists using JSON field comparisons (->> =) - [] - let byFields tableName howMatched fields sqlProps = - Custom.scalar - (Query.exists tableName (Query.whereByFields howMatched fields)) - (addFieldParams fields []) - toExists - sqlProps - - /// Determine if a document exists using a JSON containment query (@>) - [] - let byContains tableName (criteria: 'TContains) sqlProps = - Custom.scalar - (Query.exists tableName (Query.whereDataContains "@criteria")) - [ jsonParam "@criteria" criteria ] - toExists - sqlProps - - /// Determine if a document exists using a JSON Path match query (@?) - [] - let byJsonPath tableName jsonPath sqlProps = - Custom.scalar - (Query.exists tableName (Query.whereJsonPathMatches "@path")) - [ "@path", Sql.string jsonPath ] - toExists - sqlProps - - /// Commands to determine if documents exist - [] - module Find = - - /// Retrieve all documents in the given table - [] - let all<'TDoc> tableName sqlProps = - Custom.list<'TDoc> (Query.find tableName) [] fromData<'TDoc> sqlProps - - /// Retrieve all documents in the given table - let All<'TDoc>(tableName, sqlProps) = - Custom.List<'TDoc>(Query.find tableName, [], fromData<'TDoc>, sqlProps) - - /// Retrieve all documents in the given table ordered by the given fields in the document - [] - let allOrdered<'TDoc> tableName orderFields sqlProps = - Custom.list<'TDoc> (Query.find tableName + Query.orderBy orderFields PostgreSQL) [] fromData<'TDoc> sqlProps - - /// Retrieve all documents in the given table ordered by the given fields in the document - let AllOrdered<'TDoc>(tableName, orderFields, sqlProps) = - Custom.List<'TDoc>( - Query.find tableName + Query.orderBy orderFields PostgreSQL, [], fromData<'TDoc>, sqlProps) - - /// Retrieve a document by its ID (returns None if not found) - [] - let byId<'TKey, 'TDoc> tableName (docId: 'TKey) sqlProps = - Custom.single (Query.byId (Query.find tableName) docId) [ idParam docId ] fromData<'TDoc> sqlProps - - /// Retrieve a document by its ID (returns null if not found) - let ById<'TKey, 'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, docId: 'TKey, sqlProps) = - Custom.Single<'TDoc>( - Query.byId (Query.find tableName) docId, [ idParam docId ], fromData<'TDoc>, sqlProps) - - /// Retrieve documents matching JSON field comparisons (->> =) - [] - let byFields<'TDoc> tableName howMatched fields sqlProps = - Custom.list<'TDoc> - (Query.byFields (Query.find tableName) howMatched fields) - (addFieldParams fields []) - fromData<'TDoc> - sqlProps - - /// Retrieve documents matching JSON field comparisons (->> =) - let ByFields<'TDoc>(tableName, howMatched, fields, sqlProps) = - Custom.List<'TDoc>( - Query.byFields (Query.find tableName) howMatched fields, - addFieldParams fields [], - fromData<'TDoc>, - sqlProps) - - /// Retrieve documents matching JSON field comparisons (->> =) ordered by the given fields in the document - [] - let byFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields sqlProps = - Custom.list<'TDoc> - (Query.byFields (Query.find tableName) howMatched queryFields + Query.orderBy orderFields PostgreSQL) - (addFieldParams queryFields []) - fromData<'TDoc> - sqlProps - - /// Retrieve documents matching JSON field comparisons (->> =) ordered by the given fields in the document - let ByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, sqlProps) = - Custom.List<'TDoc>( - Query.byFields (Query.find tableName) howMatched queryFields + Query.orderBy orderFields PostgreSQL, - addFieldParams queryFields [], - fromData<'TDoc>, - sqlProps) - - /// Retrieve documents matching a JSON containment query (@>) - [] - let byContains<'TDoc> tableName (criteria: obj) sqlProps = - Custom.list<'TDoc> - (Query.byContains (Query.find tableName)) [ jsonParam "@criteria" criteria ] fromData<'TDoc> sqlProps - - /// Retrieve documents matching a JSON containment query (@>) - let ByContains<'TDoc>(tableName, criteria: obj, sqlProps) = - Custom.List<'TDoc>( - Query.byContains (Query.find tableName), - [ jsonParam "@criteria" criteria ], - fromData<'TDoc>, - sqlProps) - - /// Retrieve documents matching a JSON containment query (@>) ordered by the given fields in the document - [] - let byContainsOrdered<'TDoc> tableName (criteria: obj) orderFields sqlProps = - Custom.list<'TDoc> - (Query.byContains (Query.find tableName) + Query.orderBy orderFields PostgreSQL) - [ jsonParam "@criteria" criteria ] - fromData<'TDoc> - sqlProps - - /// Retrieve documents matching a JSON containment query (@>) ordered by the given fields in the document - let ByContainsOrdered<'TDoc>(tableName, criteria: obj, orderFields, sqlProps) = - Custom.List<'TDoc>( - Query.byContains (Query.find tableName) + Query.orderBy orderFields PostgreSQL, - [ jsonParam "@criteria" criteria ], - fromData<'TDoc>, - sqlProps) - - /// Retrieve documents matching a JSON Path match query (@?) - [] - let byJsonPath<'TDoc> tableName jsonPath sqlProps = - Custom.list<'TDoc> - (Query.byPathMatch (Query.find tableName)) [ "@path", Sql.string jsonPath ] fromData<'TDoc> sqlProps - - /// Retrieve documents matching a JSON Path match query (@?) - let ByJsonPath<'TDoc>(tableName, jsonPath, sqlProps) = - Custom.List<'TDoc>( - Query.byPathMatch (Query.find tableName), - [ "@path", Sql.string jsonPath ], - fromData<'TDoc>, - sqlProps) - - /// Retrieve documents matching a JSON Path match query (@?) ordered by the given fields in the document - [] - let byJsonPathOrdered<'TDoc> tableName jsonPath orderFields sqlProps = - Custom.list<'TDoc> - (Query.byPathMatch (Query.find tableName) + Query.orderBy orderFields PostgreSQL) - [ "@path", Sql.string jsonPath ] - fromData<'TDoc> - sqlProps - - /// Retrieve documents matching a JSON Path match query (@?) ordered by the given fields in the document - let ByJsonPathOrdered<'TDoc>(tableName, jsonPath, orderFields, sqlProps) = - Custom.List<'TDoc>( - Query.byPathMatch (Query.find tableName) + Query.orderBy orderFields PostgreSQL, - [ "@path", Sql.string jsonPath ], - fromData<'TDoc>, - sqlProps) - - /// Retrieve the first document matching JSON field comparisons (->> =); returns None if not found - [] - let firstByFields<'TDoc> tableName howMatched fields sqlProps = - Custom.single<'TDoc> - $"{Query.byFields (Query.find tableName) howMatched fields} LIMIT 1" - (addFieldParams fields []) - fromData<'TDoc> - sqlProps - - /// Retrieve the first document matching JSON field comparisons (->> =); returns null if not found - let FirstByFields<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, howMatched, fields, sqlProps) = - Custom.Single<'TDoc>( - $"{Query.byFields (Query.find tableName) howMatched fields} LIMIT 1", - addFieldParams fields [], - fromData<'TDoc>, - sqlProps) - - /// Retrieve the first document matching JSON field comparisons (->> =) ordered by the given fields in the - /// document; returns None if not found - [] - let firstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields sqlProps = - Custom.single<'TDoc> - $"{Query.byFields (Query.find tableName) howMatched queryFields}{Query.orderBy orderFields PostgreSQL} LIMIT 1" - (addFieldParams queryFields []) - fromData<'TDoc> - sqlProps - - /// Retrieve the first document matching JSON field comparisons (->> =) ordered by the given fields in the - /// document; returns null if not found - let FirstByFieldsOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>( - tableName, howMatched, queryFields, orderFields, sqlProps) = - Custom.Single<'TDoc>( - $"{Query.byFields (Query.find tableName) howMatched queryFields}{Query.orderBy orderFields PostgreSQL} LIMIT 1", - addFieldParams queryFields [], - fromData<'TDoc>, - sqlProps) - - /// Retrieve the first document matching a JSON containment query (@>); returns None if not found - [] - let firstByContains<'TDoc> tableName (criteria: obj) sqlProps = - Custom.single<'TDoc> - $"{Query.byContains (Query.find tableName)} LIMIT 1" - [ jsonParam "@criteria" criteria ] - fromData<'TDoc> - sqlProps - - /// Retrieve the first document matching a JSON containment query (@>); returns null if not found - let FirstByContains<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, criteria: obj, sqlProps) = - Custom.Single<'TDoc>( - $"{Query.byContains (Query.find tableName)} LIMIT 1", - [ jsonParam "@criteria" criteria ], - fromData<'TDoc>, - sqlProps) - - /// Retrieve the first document matching a JSON containment query (@>) ordered by the given fields in the - /// document; returns None if not found - [] - let firstByContainsOrdered<'TDoc> tableName (criteria: obj) orderFields sqlProps = - Custom.single<'TDoc> - $"{Query.byContains (Query.find tableName)}{Query.orderBy orderFields PostgreSQL} LIMIT 1" - [ jsonParam "@criteria" criteria ] - fromData<'TDoc> - sqlProps - - /// Retrieve the first document matching a JSON containment query (@>) ordered by the given fields in the - /// document; returns null if not found - let FirstByContainsOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>( - tableName, criteria: obj, orderFields, sqlProps) = - Custom.Single<'TDoc>( - $"{Query.byContains (Query.find tableName)}{Query.orderBy orderFields PostgreSQL} LIMIT 1", - [ jsonParam "@criteria" criteria ], - fromData<'TDoc>, - sqlProps) - - /// Retrieve the first document matching a JSON Path match query (@?); returns None if not found - [] - let firstByJsonPath<'TDoc> tableName jsonPath sqlProps = - Custom.single<'TDoc> - $"{Query.byPathMatch (Query.find tableName)} LIMIT 1" - [ "@path", Sql.string jsonPath ] - fromData<'TDoc> - sqlProps - - /// Retrieve the first document matching a JSON Path match query (@?); returns null if not found - let FirstByJsonPath<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, jsonPath, sqlProps) = - Custom.Single<'TDoc>( - $"{Query.byPathMatch (Query.find tableName)} LIMIT 1", - [ "@path", Sql.string jsonPath ], - fromData<'TDoc>, - sqlProps) - - /// Retrieve the first document matching a JSON Path match query (@?) ordered by the given fields in the - /// document; returns None if not found - [] - let firstByJsonPathOrdered<'TDoc> tableName jsonPath orderFields sqlProps = - Custom.single<'TDoc> - $"{Query.byPathMatch (Query.find tableName)}{Query.orderBy orderFields PostgreSQL} LIMIT 1" - [ "@path", Sql.string jsonPath ] - fromData<'TDoc> - sqlProps - - /// Retrieve the first document matching a JSON Path match query (@?) ordered by the given fields in the - /// document; returns null if not found - let FirstByJsonPathOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>( - tableName, jsonPath, orderFields, sqlProps) = - Custom.Single<'TDoc>( - $"{Query.byPathMatch (Query.find tableName)}{Query.orderBy orderFields PostgreSQL} LIMIT 1", - [ "@path", Sql.string jsonPath ], - fromData<'TDoc>, - sqlProps) - - /// Commands to update documents - [] - module Update = - - /// Update an entire document by its ID - [] - let byId tableName (docId: 'TKey) (document: 'TDoc) sqlProps = - Custom.nonQuery - (Query.byId (Query.update tableName) docId) [ idParam docId; jsonParam "@data" document ] sqlProps - - /// 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) sqlProps = - byId tableName (idFunc document) document sqlProps - - /// 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, sqlProps) = - byFunc tableName idFunc.Invoke document sqlProps - - /// Commands to patch (partially update) documents - [] - module Patch = - - /// Patch a document by its ID - [] - let byId tableName (docId: 'TKey) (patch: 'TPatch) sqlProps = - Custom.nonQuery - (Query.byId (Query.patch tableName) docId) [ idParam docId; jsonParam "@data" patch ] sqlProps - - /// Patch documents using a JSON field comparison query in the WHERE clause (->> =) - [] - let byFields tableName howMatched fields (patch: 'TPatch) sqlProps = - Custom.nonQuery - (Query.byFields (Query.patch tableName) howMatched fields) - (addFieldParams fields [ jsonParam "@data" patch ]) - sqlProps - - /// Patch documents using a JSON containment query in the WHERE clause (@>) - [] - let byContains tableName (criteria: 'TContains) (patch: 'TPatch) sqlProps = - Custom.nonQuery - (Query.byContains (Query.patch tableName)) - [ jsonParam "@data" patch; jsonParam "@criteria" criteria ] - sqlProps - - /// Patch documents using a JSON Path match query in the WHERE clause (@?) - [] - let byJsonPath tableName jsonPath (patch: 'TPatch) sqlProps = - Custom.nonQuery - (Query.byPathMatch (Query.patch tableName)) - [ jsonParam "@data" patch; "@path", Sql.string jsonPath ] - sqlProps - - /// Commands to remove fields from documents - [] - module RemoveFields = - - /// Remove fields from a document by the document's ID - [] - let byId tableName (docId: 'TKey) fieldNames sqlProps = - Custom.nonQuery - (Query.byId (Query.removeFields tableName) docId) [ idParam docId; fieldNameParams fieldNames ] sqlProps - - /// Remove fields from documents via a comparison on JSON fields in the document - [] - let byFields tableName howMatched fields fieldNames sqlProps = - Custom.nonQuery - (Query.byFields (Query.removeFields tableName) howMatched fields) - (addFieldParams fields [ fieldNameParams fieldNames ]) - sqlProps - - /// Remove fields from documents via a JSON containment query (@>) - [] - let byContains tableName (criteria: 'TContains) fieldNames sqlProps = - Custom.nonQuery - (Query.byContains (Query.removeFields tableName)) - [ jsonParam "@criteria" criteria; fieldNameParams fieldNames ] - sqlProps - - /// Remove fields from documents via a JSON Path match query (@?) - [] - let byJsonPath tableName jsonPath fieldNames sqlProps = - Custom.nonQuery - (Query.byPathMatch (Query.removeFields tableName)) - [ "@path", Sql.string jsonPath; fieldNameParams fieldNames ] - sqlProps - - /// Commands to delete documents - [] - module Delete = - - /// Delete a document by its ID - [] - let byId tableName (docId: 'TKey) sqlProps = - Custom.nonQuery (Query.byId (Query.delete tableName) docId) [ idParam docId ] sqlProps - - /// Delete documents by matching a JSON field comparison query (->> =) - [] - let byFields tableName howMatched fields sqlProps = - Custom.nonQuery - (Query.byFields (Query.delete tableName) howMatched fields) (addFieldParams fields []) sqlProps - - /// Delete documents by matching a JSON contains query (@>) - [] - let byContains tableName (criteria: 'TCriteria) sqlProps = - Custom.nonQuery (Query.byContains (Query.delete tableName)) [ jsonParam "@criteria" criteria ] sqlProps - - /// Delete documents by matching a JSON Path match query (@?) - [] - let byJsonPath tableName path sqlProps = - Custom.nonQuery (Query.byPathMatch (Query.delete tableName)) [ "@path", Sql.string path ] sqlProps - - -/// Commands to execute custom SQL queries -[] -module Custom = - - /// Execute a query that returns a list of results - [] - let list<'TDoc> query parameters (mapFunc: RowReader -> 'TDoc) = - WithProps.Custom.list<'TDoc> query parameters mapFunc (fromDataSource ()) - - /// Execute a query that returns a list of results - let List<'TDoc>(query, parameters, mapFunc: System.Func) = - WithProps.Custom.List<'TDoc>(query, parameters, mapFunc, fromDataSource ()) - - /// Execute a query that returns one or no results; returns None if not found - [] - let single<'TDoc> query parameters (mapFunc: RowReader -> 'TDoc) = - WithProps.Custom.single<'TDoc> query parameters mapFunc (fromDataSource ()) - - /// Execute a query that returns one or no results; returns null if not found - let Single<'TDoc when 'TDoc: null and 'TDoc: not struct>( - query, parameters, mapFunc: System.Func) = - WithProps.Custom.Single<'TDoc>(query, parameters, mapFunc, fromDataSource ()) - - /// Execute a query that returns no results - [] - let nonQuery query parameters = - WithProps.Custom.nonQuery query parameters (fromDataSource ()) - - /// Execute a query that returns a scalar value - [] - let scalar<'T when 'T: struct> query parameters (mapFunc: RowReader -> 'T) = - WithProps.Custom.scalar query parameters mapFunc (fromDataSource ()) - - /// Execute a query that returns a scalar value - let Scalar<'T when 'T: struct>(query, parameters, mapFunc: System.Func) = - WithProps.Custom.Scalar<'T>(query, parameters, mapFunc, fromDataSource ()) - - -/// Table and index definition commands -[] -module Definition = - - /// Create a document table - [] - let ensureTable name = - WithProps.Definition.ensureTable name (fromDataSource ()) - - /// Create an index on documents in the specified table - [] - let ensureDocumentIndex name idxType = - WithProps.Definition.ensureDocumentIndex name idxType (fromDataSource ()) - - /// Create an index on field(s) within documents in the specified table - [] - let ensureFieldIndex tableName indexName fields = - WithProps.Definition.ensureFieldIndex tableName indexName fields (fromDataSource ()) - - -/// Document writing functions -[] -module Document = - - /// Insert a new document - [] - let insert<'TDoc> tableName (document: 'TDoc) = - WithProps.Document.insert<'TDoc> tableName document (fromDataSource ()) - - /// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert") - [] - let save<'TDoc> tableName (document: 'TDoc) = - WithProps.Document.save<'TDoc> tableName document (fromDataSource ()) - - -/// Queries to count documents -[] -module Count = - - /// Count all documents in a table - [] - let all tableName = - WithProps.Count.all tableName (fromDataSource ()) - - /// Count matching documents using a JSON field comparison query (->> =) - [] - let byFields tableName howMatched fields = - WithProps.Count.byFields tableName howMatched fields (fromDataSource ()) - - /// Count matching documents using a JSON containment query (@>) - [] - let byContains tableName criteria = - WithProps.Count.byContains tableName criteria (fromDataSource ()) - - /// Count matching documents using a JSON Path match query (@?) - [] - let byJsonPath tableName jsonPath = - WithProps.Count.byJsonPath tableName jsonPath (fromDataSource ()) - - -/// Queries to determine if documents exist -[] -module Exists = - - /// Determine if a document exists for the given ID - [] - let byId tableName docId = - WithProps.Exists.byId tableName docId (fromDataSource ()) - - /// Determine if documents exist using a JSON field comparison query (->> =) - [] - let byFields tableName howMatched fields = - WithProps.Exists.byFields tableName howMatched fields (fromDataSource ()) - - /// Determine if documents exist using a JSON containment query (@>) - [] - let byContains tableName criteria = - WithProps.Exists.byContains tableName criteria (fromDataSource ()) - - /// Determine if documents exist using a JSON Path match query (@?) - [] - let byJsonPath tableName jsonPath = - WithProps.Exists.byJsonPath tableName jsonPath (fromDataSource ()) - - -/// Commands to retrieve documents -[] -module Find = - - /// Retrieve all documents in the given table - [] - let all<'TDoc> tableName = - WithProps.Find.all<'TDoc> tableName (fromDataSource ()) - - /// Retrieve all documents in the given table - let All<'TDoc> tableName = - WithProps.Find.All<'TDoc>(tableName, fromDataSource ()) - - /// Retrieve all documents in the given table ordered by the given fields in the document - [] - let allOrdered<'TDoc> tableName orderFields = - WithProps.Find.allOrdered<'TDoc> tableName orderFields (fromDataSource ()) - - /// Retrieve all documents in the given table ordered by the given fields in the document - let AllOrdered<'TDoc> tableName orderFields = - WithProps.Find.AllOrdered<'TDoc>(tableName, orderFields, fromDataSource ()) - - /// Retrieve a document by its ID; returns None if not found - [] - let byId<'TKey, 'TDoc> tableName docId = - WithProps.Find.byId<'TKey, 'TDoc> tableName docId (fromDataSource ()) - - /// Retrieve a document by its ID; returns null if not found - let ById<'TKey, 'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, docId: 'TKey) = - WithProps.Find.ById<'TKey, 'TDoc>(tableName, docId, fromDataSource ()) - - /// Retrieve documents matching a JSON field comparison query (->> =) - [] - let byFields<'TDoc> tableName howMatched fields = - WithProps.Find.byFields<'TDoc> tableName howMatched fields (fromDataSource ()) - - /// Retrieve documents matching a JSON field comparison query (->> =) - let ByFields<'TDoc>(tableName, howMatched, fields) = - WithProps.Find.ByFields<'TDoc>(tableName, howMatched, fields, fromDataSource ()) - - /// Retrieve documents matching a JSON field comparison query (->> =) ordered by the given fields in the document - [] - let byFieldsOrdered<'TDoc> tableName howMatched queryField orderFields = - WithProps.Find.byFieldsOrdered<'TDoc> tableName howMatched queryField orderFields (fromDataSource ()) - - /// Retrieve documents matching a JSON field comparison query (->> =) ordered by the given fields in the document - let ByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields) = - WithProps.Find.ByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, fromDataSource ()) - - /// Retrieve documents matching a JSON containment query (@>) - [] - let byContains<'TDoc> tableName (criteria: obj) = - WithProps.Find.byContains<'TDoc> tableName criteria (fromDataSource ()) - - /// Retrieve documents matching a JSON containment query (@>) - let ByContains<'TDoc>(tableName, criteria: obj) = - WithProps.Find.ByContains<'TDoc>(tableName, criteria, fromDataSource ()) - - /// Retrieve documents matching a JSON containment query (@>) ordered by the given fields in the document - [] - let byContainsOrdered<'TDoc> tableName (criteria: obj) orderFields = - WithProps.Find.byContainsOrdered<'TDoc> tableName criteria orderFields (fromDataSource ()) - - /// Retrieve documents matching a JSON containment query (@>) ordered by the given fields in the document - let ByContainsOrdered<'TDoc>(tableName, criteria: obj, orderFields) = - WithProps.Find.ByContainsOrdered<'TDoc>(tableName, criteria, orderFields, fromDataSource ()) - - /// Retrieve documents matching a JSON Path match query (@?) - [] - let byJsonPath<'TDoc> tableName jsonPath = - WithProps.Find.byJsonPath<'TDoc> tableName jsonPath (fromDataSource ()) - - /// Retrieve documents matching a JSON Path match query (@?) - let ByJsonPath<'TDoc>(tableName, jsonPath) = - WithProps.Find.ByJsonPath<'TDoc>(tableName, jsonPath, fromDataSource ()) - - /// Retrieve documents matching a JSON Path match query (@?) ordered by the given fields in the document - [] - let byJsonPathOrdered<'TDoc> tableName jsonPath orderFields = - WithProps.Find.byJsonPathOrdered<'TDoc> tableName jsonPath orderFields (fromDataSource ()) - - /// Retrieve documents matching a JSON Path match query (@?) ordered by the given fields in the document - let ByJsonPathOrdered<'TDoc>(tableName, jsonPath, orderFields) = - WithProps.Find.ByJsonPathOrdered<'TDoc>(tableName, jsonPath, orderFields, fromDataSource ()) - - /// Retrieve the first document matching a JSON field comparison query (->> =); returns None if not found - [] - let firstByFields<'TDoc> tableName howMatched fields = - WithProps.Find.firstByFields<'TDoc> tableName howMatched fields (fromDataSource ()) - - /// Retrieve the first document matching a JSON field comparison query (->> =); returns null if not found - let FirstByFields<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, howMatched, fields) = - WithProps.Find.FirstByFields<'TDoc>(tableName, howMatched, fields, fromDataSource ()) - - /// Retrieve the first document matching a JSON field comparison query (->> =) ordered by the given fields in the - /// document; returns None if not found - [] - let firstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields = - WithProps.Find.firstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields (fromDataSource ()) - - /// Retrieve the first document matching a JSON field comparison query (->> =) ordered by the given fields in the - /// document; returns null if not found - let FirstByFieldsOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>( - tableName, howMatched, queryFields, orderFields) = - WithProps.Find.FirstByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, fromDataSource ()) - - /// Retrieve the first document matching a JSON containment query (@>); returns None if not found - [] - let firstByContains<'TDoc> tableName (criteria: obj) = - WithProps.Find.firstByContains<'TDoc> tableName criteria (fromDataSource ()) - - /// Retrieve the first document matching a JSON containment query (@>); returns null if not found - let FirstByContains<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, criteria: obj) = - WithProps.Find.FirstByContains<'TDoc>(tableName, criteria, fromDataSource ()) - - /// Retrieve the first document matching a JSON containment query (@>) ordered by the given fields in the document; - /// returns None if not found - [] - let firstByContainsOrdered<'TDoc> tableName (criteria: obj) orderFields = - WithProps.Find.firstByContainsOrdered<'TDoc> tableName criteria orderFields (fromDataSource ()) - - /// Retrieve the first document matching a JSON containment query (@>) ordered by the given fields in the document; - /// returns null if not found - let FirstByContainsOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, criteria: obj, orderFields) = - WithProps.Find.FirstByContainsOrdered<'TDoc>(tableName, criteria, orderFields, fromDataSource ()) - - /// Retrieve the first document matching a JSON Path match query (@?); returns None if not found - [] - let firstByJsonPath<'TDoc> tableName jsonPath = - WithProps.Find.firstByJsonPath<'TDoc> tableName jsonPath (fromDataSource ()) - - /// Retrieve the first document matching a JSON Path match query (@?); returns null if not found - let FirstByJsonPath<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, jsonPath) = - WithProps.Find.FirstByJsonPath<'TDoc>(tableName, jsonPath, fromDataSource ()) - - /// Retrieve the first document matching a JSON Path match query (@?) ordered by the given fields in the document; - /// returns None if not found - [] - let firstByJsonPathOrdered<'TDoc> tableName jsonPath orderFields = - WithProps.Find.firstByJsonPathOrdered<'TDoc> tableName jsonPath orderFields (fromDataSource ()) - - /// Retrieve the first document matching a JSON Path match query (@?) ordered by the given fields in the document; - /// returns null if not found - let FirstByJsonPathOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, jsonPath, orderFields) = - WithProps.Find.FirstByJsonPathOrdered<'TDoc>(tableName, jsonPath, orderFields, fromDataSource ()) - - -/// Commands to update documents -[] -module Update = - - /// Update an entire document by its ID - [] - let byId tableName (docId: 'TKey) (document: 'TDoc) = - WithProps.Update.byId tableName docId document (fromDataSource ()) - - /// 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) = - WithProps.Update.byFunc tableName idFunc document (fromDataSource ()) - - /// 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) = - WithProps.Update.ByFunc(tableName, idFunc, document, fromDataSource ()) - - -/// Commands to patch (partially update) documents -[] -module Patch = - - /// Patch a document by its ID - [] - let byId tableName (docId: 'TKey) (patch: 'TPatch) = - WithProps.Patch.byId tableName docId patch (fromDataSource ()) - - /// Patch documents using a JSON field comparison query in the WHERE clause (->> =) - [] - let byFields tableName howMatched fields (patch: 'TPatch) = - WithProps.Patch.byFields tableName howMatched fields patch (fromDataSource ()) - - /// Patch documents using a JSON containment query in the WHERE clause (@>) - [] - let byContains tableName (criteria: 'TCriteria) (patch: 'TPatch) = - WithProps.Patch.byContains tableName criteria patch (fromDataSource ()) - - /// Patch documents using a JSON Path match query in the WHERE clause (@?) - [] - let byJsonPath tableName jsonPath (patch: 'TPatch) = - WithProps.Patch.byJsonPath tableName jsonPath patch (fromDataSource ()) - - -/// Commands to remove fields from documents -[] -module RemoveFields = - - /// Remove fields from a document by the document's ID - [] - let byId tableName (docId: 'TKey) fieldNames = - WithProps.RemoveFields.byId tableName docId fieldNames (fromDataSource ()) - - /// Remove fields from documents via a comparison on JSON fields in the document - [] - let byFields tableName howMatched fields fieldNames = - WithProps.RemoveFields.byFields tableName howMatched fields fieldNames (fromDataSource ()) - - /// Remove fields from documents via a JSON containment query (@>) - [] - let byContains tableName (criteria: 'TContains) fieldNames = - WithProps.RemoveFields.byContains tableName criteria fieldNames (fromDataSource ()) - - /// Remove fields from documents via a JSON Path match query (@?) - [] - let byJsonPath tableName jsonPath fieldNames = - WithProps.RemoveFields.byJsonPath tableName jsonPath fieldNames (fromDataSource ()) - - -/// Commands to delete documents -[] -module Delete = - - /// Delete a document by its ID - [] - let byId tableName (docId: 'TKey) = - WithProps.Delete.byId tableName docId (fromDataSource ()) - - /// Delete documents by matching a JSON field comparison query (->> =) - [] - let byFields tableName howMatched fields = - WithProps.Delete.byFields tableName howMatched fields (fromDataSource ()) - - /// Delete documents by matching a JSON containment query (@>) - [] - let byContains tableName (criteria: 'TContains) = - WithProps.Delete.byContains tableName criteria (fromDataSource ()) - - /// Delete documents by matching a JSON Path match query (@?) - [] - let byJsonPath tableName path = - WithProps.Delete.byJsonPath tableName path (fromDataSource ()) diff --git a/src/Postgres/WithProps.fs b/src/Postgres/WithProps.fs new file mode 100644 index 0000000..da7bc86 --- /dev/null +++ b/src/Postgres/WithProps.fs @@ -0,0 +1,840 @@ +/// Versions of queries that accept SqlProps as the last parameter +module BitBadger.Documents.Postgres.WithProps + +open BitBadger.Documents +open Npgsql.FSharp + +/// Commands to execute custom SQL queries +[] +module Custom = + + module FSharpList = Microsoft.FSharp.Collections.List + + /// 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 SqlProps to use to execute the query + /// A list of results for the given query + [] + let list<'TDoc> query parameters (mapFunc: RowReader -> 'TDoc) sqlProps = + Sql.query query sqlProps + |> Sql.parameters (FSharpList.ofSeq parameters) + |> Sql.executeAsync 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 SqlProps to use to execute the query + /// A list of results for the given query + let List<'TDoc>(query, parameters, mapFunc: System.Func, sqlProps) = backgroundTask { + let! results = list<'TDoc> query parameters mapFunc.Invoke sqlProps + return ResizeArray 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 SqlProps to use to execute the query + /// Some with the first matching result, or None if not found + [] + let single<'TDoc> query parameters mapFunc sqlProps = backgroundTask { + let! results = list<'TDoc> query parameters mapFunc sqlProps + return FSharpList.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 SqlProps 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, sqlProps) = backgroundTask { + let! result = single<'TDoc> query parameters mapFunc.Invoke sqlProps + return Option.toObj result + } + + /// Execute a query that returns no results + /// The query to retrieve the results + /// Parameters to use for the query + /// The SqlProps to use to execute the query + [] + let nonQuery query parameters sqlProps = + Sql.query query sqlProps + |> Sql.parameters (FSharpList.ofSeq parameters) + |> Sql.executeNonQueryAsync + |> ignoreTask + + /// 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 SqlProps to use to execute the query + /// The scalar value for the query + [] + let scalar<'T when 'T: struct> query parameters (mapFunc: RowReader -> 'T) sqlProps = + Sql.query query sqlProps + |> Sql.parameters (FSharpList.ofSeq parameters) + |> Sql.executeRowAsync mapFunc + + /// 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 SqlProps to use to execute the query + /// The scalar value for the query + let Scalar<'T when 'T: struct>(query, parameters, mapFunc: System.Func, sqlProps) = + scalar<'T> query parameters mapFunc.Invoke sqlProps + +/// Table and index definition commands +module Definition = + + /// Create a document table + /// The table whose existence should be ensured (may include schema) + /// The SqlProps to use to execute the query + [] + let ensureTable name sqlProps = backgroundTask { + do! Custom.nonQuery (Query.Definition.ensureTable name) [] sqlProps + do! Custom.nonQuery (Query.Definition.ensureKey name PostgreSQL) [] sqlProps + } + + /// Create an index on documents in the specified table + /// The table to be indexed (may include schema) + /// The type of document index to create + /// The SqlProps to use to execute the query + [] + let ensureDocumentIndex name idxType sqlProps = + Custom.nonQuery (Query.Definition.ensureDocumentIndex name idxType) [] sqlProps + + /// 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 SqlProps to use to execute the query + [] + let ensureFieldIndex tableName indexName fields sqlProps = + Custom.nonQuery (Query.Definition.ensureIndexOn tableName indexName fields PostgreSQL) [] sqlProps + +/// 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 SqlProps to use to execute the query + [] + let insert<'TDoc> tableName (document: 'TDoc) sqlProps = + 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}')::numeric), 0) + 1 FROM {tableName}) || '" + | Guid -> $"\"{AutoId.GenerateGuid()}\"" + | RandomString -> $"\"{AutoId.GenerateRandomString(Configuration.idStringLength ())}\"" + | Disabled -> "@data" + |> function it -> $"""@data::jsonb || ('{{"{idField}":{it}}}')::jsonb""" + else "@data" + (Query.insert tableName).Replace("@data", dataParam) + Custom.nonQuery query [ jsonParam "@data" document ] sqlProps + + /// 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 SqlProps to use to execute the query + [] + let save<'TDoc> tableName (document: 'TDoc) sqlProps = + Custom.nonQuery (Query.save tableName) [ jsonParam "@data" document ] sqlProps + +/// Commands to count documents +[] +module Count = + + /// Count all documents in a table + /// The table in which documents should be counted (may include schema) + /// The SqlProps to use to execute the query + /// The count of the documents in the table + [] + let all tableName sqlProps = + Custom.scalar (Query.count tableName) [] toCount sqlProps + + /// 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 SqlProps to use to execute the query + /// The count of matching documents in the table + [] + let byFields tableName howMatched fields sqlProps = + Custom.scalar + (Query.byFields (Query.count tableName) howMatched fields) (addFieldParams fields []) toCount sqlProps + + /// Count matching documents using a JSON containment query (@>) + /// The table in which documents should be counted (may include schema) + /// The document to match with the containment query + /// The SqlProps to use to execute the query + /// The count of the documents in the table + [] + let byContains tableName (criteria: 'TContains) sqlProps = + Custom.scalar + (Query.byContains (Query.count tableName)) [ jsonParam "@criteria" criteria ] toCount sqlProps + + /// Count matching documents using a JSON Path match query (@?) + /// The table in which documents should be counted (may include schema) + /// The JSON Path expression to be matched + /// The SqlProps to use to execute the query + /// The count of the documents in the table + [] + let byJsonPath tableName jsonPath sqlProps = + Custom.scalar + (Query.byPathMatch (Query.count tableName)) [ "@path", Sql.string jsonPath ] toCount sqlProps + +/// 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 SqlProps to use to execute the query + /// True if a document exists, false if not + [] + let byId tableName (docId: 'TKey) sqlProps = + Custom.scalar (Query.exists tableName (Query.whereById docId)) [ idParam docId ] toExists sqlProps + + /// 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 SqlProps to use to execute the query + /// True if any matching documents exist, false if not + [] + let byFields tableName howMatched fields sqlProps = + Custom.scalar + (Query.exists tableName (Query.whereByFields howMatched fields)) + (addFieldParams fields []) + toExists + sqlProps + + /// Determine if a document exists using a JSON containment query (@>) + /// The table in which existence should be checked (may include schema) + /// The document to match with the containment query + /// The SqlProps to use to execute the query + /// True if any matching documents exist, false if not + [] + let byContains tableName (criteria: 'TContains) sqlProps = + Custom.scalar + (Query.exists tableName (Query.whereDataContains "@criteria")) + [ jsonParam "@criteria" criteria ] + toExists + sqlProps + + /// Determine if a document exists using a JSON Path match query (@?) + /// The table in which existence should be checked (may include schema) + /// The JSON Path expression to be matched + /// The SqlProps to use to execute the query + /// True if any matching documents exist, false if not + [] + let byJsonPath tableName jsonPath sqlProps = + Custom.scalar + (Query.exists tableName (Query.whereJsonPathMatches "@path")) + [ "@path", Sql.string jsonPath ] + toExists + sqlProps + +/// Commands to retrieve documents +[] +module Find = + + /// Retrieve all documents in the given table + /// The table from which documents should be retrieved (may include schema) + /// The SqlProps to use to execute the query + /// All documents from the given table + [] + let all<'TDoc> tableName sqlProps = + Custom.list<'TDoc> (Query.find tableName) [] fromData<'TDoc> sqlProps + + /// Retrieve all documents in the given table + /// The table from which documents should be retrieved (may include schema) + /// The SqlProps to use to execute the query + /// All documents from the given table + let All<'TDoc>(tableName, sqlProps) = + Custom.List<'TDoc>(Query.find tableName, [], fromData<'TDoc>, sqlProps) + + /// 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 SqlProps to use to execute the query + /// All documents from the given table, ordered by the given fields + [] + let allOrdered<'TDoc> tableName orderFields sqlProps = + Custom.list<'TDoc> (Query.find tableName + Query.orderBy orderFields PostgreSQL) [] fromData<'TDoc> sqlProps + + /// 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 SqlProps to use to execute the query + /// All documents from the given table, ordered by the given fields + let AllOrdered<'TDoc>(tableName, orderFields, sqlProps) = + Custom.List<'TDoc>( + Query.find tableName + Query.orderBy orderFields PostgreSQL, [], fromData<'TDoc>, sqlProps) + + /// 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 SqlProps to use to execute the query + /// Some with the document if found, None otherwise + [] + let byId<'TKey, 'TDoc> tableName (docId: 'TKey) sqlProps = + Custom.single (Query.byId (Query.find tableName) docId) [ idParam docId ] fromData<'TDoc> sqlProps + + /// 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 SqlProps 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, sqlProps) = + Custom.Single<'TDoc>( + Query.byId (Query.find tableName) docId, [ idParam docId ], fromData<'TDoc>, sqlProps) + + /// 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 SqlProps to use to execute the query + /// All documents matching the given fields + [] + let byFields<'TDoc> tableName howMatched fields sqlProps = + Custom.list<'TDoc> + (Query.byFields (Query.find tableName) howMatched fields) + (addFieldParams fields []) + fromData<'TDoc> + sqlProps + + /// 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 SqlProps to use to execute the query + /// All documents matching the given fields + let ByFields<'TDoc>(tableName, howMatched, fields, sqlProps) = + Custom.List<'TDoc>( + Query.byFields (Query.find tableName) howMatched fields, + addFieldParams fields [], + fromData<'TDoc>, + sqlProps) + + /// + /// 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 SqlProps 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 sqlProps = + Custom.list<'TDoc> + (Query.byFields (Query.find tableName) howMatched queryFields + Query.orderBy orderFields PostgreSQL) + (addFieldParams queryFields []) + fromData<'TDoc> + sqlProps + + /// + /// 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 SqlProps 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, sqlProps) = + Custom.List<'TDoc>( + Query.byFields (Query.find tableName) howMatched queryFields + Query.orderBy orderFields PostgreSQL, + addFieldParams queryFields [], + fromData<'TDoc>, + sqlProps) + + /// Retrieve documents matching a JSON containment query (@>) + /// The table from which documents should be retrieved (may include schema) + /// The document to match with the containment query + /// The SqlProps to use to execute the query + /// All documents matching the given containment query + [] + let byContains<'TDoc> tableName (criteria: obj) sqlProps = + Custom.list<'TDoc> + (Query.byContains (Query.find tableName)) [ jsonParam "@criteria" criteria ] fromData<'TDoc> sqlProps + + /// Retrieve documents matching a JSON containment query (@>) + /// The table from which documents should be retrieved (may include schema) + /// The document to match with the containment query + /// The SqlProps to use to execute the query + /// All documents matching the given containment query + let ByContains<'TDoc>(tableName, criteria: obj, sqlProps) = + Custom.List<'TDoc>( + Query.byContains (Query.find tableName), + [ jsonParam "@criteria" criteria ], + fromData<'TDoc>, + sqlProps) + + /// + /// Retrieve documents matching a JSON containment query (@>) ordered by the given fields in the + /// document + /// + /// 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 + /// The SqlProps to use to execute the query + /// All documents matching the given containment query, ordered by the given fields + [] + let byContainsOrdered<'TDoc> tableName (criteria: obj) orderFields sqlProps = + Custom.list<'TDoc> + (Query.byContains (Query.find tableName) + Query.orderBy orderFields PostgreSQL) + [ jsonParam "@criteria" criteria ] + fromData<'TDoc> + sqlProps + + /// + /// Retrieve documents matching a JSON containment query (@>) ordered by the given fields in the + /// document + /// + /// 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 + /// The SqlProps to use to execute the query + /// All documents matching the given containment query, ordered by the given fields + let ByContainsOrdered<'TDoc>(tableName, criteria: obj, orderFields, sqlProps) = + Custom.List<'TDoc>( + Query.byContains (Query.find tableName) + Query.orderBy orderFields PostgreSQL, + [ jsonParam "@criteria" criteria ], + fromData<'TDoc>, + sqlProps) + + /// Retrieve documents matching a JSON Path match query (@?) + /// The table from which documents should be retrieved (may include schema) + /// The JSON Path expression to match + /// The SqlProps to use to execute the query + /// All documents matching the given JSON Path expression + [] + let byJsonPath<'TDoc> tableName jsonPath sqlProps = + Custom.list<'TDoc> + (Query.byPathMatch (Query.find tableName)) [ "@path", Sql.string jsonPath ] fromData<'TDoc> sqlProps + + /// Retrieve documents matching a JSON Path match query (@?) + /// The table from which documents should be retrieved (may include schema) + /// The JSON Path expression to match + /// The SqlProps to use to execute the query + /// All documents matching the given JSON Path expression + let ByJsonPath<'TDoc>(tableName, jsonPath, sqlProps) = + Custom.List<'TDoc>( + Query.byPathMatch (Query.find tableName), + [ "@path", Sql.string jsonPath ], + fromData<'TDoc>, + sqlProps) + + /// + /// Retrieve documents matching a JSON Path match query (@?) ordered by the given fields in the document + /// + /// 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 + /// The SqlProps to use to execute the query + /// All documents matching the given JSON Path expression, ordered by the given fields + [] + let byJsonPathOrdered<'TDoc> tableName jsonPath orderFields sqlProps = + Custom.list<'TDoc> + (Query.byPathMatch (Query.find tableName) + Query.orderBy orderFields PostgreSQL) + [ "@path", Sql.string jsonPath ] + fromData<'TDoc> + sqlProps + + /// + /// Retrieve documents matching a JSON Path match query (@?) ordered by the given fields in the document + /// + /// 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 + /// The SqlProps to use to execute the query + /// All documents matching the given JSON Path expression, ordered by the given fields + let ByJsonPathOrdered<'TDoc>(tableName, jsonPath, orderFields, sqlProps) = + Custom.List<'TDoc>( + Query.byPathMatch (Query.find tableName) + Query.orderBy orderFields PostgreSQL, + [ "@path", Sql.string jsonPath ], + fromData<'TDoc>, + sqlProps) + + /// 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 SqlProps to use to execute the query + /// Some with the first document, or None if not found + [] + let firstByFields<'TDoc> tableName howMatched fields sqlProps = + Custom.single<'TDoc> + $"{Query.byFields (Query.find tableName) howMatched fields} LIMIT 1" + (addFieldParams fields []) + fromData<'TDoc> + sqlProps + + /// 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 SqlProps 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, sqlProps) = + Custom.Single<'TDoc>( + $"{Query.byFields (Query.find tableName) howMatched fields} LIMIT 1", + addFieldParams fields [], + fromData<'TDoc>, + sqlProps) + + /// + /// 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 SqlProps 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 sqlProps = + Custom.single<'TDoc> + $"{Query.byFields (Query.find tableName) howMatched queryFields}{Query.orderBy orderFields PostgreSQL} LIMIT 1" + (addFieldParams queryFields []) + fromData<'TDoc> + sqlProps + + /// + /// 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 SqlProps 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, sqlProps) = + Custom.Single<'TDoc>( + $"{Query.byFields (Query.find tableName) howMatched queryFields}{Query.orderBy orderFields PostgreSQL} LIMIT 1", + addFieldParams queryFields [], + fromData<'TDoc>, + sqlProps) + + /// Retrieve the first document matching a JSON containment query (@>) + /// The table from which a document should be retrieved (may include schema) + /// The document to match with the containment query + /// The SqlProps to use to execute the query + /// Some with the first document, or None if not found + [] + let firstByContains<'TDoc> tableName (criteria: obj) sqlProps = + Custom.single<'TDoc> + $"{Query.byContains (Query.find tableName)} LIMIT 1" + [ jsonParam "@criteria" criteria ] + fromData<'TDoc> + sqlProps + + /// Retrieve the first document matching a JSON containment query (@>) + /// The table from which a document should be retrieved (may include schema) + /// The document to match with the containment query + /// The SqlProps to use to execute the query + /// The first document, or null if not found + let FirstByContains<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, criteria: obj, sqlProps) = + Custom.Single<'TDoc>( + $"{Query.byContains (Query.find tableName)} LIMIT 1", + [ jsonParam "@criteria" criteria ], + fromData<'TDoc>, + sqlProps) + + /// + /// Retrieve the first document matching a JSON containment query (@>) ordered by the given fields in + /// the document + /// + /// 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 SqlProps to use to execute the query + /// + /// Some with the first document ordered by the given fields, or None if not found + /// + [] + let firstByContainsOrdered<'TDoc> tableName (criteria: obj) orderFields sqlProps = + Custom.single<'TDoc> + $"{Query.byContains (Query.find tableName)}{Query.orderBy orderFields PostgreSQL} LIMIT 1" + [ jsonParam "@criteria" criteria ] + fromData<'TDoc> + sqlProps + + /// + /// Retrieve the first document matching a JSON containment query (@>) ordered by the given fields in + /// the document + /// + /// 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 SqlProps to use to execute the query + /// The first document ordered by the given fields, or null if not found + let FirstByContainsOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>( + tableName, criteria: obj, orderFields, sqlProps) = + Custom.Single<'TDoc>( + $"{Query.byContains (Query.find tableName)}{Query.orderBy orderFields PostgreSQL} LIMIT 1", + [ jsonParam "@criteria" criteria ], + fromData<'TDoc>, + sqlProps) + + /// Retrieve the first document matching a JSON Path match query (@?) + /// The table from which a document should be retrieved (may include schema) + /// The JSON Path expression to match + /// The SqlProps to use to execute the query + /// Some with the first document, or None if not found + [] + let firstByJsonPath<'TDoc> tableName jsonPath sqlProps = + Custom.single<'TDoc> + $"{Query.byPathMatch (Query.find tableName)} LIMIT 1" + [ "@path", Sql.string jsonPath ] + fromData<'TDoc> + sqlProps + + /// Retrieve the first document matching a JSON Path match query (@?) + /// The table from which a document should be retrieved (may include schema) + /// The JSON Path expression to match + /// The SqlProps to use to execute the query + /// The first document, or null if not found + let FirstByJsonPath<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, jsonPath, sqlProps) = + Custom.Single<'TDoc>( + $"{Query.byPathMatch (Query.find tableName)} LIMIT 1", + [ "@path", Sql.string jsonPath ], + fromData<'TDoc>, + sqlProps) + + /// + /// Retrieve the first document matching a JSON Path match query (@?) ordered by the given fields in the + /// document + /// + /// 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 SqlProps to use to execute the query + /// + /// Some with the first document ordered by the given fields, or None if not found + /// + [] + let firstByJsonPathOrdered<'TDoc> tableName jsonPath orderFields sqlProps = + Custom.single<'TDoc> + $"{Query.byPathMatch (Query.find tableName)}{Query.orderBy orderFields PostgreSQL} LIMIT 1" + [ "@path", Sql.string jsonPath ] + fromData<'TDoc> + sqlProps + + /// + /// Retrieve the first document matching a JSON Path match query (@?) ordered by the given fields in the + /// document + /// + /// 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 SqlProps to use to execute the query + /// The first document ordered by the given fields, or null if not found + let FirstByJsonPathOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>( + tableName, jsonPath, orderFields, sqlProps) = + Custom.Single<'TDoc>( + $"{Query.byPathMatch (Query.find tableName)}{Query.orderBy orderFields PostgreSQL} LIMIT 1", + [ "@path", Sql.string jsonPath ], + fromData<'TDoc>, + sqlProps) + +/// 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 SqlProps to use to execute the query + [] + let byId tableName (docId: 'TKey) (document: 'TDoc) sqlProps = + Custom.nonQuery + (Query.byId (Query.update tableName) docId) [ idParam docId; jsonParam "@data" document ] sqlProps + + /// + /// 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 SqlProps to use to execute the query + [] + let byFunc tableName (idFunc: 'TDoc -> 'TKey) (document: 'TDoc) sqlProps = + byId tableName (idFunc document) document sqlProps + + /// + /// 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 SqlProps to use to execute the query + let ByFunc(tableName, idFunc: System.Func<'TDoc, 'TKey>, document: 'TDoc, sqlProps) = + byFunc tableName idFunc.Invoke document sqlProps + +/// 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 SqlProps to use to execute the query + [] + let byId tableName (docId: 'TKey) (patch: 'TPatch) sqlProps = + Custom.nonQuery + (Query.byId (Query.patch tableName) docId) [ idParam docId; jsonParam "@data" patch ] sqlProps + + /// + /// 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 SqlProps to use to execute the query + [] + let byFields tableName howMatched fields (patch: 'TPatch) sqlProps = + Custom.nonQuery + (Query.byFields (Query.patch tableName) howMatched fields) + (addFieldParams fields [ jsonParam "@data" patch ]) + sqlProps + + /// Patch documents using a JSON containment query in the WHERE clause (@>) + /// 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 + /// The SqlProps to use to execute the query + [] + let byContains tableName (criteria: 'TContains) (patch: 'TPatch) sqlProps = + Custom.nonQuery + (Query.byContains (Query.patch tableName)) + [ jsonParam "@data" patch; jsonParam "@criteria" criteria ] + sqlProps + + /// Patch documents using a JSON Path match query in the WHERE clause (@?) + /// 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 + /// The SqlProps to use to execute the query + [] + let byJsonPath tableName jsonPath (patch: 'TPatch) sqlProps = + Custom.nonQuery + (Query.byPathMatch (Query.patch tableName)) + [ jsonParam "@data" patch; "@path", Sql.string jsonPath ] + sqlProps + +/// 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 SqlProps to use to execute the query + [] + let byId tableName (docId: 'TKey) fieldNames sqlProps = + Custom.nonQuery + (Query.byId (Query.removeFields tableName) docId) [ idParam docId; fieldNameParams fieldNames ] sqlProps + + /// 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 SqlProps to use to execute the query + [] + let byFields tableName howMatched fields fieldNames sqlProps = + Custom.nonQuery + (Query.byFields (Query.removeFields tableName) howMatched fields) + (addFieldParams fields [ fieldNameParams fieldNames ]) + sqlProps + + /// Remove fields from documents via a JSON containment 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 + /// The SqlProps to use to execute the query + [] + let byContains tableName (criteria: 'TContains) fieldNames sqlProps = + Custom.nonQuery + (Query.byContains (Query.removeFields tableName)) + [ jsonParam "@criteria" criteria; fieldNameParams fieldNames ] + sqlProps + + /// Remove fields from documents via a JSON Path match 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 + /// The SqlProps to use to execute the query + [] + let byJsonPath tableName jsonPath fieldNames sqlProps = + Custom.nonQuery + (Query.byPathMatch (Query.removeFields tableName)) + [ "@path", Sql.string jsonPath; fieldNameParams fieldNames ] + sqlProps + +/// 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 SqlProps to use to execute the query + [] + let byId tableName (docId: 'TKey) sqlProps = + Custom.nonQuery (Query.byId (Query.delete tableName) docId) [ idParam docId ] sqlProps + + /// 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 SqlProps to use to execute the query + [] + let byFields tableName howMatched fields sqlProps = + Custom.nonQuery + (Query.byFields (Query.delete tableName) howMatched fields) (addFieldParams fields []) sqlProps + + /// Delete documents by matching a JSON contains query (@>) + /// The table in which documents should be deleted (may include schema) + /// The document to match the containment query + /// The SqlProps to use to execute the query + [] + let byContains tableName (criteria: 'TCriteria) sqlProps = + Custom.nonQuery (Query.byContains (Query.delete tableName)) [ jsonParam "@criteria" criteria ] sqlProps + + /// Delete documents by matching a JSON Path match query (@?) + /// The table in which documents should be deleted (may include schema) + /// The JSON Path expression to match + /// The SqlProps to use to execute the query + [] + let byJsonPath tableName jsonPath sqlProps = + Custom.nonQuery (Query.byPathMatch (Query.delete tableName)) [ "@path", Sql.string jsonPath ] sqlProps diff --git a/src/Sqlite/BitBadger.Documents.Sqlite.fsproj b/src/Sqlite/BitBadger.Documents.Sqlite.fsproj index 5865390..e19b49d 100644 --- a/src/Sqlite/BitBadger.Documents.Sqlite.fsproj +++ b/src/Sqlite/BitBadger.Documents.Sqlite.fsproj @@ -3,6 +3,7 @@ Use SQLite as a document database JSON Document SQLite + bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml diff --git a/src/Sqlite/Extensions.fs b/src/Sqlite/Extensions.fs index 9f143dc..901bc0a 100644 --- a/src/Sqlite/Extensions.fs +++ b/src/Sqlite/Extensions.fs @@ -2,263 +2,487 @@ namespace BitBadger.Documents.Sqlite open Microsoft.Data.Sqlite -/// F# extensions for the SqliteConnection type +/// F# extensions for the SqliteConnection type [] module Extensions = type SqliteConnection with - - /// 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 member conn.customList<'TDoc> query parameters mapFunc = WithConn.Custom.list<'TDoc> query parameters mapFunc conn - /// Execute a query that returns one or no 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 + /// Some with the first matching result, or None if not found member conn.customSingle<'TDoc> query parameters mapFunc = 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 member conn.customNonQuery query parameters = 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 member conn.customScalar<'T when 'T: struct> query parameters mapFunc = WithConn.Custom.scalar<'T> query parameters mapFunc conn - /// Create a document table + /// Create a document table + /// The table whose existence should be ensured (may include schema) member conn.ensureTable name = WithConn.Definition.ensureTable name 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 member conn.ensureFieldIndex tableName indexName fields = WithConn.Definition.ensureFieldIndex tableName indexName fields conn - - /// 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 member conn.insert<'TDoc> tableName (document: 'TDoc) = WithConn.Document.insert<'TDoc> tableName document conn + /// /// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert") + /// + /// The table into which the document should be saved (may include schema) + /// The document to be saved member conn.save<'TDoc> tableName (document: 'TDoc) = WithConn.Document.save tableName document conn - /// Count all documents in a table + /// Count all documents in a table + /// The table in which documents should be counted (may include schema) + /// The count of the documents in the table member conn.countAll tableName = WithConn.Count.all tableName 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 count of matching documents in the table member conn.countByFields tableName howMatched fields = WithConn.Count.byFields tableName howMatched fields conn - - /// 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 + /// True if a document exists, false if not member conn.existsById tableName (docId: 'TKey) = WithConn.Exists.byId tableName docId conn - /// Determine if a document exists using a comparison on JSON fields + /// Determine if a document exists using JSON field comparisons (->> =, etc.) + /// The table in which existence should be checked (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// True if any matching documents exist, false if not member conn.existsByFields tableName howMatched fields = WithConn.Exists.byFields tableName howMatched fields 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) + /// All documents from the given table member conn.findAll<'TDoc> tableName = WithConn.Find.all<'TDoc> tableName 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 + /// All documents from the given table, ordered by the given fields member conn.findAllOrdered<'TDoc> tableName orderFields = WithConn.Find.allOrdered<'TDoc> tableName orderFields conn - /// Retrieve a document by its ID + /// Retrieve a document by its ID + /// The table from which a document should be retrieved (may include schema) + /// The ID of the document to retrieve + /// Some with the document if found, None otherwise member conn.findById<'TKey, 'TDoc> tableName (docId: 'TKey) = WithConn.Find.byId<'TKey, 'TDoc> tableName docId 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 + /// All documents matching the given fields member conn.findByFields<'TDoc> tableName howMatched fields = WithConn.Find.byFields<'TDoc> tableName howMatched fields conn - - /// Retrieve documents via a comparison on JSON fields ordered by the given fields in the document + + /// + /// Retrieve documents matching JSON field comparisons (->> =, etc.) ordered by the given fields + /// in the document + /// + /// The table from which documents should be retrieved (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// Fields by which the results should be ordered + /// All documents matching the given fields, ordered by the other given fields member conn.findByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields = WithConn.Find.byFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields 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 + /// Some with the first document, or None if not found member conn.findFirstByFields<'TDoc> tableName howMatched fields = WithConn.Find.firstByFields<'TDoc> tableName howMatched fields 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 + /// + /// Some with the first document ordered by the given fields, or None if not found + /// member conn.findFirstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields = WithConn.Find.firstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields conn - - /// 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 member conn.updateById tableName (docId: 'TKey) (document: 'TDoc) = 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 + + /// + /// 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 member conn.updateByFunc tableName (idFunc: 'TDoc -> 'TKey) (document: 'TDoc) = WithConn.Update.byFunc tableName idFunc document conn - - /// 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 member conn.patchById tableName (docId: 'TKey) (patch: 'TPatch) = WithConn.Patch.byId tableName docId 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 member conn.patchByFields tableName howMatched fields (patch: 'TPatch) = WithConn.Patch.byFields tableName howMatched fields patch conn - - /// 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 member conn.removeFieldsById tableName (docId: 'TKey) fieldNames = WithConn.RemoveFields.byId tableName docId fieldNames conn - - /// Remove a field from a document 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 member conn.removeFieldsByFields tableName howMatched fields fieldNames = WithConn.RemoveFields.byFields tableName howMatched fields fieldNames conn - - /// 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 member conn.deleteById tableName (docId: 'TKey) = WithConn.Delete.byId tableName 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 member conn.deleteByFields tableName howMatched fields = WithConn.Delete.byFields tableName howMatched fields conn open System.Runtime.CompilerServices -/// C# extensions on the SqliteConnection type +/// C# extensions on the SqliteConnection type type SqliteConnectionCSharpExtensions = - - /// Execute a query that returns a list of results + + /// Execute a query that returns a list of results + /// The SqliteConnection 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) = WithConn.Custom.List<'TDoc>(query, parameters, mapFunc, conn) - /// Execute a query that returns one or no results + /// Execute a query that returns one or no results + /// The SqliteConnection 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) = 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 SqliteConnection 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) = WithConn.Custom.nonQuery query parameters conn - /// Execute a query that returns a scalar value + /// Execute a query that returns a scalar value + /// The SqliteConnection 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) = WithConn.Custom.Scalar<'T>(query, parameters, mapFunc, conn) - /// Create a document table + /// Create a document table + /// The SqliteConnection on which to run the query + /// The table whose existence should be ensured (may include schema) [] static member inline EnsureTable(conn, name) = WithConn.Definition.ensureTable name conn - /// Create an index on one or more fields in a document table + /// Create an index on field(s) within documents in the specified table + /// The SqliteConnection 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) = WithConn.Definition.ensureFieldIndex tableName indexName fields conn - /// Insert a new document + /// Insert a new document + /// The SqliteConnection 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) = WithConn.Document.insert<'TDoc> tableName document 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 SqliteConnection 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) = WithConn.Document.save<'TDoc> tableName document conn - /// Count all documents in a table + /// Count all documents in a table + /// The SqliteConnection 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) = WithConn.Count.all tableName conn - - /// Count matching documents using a comparison on JSON fields + + /// Count matching documents using JSON field comparisons (->> =, etc.) + /// The SqliteConnection 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) = WithConn.Count.byFields tableName howMatched fields conn - - /// Determine if a document exists for the given ID + + /// Determine if a document exists for the given ID + /// The SqliteConnection 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<'TKey>(conn, tableName, docId: 'TKey) = WithConn.Exists.byId tableName docId conn - /// Determine if a document exists using a comparison on JSON fields + /// Determine if a document exists using JSON field comparisons (->> =, etc.) + /// The SqliteConnection 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) = WithConn.Exists.byFields tableName howMatched fields conn - - /// Retrieve all documents in the given table + + /// Retrieve all documents in the given table + /// The SqliteConnection 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) = WithConn.Find.All<'TDoc>(tableName, 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 SqliteConnection 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) = WithConn.Find.AllOrdered<'TDoc>(tableName, orderFields, conn) - /// Retrieve a document by its ID + /// Retrieve a document by its ID + /// The SqliteConnection 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) = WithConn.Find.ById<'TKey, 'TDoc>(tableName, docId, conn) - /// Retrieve documents via a comparison on JSON fields + /// Retrieve documents matching JSON field comparisons (->> =, etc.) + /// The SqliteConnection 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) = WithConn.Find.ByFields<'TDoc>(tableName, howMatched, fields, conn) - - /// Retrieve documents via a comparison on JSON fields ordered by the given fields in the document + + /// + /// Retrieve documents matching JSON field comparisons (->> =, etc.) ordered by the given fields in + /// the document + /// + /// The SqliteConnection 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) = WithConn.Find.ByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, conn) - - /// Retrieve documents via a comparison on JSON fields, returning only the first result + + /// Retrieve the first document matching JSON field comparisons (->> =, etc.) + /// The SqliteConnection 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) = WithConn.Find.FirstByFields<'TDoc>(tableName, howMatched, fields, 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 SqliteConnection 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) = WithConn.Find.FirstByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, conn) - - /// Update an entire document by its ID + + /// Update (replace) an entire document by its ID + /// The SqliteConnection 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<'TKey, 'TDoc>(conn, tableName, docId: 'TKey, document: 'TDoc) = 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 + + /// + /// Update (replace) an entire document by its ID, using the provided function to obtain the ID from the document + /// + /// The SqliteConnection 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<'TKey, 'TDoc>(conn, tableName, idFunc: System.Func<'TDoc, 'TKey>, doc: 'TDoc) = - WithConn.Update.ByFunc(tableName, idFunc, doc, conn) - - /// Patch a document by its ID + static member inline UpdateByFunc<'TKey, 'TDoc>( + conn, tableName, idFunc: System.Func<'TDoc, 'TKey>, document: 'TDoc) = + WithConn.Update.ByFunc(tableName, idFunc, document, conn) + + /// Patch a document by its ID + /// The SqliteConnection 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<'TKey, 'TPatch>(conn, tableName, docId: 'TKey, patch: 'TPatch) = WithConn.Patch.byId tableName docId patch conn - - /// Patch documents using a comparison on JSON fields + + /// + /// Patch documents using a JSON field comparison query in the WHERE clause (->> =, etc.) + /// + /// The SqliteConnection 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<'TPatch>(conn, tableName, howMatched, fields, patch: 'TPatch) = WithConn.Patch.byFields tableName howMatched fields patch conn - - /// Remove fields from a document by the document's ID + + /// Remove fields from a document by the document's ID + /// The SqliteConnection 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<'TKey>(conn, tableName, docId: 'TKey, fieldNames) = WithConn.RemoveFields.byId tableName docId fieldNames 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 SqliteConnection 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) = WithConn.RemoveFields.byFields tableName howMatched fields fieldNames conn - - /// Delete a document by its ID + + /// Delete a document by its ID + /// The SqliteConnection 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<'TKey>(conn, tableName, docId: 'TKey) = WithConn.Delete.byId tableName docId conn - - /// Delete documents by matching a comparison on JSON fields + + /// Delete documents by matching a JSON field comparison query (->> =, etc.) + /// The SqliteConnection 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) = WithConn.Delete.byFields tableName howMatched fields conn diff --git a/src/Sqlite/Library.fs b/src/Sqlite/Library.fs index 520f465..215a62b 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,287 +744,439 @@ 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) -/// 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) [] let ensureTable name = use conn = Configuration.dbConn () WithConn.Definition.ensureTable name 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 [] let ensureFieldIndex tableName indexName fields = use conn = Configuration.dbConn () WithConn.Definition.ensureFieldIndex tableName indexName fields conn -/// Document insert/save functions +/// Document insert/save functions [] 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 [] let insert<'TDoc> tableName (document: 'TDoc) = use conn = Configuration.dbConn () WithConn.Document.insert tableName document conn - /// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert") + /// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert") + /// The table into which the document should be saved (may include schema) + /// The document to be saved [] let save<'TDoc> tableName (document: 'TDoc) = use conn = Configuration.dbConn () WithConn.Document.save tableName document conn -/// Commands to count documents +/// 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 count of the documents in the table [] let all tableName = use conn = Configuration.dbConn () WithConn.Count.all tableName 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 count of matching documents in the table [] let byFields tableName howMatched fields = use conn = Configuration.dbConn () WithConn.Count.byFields tableName howMatched fields conn -/// Commands to determine if documents exist +/// 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 + /// True if a document exists, false if not [] let byId tableName (docId: 'TKey) = use conn = Configuration.dbConn () WithConn.Exists.byId tableName docId conn - - /// Determine if a document exists using a comparison on JSON fields + + /// Determine if a document exists using JSON field comparisons (->> =, etc.) + /// The table in which existence should be checked (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// True if any matching documents exist, false if not [] let byFields tableName howMatched fields = use conn = Configuration.dbConn () WithConn.Exists.byFields tableName howMatched fields conn -/// Commands to determine if documents exist +/// 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) + /// All documents from the given table [] let all<'TDoc> tableName = use conn = Configuration.dbConn () WithConn.Find.all<'TDoc> tableName conn - /// Retrieve all documents in the given table + /// Retrieve all documents in the given table + /// The table from which documents should be retrieved (may include schema) + /// All documents from the given table let All<'TDoc> tableName = use conn = Configuration.dbConn () WithConn.Find.All<'TDoc>(tableName, conn) - /// Retrieve all documents in the given table ordered by the given fields in the document + /// Retrieve all documents in the given table ordered by the given fields in the document + /// The table from which documents should be retrieved (may include schema) + /// Fields by which the results should be ordered + /// All documents from the given table, ordered by the given fields [] let allOrdered<'TDoc> tableName orderFields = use conn = Configuration.dbConn () WithConn.Find.allOrdered<'TDoc> tableName orderFields conn - /// Retrieve all documents in the given table ordered by the given fields in the document + /// Retrieve all documents in the given table ordered by the given fields in the document + /// The table from which documents should be retrieved (may include schema) + /// Fields by which the results should be ordered + /// All documents from the given table, ordered by the given fields let AllOrdered<'TDoc> tableName orderFields = use conn = Configuration.dbConn () WithConn.Find.AllOrdered<'TDoc>(tableName, orderFields, conn) - /// Retrieve a document by its ID (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 + /// Some with the document if found, None otherwise [] let byId<'TKey, 'TDoc> tableName docId = use conn = Configuration.dbConn () WithConn.Find.byId<'TKey, 'TDoc> tableName docId conn - /// Retrieve a document by its ID (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 document if found, null otherwise let ById<'TKey, 'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, docId) = use conn = Configuration.dbConn () WithConn.Find.ById<'TKey, 'TDoc>(tableName, docId, conn) - /// Retrieve documents 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 + /// All documents matching the given fields [] let byFields<'TDoc> tableName howMatched fields = use conn = Configuration.dbConn () WithConn.Find.byFields<'TDoc> tableName howMatched fields conn - - /// Retrieve documents 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 + /// All documents matching the given fields let ByFields<'TDoc>(tableName, howMatched, fields) = use conn = Configuration.dbConn () WithConn.Find.ByFields<'TDoc>(tableName, howMatched, fields, conn) - - /// Retrieve documents via a comparison on JSON fields ordered by the given fields in the document + + /// + /// Retrieve documents matching JSON field comparisons (->> =, etc.) ordered by the given fields in + /// the document + /// + /// The table from which documents should be retrieved (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// Fields by which the results should be ordered + /// All documents matching the given fields, ordered by the other given fields [] let byFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields = use conn = Configuration.dbConn () WithConn.Find.byFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields conn - - /// Retrieve documents via a comparison on JSON fields ordered by the given fields in the document + + /// + /// Retrieve documents matching JSON field comparisons (->> =, etc.) ordered by the given fields in + /// the document + /// + /// The table from which documents should be retrieved (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// Fields by which the results should be ordered + /// All documents matching the given fields, ordered by the other given fields let ByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields) = use conn = Configuration.dbConn () WithConn.Find.ByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, conn) - - /// Retrieve documents 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 + /// Some with the first document, or None if not found [] let firstByFields<'TDoc> tableName howMatched fields = use conn = Configuration.dbConn () WithConn.Find.firstByFields<'TDoc> tableName howMatched fields conn - - /// Retrieve 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 first document, or null if not found let FirstByFields<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, howMatched, fields) = use conn = Configuration.dbConn () WithConn.Find.FirstByFields<'TDoc>(tableName, howMatched, fields, conn) - /// Retrieve 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 + /// + /// Some with the first document ordered by the given fields, or None if not found + /// [] let firstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields = use conn = Configuration.dbConn () WithConn.Find.firstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields conn - - /// Retrieve 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 first document ordered by the given fields, or null if not found let FirstByFieldsOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>( tableName, howMatched, queryFields, orderFields) = use conn = Configuration.dbConn () WithConn.Find.FirstByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, conn) -/// Commands to update documents +/// 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 [] 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 + + /// + /// Update (replace) an entire document by its ID, using the provided function to obtain the ID from the document + /// + /// The table in which a document should be updated (may include schema) + /// The function to obtain the ID of the document + /// The new document [] let byFunc tableName (idFunc: 'TDoc -> 'TKey) (document: 'TDoc) = use conn = Configuration.dbConn () WithConn.Update.byFunc tableName idFunc document conn - - /// Update 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 let ByFunc(tableName, idFunc: System.Func<'TDoc, 'TKey>, document: 'TDoc) = use conn = Configuration.dbConn () WithConn.Update.ByFunc(tableName, idFunc, document, conn) -/// Commands to patch (partially update) documents +/// 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 [] 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 + + /// + /// Patch documents using a JSON field comparison query in the WHERE clause (->> =, etc.) + /// + /// The table in which documents should be patched (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// The partial document to patch the existing document [] let byFields tableName howMatched fields (patch: 'TPatch) = use conn = Configuration.dbConn () WithConn.Patch.byFields tableName howMatched fields patch conn -/// Commands to remove fields from documents +/// 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 [] 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 + + /// Remove fields from documents via a comparison on JSON fields in the document + /// The table in which documents should be modified (may include schema) + /// Whether to match any or all of the field conditions + /// The field conditions to match + /// One or more field names to remove from the matching documents [] let byFields tableName howMatched fields fieldNames = use conn = Configuration.dbConn () WithConn.RemoveFields.byFields tableName howMatched fields fieldNames conn -/// Commands to delete documents +/// 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 [] let byId tableName (docId: 'TKey) = use conn = Configuration.dbConn () WithConn.Delete.byId tableName 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 [] let byFields tableName howMatched fields = use conn = Configuration.dbConn () 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" ] }