Compare commits
15 Commits
v4-rc4
...
0c308c5f33
| Author | SHA1 | Date | |
|---|---|---|---|
| 0c308c5f33 | |||
| d9d37f110d | |||
| e0fb793ec9 | |||
| 74e5b77edb | |||
| 98bc83ac17 | |||
| 35755df99a | |||
| b1c3991e11 | |||
| 85750e19f2 | |||
| d8f64417e5 | |||
| 202fca272e | |||
| 8a15bdce2e | |||
| d131eda56e | |||
| e2232e91bb | |||
| e96c449324 | |||
| 433302d995 |
@@ -35,6 +35,12 @@ type Op =
|
||||
| NEX -> "IS NULL"
|
||||
|
||||
|
||||
/// The dialect in which a command should be rendered
|
||||
[<Struct>]
|
||||
type Dialect =
|
||||
| PostgreSQL
|
||||
| SQLite
|
||||
|
||||
/// Criteria for a field WHERE clause
|
||||
type Field = {
|
||||
/// The name of the field
|
||||
@@ -45,43 +51,98 @@ type Field = {
|
||||
|
||||
/// The value of the field
|
||||
Value: obj
|
||||
|
||||
/// The name of the parameter for this field
|
||||
ParameterName: string option
|
||||
|
||||
/// The table qualifier for this field
|
||||
Qualifier: string option
|
||||
} with
|
||||
|
||||
/// Create an equals (=) field criterion
|
||||
static member EQ name (value: obj) =
|
||||
{ Name = name; Op = EQ; Value = value }
|
||||
{ Name = name; Op = EQ; Value = value; ParameterName = None; Qualifier = None }
|
||||
|
||||
/// Create a greater than (>) field criterion
|
||||
static member GT name (value: obj) =
|
||||
{ Name = name; Op = GT; Value = value }
|
||||
{ Name = name; Op = GT; Value = value; ParameterName = None; Qualifier = None }
|
||||
|
||||
/// Create a greater than or equal to (>=) field criterion
|
||||
static member GE name (value: obj) =
|
||||
{ Name = name; Op = GE; Value = value }
|
||||
{ Name = name; Op = GE; Value = value; ParameterName = None; Qualifier = None }
|
||||
|
||||
/// Create a less than (<) field criterion
|
||||
static member LT name (value: obj) =
|
||||
{ Name = name; Op = LT; Value = value }
|
||||
{ Name = name; Op = LT; Value = value; ParameterName = None; Qualifier = None }
|
||||
|
||||
/// Create a less than or equal to (<=) field criterion
|
||||
static member LE name (value: obj) =
|
||||
{ Name = name; Op = LE; Value = value }
|
||||
{ Name = name; Op = LE; Value = value; ParameterName = None; Qualifier = None }
|
||||
|
||||
/// Create a not equals (<>) field criterion
|
||||
static member NE name (value: obj) =
|
||||
{ Name = name; Op = NE; Value = value }
|
||||
{ Name = name; Op = NE; Value = value; ParameterName = None; Qualifier = None }
|
||||
|
||||
/// Create a BETWEEN field criterion
|
||||
static member BT name (min: obj) (max: obj) =
|
||||
{ Name = name; Op = BT; Value = [ min; max ] }
|
||||
{ Name = name; Op = BT; Value = [ min; max ]; ParameterName = None; Qualifier = None }
|
||||
|
||||
/// Create an exists (IS NOT NULL) field criterion
|
||||
static member EX name =
|
||||
{ Name = name; Op = EX; Value = obj () }
|
||||
{ Name = name; Op = EX; Value = obj (); ParameterName = None; Qualifier = None }
|
||||
|
||||
/// Create a not exists (IS NULL) field criterion
|
||||
static member NEX name =
|
||||
{ Name = name; Op = NEX; Value = obj () }
|
||||
{ Name = name; Op = NEX; Value = obj (); ParameterName = None; Qualifier = None }
|
||||
|
||||
/// Transform a field name (a.b.c) to a path for the given SQL dialect
|
||||
static member NameToPath (name: string) dialect =
|
||||
let path =
|
||||
if name.Contains '.' then
|
||||
match dialect with
|
||||
| PostgreSQL -> "#>>'{" + String.concat "," (name.Split '.') + "}'"
|
||||
| SQLite -> "->>'" + String.concat "'->>'" (name.Split '.') + "'"
|
||||
else $"->>'{name}'"
|
||||
$"data{path}"
|
||||
|
||||
/// Specify the name of the parameter for this field
|
||||
member this.WithParameterName name =
|
||||
{ this with ParameterName = Some name }
|
||||
|
||||
/// Specify a qualifier (alias) for the table from which this field will be referenced
|
||||
member this.WithQualifier alias =
|
||||
{ this with Qualifier = Some alias }
|
||||
|
||||
/// Get the qualified path to the field
|
||||
member this.Path dialect =
|
||||
(this.Qualifier |> Option.map (fun q -> $"{q}.") |> Option.defaultValue "") + Field.NameToPath this.Name dialect
|
||||
|
||||
|
||||
/// How fields should be matched
|
||||
[<Struct>]
|
||||
type FieldMatch =
|
||||
/// Any field matches (OR)
|
||||
| Any
|
||||
/// All fields match (AND)
|
||||
| All
|
||||
|
||||
/// 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)
|
||||
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
|
||||
member this.Derive paramName =
|
||||
match paramName with
|
||||
| Some it -> it
|
||||
| None ->
|
||||
currentIdx <- currentIdx + 1
|
||||
$"@field{currentIdx}"
|
||||
|
||||
|
||||
/// The required document serialization implementation
|
||||
@@ -152,10 +213,10 @@ module Configuration =
|
||||
[<RequireQualifiedAccess>]
|
||||
module Query =
|
||||
|
||||
/// Create a SELECT clause to retrieve the document data from the given table
|
||||
[<CompiledName "SelectFromTable">]
|
||||
let selectFromTable tableName =
|
||||
$"SELECT data FROM %s{tableName}"
|
||||
/// Combine a query (select, update, etc.) and a WHERE clause
|
||||
[<CompiledName "StatementWhere">]
|
||||
let statementWhere statement where =
|
||||
$"%s{statement} WHERE %s{where}"
|
||||
|
||||
/// Queries to define tables and indexes
|
||||
module Definition =
|
||||
@@ -172,7 +233,7 @@ module Query =
|
||||
|
||||
/// SQL statement to create an index on one or more fields in a JSON document
|
||||
[<CompiledName "EnsureIndexOn">]
|
||||
let ensureIndexOn tableName indexName (fields: string seq) =
|
||||
let ensureIndexOn tableName indexName (fields: string seq) dialect =
|
||||
let _, tbl = splitSchemaAndTable tableName
|
||||
let jsonFields =
|
||||
fields
|
||||
@@ -180,14 +241,14 @@ module Query =
|
||||
let parts = it.Split ' '
|
||||
let fieldName = if Array.length parts = 1 then it else parts[0]
|
||||
let direction = if Array.length parts < 2 then "" else $" {parts[1]}"
|
||||
$"(data ->> '{fieldName}'){direction}")
|
||||
$"({Field.NameToPath fieldName dialect}){direction}")
|
||||
|> 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
|
||||
[<CompiledName "EnsureKey">]
|
||||
let ensureKey tableName =
|
||||
(ensureIndexOn tableName "key" [ Configuration.idField () ]).Replace("INDEX", "UNIQUE INDEX")
|
||||
let ensureKey tableName dialect =
|
||||
(ensureIndexOn tableName "key" [ Configuration.idField () ] dialect).Replace("INDEX", "UNIQUE INDEX")
|
||||
|
||||
/// Query to insert a document
|
||||
[<CompiledName "Insert">]
|
||||
@@ -201,3 +262,33 @@ module Query =
|
||||
"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)
|
||||
[<CompiledName "Count">]
|
||||
let count tableName =
|
||||
$"SELECT COUNT(*) AS it FROM %s{tableName}"
|
||||
|
||||
/// Query to check for document existence in a table
|
||||
[<CompiledName "Exists">]
|
||||
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)
|
||||
[<CompiledName "Find">]
|
||||
let find tableName =
|
||||
$"SELECT data FROM %s{tableName}"
|
||||
|
||||
/// Query to update a document (no WHERE clause)
|
||||
[<CompiledName "Update">]
|
||||
let update tableName =
|
||||
$"UPDATE %s{tableName} SET data = @data"
|
||||
|
||||
/// Query to delete documents from a table (no WHERE clause)
|
||||
[<CompiledName "Delete">]
|
||||
let delete tableName =
|
||||
$"DELETE FROM %s{tableName}"
|
||||
|
||||
/// Create a SELECT clause to retrieve the document data from the given table
|
||||
[<CompiledName "SelectFromTable">]
|
||||
[<System.Obsolete "Use Find instead">]
|
||||
let selectFromTable tableName =
|
||||
find tableName
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
namespace BitBadger.Documents.Postgres
|
||||
|
||||
open BitBadger.Documents
|
||||
open Npgsql
|
||||
open Npgsql.FSharp
|
||||
|
||||
@@ -50,8 +51,13 @@ module Extensions =
|
||||
WithProps.Count.all tableName (Sql.existingConnection conn)
|
||||
|
||||
/// Count matching documents using a JSON field comparison query (->> =)
|
||||
member conn.countByFields tableName howMatched fields =
|
||||
WithProps.Count.byFields tableName howMatched fields (Sql.existingConnection conn)
|
||||
|
||||
/// Count matching documents using a JSON field comparison query (->> =)
|
||||
[<System.Obsolete "Use countByFields instead; will be removed in v4">]
|
||||
member conn.countByField tableName field =
|
||||
WithProps.Count.byField tableName field (Sql.existingConnection conn)
|
||||
conn.countByFields tableName Any [ field ]
|
||||
|
||||
/// Count matching documents using a JSON containment query (@>)
|
||||
member conn.countByContains tableName criteria =
|
||||
@@ -66,8 +72,13 @@ module Extensions =
|
||||
WithProps.Exists.byId tableName docId (Sql.existingConnection conn)
|
||||
|
||||
/// Determine if documents exist using a JSON field comparison query (->> =)
|
||||
member conn.existsByFields tableName howMatched fields =
|
||||
WithProps.Exists.byFields tableName howMatched fields (Sql.existingConnection conn)
|
||||
|
||||
/// Determine if documents exist using a JSON field comparison query (->> =)
|
||||
[<System.Obsolete "Use existsByFields instead; will be removed in v4">]
|
||||
member conn.existsByField tableName field =
|
||||
WithProps.Exists.byField tableName field (Sql.existingConnection conn)
|
||||
conn.existsByFields tableName Any [ field ]
|
||||
|
||||
/// Determine if documents exist using a JSON containment query (@>)
|
||||
member conn.existsByContains tableName criteria =
|
||||
@@ -86,8 +97,13 @@ module Extensions =
|
||||
WithProps.Find.byId<'TKey, 'TDoc> tableName docId (Sql.existingConnection conn)
|
||||
|
||||
/// Retrieve documents matching a JSON field comparison query (->> =)
|
||||
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 (->> =)
|
||||
[<System.Obsolete "Use findByFields instead; will be removed in v4">]
|
||||
member conn.findByField<'TDoc> tableName field =
|
||||
WithProps.Find.byField<'TDoc> tableName field (Sql.existingConnection conn)
|
||||
conn.findByFields<'TDoc> tableName Any [ field ]
|
||||
|
||||
/// Retrieve documents matching a JSON containment query (@>)
|
||||
member conn.findByContains<'TDoc> tableName (criteria: obj) =
|
||||
@@ -98,8 +114,13 @@ module Extensions =
|
||||
WithProps.Find.byJsonPath<'TDoc> tableName jsonPath (Sql.existingConnection conn)
|
||||
|
||||
/// Retrieve the first document matching a JSON field comparison query (->> =); returns 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 (->> =); returns None if not found
|
||||
[<System.Obsolete "Use findFirstByFields instead; will be removed in v4">]
|
||||
member conn.findFirstByField<'TDoc> tableName field =
|
||||
WithProps.Find.firstByField<'TDoc> tableName field (Sql.existingConnection conn)
|
||||
conn.findFirstByFields<'TDoc> tableName Any [ field ]
|
||||
|
||||
/// Retrieve the first document matching a JSON containment query (@>); returns None if not found
|
||||
member conn.findFirstByContains<'TDoc> tableName (criteria: obj) =
|
||||
@@ -122,8 +143,13 @@ module Extensions =
|
||||
WithProps.Patch.byId tableName docId patch (Sql.existingConnection conn)
|
||||
|
||||
/// Patch documents using a JSON field comparison query in the WHERE clause (->> =)
|
||||
member conn.patchByFields tableName howMatched fields (patch: 'TPatch) =
|
||||
WithProps.Patch.byFields tableName howMatched fields patch (Sql.existingConnection conn)
|
||||
|
||||
/// Patch documents using a JSON field comparison query in the WHERE clause (->> =)
|
||||
[<System.Obsolete "Use patchByFields instead; will be removed in v4">]
|
||||
member conn.patchByField tableName field (patch: 'TPatch) =
|
||||
WithProps.Patch.byField tableName field patch (Sql.existingConnection conn)
|
||||
conn.patchByFields tableName Any [ field ] patch
|
||||
|
||||
/// Patch documents using a JSON containment query in the WHERE clause (@>)
|
||||
member conn.patchByContains tableName (criteria: 'TCriteria) (patch: 'TPatch) =
|
||||
@@ -137,9 +163,14 @@ module Extensions =
|
||||
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
|
||||
member conn.removeFieldsByFields tableName howMatched fields fieldNames =
|
||||
WithProps.RemoveFields.byFields tableName howMatched fields fieldNames (Sql.existingConnection conn)
|
||||
|
||||
/// Remove fields from documents via a comparison on a JSON field in the document
|
||||
[<System.Obsolete "Use removeFieldsByFields instead; will be removed in v4">]
|
||||
member conn.removeFieldsByField tableName field fieldNames =
|
||||
WithProps.RemoveFields.byField tableName field fieldNames (Sql.existingConnection conn)
|
||||
conn.removeFieldsByFields tableName Any [ field ] fieldNames
|
||||
|
||||
/// Remove fields from documents via a JSON containment query (@>)
|
||||
member conn.removeFieldsByContains tableName (criteria: 'TContains) fieldNames =
|
||||
@@ -153,9 +184,13 @@ module Extensions =
|
||||
member conn.deleteById tableName (docId: 'TKey) =
|
||||
WithProps.Delete.byId tableName docId (Sql.existingConnection conn)
|
||||
|
||||
member conn.deleteByFields tableName howMatched fields =
|
||||
WithProps.Delete.byFields tableName howMatched fields (Sql.existingConnection conn)
|
||||
|
||||
/// Delete documents by matching a JSON field comparison query (->> =)
|
||||
[<System.Obsolete "Use deleteByFields instead; will be removed in v4">]
|
||||
member conn.deleteByField tableName field =
|
||||
WithProps.Delete.byField tableName field (Sql.existingConnection conn)
|
||||
conn.deleteByFields tableName Any [ field ]
|
||||
|
||||
/// Delete documents by matching a JSON containment query (@>)
|
||||
member conn.deleteByContains tableName (criteria: 'TContains) =
|
||||
@@ -225,8 +260,14 @@ type NpgsqlConnectionCSharpExtensions =
|
||||
|
||||
/// Count matching documents using a JSON field comparison query (->> =)
|
||||
[<Extension>]
|
||||
static member inline CountByFields(conn, tableName, howMatched, fields) =
|
||||
WithProps.Count.byFields tableName howMatched fields (Sql.existingConnection conn)
|
||||
|
||||
/// Count matching documents using a JSON field comparison query (->> =)
|
||||
[<Extension>]
|
||||
[<System.Obsolete "Use CountByFields instead; will be removed in v4">]
|
||||
static member inline CountByField(conn, tableName, field) =
|
||||
WithProps.Count.byField tableName field (Sql.existingConnection conn)
|
||||
conn.CountByFields(tableName, Any, [ field ])
|
||||
|
||||
/// Count matching documents using a JSON containment query (@>)
|
||||
[<Extension>]
|
||||
@@ -245,8 +286,14 @@ type NpgsqlConnectionCSharpExtensions =
|
||||
|
||||
/// Determine if documents exist using a JSON field comparison query (->> =)
|
||||
[<Extension>]
|
||||
static member inline ExistsByFields(conn, tableName, howMatched, fields) =
|
||||
WithProps.Exists.byFields tableName howMatched fields (Sql.existingConnection conn)
|
||||
|
||||
/// Determine if documents exist using a JSON field comparison query (->> =)
|
||||
[<Extension>]
|
||||
[<System.Obsolete "Use ExistsByFields instead; will be removed in v4">]
|
||||
static member inline ExistsByField(conn, tableName, field) =
|
||||
WithProps.Exists.byField tableName field (Sql.existingConnection conn)
|
||||
conn.ExistsByFields(tableName, Any, [ field ])
|
||||
|
||||
/// Determine if documents exist using a JSON containment query (@>)
|
||||
[<Extension>]
|
||||
@@ -270,8 +317,14 @@ type NpgsqlConnectionCSharpExtensions =
|
||||
|
||||
/// Retrieve documents matching a JSON field comparison query (->> =)
|
||||
[<Extension>]
|
||||
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 (->> =)
|
||||
[<Extension>]
|
||||
[<System.Obsolete "Use FindByFields instead; will be removed in v4">]
|
||||
static member inline FindByField<'TDoc>(conn, tableName, field) =
|
||||
WithProps.Find.ByField<'TDoc>(tableName, field, Sql.existingConnection conn)
|
||||
conn.FindByFields<'TDoc>(tableName, Any, [ field ])
|
||||
|
||||
/// Retrieve documents matching a JSON containment query (@>)
|
||||
[<Extension>]
|
||||
@@ -283,10 +336,16 @@ type NpgsqlConnectionCSharpExtensions =
|
||||
static member inline FindByJsonPath<'TDoc>(conn, tableName, jsonPath) =
|
||||
WithProps.Find.ByJsonPath<'TDoc>(tableName, jsonPath, Sql.existingConnection conn)
|
||||
|
||||
/// Retrieve the first document matching a JSON field comparison query (->> =); returns None if not found
|
||||
/// Retrieve the first document matching a JSON field comparison query (->> =); returns null if not found
|
||||
[<Extension>]
|
||||
static member inline FindFirstByFields<'TDoc when 'TDoc: null>(conn, tableName, howMatched, fields) =
|
||||
WithProps.Find.FirstByFields<'TDoc>(tableName, howMatched, fields, Sql.existingConnection conn)
|
||||
|
||||
/// Retrieve the first document matching a JSON field comparison query (->> =); returns null if not found
|
||||
[<Extension>]
|
||||
[<System.Obsolete "Use FindFirstByFields instead; will be removed in v4">]
|
||||
static member inline FindFirstByField<'TDoc when 'TDoc: null>(conn, tableName, field) =
|
||||
WithProps.Find.FirstByField<'TDoc>(tableName, field, Sql.existingConnection conn)
|
||||
conn.FindFirstByFields<'TDoc>(tableName, Any, [ field ])
|
||||
|
||||
/// Retrieve the first document matching a JSON containment query (@>); returns None if not found
|
||||
[<Extension>]
|
||||
@@ -315,8 +374,14 @@ type NpgsqlConnectionCSharpExtensions =
|
||||
|
||||
/// Patch documents using a JSON field comparison query in the WHERE clause (->> =)
|
||||
[<Extension>]
|
||||
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 field comparison query in the WHERE clause (->> =)
|
||||
[<Extension>]
|
||||
[<System.Obsolete "Use PatchByFields instead; will be removed in v4">]
|
||||
static member inline PatchByField(conn, tableName, field, patch: 'TPatch) =
|
||||
WithProps.Patch.byField tableName field patch (Sql.existingConnection conn)
|
||||
conn.PatchByFields(tableName, Any, [ field ], patch)
|
||||
|
||||
/// Patch documents using a JSON containment query in the WHERE clause (@>)
|
||||
[<Extension>]
|
||||
@@ -331,22 +396,28 @@ type NpgsqlConnectionCSharpExtensions =
|
||||
/// Remove fields from a document by the document's ID
|
||||
[<Extension>]
|
||||
static member inline RemoveFieldsById(conn, tableName, docId: 'TKey, fieldNames) =
|
||||
WithProps.RemoveFields.ById(tableName, docId, fieldNames, Sql.existingConnection conn)
|
||||
WithProps.RemoveFields.byId tableName docId fieldNames (Sql.existingConnection conn)
|
||||
|
||||
/// Remove fields from documents via a comparison on JSON fields in the document
|
||||
[<Extension>]
|
||||
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 comparison on a JSON field in the document
|
||||
[<Extension>]
|
||||
[<System.Obsolete "Use RemoveFieldsByFields instead; will be removed in v4">]
|
||||
static member inline RemoveFieldsByField(conn, tableName, field, fieldNames) =
|
||||
WithProps.RemoveFields.ByField(tableName, field, fieldNames, Sql.existingConnection conn)
|
||||
conn.RemoveFieldsByFields(tableName, Any, [ field ], fieldNames)
|
||||
|
||||
/// Remove fields from documents via a JSON containment query (@>)
|
||||
[<Extension>]
|
||||
static member inline RemoveFieldsByContains(conn, tableName, criteria: 'TContains, fieldNames) =
|
||||
WithProps.RemoveFields.ByContains(tableName, criteria, fieldNames, Sql.existingConnection conn)
|
||||
WithProps.RemoveFields.byContains tableName criteria fieldNames (Sql.existingConnection conn)
|
||||
|
||||
/// Remove fields from documents via a JSON Path match query (@?)
|
||||
[<Extension>]
|
||||
static member inline RemoveFieldsByJsonPath(conn, tableName, jsonPath, fieldNames) =
|
||||
WithProps.RemoveFields.ByJsonPath(tableName, jsonPath, fieldNames, Sql.existingConnection conn)
|
||||
WithProps.RemoveFields.byJsonPath tableName jsonPath fieldNames (Sql.existingConnection conn)
|
||||
|
||||
/// Delete a document by its ID
|
||||
[<Extension>]
|
||||
@@ -355,8 +426,14 @@ type NpgsqlConnectionCSharpExtensions =
|
||||
|
||||
/// Delete documents by matching a JSON field comparison query (->> =)
|
||||
[<Extension>]
|
||||
static member inline DeleteByFields(conn, tableName, howMatched, fields) =
|
||||
WithProps.Delete.byFields tableName howMatched fields (Sql.existingConnection conn)
|
||||
|
||||
/// Delete documents by matching a JSON field comparison query (->> =)
|
||||
[<Extension>]
|
||||
[<System.Obsolete "Use DeleteByFields instead; will be removed in v4">]
|
||||
static member inline DeleteByField(conn, tableName, field) =
|
||||
WithProps.Delete.byField tableName field (Sql.existingConnection conn)
|
||||
conn.DeleteByFields(tableName, Any, [ field ])
|
||||
|
||||
/// Delete documents by matching a JSON containment query (@>)
|
||||
[<Extension>]
|
||||
|
||||
@@ -46,6 +46,23 @@ module private Helpers =
|
||||
()
|
||||
}
|
||||
|
||||
/// 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
|
||||
| :? int8 as it -> Sql.int8 it
|
||||
| :? uint8 as it -> Sql.int8 (int8 it)
|
||||
| :? int16 as it -> Sql.int16 it
|
||||
| :? uint16 as it -> Sql.int16 (int16 it)
|
||||
| :? int as it -> Sql.int it
|
||||
| :? uint32 as it -> Sql.int (int it)
|
||||
| :? int64 as it -> Sql.int64 it
|
||||
| :? uint64 as it -> Sql.int64 (int64 it)
|
||||
| :? decimal as it -> Sql.decimal it
|
||||
| :? single as it -> Sql.double (double it)
|
||||
| :? double as it -> Sql.double it
|
||||
| :? string as it -> Sql.string it
|
||||
| _ -> catchAllFunc value
|
||||
|
||||
|
||||
open BitBadger.Documents
|
||||
|
||||
@@ -53,52 +70,58 @@ open BitBadger.Documents
|
||||
[<AutoOpen>]
|
||||
module Parameters =
|
||||
|
||||
/// Create an ID parameter (name "@id", key will be treated as a string)
|
||||
/// Create an ID parameter (name "@id")
|
||||
[<CompiledName "Id">]
|
||||
let idParam (key: 'TKey) =
|
||||
"@id", Sql.string (string key)
|
||||
"@id", parameterFor key (fun it -> Sql.string (string it))
|
||||
|
||||
/// Create a parameter with a JSON value
|
||||
[<CompiledName "Json">]
|
||||
let jsonParam (name: string) (it: 'TJson) =
|
||||
name, Sql.jsonb (Configuration.serializer().Serialize it)
|
||||
|
||||
/// Create a JSON field parameter
|
||||
[<CompiledName "FSharpAddField">]
|
||||
let addFieldParam name field parameters =
|
||||
match field.Op with
|
||||
| EX | NEX -> parameters
|
||||
/// Create JSON field parameters
|
||||
[<CompiledName "AddFields">]
|
||||
let addFieldParams fields parameters =
|
||||
let name = ParameterName()
|
||||
fields
|
||||
|> Seq.map (fun it ->
|
||||
seq {
|
||||
match it.Op with
|
||||
| EX | NEX -> ()
|
||||
| BT ->
|
||||
let values = field.Value :?> obj list
|
||||
List.concat
|
||||
[ parameters
|
||||
[ ($"{name}min", Sql.parameter (NpgsqlParameter($"{name}min", List.head values)))
|
||||
($"{name}max", Sql.parameter (NpgsqlParameter($"{name}max", List.last values))) ] ]
|
||||
| _ -> (name, Sql.parameter (NpgsqlParameter(name, field.Value))) :: parameters
|
||||
|
||||
/// Create a JSON field parameter
|
||||
let AddField name field parameters =
|
||||
match field.Op with
|
||||
| EX | NEX -> parameters
|
||||
| BT ->
|
||||
let values = field.Value :?> obj list
|
||||
ResizeArray
|
||||
[ ($"{name}min", Sql.parameter (NpgsqlParameter($"{name}min", List.head values)))
|
||||
($"{name}max", Sql.parameter (NpgsqlParameter($"{name}max", List.last values))) ]
|
||||
let p = name.Derive it.ParameterName
|
||||
let values = it.Value :?> obj list
|
||||
yield ($"{p}min",
|
||||
parameterFor (List.head values) (fun v -> Sql.parameter (NpgsqlParameter($"{p}min", v))))
|
||||
yield ($"{p}max",
|
||||
parameterFor (List.last values) (fun v -> Sql.parameter (NpgsqlParameter($"{p}max", v))))
|
||||
| _ ->
|
||||
let p = name.Derive it.ParameterName
|
||||
yield (p, parameterFor it.Value (fun v -> Sql.parameter (NpgsqlParameter(p, v)))) })
|
||||
|> Seq.collect id
|
||||
|> Seq.append parameters
|
||||
| _ -> (name, Sql.parameter (NpgsqlParameter(name, field.Value))) |> Seq.singleton |> Seq.append parameters
|
||||
|> Seq.toList
|
||||
|> Seq.ofList
|
||||
|
||||
/// Create a JSON field parameter
|
||||
[<CompiledName "AddField">]
|
||||
[<System.Obsolete "Use addFieldParams (F#) / AddFields (C#) instead; will be removed in v4">]
|
||||
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
|
||||
[<CompiledName "FSharpFieldName">]
|
||||
let fieldNameParam (fieldNames: string list) =
|
||||
if fieldNames.Length = 1 then "@name", Sql.string fieldNames[0]
|
||||
else "@name", Sql.stringArray (Array.ofList fieldNames)
|
||||
|
||||
/// Append JSON field name parameters for the given field names to the given parameters
|
||||
let FieldName(fieldNames: string seq) =
|
||||
if Seq.isEmpty fieldNames then "@name", Sql.string (Seq.head fieldNames)
|
||||
[<CompiledName "FieldNames">]
|
||||
let fieldNameParams (fieldNames: string seq) =
|
||||
if Seq.length fieldNames = 1 then "@name", Sql.string (Seq.head fieldNames)
|
||||
else "@name", Sql.stringArray (Array.ofSeq fieldNames)
|
||||
|
||||
/// Append JSON field name parameters for the given field names to the given parameters
|
||||
[<CompiledName "FieldName">]
|
||||
[<System.Obsolete "Use fieldNameParams (F#) / FieldNames (C#) instead; will be removed in v4">]
|
||||
let fieldNameParam fieldNames =
|
||||
fieldNameParams fieldNames
|
||||
|
||||
/// An empty parameter sequence
|
||||
[<CompiledName "None">]
|
||||
let noParams =
|
||||
@@ -109,24 +132,34 @@ module Parameters =
|
||||
[<RequireQualifiedAccess>]
|
||||
module Query =
|
||||
|
||||
/// Create a WHERE clause fragment to implement a comparison on a field in a JSON document
|
||||
[<CompiledName "WhereByField">]
|
||||
let whereByField field paramName =
|
||||
match field.Op with
|
||||
| EX | NEX -> $"data->>'{field.Name}' {field.Op}"
|
||||
| BT ->
|
||||
let names = $"{paramName}min AND {paramName}max"
|
||||
let values = field.Value :?> obj list
|
||||
match values[0] with
|
||||
/// Create a WHERE clause fragment to implement a comparison on fields in a JSON document
|
||||
[<CompiledName "WhereByFields">]
|
||||
let whereByFields (howMatched: FieldMatch) fields =
|
||||
let name = ParameterName()
|
||||
let isNumeric (it: obj) =
|
||||
match it with
|
||||
| :? int8 | :? uint8 | :? int16 | :? uint16 | :? int | :? uint32 | :? int64 | :? uint64
|
||||
| :? decimal | :? single | :? double -> $"(data->>'{field.Name}')::numeric {field.Op} {names}"
|
||||
| _ -> $"data->>'{field.Name}' {field.Op} {names}"
|
||||
| _ -> $"data->>'{field.Name}' {field.Op} %s{paramName}"
|
||||
| :? decimal | :? single | :? double -> true
|
||||
| _ -> false
|
||||
fields
|
||||
|> Seq.map (fun it ->
|
||||
match it.Op with
|
||||
| EX | NEX -> $"{it.Path PostgreSQL} {it.Op}"
|
||||
| _ ->
|
||||
let p = name.Derive it.ParameterName
|
||||
let param, value =
|
||||
match it.Op with
|
||||
| BT -> $"{p}min AND {p}max", (it.Value :?> obj list)[0]
|
||||
| _ -> p, it.Value
|
||||
if isNumeric value then
|
||||
$"({it.Path PostgreSQL})::numeric {it.Op} {param}"
|
||||
else $"{it.Path PostgreSQL} {it.Op} {param}")
|
||||
|> String.concat $" {howMatched} "
|
||||
|
||||
/// Create a WHERE clause fragment to implement an ID-based query
|
||||
[<CompiledName "WhereById">]
|
||||
let whereById paramName =
|
||||
whereByField (Field.EQ (Configuration.idField ()) 0) paramName
|
||||
let whereById<'TKey> (docId: 'TKey) =
|
||||
whereByFields Any [ { Field.EQ (Configuration.idField ()) docId with ParameterName = Some "@id" } ]
|
||||
|
||||
/// Table and index definition queries
|
||||
module Definition =
|
||||
@@ -143,11 +176,6 @@ module Query =
|
||||
let tableName = name.Split '.' |> Array.last
|
||||
$"CREATE INDEX IF NOT EXISTS idx_{tableName}_document ON {name} USING GIN (data{extraOps})"
|
||||
|
||||
/// Query to update a document
|
||||
[<CompiledName "Update">]
|
||||
let update tableName =
|
||||
$"""UPDATE %s{tableName} SET data = @data WHERE {whereById "@id"}"""
|
||||
|
||||
/// Create a WHERE clause fragment to implement a @> (JSON contains) condition
|
||||
[<CompiledName "WhereDataContains">]
|
||||
let whereDataContains paramName =
|
||||
@@ -158,151 +186,35 @@ module Query =
|
||||
let whereJsonPathMatches paramName =
|
||||
$"data @? %s{paramName}::jsonpath"
|
||||
|
||||
/// Queries for counting documents
|
||||
module Count =
|
||||
|
||||
/// Query to count all documents in a table
|
||||
[<CompiledName "All">]
|
||||
let all tableName =
|
||||
$"SELECT COUNT(*) AS it FROM %s{tableName}"
|
||||
|
||||
/// Query to count matching documents using a text comparison on a JSON field
|
||||
[<CompiledName "ByField">]
|
||||
let byField tableName field =
|
||||
$"""SELECT COUNT(*) AS it FROM %s{tableName} WHERE {whereByField field "@field"}"""
|
||||
|
||||
/// Query to count matching documents using a JSON containment query (@>)
|
||||
[<CompiledName "ByContains">]
|
||||
let byContains tableName =
|
||||
$"""SELECT COUNT(*) AS it FROM %s{tableName} WHERE {whereDataContains "@criteria"}"""
|
||||
|
||||
/// Query to count matching documents using a JSON Path match (@?)
|
||||
[<CompiledName "ByJsonPath">]
|
||||
let byJsonPath tableName =
|
||||
$"""SELECT COUNT(*) AS it FROM %s{tableName} WHERE {whereJsonPathMatches "@path"}"""
|
||||
|
||||
/// Queries for determining document existence
|
||||
module Exists =
|
||||
|
||||
/// Query to determine if a document exists for the given ID
|
||||
[<CompiledName "ById">]
|
||||
let byId tableName =
|
||||
$"""SELECT EXISTS (SELECT 1 FROM %s{tableName} WHERE {whereById "@id"}) AS it"""
|
||||
|
||||
/// Query to determine if documents exist using a comparison on a JSON field
|
||||
[<CompiledName "ByField">]
|
||||
let byField tableName field =
|
||||
$"""SELECT EXISTS (SELECT 1 FROM %s{tableName} WHERE {whereByField field "@field"}) AS it"""
|
||||
|
||||
/// Query to determine if documents exist using a JSON containment query (@>)
|
||||
[<CompiledName "ByContains">]
|
||||
let byContains tableName =
|
||||
$"""SELECT EXISTS (SELECT 1 FROM %s{tableName} WHERE {whereDataContains "@criteria"}) AS it"""
|
||||
|
||||
/// Query to determine if documents exist using a JSON Path match (@?)
|
||||
[<CompiledName "ByJsonPath">]
|
||||
let byJsonPath tableName =
|
||||
$"""SELECT EXISTS (SELECT 1 FROM %s{tableName} WHERE {whereJsonPathMatches "@path"}) AS it"""
|
||||
|
||||
/// Queries for retrieving documents
|
||||
module Find =
|
||||
|
||||
/// Query to retrieve a document by its ID
|
||||
[<CompiledName "ById">]
|
||||
let byId tableName =
|
||||
$"""{Query.selectFromTable tableName} WHERE {whereById "@id"}"""
|
||||
|
||||
/// Query to retrieve documents using a comparison on a JSON field
|
||||
[<CompiledName "ByField">]
|
||||
let byField tableName field =
|
||||
$"""{Query.selectFromTable tableName} WHERE {whereByField field "@field"}"""
|
||||
|
||||
/// Query to retrieve documents using a JSON containment query (@>)
|
||||
[<CompiledName "ByContains">]
|
||||
let byContains tableName =
|
||||
$"""{Query.selectFromTable tableName} WHERE {whereDataContains "@criteria"}"""
|
||||
|
||||
/// Query to retrieve documents using a JSON Path match (@?)
|
||||
[<CompiledName "ByJsonPath">]
|
||||
let byJsonPath tableName =
|
||||
$"""{Query.selectFromTable tableName} WHERE {whereJsonPathMatches "@path"}"""
|
||||
|
||||
/// Queries to patch (partially update) documents
|
||||
module Patch =
|
||||
|
||||
/// Create an UPDATE statement to patch documents
|
||||
let private update tableName whereClause =
|
||||
$"UPDATE %s{tableName} SET data = data || @data WHERE {whereClause}"
|
||||
[<CompiledName "Patch">]
|
||||
let patch tableName =
|
||||
$"UPDATE %s{tableName} SET data = data || @data"
|
||||
|
||||
/// Query to patch a document by its ID
|
||||
/// Create an UPDATE statement to remove fields from documents
|
||||
[<CompiledName "RemoveFields">]
|
||||
let removeFields tableName =
|
||||
$"UPDATE %s{tableName} SET data = data - @name"
|
||||
|
||||
/// Create a query by a document's ID
|
||||
[<CompiledName "ById">]
|
||||
let byId tableName =
|
||||
whereById "@id" |> update tableName
|
||||
let byId<'TKey> statement (docId: 'TKey) =
|
||||
Query.statementWhere statement (whereById docId)
|
||||
|
||||
/// Query to patch documents match a JSON field comparison (->> =)
|
||||
[<CompiledName "ByField">]
|
||||
let byField tableName field =
|
||||
whereByField field "@field" |> update tableName
|
||||
/// Create a query on JSON fields
|
||||
[<CompiledName "ByFields">]
|
||||
let byFields statement howMatched fields =
|
||||
Query.statementWhere statement (whereByFields howMatched fields)
|
||||
|
||||
/// Query to patch documents matching a JSON containment query (@>)
|
||||
/// Create a JSON containment query
|
||||
[<CompiledName "ByContains">]
|
||||
let byContains tableName =
|
||||
whereDataContains "@criteria" |> update tableName
|
||||
let byContains statement =
|
||||
Query.statementWhere statement (whereDataContains "@criteria")
|
||||
|
||||
/// Query to patch documents matching a JSON containment query (@>)
|
||||
[<CompiledName "ByJsonPath">]
|
||||
let byJsonPath tableName =
|
||||
whereJsonPathMatches "@path" |> update tableName
|
||||
|
||||
/// Queries to remove fields from documents
|
||||
module RemoveFields =
|
||||
|
||||
/// Create an UPDATE statement to remove parameters
|
||||
let private update tableName whereClause =
|
||||
$"UPDATE %s{tableName} SET data = data - @name WHERE {whereClause}"
|
||||
|
||||
/// Query to remove fields from a document by the document's ID
|
||||
[<CompiledName "ById">]
|
||||
let byId tableName =
|
||||
whereById "@id" |> update tableName
|
||||
|
||||
/// Query to remove fields from documents via a comparison on a JSON field within the document
|
||||
[<CompiledName "ByField">]
|
||||
let byField tableName field =
|
||||
whereByField field "@field" |> update tableName
|
||||
|
||||
/// Query to patch documents matching a JSON containment query (@>)
|
||||
[<CompiledName "ByContains">]
|
||||
let byContains tableName =
|
||||
whereDataContains "@criteria" |> update tableName
|
||||
|
||||
/// Query to patch documents matching a JSON containment query (@>)
|
||||
[<CompiledName "ByJsonPath">]
|
||||
let byJsonPath tableName =
|
||||
whereJsonPathMatches "@path" |> update tableName
|
||||
|
||||
/// Queries to delete documents
|
||||
module Delete =
|
||||
|
||||
/// Query to delete a document by its ID
|
||||
[<CompiledName "ById">]
|
||||
let byId tableName =
|
||||
$"""DELETE FROM %s{tableName} WHERE {whereById "@id"}"""
|
||||
|
||||
/// Query to delete documents using a comparison on a JSON field
|
||||
[<CompiledName "ByField">]
|
||||
let byField tableName field =
|
||||
$"""DELETE FROM %s{tableName} WHERE {whereByField field "@field"}"""
|
||||
|
||||
/// Query to delete documents using a JSON containment query (@>)
|
||||
[<CompiledName "ByContains">]
|
||||
let byContains tableName =
|
||||
$"""DELETE FROM %s{tableName} WHERE {whereDataContains "@criteria"}"""
|
||||
|
||||
/// Query to delete documents using a JSON Path match (@?)
|
||||
[<CompiledName "ByJsonPath">]
|
||||
let byJsonPath tableName =
|
||||
$"""DELETE FROM %s{tableName} WHERE {whereJsonPathMatches "@path"}"""
|
||||
/// Create a JSON Path match query
|
||||
[<CompiledName "ByPathMatch">]
|
||||
let byPathMatch statement =
|
||||
Query.statementWhere statement (whereJsonPathMatches "@path")
|
||||
|
||||
|
||||
/// Functions for dealing with results
|
||||
@@ -333,6 +245,8 @@ module Results =
|
||||
/// Versions of queries that accept SqlProps as the last parameter
|
||||
module WithProps =
|
||||
|
||||
module FSharpList = Microsoft.FSharp.Collections.List
|
||||
|
||||
/// Commands to execute custom SQL queries
|
||||
[<RequireQualifiedAccess>]
|
||||
module Custom =
|
||||
@@ -341,12 +255,12 @@ module WithProps =
|
||||
[<CompiledName "FSharpList">]
|
||||
let list<'TDoc> query parameters (mapFunc: RowReader -> 'TDoc) sqlProps =
|
||||
Sql.query query sqlProps
|
||||
|> Sql.parameters parameters
|
||||
|> 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<RowReader, 'TDoc>, sqlProps) = backgroundTask {
|
||||
let! results = list<'TDoc> query (List.ofSeq parameters) mapFunc.Invoke sqlProps
|
||||
let! results = list<'TDoc> query parameters mapFunc.Invoke sqlProps
|
||||
return ResizeArray results
|
||||
}
|
||||
|
||||
@@ -360,7 +274,7 @@ module WithProps =
|
||||
/// Execute a query that returns one or no results; returns null if not found
|
||||
let Single<'TDoc when 'TDoc: null>(
|
||||
query, parameters, mapFunc: System.Func<RowReader, 'TDoc>, sqlProps) = backgroundTask {
|
||||
let! result = single<'TDoc> query (FSharp.Collections.List.ofSeq parameters) mapFunc.Invoke sqlProps
|
||||
let! result = single<'TDoc> query parameters mapFunc.Invoke sqlProps
|
||||
return Option.toObj result
|
||||
}
|
||||
|
||||
@@ -368,7 +282,7 @@ module WithProps =
|
||||
[<CompiledName "NonQuery">]
|
||||
let nonQuery query parameters sqlProps =
|
||||
Sql.query query sqlProps
|
||||
|> Sql.parameters (FSharp.Collections.List.ofSeq parameters)
|
||||
|> Sql.parameters (FSharpList.ofSeq parameters)
|
||||
|> Sql.executeNonQueryAsync
|
||||
|> ignoreTask
|
||||
|
||||
@@ -376,12 +290,12 @@ module WithProps =
|
||||
[<CompiledName "FSharpScalar">]
|
||||
let scalar<'T when 'T: struct> query parameters (mapFunc: RowReader -> 'T) sqlProps =
|
||||
Sql.query query sqlProps
|
||||
|> Sql.parameters parameters
|
||||
|> 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<RowReader, 'T>, sqlProps) =
|
||||
scalar<'T> query (FSharp.Collections.List.ofSeq parameters) mapFunc.Invoke sqlProps
|
||||
scalar<'T> query parameters mapFunc.Invoke sqlProps
|
||||
|
||||
/// Table and index definition commands
|
||||
module Definition =
|
||||
@@ -390,7 +304,7 @@ module WithProps =
|
||||
[<CompiledName "EnsureTable">]
|
||||
let ensureTable name sqlProps = backgroundTask {
|
||||
do! Custom.nonQuery (Query.Definition.ensureTable name) [] sqlProps
|
||||
do! Custom.nonQuery (Query.Definition.ensureKey name) [] sqlProps
|
||||
do! Custom.nonQuery (Query.Definition.ensureKey name PostgreSQL) [] sqlProps
|
||||
}
|
||||
|
||||
/// Create an index on documents in the specified table
|
||||
@@ -401,7 +315,7 @@ module WithProps =
|
||||
/// Create an index on field(s) within documents in the specified table
|
||||
[<CompiledName "EnsureFieldIndex">]
|
||||
let ensureFieldIndex tableName indexName fields sqlProps =
|
||||
Custom.nonQuery (Query.Definition.ensureIndexOn tableName indexName fields) [] sqlProps
|
||||
Custom.nonQuery (Query.Definition.ensureIndexOn tableName indexName fields PostgreSQL) [] sqlProps
|
||||
|
||||
/// Commands to add documents
|
||||
[<AutoOpen>]
|
||||
@@ -424,22 +338,25 @@ module WithProps =
|
||||
/// Count all documents in a table
|
||||
[<CompiledName "All">]
|
||||
let all tableName sqlProps =
|
||||
Custom.scalar (Query.Count.all tableName) [] toCount sqlProps
|
||||
Custom.scalar (Query.count tableName) [] toCount sqlProps
|
||||
|
||||
/// Count matching documents using a JSON field comparison (->> =)
|
||||
[<CompiledName "ByField">]
|
||||
let byField tableName field sqlProps =
|
||||
Custom.scalar (Query.Count.byField tableName field) (addFieldParam "@field" field []) toCount sqlProps
|
||||
/// Count matching documents using JSON field comparisons (->> =)
|
||||
[<CompiledName "ByFields">]
|
||||
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 (@>)
|
||||
[<CompiledName "ByContains">]
|
||||
let byContains tableName (criteria: 'TContains) sqlProps =
|
||||
Custom.scalar (Query.Count.byContains tableName) [ jsonParam "@criteria" criteria ] toCount sqlProps
|
||||
Custom.scalar
|
||||
(Query.byContains (Query.count tableName)) [ jsonParam "@criteria" criteria ] toCount sqlProps
|
||||
|
||||
/// Count matching documents using a JSON Path match query (@?)
|
||||
[<CompiledName "ByJsonPath">]
|
||||
let byJsonPath tableName jsonPath sqlProps =
|
||||
Custom.scalar (Query.Count.byJsonPath tableName) [ "@path", Sql.string jsonPath ] toCount sqlProps
|
||||
Custom.scalar
|
||||
(Query.byPathMatch (Query.count tableName)) [ "@path", Sql.string jsonPath ] toCount sqlProps
|
||||
|
||||
/// Commands to determine if documents exist
|
||||
[<RequireQualifiedAccess>]
|
||||
@@ -448,22 +365,34 @@ module WithProps =
|
||||
/// Determine if a document exists for the given ID
|
||||
[<CompiledName "ById">]
|
||||
let byId tableName (docId: 'TKey) sqlProps =
|
||||
Custom.scalar (Query.Exists.byId tableName) [ idParam docId ] toExists sqlProps
|
||||
Custom.scalar (Query.exists tableName (Query.whereById docId)) [ idParam docId ] toExists sqlProps
|
||||
|
||||
/// Determine if a document exists using a JSON field comparison (->> =)
|
||||
[<CompiledName "ByField">]
|
||||
let byField tableName field sqlProps =
|
||||
Custom.scalar (Query.Exists.byField tableName field) (addFieldParam "@field" field []) toExists sqlProps
|
||||
/// Determine if a document exists using JSON field comparisons (->> =)
|
||||
[<CompiledName "ByFields">]
|
||||
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 (@>)
|
||||
[<CompiledName "ByContains">]
|
||||
let byContains tableName (criteria: 'TContains) sqlProps =
|
||||
Custom.scalar (Query.Exists.byContains tableName) [ jsonParam "@criteria" criteria ] toExists 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 (@?)
|
||||
[<CompiledName "ByJsonPath">]
|
||||
let byJsonPath tableName jsonPath sqlProps =
|
||||
Custom.scalar (Query.Exists.byJsonPath tableName) [ "@path", Sql.string jsonPath ] toExists sqlProps
|
||||
Custom.scalar
|
||||
(Query.exists tableName (Query.whereJsonPathMatches "@path"))
|
||||
[ "@path", Sql.string jsonPath ]
|
||||
toExists
|
||||
sqlProps
|
||||
|
||||
/// Commands to determine if documents exist
|
||||
[<RequireQualifiedAccess>]
|
||||
@@ -472,81 +401,119 @@ module WithProps =
|
||||
/// Retrieve all documents in the given table
|
||||
[<CompiledName "FSharpAll">]
|
||||
let all<'TDoc> tableName sqlProps =
|
||||
Custom.list<'TDoc> (Query.selectFromTable tableName) [] fromData<'TDoc> 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.selectFromTable tableName, [], fromData<'TDoc>, sqlProps)
|
||||
Custom.List<'TDoc>(Query.find tableName, [], fromData<'TDoc>, sqlProps)
|
||||
|
||||
/// Retrieve a document by its ID (returns None if not found)
|
||||
[<CompiledName "FSharpById">]
|
||||
let byId<'TKey, 'TDoc> tableName (docId: 'TKey) sqlProps =
|
||||
Custom.single (Query.Find.byId tableName) [ idParam docId ] fromData<'TDoc> 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>(tableName, docId: 'TKey, sqlProps) =
|
||||
Custom.Single<'TDoc>(Query.Find.byId tableName, [ idParam docId ], fromData<'TDoc>, sqlProps)
|
||||
Custom.Single<'TDoc>(
|
||||
Query.byId (Query.find tableName) docId, [ idParam docId ], fromData<'TDoc>, sqlProps)
|
||||
|
||||
/// Retrieve documents matching JSON field comparisons (->> =)
|
||||
[<CompiledName "FSharpByFields">]
|
||||
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 a JSON field comparison (->> =)
|
||||
[<CompiledName "FSharpByField">]
|
||||
[<System.Obsolete "Use byFields instead; will be removed in v4">]
|
||||
let byField<'TDoc> tableName field sqlProps =
|
||||
Custom.list<'TDoc>
|
||||
(Query.Find.byField tableName field) (addFieldParam "@field" field []) fromData<'TDoc> sqlProps
|
||||
byFields<'TDoc> tableName Any [ field ] 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 a JSON field comparison (->> =)
|
||||
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||
let ByField<'TDoc>(tableName, field, sqlProps) =
|
||||
Custom.List<'TDoc>(
|
||||
Query.Find.byField tableName field, addFieldParam "@field" field [], fromData<'TDoc>, sqlProps)
|
||||
ByFields<'TDoc>(tableName, Any, Seq.singleton field, sqlProps)
|
||||
|
||||
/// Retrieve documents matching a JSON containment query (@>)
|
||||
[<CompiledName "FSharpByContains">]
|
||||
let byContains<'TDoc> tableName (criteria: obj) sqlProps =
|
||||
Custom.list<'TDoc>
|
||||
(Query.Find.byContains tableName) [ jsonParam "@criteria" criteria ] fromData<'TDoc> sqlProps
|
||||
(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.Find.byContains tableName, [ jsonParam "@criteria" criteria ], fromData<'TDoc>, sqlProps)
|
||||
Query.byContains (Query.find tableName),
|
||||
[ jsonParam "@criteria" criteria ],
|
||||
fromData<'TDoc>,
|
||||
sqlProps)
|
||||
|
||||
/// Retrieve documents matching a JSON Path match query (@?)
|
||||
[<CompiledName "FSharpByJsonPath">]
|
||||
let byJsonPath<'TDoc> tableName jsonPath sqlProps =
|
||||
Custom.list<'TDoc>
|
||||
(Query.Find.byJsonPath tableName) [ "@path", Sql.string jsonPath ] fromData<'TDoc> sqlProps
|
||||
(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.Find.byJsonPath tableName, [ "@path", Sql.string jsonPath ], fromData<'TDoc>, sqlProps)
|
||||
Query.byPathMatch (Query.find tableName),
|
||||
[ "@path", Sql.string jsonPath ],
|
||||
fromData<'TDoc>,
|
||||
sqlProps)
|
||||
|
||||
/// Retrieve the first document matching a JSON field comparison (->> =); returns None if not found
|
||||
[<CompiledName "FSharpFirstByField">]
|
||||
let firstByField<'TDoc> tableName field sqlProps =
|
||||
/// Retrieve the first document matching JSON field comparisons (->> =); returns None if not found
|
||||
[<CompiledName "FSharpFirstByFields">]
|
||||
let firstByFields<'TDoc> tableName howMatched fields sqlProps =
|
||||
Custom.single<'TDoc>
|
||||
$"{Query.Find.byField tableName field} LIMIT 1"
|
||||
(addFieldParam "@field" field [])
|
||||
$"{Query.byFields (Query.find tableName) howMatched fields} LIMIT 1"
|
||||
(addFieldParams fields [])
|
||||
fromData<'TDoc>
|
||||
sqlProps
|
||||
|
||||
/// Retrieve the first document matching a JSON field comparison (->> =); returns null if not found
|
||||
let FirstByField<'TDoc when 'TDoc: null>(tableName, field, sqlProps) =
|
||||
/// Retrieve the first document matching a JSON field comparison (->> =); returns None if not found
|
||||
[<CompiledName "FSharpFirstByField">]
|
||||
[<System.Obsolete "Use firstByFields instead; will be removed in v4">]
|
||||
let firstByField<'TDoc> tableName field sqlProps =
|
||||
firstByFields<'TDoc> tableName Any [ field ] sqlProps
|
||||
|
||||
/// Retrieve the first document matching JSON field comparisons (->> =); returns null if not found
|
||||
let FirstByFields<'TDoc when 'TDoc: null>(tableName, howMatched, fields, sqlProps) =
|
||||
Custom.Single<'TDoc>(
|
||||
$"{Query.Find.byField tableName field} LIMIT 1",
|
||||
addFieldParam "@field" field [],
|
||||
$"{Query.byFields (Query.find tableName) howMatched fields} LIMIT 1",
|
||||
addFieldParams fields [],
|
||||
fromData<'TDoc>,
|
||||
sqlProps)
|
||||
|
||||
/// Retrieve the first document matching a JSON field comparison (->> =); returns null if not found
|
||||
[<System.Obsolete "Use FirstByFields instead; will be removed in v4">]
|
||||
let FirstByField<'TDoc when 'TDoc: null>(tableName, field, sqlProps) =
|
||||
FirstByFields<'TDoc>(tableName, Any, Seq.singleton field, sqlProps)
|
||||
|
||||
/// Retrieve the first document matching a JSON containment query (@>); returns None if not found
|
||||
[<CompiledName "FSharpFirstByContains">]
|
||||
let firstByContains<'TDoc> tableName (criteria: obj) sqlProps =
|
||||
Custom.single<'TDoc>
|
||||
$"{Query.Find.byContains tableName} LIMIT 1" [ jsonParam "@criteria" criteria ] fromData<'TDoc> sqlProps
|
||||
$"{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>(tableName, criteria: obj, sqlProps) =
|
||||
Custom.Single<'TDoc>(
|
||||
$"{Query.Find.byContains tableName} LIMIT 1",
|
||||
$"{Query.byContains (Query.find tableName)} LIMIT 1",
|
||||
[ jsonParam "@criteria" criteria ],
|
||||
fromData<'TDoc>,
|
||||
sqlProps)
|
||||
@@ -555,12 +522,15 @@ module WithProps =
|
||||
[<CompiledName "FSharpFirstByJsonPath">]
|
||||
let firstByJsonPath<'TDoc> tableName jsonPath sqlProps =
|
||||
Custom.single<'TDoc>
|
||||
$"{Query.Find.byJsonPath tableName} LIMIT 1" [ "@path", Sql.string jsonPath ] fromData<'TDoc> sqlProps
|
||||
$"{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>(tableName, jsonPath, sqlProps) =
|
||||
Custom.Single<'TDoc>(
|
||||
$"{Query.Find.byJsonPath tableName} LIMIT 1",
|
||||
$"{Query.byPathMatch (Query.find tableName)} LIMIT 1",
|
||||
[ "@path", Sql.string jsonPath ],
|
||||
fromData<'TDoc>,
|
||||
sqlProps)
|
||||
@@ -572,7 +542,8 @@ module WithProps =
|
||||
/// Update an entire document by its ID
|
||||
[<CompiledName "ById">]
|
||||
let byId tableName (docId: 'TKey) (document: 'TDoc) sqlProps =
|
||||
Custom.nonQuery (Query.update tableName) [ idParam docId; jsonParam "@data" document ] 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
|
||||
[<CompiledName "FSharpByFunc">]
|
||||
@@ -590,77 +561,79 @@ module WithProps =
|
||||
/// Patch a document by its ID
|
||||
[<CompiledName "ById">]
|
||||
let byId tableName (docId: 'TKey) (patch: 'TPatch) sqlProps =
|
||||
Custom.nonQuery (Query.Patch.byId tableName) [ idParam docId; jsonParam "@data" patch ] 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 (->> =)
|
||||
[<CompiledName "ByFields">]
|
||||
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 field comparison query in the WHERE clause (->> =)
|
||||
[<CompiledName "ByField">]
|
||||
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||
let byField tableName field (patch: 'TPatch) sqlProps =
|
||||
Custom.nonQuery
|
||||
(Query.Patch.byField tableName field)
|
||||
(addFieldParam "@field" field [ jsonParam "@data" patch ])
|
||||
sqlProps
|
||||
byFields tableName Any [ field ] patch sqlProps
|
||||
|
||||
/// Patch documents using a JSON containment query in the WHERE clause (@>)
|
||||
[<CompiledName "ByContains">]
|
||||
let byContains tableName (criteria: 'TContains) (patch: 'TPatch) sqlProps =
|
||||
Custom.nonQuery
|
||||
(Query.Patch.byContains tableName) [ jsonParam "@data" patch; jsonParam "@criteria" criteria ] sqlProps
|
||||
(Query.byContains (Query.patch tableName))
|
||||
[ jsonParam "@data" patch; jsonParam "@criteria" criteria ]
|
||||
sqlProps
|
||||
|
||||
/// Patch documents using a JSON Path match query in the WHERE clause (@?)
|
||||
[<CompiledName "ByJsonPath">]
|
||||
let byJsonPath tableName jsonPath (patch: 'TPatch) sqlProps =
|
||||
Custom.nonQuery
|
||||
(Query.Patch.byJsonPath tableName) [ jsonParam "@data" patch; "@path", Sql.string jsonPath ] sqlProps
|
||||
(Query.byPathMatch (Query.patch tableName))
|
||||
[ jsonParam "@data" patch; "@path", Sql.string jsonPath ]
|
||||
sqlProps
|
||||
|
||||
/// Commands to remove fields from documents
|
||||
[<RequireQualifiedAccess>]
|
||||
module RemoveFields =
|
||||
|
||||
/// Remove fields from a document by the document's ID
|
||||
[<CompiledName "FSharpById">]
|
||||
[<CompiledName "ById">]
|
||||
let byId tableName (docId: 'TKey) fieldNames sqlProps =
|
||||
Custom.nonQuery (Query.RemoveFields.byId tableName) [ idParam docId; fieldNameParam fieldNames ] sqlProps
|
||||
|
||||
/// Remove fields from a document by the document's ID
|
||||
let ById(tableName, docId: 'TKey, fieldNames, sqlProps) =
|
||||
byId tableName docId (List.ofSeq fieldNames) sqlProps
|
||||
|
||||
/// Remove fields from documents via a comparison on a JSON field in the document
|
||||
[<CompiledName "FSharpByField">]
|
||||
let byField tableName field fieldNames sqlProps =
|
||||
Custom.nonQuery
|
||||
(Query.RemoveFields.byField tableName field)
|
||||
(addFieldParam "@field" field [ fieldNameParam fieldNames ])
|
||||
(Query.byId (Query.removeFields tableName) docId) [ idParam docId; fieldNameParams fieldNames ] sqlProps
|
||||
|
||||
/// Remove fields from documents via a comparison on JSON fields in the document
|
||||
[<CompiledName "ByFields">]
|
||||
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 comparison on a JSON field in the document
|
||||
let ByField(tableName, field, fieldNames, sqlProps) =
|
||||
byField tableName field (List.ofSeq fieldNames) sqlProps
|
||||
[<CompiledName "ByField">]
|
||||
[<System.Obsolete "Use byFields instead; will be removed in v4">]
|
||||
let byField tableName field fieldNames sqlProps =
|
||||
byFields tableName Any [ field ] fieldNames sqlProps
|
||||
|
||||
/// Remove fields from documents via a JSON containment query (@>)
|
||||
[<CompiledName "FSharpByContains">]
|
||||
[<CompiledName "ByContains">]
|
||||
let byContains tableName (criteria: 'TContains) fieldNames sqlProps =
|
||||
Custom.nonQuery
|
||||
(Query.RemoveFields.byContains tableName)
|
||||
[ jsonParam "@criteria" criteria; fieldNameParam fieldNames ]
|
||||
(Query.byContains (Query.removeFields tableName))
|
||||
[ jsonParam "@criteria" criteria; fieldNameParams fieldNames ]
|
||||
sqlProps
|
||||
|
||||
/// Remove fields from documents via a JSON containment query (@>)
|
||||
let ByContains(tableName, criteria: 'TContains, fieldNames, sqlProps) =
|
||||
byContains tableName criteria (List.ofSeq fieldNames) sqlProps
|
||||
|
||||
/// Remove fields from documents via a JSON Path match query (@?)
|
||||
[<CompiledName "FSharpByJsonPath">]
|
||||
let byJsonPath tableName jsonPath fieldNames sqlProps =
|
||||
Custom.nonQuery
|
||||
(Query.RemoveFields.byJsonPath tableName)
|
||||
[ "@path", Sql.string jsonPath; fieldNameParam fieldNames ]
|
||||
(Query.byPathMatch (Query.removeFields tableName))
|
||||
[ "@path", Sql.string jsonPath; fieldNameParams fieldNames ]
|
||||
sqlProps
|
||||
|
||||
/// Remove fields from documents via a JSON Path match query (@?)
|
||||
let ByJsonPath(tableName, jsonPath, fieldNames, sqlProps) =
|
||||
byJsonPath tableName jsonPath (List.ofSeq fieldNames) sqlProps
|
||||
|
||||
/// Commands to delete documents
|
||||
[<RequireQualifiedAccess>]
|
||||
module Delete =
|
||||
@@ -668,22 +641,29 @@ module WithProps =
|
||||
/// Delete a document by its ID
|
||||
[<CompiledName "ById">]
|
||||
let byId tableName (docId: 'TKey) sqlProps =
|
||||
Custom.nonQuery (Query.Delete.byId tableName) [ idParam docId ] sqlProps
|
||||
Custom.nonQuery (Query.byId (Query.delete tableName) docId) [ idParam docId ] sqlProps
|
||||
|
||||
/// Delete documents by matching a JSON field comparison query (->> =)
|
||||
[<CompiledName "ByFields">]
|
||||
let byFields tableName howMatched fields sqlProps =
|
||||
Custom.nonQuery
|
||||
(Query.byFields (Query.delete tableName) howMatched fields) (addFieldParams fields []) sqlProps
|
||||
|
||||
/// Delete documents by matching a JSON field comparison query (->> =)
|
||||
[<CompiledName "ByField">]
|
||||
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||
let byField tableName field sqlProps =
|
||||
Custom.nonQuery (Query.Delete.byField tableName field) (addFieldParam "@field" field []) sqlProps
|
||||
byFields tableName Any [ field ] sqlProps
|
||||
|
||||
/// Delete documents by matching a JSON contains query (@>)
|
||||
[<CompiledName "ByContains">]
|
||||
let byContains tableName (criteria: 'TCriteria) sqlProps =
|
||||
Custom.nonQuery (Query.Delete.byContains tableName) [ jsonParam "@criteria" criteria ] sqlProps
|
||||
Custom.nonQuery (Query.byContains (Query.delete tableName)) [ jsonParam "@criteria" criteria ] sqlProps
|
||||
|
||||
/// Delete documents by matching a JSON Path match query (@?)
|
||||
[<CompiledName "ByJsonPath">]
|
||||
let byJsonPath tableName path sqlProps =
|
||||
Custom.nonQuery (Query.Delete.byJsonPath tableName) [ "@path", Sql.string path ] sqlProps
|
||||
Custom.nonQuery (Query.byPathMatch (Query.delete tableName)) [ "@path", Sql.string path ] sqlProps
|
||||
|
||||
|
||||
/// Commands to execute custom SQL queries
|
||||
@@ -767,10 +747,16 @@ module Count =
|
||||
let all tableName =
|
||||
WithProps.Count.all tableName (fromDataSource ())
|
||||
|
||||
/// Count matching documents using a JSON field comparison query (->> =)
|
||||
[<CompiledName "ByFields">]
|
||||
let byFields tableName howMatched fields =
|
||||
WithProps.Count.byFields tableName howMatched fields (fromDataSource ())
|
||||
|
||||
/// Count matching documents using a JSON field comparison query (->> =)
|
||||
[<CompiledName "ByField">]
|
||||
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||
let byField tableName field =
|
||||
WithProps.Count.byField tableName field (fromDataSource ())
|
||||
byFields tableName Any [ field ]
|
||||
|
||||
/// Count matching documents using a JSON containment query (@>)
|
||||
[<CompiledName "ByContains">]
|
||||
@@ -792,10 +778,16 @@ module Exists =
|
||||
let byId tableName docId =
|
||||
WithProps.Exists.byId tableName docId (fromDataSource ())
|
||||
|
||||
/// Determine if documents exist using a JSON field comparison query (->> =)
|
||||
[<CompiledName "ByFields">]
|
||||
let byFields tableName howMatched fields =
|
||||
WithProps.Exists.byFields tableName howMatched fields (fromDataSource ())
|
||||
|
||||
/// Determine if documents exist using a JSON field comparison query (->> =)
|
||||
[<CompiledName "ByField">]
|
||||
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||
let byField tableName field =
|
||||
WithProps.Exists.byField tableName field (fromDataSource ())
|
||||
byFields tableName Any [ field ]
|
||||
|
||||
/// Determine if documents exist using a JSON containment query (@>)
|
||||
[<CompiledName "ByContains">]
|
||||
@@ -831,13 +823,24 @@ module Find =
|
||||
WithProps.Find.ById<'TKey, 'TDoc>(tableName, docId, fromDataSource ())
|
||||
|
||||
/// Retrieve documents matching a JSON field comparison query (->> =)
|
||||
[<CompiledName "FSharpByField">]
|
||||
let byField<'TDoc> tableName field =
|
||||
WithProps.Find.byField<'TDoc> tableName field (fromDataSource ())
|
||||
[<CompiledName "FSharpByFields">]
|
||||
let byFields<'TDoc> tableName howMatched fields =
|
||||
WithProps.Find.byFields<'TDoc> tableName howMatched fields (fromDataSource ())
|
||||
|
||||
/// Retrieve documents matching a JSON field comparison query (->> =)
|
||||
[<CompiledName "FSharpByField">]
|
||||
[<System.Obsolete "Use byFields instead; will be removed in v4">]
|
||||
let byField<'TDoc> tableName field =
|
||||
byFields<'TDoc> tableName Any [ field ]
|
||||
|
||||
/// 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 (->> =)
|
||||
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||
let ByField<'TDoc>(tableName, field) =
|
||||
WithProps.Find.ByField<'TDoc>(tableName, field, fromDataSource ())
|
||||
ByFields<'TDoc>(tableName, Any, Seq.singleton field)
|
||||
|
||||
/// Retrieve documents matching a JSON containment query (@>)
|
||||
[<CompiledName "FSharpByContains">]
|
||||
@@ -857,14 +860,25 @@ module Find =
|
||||
let ByJsonPath<'TDoc>(tableName, jsonPath) =
|
||||
WithProps.Find.ByJsonPath<'TDoc>(tableName, jsonPath, fromDataSource ())
|
||||
|
||||
/// Retrieve the first document matching a JSON field comparison query (->> =); returns None if not found
|
||||
[<CompiledName "FSharpFirstByFields">]
|
||||
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 None if not found
|
||||
[<CompiledName "FSharpFirstByField">]
|
||||
[<System.Obsolete "Use firstByFields instead; will be removed in v4">]
|
||||
let firstByField<'TDoc> tableName field =
|
||||
WithProps.Find.firstByField<'TDoc> tableName field (fromDataSource ())
|
||||
firstByFields<'TDoc> tableName Any [ field ]
|
||||
|
||||
/// Retrieve the first document matching a JSON field comparison query (->> =); returns null if not found
|
||||
let FirstByFields<'TDoc when 'TDoc: null>(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
|
||||
[<System.Obsolete "Use FirstByFields instead; will be removed in v4">]
|
||||
let FirstByField<'TDoc when 'TDoc: null>(tableName, field) =
|
||||
WithProps.Find.FirstByField<'TDoc>(tableName, field, fromDataSource ())
|
||||
FirstByFields<'TDoc>(tableName, Any, Seq.singleton field)
|
||||
|
||||
/// Retrieve the first document matching a JSON containment query (@>); returns None if not found
|
||||
[<CompiledName "FSharpFirstByContains">]
|
||||
@@ -913,10 +927,16 @@ module Patch =
|
||||
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 (->> =)
|
||||
[<CompiledName "ByFields">]
|
||||
let byFields tableName howMatched fields (patch: 'TPatch) =
|
||||
WithProps.Patch.byFields tableName howMatched fields patch (fromDataSource ())
|
||||
|
||||
/// Patch documents using a JSON field comparison query in the WHERE clause (->> =)
|
||||
[<CompiledName "ByField">]
|
||||
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||
let byField tableName field (patch: 'TPatch) =
|
||||
WithProps.Patch.byField tableName field patch (fromDataSource ())
|
||||
byFields tableName Any [ field ] patch
|
||||
|
||||
/// Patch documents using a JSON containment query in the WHERE clause (@>)
|
||||
[<CompiledName "ByContains">]
|
||||
@@ -934,41 +954,31 @@ module Patch =
|
||||
module RemoveFields =
|
||||
|
||||
/// Remove fields from a document by the document's ID
|
||||
[<CompiledName "FSharpById">]
|
||||
[<CompiledName "ById">]
|
||||
let byId tableName (docId: 'TKey) fieldNames =
|
||||
WithProps.RemoveFields.byId tableName docId fieldNames (fromDataSource ())
|
||||
|
||||
/// 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
|
||||
[<CompiledName "ByFields">]
|
||||
let byFields tableName howMatched fields fieldNames =
|
||||
WithProps.RemoveFields.byFields tableName howMatched fields fieldNames (fromDataSource ())
|
||||
|
||||
/// Remove fields from documents via a comparison on a JSON field in the document
|
||||
[<CompiledName "FSharpByField">]
|
||||
[<CompiledName "ByField">]
|
||||
[<System.Obsolete "Use byFields instead; will be removed in v4">]
|
||||
let byField tableName field fieldNames =
|
||||
WithProps.RemoveFields.byField tableName field fieldNames (fromDataSource ())
|
||||
|
||||
/// Remove fields from documents via a comparison on a JSON field in the document
|
||||
let ByField(tableName, field, fieldNames) =
|
||||
WithProps.RemoveFields.ByField(tableName, field, fieldNames, fromDataSource ())
|
||||
byFields tableName Any [ field ] fieldNames
|
||||
|
||||
/// Remove fields from documents via a JSON containment query (@>)
|
||||
[<CompiledName "FSharpByContains">]
|
||||
[<CompiledName "ByContains">]
|
||||
let byContains tableName (criteria: 'TContains) fieldNames =
|
||||
WithProps.RemoveFields.byContains tableName criteria 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 (@?)
|
||||
[<CompiledName "FSharpByJsonPath">]
|
||||
[<CompiledName "ByJsonPath">]
|
||||
let byJsonPath tableName jsonPath fieldNames =
|
||||
WithProps.RemoveFields.byJsonPath tableName jsonPath 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
|
||||
[<RequireQualifiedAccess>]
|
||||
@@ -979,10 +989,16 @@ module Delete =
|
||||
let byId tableName (docId: 'TKey) =
|
||||
WithProps.Delete.byId tableName docId (fromDataSource ())
|
||||
|
||||
/// Delete documents by matching a JSON field comparison query (->> =)
|
||||
[<CompiledName "ByFields">]
|
||||
let byFields tableName howMatched fields =
|
||||
WithProps.Delete.byFields tableName howMatched fields (fromDataSource ())
|
||||
|
||||
/// Delete documents by matching a JSON field comparison query (->> =)
|
||||
[<CompiledName "ByField">]
|
||||
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||
let byField tableName field =
|
||||
WithProps.Delete.byField tableName field (fromDataSource ())
|
||||
byFields tableName Any [ field ]
|
||||
|
||||
/// Delete documents by matching a JSON containment query (@>)
|
||||
[<CompiledName "ByContains">]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
namespace BitBadger.Documents.Sqlite
|
||||
|
||||
open BitBadger.Documents
|
||||
open Microsoft.Data.Sqlite
|
||||
|
||||
/// F# extensions for the SqliteConnection type
|
||||
@@ -44,17 +45,27 @@ module Extensions =
|
||||
member conn.countAll tableName =
|
||||
WithConn.Count.all tableName conn
|
||||
|
||||
/// Count matching documents using a comparison on JSON fields
|
||||
member conn.countByFields tableName howMatched fields =
|
||||
WithConn.Count.byFields tableName howMatched fields conn
|
||||
|
||||
/// Count matching documents using a comparison on a JSON field
|
||||
[<System.Obsolete "Use countByFields instead; will be removed in v4">]
|
||||
member conn.countByField tableName field =
|
||||
WithConn.Count.byField tableName field conn
|
||||
conn.countByFields tableName Any [ field ]
|
||||
|
||||
/// Determine if a document exists for the given ID
|
||||
member conn.existsById tableName (docId: 'TKey) =
|
||||
WithConn.Exists.byId tableName docId conn
|
||||
|
||||
/// Determine if a document exists using a comparison on JSON fields
|
||||
member conn.existsByFields tableName howMatched fields =
|
||||
WithConn.Exists.byFields tableName howMatched fields conn
|
||||
|
||||
/// Determine if a document exists using a comparison on a JSON field
|
||||
[<System.Obsolete "Use existsByFields instead; will be removed in v4">]
|
||||
member conn.existsByField tableName field =
|
||||
WithConn.Exists.byField tableName field conn
|
||||
conn.existsByFields tableName Any [ field ]
|
||||
|
||||
/// Retrieve all documents in the given table
|
||||
member conn.findAll<'TDoc> tableName =
|
||||
@@ -64,13 +75,23 @@ module Extensions =
|
||||
member conn.findById<'TKey, 'TDoc> tableName (docId: 'TKey) =
|
||||
WithConn.Find.byId<'TKey, 'TDoc> tableName docId conn
|
||||
|
||||
/// Retrieve documents via a comparison on JSON fields
|
||||
member conn.findByFields<'TDoc> tableName howMatched fields =
|
||||
WithConn.Find.byFields<'TDoc> tableName howMatched fields conn
|
||||
|
||||
/// Retrieve documents via a comparison on a JSON field
|
||||
[<System.Obsolete "Use findByFields instead; will be removed in v4">]
|
||||
member conn.findByField<'TDoc> tableName field =
|
||||
WithConn.Find.byField<'TDoc> tableName field conn
|
||||
conn.findByFields<'TDoc> tableName Any [ field ]
|
||||
|
||||
/// Retrieve documents via a comparison on JSON fields, returning only the first result
|
||||
member conn.findFirstByFields<'TDoc> tableName howMatched fields =
|
||||
WithConn.Find.firstByFields<'TDoc> tableName howMatched fields conn
|
||||
|
||||
/// Retrieve documents via a comparison on a JSON field, returning only the first result
|
||||
[<System.Obsolete "Use findFirstByFields instead; will be removed in v4">]
|
||||
member conn.findFirstByField<'TDoc> tableName field =
|
||||
WithConn.Find.firstByField<'TDoc> tableName field conn
|
||||
conn.findFirstByFields<'TDoc> tableName Any [ field ]
|
||||
|
||||
/// Update an entire document by its ID
|
||||
member conn.updateById tableName (docId: 'TKey) (document: 'TDoc) =
|
||||
@@ -84,25 +105,40 @@ module Extensions =
|
||||
member conn.patchById tableName (docId: 'TKey) (patch: 'TPatch) =
|
||||
WithConn.Patch.byId tableName docId patch conn
|
||||
|
||||
/// Patch documents using a comparison on JSON fields
|
||||
member conn.patchByFields tableName howMatched fields (patch: 'TPatch) =
|
||||
WithConn.Patch.byFields tableName howMatched fields patch conn
|
||||
|
||||
/// Patch documents using a comparison on a JSON field
|
||||
[<System.Obsolete "Use patchByFields instead; will be removed in v4">]
|
||||
member conn.patchByField tableName field (patch: 'TPatch) =
|
||||
WithConn.Patch.byField tableName field patch conn
|
||||
conn.patchByFields tableName Any [ field ] patch
|
||||
|
||||
/// Remove fields from a document by the document's ID
|
||||
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
|
||||
member conn.removeFieldsByFields tableName howMatched fields fieldNames =
|
||||
WithConn.RemoveFields.byFields tableName howMatched fields fieldNames conn
|
||||
|
||||
/// Remove a field from a document via a comparison on a JSON field in the document
|
||||
[<System.Obsolete "Use removeFieldsByFields instead; will be removed in v4">]
|
||||
member conn.removeFieldsByField tableName field fieldNames =
|
||||
WithConn.RemoveFields.byField tableName field fieldNames conn
|
||||
conn.removeFieldsByFields tableName Any [ field ] fieldNames
|
||||
|
||||
/// Delete a document by its ID
|
||||
member conn.deleteById tableName (docId: 'TKey) =
|
||||
WithConn.Delete.byId tableName docId conn
|
||||
|
||||
/// Delete documents by matching a comparison on JSON fields
|
||||
member conn.deleteByFields tableName howMatched fields =
|
||||
WithConn.Delete.byFields tableName howMatched fields conn
|
||||
|
||||
/// Delete documents by matching a comparison on a JSON field
|
||||
[<System.Obsolete "Use deleteByFields instead; will be removed in v4">]
|
||||
member conn.deleteByField tableName field =
|
||||
WithConn.Delete.byField tableName field conn
|
||||
conn.deleteByFields tableName Any [ field ]
|
||||
|
||||
|
||||
open System.Runtime.CompilerServices
|
||||
@@ -157,20 +193,32 @@ type SqliteConnectionCSharpExtensions =
|
||||
static member inline CountAll(conn, tableName) =
|
||||
WithConn.Count.all tableName conn
|
||||
|
||||
/// Count matching documents using a comparison on JSON fields
|
||||
[<Extension>]
|
||||
static member inline CountByFields(conn, tableName, howMatched, fields) =
|
||||
WithConn.Count.byFields tableName howMatched fields conn
|
||||
|
||||
/// Count matching documents using a comparison on a JSON field
|
||||
[<Extension>]
|
||||
[<System.Obsolete "Use CountByFields instead; will be removed in v4">]
|
||||
static member inline CountByField(conn, tableName, field) =
|
||||
WithConn.Count.byField tableName field conn
|
||||
conn.CountByFields(tableName, Any, [ field ])
|
||||
|
||||
/// Determine if a document exists for the given ID
|
||||
[<Extension>]
|
||||
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
|
||||
[<Extension>]
|
||||
static member inline ExistsByFields(conn, tableName, howMatched, fields) =
|
||||
WithConn.Exists.byFields tableName howMatched fields conn
|
||||
|
||||
/// Determine if a document exists using a comparison on a JSON field
|
||||
[<Extension>]
|
||||
[<System.Obsolete "Use ExistsByFields instead; will be removed in v4">]
|
||||
static member inline ExistsByField(conn, tableName, field) =
|
||||
WithConn.Exists.byField tableName field conn
|
||||
conn.ExistsByFields(tableName, Any, [ field ])
|
||||
|
||||
/// Retrieve all documents in the given table
|
||||
[<Extension>]
|
||||
@@ -182,15 +230,27 @@ type SqliteConnectionCSharpExtensions =
|
||||
static member inline FindById<'TKey, 'TDoc when 'TDoc: null>(conn, tableName, docId: 'TKey) =
|
||||
WithConn.Find.ById<'TKey, 'TDoc>(tableName, docId, conn)
|
||||
|
||||
/// Retrieve documents via a comparison on JSON fields
|
||||
[<Extension>]
|
||||
static member inline FindByFields<'TDoc>(conn, tableName, howMatched, fields) =
|
||||
WithConn.Find.ByFields<'TDoc>(tableName, howMatched, fields, conn)
|
||||
|
||||
/// Retrieve documents via a comparison on a JSON field
|
||||
[<Extension>]
|
||||
[<System.Obsolete "Use FindByFields instead; will be removed in v4">]
|
||||
static member inline FindByField<'TDoc>(conn, tableName, field) =
|
||||
WithConn.Find.ByField<'TDoc>(tableName, field, conn)
|
||||
conn.FindByFields<'TDoc>(tableName, Any, [ field ])
|
||||
|
||||
/// Retrieve documents via a comparison on JSON fields, returning only the first result
|
||||
[<Extension>]
|
||||
static member inline FindFirstByFields<'TDoc when 'TDoc: null>(conn, tableName, howMatched, fields) =
|
||||
WithConn.Find.FirstByFields<'TDoc>(tableName, howMatched, fields, conn)
|
||||
|
||||
/// Retrieve documents via a comparison on a JSON field, returning only the first result
|
||||
[<Extension>]
|
||||
[<System.Obsolete "Use FindFirstByFields instead; will be removed in v4">]
|
||||
static member inline FindFirstByField<'TDoc when 'TDoc: null>(conn, tableName, field) =
|
||||
WithConn.Find.FirstByField<'TDoc>(tableName, field, conn)
|
||||
conn.FindFirstByFields<'TDoc>(tableName, Any, [ field ])
|
||||
|
||||
/// Update an entire document by its ID
|
||||
[<Extension>]
|
||||
@@ -207,27 +267,45 @@ type SqliteConnectionCSharpExtensions =
|
||||
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
|
||||
[<Extension>]
|
||||
static member inline PatchByFields<'TPatch>(conn, tableName, howMatched, fields, patch: 'TPatch) =
|
||||
WithConn.Patch.byFields tableName howMatched fields patch conn
|
||||
|
||||
/// Patch documents using a comparison on a JSON field
|
||||
[<Extension>]
|
||||
[<System.Obsolete "Use PatchByFields instead; will be removed in v4">]
|
||||
static member inline PatchByField<'TPatch>(conn, tableName, field, patch: 'TPatch) =
|
||||
WithConn.Patch.byField tableName field patch conn
|
||||
conn.PatchByFields(tableName, Any, [ field ], patch)
|
||||
|
||||
/// Remove fields from a document by the document's ID
|
||||
[<Extension>]
|
||||
static member inline RemoveFieldsById<'TKey>(conn, tableName, docId: 'TKey, fieldNames) =
|
||||
WithConn.RemoveFields.ById(tableName, docId, fieldNames, conn)
|
||||
WithConn.RemoveFields.byId tableName docId fieldNames conn
|
||||
|
||||
/// Remove fields from documents via a comparison on JSON fields in the document
|
||||
[<Extension>]
|
||||
static member inline RemoveFieldsByFields(conn, tableName, howMatched, fields, fieldNames) =
|
||||
WithConn.RemoveFields.byFields tableName howMatched fields fieldNames conn
|
||||
|
||||
/// Remove fields from documents via a comparison on a JSON field in the document
|
||||
[<Extension>]
|
||||
[<System.Obsolete "Use RemoveFieldsByFields instead; will be removed in v4">]
|
||||
static member inline RemoveFieldsByField(conn, tableName, field, fieldNames) =
|
||||
WithConn.RemoveFields.ByField(tableName, field, fieldNames, conn)
|
||||
conn.RemoveFieldsByFields(tableName, Any, [ field ], fieldNames)
|
||||
|
||||
/// Delete a document by its ID
|
||||
[<Extension>]
|
||||
static member inline DeleteById<'TKey>(conn, tableName, docId: 'TKey) =
|
||||
WithConn.Delete.byId tableName docId conn
|
||||
|
||||
/// Delete documents by matching a comparison on JSON fields
|
||||
[<Extension>]
|
||||
static member inline DeleteByFields(conn, tableName, howMatched, fields) =
|
||||
WithConn.Delete.byFields tableName howMatched fields conn
|
||||
|
||||
/// Delete documents by matching a comparison on a JSON field
|
||||
[<Extension>]
|
||||
[<System.Obsolete "Use DeleteByFields instead; will be removed in v4">]
|
||||
static member inline DeleteByField(conn, tableName, field) =
|
||||
WithConn.Delete.byField tableName field conn
|
||||
conn.DeleteByFields(tableName, Any, [ field ])
|
||||
|
||||
@@ -31,20 +31,47 @@ module Configuration =
|
||||
[<RequireQualifiedAccess>]
|
||||
module Query =
|
||||
|
||||
/// Create a WHERE clause fragment to implement a comparison on a field in a JSON document
|
||||
[<CompiledName "WhereByField">]
|
||||
let whereByField field paramName =
|
||||
let theRest =
|
||||
match field.Op with
|
||||
| EX | NEX -> ""
|
||||
| BT -> $" {paramName}min AND {paramName}max"
|
||||
| _ -> $" %s{paramName}"
|
||||
$"data->>'{field.Name}' {field.Op}{theRest}"
|
||||
/// Create a WHERE clause fragment to implement a comparison on fields in a JSON document
|
||||
[<CompiledName "WhereByFields">]
|
||||
let whereByFields (howMatched: FieldMatch) fields =
|
||||
let name = ParameterName()
|
||||
fields
|
||||
|> Seq.map (fun it ->
|
||||
match it.Op with
|
||||
| EX | NEX -> $"{it.Path SQLite} {it.Op}"
|
||||
| BT ->
|
||||
let p = name.Derive it.ParameterName
|
||||
$"{it.Path SQLite} {it.Op} {p}min AND {p}max"
|
||||
| _ -> $"{it.Path SQLite} {it.Op} {name.Derive it.ParameterName}")
|
||||
|> String.concat $" {howMatched} "
|
||||
|
||||
/// Create a WHERE clause fragment to implement an ID-based query
|
||||
[<CompiledName "WhereById">]
|
||||
let whereById paramName =
|
||||
whereByField (Field.EQ (Configuration.idField ()) 0) paramName
|
||||
whereByFields Any [ { Field.EQ (Configuration.idField ()) 0 with ParameterName = Some paramName } ]
|
||||
|
||||
/// Create an UPDATE statement to patch documents
|
||||
[<CompiledName "Patch">]
|
||||
let patch tableName =
|
||||
$"UPDATE %s{tableName} SET data = json_patch(data, json(@data))"
|
||||
|
||||
/// Create an UPDATE statement to remove fields from documents
|
||||
[<CompiledName "RemoveFields">]
|
||||
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
|
||||
[<CompiledName "ById">]
|
||||
let byId<'TKey> statement (docId: 'TKey) =
|
||||
Query.statementWhere
|
||||
statement
|
||||
(whereByFields Any [ { Field.EQ (Configuration.idField ()) docId with ParameterName = Some "@id" } ])
|
||||
|
||||
/// Create a query on JSON fields
|
||||
[<CompiledName "ByFields">]
|
||||
let byFields statement howMatched fields =
|
||||
Query.statementWhere statement (whereByFields howMatched fields)
|
||||
|
||||
/// Data definition
|
||||
module Definition =
|
||||
@@ -54,106 +81,6 @@ module Query =
|
||||
let ensureTable name =
|
||||
Query.Definition.ensureTableFor name "TEXT"
|
||||
|
||||
/// Query to update a document
|
||||
[<CompiledName "Update">]
|
||||
let update tableName =
|
||||
$"""UPDATE %s{tableName} SET data = @data WHERE {whereById "@id"}"""
|
||||
|
||||
/// Queries for counting documents
|
||||
module Count =
|
||||
|
||||
/// Query to count all documents in a table
|
||||
[<CompiledName "All">]
|
||||
let all tableName =
|
||||
$"SELECT COUNT(*) AS it FROM %s{tableName}"
|
||||
|
||||
/// Query to count matching documents using a text comparison on a JSON field
|
||||
[<CompiledName "ByField">]
|
||||
let byField tableName field =
|
||||
$"""SELECT COUNT(*) AS it FROM %s{tableName} WHERE {whereByField field "@field"}"""
|
||||
|
||||
/// Queries for determining document existence
|
||||
module Exists =
|
||||
|
||||
/// Query to determine if a document exists for the given ID
|
||||
[<CompiledName "ById">]
|
||||
let byId tableName =
|
||||
$"""SELECT EXISTS (SELECT 1 FROM %s{tableName} WHERE {whereById "@id"}) AS it"""
|
||||
|
||||
/// Query to determine if documents exist using a comparison on a JSON field
|
||||
[<CompiledName "ByField">]
|
||||
let byField tableName field =
|
||||
$"""SELECT EXISTS (SELECT 1 FROM %s{tableName} WHERE {whereByField field "@field"}) AS it"""
|
||||
|
||||
/// Queries for retrieving documents
|
||||
module Find =
|
||||
|
||||
/// Query to retrieve a document by its ID
|
||||
[<CompiledName "ById">]
|
||||
let byId tableName =
|
||||
$"""{Query.selectFromTable tableName} WHERE {whereById "@id"}"""
|
||||
|
||||
/// Query to retrieve documents using a comparison on a JSON field
|
||||
[<CompiledName "ByField">]
|
||||
let byField tableName field =
|
||||
$"""{Query.selectFromTable tableName} WHERE {whereByField field "@field"}"""
|
||||
|
||||
/// Document patching (partial update) queries
|
||||
module Patch =
|
||||
|
||||
/// Create an UPDATE statement to patch documents
|
||||
let internal update tableName whereClause =
|
||||
$"UPDATE %s{tableName} SET data = json_patch(data, json(@data)) WHERE %s{whereClause}"
|
||||
|
||||
/// Query to patch (partially update) a document by its ID
|
||||
[<CompiledName "ById">]
|
||||
let byId tableName =
|
||||
whereById "@id" |> update tableName
|
||||
|
||||
/// Query to patch (partially update) a document via a comparison on a JSON field
|
||||
[<CompiledName "ByField">]
|
||||
let byField tableName field =
|
||||
whereByField field "@field" |> update tableName
|
||||
|
||||
/// Queries to remove fields from documents
|
||||
module RemoveFields =
|
||||
|
||||
/// Create an UPDATE statement to remove parameters
|
||||
let internal update tableName (parameters: SqliteParameter list) whereClause =
|
||||
let paramNames = parameters |> List.map _.ParameterName |> String.concat ", "
|
||||
$"UPDATE %s{tableName} SET data = json_remove(data, {paramNames}) WHERE {whereClause}"
|
||||
|
||||
/// Query to remove fields from a document by the document's ID
|
||||
[<CompiledName "FSharpById">]
|
||||
let byId tableName parameters =
|
||||
whereById "@id" |> update tableName parameters
|
||||
|
||||
/// Query to remove fields from a document by the document's ID
|
||||
let ById(tableName, parameters) =
|
||||
byId tableName (List.ofSeq parameters)
|
||||
|
||||
/// Query to remove fields from documents via a comparison on a JSON field within the document
|
||||
[<CompiledName "FSharpByField">]
|
||||
let byField tableName field parameters =
|
||||
whereByField field "@field" |> update tableName parameters
|
||||
|
||||
/// Query to remove fields from documents via a comparison on a JSON field within the document
|
||||
let ByField(tableName, field, parameters) =
|
||||
byField tableName field (List.ofSeq parameters)
|
||||
|
||||
/// Queries to delete documents
|
||||
module Delete =
|
||||
|
||||
/// Query to delete a document by its ID
|
||||
[<CompiledName "ById">]
|
||||
let byId tableName =
|
||||
$"""DELETE FROM %s{tableName} WHERE {whereById "@id"}"""
|
||||
|
||||
/// Query to delete documents using a comparison on a JSON field
|
||||
[<CompiledName "ByField">]
|
||||
let byField tableName field =
|
||||
$"""DELETE FROM %s{tableName} WHERE {whereByField field "@field"}"""
|
||||
|
||||
|
||||
/// Parameter handling helpers
|
||||
[<AutoOpen>]
|
||||
@@ -169,39 +96,39 @@ module Parameters =
|
||||
let jsonParam name (it: 'TJson) =
|
||||
SqliteParameter(name, Configuration.serializer().Serialize it)
|
||||
|
||||
/// Create a JSON field parameter (name "@field")
|
||||
[<CompiledName "FSharpAddField">]
|
||||
let addFieldParam name field parameters =
|
||||
match field.Op with
|
||||
| EX | NEX -> parameters
|
||||
/// Create JSON field parameters
|
||||
[<CompiledName "AddFields">]
|
||||
let addFieldParams fields parameters =
|
||||
let name = ParameterName()
|
||||
fields
|
||||
|> Seq.map (fun it ->
|
||||
seq {
|
||||
match it.Op with
|
||||
| EX | NEX -> ()
|
||||
| BT ->
|
||||
let values = field.Value :?> obj list
|
||||
SqliteParameter($"{name}min", values[0]) :: SqliteParameter($"{name}max", values[1]) :: parameters
|
||||
| _ -> SqliteParameter(name, field.Value) :: parameters
|
||||
|
||||
/// Create a JSON field parameter (name "@field")
|
||||
let AddField(name, field, parameters) =
|
||||
match field.Op with
|
||||
| EX | NEX -> parameters
|
||||
| BT ->
|
||||
let values = field.Value :?> obj list
|
||||
// let min = SqliteParameter($"{name}min", SqliteType.Integer)
|
||||
// min.Value <- values[0]
|
||||
// let max = SqliteParameter($"{name}max", SqliteType.Integer)
|
||||
// max.Value <- values[1]
|
||||
[ SqliteParameter($"{name}min", values[0]); SqliteParameter($"{name}max", values[1]) ]
|
||||
// [min; max]
|
||||
let p = name.Derive it.ParameterName
|
||||
let values = it.Value :?> obj list
|
||||
yield SqliteParameter($"{p}min", List.head values)
|
||||
yield SqliteParameter($"{p}max", List.last values)
|
||||
| _ -> yield SqliteParameter(name.Derive it.ParameterName, it.Value) })
|
||||
|> Seq.collect id
|
||||
|> Seq.append parameters
|
||||
| _ -> SqliteParameter(name, field.Value) |> Seq.singleton |> Seq.append parameters
|
||||
|> Seq.toList
|
||||
|> Seq.ofList
|
||||
|
||||
/// Create a JSON field parameter (name "@field")
|
||||
[<CompiledName "AddField">]
|
||||
[<System.Obsolete "Use addFieldParams instead; will be removed in v4">]
|
||||
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
|
||||
[<CompiledName "FSharpFieldNames">]
|
||||
let fieldNameParams paramName (fieldNames: string list) =
|
||||
fieldNames |> List.mapi (fun idx name -> SqliteParameter($"%s{paramName}{idx}", $"$.{name}"))
|
||||
|
||||
/// Append JSON field name parameters for the given field names to the given parameters
|
||||
let FieldNames(paramName, fieldNames: string seq) =
|
||||
fieldNames |> Seq.mapi (fun idx name -> SqliteParameter($"%s{paramName}{idx}", $"$.{name}"))
|
||||
[<CompiledName "FieldNames">]
|
||||
let fieldNameParams paramName fieldNames =
|
||||
fieldNames
|
||||
|> Seq.mapi (fun idx name -> SqliteParameter($"%s{paramName}{idx}", $"$.%s{name}"))
|
||||
|> Seq.toList
|
||||
|> Seq.ofList
|
||||
|
||||
/// An empty parameter sequence
|
||||
[<CompiledName "None">]
|
||||
@@ -323,13 +250,13 @@ module WithConn =
|
||||
[<CompiledName "EnsureTable">]
|
||||
let ensureTable name conn = backgroundTask {
|
||||
do! Custom.nonQuery (Query.Definition.ensureTable name) [] conn
|
||||
do! Custom.nonQuery (Query.Definition.ensureKey name) [] conn
|
||||
do! Custom.nonQuery (Query.Definition.ensureKey name SQLite) [] conn
|
||||
}
|
||||
|
||||
/// Create an index on a document table
|
||||
[<CompiledName "EnsureFieldIndex">]
|
||||
let ensureFieldIndex tableName indexName fields conn =
|
||||
Custom.nonQuery (Query.Definition.ensureIndexOn tableName indexName fields) [] conn
|
||||
Custom.nonQuery (Query.Definition.ensureIndexOn tableName indexName fields SQLite) [] conn
|
||||
|
||||
/// Insert a new document
|
||||
[<CompiledName "Insert">]
|
||||
@@ -348,12 +275,13 @@ module WithConn =
|
||||
/// Count all documents in a table
|
||||
[<CompiledName "All">]
|
||||
let all tableName conn =
|
||||
Custom.scalar (Query.Count.all tableName) [] toCount conn
|
||||
Custom.scalar (Query.count tableName) [] toCount conn
|
||||
|
||||
/// Count matching documents using a comparison on a JSON field
|
||||
[<CompiledName "ByField">]
|
||||
let byField tableName field conn =
|
||||
Custom.scalar (Query.Count.byField tableName field) (addFieldParam "@field" field []) toCount conn
|
||||
/// Count matching documents using a comparison on JSON fields
|
||||
[<CompiledName "ByFields">]
|
||||
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
|
||||
[<RequireQualifiedAccess>]
|
||||
@@ -362,12 +290,16 @@ module WithConn =
|
||||
/// Determine if a document exists for the given ID
|
||||
[<CompiledName "ById">]
|
||||
let byId tableName (docId: 'TKey) conn =
|
||||
Custom.scalar (Query.Exists.byId tableName) [ idParam docId ] toExists conn
|
||||
Custom.scalar (Query.exists tableName (Query.whereById "@id")) [ idParam docId ] toExists conn
|
||||
|
||||
/// Determine if a document exists using a comparison on a JSON field
|
||||
[<CompiledName "ByField">]
|
||||
let byField tableName field conn =
|
||||
Custom.scalar (Query.Exists.byField tableName field) (addFieldParam "@field" field []) toExists conn
|
||||
/// Determine if a document exists using a comparison on JSON fields
|
||||
[<CompiledName "ByFields">]
|
||||
let byFields tableName howMatched fields conn =
|
||||
Custom.scalar
|
||||
(Query.exists tableName (Query.whereByFields howMatched fields))
|
||||
(addFieldParams fields [])
|
||||
toExists
|
||||
conn
|
||||
|
||||
/// Commands to retrieve documents
|
||||
[<RequireQualifiedAccess>]
|
||||
@@ -376,42 +308,76 @@ module WithConn =
|
||||
/// Retrieve all documents in the given table
|
||||
[<CompiledName "FSharpAll">]
|
||||
let all<'TDoc> tableName conn =
|
||||
Custom.list<'TDoc> (Query.selectFromTable tableName) [] fromData<'TDoc> conn
|
||||
Custom.list<'TDoc> (Query.find tableName) [] fromData<'TDoc> conn
|
||||
|
||||
/// Retrieve all documents in the given table
|
||||
let All<'TDoc>(tableName, conn) =
|
||||
Custom.List(Query.selectFromTable tableName, [], fromData<'TDoc>, conn)
|
||||
Custom.List(Query.find tableName, [], fromData<'TDoc>, conn)
|
||||
|
||||
/// Retrieve a document by its ID (returns None if not found)
|
||||
[<CompiledName "FSharpById">]
|
||||
let byId<'TKey, 'TDoc> tableName (docId: 'TKey) conn =
|
||||
Custom.single<'TDoc> (Query.Find.byId tableName) [ idParam docId ] fromData<'TDoc> 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)
|
||||
let ById<'TKey, 'TDoc when 'TDoc: null>(tableName, docId: 'TKey, conn) =
|
||||
Custom.Single<'TDoc>(Query.Find.byId tableName, [ idParam docId ], fromData<'TDoc>, conn)
|
||||
Custom.Single<'TDoc>(Query.byId (Query.find tableName) docId, [ idParam docId ], fromData<'TDoc>, conn)
|
||||
|
||||
/// Retrieve documents via a comparison on JSON fields
|
||||
[<CompiledName "FSharpByFields">]
|
||||
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 a JSON field
|
||||
[<CompiledName "FSharpByField">]
|
||||
[<System.Obsolete "Use byFields instead; will be removed in v4">]
|
||||
let byField<'TDoc> tableName field conn =
|
||||
Custom.list<'TDoc>
|
||||
(Query.Find.byField tableName field) (addFieldParam "@field" field []) fromData<'TDoc> conn
|
||||
byFields<'TDoc> tableName Any [ field ] conn
|
||||
|
||||
/// Retrieve documents via a comparison on JSON 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 a JSON field
|
||||
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||
let ByField<'TDoc>(tableName, field, conn) =
|
||||
Custom.List<'TDoc>(
|
||||
Query.Find.byField tableName field, addFieldParam "@field" field [], fromData<'TDoc>, conn)
|
||||
ByFields<'TDoc>(tableName, Any, [ field ], conn)
|
||||
|
||||
/// Retrieve documents via a comparison on JSON fields, returning only the first result
|
||||
[<CompiledName "FSharpFirstByFields">]
|
||||
let firstByFields<'TDoc> 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 a JSON field, returning only the first result
|
||||
[<CompiledName "FSharpFirstByField">]
|
||||
[<System.Obsolete "Use firstByFields instead; will be removed in v4">]
|
||||
let firstByField<'TDoc> tableName field conn =
|
||||
Custom.single
|
||||
$"{Query.Find.byField tableName field} LIMIT 1" (addFieldParam "@field" field []) fromData<'TDoc> conn
|
||||
firstByFields<'TDoc> tableName Any [ field ] conn
|
||||
|
||||
/// Retrieve documents via a comparison on JSON fields, returning only the first result
|
||||
let FirstByFields<'TDoc when 'TDoc: null>(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 a JSON field, returning only the first result
|
||||
[<System.Obsolete "Use FirstByFields instead; will be removed in v4">]
|
||||
let FirstByField<'TDoc when 'TDoc: null>(tableName, field, conn) =
|
||||
Custom.Single(
|
||||
$"{Query.Find.byField tableName field} LIMIT 1", addFieldParam "@field" field [], fromData<'TDoc>, conn)
|
||||
FirstByFields(tableName, Any, [ field ], conn)
|
||||
|
||||
/// Commands to update documents
|
||||
[<RequireQualifiedAccess>]
|
||||
@@ -420,7 +386,10 @@ module WithConn =
|
||||
/// Update an entire document by its ID
|
||||
[<CompiledName "ById">]
|
||||
let byId tableName (docId: 'TKey) (document: 'TDoc) conn =
|
||||
Custom.nonQuery (Query.update tableName) [ idParam docId; jsonParam "@data" document ] conn
|
||||
Custom.nonQuery
|
||||
(Query.statementWhere (Query.update tableName) (Query.whereById "@id"))
|
||||
[ idParam docId; jsonParam "@data" document ]
|
||||
conn
|
||||
|
||||
/// Update an entire document by its ID, using the provided function to obtain the ID from the document
|
||||
[<CompiledName "FSharpByFunc">]
|
||||
@@ -438,38 +407,50 @@ module WithConn =
|
||||
/// Patch a document by its ID
|
||||
[<CompiledName "ById">]
|
||||
let byId tableName (docId: 'TKey) (patch: 'TPatch) conn =
|
||||
Custom.nonQuery (Query.Patch.byId tableName) [ idParam docId; jsonParam "@data" patch ] conn
|
||||
Custom.nonQuery
|
||||
(Query.byId (Query.patch tableName) docId) [ idParam docId; jsonParam "@data" patch ] conn
|
||||
|
||||
/// Patch documents using a comparison on JSON fields
|
||||
[<CompiledName "ByFields">]
|
||||
let byFields tableName howMatched fields (patch: 'TPatch) conn =
|
||||
Custom.nonQuery
|
||||
(Query.byFields (Query.patch tableName) howMatched fields)
|
||||
(addFieldParams fields [ jsonParam "@data" patch ])
|
||||
conn
|
||||
|
||||
/// Patch documents using a comparison on a JSON field
|
||||
[<CompiledName "ByField">]
|
||||
let byField tableName field (patch: 'TPatch) (conn: SqliteConnection) =
|
||||
Custom.nonQuery
|
||||
(Query.Patch.byField tableName field) (addFieldParam "@field" field [ jsonParam "@data" patch ]) conn
|
||||
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||
let byField tableName field (patch: 'TPatch) conn =
|
||||
byFields tableName Any [ field ] patch conn
|
||||
|
||||
/// Commands to remove fields from documents
|
||||
[<RequireQualifiedAccess>]
|
||||
module RemoveFields =
|
||||
|
||||
/// Remove fields from a document by the document's ID
|
||||
[<CompiledName "FSharpById">]
|
||||
[<CompiledName "ById">]
|
||||
let byId tableName (docId: 'TKey) fieldNames conn =
|
||||
let nameParams = fieldNameParams "@name" fieldNames
|
||||
Custom.nonQuery (Query.RemoveFields.byId tableName nameParams) (idParam docId :: nameParams) conn
|
||||
Custom.nonQuery
|
||||
(Query.byId (Query.removeFields tableName nameParams) docId)
|
||||
(idParam docId |> Seq.singleton |> Seq.append nameParams)
|
||||
conn
|
||||
|
||||
/// Remove fields from a document by the document's ID
|
||||
let ById(tableName, docId: 'TKey, fieldNames, conn) =
|
||||
byId tableName docId (List.ofSeq fieldNames) conn
|
||||
|
||||
/// Remove fields from documents via a comparison on a JSON field in the document
|
||||
[<CompiledName "FSharpByField">]
|
||||
let byField tableName field fieldNames conn =
|
||||
/// Remove fields from documents via a comparison on JSON fields in the document
|
||||
[<CompiledName "ByFields">]
|
||||
let byFields tableName howMatched fields fieldNames conn =
|
||||
let nameParams = fieldNameParams "@name" fieldNames
|
||||
Custom.nonQuery
|
||||
(Query.RemoveFields.byField tableName field nameParams) (addFieldParam "@field" field nameParams) conn
|
||||
(Query.byFields (Query.removeFields tableName nameParams) howMatched fields)
|
||||
(addFieldParams fields nameParams)
|
||||
conn
|
||||
|
||||
/// Remove fields from documents via a comparison on a JSON field in the document
|
||||
let ByField(tableName, field, fieldNames, conn) =
|
||||
byField tableName field (List.ofSeq fieldNames) conn
|
||||
[<CompiledName "ByField">]
|
||||
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||
let byField tableName field fieldNames conn =
|
||||
byFields tableName Any [ field ] fieldNames conn
|
||||
|
||||
/// Commands to delete documents
|
||||
[<RequireQualifiedAccess>]
|
||||
@@ -478,12 +459,18 @@ module WithConn =
|
||||
/// Delete a document by its ID
|
||||
[<CompiledName "ById">]
|
||||
let byId tableName (docId: 'TKey) conn =
|
||||
Custom.nonQuery (Query.Delete.byId tableName) [ idParam docId ] conn
|
||||
Custom.nonQuery (Query.byId (Query.delete tableName) docId) [ idParam docId ] conn
|
||||
|
||||
/// Delete documents by matching a comparison on JSON fields
|
||||
[<CompiledName "ByFields">]
|
||||
let byFields tableName howMatched fields conn =
|
||||
Custom.nonQuery (Query.byFields (Query.delete tableName) howMatched fields) (addFieldParams fields []) conn
|
||||
|
||||
/// Delete documents by matching a comparison on a JSON field
|
||||
[<CompiledName "ByField">]
|
||||
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||
let byField tableName field conn =
|
||||
Custom.nonQuery (Query.Delete.byField tableName field) (addFieldParam "@field" field []) conn
|
||||
byFields tableName Any [ field ] conn
|
||||
|
||||
|
||||
/// Commands to execute custom SQL queries
|
||||
@@ -571,11 +558,17 @@ module Count =
|
||||
use conn = Configuration.dbConn ()
|
||||
WithConn.Count.all tableName conn
|
||||
|
||||
/// Count matching documents using a comparison on JSON fields
|
||||
[<CompiledName "ByFields">]
|
||||
let byFields tableName howMatched fields =
|
||||
use conn = Configuration.dbConn ()
|
||||
WithConn.Count.byFields tableName howMatched fields conn
|
||||
|
||||
/// Count matching documents using a comparison on a JSON field
|
||||
[<CompiledName "ByField">]
|
||||
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||
let byField tableName field =
|
||||
use conn = Configuration.dbConn ()
|
||||
WithConn.Count.byField tableName field conn
|
||||
byFields tableName Any [ field ]
|
||||
|
||||
/// Commands to determine if documents exist
|
||||
[<RequireQualifiedAccess>]
|
||||
@@ -587,11 +580,17 @@ module Exists =
|
||||
use conn = Configuration.dbConn ()
|
||||
WithConn.Exists.byId tableName docId conn
|
||||
|
||||
/// Determine if a document exists using a comparison on JSON fields
|
||||
[<CompiledName "ByFields">]
|
||||
let byFields tableName howMatched fields =
|
||||
use conn = Configuration.dbConn ()
|
||||
WithConn.Exists.byFields tableName howMatched fields conn
|
||||
|
||||
/// Determine if a document exists using a comparison on a JSON field
|
||||
[<CompiledName "ByField">]
|
||||
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||
let byField tableName field =
|
||||
use conn = Configuration.dbConn ()
|
||||
WithConn.Exists.byField tableName field conn
|
||||
byFields tableName Any [ field ]
|
||||
|
||||
/// Commands to determine if documents exist
|
||||
[<RequireQualifiedAccess>]
|
||||
@@ -619,27 +618,49 @@ module Find =
|
||||
use conn = Configuration.dbConn ()
|
||||
WithConn.Find.ById<'TKey, 'TDoc>(tableName, docId, conn)
|
||||
|
||||
/// Retrieve documents via a comparison on a JSON field
|
||||
[<CompiledName "FSharpByField">]
|
||||
let byField<'TDoc> tableName field =
|
||||
/// Retrieve documents via a comparison on JSON fields
|
||||
[<CompiledName "FSharpByFields">]
|
||||
let byFields<'TDoc> tableName howMatched fields =
|
||||
use conn = Configuration.dbConn ()
|
||||
WithConn.Find.byField<'TDoc> tableName field conn
|
||||
WithConn.Find.byFields<'TDoc> tableName howMatched fields conn
|
||||
|
||||
/// Retrieve documents via a comparison on a JSON field
|
||||
let ByField<'TDoc>(tableName, field) =
|
||||
[<CompiledName "FSharpByField">]
|
||||
[<System.Obsolete "Use byFields instead; will be removed in v4">]
|
||||
let byField<'TDoc> tableName field =
|
||||
byFields tableName Any [ field ]
|
||||
|
||||
/// Retrieve documents via a comparison on JSON fields
|
||||
let ByFields<'TDoc>(tableName, howMatched, fields) =
|
||||
use conn = Configuration.dbConn ()
|
||||
WithConn.Find.ByField<'TDoc>(tableName, field, conn)
|
||||
WithConn.Find.ByFields<'TDoc>(tableName, howMatched, fields, conn)
|
||||
|
||||
/// Retrieve documents via a comparison on a JSON field
|
||||
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||
let ByField<'TDoc>(tableName, field) =
|
||||
ByFields<'TDoc>(tableName, Any, [ field ])
|
||||
|
||||
/// Retrieve documents via a comparison on JSON fields, returning only the first result
|
||||
[<CompiledName "FSharpFirstByFields">]
|
||||
let firstByFields<'TDoc> tableName howMatched fields =
|
||||
use conn = Configuration.dbConn ()
|
||||
WithConn.Find.firstByFields<'TDoc> tableName howMatched fields conn
|
||||
|
||||
/// Retrieve documents via a comparison on a JSON field, returning only the first result
|
||||
[<CompiledName "FSharpFirstByField">]
|
||||
[<System.Obsolete "Use firstByFields instead; will be removed in v4">]
|
||||
let firstByField<'TDoc> tableName field =
|
||||
firstByFields<'TDoc> tableName Any [ field ]
|
||||
|
||||
/// Retrieve documents via a comparison on JSON fields, returning only the first result
|
||||
let FirstByFields<'TDoc when 'TDoc: null>(tableName, howMatched, fields) =
|
||||
use conn = Configuration.dbConn ()
|
||||
WithConn.Find.firstByField<'TDoc> tableName field conn
|
||||
WithConn.Find.FirstByFields<'TDoc>(tableName, howMatched, fields, conn)
|
||||
|
||||
/// Retrieve documents via a comparison on a JSON field, returning only the first result
|
||||
[<System.Obsolete "Use FirstByFields instead; will be removed in v4">]
|
||||
let FirstByField<'TDoc when 'TDoc: null>(tableName, field) =
|
||||
use conn = Configuration.dbConn ()
|
||||
WithConn.Find.FirstByField<'TDoc>(tableName, field, conn)
|
||||
FirstByFields<'TDoc>(tableName, Any, [ field ])
|
||||
|
||||
/// Commands to update documents
|
||||
[<RequireQualifiedAccess>]
|
||||
@@ -672,37 +693,39 @@ module Patch =
|
||||
use conn = Configuration.dbConn ()
|
||||
WithConn.Patch.byId tableName docId patch conn
|
||||
|
||||
/// Patch documents using a comparison on JSON fields in the WHERE clause
|
||||
[<CompiledName "ByFields">]
|
||||
let byFields tableName howMatched fields (patch: 'TPatch) =
|
||||
use conn = Configuration.dbConn ()
|
||||
WithConn.Patch.byFields tableName howMatched fields patch conn
|
||||
|
||||
/// Patch documents using a comparison on a JSON field in the WHERE clause
|
||||
[<CompiledName "ByField">]
|
||||
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||
let byField tableName field (patch: 'TPatch) =
|
||||
use conn = Configuration.dbConn ()
|
||||
WithConn.Patch.byField tableName field patch conn
|
||||
byFields tableName Any [ field ] patch
|
||||
|
||||
/// Commands to remove fields from documents
|
||||
[<RequireQualifiedAccess>]
|
||||
module RemoveFields =
|
||||
|
||||
/// Remove fields from a document by the document's ID
|
||||
[<CompiledName "FSharpById">]
|
||||
[<CompiledName "ById">]
|
||||
let byId tableName (docId: 'TKey) fieldNames =
|
||||
use conn = Configuration.dbConn ()
|
||||
WithConn.RemoveFields.byId tableName docId fieldNames conn
|
||||
|
||||
/// Remove fields from a document by the document's ID
|
||||
let ById(tableName, docId: 'TKey, fieldNames) =
|
||||
/// Remove field from documents via a comparison on JSON fields in the document
|
||||
[<CompiledName "ByFields">]
|
||||
let byFields tableName howMatched fields fieldNames =
|
||||
use conn = Configuration.dbConn ()
|
||||
WithConn.RemoveFields.ById(tableName, docId, fieldNames, conn)
|
||||
WithConn.RemoveFields.byFields tableName howMatched fields fieldNames conn
|
||||
|
||||
/// Remove field from documents via a comparison on a JSON field in the document
|
||||
[<CompiledName "FSharpByField">]
|
||||
[<CompiledName "ByField">]
|
||||
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||
let byField tableName field fieldNames =
|
||||
use conn = Configuration.dbConn ()
|
||||
WithConn.RemoveFields.byField tableName field fieldNames conn
|
||||
|
||||
/// Remove field from documents via a comparison on a JSON field in the document
|
||||
let ByField(tableName, field, fieldNames) =
|
||||
use conn = Configuration.dbConn ()
|
||||
WithConn.RemoveFields.ByField(tableName, field, fieldNames, conn)
|
||||
byFields tableName Any [ field ] fieldNames
|
||||
|
||||
/// Commands to delete documents
|
||||
[<RequireQualifiedAccess>]
|
||||
@@ -714,8 +737,14 @@ module Delete =
|
||||
use conn = Configuration.dbConn ()
|
||||
WithConn.Delete.byId tableName docId conn
|
||||
|
||||
/// Delete documents by matching a comparison on JSON fields
|
||||
[<CompiledName "ByFields">]
|
||||
let byFields tableName howMatched fields =
|
||||
use conn = Configuration.dbConn ()
|
||||
WithConn.Delete.byFields tableName howMatched fields conn
|
||||
|
||||
/// Delete documents by matching a comparison on a JSON field
|
||||
[<CompiledName "ByField">]
|
||||
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||
let byField tableName field =
|
||||
use conn = Configuration.dbConn ()
|
||||
WithConn.Delete.byField tableName field conn
|
||||
byFields tableName Any [ field ]
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Expecto.CSharp;
|
||||
using Expecto;
|
||||
using Microsoft.FSharp.Collections;
|
||||
using Microsoft.FSharp.Core;
|
||||
|
||||
namespace BitBadger.Documents.Tests.CSharp;
|
||||
|
||||
@@ -24,11 +25,11 @@ public static class CommonCSharpTests
|
||||
/// Unit tests
|
||||
/// </summary>
|
||||
[Tests]
|
||||
public static readonly Test Unit = TestList("Common.C# Unit", new[]
|
||||
{
|
||||
public static readonly Test Unit = TestList("Common.C# Unit",
|
||||
[
|
||||
TestSequenced(
|
||||
TestList("Configuration", new[]
|
||||
{
|
||||
TestList("Configuration",
|
||||
[
|
||||
TestCase("UseSerializer succeeds", () =>
|
||||
{
|
||||
try
|
||||
@@ -70,9 +71,9 @@ public static class CommonCSharpTests
|
||||
Configuration.UseIdField("Id");
|
||||
}
|
||||
})
|
||||
})),
|
||||
TestList("Op", new[]
|
||||
{
|
||||
])),
|
||||
TestList("Op",
|
||||
[
|
||||
TestCase("EQ succeeds", () =>
|
||||
{
|
||||
Expect.equal(Op.EQ.ToString(), "=", "The equals operator was not correct");
|
||||
@@ -109,9 +110,9 @@ public static class CommonCSharpTests
|
||||
{
|
||||
Expect.equal(Op.NEX.ToString(), "IS NULL", "The \"not exists\" operator was not correct");
|
||||
})
|
||||
}),
|
||||
TestList("Field", new[]
|
||||
{
|
||||
]),
|
||||
TestList("Field",
|
||||
[
|
||||
TestCase("EQ succeeds", () =>
|
||||
{
|
||||
var field = Field.EQ("Test", 14);
|
||||
@@ -159,7 +160,7 @@ public static class CommonCSharpTests
|
||||
var field = Field.BT("Age", 18, 49);
|
||||
Expect.equal(field.Name, "Age", "Field name incorrect");
|
||||
Expect.equal(field.Op, Op.BT, "Operator incorrect");
|
||||
Expect.equal(((FSharpList<object>)field.Value).ToArray(), new object[] { 18, 49 }, "Value incorrect");
|
||||
Expect.equal(((FSharpList<object>)field.Value).ToArray(), [18, 49], "Value incorrect");
|
||||
}),
|
||||
TestCase("EX succeeds", () =>
|
||||
{
|
||||
@@ -172,48 +173,159 @@ public static class CommonCSharpTests
|
||||
var field = Field.NEX("Rad");
|
||||
Expect.equal(field.Name, "Rad", "Field name incorrect");
|
||||
Expect.equal(field.Op, Op.NEX, "Operator incorrect");
|
||||
}),
|
||||
TestCase("WithParameterName succeeds", () =>
|
||||
{
|
||||
var field = Field.EQ("Bob", "Tom").WithParameterName("@name");
|
||||
Expect.isSome(field.ParameterName, "The parameter name should have been filled");
|
||||
Expect.equal("@name", field.ParameterName.Value, "The parameter name is incorrect");
|
||||
}),
|
||||
TestCase("WithQualifier succeeds", () =>
|
||||
{
|
||||
var field = Field.EQ("Bill", "Matt").WithQualifier("joe");
|
||||
Expect.isSome(field.Qualifier, "The table qualifier should have been filled");
|
||||
Expect.equal("joe", field.Qualifier.Value, "The table qualifier is incorrect");
|
||||
}),
|
||||
TestList("Path",
|
||||
[
|
||||
TestCase("succeeds for a PostgreSQL single field with no qualifier", () =>
|
||||
{
|
||||
var field = Field.GE("SomethingCool", 18);
|
||||
Expect.equal("data->>'SomethingCool'", field.Path(Dialect.PostgreSQL),
|
||||
"The PostgreSQL path is incorrect");
|
||||
}),
|
||||
TestCase("succeeds for a PostgreSQL single field with a qualifier", () =>
|
||||
{
|
||||
var field = Field.LT("SomethingElse", 9).WithQualifier("this");
|
||||
Expect.equal("this.data->>'SomethingElse'", field.Path(Dialect.PostgreSQL),
|
||||
"The PostgreSQL path is incorrect");
|
||||
}),
|
||||
TestCase("succeeds for a PostgreSQL nested field with no qualifier", () =>
|
||||
{
|
||||
var field = Field.EQ("My.Nested.Field", "howdy");
|
||||
Expect.equal("data#>>'{My,Nested,Field}'", field.Path(Dialect.PostgreSQL),
|
||||
"The PostgreSQL path is incorrect");
|
||||
}),
|
||||
TestCase("succeeds for a PostgreSQL nested field with a qualifier", () =>
|
||||
{
|
||||
var field = Field.EQ("Nest.Away", "doc").WithQualifier("bird");
|
||||
Expect.equal("bird.data#>>'{Nest,Away}'", field.Path(Dialect.PostgreSQL),
|
||||
"The PostgreSQL path is incorrect");
|
||||
}),
|
||||
TestCase("succeeds for a SQLite single field with no qualifier", () =>
|
||||
{
|
||||
var field = Field.GE("SomethingCool", 18);
|
||||
Expect.equal("data->>'SomethingCool'", field.Path(Dialect.SQLite), "The SQLite path is incorrect");
|
||||
}),
|
||||
TestCase("succeeds for a SQLite single field with a qualifier", () =>
|
||||
{
|
||||
var field = Field.LT("SomethingElse", 9).WithQualifier("this");
|
||||
Expect.equal("this.data->>'SomethingElse'", field.Path(Dialect.SQLite),
|
||||
"The SQLite path is incorrect");
|
||||
}),
|
||||
TestCase("succeeds for a SQLite nested field with no qualifier", () =>
|
||||
{
|
||||
var field = Field.EQ("My.Nested.Field", "howdy");
|
||||
Expect.equal("data->>'My'->>'Nested'->>'Field'", field.Path(Dialect.SQLite),
|
||||
"The SQLite path is incorrect");
|
||||
}),
|
||||
TestCase("succeeds for a SQLite nested field with a qualifier", () =>
|
||||
{
|
||||
var field = Field.EQ("Nest.Away", "doc").WithQualifier("bird");
|
||||
Expect.equal("bird.data->>'Nest'->>'Away'", field.Path(Dialect.SQLite),
|
||||
"The SQLite path is incorrect");
|
||||
})
|
||||
])
|
||||
]),
|
||||
TestList("FieldMatch.ToString",
|
||||
[
|
||||
TestCase("succeeds for Any", () =>
|
||||
{
|
||||
Expect.equal(FieldMatch.Any.ToString(), "OR", "SQL for Any is incorrect");
|
||||
}),
|
||||
TestList("Query", new[]
|
||||
TestCase("succeeds for All", () =>
|
||||
{
|
||||
TestCase("SelectFromTable succeeds", () =>
|
||||
Expect.equal(FieldMatch.All.ToString(), "AND", "SQL for All is incorrect");
|
||||
})
|
||||
]),
|
||||
TestList("ParameterName.Derive",
|
||||
[
|
||||
TestCase("succeeds with existing name", () =>
|
||||
{
|
||||
Expect.equal(Query.SelectFromTable("test.table"), "SELECT data FROM test.table",
|
||||
"SELECT statement not correct");
|
||||
ParameterName name = new();
|
||||
Expect.equal(name.Derive(FSharpOption<string>.Some("@taco")), "@taco", "Name should have been @taco");
|
||||
Expect.equal(name.Derive(FSharpOption<string>.None), "@field0",
|
||||
"Counter should not have advanced for named field");
|
||||
}),
|
||||
TestList("Definition", new[]
|
||||
TestCase("Derive succeeds with non-existent name", () =>
|
||||
{
|
||||
ParameterName name = new();
|
||||
Expect.equal(name.Derive(FSharpOption<string>.None), "@field0",
|
||||
"Anonymous field name should have been returned");
|
||||
Expect.equal(name.Derive(FSharpOption<string>.None), "@field1",
|
||||
"Counter should have advanced from previous call");
|
||||
Expect.equal(name.Derive(FSharpOption<string>.None), "@field2",
|
||||
"Counter should have advanced from previous call");
|
||||
Expect.equal(name.Derive(FSharpOption<string>.None), "@field3",
|
||||
"Counter should have advanced from previous call");
|
||||
})
|
||||
]),
|
||||
TestList("Query",
|
||||
[
|
||||
TestCase("StatementWhere succeeds", () =>
|
||||
{
|
||||
Expect.equal(Query.StatementWhere("q", "r"), "q WHERE r", "Statements not combined correctly");
|
||||
}),
|
||||
TestList("Definition",
|
||||
[
|
||||
TestCase("EnsureTableFor succeeds", () =>
|
||||
{
|
||||
Expect.equal(Query.Definition.EnsureTableFor("my.table", "JSONB"),
|
||||
"CREATE TABLE IF NOT EXISTS my.table (data JSONB NOT NULL)",
|
||||
"CREATE TABLE statement not constructed correctly");
|
||||
}),
|
||||
TestList("EnsureKey", new[]
|
||||
{
|
||||
TestList("EnsureKey",
|
||||
[
|
||||
TestCase("succeeds when a schema is present", () =>
|
||||
{
|
||||
Expect.equal(Query.Definition.EnsureKey("test.table"),
|
||||
Expect.equal(Query.Definition.EnsureKey("test.table", Dialect.SQLite),
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data->>'Id'))",
|
||||
"CREATE INDEX for key statement with schema not constructed correctly");
|
||||
}),
|
||||
TestCase("succeeds when a schema is not present", () =>
|
||||
{
|
||||
Expect.equal(Query.Definition.EnsureKey("table"),
|
||||
Expect.equal(Query.Definition.EnsureKey("table", Dialect.PostgreSQL),
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON table ((data->>'Id'))",
|
||||
"CREATE INDEX for key statement without schema not constructed correctly");
|
||||
})
|
||||
}),
|
||||
TestCase("EnsureIndexOn succeeds for multiple fields and directions", () =>
|
||||
]),
|
||||
TestList("EnsureIndexOn",
|
||||
[
|
||||
TestCase("succeeds for multiple fields and directions", () =>
|
||||
{
|
||||
Expect.equal(
|
||||
Query.Definition.EnsureIndexOn("test.table", "gibberish",
|
||||
new[] { "taco", "guac DESC", "salsa ASC" }),
|
||||
new[] { "taco", "guac DESC", "salsa ASC" }, Dialect.SQLite),
|
||||
"CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table "
|
||||
+ "((data->>'taco'), (data->>'guac') DESC, (data->>'salsa') ASC)",
|
||||
"CREATE INDEX for multiple field statement incorrect");
|
||||
})
|
||||
}),
|
||||
TestCase("succeeds for nested PostgreSQL field", () =>
|
||||
{
|
||||
Expect.equal(
|
||||
Query.Definition.EnsureIndexOn("tbl", "nest", ["a.b.c"], Dialect.PostgreSQL),
|
||||
"CREATE INDEX IF NOT EXISTS idx_tbl_nest ON tbl ((data#>>'{a,b,c}'))",
|
||||
"CREATE INDEX for nested PostgreSQL field incorrect");
|
||||
}),
|
||||
TestCase("succeeds for nested SQLite field", () =>
|
||||
{
|
||||
Expect.equal(
|
||||
Query.Definition.EnsureIndexOn("tbl", "nest", ["a.b.c"], Dialect.SQLite),
|
||||
"CREATE INDEX IF NOT EXISTS idx_tbl_nest ON tbl ((data->>'a'->>'b'->>'c'))",
|
||||
"CREATE INDEX for nested SQLite field incorrect");
|
||||
})
|
||||
])
|
||||
]),
|
||||
TestCase("Insert succeeds", () =>
|
||||
{
|
||||
Expect.equal(Query.Insert("tbl"), "INSERT INTO tbl VALUES (@data)", "INSERT statement not correct");
|
||||
@@ -223,7 +335,28 @@ public static class CommonCSharpTests
|
||||
Expect.equal(Query.Save("tbl"),
|
||||
"INSERT INTO tbl VALUES (@data) ON CONFLICT ((data->>'Id')) DO UPDATE SET data = EXCLUDED.data",
|
||||
"INSERT ON CONFLICT UPDATE statement not correct");
|
||||
}),
|
||||
TestCase("Count succeeds", () =>
|
||||
{
|
||||
Expect.equal(Query.Count("tbl"), "SELECT COUNT(*) AS it FROM tbl", "Count query not correct");
|
||||
}),
|
||||
TestCase("Exists succeeds", () =>
|
||||
{
|
||||
Expect.equal(Query.Exists("tbl", "chicken"), "SELECT EXISTS (SELECT 1 FROM tbl WHERE chicken) AS it",
|
||||
"Exists query not correct");
|
||||
}),
|
||||
TestCase("Find succeeds", () =>
|
||||
{
|
||||
Expect.equal(Query.Find("test.table"), "SELECT data FROM test.table", "Find query not correct");
|
||||
}),
|
||||
TestCase("Update succeeds", () =>
|
||||
{
|
||||
Expect.equal(Query.Update("tbl"), "UPDATE tbl SET data = @data", "Update query not correct");
|
||||
}),
|
||||
TestCase("Delete succeeds", () =>
|
||||
{
|
||||
Expect.equal(Query.Delete("tbl"), "DELETE FROM tbl", "Delete query not correct");
|
||||
})
|
||||
})
|
||||
});
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -31,10 +31,10 @@ public class PostgresCSharpExtensionTests
|
||||
/// Integration tests for the SQLite extension methods
|
||||
/// </summary>
|
||||
[Tests]
|
||||
public static readonly Test Integration = TestList("Postgres.C#.Extensions", new[]
|
||||
{
|
||||
TestList("CustomList", new[]
|
||||
{
|
||||
public static readonly Test Integration = TestList("Postgres.C#.Extensions",
|
||||
[
|
||||
TestList("CustomList",
|
||||
[
|
||||
TestCase("succeeds when data is found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -57,9 +57,9 @@ public class PostgresCSharpExtensionTests
|
||||
Results.FromData<JsonDocument>);
|
||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||
})
|
||||
}),
|
||||
TestList("CustomSingle", new[]
|
||||
{
|
||||
]),
|
||||
TestList("CustomSingle",
|
||||
[
|
||||
TestCase("succeeds when a row is found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -81,9 +81,9 @@ public class PostgresCSharpExtensionTests
|
||||
new[] { Tuple.Create("@id", Sql.@string("eighty")) }, Results.FromData<JsonDocument>);
|
||||
Expect.isNull(doc, "There should not have been a document returned");
|
||||
})
|
||||
}),
|
||||
TestList("CustomNonQuery", new[]
|
||||
{
|
||||
]),
|
||||
TestList("CustomNonQuery",
|
||||
[
|
||||
TestCase("succeeds when operating on data", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -107,7 +107,7 @@ public class PostgresCSharpExtensionTests
|
||||
var remaining = await conn.CountAll(PostgresDb.TableName);
|
||||
Expect.equal(remaining, 5, "There should be 5 documents remaining in the table");
|
||||
})
|
||||
}),
|
||||
]),
|
||||
TestCase("Scalar succeeds", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -169,8 +169,8 @@ public class PostgresCSharpExtensionTests
|
||||
exists = await indexExists();
|
||||
Expect.isTrue(exists, "The index should now exist");
|
||||
}),
|
||||
TestList("Insert", new[]
|
||||
{
|
||||
TestList("Insert",
|
||||
[
|
||||
TestCase("succeeds", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -198,9 +198,9 @@ public class PostgresCSharpExtensionTests
|
||||
// This is what should have happened
|
||||
}
|
||||
})
|
||||
}),
|
||||
TestList("save", new[]
|
||||
{
|
||||
]),
|
||||
TestList("save",
|
||||
[
|
||||
TestCase("succeeds when a document is inserted", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -230,7 +230,7 @@ public class PostgresCSharpExtensionTests
|
||||
Expect.isNotNull(after, "There should have been a document returned post-update");
|
||||
Expect.equal(after.Sub!.Foo, "c", "The updated document is not correct");
|
||||
})
|
||||
}),
|
||||
]),
|
||||
TestCase("CountAll succeeds", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -240,6 +240,7 @@ public class PostgresCSharpExtensionTests
|
||||
var theCount = await conn.CountAll(PostgresDb.TableName);
|
||||
Expect.equal(theCount, 5, "There should have been 5 matching documents");
|
||||
}),
|
||||
#pragma warning disable CS0618
|
||||
TestCase("CountByField succeeds", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -249,6 +250,7 @@ public class PostgresCSharpExtensionTests
|
||||
var theCount = await conn.CountByField(PostgresDb.TableName, Field.EQ("Value", "purple"));
|
||||
Expect.equal(theCount, 2, "There should have been 2 matching documents");
|
||||
}),
|
||||
#pragma warning restore CS0618
|
||||
TestCase("CountByContains succeeds", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -267,8 +269,8 @@ public class PostgresCSharpExtensionTests
|
||||
var theCount = await conn.CountByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ > 5)");
|
||||
Expect.equal(theCount, 3, "There should have been 3 matching documents");
|
||||
}),
|
||||
TestList("ExistsById", new[]
|
||||
{
|
||||
TestList("ExistsById",
|
||||
[
|
||||
TestCase("succeeds when a document exists", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -287,9 +289,10 @@ public class PostgresCSharpExtensionTests
|
||||
var exists = await conn.ExistsById(PostgresDb.TableName, "seven");
|
||||
Expect.isFalse(exists, "There should not have been an existing document");
|
||||
})
|
||||
}),
|
||||
TestList("ExistsByField", new[]
|
||||
{
|
||||
]),
|
||||
#pragma warning disable CS0618
|
||||
TestList("ExistsByField",
|
||||
[
|
||||
TestCase("succeeds when documents exist", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -308,9 +311,10 @@ public class PostgresCSharpExtensionTests
|
||||
var exists = await conn.ExistsByField(PostgresDb.TableName, Field.EQ("NumValue", "six"));
|
||||
Expect.isFalse(exists, "There should not have been existing documents");
|
||||
})
|
||||
}),
|
||||
TestList("ExistsByContains", new[]
|
||||
{
|
||||
]),
|
||||
#pragma warning restore CS0618
|
||||
TestList("ExistsByContains",
|
||||
[
|
||||
TestCase("succeeds when documents exist", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -329,9 +333,9 @@ public class PostgresCSharpExtensionTests
|
||||
var exists = await conn.ExistsByContains(PostgresDb.TableName, new { Nothing = "none" });
|
||||
Expect.isFalse(exists, "There should not have been any existing documents");
|
||||
})
|
||||
}),
|
||||
TestList("ExistsByJsonPath", new[]
|
||||
{
|
||||
]),
|
||||
TestList("ExistsByJsonPath",
|
||||
[
|
||||
TestCase("succeeds when documents exist", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -350,9 +354,9 @@ public class PostgresCSharpExtensionTests
|
||||
var exists = await conn.ExistsByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ > 1000)");
|
||||
Expect.isFalse(exists, "There should not have been any existing documents");
|
||||
})
|
||||
}),
|
||||
TestList("FindAll", new[]
|
||||
{
|
||||
]),
|
||||
TestList("FindAll",
|
||||
[
|
||||
TestCase("succeeds when there is data", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -372,9 +376,9 @@ public class PostgresCSharpExtensionTests
|
||||
var results = await conn.FindAll<JsonDocument>(PostgresDb.TableName);
|
||||
Expect.isEmpty(results, "There should have been no documents returned");
|
||||
})
|
||||
}),
|
||||
TestList("FindById", new[]
|
||||
{
|
||||
]),
|
||||
TestList("FindById",
|
||||
[
|
||||
TestCase("succeeds when a document is found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -394,9 +398,10 @@ public class PostgresCSharpExtensionTests
|
||||
var doc = await conn.FindById<string, JsonDocument>(PostgresDb.TableName, "three hundred eighty-seven");
|
||||
Expect.isNull(doc, "There should not have been a document returned");
|
||||
})
|
||||
}),
|
||||
TestList("FindByField", new[]
|
||||
{
|
||||
]),
|
||||
#pragma warning disable CS0618
|
||||
TestList("FindByField",
|
||||
[
|
||||
TestCase("succeeds when documents are found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -415,9 +420,10 @@ public class PostgresCSharpExtensionTests
|
||||
var docs = await conn.FindByField<JsonDocument>(PostgresDb.TableName, Field.EQ("Value", "mauve"));
|
||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||
})
|
||||
}),
|
||||
TestList("FindByContains", new[]
|
||||
{
|
||||
]),
|
||||
#pragma warning restore CS0618
|
||||
TestList("FindByContains",
|
||||
[
|
||||
TestCase("succeeds when documents are found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -437,9 +443,9 @@ public class PostgresCSharpExtensionTests
|
||||
var docs = await conn.FindByContains<JsonDocument>(PostgresDb.TableName, new { Value = "mauve" });
|
||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||
})
|
||||
}),
|
||||
TestList("FindByJsonPath", new[]
|
||||
{
|
||||
]),
|
||||
TestList("FindByJsonPath",
|
||||
[
|
||||
TestCase("succeeds when documents are found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -458,9 +464,10 @@ public class PostgresCSharpExtensionTests
|
||||
var docs = await conn.FindByJsonPath<JsonDocument>(PostgresDb.TableName, "$.NumValue ? (@ < 0)");
|
||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||
})
|
||||
}),
|
||||
TestList("FindFirstByField", new[]
|
||||
{
|
||||
]),
|
||||
#pragma warning disable CS0618
|
||||
TestList("FindFirstByField",
|
||||
[
|
||||
TestCase("succeeds when a document is found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -490,9 +497,10 @@ public class PostgresCSharpExtensionTests
|
||||
var doc = await conn.FindFirstByField<JsonDocument>(PostgresDb.TableName, Field.EQ("Value", "absent"));
|
||||
Expect.isNull(doc, "There should not have been a document returned");
|
||||
})
|
||||
}),
|
||||
TestList("FindFirstByContains", new[]
|
||||
{
|
||||
]),
|
||||
#pragma warning restore CS0618
|
||||
TestList("FindFirstByContains",
|
||||
[
|
||||
TestCase("succeeds when a document is found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -523,9 +531,9 @@ public class PostgresCSharpExtensionTests
|
||||
var doc = await conn.FindFirstByContains<JsonDocument>(PostgresDb.TableName, new { Value = "absent" });
|
||||
Expect.isNull(doc, "There should not have been a document returned");
|
||||
})
|
||||
}),
|
||||
TestList("FindFirstByJsonPath", new[]
|
||||
{
|
||||
]),
|
||||
TestList("FindFirstByJsonPath",
|
||||
[
|
||||
TestCase("succeeds when a document is found", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -557,9 +565,9 @@ public class PostgresCSharpExtensionTests
|
||||
var doc = await conn.FindFirstByJsonPath<JsonDocument>(PostgresDb.TableName, "$.Id ? (@ == \"nope\")");
|
||||
Expect.isNull(doc, "There should not have been a document returned");
|
||||
})
|
||||
}),
|
||||
TestList("UpdateById", new[]
|
||||
{
|
||||
]),
|
||||
TestList("UpdateById",
|
||||
[
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -588,9 +596,9 @@ public class PostgresCSharpExtensionTests
|
||||
await conn.UpdateById(PostgresDb.TableName, "test",
|
||||
new JsonDocument { Id = "x", Sub = new() { Foo = "blue", Bar = "red" } });
|
||||
})
|
||||
}),
|
||||
TestList("UpdateByFunc", new[]
|
||||
{
|
||||
]),
|
||||
TestList("UpdateByFunc",
|
||||
[
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -617,9 +625,9 @@ public class PostgresCSharpExtensionTests
|
||||
await conn.UpdateByFunc(PostgresDb.TableName, doc => doc.Id,
|
||||
new JsonDocument { Id = "one", Value = "le un", NumValue = 1 });
|
||||
})
|
||||
}),
|
||||
TestList("PatchById", new[]
|
||||
{
|
||||
]),
|
||||
TestList("PatchById",
|
||||
[
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -641,9 +649,10 @@ public class PostgresCSharpExtensionTests
|
||||
// This not raising an exception is the test
|
||||
await conn.PatchById(PostgresDb.TableName, "test", new { Foo = "green" });
|
||||
})
|
||||
}),
|
||||
TestList("PatchByField", new[]
|
||||
{
|
||||
]),
|
||||
#pragma warning disable CS0618
|
||||
TestList("PatchByField",
|
||||
[
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -664,9 +673,10 @@ public class PostgresCSharpExtensionTests
|
||||
// This not raising an exception is the test
|
||||
await conn.PatchByField(PostgresDb.TableName, Field.EQ("Value", "burgundy"), new { Foo = "green" });
|
||||
})
|
||||
}),
|
||||
TestList("PatchByContains", new[]
|
||||
{
|
||||
]),
|
||||
#pragma warning restore CS0618
|
||||
TestList("PatchByContains",
|
||||
[
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -687,9 +697,9 @@ public class PostgresCSharpExtensionTests
|
||||
// This not raising an exception is the test
|
||||
await conn.PatchByContains(PostgresDb.TableName, new { Value = "burgundy" }, new { Foo = "green" });
|
||||
})
|
||||
}),
|
||||
TestList("PatchByJsonPath", new[]
|
||||
{
|
||||
]),
|
||||
TestList("PatchByJsonPath",
|
||||
[
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -710,9 +720,9 @@ public class PostgresCSharpExtensionTests
|
||||
// This not raising an exception is the test
|
||||
await conn.PatchByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ < 0)", new { Foo = "green" });
|
||||
})
|
||||
}),
|
||||
TestList("RemoveFieldsById", new[]
|
||||
{
|
||||
]),
|
||||
TestList("RemoveFieldsById",
|
||||
[
|
||||
TestCase("succeeds when multiple fields are removed", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -754,9 +764,10 @@ public class PostgresCSharpExtensionTests
|
||||
// This not raising an exception is the test
|
||||
await conn.RemoveFieldsById(PostgresDb.TableName, "two", new[] { "Value" });
|
||||
})
|
||||
}),
|
||||
TestList("RemoveFieldsByField", new[]
|
||||
{
|
||||
]),
|
||||
#pragma warning disable CS0618
|
||||
TestList("RemoveFieldsByField",
|
||||
[
|
||||
TestCase("succeeds when multiple fields are removed", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -800,9 +811,10 @@ public class PostgresCSharpExtensionTests
|
||||
await conn.RemoveFieldsByField(PostgresDb.TableName, Field.NE("Abracadabra", "apple"),
|
||||
new[] { "Value" });
|
||||
})
|
||||
}),
|
||||
TestList("RemoveFieldsByContains", new[]
|
||||
{
|
||||
]),
|
||||
#pragma warning restore CS0618
|
||||
TestList("RemoveFieldsByContains",
|
||||
[
|
||||
TestCase("succeeds when multiple fields are removed", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -846,9 +858,9 @@ public class PostgresCSharpExtensionTests
|
||||
await conn.RemoveFieldsByContains(PostgresDb.TableName, new { Abracadabra = "apple" },
|
||||
new[] { "Value" });
|
||||
})
|
||||
}),
|
||||
TestList("RemoveFieldsByJsonPath", new[]
|
||||
{
|
||||
]),
|
||||
TestList("RemoveFieldsByJsonPath",
|
||||
[
|
||||
TestCase("succeeds when multiple fields are removed", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -892,9 +904,9 @@ public class PostgresCSharpExtensionTests
|
||||
await conn.RemoveFieldsByJsonPath(PostgresDb.TableName, "$.Abracadabra ? (@ == \"apple\")",
|
||||
new[] { "Value" });
|
||||
})
|
||||
}),
|
||||
TestList("DeleteById", new[]
|
||||
{
|
||||
]),
|
||||
TestList("DeleteById",
|
||||
[
|
||||
TestCase("succeeds when a document is deleted", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -915,9 +927,10 @@ public class PostgresCSharpExtensionTests
|
||||
var remaining = await conn.CountAll(PostgresDb.TableName);
|
||||
Expect.equal(remaining, 5, "There should have been 5 documents remaining");
|
||||
})
|
||||
}),
|
||||
TestList("DeleteByField", new[]
|
||||
{
|
||||
]),
|
||||
#pragma warning disable CS0618
|
||||
TestList("DeleteByField",
|
||||
[
|
||||
TestCase("succeeds when documents are deleted", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -938,9 +951,10 @@ public class PostgresCSharpExtensionTests
|
||||
var remaining = await conn.CountAll(PostgresDb.TableName);
|
||||
Expect.equal(remaining, 5, "There should have been 5 documents remaining");
|
||||
})
|
||||
}),
|
||||
TestList("DeleteByContains", new[]
|
||||
{
|
||||
]),
|
||||
#pragma warning restore CS0618
|
||||
TestList("DeleteByContains",
|
||||
[
|
||||
TestCase("succeeds when documents are deleted", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -961,9 +975,9 @@ public class PostgresCSharpExtensionTests
|
||||
var remaining = await conn.CountAll(PostgresDb.TableName);
|
||||
Expect.equal(remaining, 5, "There should have been 5 documents remaining");
|
||||
})
|
||||
}),
|
||||
TestList("DeleteByJsonPath", new[]
|
||||
{
|
||||
]),
|
||||
TestList("DeleteByJsonPath",
|
||||
[
|
||||
TestCase("succeeds when documents are deleted", async () =>
|
||||
{
|
||||
await using var db = PostgresDb.BuildDb();
|
||||
@@ -984,6 +998,6 @@ public class PostgresCSharpExtensionTests
|
||||
var remaining = await conn.CountAll(PostgresDb.TableName);
|
||||
Expect.equal(remaining, 5, "There should have been 5 documents remaining");
|
||||
})
|
||||
}),
|
||||
});
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,4 @@
|
||||
using BitBadger.Documents.Postgres;
|
||||
using Npgsql;
|
||||
using Npgsql.FSharp;
|
||||
using ThrowawayDb.Postgres;
|
||||
@@ -131,7 +132,7 @@ public static class PostgresDb
|
||||
var sqlProps = Sql.connect(database.ConnectionString);
|
||||
|
||||
Sql.executeNonQuery(Sql.query(Postgres.Query.Definition.EnsureTable(TableName), sqlProps));
|
||||
Sql.executeNonQuery(Sql.query(Query.Definition.EnsureKey(TableName), sqlProps));
|
||||
Sql.executeNonQuery(Sql.query(Query.Definition.EnsureKey(TableName, Dialect.PostgreSQL), sqlProps));
|
||||
|
||||
Postgres.Configuration.UseDataSource(MkDataSource(database.ConnectionString));
|
||||
|
||||
|
||||
@@ -18,10 +18,10 @@ public static class SqliteCSharpExtensionTests
|
||||
/// Integration tests for the SQLite extension methods
|
||||
/// </summary>
|
||||
[Tests]
|
||||
public static readonly Test Integration = TestList("Sqlite.C#.Extensions", new[]
|
||||
{
|
||||
TestList("CustomSingle", new[]
|
||||
{
|
||||
public static readonly Test Integration = TestList("Sqlite.C#.Extensions",
|
||||
[
|
||||
TestList("CustomSingle",
|
||||
[
|
||||
TestCase("succeeds when a row is found", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -43,9 +43,9 @@ public static class SqliteCSharpExtensionTests
|
||||
new[] { Parameters.Id("eighty") }, Results.FromData<JsonDocument>);
|
||||
Expect.isNull(doc, "There should not have been a document returned");
|
||||
})
|
||||
}),
|
||||
TestList("CustomList", new[]
|
||||
{
|
||||
]),
|
||||
TestList("CustomList",
|
||||
[
|
||||
TestCase("succeeds when data is found", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -67,9 +67,9 @@ public static class SqliteCSharpExtensionTests
|
||||
new[] { new SqliteParameter("@value", 100) }, Results.FromData<JsonDocument>);
|
||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||
})
|
||||
}),
|
||||
TestList("CustomNonQuery", new[]
|
||||
{
|
||||
]),
|
||||
TestList("CustomNonQuery",
|
||||
[
|
||||
TestCase("succeeds when operating on data", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -93,7 +93,7 @@ public static class SqliteCSharpExtensionTests
|
||||
var remaining = await conn.CountAll(SqliteDb.TableName);
|
||||
Expect.equal(remaining, 5L, "There should be 5 documents remaining in the table");
|
||||
})
|
||||
}),
|
||||
]),
|
||||
TestCase("CustomScalar succeeds", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -140,8 +140,8 @@ public static class SqliteCSharpExtensionTests
|
||||
exists = await indexExists();
|
||||
Expect.isTrue(exists, "The index should now exist");
|
||||
}),
|
||||
TestList("Insert", new[]
|
||||
{
|
||||
TestList("Insert",
|
||||
[
|
||||
TestCase("succeeds", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -168,9 +168,9 @@ public static class SqliteCSharpExtensionTests
|
||||
// This is what is supposed to happen
|
||||
}
|
||||
})
|
||||
}),
|
||||
TestList("Save", new[]
|
||||
{
|
||||
]),
|
||||
TestList("Save",
|
||||
[
|
||||
TestCase("succeeds when a document is inserted", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -203,7 +203,7 @@ public static class SqliteCSharpExtensionTests
|
||||
Expect.equal(after!.Id, "test", "The updated document is not correct");
|
||||
Expect.isNull(after.Sub, "There should not have been a sub-document in the updated document");
|
||||
})
|
||||
}),
|
||||
]),
|
||||
TestCase("CountAll succeeds", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -213,6 +213,7 @@ public static class SqliteCSharpExtensionTests
|
||||
var theCount = await conn.CountAll(SqliteDb.TableName);
|
||||
Expect.equal(theCount, 5L, "There should have been 5 matching documents");
|
||||
}),
|
||||
#pragma warning disable CS0618
|
||||
TestCase("CountByField succeeds", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -222,8 +223,9 @@ public static class SqliteCSharpExtensionTests
|
||||
var theCount = await conn.CountByField(SqliteDb.TableName, Field.EQ("Value", "purple"));
|
||||
Expect.equal(theCount, 2L, "There should have been 2 matching documents");
|
||||
}),
|
||||
TestList("ExistsById", new[]
|
||||
{
|
||||
#pragma warning restore CS0618
|
||||
TestList("ExistsById",
|
||||
[
|
||||
TestCase("succeeds when a document exists", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -242,9 +244,10 @@ public static class SqliteCSharpExtensionTests
|
||||
var exists = await conn.ExistsById(SqliteDb.TableName, "seven");
|
||||
Expect.isFalse(exists, "There should not have been an existing document");
|
||||
})
|
||||
}),
|
||||
TestList("ExistsByField", new[]
|
||||
{
|
||||
]),
|
||||
#pragma warning disable CS0618
|
||||
TestList("ExistsByField",
|
||||
[
|
||||
TestCase("succeeds when documents exist", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -263,9 +266,10 @@ public static class SqliteCSharpExtensionTests
|
||||
var exists = await conn.ExistsByField(SqliteDb.TableName, Field.EQ("Nothing", "none"));
|
||||
Expect.isFalse(exists, "There should not have been any existing documents");
|
||||
})
|
||||
}),
|
||||
TestList("FindAll", new[]
|
||||
{
|
||||
]),
|
||||
#pragma warning restore CS0618
|
||||
TestList("FindAll",
|
||||
[
|
||||
TestCase("succeeds when there is data", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -285,9 +289,9 @@ public static class SqliteCSharpExtensionTests
|
||||
var results = await conn.FindAll<JsonDocument>(SqliteDb.TableName);
|
||||
Expect.isEmpty(results, "There should have been no documents returned");
|
||||
})
|
||||
}),
|
||||
TestList("FindById", new[]
|
||||
{
|
||||
]),
|
||||
TestList("FindById",
|
||||
[
|
||||
TestCase("succeeds when a document is found", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -307,9 +311,10 @@ public static class SqliteCSharpExtensionTests
|
||||
var doc = await conn.FindById<string, JsonDocument>(SqliteDb.TableName, "eighty-seven");
|
||||
Expect.isNull(doc, "There should not have been a document returned");
|
||||
})
|
||||
}),
|
||||
TestList("FindByField", new[]
|
||||
{
|
||||
]),
|
||||
#pragma warning disable CS0618
|
||||
TestList("FindByField",
|
||||
[
|
||||
TestCase("succeeds when documents are found", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -328,9 +333,9 @@ public static class SqliteCSharpExtensionTests
|
||||
var docs = await conn.FindByField<JsonDocument>(SqliteDb.TableName, Field.EQ("Value", "mauve"));
|
||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||
})
|
||||
}),
|
||||
TestList("FindFirstByField", new[]
|
||||
{
|
||||
]),
|
||||
TestList("FindFirstByField",
|
||||
[
|
||||
TestCase("succeeds when a document is found", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -360,9 +365,10 @@ public static class SqliteCSharpExtensionTests
|
||||
var doc = await conn.FindFirstByField<JsonDocument>(SqliteDb.TableName, Field.EQ("Value", "absent"));
|
||||
Expect.isNull(doc, "There should not have been a document returned");
|
||||
})
|
||||
}),
|
||||
TestList("UpdateById", new[]
|
||||
{
|
||||
]),
|
||||
#pragma warning restore CS0618
|
||||
TestList("UpdateById",
|
||||
[
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -389,9 +395,9 @@ public static class SqliteCSharpExtensionTests
|
||||
await conn.UpdateById(SqliteDb.TableName, "test",
|
||||
new JsonDocument { Id = "x", Sub = new() { Foo = "blue", Bar = "red" } });
|
||||
})
|
||||
}),
|
||||
TestList("UpdateByFunc", new[]
|
||||
{
|
||||
]),
|
||||
TestList("UpdateByFunc",
|
||||
[
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -418,9 +424,9 @@ public static class SqliteCSharpExtensionTests
|
||||
await conn.UpdateByFunc(SqliteDb.TableName, doc => doc.Id,
|
||||
new JsonDocument { Id = "one", Value = "le un", NumValue = 1 });
|
||||
})
|
||||
}),
|
||||
TestList("PatchById", new[]
|
||||
{
|
||||
]),
|
||||
TestList("PatchById",
|
||||
[
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -443,9 +449,10 @@ public static class SqliteCSharpExtensionTests
|
||||
// This not raising an exception is the test
|
||||
await conn.PatchById(SqliteDb.TableName, "test", new { Foo = "green" });
|
||||
})
|
||||
}),
|
||||
TestList("PatchByField", new[]
|
||||
{
|
||||
]),
|
||||
#pragma warning disable CS0618
|
||||
TestList("PatchByField",
|
||||
[
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -466,9 +473,10 @@ public static class SqliteCSharpExtensionTests
|
||||
// This not raising an exception is the test
|
||||
await conn.PatchByField(SqliteDb.TableName, Field.EQ("Value", "burgundy"), new { Foo = "green" });
|
||||
})
|
||||
}),
|
||||
TestList("RemoveFieldsById", new[]
|
||||
{
|
||||
]),
|
||||
#pragma warning restore CS0618
|
||||
TestList("RemoveFieldsById",
|
||||
[
|
||||
TestCase("succeeds when fields are removed", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -498,9 +506,10 @@ public static class SqliteCSharpExtensionTests
|
||||
// This not raising an exception is the test
|
||||
await conn.RemoveFieldsById(SqliteDb.TableName, "two", new[] { "Value" });
|
||||
})
|
||||
}),
|
||||
TestList("RemoveFieldsByField", new[]
|
||||
{
|
||||
]),
|
||||
#pragma warning disable CS0618
|
||||
TestList("RemoveFieldsByField",
|
||||
[
|
||||
TestCase("succeeds when a field is removed", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -529,9 +538,10 @@ public static class SqliteCSharpExtensionTests
|
||||
// This not raising an exception is the test
|
||||
await conn.RemoveFieldsByField(SqliteDb.TableName, Field.NE("Abracadabra", "apple"), new[] { "Value" });
|
||||
})
|
||||
}),
|
||||
TestList("DeleteById", new[]
|
||||
{
|
||||
]),
|
||||
#pragma warning restore CS0618
|
||||
TestList("DeleteById",
|
||||
[
|
||||
TestCase("succeeds when a document is deleted", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -552,9 +562,10 @@ public static class SqliteCSharpExtensionTests
|
||||
var remaining = await conn.CountAll(SqliteDb.TableName);
|
||||
Expect.equal(remaining, 5L, "There should have been 5 documents remaining");
|
||||
})
|
||||
}),
|
||||
TestList("DeleteByField", new[]
|
||||
{
|
||||
]),
|
||||
#pragma warning disable CS0618
|
||||
TestList("DeleteByField",
|
||||
[
|
||||
TestCase("succeeds when documents are deleted", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -575,7 +586,8 @@ public static class SqliteCSharpExtensionTests
|
||||
var remaining = await conn.CountAll(SqliteDb.TableName);
|
||||
Expect.equal(remaining, 5L, "There should have been 5 documents remaining");
|
||||
})
|
||||
}),
|
||||
]),
|
||||
#pragma warning restore CS0618
|
||||
TestCase("Clean up database", () => Sqlite.Configuration.UseConnectionString("data source=:memory:"))
|
||||
});
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Text.Json;
|
||||
using Expecto.CSharp;
|
||||
using Expecto.CSharp;
|
||||
using Expecto;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.FSharp.Core;
|
||||
@@ -17,132 +16,86 @@ public static class SqliteCSharpTests
|
||||
/// <summary>
|
||||
/// Unit tests for the SQLite library
|
||||
/// </summary>
|
||||
private static readonly Test Unit = TestList("Unit", new[]
|
||||
private static readonly Test Unit = TestList("Unit",
|
||||
[
|
||||
TestList("Query",
|
||||
[
|
||||
TestList("WhereByFields",
|
||||
[
|
||||
TestCase("succeeds for a single field when a logical operator is passed", () =>
|
||||
{
|
||||
TestList("Query", new[]
|
||||
{
|
||||
TestList("WhereByField", new[]
|
||||
{
|
||||
TestCase("succeeds when a logical operator is passed", () =>
|
||||
{
|
||||
Expect.equal(Sqlite.Query.WhereByField(Field.GT("theField", 0), "@test"),
|
||||
Expect.equal(
|
||||
Sqlite.Query.WhereByFields(FieldMatch.Any,
|
||||
[Field.GT("theField", 0).WithParameterName("@test")]),
|
||||
"data->>'theField' > @test", "WHERE clause not correct");
|
||||
}),
|
||||
TestCase("succeeds when an existence operator is passed", () =>
|
||||
TestCase("succeeds for a single field when an existence operator is passed", () =>
|
||||
{
|
||||
Expect.equal(Sqlite.Query.WhereByField(Field.NEX("thatField"), ""), "data->>'thatField' IS NULL",
|
||||
"WHERE clause not correct");
|
||||
Expect.equal(Sqlite.Query.WhereByFields(FieldMatch.Any, [Field.NEX("thatField")]),
|
||||
"data->>'thatField' IS NULL", "WHERE clause not correct");
|
||||
}),
|
||||
TestCase("succeeds when the between operator is passed", () =>
|
||||
TestCase("succeeds for a single field when a between operator is passed", () =>
|
||||
{
|
||||
Expect.equal(Sqlite.Query.WhereByField(Field.BT("aField", 50, 99), "@range"),
|
||||
Expect.equal(
|
||||
Sqlite.Query.WhereByFields(FieldMatch.All,
|
||||
[Field.BT("aField", 50, 99).WithParameterName("@range")]),
|
||||
"data->>'aField' BETWEEN @rangemin AND @rangemax", "WHERE clause not correct");
|
||||
})
|
||||
}),
|
||||
TestCase("succeeds for all multiple fields with logical operators", () =>
|
||||
{
|
||||
Expect.equal(
|
||||
Sqlite.Query.WhereByFields(FieldMatch.All,
|
||||
[Field.EQ("theFirst", "1"), Field.EQ("numberTwo", "2")]),
|
||||
"data->>'theFirst' = @field0 AND data->>'numberTwo' = @field1", "WHERE clause not correct");
|
||||
}),
|
||||
TestCase("succeeds for any multiple fields with an existence operator", () =>
|
||||
{
|
||||
Expect.equal(
|
||||
Sqlite.Query.WhereByFields(FieldMatch.Any, [Field.NEX("thatField"), Field.GE("thisField", 18)]),
|
||||
"data->>'thatField' IS NULL OR data->>'thisField' >= @field0", "WHERE clause not correct");
|
||||
}),
|
||||
TestCase("succeeds for all multiple fields with between operators", () =>
|
||||
{
|
||||
Expect.equal(
|
||||
Sqlite.Query.WhereByFields(FieldMatch.All,
|
||||
[Field.BT("aField", 50, 99), Field.BT("anotherField", "a", "b")]),
|
||||
"data->>'aField' BETWEEN @field0min AND @field0max AND data->>'anotherField' BETWEEN @field1min AND @field1max",
|
||||
"WHERE clause not correct");
|
||||
})
|
||||
]),
|
||||
TestCase("WhereById succeeds", () =>
|
||||
{
|
||||
Expect.equal(Sqlite.Query.WhereById("@id"), "data->>'Id' = @id", "WHERE clause not correct");
|
||||
}),
|
||||
TestCase("Patch succeeds", () =>
|
||||
{
|
||||
Expect.equal(Sqlite.Query.Patch(SqliteDb.TableName),
|
||||
$"UPDATE {SqliteDb.TableName} SET data = json_patch(data, json(@data))", "Patch query not correct");
|
||||
}),
|
||||
TestCase("RemoveFields succeeds", () =>
|
||||
{
|
||||
Expect.equal(Sqlite.Query.RemoveFields(SqliteDb.TableName, [new("@a", "a"), new("@b", "b")]),
|
||||
$"UPDATE {SqliteDb.TableName} SET data = json_remove(data, @a, @b)",
|
||||
"Field removal query not correct");
|
||||
}),
|
||||
TestCase("ById succeeds", () =>
|
||||
{
|
||||
Expect.equal(Sqlite.Query.ById("test", "14"), "test WHERE data->>'Id' = @id",
|
||||
"By-ID query not correct");
|
||||
}),
|
||||
TestCase("ByFields succeeds", () =>
|
||||
{
|
||||
Expect.equal(Sqlite.Query.ByFields("unit", FieldMatch.Any, [Field.GT("That", 14)]),
|
||||
"unit WHERE data->>'That' > @field0", "By-Field query not correct");
|
||||
}),
|
||||
TestCase("Definition.EnsureTable succeeds", () =>
|
||||
{
|
||||
Expect.equal(Sqlite.Query.Definition.EnsureTable("tbl"),
|
||||
"CREATE TABLE IF NOT EXISTS tbl (data TEXT NOT NULL)", "CREATE TABLE statement not correct");
|
||||
}),
|
||||
TestCase("Update succeeds", () =>
|
||||
{
|
||||
Expect.equal(Sqlite.Query.Update("tbl"), "UPDATE tbl SET data = @data WHERE data->>'Id' = @id",
|
||||
"UPDATE full statement not correct");
|
||||
}),
|
||||
TestList("Count", new[]
|
||||
{
|
||||
TestCase("All succeeds", () =>
|
||||
{
|
||||
Expect.equal(Sqlite.Query.Count.All("tbl"), "SELECT COUNT(*) AS it FROM tbl",
|
||||
"Count query not correct");
|
||||
}),
|
||||
TestCase("ByField succeeds", () =>
|
||||
{
|
||||
Expect.equal(Sqlite.Query.Count.ByField("tbl", Field.EQ("thatField", 0)),
|
||||
"SELECT COUNT(*) AS it FROM tbl WHERE data->>'thatField' = @field",
|
||||
"JSON field text comparison count query not correct");
|
||||
})
|
||||
}),
|
||||
TestList("Exists", new[]
|
||||
{
|
||||
TestCase("ById succeeds", () =>
|
||||
{
|
||||
Expect.equal(Sqlite.Query.Exists.ById("tbl"),
|
||||
"SELECT EXISTS (SELECT 1 FROM tbl WHERE data->>'Id' = @id) AS it",
|
||||
"ID existence query not correct");
|
||||
}),
|
||||
TestCase("ByField succeeds", () =>
|
||||
{
|
||||
Expect.equal(Sqlite.Query.Exists.ByField("tbl", Field.LT("Test", 0)),
|
||||
"SELECT EXISTS (SELECT 1 FROM tbl WHERE data->>'Test' < @field) AS it",
|
||||
"JSON field text comparison exists query not correct");
|
||||
})
|
||||
}),
|
||||
TestList("Find", new[]
|
||||
{
|
||||
TestCase("ById succeeds", () =>
|
||||
{
|
||||
Expect.equal(Sqlite.Query.Find.ById("tbl"), "SELECT data FROM tbl WHERE data->>'Id' = @id",
|
||||
"SELECT by ID query not correct");
|
||||
}),
|
||||
TestCase("ByField succeeds", () =>
|
||||
{
|
||||
Expect.equal(Sqlite.Query.Find.ByField("tbl", Field.GE("Golf", 0)),
|
||||
"SELECT data FROM tbl WHERE data->>'Golf' >= @field",
|
||||
"SELECT by JSON comparison query not correct");
|
||||
})
|
||||
}),
|
||||
TestList("Patch", new[]
|
||||
{
|
||||
TestCase("ById succeeds", () =>
|
||||
{
|
||||
Expect.equal(Sqlite.Query.Patch.ById("tbl"),
|
||||
"UPDATE tbl SET data = json_patch(data, json(@data)) WHERE data->>'Id' = @id",
|
||||
"UPDATE partial by ID statement not correct");
|
||||
}),
|
||||
TestCase("ByField succeeds", () =>
|
||||
{
|
||||
Expect.equal(Sqlite.Query.Patch.ByField("tbl", Field.NE("Part", 0)),
|
||||
"UPDATE tbl SET data = json_patch(data, json(@data)) WHERE data->>'Part' <> @field",
|
||||
"UPDATE partial by JSON comparison query not correct");
|
||||
})
|
||||
}),
|
||||
TestList("RemoveFields", new[]
|
||||
{
|
||||
TestCase("ById succeeds", () =>
|
||||
{
|
||||
Expect.equal(Sqlite.Query.RemoveFields.ById("tbl", new[] { new SqliteParameter("@name", "one") }),
|
||||
"UPDATE tbl SET data = json_remove(data, @name) WHERE data->>'Id' = @id",
|
||||
"Remove field by ID query not correct");
|
||||
}),
|
||||
TestCase("ByField succeeds", () =>
|
||||
{
|
||||
Expect.equal(Sqlite.Query.RemoveFields.ByField("tbl", Field.LT("Fly", 0),
|
||||
new[] { new SqliteParameter("@name0", "one"), new SqliteParameter("@name1", "two") }),
|
||||
"UPDATE tbl SET data = json_remove(data, @name0, @name1) WHERE data->>'Fly' < @field",
|
||||
"Remove field by field query not correct");
|
||||
})
|
||||
}),
|
||||
TestList("Delete", new[]
|
||||
{
|
||||
TestCase("ById succeeds", () =>
|
||||
{
|
||||
Expect.equal(Sqlite.Query.Delete.ById("tbl"), "DELETE FROM tbl WHERE data->>'Id' = @id",
|
||||
"DELETE by ID query not correct");
|
||||
}),
|
||||
TestCase("ByField succeeds", () =>
|
||||
{
|
||||
Expect.equal(Sqlite.Query.Delete.ByField("tbl", Field.NEX("gone")),
|
||||
"DELETE FROM tbl WHERE data->>'gone' IS NULL", "DELETE by JSON comparison query not correct");
|
||||
})
|
||||
})
|
||||
}),
|
||||
TestList("Parameters", new[]
|
||||
{
|
||||
]),
|
||||
TestList("Parameters",
|
||||
[
|
||||
TestCase("Id succeeds", () =>
|
||||
{
|
||||
var theParam = Parameters.Id(7);
|
||||
@@ -155,10 +108,10 @@ public static class SqliteCSharpTests
|
||||
Expect.equal(theParam.ParameterName, "@test", "The parameter name is incorrect");
|
||||
Expect.equal(theParam.Value, "{\"Nice\":\"job\"}", "The parameter value is incorrect");
|
||||
}),
|
||||
#pragma warning disable CS0618
|
||||
TestCase("AddField succeeds when adding a parameter", () =>
|
||||
{
|
||||
var paramList = Parameters.AddField("@field", Field.EQ("it", 99), Enumerable.Empty<SqliteParameter>())
|
||||
.ToList();
|
||||
var paramList = Parameters.AddField("@field", Field.EQ("it", 99), []).ToList();
|
||||
Expect.hasLength(paramList, 1, "There should have been a parameter added");
|
||||
var theParam = paramList[0];
|
||||
Expect.equal(theParam.ParameterName, "@field", "The parameter name is incorrect");
|
||||
@@ -166,25 +119,26 @@ public static class SqliteCSharpTests
|
||||
}),
|
||||
TestCase("AddField succeeds when not adding a parameter", () =>
|
||||
{
|
||||
var paramSeq = Parameters.AddField("@it", Field.EX("Coffee"), Enumerable.Empty<SqliteParameter>());
|
||||
var paramSeq = Parameters.AddField("@it", Field.EX("Coffee"), []);
|
||||
Expect.isEmpty(paramSeq, "There should not have been any parameters added");
|
||||
}),
|
||||
#pragma warning restore CS0618
|
||||
TestCase("None succeeds", () =>
|
||||
{
|
||||
Expect.isEmpty(Parameters.None, "The parameter list should have been empty");
|
||||
})
|
||||
})
|
||||
])
|
||||
// Results are exhaustively executed in the context of other tests
|
||||
});
|
||||
]);
|
||||
|
||||
private static readonly List<JsonDocument> TestDocuments = new()
|
||||
{
|
||||
private static readonly List<JsonDocument> TestDocuments =
|
||||
[
|
||||
new() { Id = "one", Value = "FIRST!", NumValue = 0 },
|
||||
new() { Id = "two", Value = "another", NumValue = 10, Sub = new() { Foo = "green", Bar = "blue" } },
|
||||
new() { Id = "three", Value = "", NumValue = 4 },
|
||||
new() { Id = "four", Value = "purple", NumValue = 17, Sub = new() { Foo = "green", Bar = "red" } },
|
||||
new() { Id = "five", Value = "purple", NumValue = 18 }
|
||||
};
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Add the test documents to the database
|
||||
@@ -194,8 +148,8 @@ public static class SqliteCSharpTests
|
||||
foreach (var doc in TestDocuments) await Document.Insert(SqliteDb.TableName, doc);
|
||||
}
|
||||
|
||||
private static readonly Test Integration = TestList("Integration", new[]
|
||||
{
|
||||
private static readonly Test Integration = TestList("Integration",
|
||||
[
|
||||
TestCase("Configuration.UseConnectionString succeeds", () =>
|
||||
{
|
||||
try
|
||||
@@ -209,10 +163,10 @@ public static class SqliteCSharpTests
|
||||
Sqlite.Configuration.UseConnectionString("Data Source=:memory:");
|
||||
}
|
||||
}),
|
||||
TestList("Custom", new[]
|
||||
{
|
||||
TestList("Single", new[]
|
||||
{
|
||||
TestList("Custom",
|
||||
[
|
||||
TestList("Single",
|
||||
[
|
||||
TestCase("succeeds when a row is found", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -232,9 +186,9 @@ public static class SqliteCSharpTests
|
||||
new[] { Parameters.Id("eighty") }, Results.FromData<JsonDocument>);
|
||||
Expect.isNull(doc, "There should not have been a document returned");
|
||||
})
|
||||
}),
|
||||
TestList("List", new[]
|
||||
{
|
||||
]),
|
||||
TestList("List",
|
||||
[
|
||||
TestCase("succeeds when data is found", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -254,9 +208,9 @@ public static class SqliteCSharpTests
|
||||
new[] { new SqliteParameter("@value", 100) }, Results.FromData<JsonDocument>);
|
||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||
})
|
||||
}),
|
||||
TestList("NonQuery", new[]
|
||||
{
|
||||
]),
|
||||
TestList("NonQuery",
|
||||
[
|
||||
TestCase("succeeds when operating on data", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -278,7 +232,7 @@ public static class SqliteCSharpTests
|
||||
var remaining = await Count.All(SqliteDb.TableName);
|
||||
Expect.equal(remaining, 5L, "There should be 5 documents remaining in the table");
|
||||
})
|
||||
}),
|
||||
]),
|
||||
TestCase("Scalar succeeds", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -286,9 +240,9 @@ public static class SqliteCSharpTests
|
||||
var nbr = await Custom.Scalar("SELECT 5 AS test_value", Parameters.None, rdr => rdr.GetInt32(0));
|
||||
Expect.equal(nbr, 5, "The query should have returned the number 5");
|
||||
})
|
||||
}),
|
||||
TestList("Definition", new[]
|
||||
{
|
||||
]),
|
||||
TestList("Definition",
|
||||
[
|
||||
TestCase("EnsureTable succeeds", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -316,21 +270,23 @@ public static class SqliteCSharpTests
|
||||
TestCase("EnsureFieldIndex succeeds", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
var indexExists = () => Custom.Scalar(
|
||||
$"SELECT EXISTS (SELECT 1 FROM {SqliteDb.Catalog} WHERE name = 'idx_ensured_test') AS it",
|
||||
Parameters.None, Results.ToExists);
|
||||
|
||||
var exists = await indexExists();
|
||||
var exists = await IndexExists();
|
||||
Expect.isFalse(exists, "The index should not exist already");
|
||||
|
||||
await Definition.EnsureTable("ensured");
|
||||
await Definition.EnsureFieldIndex("ensured", "test", new[] { "Id", "Category" });
|
||||
exists = await indexExists();
|
||||
exists = await IndexExists();
|
||||
Expect.isTrue(exists, "The index should now exist");
|
||||
return;
|
||||
|
||||
Task<bool> IndexExists() => Custom.Scalar(
|
||||
$"SELECT EXISTS (SELECT 1 FROM {SqliteDb.Catalog} WHERE name = 'idx_ensured_test') AS it",
|
||||
Parameters.None, Results.ToExists);
|
||||
})
|
||||
}),
|
||||
TestList("Document.Insert", new[]
|
||||
{
|
||||
]),
|
||||
TestList("Document.Insert",
|
||||
[
|
||||
TestCase("succeeds", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -355,9 +311,9 @@ public static class SqliteCSharpTests
|
||||
// This is what is supposed to happen
|
||||
}
|
||||
})
|
||||
}),
|
||||
TestList("Document.Save", new[]
|
||||
{
|
||||
]),
|
||||
TestList("Document.Save",
|
||||
[
|
||||
TestCase("succeeds when a document is inserted", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -388,9 +344,9 @@ public static class SqliteCSharpTests
|
||||
Expect.equal(after!.Id, "test", "The updated document is not correct");
|
||||
Expect.isNull(after.Sub, "There should not have been a sub-document in the updated document");
|
||||
})
|
||||
}),
|
||||
TestList("Count", new[]
|
||||
{
|
||||
]),
|
||||
TestList("Count",
|
||||
[
|
||||
TestCase("All succeeds", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -399,6 +355,7 @@ public static class SqliteCSharpTests
|
||||
var theCount = await Count.All(SqliteDb.TableName);
|
||||
Expect.equal(theCount, 5L, "There should have been 5 matching documents");
|
||||
}),
|
||||
#pragma warning disable CS0618
|
||||
TestCase("ByField succeeds for numeric range", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -415,11 +372,12 @@ public static class SqliteCSharpTests
|
||||
var theCount = await Count.ByField(SqliteDb.TableName, Field.BT("Value", "aardvark", "apple"));
|
||||
Expect.equal(theCount, 1L, "There should have been 1 matching document");
|
||||
})
|
||||
}),
|
||||
TestList("Exists", new[]
|
||||
{
|
||||
TestList("ById", new[]
|
||||
{
|
||||
#pragma warning restore CS0618
|
||||
]),
|
||||
TestList("Exists",
|
||||
[
|
||||
TestList("ById",
|
||||
[
|
||||
TestCase("succeeds when a document exists", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -436,9 +394,10 @@ public static class SqliteCSharpTests
|
||||
var exists = await Exists.ById(SqliteDb.TableName, "seven");
|
||||
Expect.isFalse(exists, "There should not have been an existing document");
|
||||
})
|
||||
}),
|
||||
TestList("ByField", new[]
|
||||
{
|
||||
]),
|
||||
#pragma warning disable CS0618
|
||||
TestList("ByField",
|
||||
[
|
||||
TestCase("succeeds when documents exist", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -455,12 +414,13 @@ public static class SqliteCSharpTests
|
||||
var exists = await Exists.ByField(SqliteDb.TableName, Field.EQ("Nothing", "none"));
|
||||
Expect.isFalse(exists, "There should not have been any existing documents");
|
||||
})
|
||||
})
|
||||
}),
|
||||
TestList("Find", new[]
|
||||
{
|
||||
TestList("All", new[]
|
||||
{
|
||||
])
|
||||
#pragma warning restore CS0618
|
||||
]),
|
||||
TestList("Find",
|
||||
[
|
||||
TestList("All",
|
||||
[
|
||||
TestCase("succeeds when there is data", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -478,9 +438,9 @@ public static class SqliteCSharpTests
|
||||
var results = await Find.All<SubDocument>(SqliteDb.TableName);
|
||||
Expect.isEmpty(results, "There should have been no documents returned");
|
||||
})
|
||||
}),
|
||||
TestList("ById", new[]
|
||||
{
|
||||
]),
|
||||
TestList("ById",
|
||||
[
|
||||
TestCase("succeeds when a document is found", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -498,9 +458,10 @@ public static class SqliteCSharpTests
|
||||
var doc = await Find.ById<string, JsonDocument>(SqliteDb.TableName, "twenty two");
|
||||
Expect.isNull(doc, "There should not have been a document returned");
|
||||
})
|
||||
}),
|
||||
TestList("ByField", new[]
|
||||
{
|
||||
]),
|
||||
#pragma warning disable CS0618
|
||||
TestList("ByField",
|
||||
[
|
||||
TestCase("succeeds when documents are found", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -517,9 +478,9 @@ public static class SqliteCSharpTests
|
||||
var docs = await Find.ByField<JsonDocument>(SqliteDb.TableName, Field.EQ("Value", "mauve"));
|
||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||
})
|
||||
}),
|
||||
TestList("FirstByField", new[]
|
||||
{
|
||||
]),
|
||||
TestList("FirstByField",
|
||||
[
|
||||
TestCase("succeeds when a document is found", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -546,12 +507,13 @@ public static class SqliteCSharpTests
|
||||
var doc = await Find.FirstByField<JsonDocument>(SqliteDb.TableName, Field.EQ("Value", "absent"));
|
||||
Expect.isNull(doc, "There should not have been a document returned");
|
||||
})
|
||||
})
|
||||
}),
|
||||
TestList("Update", new[]
|
||||
{
|
||||
TestList("ById", new[]
|
||||
{
|
||||
])
|
||||
#pragma warning restore CS0618
|
||||
]),
|
||||
TestList("Update",
|
||||
[
|
||||
TestList("ById",
|
||||
[
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -577,9 +539,9 @@ public static class SqliteCSharpTests
|
||||
await Update.ById(SqliteDb.TableName, "test",
|
||||
new JsonDocument { Id = "x", Sub = new() { Foo = "blue", Bar = "red" } });
|
||||
})
|
||||
}),
|
||||
TestList("ByFunc", new[]
|
||||
{
|
||||
]),
|
||||
TestList("ByFunc",
|
||||
[
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -605,12 +567,12 @@ public static class SqliteCSharpTests
|
||||
await Update.ByFunc(SqliteDb.TableName, doc => doc.Id,
|
||||
new JsonDocument { Id = "one", Value = "le un", NumValue = 1 });
|
||||
})
|
||||
}),
|
||||
}),
|
||||
TestList("Patch", new[]
|
||||
{
|
||||
TestList("ById", new[]
|
||||
{
|
||||
]),
|
||||
]),
|
||||
TestList("Patch",
|
||||
[
|
||||
TestList("ById",
|
||||
[
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -632,9 +594,10 @@ public static class SqliteCSharpTests
|
||||
// This not raising an exception is the test
|
||||
await Patch.ById(SqliteDb.TableName, "test", new { Foo = "green" });
|
||||
})
|
||||
}),
|
||||
TestList("ByField", new[]
|
||||
{
|
||||
]),
|
||||
#pragma warning disable CS0618
|
||||
TestList("ByField",
|
||||
[
|
||||
TestCase("succeeds when a document is updated", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -654,12 +617,13 @@ public static class SqliteCSharpTests
|
||||
// This not raising an exception is the test
|
||||
await Patch.ByField(SqliteDb.TableName, Field.EQ("Value", "burgundy"), new { Foo = "green" });
|
||||
})
|
||||
})
|
||||
}),
|
||||
TestList("RemoveFields", new[]
|
||||
{
|
||||
TestList("ById", new[]
|
||||
{
|
||||
])
|
||||
#pragma warning restore CS0618
|
||||
]),
|
||||
TestList("RemoveFields",
|
||||
[
|
||||
TestList("ById",
|
||||
[
|
||||
TestCase("succeeds when fields are removed", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -686,9 +650,10 @@ public static class SqliteCSharpTests
|
||||
// This not raising an exception is the test
|
||||
await RemoveFields.ById(SqliteDb.TableName, "two", new[] { "Value" });
|
||||
})
|
||||
}),
|
||||
TestList("ByField", new[]
|
||||
{
|
||||
]),
|
||||
#pragma warning disable CS0618
|
||||
TestList("ByField",
|
||||
[
|
||||
TestCase("succeeds when a field is removed", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -714,12 +679,13 @@ public static class SqliteCSharpTests
|
||||
// This not raising an exception is the test
|
||||
await RemoveFields.ByField(SqliteDb.TableName, Field.NE("Abracadabra", "apple"), new[] { "Value" });
|
||||
})
|
||||
})
|
||||
}),
|
||||
TestList("Delete", new[]
|
||||
{
|
||||
TestList("ById", new[]
|
||||
{
|
||||
])
|
||||
#pragma warning restore CS0618
|
||||
]),
|
||||
TestList("Delete",
|
||||
[
|
||||
TestList("ById",
|
||||
[
|
||||
TestCase("succeeds when a document is deleted", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -738,9 +704,10 @@ public static class SqliteCSharpTests
|
||||
var remaining = await Count.All(SqliteDb.TableName);
|
||||
Expect.equal(remaining, 5L, "There should have been 5 documents remaining");
|
||||
})
|
||||
}),
|
||||
TestList("ByField", new[]
|
||||
{
|
||||
]),
|
||||
#pragma warning disable CS0618
|
||||
TestList("ByField",
|
||||
[
|
||||
TestCase("succeeds when documents are deleted", async () =>
|
||||
{
|
||||
await using var db = await SqliteDb.BuildDb();
|
||||
@@ -759,14 +726,15 @@ public static class SqliteCSharpTests
|
||||
var remaining = await Count.All(SqliteDb.TableName);
|
||||
Expect.equal(remaining, 5L, "There should have been 5 documents remaining");
|
||||
})
|
||||
})
|
||||
}),
|
||||
])
|
||||
#pragma warning restore CS0618
|
||||
]),
|
||||
TestCase("Clean up database", () => Sqlite.Configuration.UseConnectionString("data source=:memory:"))
|
||||
});
|
||||
]);
|
||||
|
||||
/// <summary>
|
||||
/// All tests for SQLite C# functions and methods
|
||||
/// </summary>
|
||||
[Tests]
|
||||
public static readonly Test All = TestList("Sqlite.C#", new[] { Unit, TestSequenced(Integration) });
|
||||
public static readonly Test All = TestList("Sqlite.C#", [Unit, TestSequenced(Integration)]);
|
||||
}
|
||||
|
||||
@@ -44,57 +44,142 @@ let all =
|
||||
Expect.equal field.Name "Test" "Field name incorrect"
|
||||
Expect.equal field.Op EQ "Operator incorrect"
|
||||
Expect.equal field.Value 14 "Value incorrect"
|
||||
Expect.isNone field.ParameterName "The default parameter name should be None"
|
||||
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
||||
}
|
||||
test "GT succeeds" {
|
||||
let field = Field.GT "Great" "night"
|
||||
Expect.equal field.Name "Great" "Field name incorrect"
|
||||
Expect.equal field.Op GT "Operator incorrect"
|
||||
Expect.equal field.Value "night" "Value incorrect"
|
||||
Expect.isNone field.ParameterName "The default parameter name should be None"
|
||||
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
||||
}
|
||||
test "GE succeeds" {
|
||||
let field = Field.GE "Nice" 88L
|
||||
Expect.equal field.Name "Nice" "Field name incorrect"
|
||||
Expect.equal field.Op GE "Operator incorrect"
|
||||
Expect.equal field.Value 88L "Value incorrect"
|
||||
Expect.isNone field.ParameterName "The default parameter name should be None"
|
||||
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
||||
}
|
||||
test "LT succeeds" {
|
||||
let field = Field.LT "Lesser" "seven"
|
||||
Expect.equal field.Name "Lesser" "Field name incorrect"
|
||||
Expect.equal field.Op LT "Operator incorrect"
|
||||
Expect.equal field.Value "seven" "Value incorrect"
|
||||
Expect.isNone field.ParameterName "The default parameter name should be None"
|
||||
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
||||
}
|
||||
test "LE succeeds" {
|
||||
let field = Field.LE "Nobody" "KNOWS";
|
||||
Expect.equal field.Name "Nobody" "Field name incorrect"
|
||||
Expect.equal field.Op LE "Operator incorrect"
|
||||
Expect.equal field.Value "KNOWS" "Value incorrect"
|
||||
Expect.isNone field.ParameterName "The default parameter name should be None"
|
||||
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
||||
}
|
||||
test "NE succeeds" {
|
||||
let field = Field.NE "Park" "here"
|
||||
Expect.equal field.Name "Park" "Field name incorrect"
|
||||
Expect.equal field.Op NE "Operator incorrect"
|
||||
Expect.equal field.Value "here" "Value incorrect"
|
||||
Expect.isNone field.ParameterName "The default parameter name should be None"
|
||||
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
||||
}
|
||||
test "BT succeeds" {
|
||||
let field = Field.BT "Age" 18 49
|
||||
Expect.equal field.Name "Age" "Field name incorrect"
|
||||
Expect.equal field.Op BT "Operator incorrect"
|
||||
Expect.sequenceEqual (field.Value :?> obj list) [ 18; 49 ] "Value incorrect"
|
||||
Expect.isNone field.ParameterName "The default parameter name should be None"
|
||||
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
||||
}
|
||||
test "EX succeeds" {
|
||||
let field = Field.EX "Groovy"
|
||||
Expect.equal field.Name "Groovy" "Field name incorrect"
|
||||
Expect.equal field.Op EX "Operator incorrect"
|
||||
Expect.isNone field.ParameterName "The default parameter name should be None"
|
||||
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
||||
}
|
||||
test "NEX succeeds" {
|
||||
let field = Field.NEX "Rad"
|
||||
Expect.equal field.Name "Rad" "Field name incorrect"
|
||||
Expect.equal field.Op NEX "Operator incorrect"
|
||||
Expect.isNone field.ParameterName "The default parameter name should be None"
|
||||
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
||||
}
|
||||
test "WithParameterName succeeds" {
|
||||
let field = (Field.EQ "Bob" "Tom").WithParameterName "@name"
|
||||
Expect.isSome field.ParameterName "The parameter name should have been filled"
|
||||
Expect.equal "@name" field.ParameterName.Value "The parameter name is incorrect"
|
||||
}
|
||||
test "WithQualifier succeeds" {
|
||||
let field = (Field.EQ "Bill" "Matt").WithQualifier "joe"
|
||||
Expect.isSome field.Qualifier "The table qualifier should have been filled"
|
||||
Expect.equal "joe" field.Qualifier.Value "The table qualifier is incorrect"
|
||||
}
|
||||
testList "Path" [
|
||||
test "succeeds for a PostgreSQL single field with no qualifier" {
|
||||
let field = Field.GE "SomethingCool" 18
|
||||
Expect.equal "data->>'SomethingCool'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect"
|
||||
}
|
||||
test "succeeds for a PostgreSQL single field with a qualifier" {
|
||||
let field = { Field.LT "SomethingElse" 9 with Qualifier = Some "this" }
|
||||
Expect.equal
|
||||
"this.data->>'SomethingElse'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect"
|
||||
}
|
||||
test "succeeds for a PostgreSQL nested field with no qualifier" {
|
||||
let field = Field.EQ "My.Nested.Field" "howdy"
|
||||
Expect.equal "data#>>'{My,Nested,Field}'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect"
|
||||
}
|
||||
test "succeeds for a PostgreSQL nested field with a qualifier" {
|
||||
let field = { Field.EQ "Nest.Away" "doc" with Qualifier = Some "bird" }
|
||||
Expect.equal "bird.data#>>'{Nest,Away}'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect"
|
||||
}
|
||||
test "succeeds for a SQLite single field with no qualifier" {
|
||||
let field = Field.GE "SomethingCool" 18
|
||||
Expect.equal "data->>'SomethingCool'" (field.Path SQLite) "The SQLite path is incorrect"
|
||||
}
|
||||
test "succeeds for a SQLite single field with a qualifier" {
|
||||
let field = { Field.LT "SomethingElse" 9 with Qualifier = Some "this" }
|
||||
Expect.equal "this.data->>'SomethingElse'" (field.Path SQLite) "The SQLite path is incorrect"
|
||||
}
|
||||
test "succeeds for a SQLite nested field with no qualifier" {
|
||||
let field = Field.EQ "My.Nested.Field" "howdy"
|
||||
Expect.equal "data->>'My'->>'Nested'->>'Field'" (field.Path SQLite) "The SQLite path is incorrect"
|
||||
}
|
||||
test "succeeds for a SQLite nested field with a qualifier" {
|
||||
let field = { Field.EQ "Nest.Away" "doc" with Qualifier = Some "bird" }
|
||||
Expect.equal "bird.data->>'Nest'->>'Away'" (field.Path SQLite) "The SQLite path is incorrect"
|
||||
}
|
||||
]
|
||||
]
|
||||
testList "FieldMatch.ToString" [
|
||||
test "succeeds for Any" {
|
||||
Expect.equal (string Any) "OR" "SQL for Any is incorrect"
|
||||
}
|
||||
test "succeeds for All" {
|
||||
Expect.equal (string All) "AND" "SQL for All is incorrect"
|
||||
}
|
||||
]
|
||||
testList "ParameterName.Derive" [
|
||||
test "succeeds with existing name" {
|
||||
let name = ParameterName()
|
||||
Expect.equal (name.Derive(Some "@taco")) "@taco" "Name should have been @taco"
|
||||
Expect.equal (name.Derive None) "@field0" "Counter should not have advanced for named field"
|
||||
}
|
||||
test "succeeds with non-existent name" {
|
||||
let name = ParameterName()
|
||||
Expect.equal (name.Derive None) "@field0" "Anonymous field name should have been returned"
|
||||
Expect.equal (name.Derive None) "@field1" "Counter should have advanced from previous call"
|
||||
Expect.equal (name.Derive None) "@field2" "Counter should have advanced from previous call"
|
||||
Expect.equal (name.Derive None) "@field3" "Counter should have advanced from previous call"
|
||||
}
|
||||
]
|
||||
testList "Query" [
|
||||
test "selectFromTable succeeds" {
|
||||
Expect.equal (Query.selectFromTable tbl) $"SELECT data FROM {tbl}" "SELECT statement not correct"
|
||||
test "statementWhere succeeds" {
|
||||
Expect.equal (Query.statementWhere "x" "y") "x WHERE y" "Statements not combined correctly"
|
||||
}
|
||||
testList "Definition" [
|
||||
test "ensureTableFor succeeds" {
|
||||
@@ -106,25 +191,40 @@ let all =
|
||||
testList "ensureKey" [
|
||||
test "succeeds when a schema is present" {
|
||||
Expect.equal
|
||||
(Query.Definition.ensureKey "test.table")
|
||||
(Query.Definition.ensureKey "test.table" PostgreSQL)
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data->>'Id'))"
|
||||
"CREATE INDEX for key statement with schema not constructed correctly"
|
||||
}
|
||||
test "succeeds when a schema is not present" {
|
||||
Expect.equal
|
||||
(Query.Definition.ensureKey "table")
|
||||
(Query.Definition.ensureKey "table" SQLite)
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON table ((data->>'Id'))"
|
||||
"CREATE INDEX for key statement without schema not constructed correctly"
|
||||
}
|
||||
]
|
||||
test "ensureIndexOn succeeds for multiple fields and directions" {
|
||||
testList "ensureIndexOn" [
|
||||
test "succeeds for multiple fields and directions" {
|
||||
Expect.equal
|
||||
(Query.Definition.ensureIndexOn "test.table" "gibberish" [ "taco"; "guac DESC"; "salsa ASC" ])
|
||||
(Query.Definition.ensureIndexOn
|
||||
"test.table" "gibberish" [ "taco"; "guac DESC"; "salsa ASC" ] PostgreSQL)
|
||||
([ "CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table "
|
||||
"((data->>'taco'), (data->>'guac') DESC, (data->>'salsa') ASC)" ]
|
||||
|> String.concat "")
|
||||
"CREATE INDEX for multiple field statement incorrect"
|
||||
}
|
||||
test "succeeds for nested PostgreSQL field" {
|
||||
Expect.equal
|
||||
(Query.Definition.ensureIndexOn tbl "nest" [ "a.b.c" ] PostgreSQL)
|
||||
$"CREATE INDEX IF NOT EXISTS idx_{tbl}_nest ON {tbl} ((data#>>'{{a,b,c}}'))"
|
||||
"CREATE INDEX for nested PostgreSQL field incorrect"
|
||||
}
|
||||
test "succeeds for nested SQLite field" {
|
||||
Expect.equal
|
||||
(Query.Definition.ensureIndexOn tbl "nest" [ "a.b.c" ] SQLite)
|
||||
$"CREATE INDEX IF NOT EXISTS idx_{tbl}_nest ON {tbl} ((data->>'a'->>'b'->>'c'))"
|
||||
"CREATE INDEX for nested SQLite field incorrect"
|
||||
}
|
||||
]
|
||||
]
|
||||
test "insert succeeds" {
|
||||
Expect.equal (Query.insert tbl) $"INSERT INTO {tbl} VALUES (@data)" "INSERT statement not correct"
|
||||
@@ -135,6 +235,23 @@ let all =
|
||||
$"INSERT INTO {tbl} VALUES (@data) ON CONFLICT ((data->>'Id')) DO UPDATE SET data = EXCLUDED.data"
|
||||
"INSERT ON CONFLICT UPDATE statement not correct"
|
||||
}
|
||||
test "count succeeds" {
|
||||
Expect.equal (Query.count tbl) $"SELECT COUNT(*) AS it FROM {tbl}" "Count query not correct"
|
||||
}
|
||||
test "exists succeeds" {
|
||||
Expect.equal
|
||||
(Query.exists tbl "turkey")
|
||||
$"SELECT EXISTS (SELECT 1 FROM {tbl} WHERE turkey) AS it"
|
||||
"Exists query not correct"
|
||||
}
|
||||
test "find succeeds" {
|
||||
Expect.equal (Query.find tbl) $"SELECT data FROM {tbl}" "Find query not correct"
|
||||
}
|
||||
test "update succeeds" {
|
||||
Expect.equal (Query.update tbl) $"UPDATE {tbl} SET data = @data" "Update query not correct"
|
||||
}
|
||||
test "delete succeeds" {
|
||||
Expect.equal (Query.delete tbl) $"DELETE FROM {tbl}" "Delete query not correct"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ open Expecto
|
||||
open Npgsql
|
||||
open Types
|
||||
|
||||
#nowarn "0044"
|
||||
|
||||
/// Open a connection to the throwaway database
|
||||
let private mkConn (db: ThrowawayPostgresDb) =
|
||||
let conn = new NpgsqlConnection(db.ConnectionString)
|
||||
|
||||
@@ -5,52 +5,147 @@ open BitBadger.Documents
|
||||
open BitBadger.Documents.Postgres
|
||||
open BitBadger.Documents.Tests
|
||||
|
||||
#nowarn "0044"
|
||||
|
||||
/// Tests which do not hit the database
|
||||
let unitTests =
|
||||
testList "Unit" [
|
||||
testList "Parameters" [
|
||||
test "idParam succeeds" {
|
||||
Expect.equal (idParam 88) ("@id", Sql.string "88") "ID parameter not constructed correctly"
|
||||
testList "idParam" [
|
||||
// NOTE: these tests also exercise all branches of the internal parameterFor function
|
||||
test "succeeds for byte ID" {
|
||||
Expect.equal
|
||||
(idParam (sbyte 7)) ("@id", Sql.int8 (sbyte 7)) "Byte ID parameter not constructed correctly"
|
||||
}
|
||||
test "succeeds for unsigned byte ID" {
|
||||
Expect.equal
|
||||
(idParam (byte 7))
|
||||
("@id", Sql.int8 (int8 (byte 7)))
|
||||
"Unsigned byte ID parameter not constructed correctly"
|
||||
}
|
||||
test "succeeds for short ID" {
|
||||
Expect.equal
|
||||
(idParam (int16 44))
|
||||
("@id", Sql.int16 (int16 44))
|
||||
"Short ID parameter not constructed correctly"
|
||||
}
|
||||
test "succeeds for unsigned short ID" {
|
||||
Expect.equal
|
||||
(idParam (uint16 64))
|
||||
("@id", Sql.int16 (int16 64))
|
||||
"Unsigned short ID parameter not constructed correctly"
|
||||
}
|
||||
test "succeeds for integer ID" {
|
||||
Expect.equal (idParam 88) ("@id", Sql.int 88) "Int ID parameter not constructed correctly"
|
||||
}
|
||||
test "succeeds for unsigned integer ID" {
|
||||
Expect.equal
|
||||
(idParam (uint 889)) ("@id", Sql.int 889) "Unsigned int ID parameter not constructed correctly"
|
||||
}
|
||||
test "succeeds for long ID" {
|
||||
Expect.equal
|
||||
(idParam (int64 123))
|
||||
("@id", Sql.int64 (int64 123))
|
||||
"Long ID parameter not constructed correctly"
|
||||
}
|
||||
test "succeeds for unsigned long ID" {
|
||||
Expect.equal
|
||||
(idParam (uint64 6464))
|
||||
("@id", Sql.int64 (int64 6464))
|
||||
"Unsigned long ID parameter not constructed correctly"
|
||||
}
|
||||
test "succeeds for decimal ID" {
|
||||
Expect.equal
|
||||
(idParam (decimal 4.56))
|
||||
("@id", Sql.decimal (decimal 4.56))
|
||||
"Decimal ID parameter not constructed correctly"
|
||||
}
|
||||
test "succeeds for single ID" {
|
||||
Expect.equal
|
||||
(idParam (single 5.67))
|
||||
("@id", Sql.double (double (single 5.67)))
|
||||
"Single ID parameter not constructed correctly"
|
||||
}
|
||||
test "succeeds for double ID" {
|
||||
Expect.equal
|
||||
(idParam (double 6.78))
|
||||
("@id", Sql.double (double 6.78))
|
||||
"Double ID parameter not constructed correctly"
|
||||
}
|
||||
test "succeeds for string ID" {
|
||||
Expect.equal (idParam "99") ("@id", Sql.string "99") "String ID parameter not constructed correctly"
|
||||
}
|
||||
test "succeeds for non-numeric non-string ID" {
|
||||
let target = { new obj() with override _.ToString() = "ToString was called" }
|
||||
Expect.equal
|
||||
(idParam target)
|
||||
("@id", Sql.string "ToString was called")
|
||||
"Non-numeric, non-string parameter not constructed correctly"
|
||||
}
|
||||
]
|
||||
test "jsonParam succeeds" {
|
||||
Expect.equal
|
||||
(jsonParam "@test" {| Something = "good" |})
|
||||
("@test", Sql.jsonb """{"Something":"good"}""")
|
||||
"JSON parameter not constructed correctly"
|
||||
}
|
||||
testList "addFieldParam" [
|
||||
testList "addFieldParams" [
|
||||
test "succeeds when a parameter is added" {
|
||||
let paramList = addFieldParam "@field" (Field.EQ "it" "242") []
|
||||
let paramList = addFieldParams [ Field.EQ "it" "242" ] []
|
||||
Expect.hasLength paramList 1 "There should have been a parameter added"
|
||||
let it = paramList[0]
|
||||
Expect.equal (fst it) "@field" "Field parameter name not correct"
|
||||
match snd it with
|
||||
| SqlValue.Parameter value ->
|
||||
Expect.equal value.ParameterName "@field" "Parameter name not correct"
|
||||
Expect.equal value.Value "242" "Parameter value not correct"
|
||||
| _ -> Expect.isTrue false "The parameter was not a Parameter type"
|
||||
let name, value = Seq.head paramList
|
||||
Expect.equal name "@field0" "Field parameter name not correct"
|
||||
Expect.equal value (Sql.string "242") "Parameter value not correct"
|
||||
}
|
||||
test "succeeds when multiple independent parameters are added" {
|
||||
let paramList = addFieldParams [ Field.EQ "me" "you"; Field.GT "us" "them" ] [ idParam 14 ]
|
||||
Expect.hasLength paramList 3 "There should have been 2 parameters added"
|
||||
let p = Array.ofSeq paramList
|
||||
Expect.equal (fst p[0]) "@id" "First field parameter name not correct"
|
||||
Expect.equal (snd p[0]) (Sql.int 14) "First parameter value not correct"
|
||||
Expect.equal (fst p[1]) "@field0" "Second field parameter name not correct"
|
||||
Expect.equal (snd p[1]) (Sql.string "you") "Second parameter value not correct"
|
||||
Expect.equal (fst p[2]) "@field1" "Third parameter name not correct"
|
||||
Expect.equal (snd p[2]) (Sql.string "them") "Third parameter value not correct"
|
||||
}
|
||||
test "succeeds when a parameter is not added" {
|
||||
let paramList = addFieldParam "@field" (Field.EX "tacos") []
|
||||
let paramList = addFieldParams [ Field.EX "tacos" ] []
|
||||
Expect.isEmpty paramList "There should not have been any parameters added"
|
||||
}
|
||||
test "succeeds when two parameters are added" {
|
||||
let paramList = addFieldParam "@field" (Field.BT "that" "eh" "zed") []
|
||||
test "succeeds when two parameters are added for one field" {
|
||||
let paramList =
|
||||
addFieldParams [ { Field.BT "that" "eh" "zed" with ParameterName = Some "@test" } ] []
|
||||
Expect.hasLength paramList 2 "There should have been 2 parameters added"
|
||||
let min = paramList[0]
|
||||
Expect.equal (fst min) "@fieldmin" "Minimum field name not correct"
|
||||
match snd min with
|
||||
| SqlValue.Parameter value ->
|
||||
Expect.equal value.ParameterName "@fieldmin" "Minimum parameter name not correct"
|
||||
Expect.equal value.Value "eh" "Minimum parameter value not correct"
|
||||
| _ -> Expect.isTrue false "Minimum parameter was not a Parameter type"
|
||||
let max = paramList[1]
|
||||
Expect.equal (fst max) "@fieldmax" "Maximum field name not correct"
|
||||
match snd max with
|
||||
| SqlValue.Parameter value ->
|
||||
Expect.equal value.ParameterName "@fieldmax" "Maximum parameter name not correct"
|
||||
Expect.equal value.Value "zed" "Maximum parameter value not correct"
|
||||
| _ -> Expect.isTrue false "Maximum parameter was not a Parameter type"
|
||||
let name, value = Seq.head paramList
|
||||
Expect.equal name "@testmin" "Minimum field name not correct"
|
||||
Expect.equal value (Sql.string "eh") "Minimum parameter value not correct"
|
||||
let name, value = paramList |> Seq.skip 1 |> Seq.head
|
||||
Expect.equal name "@testmax" "Maximum field name not correct"
|
||||
Expect.equal value (Sql.string "zed") "Maximum parameter value not correct"
|
||||
}
|
||||
]
|
||||
testList "fieldNameParams" [
|
||||
test "succeeds for one name" {
|
||||
let name, value = fieldNameParams [ "bob" ]
|
||||
Expect.equal name "@name" "The parameter name was incorrect"
|
||||
Expect.equal value (Sql.string "bob") "The parameter value was incorrect"
|
||||
}
|
||||
test "succeeds for multiple names" {
|
||||
let name, value = fieldNameParams [ "bob"; "tom"; "mike" ]
|
||||
Expect.equal name "@name" "The parameter name was incorrect"
|
||||
Expect.equal value (Sql.stringArray [| "bob"; "tom"; "mike" |]) "The parameter value was incorrect"
|
||||
}
|
||||
]
|
||||
testList "fieldNameParam" [
|
||||
test "succeeds for one name" {
|
||||
let name, value = fieldNameParam [ "bob" ]
|
||||
Expect.equal name "@name" "The parameter name was incorrect"
|
||||
Expect.equal value (Sql.string "bob") "The parameter value was incorrect"
|
||||
}
|
||||
test "succeeds for multiple names" {
|
||||
let name, value = fieldNameParam [ "bob"; "tom"; "mike" ]
|
||||
Expect.equal name "@name" "The parameter name was incorrect"
|
||||
Expect.equal value (Sql.stringArray [| "bob"; "tom"; "mike" |]) "The parameter value was incorrect"
|
||||
}
|
||||
]
|
||||
test "noParams succeeds" {
|
||||
@@ -58,35 +153,64 @@ let unitTests =
|
||||
}
|
||||
]
|
||||
testList "Query" [
|
||||
testList "whereByField" [
|
||||
test "succeeds when a logical operator is passed" {
|
||||
testList "whereByFields" [
|
||||
test "succeeds for a single field when a logical operator is passed" {
|
||||
Expect.equal
|
||||
(Query.whereByField (Field.GT "theField" 0) "@test")
|
||||
(Query.whereByFields Any [ { Field.GT "theField" "0" with ParameterName = Some "@test" } ])
|
||||
"data->>'theField' > @test"
|
||||
"WHERE clause not correct"
|
||||
}
|
||||
test "succeeds when an existence operator is passed" {
|
||||
test "succeeds for a single field when an existence operator is passed" {
|
||||
Expect.equal
|
||||
(Query.whereByField (Field.NEX "thatField") "")
|
||||
(Query.whereByFields Any [ Field.NEX "thatField" ])
|
||||
"data->>'thatField' IS NULL"
|
||||
"WHERE clause not correct"
|
||||
}
|
||||
test "succeeds when a between operator is passed with numeric values" {
|
||||
test "succeeds for a single field when a between operator is passed with numeric values" {
|
||||
Expect.equal
|
||||
(Query.whereByField (Field.BT "aField" 50 99) "@range")
|
||||
(Query.whereByFields All [ { Field.BT "aField" 50 99 with ParameterName = Some "@range" } ])
|
||||
"(data->>'aField')::numeric BETWEEN @rangemin AND @rangemax"
|
||||
"WHERE clause not correct"
|
||||
}
|
||||
test "succeeds when a between operator is passed with non-numeric values" {
|
||||
test "succeeds for a single field when a between operator is passed with non-numeric values" {
|
||||
Expect.equal
|
||||
(Query.whereByField (Field.BT "field0" "a" "b") "@alpha")
|
||||
(Query.whereByFields Any [ { Field.BT "field0" "a" "b" with ParameterName = Some "@alpha" } ])
|
||||
"data->>'field0' BETWEEN @alphamin AND @alphamax"
|
||||
"WHERE clause not correct"
|
||||
}
|
||||
]
|
||||
test "whereById succeeds" {
|
||||
Expect.equal (Query.whereById "@id") "data->>'Id' = @id" "WHERE clause not correct"
|
||||
test "succeeds for all multiple fields with logical operators" {
|
||||
Expect.equal
|
||||
(Query.whereByFields All [ Field.EQ "theFirst" "1"; Field.EQ "numberTwo" "2" ])
|
||||
"data->>'theFirst' = @field0 AND data->>'numberTwo' = @field1"
|
||||
"WHERE clause not correct"
|
||||
}
|
||||
test "succeeds for any multiple fields with an existence operator" {
|
||||
Expect.equal
|
||||
(Query.whereByFields Any [ Field.NEX "thatField"; Field.GE "thisField" 18 ])
|
||||
"data->>'thatField' IS NULL OR (data->>'thisField')::numeric >= @field0"
|
||||
"WHERE clause not correct"
|
||||
}
|
||||
test "succeeds for all multiple fields with between operators" {
|
||||
Expect.equal
|
||||
(Query.whereByFields All [ Field.BT "aField" 50 99; Field.BT "anotherField" "a" "b" ])
|
||||
"(data->>'aField')::numeric BETWEEN @field0min AND @field0max AND data->>'anotherField' BETWEEN @field1min AND @field1max"
|
||||
"WHERE clause not correct"
|
||||
}
|
||||
]
|
||||
testList "whereById" [
|
||||
test "succeeds for numeric ID" {
|
||||
Expect.equal (Query.whereById 18) "(data->>'Id')::numeric = @id" "WHERE clause not correct"
|
||||
}
|
||||
test "succeeds for string ID" {
|
||||
Expect.equal (Query.whereById "18") "data->>'Id' = @id" "WHERE clause not correct"
|
||||
}
|
||||
test "succeeds for non-numeric non-string ID" {
|
||||
Expect.equal
|
||||
(Query.whereById (System.Uri "https://example.com"))
|
||||
"data->>'Id' = @id"
|
||||
"WHERE clause not correct"
|
||||
}
|
||||
]
|
||||
testList "Definition" [
|
||||
test "ensureTable succeeds" {
|
||||
Expect.equal
|
||||
@@ -108,172 +232,40 @@ let unitTests =
|
||||
"CREATE INDEX statement not constructed correctly"
|
||||
}
|
||||
]
|
||||
test "update succeeds" {
|
||||
Expect.equal
|
||||
(Query.update PostgresDb.TableName)
|
||||
$"UPDATE {PostgresDb.TableName} SET data = @data WHERE data->>'Id' = @id"
|
||||
"UPDATE full statement not correct"
|
||||
}
|
||||
test "whereDataContains succeeds" {
|
||||
Expect.equal (Query.whereDataContains "@test") "data @> @test" "WHERE clause not correct"
|
||||
}
|
||||
test "whereJsonPathMatches succeeds" {
|
||||
Expect.equal (Query.whereJsonPathMatches "@path") "data @? @path::jsonpath" "WHERE clause not correct"
|
||||
}
|
||||
testList "Count" [
|
||||
test "all succeeds" {
|
||||
test "patch succeeds" {
|
||||
Expect.equal
|
||||
(Query.Count.all PostgresDb.TableName)
|
||||
$"SELECT COUNT(*) AS it FROM {PostgresDb.TableName}"
|
||||
"Count query not correct"
|
||||
(Query.patch PostgresDb.TableName)
|
||||
$"UPDATE {PostgresDb.TableName} SET data = data || @data"
|
||||
"Patch query not correct"
|
||||
}
|
||||
test "byField succeeds" {
|
||||
test "removeFields succeeds" {
|
||||
Expect.equal
|
||||
(Query.Count.byField PostgresDb.TableName (Field.EQ "thatField" 0))
|
||||
$"SELECT COUNT(*) AS it FROM {PostgresDb.TableName} WHERE data->>'thatField' = @field"
|
||||
"JSON field text comparison count query not correct"
|
||||
(Query.removeFields PostgresDb.TableName)
|
||||
$"UPDATE {PostgresDb.TableName} SET data = data - @name"
|
||||
"Field removal query not correct"
|
||||
}
|
||||
test "byContains succeeds" {
|
||||
Expect.equal
|
||||
(Query.Count.byContains PostgresDb.TableName)
|
||||
$"SELECT COUNT(*) AS it FROM {PostgresDb.TableName} WHERE data @> @criteria"
|
||||
"JSON containment count query not correct"
|
||||
}
|
||||
test "byJsonPath succeeds" {
|
||||
Expect.equal
|
||||
(Query.Count.byJsonPath PostgresDb.TableName)
|
||||
$"SELECT COUNT(*) AS it FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath"
|
||||
"JSON Path match count query not correct"
|
||||
}
|
||||
]
|
||||
testList "Exists" [
|
||||
test "byId succeeds" {
|
||||
Expect.equal
|
||||
(Query.Exists.byId PostgresDb.TableName)
|
||||
$"SELECT EXISTS (SELECT 1 FROM {PostgresDb.TableName} WHERE data->>'Id' = @id) AS it"
|
||||
"ID existence query not correct"
|
||||
Expect.equal (Query.byId "test" "14") "test WHERE data->>'Id' = @id" "By-ID query not correct"
|
||||
}
|
||||
test "byField succeeds" {
|
||||
test "byFields succeeds" {
|
||||
Expect.equal
|
||||
(Query.Exists.byField PostgresDb.TableName (Field.LT "Test" 0))
|
||||
$"SELECT EXISTS (SELECT 1 FROM {PostgresDb.TableName} WHERE data->>'Test' < @field) AS it"
|
||||
"JSON field text comparison exists query not correct"
|
||||
(Query.byFields "unit" Any [ Field.GT "That" 14 ])
|
||||
"unit WHERE (data->>'That')::numeric > @field0"
|
||||
"By-Field query not correct"
|
||||
}
|
||||
test "byContains succeeds" {
|
||||
Expect.equal (Query.byContains "exam") "exam WHERE data @> @criteria" "By-Contains query not correct"
|
||||
}
|
||||
test "byPathMach succeeds" {
|
||||
Expect.equal
|
||||
(Query.Exists.byContains PostgresDb.TableName)
|
||||
$"SELECT EXISTS (SELECT 1 FROM {PostgresDb.TableName} WHERE data @> @criteria) AS it"
|
||||
"JSON containment exists query not correct"
|
||||
(Query.byPathMatch "verify") "verify WHERE data @? @path::jsonpath" "By-JSON Path query not correct"
|
||||
}
|
||||
test "byJsonPath succeeds" {
|
||||
Expect.equal
|
||||
(Query.Exists.byJsonPath PostgresDb.TableName)
|
||||
$"SELECT EXISTS (SELECT 1 FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath) AS it"
|
||||
"JSON Path match existence query not correct"
|
||||
}
|
||||
]
|
||||
testList "Find" [
|
||||
test "byId succeeds" {
|
||||
Expect.equal
|
||||
(Query.Find.byId PostgresDb.TableName)
|
||||
$"SELECT data FROM {PostgresDb.TableName} WHERE data->>'Id' = @id"
|
||||
"SELECT by ID query not correct"
|
||||
}
|
||||
test "byField succeeds" {
|
||||
Expect.equal
|
||||
(Query.Find.byField PostgresDb.TableName (Field.GE "Golf" 0))
|
||||
$"SELECT data FROM {PostgresDb.TableName} WHERE data->>'Golf' >= @field"
|
||||
"SELECT by JSON comparison query not correct"
|
||||
}
|
||||
test "byContains succeeds" {
|
||||
Expect.equal
|
||||
(Query.Find.byContains PostgresDb.TableName)
|
||||
$"SELECT data FROM {PostgresDb.TableName} WHERE data @> @criteria"
|
||||
"SELECT by JSON containment query not correct"
|
||||
}
|
||||
test "byJsonPath succeeds" {
|
||||
Expect.equal
|
||||
(Query.Find.byJsonPath PostgresDb.TableName)
|
||||
$"SELECT data FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath"
|
||||
"SELECT by JSON Path match query not correct"
|
||||
}
|
||||
]
|
||||
testList "Patch" [
|
||||
test "byId succeeds" {
|
||||
Expect.equal
|
||||
(Query.Patch.byId PostgresDb.TableName)
|
||||
$"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data->>'Id' = @id"
|
||||
"UPDATE partial by ID statement not correct"
|
||||
}
|
||||
test "byField succeeds" {
|
||||
Expect.equal
|
||||
(Query.Patch.byField PostgresDb.TableName (Field.LT "Snail" 0))
|
||||
$"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data->>'Snail' < @field"
|
||||
"UPDATE partial by ID statement not correct"
|
||||
}
|
||||
test "byContains succeeds" {
|
||||
Expect.equal
|
||||
(Query.Patch.byContains PostgresDb.TableName)
|
||||
$"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data @> @criteria"
|
||||
"UPDATE partial by JSON containment statement not correct"
|
||||
}
|
||||
test "byJsonPath succeeds" {
|
||||
Expect.equal
|
||||
(Query.Patch.byJsonPath PostgresDb.TableName)
|
||||
$"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data @? @path::jsonpath"
|
||||
"UPDATE partial by JSON Path statement not correct"
|
||||
}
|
||||
]
|
||||
testList "RemoveFields" [
|
||||
test "byId succeeds" {
|
||||
Expect.equal
|
||||
(Query.RemoveFields.byId "tbl")
|
||||
"UPDATE tbl SET data = data - @name WHERE data->>'Id' = @id"
|
||||
"Remove field by ID query not correct"
|
||||
}
|
||||
test "byField succeeds" {
|
||||
Expect.equal
|
||||
(Query.RemoveFields.byField "tbl" (Field.LT "Fly" 0))
|
||||
"UPDATE tbl SET data = data - @name WHERE data->>'Fly' < @field"
|
||||
"Remove field by field query not correct"
|
||||
}
|
||||
test "byContains succeeds" {
|
||||
Expect.equal
|
||||
(Query.RemoveFields.byContains "tbl")
|
||||
"UPDATE tbl SET data = data - @name WHERE data @> @criteria"
|
||||
"Remove field by contains query not correct"
|
||||
}
|
||||
test "byJsonPath succeeds" {
|
||||
Expect.equal
|
||||
(Query.RemoveFields.byJsonPath "tbl")
|
||||
"UPDATE tbl SET data = data - @name WHERE data @? @path::jsonpath"
|
||||
"Remove field by JSON path query not correct"
|
||||
}
|
||||
]
|
||||
testList "Delete" [
|
||||
test "byId succeeds" {
|
||||
Expect.equal
|
||||
(Query.Delete.byId PostgresDb.TableName)
|
||||
$"DELETE FROM {PostgresDb.TableName} WHERE data->>'Id' = @id"
|
||||
"DELETE by ID query not correct"
|
||||
}
|
||||
test "byField succeeds" {
|
||||
Expect.equal
|
||||
(Query.Delete.byField PostgresDb.TableName (Field.NEX "gone"))
|
||||
$"DELETE FROM {PostgresDb.TableName} WHERE data->>'gone' IS NULL"
|
||||
"DELETE by JSON comparison query not correct"
|
||||
}
|
||||
test "byContains succeeds" {
|
||||
Expect.equal (Query.Delete.byContains PostgresDb.TableName)
|
||||
$"DELETE FROM {PostgresDb.TableName} WHERE data @> @criteria"
|
||||
"DELETE by JSON containment query not correct"
|
||||
}
|
||||
test "byJsonPath succeeds" {
|
||||
Expect.equal (Query.Delete.byJsonPath PostgresDb.TableName)
|
||||
$"DELETE FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath"
|
||||
"DELETE by JSON Path match query not correct"
|
||||
}
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
@@ -488,20 +480,39 @@ let integrationTests =
|
||||
let! theCount = Count.all PostgresDb.TableName
|
||||
Expect.equal theCount 5 "There should have been 5 matching documents"
|
||||
}
|
||||
testTask "byField succeeds for numeric range" {
|
||||
testList "byFields" [
|
||||
testTask "succeeds when items are found" {
|
||||
use db = PostgresDb.BuildDb()
|
||||
do! loadDocs ()
|
||||
|
||||
let! theCount =
|
||||
Count.byFields PostgresDb.TableName Any [ Field.BT "NumValue" 15 20; Field.EQ "NumValue" 0 ]
|
||||
Expect.equal theCount 3 "There should have been 3 matching documents"
|
||||
}
|
||||
testTask "succeeds when items are not found" {
|
||||
use db = PostgresDb.BuildDb()
|
||||
do! loadDocs ()
|
||||
|
||||
let! theCount = Count.byFields PostgresDb.TableName All [ Field.EX "Sub"; Field.GT "NumValue" 100 ]
|
||||
Expect.equal theCount 0 "There should have been no matching documents"
|
||||
}
|
||||
]
|
||||
testList "byField" [
|
||||
testTask "succeeds for numeric range" {
|
||||
use db = PostgresDb.BuildDb()
|
||||
do! loadDocs ()
|
||||
|
||||
let! theCount = Count.byField PostgresDb.TableName (Field.BT "NumValue" 10 20)
|
||||
Expect.equal theCount 3 "There should have been 3 matching documents"
|
||||
}
|
||||
testTask "byField succeeds for non-numeric range" {
|
||||
testTask "succeeds for non-numeric range" {
|
||||
use db = PostgresDb.BuildDb()
|
||||
do! loadDocs ()
|
||||
|
||||
let! theCount = Count.byField PostgresDb.TableName (Field.BT "Value" "aardvark" "apple")
|
||||
Expect.equal theCount 1 "There should have been 1 matching document"
|
||||
}
|
||||
]
|
||||
testTask "byContains succeeds" {
|
||||
use db = PostgresDb.BuildDb()
|
||||
do! loadDocs ()
|
||||
@@ -534,6 +545,23 @@ let integrationTests =
|
||||
Expect.isFalse exists "There should not have been an existing document"
|
||||
}
|
||||
]
|
||||
testList "byFields" [
|
||||
testTask "succeeds when documents exist" {
|
||||
use db = PostgresDb.BuildDb()
|
||||
do! loadDocs ()
|
||||
|
||||
let! exists = Exists.byFields PostgresDb.TableName Any [ Field.EX "Sub"; Field.EX "Boo" ]
|
||||
Expect.isTrue exists "There should have been existing documents"
|
||||
}
|
||||
testTask "succeeds when documents do not exist" {
|
||||
use db = PostgresDb.BuildDb()
|
||||
do! loadDocs ()
|
||||
|
||||
let! exists =
|
||||
Exists.byFields PostgresDb.TableName All [ Field.EQ "NumValue" "six"; Field.EX "Nope" ]
|
||||
Expect.isFalse exists "There should not have been existing documents"
|
||||
}
|
||||
]
|
||||
testList "byField" [
|
||||
testTask "succeeds when documents exist" {
|
||||
use db = PostgresDb.BuildDb()
|
||||
@@ -623,6 +651,26 @@ let integrationTests =
|
||||
Expect.isNone doc "There should not have been a document returned"
|
||||
}
|
||||
]
|
||||
testList "byFields" [
|
||||
testTask "succeeds when documents are found" {
|
||||
use db = PostgresDb.BuildDb()
|
||||
do! loadDocs ()
|
||||
|
||||
let! docs =
|
||||
Find.byFields<JsonDocument>
|
||||
PostgresDb.TableName All [ Field.EQ "Value" "purple"; Field.EX "Sub" ]
|
||||
Expect.equal (List.length docs) 1 "There should have been one document returned"
|
||||
}
|
||||
testTask "succeeds when documents are not found" {
|
||||
use db = PostgresDb.BuildDb()
|
||||
do! loadDocs ()
|
||||
|
||||
let! docs =
|
||||
Find.byFields<JsonDocument>
|
||||
PostgresDb.TableName All [ Field.EQ "Value" "mauve"; Field.NE "NumValue" 40 ]
|
||||
Expect.isEmpty docs "There should have been no documents returned"
|
||||
}
|
||||
]
|
||||
testList "byField" [
|
||||
testTask "succeeds when documents are found" {
|
||||
use db = PostgresDb.BuildDb()
|
||||
|
||||
@@ -8,6 +8,8 @@ open Expecto
|
||||
open Microsoft.Data.Sqlite
|
||||
open Types
|
||||
|
||||
#nowarn "0044"
|
||||
|
||||
/// Integration tests for the F# extensions on the SqliteConnection data type
|
||||
let integrationTests =
|
||||
let loadDocs () = backgroundTask {
|
||||
|
||||
@@ -8,129 +8,80 @@ open Expecto
|
||||
open Microsoft.Data.Sqlite
|
||||
open Types
|
||||
|
||||
#nowarn "0044"
|
||||
|
||||
/// Unit tests for the SQLite library
|
||||
let unitTests =
|
||||
testList "Unit" [
|
||||
testList "Query" [
|
||||
testList "whereByField" [
|
||||
test "succeeds when a logical operator is passed" {
|
||||
testList "whereByFields" [
|
||||
test "succeeds for a single field when a logical operator is passed" {
|
||||
Expect.equal
|
||||
(Query.whereByField (Field.GT "theField" 0) "@test")
|
||||
(Query.whereByFields Any [ { Field.GT "theField" 0 with ParameterName = Some "@test" } ])
|
||||
"data->>'theField' > @test"
|
||||
"WHERE clause not correct"
|
||||
}
|
||||
test "succeeds when an existence operator is passed" {
|
||||
test "succeeds for a single field when an existence operator is passed" {
|
||||
Expect.equal
|
||||
(Query.whereByField (Field.NEX "thatField") "")
|
||||
(Query.whereByFields Any [ Field.NEX "thatField" ])
|
||||
"data->>'thatField' IS NULL"
|
||||
"WHERE clause not correct"
|
||||
}
|
||||
test "succeeds when the between operator is passed" {
|
||||
test "succeeds for a single field when a between operator is passed" {
|
||||
Expect.equal
|
||||
(Query.whereByField (Field.BT "aField" 50 99) "@range")
|
||||
(Query.whereByFields All [ { Field.BT "aField" 50 99 with ParameterName = Some "@range" } ])
|
||||
"data->>'aField' BETWEEN @rangemin AND @rangemax"
|
||||
"WHERE clause not correct"
|
||||
}
|
||||
test "succeeds for all multiple fields with logical operators" {
|
||||
Expect.equal
|
||||
(Query.whereByFields All [ Field.EQ "theFirst" "1"; Field.EQ "numberTwo" "2" ])
|
||||
"data->>'theFirst' = @field0 AND data->>'numberTwo' = @field1"
|
||||
"WHERE clause not correct"
|
||||
}
|
||||
test "succeeds for any multiple fields with an existence operator" {
|
||||
Expect.equal
|
||||
(Query.whereByFields Any [ Field.NEX "thatField"; Field.GE "thisField" 18 ])
|
||||
"data->>'thatField' IS NULL OR data->>'thisField' >= @field0"
|
||||
"WHERE clause not correct"
|
||||
}
|
||||
test "succeeds for all multiple fields with between operators" {
|
||||
Expect.equal
|
||||
(Query.whereByFields All [ Field.BT "aField" 50 99; Field.BT "anotherField" "a" "b" ])
|
||||
"data->>'aField' BETWEEN @field0min AND @field0max AND data->>'anotherField' BETWEEN @field1min AND @field1max"
|
||||
"WHERE clause not correct"
|
||||
}
|
||||
]
|
||||
test "whereById succeeds" {
|
||||
Expect.equal (Query.whereById "@id") "data->>'Id' = @id" "WHERE clause not correct"
|
||||
}
|
||||
test "patch succeeds" {
|
||||
Expect.equal
|
||||
(Query.patch SqliteDb.TableName)
|
||||
$"UPDATE {SqliteDb.TableName} SET data = json_patch(data, json(@data))"
|
||||
"Patch query not correct"
|
||||
}
|
||||
test "removeFields succeeds" {
|
||||
Expect.equal
|
||||
(Query.removeFields SqliteDb.TableName [ SqliteParameter("@a", "a"); SqliteParameter("@b", "b") ])
|
||||
$"UPDATE {SqliteDb.TableName} SET data = json_remove(data, @a, @b)"
|
||||
"Field removal query not correct"
|
||||
}
|
||||
test "byId succeeds" {
|
||||
Expect.equal (Query.byId "test" "14") "test WHERE data->>'Id' = @id" "By-ID query not correct"
|
||||
}
|
||||
test "byFields succeeds" {
|
||||
Expect.equal
|
||||
(Query.byFields "unit" Any [ Field.GT "That" 14 ])
|
||||
"unit WHERE data->>'That' > @field0"
|
||||
"By-Field query not correct"
|
||||
}
|
||||
test "Definition.ensureTable succeeds" {
|
||||
Expect.equal
|
||||
(Query.Definition.ensureTable "tbl")
|
||||
"CREATE TABLE IF NOT EXISTS tbl (data TEXT NOT NULL)"
|
||||
"CREATE TABLE statement not correct"
|
||||
}
|
||||
test "update succeeds" {
|
||||
Expect.equal
|
||||
(Query.update "tbl")
|
||||
"UPDATE tbl SET data = @data WHERE data->>'Id' = @id"
|
||||
"UPDATE full statement not correct"
|
||||
}
|
||||
testList "Count" [
|
||||
test "all succeeds" {
|
||||
Expect.equal (Query.Count.all "tbl") $"SELECT COUNT(*) AS it FROM tbl" "Count query not correct"
|
||||
}
|
||||
test "byField succeeds" {
|
||||
Expect.equal
|
||||
(Query.Count.byField "tbl" (Field.EQ "thatField" 0))
|
||||
"SELECT COUNT(*) AS it FROM tbl WHERE data->>'thatField' = @field"
|
||||
"JSON field text comparison count query not correct"
|
||||
}
|
||||
]
|
||||
testList "Exists" [
|
||||
test "byId succeeds" {
|
||||
Expect.equal
|
||||
(Query.Exists.byId "tbl")
|
||||
"SELECT EXISTS (SELECT 1 FROM tbl WHERE data->>'Id' = @id) AS it"
|
||||
"ID existence query not correct"
|
||||
}
|
||||
test "byField succeeds" {
|
||||
Expect.equal
|
||||
(Query.Exists.byField "tbl" (Field.LT "Test" 0))
|
||||
"SELECT EXISTS (SELECT 1 FROM tbl WHERE data->>'Test' < @field) AS it"
|
||||
"JSON field text comparison exists query not correct"
|
||||
}
|
||||
]
|
||||
testList "Find" [
|
||||
test "byId succeeds" {
|
||||
Expect.equal
|
||||
(Query.Find.byId "tbl")
|
||||
"SELECT data FROM tbl WHERE data->>'Id' = @id"
|
||||
"SELECT by ID query not correct"
|
||||
}
|
||||
test "byField succeeds" {
|
||||
Expect.equal
|
||||
(Query.Find.byField "tbl" (Field.GE "Golf" 0))
|
||||
"SELECT data FROM tbl WHERE data->>'Golf' >= @field"
|
||||
"SELECT by JSON comparison query not correct"
|
||||
}
|
||||
]
|
||||
testList "Patch" [
|
||||
test "byId succeeds" {
|
||||
Expect.equal
|
||||
(Query.Patch.byId "tbl")
|
||||
"UPDATE tbl SET data = json_patch(data, json(@data)) WHERE data->>'Id' = @id"
|
||||
"UPDATE partial by ID statement not correct"
|
||||
}
|
||||
test "byField succeeds" {
|
||||
Expect.equal
|
||||
(Query.Patch.byField "tbl" (Field.NE "Part" 0))
|
||||
"UPDATE tbl SET data = json_patch(data, json(@data)) WHERE data->>'Part' <> @field"
|
||||
"UPDATE partial by JSON comparison query not correct"
|
||||
}
|
||||
]
|
||||
testList "RemoveFields" [
|
||||
test "byId succeeds" {
|
||||
Expect.equal
|
||||
(Query.RemoveFields.byId "tbl" [ SqliteParameter("@name", "one") ])
|
||||
"UPDATE tbl SET data = json_remove(data, @name) WHERE data->>'Id' = @id"
|
||||
"Remove field by ID query not correct"
|
||||
}
|
||||
test "byField succeeds" {
|
||||
Expect.equal
|
||||
(Query.RemoveFields.byField
|
||||
"tbl"
|
||||
(Field.GT "Fly" 0)
|
||||
[ SqliteParameter("@name0", "one"); SqliteParameter("@name1", "two") ])
|
||||
"UPDATE tbl SET data = json_remove(data, @name0, @name1) WHERE data->>'Fly' > @field"
|
||||
"Remove field by field query not correct"
|
||||
}
|
||||
]
|
||||
testList "Delete" [
|
||||
test "byId succeeds" {
|
||||
Expect.equal
|
||||
(Query.Delete.byId "tbl")
|
||||
"DELETE FROM tbl WHERE data->>'Id' = @id"
|
||||
"DELETE by ID query not correct"
|
||||
}
|
||||
test "byField succeeds" {
|
||||
Expect.equal
|
||||
(Query.Delete.byField "tbl" (Field.NEX "gone"))
|
||||
"DELETE FROM tbl WHERE data->>'gone' IS NULL"
|
||||
"DELETE by JSON comparison query not correct"
|
||||
}
|
||||
]
|
||||
]
|
||||
testList "Parameters" [
|
||||
test "idParam succeeds" {
|
||||
@@ -147,7 +98,7 @@ let unitTests =
|
||||
test "succeeds when adding a parameter" {
|
||||
let paramList = addFieldParam "@field" (Field.EQ "it" 99) []
|
||||
Expect.hasLength paramList 1 "There should have been a parameter added"
|
||||
let theParam = paramList[0]
|
||||
let theParam = Seq.head paramList
|
||||
Expect.equal theParam.ParameterName "@field" "The parameter name is incorrect"
|
||||
Expect.equal theParam.Value 99 "The parameter value is incorrect"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user