Compare commits
20 Commits
v3
...
0c308c5f33
| Author | SHA1 | Date | |
|---|---|---|---|
| 0c308c5f33 | |||
| d9d37f110d | |||
| e0fb793ec9 | |||
| 74e5b77edb | |||
| 98bc83ac17 | |||
| 35755df99a | |||
| b1c3991e11 | |||
| 85750e19f2 | |||
| d8f64417e5 | |||
| 202fca272e | |||
| 8a15bdce2e | |||
| d131eda56e | |||
| e2232e91bb | |||
| e96c449324 | |||
| 433302d995 | |||
| 039761fcca | |||
| a0da5f83b1 | |||
| d7c8cae0c7 | |||
| 1707d3ce63 | |||
| 7d7214e9f2 |
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Description>Common files for PostgreSQL and SQLite document database libraries</Description>
|
<Description>Common files for PostgreSQL and SQLite document database libraries</Description>
|
||||||
<PackageReleaseNotes>v3 release</PackageReleaseNotes>
|
|
||||||
<PackageTags>JSON Document SQL</PackageTags>
|
<PackageTags>JSON Document SQL</PackageTags>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@@ -13,7 +12,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="FSharp.SystemTextJson" Version="1.2.42" />
|
<PackageReference Include="FSharp.SystemTextJson" Version="1.3.13" />
|
||||||
|
<PackageReference Update="FSharp.Core" Version="8.0.300" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ type Op =
|
|||||||
| LE
|
| LE
|
||||||
/// Not Equal to (<>)
|
/// Not Equal to (<>)
|
||||||
| NE
|
| NE
|
||||||
|
/// Between (BETWEEN)
|
||||||
|
| BT
|
||||||
/// Exists (IS NOT NULL)
|
/// Exists (IS NOT NULL)
|
||||||
| EX
|
| EX
|
||||||
/// Does Not Exist (IS NULL)
|
/// Does Not Exist (IS NULL)
|
||||||
@@ -28,10 +30,17 @@ type Op =
|
|||||||
| LT -> "<"
|
| LT -> "<"
|
||||||
| LE -> "<="
|
| LE -> "<="
|
||||||
| NE -> "<>"
|
| NE -> "<>"
|
||||||
|
| BT -> "BETWEEN"
|
||||||
| EX -> "IS NOT NULL"
|
| EX -> "IS NOT NULL"
|
||||||
| NEX -> "IS NULL"
|
| NEX -> "IS NULL"
|
||||||
|
|
||||||
|
|
||||||
|
/// The dialect in which a command should be rendered
|
||||||
|
[<Struct>]
|
||||||
|
type Dialect =
|
||||||
|
| PostgreSQL
|
||||||
|
| SQLite
|
||||||
|
|
||||||
/// Criteria for a field WHERE clause
|
/// Criteria for a field WHERE clause
|
||||||
type Field = {
|
type Field = {
|
||||||
/// The name of the field
|
/// The name of the field
|
||||||
@@ -42,39 +51,98 @@ type Field = {
|
|||||||
|
|
||||||
/// The value of the field
|
/// The value of the field
|
||||||
Value: obj
|
Value: obj
|
||||||
|
|
||||||
|
/// The name of the parameter for this field
|
||||||
|
ParameterName: string option
|
||||||
|
|
||||||
|
/// The table qualifier for this field
|
||||||
|
Qualifier: string option
|
||||||
} with
|
} with
|
||||||
|
|
||||||
/// Create an equals (=) field criterion
|
/// Create an equals (=) field criterion
|
||||||
static member EQ name (value: obj) =
|
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
|
/// Create a greater than (>) field criterion
|
||||||
static member GT name (value: obj) =
|
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
|
/// Create a greater than or equal to (>=) field criterion
|
||||||
static member GE name (value: obj) =
|
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
|
/// Create a less than (<) field criterion
|
||||||
static member LT name (value: obj) =
|
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
|
/// Create a less than or equal to (<=) field criterion
|
||||||
static member LE name (value: obj) =
|
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
|
/// Create a not equals (<>) field criterion
|
||||||
static member NE name (value: obj) =
|
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 ]; ParameterName = None; Qualifier = None }
|
||||||
|
|
||||||
/// Create an exists (IS NOT NULL) field criterion
|
/// Create an exists (IS NOT NULL) field criterion
|
||||||
static member EX name =
|
static member EX name =
|
||||||
{ Name = name; Op = EX; Value = obj () }
|
{ Name = name; Op = EX; Value = obj (); ParameterName = None; Qualifier = None }
|
||||||
|
|
||||||
/// Create an not exists (IS NULL) field criterion
|
/// Create a not exists (IS NULL) field criterion
|
||||||
static member NEX name =
|
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
|
/// The required document serialization implementation
|
||||||
@@ -145,21 +213,10 @@ module Configuration =
|
|||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module Query =
|
module Query =
|
||||||
|
|
||||||
/// Create a SELECT clause to retrieve the document data from the given table
|
/// Combine a query (select, update, etc.) and a WHERE clause
|
||||||
[<CompiledName "SelectFromTable">]
|
[<CompiledName "StatementWhere">]
|
||||||
let selectFromTable tableName =
|
let statementWhere statement where =
|
||||||
$"SELECT data FROM %s{tableName}"
|
$"%s{statement} WHERE %s{where}"
|
||||||
|
|
||||||
/// 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 -> string field.Op | _ -> $"{field.Op} %s{paramName}"
|
|
||||||
$"data ->> '%s{field.Name}' {theRest}"
|
|
||||||
|
|
||||||
/// Create a WHERE clause fragment to implement an ID-based query
|
|
||||||
[<CompiledName "WhereById">]
|
|
||||||
let whereById paramName =
|
|
||||||
whereByField (Field.EQ (Configuration.idField ()) 0) paramName
|
|
||||||
|
|
||||||
/// Queries to define tables and indexes
|
/// Queries to define tables and indexes
|
||||||
module Definition =
|
module Definition =
|
||||||
@@ -176,7 +233,7 @@ module Query =
|
|||||||
|
|
||||||
/// SQL statement to create an index on one or more fields in a JSON document
|
/// SQL statement to create an index on one or more fields in a JSON document
|
||||||
[<CompiledName "EnsureIndexOn">]
|
[<CompiledName "EnsureIndexOn">]
|
||||||
let ensureIndexOn tableName indexName (fields: string seq) =
|
let ensureIndexOn tableName indexName (fields: string seq) dialect =
|
||||||
let _, tbl = splitSchemaAndTable tableName
|
let _, tbl = splitSchemaAndTable tableName
|
||||||
let jsonFields =
|
let jsonFields =
|
||||||
fields
|
fields
|
||||||
@@ -184,14 +241,14 @@ module Query =
|
|||||||
let parts = it.Split ' '
|
let parts = it.Split ' '
|
||||||
let fieldName = if Array.length parts = 1 then it else parts[0]
|
let fieldName = if Array.length parts = 1 then it else parts[0]
|
||||||
let direction = if Array.length parts < 2 then "" else $" {parts[1]}"
|
let direction = if Array.length parts < 2 then "" else $" {parts[1]}"
|
||||||
$"(data ->> '{fieldName}'){direction}")
|
$"({Field.NameToPath fieldName dialect}){direction}")
|
||||||
|> String.concat ", "
|
|> String.concat ", "
|
||||||
$"CREATE INDEX IF NOT EXISTS idx_{tbl}_%s{indexName} ON {tableName} ({jsonFields})"
|
$"CREATE INDEX IF NOT EXISTS idx_{tbl}_%s{indexName} ON {tableName} ({jsonFields})"
|
||||||
|
|
||||||
/// SQL statement to create a key index for a document table
|
/// SQL statement to create a key index for a document table
|
||||||
[<CompiledName "EnsureKey">]
|
[<CompiledName "EnsureKey">]
|
||||||
let ensureKey tableName =
|
let ensureKey tableName dialect =
|
||||||
(ensureIndexOn tableName "key" [ Configuration.idField () ]).Replace("INDEX", "UNIQUE INDEX")
|
(ensureIndexOn tableName "key" [ Configuration.idField () ] dialect).Replace("INDEX", "UNIQUE INDEX")
|
||||||
|
|
||||||
/// Query to insert a document
|
/// Query to insert a document
|
||||||
[<CompiledName "Insert">]
|
[<CompiledName "Insert">]
|
||||||
@@ -202,62 +259,36 @@ module Query =
|
|||||||
[<CompiledName "Save">]
|
[<CompiledName "Save">]
|
||||||
let save tableName =
|
let save tableName =
|
||||||
sprintf
|
sprintf
|
||||||
"INSERT INTO %s VALUES (@data) ON CONFLICT ((data ->> '%s')) DO UPDATE SET data = EXCLUDED.data"
|
"INSERT INTO %s VALUES (@data) ON CONFLICT ((data->>'%s')) DO UPDATE SET data = EXCLUDED.data"
|
||||||
tableName (Configuration.idField ())
|
tableName (Configuration.idField ())
|
||||||
|
|
||||||
/// Query to update a document
|
/// 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">]
|
[<CompiledName "Update">]
|
||||||
let update tableName =
|
let update tableName =
|
||||||
$"""UPDATE %s{tableName} SET data = @data WHERE {whereById "@id"}"""
|
$"UPDATE %s{tableName} SET data = @data"
|
||||||
|
|
||||||
/// Queries for counting documents
|
/// Query to delete documents from a table (no WHERE clause)
|
||||||
module Count =
|
[<CompiledName "Delete">]
|
||||||
|
let delete tableName =
|
||||||
|
$"DELETE FROM %s{tableName}"
|
||||||
|
|
||||||
/// Query to count all documents in a table
|
/// Create a SELECT clause to retrieve the document data from the given table
|
||||||
[<CompiledName "All">]
|
[<CompiledName "SelectFromTable">]
|
||||||
let all tableName =
|
[<System.Obsolete "Use Find instead">]
|
||||||
$"SELECT COUNT(*) AS it FROM %s{tableName}"
|
let selectFromTable tableName =
|
||||||
|
find 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 =
|
|
||||||
$"""{selectFromTable tableName} WHERE {whereById "@id"}"""
|
|
||||||
|
|
||||||
/// Query to retrieve documents using a comparison on a JSON field
|
|
||||||
[<CompiledName "ByField">]
|
|
||||||
let byField tableName field =
|
|
||||||
$"""{selectFromTable tableName} WHERE {whereByField field "@field"}"""
|
|
||||||
|
|
||||||
/// 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"}"""
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
|
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
|
||||||
<DebugType>embedded</DebugType>
|
<DebugType>embedded</DebugType>
|
||||||
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
||||||
<AssemblyVersion>3.0.0.0</AssemblyVersion>
|
<AssemblyVersion>3.1.0.0</AssemblyVersion>
|
||||||
<FileVersion>3.0.0.0</FileVersion>
|
<FileVersion>3.1.0.0</FileVersion>
|
||||||
<VersionPrefix>3.0.0</VersionPrefix>
|
<VersionPrefix>3.1.0</VersionPrefix>
|
||||||
|
<PackageReleaseNotes>Add BT (between) operator; drop .NET 7 support</PackageReleaseNotes>
|
||||||
<Authors>danieljsummers</Authors>
|
<Authors>danieljsummers</Authors>
|
||||||
<Company>Bit Badger Solutions</Company>
|
<Company>Bit Badger Solutions</Company>
|
||||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Description>Use PostgreSQL as a document database</Description>
|
<Description>Use PostgreSQL as a document database</Description>
|
||||||
<PackageReleaseNotes>v3 release; official replacement for BitBadger.Npgsql.Documents</PackageReleaseNotes>
|
|
||||||
<PackageTags>JSON Document PostgreSQL Npgsql</PackageTags>
|
<PackageTags>JSON Document PostgreSQL Npgsql</PackageTags>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@@ -15,6 +14,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Npgsql.FSharp" Version="5.7.0" />
|
<PackageReference Include="Npgsql.FSharp" Version="5.7.0" />
|
||||||
|
<PackageReference Update="FSharp.Core" Version="8.0.300" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
namespace BitBadger.Documents.Postgres
|
namespace BitBadger.Documents.Postgres
|
||||||
|
|
||||||
|
open BitBadger.Documents
|
||||||
open Npgsql
|
open Npgsql
|
||||||
open Npgsql.FSharp
|
open Npgsql.FSharp
|
||||||
|
|
||||||
@@ -50,8 +51,13 @@ module Extensions =
|
|||||||
WithProps.Count.all tableName (Sql.existingConnection conn)
|
WithProps.Count.all tableName (Sql.existingConnection conn)
|
||||||
|
|
||||||
/// Count matching documents using a JSON field comparison query (->> =)
|
/// 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 =
|
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 (@>)
|
/// Count matching documents using a JSON containment query (@>)
|
||||||
member conn.countByContains tableName criteria =
|
member conn.countByContains tableName criteria =
|
||||||
@@ -66,8 +72,13 @@ module Extensions =
|
|||||||
WithProps.Exists.byId tableName docId (Sql.existingConnection conn)
|
WithProps.Exists.byId tableName docId (Sql.existingConnection conn)
|
||||||
|
|
||||||
/// Determine if documents exist using a JSON field comparison query (->> =)
|
/// 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 =
|
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 (@>)
|
/// Determine if documents exist using a JSON containment query (@>)
|
||||||
member conn.existsByContains tableName criteria =
|
member conn.existsByContains tableName criteria =
|
||||||
@@ -86,8 +97,13 @@ module Extensions =
|
|||||||
WithProps.Find.byId<'TKey, 'TDoc> tableName docId (Sql.existingConnection conn)
|
WithProps.Find.byId<'TKey, 'TDoc> tableName docId (Sql.existingConnection conn)
|
||||||
|
|
||||||
/// Retrieve documents matching a JSON field comparison query (->> =)
|
/// 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 =
|
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 (@>)
|
/// Retrieve documents matching a JSON containment query (@>)
|
||||||
member conn.findByContains<'TDoc> tableName (criteria: obj) =
|
member conn.findByContains<'TDoc> tableName (criteria: obj) =
|
||||||
@@ -98,8 +114,13 @@ module Extensions =
|
|||||||
WithProps.Find.byJsonPath<'TDoc> tableName jsonPath (Sql.existingConnection conn)
|
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 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 =
|
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
|
/// Retrieve the first document matching a JSON containment query (@>); returns None if not found
|
||||||
member conn.findFirstByContains<'TDoc> tableName (criteria: obj) =
|
member conn.findFirstByContains<'TDoc> tableName (criteria: obj) =
|
||||||
@@ -122,8 +143,13 @@ module Extensions =
|
|||||||
WithProps.Patch.byId tableName docId patch (Sql.existingConnection conn)
|
WithProps.Patch.byId tableName docId patch (Sql.existingConnection conn)
|
||||||
|
|
||||||
/// Patch documents using a JSON field comparison query in the WHERE clause (->> =)
|
/// Patch documents using a JSON field comparison query in the WHERE clause (->> =)
|
||||||
|
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) =
|
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 (@>)
|
/// Patch documents using a JSON containment query in the WHERE clause (@>)
|
||||||
member conn.patchByContains tableName (criteria: 'TCriteria) (patch: 'TPatch) =
|
member conn.patchByContains tableName (criteria: 'TCriteria) (patch: 'TPatch) =
|
||||||
@@ -137,9 +163,14 @@ module Extensions =
|
|||||||
member conn.removeFieldsById tableName (docId: 'TKey) fieldNames =
|
member conn.removeFieldsById 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
|
||||||
|
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
|
/// 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 =
|
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 (@>)
|
/// Remove fields from documents via a JSON containment query (@>)
|
||||||
member conn.removeFieldsByContains tableName (criteria: 'TContains) fieldNames =
|
member conn.removeFieldsByContains tableName (criteria: 'TContains) fieldNames =
|
||||||
@@ -153,9 +184,13 @@ module Extensions =
|
|||||||
member conn.deleteById tableName (docId: 'TKey) =
|
member conn.deleteById tableName (docId: 'TKey) =
|
||||||
WithProps.Delete.byId tableName docId (Sql.existingConnection conn)
|
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 (->> =)
|
/// Delete documents by matching a JSON field comparison query (->> =)
|
||||||
|
[<System.Obsolete "Use deleteByFields instead; will be removed in v4">]
|
||||||
member conn.deleteByField tableName field =
|
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 (@>)
|
/// Delete documents by matching a JSON containment query (@>)
|
||||||
member conn.deleteByContains tableName (criteria: 'TContains) =
|
member conn.deleteByContains tableName (criteria: 'TContains) =
|
||||||
@@ -225,8 +260,14 @@ type NpgsqlConnectionCSharpExtensions =
|
|||||||
|
|
||||||
/// Count matching documents using a JSON field comparison query (->> =)
|
/// Count matching documents using a JSON field comparison query (->> =)
|
||||||
[<Extension>]
|
[<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) =
|
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 (@>)
|
/// Count matching documents using a JSON containment query (@>)
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
@@ -245,8 +286,14 @@ type NpgsqlConnectionCSharpExtensions =
|
|||||||
|
|
||||||
/// Determine if documents exist using a JSON field comparison query (->> =)
|
/// Determine if documents exist using a JSON field comparison query (->> =)
|
||||||
[<Extension>]
|
[<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) =
|
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 (@>)
|
/// Determine if documents exist using a JSON containment query (@>)
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
@@ -270,8 +317,14 @@ type NpgsqlConnectionCSharpExtensions =
|
|||||||
|
|
||||||
/// Retrieve documents matching a JSON field comparison query (->> =)
|
/// Retrieve documents matching a JSON field comparison query (->> =)
|
||||||
[<Extension>]
|
[<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) =
|
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 (@>)
|
/// Retrieve documents matching a JSON containment query (@>)
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
@@ -283,10 +336,16 @@ type NpgsqlConnectionCSharpExtensions =
|
|||||||
static member inline FindByJsonPath<'TDoc>(conn, tableName, jsonPath) =
|
static member inline FindByJsonPath<'TDoc>(conn, tableName, jsonPath) =
|
||||||
WithProps.Find.ByJsonPath<'TDoc>(tableName, jsonPath, Sql.existingConnection conn)
|
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>]
|
[<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) =
|
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
|
/// Retrieve the first document matching a JSON containment query (@>); returns None if not found
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
@@ -315,8 +374,14 @@ type NpgsqlConnectionCSharpExtensions =
|
|||||||
|
|
||||||
/// Patch documents using a JSON field comparison query in the WHERE clause (->> =)
|
/// Patch documents using a JSON field comparison query in the WHERE clause (->> =)
|
||||||
[<Extension>]
|
[<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) =
|
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 (@>)
|
/// Patch documents using a JSON containment query in the WHERE clause (@>)
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
@@ -331,22 +396,28 @@ type NpgsqlConnectionCSharpExtensions =
|
|||||||
/// Remove fields from a document by the document's ID
|
/// Remove fields from a document by the document's ID
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline RemoveFieldsById(conn, tableName, docId: 'TKey, fieldNames) =
|
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
|
/// Remove fields from documents via a comparison on a JSON field in the document
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
|
[<System.Obsolete "Use RemoveFieldsByFields instead; will be removed in v4">]
|
||||||
static member inline RemoveFieldsByField(conn, tableName, field, fieldNames) =
|
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 (@>)
|
/// Remove fields from documents via a JSON containment query (@>)
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline RemoveFieldsByContains(conn, tableName, criteria: 'TContains, fieldNames) =
|
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 (@?)
|
/// Remove fields from documents via a JSON Path match query (@?)
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline RemoveFieldsByJsonPath(conn, tableName, jsonPath, fieldNames) =
|
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
|
/// Delete a document by its ID
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
@@ -355,8 +426,14 @@ type NpgsqlConnectionCSharpExtensions =
|
|||||||
|
|
||||||
/// Delete documents by matching a JSON field comparison query (->> =)
|
/// Delete documents by matching a JSON field comparison query (->> =)
|
||||||
[<Extension>]
|
[<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) =
|
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 (@>)
|
/// Delete documents by matching a JSON containment query (@>)
|
||||||
[<Extension>]
|
[<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
|
open BitBadger.Documents
|
||||||
|
|
||||||
@@ -53,40 +70,58 @@ open BitBadger.Documents
|
|||||||
[<AutoOpen>]
|
[<AutoOpen>]
|
||||||
module Parameters =
|
module Parameters =
|
||||||
|
|
||||||
/// Create an ID parameter (name "@id", key will be treated as a string)
|
/// Create an ID parameter (name "@id")
|
||||||
[<CompiledName "Id">]
|
[<CompiledName "Id">]
|
||||||
let idParam (key: 'TKey) =
|
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
|
/// Create a parameter with a JSON value
|
||||||
[<CompiledName "Json">]
|
[<CompiledName "Json">]
|
||||||
let jsonParam (name: string) (it: 'TJson) =
|
let jsonParam (name: string) (it: 'TJson) =
|
||||||
name, Sql.jsonb (Configuration.serializer().Serialize it)
|
name, Sql.jsonb (Configuration.serializer().Serialize it)
|
||||||
|
|
||||||
/// Create a JSON field parameter (name "@field")
|
/// Create JSON field parameters
|
||||||
[<CompiledName "FSharpAddField">]
|
[<CompiledName "AddFields">]
|
||||||
|
let addFieldParams fields parameters =
|
||||||
|
let name = ParameterName()
|
||||||
|
fields
|
||||||
|
|> Seq.map (fun it ->
|
||||||
|
seq {
|
||||||
|
match it.Op with
|
||||||
|
| EX | NEX -> ()
|
||||||
|
| BT ->
|
||||||
|
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
|
||||||
|
|> 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 =
|
let addFieldParam name field parameters =
|
||||||
match field.Op with
|
addFieldParams [ { field with ParameterName = Some name } ] parameters
|
||||||
| EX | NEX -> parameters
|
|
||||||
| _ -> (name, Sql.parameter (NpgsqlParameter(name, field.Value))) :: parameters
|
|
||||||
|
|
||||||
/// Create a JSON field parameter (name "@field")
|
|
||||||
let AddField name field parameters =
|
|
||||||
match field.Op with
|
|
||||||
| EX | NEX -> parameters
|
|
||||||
| _ -> (name, Sql.parameter (NpgsqlParameter(name, field.Value))) |> Seq.singleton |> Seq.append parameters
|
|
||||||
|
|
||||||
/// Append JSON field name parameters for the given field names to the given parameters
|
/// Append JSON field name parameters for the given field names to the given parameters
|
||||||
[<CompiledName "FSharpFieldName">]
|
[<CompiledName "FieldNames">]
|
||||||
let fieldNameParam (fieldNames: string list) =
|
let fieldNameParams (fieldNames: string seq) =
|
||||||
if fieldNames.Length = 1 then "@name", Sql.string fieldNames[0]
|
if Seq.length fieldNames = 1 then "@name", Sql.string (Seq.head fieldNames)
|
||||||
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)
|
|
||||||
else "@name", Sql.stringArray (Array.ofSeq 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
|
/// An empty parameter sequence
|
||||||
[<CompiledName "None">]
|
[<CompiledName "None">]
|
||||||
let noParams =
|
let noParams =
|
||||||
@@ -97,6 +132,35 @@ module Parameters =
|
|||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module Query =
|
module Query =
|
||||||
|
|
||||||
|
/// 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 -> 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<'TKey> (docId: 'TKey) =
|
||||||
|
whereByFields Any [ { Field.EQ (Configuration.idField ()) docId with ParameterName = Some "@id" } ]
|
||||||
|
|
||||||
/// Table and index definition queries
|
/// Table and index definition queries
|
||||||
module Definition =
|
module Definition =
|
||||||
|
|
||||||
@@ -122,111 +186,35 @@ module Query =
|
|||||||
let whereJsonPathMatches paramName =
|
let whereJsonPathMatches paramName =
|
||||||
$"data @? %s{paramName}::jsonpath"
|
$"data @? %s{paramName}::jsonpath"
|
||||||
|
|
||||||
/// Queries for counting documents
|
/// Create an UPDATE statement to patch documents
|
||||||
module Count =
|
[<CompiledName "Patch">]
|
||||||
|
let patch tableName =
|
||||||
|
$"UPDATE %s{tableName} SET data = data || @data"
|
||||||
|
|
||||||
/// Query to count matching documents using a JSON containment query (@>)
|
/// Create an UPDATE statement to remove fields from documents
|
||||||
[<CompiledName "ByContains">]
|
[<CompiledName "RemoveFields">]
|
||||||
let byContains tableName =
|
let removeFields tableName =
|
||||||
$"""SELECT COUNT(*) AS it FROM %s{tableName} WHERE {whereDataContains "@criteria"}"""
|
$"UPDATE %s{tableName} SET data = data - @name"
|
||||||
|
|
||||||
/// Query to count matching documents using a JSON Path match (@?)
|
/// Create a query by a document's ID
|
||||||
[<CompiledName "ByJsonPath">]
|
[<CompiledName "ById">]
|
||||||
let byJsonPath tableName =
|
let byId<'TKey> statement (docId: 'TKey) =
|
||||||
$"""SELECT COUNT(*) AS it FROM %s{tableName} WHERE {whereJsonPathMatches "@path"}"""
|
Query.statementWhere statement (whereById docId)
|
||||||
|
|
||||||
/// Queries for determining document existence
|
/// Create a query on JSON fields
|
||||||
module Exists =
|
[<CompiledName "ByFields">]
|
||||||
|
let byFields statement howMatched fields =
|
||||||
|
Query.statementWhere statement (whereByFields howMatched fields)
|
||||||
|
|
||||||
/// Query to determine if documents exist using a JSON containment query (@>)
|
/// Create a JSON containment query
|
||||||
[<CompiledName "ByContains">]
|
[<CompiledName "ByContains">]
|
||||||
let byContains tableName =
|
let byContains statement =
|
||||||
$"""SELECT EXISTS (SELECT 1 FROM %s{tableName} WHERE {whereDataContains "@criteria"}) AS it"""
|
Query.statementWhere statement (whereDataContains "@criteria")
|
||||||
|
|
||||||
/// Query to determine if documents exist using a JSON Path match (@?)
|
/// Create a JSON Path match query
|
||||||
[<CompiledName "ByJsonPath">]
|
[<CompiledName "ByPathMatch">]
|
||||||
let byJsonPath tableName =
|
let byPathMatch statement =
|
||||||
$"""SELECT EXISTS (SELECT 1 FROM %s{tableName} WHERE {whereJsonPathMatches "@path"}) AS it"""
|
Query.statementWhere statement (whereJsonPathMatches "@path")
|
||||||
|
|
||||||
/// Queries for retrieving documents
|
|
||||||
module Find =
|
|
||||||
|
|
||||||
/// 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}"
|
|
||||||
|
|
||||||
/// Query to patch a document by its ID
|
|
||||||
[<CompiledName "ById">]
|
|
||||||
let byId tableName =
|
|
||||||
Query.whereById "@id" |> update tableName
|
|
||||||
|
|
||||||
/// Query to patch documents match a JSON field comparison (->> =)
|
|
||||||
[<CompiledName "ByField">]
|
|
||||||
let byField tableName field =
|
|
||||||
Query.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 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 =
|
|
||||||
Query.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 =
|
|
||||||
Query.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 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"}"""
|
|
||||||
|
|
||||||
|
|
||||||
/// Functions for dealing with results
|
/// Functions for dealing with results
|
||||||
@@ -257,6 +245,8 @@ module Results =
|
|||||||
/// Versions of queries that accept SqlProps as the last parameter
|
/// Versions of queries that accept SqlProps as the last parameter
|
||||||
module WithProps =
|
module WithProps =
|
||||||
|
|
||||||
|
module FSharpList = Microsoft.FSharp.Collections.List
|
||||||
|
|
||||||
/// Commands to execute custom SQL queries
|
/// Commands to execute custom SQL queries
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module Custom =
|
module Custom =
|
||||||
@@ -265,12 +255,12 @@ module WithProps =
|
|||||||
[<CompiledName "FSharpList">]
|
[<CompiledName "FSharpList">]
|
||||||
let list<'TDoc> query parameters (mapFunc: RowReader -> 'TDoc) sqlProps =
|
let list<'TDoc> query parameters (mapFunc: RowReader -> 'TDoc) sqlProps =
|
||||||
Sql.query query sqlProps
|
Sql.query query sqlProps
|
||||||
|> Sql.parameters parameters
|
|> Sql.parameters (List.ofSeq parameters)
|
||||||
|> Sql.executeAsync mapFunc
|
|> Sql.executeAsync mapFunc
|
||||||
|
|
||||||
/// Execute a query that returns a list of results
|
/// Execute a query that returns a list of results
|
||||||
let List<'TDoc>(query, parameters, mapFunc: System.Func<RowReader, 'TDoc>, sqlProps) = backgroundTask {
|
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
|
return ResizeArray results
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,7 +274,7 @@ module WithProps =
|
|||||||
/// Execute a query that returns one or no results; returns null if not found
|
/// Execute a query that returns one or no results; returns null if not found
|
||||||
let Single<'TDoc when 'TDoc: null>(
|
let Single<'TDoc when 'TDoc: null>(
|
||||||
query, parameters, mapFunc: System.Func<RowReader, 'TDoc>, sqlProps) = backgroundTask {
|
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
|
return Option.toObj result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,7 +282,7 @@ module WithProps =
|
|||||||
[<CompiledName "NonQuery">]
|
[<CompiledName "NonQuery">]
|
||||||
let nonQuery query parameters sqlProps =
|
let nonQuery query parameters sqlProps =
|
||||||
Sql.query query sqlProps
|
Sql.query query sqlProps
|
||||||
|> Sql.parameters (FSharp.Collections.List.ofSeq parameters)
|
|> Sql.parameters (FSharpList.ofSeq parameters)
|
||||||
|> Sql.executeNonQueryAsync
|
|> Sql.executeNonQueryAsync
|
||||||
|> ignoreTask
|
|> ignoreTask
|
||||||
|
|
||||||
@@ -300,12 +290,12 @@ module WithProps =
|
|||||||
[<CompiledName "FSharpScalar">]
|
[<CompiledName "FSharpScalar">]
|
||||||
let scalar<'T when 'T: struct> query parameters (mapFunc: RowReader -> 'T) sqlProps =
|
let scalar<'T when 'T: struct> query parameters (mapFunc: RowReader -> 'T) sqlProps =
|
||||||
Sql.query query sqlProps
|
Sql.query query sqlProps
|
||||||
|> Sql.parameters parameters
|
|> Sql.parameters (FSharpList.ofSeq parameters)
|
||||||
|> Sql.executeRowAsync mapFunc
|
|> Sql.executeRowAsync mapFunc
|
||||||
|
|
||||||
/// Execute a query that returns a scalar value
|
/// Execute a query that returns a scalar value
|
||||||
let Scalar<'T when 'T: struct>(query, parameters, mapFunc: System.Func<RowReader, 'T>, sqlProps) =
|
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
|
/// Table and index definition commands
|
||||||
module Definition =
|
module Definition =
|
||||||
@@ -314,7 +304,7 @@ module WithProps =
|
|||||||
[<CompiledName "EnsureTable">]
|
[<CompiledName "EnsureTable">]
|
||||||
let ensureTable name sqlProps = backgroundTask {
|
let ensureTable name sqlProps = backgroundTask {
|
||||||
do! Custom.nonQuery (Query.Definition.ensureTable name) [] sqlProps
|
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
|
/// Create an index on documents in the specified table
|
||||||
@@ -325,7 +315,7 @@ module WithProps =
|
|||||||
/// Create an index on field(s) within documents in the specified table
|
/// Create an index on field(s) within documents in the specified table
|
||||||
[<CompiledName "EnsureFieldIndex">]
|
[<CompiledName "EnsureFieldIndex">]
|
||||||
let ensureFieldIndex tableName indexName fields sqlProps =
|
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
|
/// Commands to add documents
|
||||||
[<AutoOpen>]
|
[<AutoOpen>]
|
||||||
@@ -348,22 +338,25 @@ module WithProps =
|
|||||||
/// Count all documents in a table
|
/// Count all documents in a table
|
||||||
[<CompiledName "All">]
|
[<CompiledName "All">]
|
||||||
let all tableName sqlProps =
|
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 (->> =)
|
/// Count matching documents using JSON field comparisons (->> =)
|
||||||
[<CompiledName "ByField">]
|
[<CompiledName "ByFields">]
|
||||||
let byField tableName field sqlProps =
|
let byFields tableName howMatched fields sqlProps =
|
||||||
Custom.scalar (Query.Count.byField tableName field) (addFieldParam "@field" field []) toCount sqlProps
|
Custom.scalar
|
||||||
|
(Query.byFields (Query.count tableName) howMatched fields) (addFieldParams fields []) toCount sqlProps
|
||||||
|
|
||||||
/// Count matching documents using a JSON containment query (@>)
|
/// Count matching documents using a JSON containment query (@>)
|
||||||
[<CompiledName "ByContains">]
|
[<CompiledName "ByContains">]
|
||||||
let byContains tableName (criteria: 'TContains) sqlProps =
|
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 (@?)
|
/// Count matching documents using a JSON Path match query (@?)
|
||||||
[<CompiledName "ByJsonPath">]
|
[<CompiledName "ByJsonPath">]
|
||||||
let byJsonPath tableName jsonPath sqlProps =
|
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
|
/// Commands to determine if documents exist
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
@@ -372,22 +365,34 @@ module WithProps =
|
|||||||
/// Determine if a document exists for the given ID
|
/// Determine if a document exists for the given ID
|
||||||
[<CompiledName "ById">]
|
[<CompiledName "ById">]
|
||||||
let byId tableName (docId: 'TKey) sqlProps =
|
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 (->> =)
|
/// Determine if a document exists using JSON field comparisons (->> =)
|
||||||
[<CompiledName "ByField">]
|
[<CompiledName "ByFields">]
|
||||||
let byField tableName field sqlProps =
|
let byFields tableName howMatched fields sqlProps =
|
||||||
Custom.scalar (Query.Exists.byField tableName field) (addFieldParam "@field" field []) toExists sqlProps
|
Custom.scalar
|
||||||
|
(Query.exists tableName (Query.whereByFields howMatched fields))
|
||||||
|
(addFieldParams fields [])
|
||||||
|
toExists
|
||||||
|
sqlProps
|
||||||
|
|
||||||
/// Determine if a document exists using a JSON containment query (@>)
|
/// Determine if a document exists using a JSON containment query (@>)
|
||||||
[<CompiledName "ByContains">]
|
[<CompiledName "ByContains">]
|
||||||
let byContains tableName (criteria: 'TContains) sqlProps =
|
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 (@?)
|
/// Determine if a document exists using a JSON Path match query (@?)
|
||||||
[<CompiledName "ByJsonPath">]
|
[<CompiledName "ByJsonPath">]
|
||||||
let byJsonPath tableName jsonPath sqlProps =
|
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
|
/// Commands to determine if documents exist
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
@@ -396,81 +401,119 @@ module WithProps =
|
|||||||
/// Retrieve all documents in the given table
|
/// Retrieve all documents in the given table
|
||||||
[<CompiledName "FSharpAll">]
|
[<CompiledName "FSharpAll">]
|
||||||
let all<'TDoc> tableName sqlProps =
|
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
|
/// Retrieve all documents in the given table
|
||||||
let All<'TDoc>(tableName, sqlProps) =
|
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)
|
/// Retrieve a document by its ID (returns None if not found)
|
||||||
[<CompiledName "FSharpById">]
|
[<CompiledName "FSharpById">]
|
||||||
let byId<'TKey, 'TDoc> tableName (docId: 'TKey) sqlProps =
|
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)
|
/// Retrieve a document by its ID (returns null if not found)
|
||||||
let ById<'TKey, 'TDoc when 'TDoc: null>(tableName, docId: 'TKey, sqlProps) =
|
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 (->> =)
|
/// Retrieve documents matching a JSON field comparison (->> =)
|
||||||
[<CompiledName "FSharpByField">]
|
[<CompiledName "FSharpByField">]
|
||||||
|
[<System.Obsolete "Use byFields instead; will be removed in v4">]
|
||||||
let byField<'TDoc> tableName field sqlProps =
|
let byField<'TDoc> tableName field sqlProps =
|
||||||
Custom.list<'TDoc>
|
byFields<'TDoc> tableName Any [ field ] sqlProps
|
||||||
(Query.Find.byField tableName field) (addFieldParam "@field" field []) fromData<'TDoc> sqlProps
|
|
||||||
|
/// Retrieve documents matching JSON field comparisons (->> =)
|
||||||
|
let ByFields<'TDoc>(tableName, howMatched, fields, sqlProps) =
|
||||||
|
Custom.List<'TDoc>(
|
||||||
|
Query.byFields (Query.find tableName) howMatched fields,
|
||||||
|
addFieldParams fields [],
|
||||||
|
fromData<'TDoc>,
|
||||||
|
sqlProps)
|
||||||
|
|
||||||
/// Retrieve documents matching a JSON field comparison (->> =)
|
/// Retrieve documents matching a JSON field comparison (->> =)
|
||||||
|
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||||
let ByField<'TDoc>(tableName, field, sqlProps) =
|
let ByField<'TDoc>(tableName, field, sqlProps) =
|
||||||
Custom.List<'TDoc>(
|
ByFields<'TDoc>(tableName, Any, Seq.singleton field, sqlProps)
|
||||||
Query.Find.byField tableName field, addFieldParam "@field" field [], fromData<'TDoc>, sqlProps)
|
|
||||||
|
|
||||||
/// Retrieve documents matching a JSON containment query (@>)
|
/// Retrieve documents matching a JSON containment query (@>)
|
||||||
[<CompiledName "FSharpByContains">]
|
[<CompiledName "FSharpByContains">]
|
||||||
let byContains<'TDoc> tableName (criteria: obj) sqlProps =
|
let byContains<'TDoc> tableName (criteria: obj) sqlProps =
|
||||||
Custom.list<'TDoc>
|
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 (@>)
|
/// Retrieve documents matching a JSON containment query (@>)
|
||||||
let ByContains<'TDoc>(tableName, criteria: obj, sqlProps) =
|
let ByContains<'TDoc>(tableName, criteria: obj, sqlProps) =
|
||||||
Custom.List<'TDoc>(
|
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 (@?)
|
/// Retrieve documents matching a JSON Path match query (@?)
|
||||||
[<CompiledName "FSharpByJsonPath">]
|
[<CompiledName "FSharpByJsonPath">]
|
||||||
let byJsonPath<'TDoc> tableName jsonPath sqlProps =
|
let byJsonPath<'TDoc> tableName jsonPath sqlProps =
|
||||||
Custom.list<'TDoc>
|
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 (@?)
|
/// Retrieve documents matching a JSON Path match query (@?)
|
||||||
let ByJsonPath<'TDoc>(tableName, jsonPath, sqlProps) =
|
let ByJsonPath<'TDoc>(tableName, jsonPath, sqlProps) =
|
||||||
Custom.List<'TDoc>(
|
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
|
/// Retrieve the first document matching JSON field comparisons (->> =); returns None if not found
|
||||||
[<CompiledName "FSharpFirstByField">]
|
[<CompiledName "FSharpFirstByFields">]
|
||||||
let firstByField<'TDoc> tableName field sqlProps =
|
let firstByFields<'TDoc> tableName howMatched fields sqlProps =
|
||||||
Custom.single<'TDoc>
|
Custom.single<'TDoc>
|
||||||
$"{Query.Find.byField tableName field} LIMIT 1"
|
$"{Query.byFields (Query.find tableName) howMatched fields} LIMIT 1"
|
||||||
(addFieldParam "@field" field [])
|
(addFieldParams fields [])
|
||||||
fromData<'TDoc>
|
fromData<'TDoc>
|
||||||
sqlProps
|
sqlProps
|
||||||
|
|
||||||
/// Retrieve the first document matching a JSON field comparison (->> =); returns null if not found
|
/// Retrieve the first document matching a JSON field comparison (->> =); returns None if not found
|
||||||
let FirstByField<'TDoc when 'TDoc: null>(tableName, field, sqlProps) =
|
[<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>(
|
Custom.Single<'TDoc>(
|
||||||
$"{Query.Find.byField tableName field} LIMIT 1",
|
$"{Query.byFields (Query.find tableName) howMatched fields} LIMIT 1",
|
||||||
addFieldParam "@field" field [],
|
addFieldParams fields [],
|
||||||
fromData<'TDoc>,
|
fromData<'TDoc>,
|
||||||
sqlProps)
|
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
|
/// Retrieve the first document matching a JSON containment query (@>); returns None if not found
|
||||||
[<CompiledName "FSharpFirstByContains">]
|
[<CompiledName "FSharpFirstByContains">]
|
||||||
let firstByContains<'TDoc> tableName (criteria: obj) sqlProps =
|
let firstByContains<'TDoc> tableName (criteria: obj) sqlProps =
|
||||||
Custom.single<'TDoc>
|
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
|
/// Retrieve the first document matching a JSON containment query (@>); returns null if not found
|
||||||
let FirstByContains<'TDoc when 'TDoc: null>(tableName, criteria: obj, sqlProps) =
|
let FirstByContains<'TDoc when 'TDoc: null>(tableName, criteria: obj, sqlProps) =
|
||||||
Custom.Single<'TDoc>(
|
Custom.Single<'TDoc>(
|
||||||
$"{Query.Find.byContains tableName} LIMIT 1",
|
$"{Query.byContains (Query.find tableName)} LIMIT 1",
|
||||||
[ jsonParam "@criteria" criteria ],
|
[ jsonParam "@criteria" criteria ],
|
||||||
fromData<'TDoc>,
|
fromData<'TDoc>,
|
||||||
sqlProps)
|
sqlProps)
|
||||||
@@ -479,12 +522,15 @@ module WithProps =
|
|||||||
[<CompiledName "FSharpFirstByJsonPath">]
|
[<CompiledName "FSharpFirstByJsonPath">]
|
||||||
let firstByJsonPath<'TDoc> tableName jsonPath sqlProps =
|
let firstByJsonPath<'TDoc> tableName jsonPath sqlProps =
|
||||||
Custom.single<'TDoc>
|
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
|
/// Retrieve the first document matching a JSON Path match query (@?); returns null if not found
|
||||||
let FirstByJsonPath<'TDoc when 'TDoc: null>(tableName, jsonPath, sqlProps) =
|
let FirstByJsonPath<'TDoc when 'TDoc: null>(tableName, jsonPath, sqlProps) =
|
||||||
Custom.Single<'TDoc>(
|
Custom.Single<'TDoc>(
|
||||||
$"{Query.Find.byJsonPath tableName} LIMIT 1",
|
$"{Query.byPathMatch (Query.find tableName)} LIMIT 1",
|
||||||
[ "@path", Sql.string jsonPath ],
|
[ "@path", Sql.string jsonPath ],
|
||||||
fromData<'TDoc>,
|
fromData<'TDoc>,
|
||||||
sqlProps)
|
sqlProps)
|
||||||
@@ -496,7 +542,8 @@ module WithProps =
|
|||||||
/// Update an entire document by its ID
|
/// Update an entire document by its ID
|
||||||
[<CompiledName "ById">]
|
[<CompiledName "ById">]
|
||||||
let byId tableName (docId: 'TKey) (document: 'TDoc) sqlProps =
|
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
|
/// Update an entire document by its ID, using the provided function to obtain the ID from the document
|
||||||
[<CompiledName "FSharpByFunc">]
|
[<CompiledName "FSharpByFunc">]
|
||||||
@@ -514,77 +561,79 @@ module WithProps =
|
|||||||
/// Patch a document by its ID
|
/// Patch a document by its ID
|
||||||
[<CompiledName "ById">]
|
[<CompiledName "ById">]
|
||||||
let byId tableName (docId: 'TKey) (patch: 'TPatch) sqlProps =
|
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 (->> =)
|
/// Patch documents using a JSON field comparison query in the WHERE clause (->> =)
|
||||||
[<CompiledName "ByField">]
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||||
let byField tableName field (patch: 'TPatch) sqlProps =
|
let byField tableName field (patch: 'TPatch) sqlProps =
|
||||||
Custom.nonQuery
|
byFields tableName Any [ field ] patch sqlProps
|
||||||
(Query.Patch.byField tableName field)
|
|
||||||
(addFieldParam "@field" field [ jsonParam "@data" patch ])
|
|
||||||
sqlProps
|
|
||||||
|
|
||||||
/// Patch documents using a JSON containment query in the WHERE clause (@>)
|
/// Patch documents using a JSON containment query in the WHERE clause (@>)
|
||||||
[<CompiledName "ByContains">]
|
[<CompiledName "ByContains">]
|
||||||
let byContains tableName (criteria: 'TContains) (patch: 'TPatch) sqlProps =
|
let byContains tableName (criteria: 'TContains) (patch: 'TPatch) sqlProps =
|
||||||
Custom.nonQuery
|
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 (@?)
|
/// Patch documents using a JSON Path match query in the WHERE clause (@?)
|
||||||
[<CompiledName "ByJsonPath">]
|
[<CompiledName "ByJsonPath">]
|
||||||
let byJsonPath tableName jsonPath (patch: 'TPatch) sqlProps =
|
let byJsonPath tableName jsonPath (patch: 'TPatch) sqlProps =
|
||||||
Custom.nonQuery
|
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
|
/// Commands to remove fields from documents
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module RemoveFields =
|
module RemoveFields =
|
||||||
|
|
||||||
/// Remove fields from a document by the document's ID
|
/// Remove fields from a document by the document's ID
|
||||||
[<CompiledName "FSharpById">]
|
[<CompiledName "ById">]
|
||||||
let byId tableName (docId: 'TKey) fieldNames sqlProps =
|
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
|
Custom.nonQuery
|
||||||
(Query.RemoveFields.byField tableName field)
|
(Query.byId (Query.removeFields tableName) docId) [ idParam docId; fieldNameParams fieldNames ] sqlProps
|
||||||
(addFieldParam "@field" field [ fieldNameParam fieldNames ])
|
|
||||||
|
/// 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
|
sqlProps
|
||||||
|
|
||||||
/// Remove fields from documents via a comparison on a JSON field in the document
|
/// Remove fields from documents via a comparison on a JSON field in the document
|
||||||
let ByField(tableName, field, fieldNames, sqlProps) =
|
[<CompiledName "ByField">]
|
||||||
byField tableName field (List.ofSeq fieldNames) sqlProps
|
[<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 (@>)
|
/// Remove fields from documents via a JSON containment query (@>)
|
||||||
[<CompiledName "FSharpByContains">]
|
[<CompiledName "ByContains">]
|
||||||
let byContains tableName (criteria: 'TContains) fieldNames sqlProps =
|
let byContains tableName (criteria: 'TContains) fieldNames sqlProps =
|
||||||
Custom.nonQuery
|
Custom.nonQuery
|
||||||
(Query.RemoveFields.byContains tableName)
|
(Query.byContains (Query.removeFields tableName))
|
||||||
[ jsonParam "@criteria" criteria; fieldNameParam fieldNames ]
|
[ jsonParam "@criteria" criteria; fieldNameParams fieldNames ]
|
||||||
sqlProps
|
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 (@?)
|
/// Remove fields from documents via a JSON Path match query (@?)
|
||||||
[<CompiledName "FSharpByJsonPath">]
|
[<CompiledName "FSharpByJsonPath">]
|
||||||
let byJsonPath tableName jsonPath fieldNames sqlProps =
|
let byJsonPath tableName jsonPath fieldNames sqlProps =
|
||||||
Custom.nonQuery
|
Custom.nonQuery
|
||||||
(Query.RemoveFields.byJsonPath tableName)
|
(Query.byPathMatch (Query.removeFields tableName))
|
||||||
[ "@path", Sql.string jsonPath; fieldNameParam fieldNames ]
|
[ "@path", Sql.string jsonPath; fieldNameParams fieldNames ]
|
||||||
sqlProps
|
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
|
/// Commands to delete documents
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module Delete =
|
module Delete =
|
||||||
@@ -592,22 +641,29 @@ module WithProps =
|
|||||||
/// Delete a document by its ID
|
/// Delete a document by its ID
|
||||||
[<CompiledName "ById">]
|
[<CompiledName "ById">]
|
||||||
let byId tableName (docId: 'TKey) sqlProps =
|
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 (->> =)
|
/// Delete documents by matching a JSON field comparison query (->> =)
|
||||||
[<CompiledName "ByField">]
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||||
let byField tableName field sqlProps =
|
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 (@>)
|
/// Delete documents by matching a JSON contains query (@>)
|
||||||
[<CompiledName "ByContains">]
|
[<CompiledName "ByContains">]
|
||||||
let byContains tableName (criteria: 'TCriteria) sqlProps =
|
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 (@?)
|
/// Delete documents by matching a JSON Path match query (@?)
|
||||||
[<CompiledName "ByJsonPath">]
|
[<CompiledName "ByJsonPath">]
|
||||||
let byJsonPath tableName path sqlProps =
|
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
|
/// Commands to execute custom SQL queries
|
||||||
@@ -691,10 +747,16 @@ module Count =
|
|||||||
let all tableName =
|
let all tableName =
|
||||||
WithProps.Count.all tableName (fromDataSource ())
|
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 (->> =)
|
/// Count matching documents using a JSON field comparison query (->> =)
|
||||||
[<CompiledName "ByField">]
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||||
let byField tableName field =
|
let byField tableName field =
|
||||||
WithProps.Count.byField tableName field (fromDataSource ())
|
byFields tableName Any [ field ]
|
||||||
|
|
||||||
/// Count matching documents using a JSON containment query (@>)
|
/// Count matching documents using a JSON containment query (@>)
|
||||||
[<CompiledName "ByContains">]
|
[<CompiledName "ByContains">]
|
||||||
@@ -716,10 +778,16 @@ module Exists =
|
|||||||
let byId tableName docId =
|
let byId tableName docId =
|
||||||
WithProps.Exists.byId tableName docId (fromDataSource ())
|
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 (->> =)
|
/// Determine if documents exist using a JSON field comparison query (->> =)
|
||||||
[<CompiledName "ByField">]
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||||
let byField tableName field =
|
let byField tableName field =
|
||||||
WithProps.Exists.byField tableName field (fromDataSource ())
|
byFields tableName Any [ field ]
|
||||||
|
|
||||||
/// Determine if documents exist using a JSON containment query (@>)
|
/// Determine if documents exist using a JSON containment query (@>)
|
||||||
[<CompiledName "ByContains">]
|
[<CompiledName "ByContains">]
|
||||||
@@ -755,13 +823,24 @@ module Find =
|
|||||||
WithProps.Find.ById<'TKey, 'TDoc>(tableName, docId, fromDataSource ())
|
WithProps.Find.ById<'TKey, 'TDoc>(tableName, docId, fromDataSource ())
|
||||||
|
|
||||||
/// Retrieve documents matching a JSON field comparison query (->> =)
|
/// Retrieve documents matching a JSON field comparison query (->> =)
|
||||||
[<CompiledName "FSharpByField">]
|
[<CompiledName "FSharpByFields">]
|
||||||
let byField<'TDoc> tableName field =
|
let byFields<'TDoc> tableName howMatched fields =
|
||||||
WithProps.Find.byField<'TDoc> tableName field (fromDataSource ())
|
WithProps.Find.byFields<'TDoc> tableName howMatched fields (fromDataSource ())
|
||||||
|
|
||||||
/// Retrieve documents matching a JSON field comparison query (->> =)
|
/// 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) =
|
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 (@>)
|
/// Retrieve documents matching a JSON containment query (@>)
|
||||||
[<CompiledName "FSharpByContains">]
|
[<CompiledName "FSharpByContains">]
|
||||||
@@ -781,14 +860,25 @@ module Find =
|
|||||||
let ByJsonPath<'TDoc>(tableName, jsonPath) =
|
let ByJsonPath<'TDoc>(tableName, jsonPath) =
|
||||||
WithProps.Find.ByJsonPath<'TDoc>(tableName, jsonPath, fromDataSource ())
|
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
|
/// Retrieve the first document matching a JSON field comparison query (->> =); returns None if not found
|
||||||
[<CompiledName "FSharpFirstByField">]
|
[<CompiledName "FSharpFirstByField">]
|
||||||
|
[<System.Obsolete "Use firstByFields instead; will be removed in v4">]
|
||||||
let firstByField<'TDoc> tableName field =
|
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
|
/// 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) =
|
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
|
/// Retrieve the first document matching a JSON containment query (@>); returns None if not found
|
||||||
[<CompiledName "FSharpFirstByContains">]
|
[<CompiledName "FSharpFirstByContains">]
|
||||||
@@ -837,10 +927,16 @@ module Patch =
|
|||||||
let byId tableName (docId: 'TKey) (patch: 'TPatch) =
|
let byId tableName (docId: 'TKey) (patch: 'TPatch) =
|
||||||
WithProps.Patch.byId tableName docId patch (fromDataSource ())
|
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 (->> =)
|
/// Patch documents using a JSON field comparison query in the WHERE clause (->> =)
|
||||||
[<CompiledName "ByField">]
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||||
let byField tableName field (patch: 'TPatch) =
|
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 (@>)
|
/// Patch documents using a JSON containment query in the WHERE clause (@>)
|
||||||
[<CompiledName "ByContains">]
|
[<CompiledName "ByContains">]
|
||||||
@@ -858,41 +954,31 @@ module Patch =
|
|||||||
module RemoveFields =
|
module RemoveFields =
|
||||||
|
|
||||||
/// Remove fields from a document by the document's ID
|
/// Remove fields from a document by the document's ID
|
||||||
[<CompiledName "FSharpById">]
|
[<CompiledName "ById">]
|
||||||
let byId tableName (docId: 'TKey) fieldNames =
|
let byId tableName (docId: 'TKey) fieldNames =
|
||||||
WithProps.RemoveFields.byId tableName docId fieldNames (fromDataSource ())
|
WithProps.RemoveFields.byId tableName docId fieldNames (fromDataSource ())
|
||||||
|
|
||||||
/// Remove fields from a document by the document's ID
|
/// Remove fields from documents via a comparison on JSON fields in the document
|
||||||
let ById(tableName, docId: 'TKey, fieldNames) =
|
[<CompiledName "ByFields">]
|
||||||
WithProps.RemoveFields.ById(tableName, docId, fieldNames, fromDataSource ())
|
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
|
/// 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 =
|
let byField tableName field fieldNames =
|
||||||
WithProps.RemoveFields.byField tableName field fieldNames (fromDataSource ())
|
byFields tableName Any [ field ] fieldNames
|
||||||
|
|
||||||
/// 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 ())
|
|
||||||
|
|
||||||
/// Remove fields from documents via a JSON containment query (@>)
|
/// Remove fields from documents via a JSON containment query (@>)
|
||||||
[<CompiledName "FSharpByContains">]
|
[<CompiledName "ByContains">]
|
||||||
let byContains tableName (criteria: 'TContains) fieldNames =
|
let byContains tableName (criteria: 'TContains) fieldNames =
|
||||||
WithProps.RemoveFields.byContains tableName criteria fieldNames (fromDataSource ())
|
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 (@?)
|
/// Remove fields from documents via a JSON Path match query (@?)
|
||||||
[<CompiledName "FSharpByJsonPath">]
|
[<CompiledName "ByJsonPath">]
|
||||||
let byJsonPath tableName jsonPath fieldNames =
|
let byJsonPath tableName jsonPath fieldNames =
|
||||||
WithProps.RemoveFields.byJsonPath tableName jsonPath fieldNames (fromDataSource ())
|
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
|
/// Commands to delete documents
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
@@ -903,10 +989,16 @@ module Delete =
|
|||||||
let byId tableName (docId: 'TKey) =
|
let byId tableName (docId: 'TKey) =
|
||||||
WithProps.Delete.byId tableName docId (fromDataSource ())
|
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 (->> =)
|
/// Delete documents by matching a JSON field comparison query (->> =)
|
||||||
[<CompiledName "ByField">]
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||||
let byField tableName field =
|
let byField tableName field =
|
||||||
WithProps.Delete.byField tableName field (fromDataSource ())
|
byFields tableName Any [ field ]
|
||||||
|
|
||||||
/// Delete documents by matching a JSON containment query (@>)
|
/// Delete documents by matching a JSON containment query (@>)
|
||||||
[<CompiledName "ByContains">]
|
[<CompiledName "ByContains">]
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Description>Use SQLite as a document database</Description>
|
<Description>Use SQLite as a document database</Description>
|
||||||
<PackageReleaseNotes>Overall v3 release; initial release supporting SQLite</PackageReleaseNotes>
|
|
||||||
<PackageTags>JSON Document SQLite</PackageTags>
|
<PackageTags>JSON Document SQLite</PackageTags>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@@ -14,7 +13,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.1" />
|
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.6" />
|
||||||
|
<PackageReference Update="FSharp.Core" Version="8.0.300" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
namespace BitBadger.Documents.Sqlite
|
namespace BitBadger.Documents.Sqlite
|
||||||
|
|
||||||
|
open BitBadger.Documents
|
||||||
open Microsoft.Data.Sqlite
|
open Microsoft.Data.Sqlite
|
||||||
|
|
||||||
/// F# extensions for the SqliteConnection type
|
/// F# extensions for the SqliteConnection type
|
||||||
@@ -44,17 +45,27 @@ module Extensions =
|
|||||||
member conn.countAll tableName =
|
member conn.countAll tableName =
|
||||||
WithConn.Count.all tableName conn
|
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
|
/// 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 =
|
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
|
/// Determine if a document exists for the given ID
|
||||||
member conn.existsById tableName (docId: 'TKey) =
|
member conn.existsById tableName (docId: 'TKey) =
|
||||||
WithConn.Exists.byId tableName docId conn
|
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
|
/// 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 =
|
member conn.existsByField tableName field =
|
||||||
WithConn.Exists.byField tableName field conn
|
conn.existsByFields tableName Any [ field ]
|
||||||
|
|
||||||
/// Retrieve all documents in the given table
|
/// Retrieve all documents in the given table
|
||||||
member conn.findAll<'TDoc> tableName =
|
member conn.findAll<'TDoc> tableName =
|
||||||
@@ -64,13 +75,23 @@ module Extensions =
|
|||||||
member conn.findById<'TKey, 'TDoc> tableName (docId: 'TKey) =
|
member conn.findById<'TKey, 'TDoc> tableName (docId: 'TKey) =
|
||||||
WithConn.Find.byId<'TKey, 'TDoc> tableName docId conn
|
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
|
/// 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 =
|
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
|
/// 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 =
|
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
|
/// Update an entire document by its ID
|
||||||
member conn.updateById tableName (docId: 'TKey) (document: 'TDoc) =
|
member conn.updateById tableName (docId: 'TKey) (document: 'TDoc) =
|
||||||
@@ -84,25 +105,40 @@ module Extensions =
|
|||||||
member conn.patchById tableName (docId: 'TKey) (patch: 'TPatch) =
|
member conn.patchById tableName (docId: 'TKey) (patch: 'TPatch) =
|
||||||
WithConn.Patch.byId tableName docId patch conn
|
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
|
/// 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) =
|
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
|
/// Remove fields from a document by the document's ID
|
||||||
member conn.removeFieldsById tableName (docId: 'TKey) fieldNames =
|
member conn.removeFieldsById tableName (docId: 'TKey) fieldNames =
|
||||||
WithConn.RemoveFields.byId tableName docId fieldNames conn
|
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
|
/// 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 =
|
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
|
/// Delete a document by its ID
|
||||||
member conn.deleteById tableName (docId: 'TKey) =
|
member conn.deleteById tableName (docId: 'TKey) =
|
||||||
WithConn.Delete.byId tableName docId conn
|
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
|
/// 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 =
|
member conn.deleteByField tableName field =
|
||||||
WithConn.Delete.byField tableName field conn
|
conn.deleteByFields tableName Any [ field ]
|
||||||
|
|
||||||
|
|
||||||
open System.Runtime.CompilerServices
|
open System.Runtime.CompilerServices
|
||||||
@@ -157,20 +193,32 @@ type SqliteConnectionCSharpExtensions =
|
|||||||
static member inline CountAll(conn, tableName) =
|
static member inline CountAll(conn, tableName) =
|
||||||
WithConn.Count.all tableName conn
|
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
|
/// Count matching documents using a comparison on a JSON field
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
|
[<System.Obsolete "Use CountByFields instead; will be removed in v4">]
|
||||||
static member inline CountByField(conn, tableName, field) =
|
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
|
/// Determine if a document exists for the given ID
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline ExistsById<'TKey>(conn, tableName, docId: 'TKey) =
|
static member inline ExistsById<'TKey>(conn, tableName, docId: 'TKey) =
|
||||||
WithConn.Exists.byId tableName docId conn
|
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
|
/// Determine if a document exists using a comparison on a JSON field
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
|
[<System.Obsolete "Use ExistsByFields instead; will be removed in v4">]
|
||||||
static member inline ExistsByField(conn, tableName, field) =
|
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
|
/// Retrieve all documents in the given table
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
@@ -182,15 +230,27 @@ type SqliteConnectionCSharpExtensions =
|
|||||||
static member inline FindById<'TKey, 'TDoc when 'TDoc: null>(conn, tableName, docId: 'TKey) =
|
static member inline FindById<'TKey, 'TDoc when 'TDoc: null>(conn, tableName, docId: 'TKey) =
|
||||||
WithConn.Find.ById<'TKey, 'TDoc>(tableName, docId, conn)
|
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
|
/// Retrieve documents via a comparison on a JSON field
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
|
[<System.Obsolete "Use FindByFields instead; will be removed in v4">]
|
||||||
static member inline FindByField<'TDoc>(conn, tableName, field) =
|
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
|
/// Retrieve documents via a comparison on a JSON field, returning only the first result
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
|
[<System.Obsolete "Use FindFirstByFields instead; will be removed in v4">]
|
||||||
static member inline FindFirstByField<'TDoc when 'TDoc: null>(conn, tableName, field) =
|
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
|
/// Update an entire document by its ID
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
@@ -207,27 +267,45 @@ type SqliteConnectionCSharpExtensions =
|
|||||||
static member inline PatchById<'TKey, 'TPatch>(conn, tableName, docId: 'TKey, patch: 'TPatch) =
|
static member inline PatchById<'TKey, 'TPatch>(conn, tableName, docId: 'TKey, patch: 'TPatch) =
|
||||||
WithConn.Patch.byId tableName docId patch conn
|
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
|
/// Patch documents using a comparison on a JSON field
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
|
[<System.Obsolete "Use PatchByFields instead; will be removed in v4">]
|
||||||
static member inline PatchByField<'TPatch>(conn, tableName, field, patch: 'TPatch) =
|
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
|
/// Remove fields from a document by the document's ID
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline RemoveFieldsById<'TKey>(conn, tableName, docId: 'TKey, fieldNames) =
|
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
|
/// Remove fields from documents via a comparison on a JSON field in the document
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
|
[<System.Obsolete "Use RemoveFieldsByFields instead; will be removed in v4">]
|
||||||
static member inline RemoveFieldsByField(conn, tableName, field, fieldNames) =
|
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
|
/// Delete a document by its ID
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline DeleteById<'TKey>(conn, tableName, docId: 'TKey) =
|
static member inline DeleteById<'TKey>(conn, tableName, docId: 'TKey) =
|
||||||
WithConn.Delete.byId tableName docId conn
|
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
|
/// Delete documents by matching a comparison on a JSON field
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
|
[<System.Obsolete "Use DeleteByFields instead; will be removed in v4">]
|
||||||
static member inline DeleteByField(conn, tableName, field) =
|
static member inline DeleteByField(conn, tableName, field) =
|
||||||
WithConn.Delete.byField tableName field conn
|
conn.DeleteByFields(tableName, Any, [ field ])
|
||||||
|
|||||||
@@ -31,6 +31,48 @@ module Configuration =
|
|||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module Query =
|
module Query =
|
||||||
|
|
||||||
|
/// 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 =
|
||||||
|
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
|
/// Data definition
|
||||||
module Definition =
|
module Definition =
|
||||||
|
|
||||||
@@ -39,49 +81,6 @@ module Query =
|
|||||||
let ensureTable name =
|
let ensureTable name =
|
||||||
Query.Definition.ensureTableFor name "TEXT"
|
Query.Definition.ensureTableFor name "TEXT"
|
||||||
|
|
||||||
/// 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 =
|
|
||||||
Query.whereById "@id" |> update tableName
|
|
||||||
|
|
||||||
/// Query to patch (partially update) a document via a comparison on a JSON field
|
|
||||||
[<CompiledName "ByField">]
|
|
||||||
let byField tableName field =
|
|
||||||
Query.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 =
|
|
||||||
Query.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 =
|
|
||||||
Query.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)
|
|
||||||
|
|
||||||
|
|
||||||
/// Parameter handling helpers
|
/// Parameter handling helpers
|
||||||
[<AutoOpen>]
|
[<AutoOpen>]
|
||||||
@@ -97,25 +96,39 @@ module Parameters =
|
|||||||
let jsonParam name (it: 'TJson) =
|
let jsonParam name (it: 'TJson) =
|
||||||
SqliteParameter(name, Configuration.serializer().Serialize it)
|
SqliteParameter(name, Configuration.serializer().Serialize it)
|
||||||
|
|
||||||
|
/// 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 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
|
||||||
|
|> Seq.toList
|
||||||
|
|> Seq.ofList
|
||||||
|
|
||||||
/// Create a JSON field parameter (name "@field")
|
/// Create a JSON field parameter (name "@field")
|
||||||
[<CompiledName "FSharpAddField">]
|
[<CompiledName "AddField">]
|
||||||
|
[<System.Obsolete "Use addFieldParams instead; will be removed in v4">]
|
||||||
let addFieldParam name field parameters =
|
let addFieldParam name field parameters =
|
||||||
match field.Op with EX | NEX -> parameters | _ -> SqliteParameter(name, field.Value) :: parameters
|
addFieldParams [ { field with ParameterName = Some name } ] parameters
|
||||||
|
|
||||||
/// Create a JSON field parameter (name "@field")
|
|
||||||
let AddField(name, field, parameters) =
|
|
||||||
match field.Op with
|
|
||||||
| EX | NEX -> parameters
|
|
||||||
| _ -> SqliteParameter(name, field.Value) |> Seq.singleton |> Seq.append parameters
|
|
||||||
|
|
||||||
/// Append JSON field name parameters for the given field names to the given parameters
|
/// Append JSON field name parameters for the given field names to the given parameters
|
||||||
[<CompiledName "FSharpFieldNames">]
|
[<CompiledName "FieldNames">]
|
||||||
let fieldNameParams paramName (fieldNames: string list) =
|
let fieldNameParams paramName fieldNames =
|
||||||
fieldNames |> List.mapi (fun idx name -> SqliteParameter($"%s{paramName}{idx}", $"$.{name}"))
|
fieldNames
|
||||||
|
|> Seq.mapi (fun idx name -> SqliteParameter($"%s{paramName}{idx}", $"$.%s{name}"))
|
||||||
/// Append JSON field name parameters for the given field names to the given parameters
|
|> Seq.toList
|
||||||
let FieldNames(paramName, fieldNames: string seq) =
|
|> Seq.ofList
|
||||||
fieldNames |> Seq.mapi (fun idx name -> SqliteParameter($"%s{paramName}{idx}", $"$.{name}"))
|
|
||||||
|
|
||||||
/// An empty parameter sequence
|
/// An empty parameter sequence
|
||||||
[<CompiledName "None">]
|
[<CompiledName "None">]
|
||||||
@@ -237,13 +250,13 @@ module WithConn =
|
|||||||
[<CompiledName "EnsureTable">]
|
[<CompiledName "EnsureTable">]
|
||||||
let ensureTable name conn = backgroundTask {
|
let ensureTable name conn = backgroundTask {
|
||||||
do! Custom.nonQuery (Query.Definition.ensureTable name) [] conn
|
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
|
/// Create an index on a document table
|
||||||
[<CompiledName "EnsureFieldIndex">]
|
[<CompiledName "EnsureFieldIndex">]
|
||||||
let ensureFieldIndex tableName indexName fields conn =
|
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
|
/// Insert a new document
|
||||||
[<CompiledName "Insert">]
|
[<CompiledName "Insert">]
|
||||||
@@ -262,12 +275,13 @@ module WithConn =
|
|||||||
/// Count all documents in a table
|
/// Count all documents in a table
|
||||||
[<CompiledName "All">]
|
[<CompiledName "All">]
|
||||||
let all tableName conn =
|
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
|
/// Count matching documents using a comparison on JSON fields
|
||||||
[<CompiledName "ByField">]
|
[<CompiledName "ByFields">]
|
||||||
let byField tableName field conn =
|
let byFields tableName howMatched fields conn =
|
||||||
Custom.scalar (Query.Count.byField tableName field) (addFieldParam "@field" field []) toCount conn
|
Custom.scalar
|
||||||
|
(Query.byFields (Query.count tableName) howMatched fields) (addFieldParams fields []) toCount conn
|
||||||
|
|
||||||
/// Commands to determine if documents exist
|
/// Commands to determine if documents exist
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
@@ -276,12 +290,16 @@ module WithConn =
|
|||||||
/// Determine if a document exists for the given ID
|
/// Determine if a document exists for the given ID
|
||||||
[<CompiledName "ById">]
|
[<CompiledName "ById">]
|
||||||
let byId tableName (docId: 'TKey) conn =
|
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
|
/// Determine if a document exists using a comparison on JSON fields
|
||||||
[<CompiledName "ByField">]
|
[<CompiledName "ByFields">]
|
||||||
let byField tableName field conn =
|
let byFields tableName howMatched fields conn =
|
||||||
Custom.scalar (Query.Exists.byField tableName field) (addFieldParam "@field" field []) toExists conn
|
Custom.scalar
|
||||||
|
(Query.exists tableName (Query.whereByFields howMatched fields))
|
||||||
|
(addFieldParams fields [])
|
||||||
|
toExists
|
||||||
|
conn
|
||||||
|
|
||||||
/// Commands to retrieve documents
|
/// Commands to retrieve documents
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
@@ -290,42 +308,76 @@ module WithConn =
|
|||||||
/// Retrieve all documents in the given table
|
/// Retrieve all documents in the given table
|
||||||
[<CompiledName "FSharpAll">]
|
[<CompiledName "FSharpAll">]
|
||||||
let all<'TDoc> tableName conn =
|
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
|
/// Retrieve all documents in the given table
|
||||||
let All<'TDoc>(tableName, conn) =
|
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)
|
/// Retrieve a document by its ID (returns None if not found)
|
||||||
[<CompiledName "FSharpById">]
|
[<CompiledName "FSharpById">]
|
||||||
let byId<'TKey, 'TDoc> tableName (docId: 'TKey) conn =
|
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)
|
/// Retrieve a document by its ID (returns null if not found)
|
||||||
let ById<'TKey, 'TDoc when 'TDoc: null>(tableName, docId: 'TKey, conn) =
|
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
|
/// Retrieve documents via a comparison on a JSON field
|
||||||
[<CompiledName "FSharpByField">]
|
[<CompiledName "FSharpByField">]
|
||||||
|
[<System.Obsolete "Use byFields instead; will be removed in v4">]
|
||||||
let byField<'TDoc> tableName field conn =
|
let byField<'TDoc> tableName field conn =
|
||||||
Custom.list<'TDoc>
|
byFields<'TDoc> tableName Any [ field ] conn
|
||||||
(Query.Find.byField tableName field) (addFieldParam "@field" field []) fromData<'TDoc> 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
|
/// 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) =
|
let ByField<'TDoc>(tableName, field, conn) =
|
||||||
Custom.List<'TDoc>(
|
ByFields<'TDoc>(tableName, Any, [ field ], conn)
|
||||||
Query.Find.byField tableName field, addFieldParam "@field" field [], fromData<'TDoc>, 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
|
/// Retrieve documents via a comparison on a JSON field, returning only the first result
|
||||||
[<CompiledName "FSharpFirstByField">]
|
[<CompiledName "FSharpFirstByField">]
|
||||||
|
[<System.Obsolete "Use firstByFields instead; will be removed in v4">]
|
||||||
let firstByField<'TDoc> tableName field conn =
|
let firstByField<'TDoc> tableName field conn =
|
||||||
Custom.single
|
firstByFields<'TDoc> tableName Any [ field ] conn
|
||||||
$"{Query.Find.byField tableName field} LIMIT 1" (addFieldParam "@field" field []) fromData<'TDoc> 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
|
/// 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) =
|
let FirstByField<'TDoc when 'TDoc: null>(tableName, field, conn) =
|
||||||
Custom.Single(
|
FirstByFields(tableName, Any, [ field ], conn)
|
||||||
$"{Query.Find.byField tableName field} LIMIT 1", addFieldParam "@field" field [], fromData<'TDoc>, conn)
|
|
||||||
|
|
||||||
/// Commands to update documents
|
/// Commands to update documents
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
@@ -334,7 +386,10 @@ module WithConn =
|
|||||||
/// Update an entire document by its ID
|
/// Update an entire document by its ID
|
||||||
[<CompiledName "ById">]
|
[<CompiledName "ById">]
|
||||||
let byId tableName (docId: 'TKey) (document: 'TDoc) conn =
|
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
|
/// Update an entire document by its ID, using the provided function to obtain the ID from the document
|
||||||
[<CompiledName "FSharpByFunc">]
|
[<CompiledName "FSharpByFunc">]
|
||||||
@@ -352,38 +407,50 @@ module WithConn =
|
|||||||
/// Patch a document by its ID
|
/// Patch a document by its ID
|
||||||
[<CompiledName "ById">]
|
[<CompiledName "ById">]
|
||||||
let byId tableName (docId: 'TKey) (patch: 'TPatch) conn =
|
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
|
/// Patch documents using a comparison on a JSON field
|
||||||
[<CompiledName "ByField">]
|
[<CompiledName "ByField">]
|
||||||
let byField tableName field (patch: 'TPatch) (conn: SqliteConnection) =
|
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||||
Custom.nonQuery
|
let byField tableName field (patch: 'TPatch) conn =
|
||||||
(Query.Patch.byField tableName field) (addFieldParam "@field" field [ jsonParam "@data" patch ]) conn
|
byFields tableName Any [ field ] patch conn
|
||||||
|
|
||||||
/// Commands to remove fields from documents
|
/// Commands to remove fields from documents
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module RemoveFields =
|
module RemoveFields =
|
||||||
|
|
||||||
/// Remove fields from a document by the document's ID
|
/// Remove fields from a document by the document's ID
|
||||||
[<CompiledName "FSharpById">]
|
[<CompiledName "ById">]
|
||||||
let byId tableName (docId: 'TKey) fieldNames conn =
|
let byId tableName (docId: 'TKey) fieldNames conn =
|
||||||
let nameParams = fieldNameParams "@name" fieldNames
|
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
|
/// Remove fields from documents via a comparison on JSON fields in the document
|
||||||
let ById(tableName, docId: 'TKey, fieldNames, conn) =
|
[<CompiledName "ByFields">]
|
||||||
byId tableName docId (List.ofSeq fieldNames) conn
|
let byFields tableName howMatched fields fieldNames conn =
|
||||||
|
|
||||||
/// Remove fields from documents via a comparison on a JSON field in the document
|
|
||||||
[<CompiledName "FSharpByField">]
|
|
||||||
let byField tableName field fieldNames conn =
|
|
||||||
let nameParams = fieldNameParams "@name" fieldNames
|
let nameParams = fieldNameParams "@name" fieldNames
|
||||||
Custom.nonQuery
|
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
|
/// Remove fields from documents via a comparison on a JSON field in the document
|
||||||
let ByField(tableName, field, fieldNames, conn) =
|
[<CompiledName "ByField">]
|
||||||
byField tableName field (List.ofSeq fieldNames) conn
|
[<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
|
/// Commands to delete documents
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
@@ -392,12 +459,18 @@ module WithConn =
|
|||||||
/// Delete a document by its ID
|
/// Delete a document by its ID
|
||||||
[<CompiledName "ById">]
|
[<CompiledName "ById">]
|
||||||
let byId tableName (docId: 'TKey) conn =
|
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
|
/// Delete documents by matching a comparison on a JSON field
|
||||||
[<CompiledName "ByField">]
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||||
let byField tableName field conn =
|
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
|
/// Commands to execute custom SQL queries
|
||||||
@@ -485,11 +558,17 @@ module Count =
|
|||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
WithConn.Count.all tableName conn
|
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
|
/// Count matching documents using a comparison on a JSON field
|
||||||
[<CompiledName "ByField">]
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||||
let byField tableName field =
|
let byField tableName field =
|
||||||
use conn = Configuration.dbConn ()
|
byFields tableName Any [ field ]
|
||||||
WithConn.Count.byField tableName field conn
|
|
||||||
|
|
||||||
/// Commands to determine if documents exist
|
/// Commands to determine if documents exist
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
@@ -501,11 +580,17 @@ module Exists =
|
|||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
WithConn.Exists.byId tableName docId conn
|
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
|
/// Determine if a document exists using a comparison on a JSON field
|
||||||
[<CompiledName "ByField">]
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||||
let byField tableName field =
|
let byField tableName field =
|
||||||
use conn = Configuration.dbConn ()
|
byFields tableName Any [ field ]
|
||||||
WithConn.Exists.byField tableName field conn
|
|
||||||
|
|
||||||
/// Commands to determine if documents exist
|
/// Commands to determine if documents exist
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
@@ -533,27 +618,49 @@ module Find =
|
|||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
WithConn.Find.ById<'TKey, 'TDoc>(tableName, docId, conn)
|
WithConn.Find.ById<'TKey, 'TDoc>(tableName, docId, conn)
|
||||||
|
|
||||||
/// Retrieve documents via a comparison on a JSON field
|
/// Retrieve documents via a comparison on JSON fields
|
||||||
[<CompiledName "FSharpByField">]
|
[<CompiledName "FSharpByFields">]
|
||||||
let byField<'TDoc> tableName field =
|
let byFields<'TDoc> tableName howMatched fields =
|
||||||
use conn = Configuration.dbConn ()
|
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
|
/// 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 ()
|
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
|
/// Retrieve documents via a comparison on a JSON field, returning only the first result
|
||||||
[<CompiledName "FSharpFirstByField">]
|
[<CompiledName "FSharpFirstByField">]
|
||||||
|
[<System.Obsolete "Use firstByFields instead; will be removed in v4">]
|
||||||
let firstByField<'TDoc> tableName field =
|
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 ()
|
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
|
/// 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) =
|
let FirstByField<'TDoc when 'TDoc: null>(tableName, field) =
|
||||||
use conn = Configuration.dbConn ()
|
FirstByFields<'TDoc>(tableName, Any, [ field ])
|
||||||
WithConn.Find.FirstByField<'TDoc>(tableName, field, conn)
|
|
||||||
|
|
||||||
/// Commands to update documents
|
/// Commands to update documents
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
@@ -586,37 +693,39 @@ module Patch =
|
|||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
WithConn.Patch.byId tableName docId patch conn
|
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
|
/// Patch documents using a comparison on a JSON field in the WHERE clause
|
||||||
[<CompiledName "ByField">]
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||||
let byField tableName field (patch: 'TPatch) =
|
let byField tableName field (patch: 'TPatch) =
|
||||||
use conn = Configuration.dbConn ()
|
byFields tableName Any [ field ] patch
|
||||||
WithConn.Patch.byField tableName field patch conn
|
|
||||||
|
|
||||||
/// Commands to remove fields from documents
|
/// Commands to remove fields from documents
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module RemoveFields =
|
module RemoveFields =
|
||||||
|
|
||||||
/// Remove fields from a document by the document's ID
|
/// Remove fields from a document by the document's ID
|
||||||
[<CompiledName "FSharpById">]
|
[<CompiledName "ById">]
|
||||||
let byId tableName (docId: 'TKey) fieldNames =
|
let byId tableName (docId: 'TKey) fieldNames =
|
||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
WithConn.RemoveFields.byId tableName docId fieldNames conn
|
WithConn.RemoveFields.byId tableName docId fieldNames conn
|
||||||
|
|
||||||
/// Remove fields from a document by the document's ID
|
/// Remove field from documents via a comparison on JSON fields in the document
|
||||||
let ById(tableName, docId: 'TKey, fieldNames) =
|
[<CompiledName "ByFields">]
|
||||||
|
let byFields tableName howMatched fields fieldNames =
|
||||||
use conn = Configuration.dbConn ()
|
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
|
/// 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 =
|
let byField tableName field fieldNames =
|
||||||
use conn = Configuration.dbConn ()
|
byFields tableName Any [ field ] fieldNames
|
||||||
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)
|
|
||||||
|
|
||||||
/// Commands to delete documents
|
/// Commands to delete documents
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
@@ -628,8 +737,14 @@ module Delete =
|
|||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
WithConn.Delete.byId tableName docId conn
|
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
|
/// Delete documents by matching a comparison on a JSON field
|
||||||
[<CompiledName "ByField">]
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
|
||||||
let byField tableName field =
|
let byField tableName field =
|
||||||
use conn = Configuration.dbConn ()
|
byFields tableName Any [ field ]
|
||||||
WithConn.Delete.byField tableName field conn
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -12,7 +13,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Expecto" Version="10.1.0" />
|
<PackageReference Include="Expecto" Version="10.2.1" />
|
||||||
<PackageReference Include="ThrowawayDb.Postgres" Version="1.4.0" />
|
<PackageReference Include="ThrowawayDb.Postgres" Version="1.4.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using Expecto.CSharp;
|
using Expecto.CSharp;
|
||||||
using Expecto;
|
using Expecto;
|
||||||
|
using Microsoft.FSharp.Collections;
|
||||||
|
using Microsoft.FSharp.Core;
|
||||||
|
|
||||||
namespace BitBadger.Documents.Tests.CSharp;
|
namespace BitBadger.Documents.Tests.CSharp;
|
||||||
|
|
||||||
@@ -23,11 +25,11 @@ public static class CommonCSharpTests
|
|||||||
/// Unit tests
|
/// Unit tests
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Tests]
|
[Tests]
|
||||||
public static readonly Test Unit = TestList("Common.C# Unit", new[]
|
public static readonly Test Unit = TestList("Common.C# Unit",
|
||||||
{
|
[
|
||||||
TestSequenced(
|
TestSequenced(
|
||||||
TestList("Configuration", new[]
|
TestList("Configuration",
|
||||||
{
|
[
|
||||||
TestCase("UseSerializer succeeds", () =>
|
TestCase("UseSerializer succeeds", () =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -69,9 +71,9 @@ public static class CommonCSharpTests
|
|||||||
Configuration.UseIdField("Id");
|
Configuration.UseIdField("Id");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})),
|
])),
|
||||||
TestList("Op", new[]
|
TestList("Op",
|
||||||
{
|
[
|
||||||
TestCase("EQ succeeds", () =>
|
TestCase("EQ succeeds", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(Op.EQ.ToString(), "=", "The equals operator was not correct");
|
Expect.equal(Op.EQ.ToString(), "=", "The equals operator was not correct");
|
||||||
@@ -96,6 +98,10 @@ public static class CommonCSharpTests
|
|||||||
{
|
{
|
||||||
Expect.equal(Op.NE.ToString(), "<>", "The not equal to operator was not correct");
|
Expect.equal(Op.NE.ToString(), "<>", "The not equal to operator was not correct");
|
||||||
}),
|
}),
|
||||||
|
TestCase("BT succeeds", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(Op.BT.ToString(), "BETWEEN", "The \"between\" operator was not correct");
|
||||||
|
}),
|
||||||
TestCase("EX succeeds", () =>
|
TestCase("EX succeeds", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(Op.EX.ToString(), "IS NOT NULL", "The \"exists\" operator was not correct");
|
Expect.equal(Op.EX.ToString(), "IS NOT NULL", "The \"exists\" operator was not correct");
|
||||||
@@ -104,9 +110,9 @@ public static class CommonCSharpTests
|
|||||||
{
|
{
|
||||||
Expect.equal(Op.NEX.ToString(), "IS NULL", "The \"not exists\" operator was not correct");
|
Expect.equal(Op.NEX.ToString(), "IS NULL", "The \"not exists\" operator was not correct");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("Field", new[]
|
TestList("Field",
|
||||||
{
|
[
|
||||||
TestCase("EQ succeeds", () =>
|
TestCase("EQ succeeds", () =>
|
||||||
{
|
{
|
||||||
var field = Field.EQ("Test", 14);
|
var field = Field.EQ("Test", 14);
|
||||||
@@ -149,6 +155,13 @@ public static class CommonCSharpTests
|
|||||||
Expect.equal(field.Op, Op.NE, "Operator incorrect");
|
Expect.equal(field.Op, Op.NE, "Operator incorrect");
|
||||||
Expect.equal(field.Value, "here", "Value incorrect");
|
Expect.equal(field.Value, "here", "Value incorrect");
|
||||||
}),
|
}),
|
||||||
|
TestCase("BT succeeds", () =>
|
||||||
|
{
|
||||||
|
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(), [18, 49], "Value incorrect");
|
||||||
|
}),
|
||||||
TestCase("EX succeeds", () =>
|
TestCase("EX succeeds", () =>
|
||||||
{
|
{
|
||||||
var field = Field.EX("Groovy");
|
var field = Field.EX("Groovy");
|
||||||
@@ -160,65 +173,159 @@ public static class CommonCSharpTests
|
|||||||
var field = Field.NEX("Rad");
|
var field = Field.NEX("Rad");
|
||||||
Expect.equal(field.Name, "Rad", "Field name incorrect");
|
Expect.equal(field.Name, "Rad", "Field name incorrect");
|
||||||
Expect.equal(field.Op, Op.NEX, "Operator incorrect");
|
Expect.equal(field.Op, Op.NEX, "Operator incorrect");
|
||||||
})
|
|
||||||
}),
|
|
||||||
TestList("Query", new[]
|
|
||||||
{
|
|
||||||
TestCase("SelectFromTable succeeds", () =>
|
|
||||||
{
|
|
||||||
Expect.equal(Query.SelectFromTable("test.table"), "SELECT data FROM test.table",
|
|
||||||
"SELECT statement not correct");
|
|
||||||
}),
|
}),
|
||||||
TestCase("WhereById succeeds", () =>
|
TestCase("WithParameterName succeeds", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(Query.WhereById("@id"), "data ->> 'Id' = @id", "WHERE clause not correct");
|
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");
|
||||||
}),
|
}),
|
||||||
TestList("WhereByField", new[]
|
TestCase("WithQualifier succeeds", () =>
|
||||||
{
|
{
|
||||||
TestCase("succeeds when a logical operator is passed", () =>
|
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", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(Query.WhereByField(Field.GT("theField", 0), "@test"), "data ->> 'theField' > @test",
|
var field = Field.GE("SomethingCool", 18);
|
||||||
"WHERE clause not correct");
|
Expect.equal("data->>'SomethingCool'", field.Path(Dialect.PostgreSQL),
|
||||||
|
"The PostgreSQL path is incorrect");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when an existence operator is passed", () =>
|
TestCase("succeeds for a PostgreSQL single field with a qualifier", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(Query.WhereByField(Field.NEX("thatField"), ""), "data ->> 'thatField' IS NULL",
|
var field = Field.LT("SomethingElse", 9).WithQualifier("this");
|
||||||
"WHERE clause not correct");
|
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("Definition", new[]
|
]),
|
||||||
|
TestList("FieldMatch.ToString",
|
||||||
|
[
|
||||||
|
TestCase("succeeds for Any", () =>
|
||||||
{
|
{
|
||||||
|
Expect.equal(FieldMatch.Any.ToString(), "OR", "SQL for Any is incorrect");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for All", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(FieldMatch.All.ToString(), "AND", "SQL for All is incorrect");
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
TestList("ParameterName.Derive",
|
||||||
|
[
|
||||||
|
TestCase("succeeds with existing name", () =>
|
||||||
|
{
|
||||||
|
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");
|
||||||
|
}),
|
||||||
|
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", () =>
|
TestCase("EnsureTableFor succeeds", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(Query.Definition.EnsureTableFor("my.table", "JSONB"),
|
Expect.equal(Query.Definition.EnsureTableFor("my.table", "JSONB"),
|
||||||
"CREATE TABLE IF NOT EXISTS my.table (data JSONB NOT NULL)",
|
"CREATE TABLE IF NOT EXISTS my.table (data JSONB NOT NULL)",
|
||||||
"CREATE TABLE statement not constructed correctly");
|
"CREATE TABLE statement not constructed correctly");
|
||||||
}),
|
}),
|
||||||
TestList("EnsureKey", new[]
|
TestList("EnsureKey",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a schema is present", () =>
|
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 UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data->>'Id'))",
|
||||||
"CREATE INDEX for key statement with schema not constructed correctly");
|
"CREATE INDEX for key statement with schema not constructed correctly");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when a schema is not present", () =>
|
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 UNIQUE INDEX IF NOT EXISTS idx_table_key ON table ((data->>'Id'))",
|
||||||
"CREATE INDEX for key statement without schema not constructed correctly");
|
"CREATE INDEX for key statement without schema not constructed correctly");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestCase("EnsureIndexOn succeeds for multiple fields and directions", () =>
|
TestList("EnsureIndexOn",
|
||||||
{
|
[
|
||||||
Expect.equal(
|
TestCase("succeeds for multiple fields and directions", () =>
|
||||||
Query.Definition.EnsureIndexOn("test.table", "gibberish",
|
{
|
||||||
new[] { "taco", "guac DESC", "salsa ASC" }),
|
Expect.equal(
|
||||||
"CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table "
|
Query.Definition.EnsureIndexOn("test.table", "gibberish",
|
||||||
+ "((data ->> 'taco'), (data ->> 'guac') DESC, (data ->> 'salsa') ASC)",
|
new[] { "taco", "guac DESC", "salsa ASC" }, Dialect.SQLite),
|
||||||
"CREATE INDEX for multiple field statement incorrect");
|
"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", () =>
|
TestCase("Insert succeeds", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(Query.Insert("tbl"), "INSERT INTO tbl VALUES (@data)", "INSERT statement not correct");
|
Expect.equal(Query.Insert("tbl"), "INSERT INTO tbl VALUES (@data)", "INSERT statement not correct");
|
||||||
@@ -226,70 +333,30 @@ public static class CommonCSharpTests
|
|||||||
TestCase("Save succeeds", () =>
|
TestCase("Save succeeds", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(Query.Save("tbl"),
|
Expect.equal(Query.Save("tbl"),
|
||||||
$"INSERT INTO tbl VALUES (@data) ON CONFLICT ((data ->> 'Id')) DO UPDATE SET data = EXCLUDED.data",
|
"INSERT INTO tbl VALUES (@data) ON CONFLICT ((data->>'Id')) DO UPDATE SET data = EXCLUDED.data",
|
||||||
"INSERT ON CONFLICT UPDATE statement not correct");
|
"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", () =>
|
TestCase("Update succeeds", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(Query.Update("tbl"), "UPDATE tbl SET data = @data WHERE data ->> 'Id' = @id",
|
Expect.equal(Query.Update("tbl"), "UPDATE tbl SET data = @data", "Update query not correct");
|
||||||
"UPDATE full statement not correct");
|
|
||||||
}),
|
}),
|
||||||
TestList("Count", new[]
|
TestCase("Delete succeeds", () =>
|
||||||
{
|
{
|
||||||
TestCase("All succeeds", () =>
|
Expect.equal(Query.Delete("tbl"), "DELETE FROM tbl", "Delete query not correct");
|
||||||
{
|
|
||||||
Expect.equal(Query.Count.All("tbl"), "SELECT COUNT(*) AS it FROM tbl", "Count query not correct");
|
|
||||||
}),
|
|
||||||
TestCase("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", new[]
|
|
||||||
{
|
|
||||||
TestCase("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");
|
|
||||||
}),
|
|
||||||
TestCase("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", new[]
|
|
||||||
{
|
|
||||||
TestCase("ById succeeds", () =>
|
|
||||||
{
|
|
||||||
Expect.equal(Query.Find.ById("tbl"), "SELECT data FROM tbl WHERE data ->> 'Id' = @id",
|
|
||||||
"SELECT by ID query not correct");
|
|
||||||
}),
|
|
||||||
TestCase("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("Delete", new[]
|
|
||||||
{
|
|
||||||
TestCase("ById succeeds", () =>
|
|
||||||
{
|
|
||||||
Expect.equal(Query.Delete.ById("tbl"), "DELETE FROM tbl WHERE data ->> 'Id' = @id",
|
|
||||||
"DELETE by ID query not correct");
|
|
||||||
}),
|
|
||||||
TestCase("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");
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
])
|
||||||
});
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ public class PostgresCSharpExtensionTests
|
|||||||
/// Integration tests for the SQLite extension methods
|
/// Integration tests for the SQLite extension methods
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Tests]
|
[Tests]
|
||||||
public static readonly Test Integration = TestList("Postgres.C#.Extensions", new[]
|
public static readonly Test Integration = TestList("Postgres.C#.Extensions",
|
||||||
{
|
[
|
||||||
TestList("CustomList", new[]
|
TestList("CustomList",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when data is found", async () =>
|
TestCase("succeeds when data is found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -57,9 +57,9 @@ public class PostgresCSharpExtensionTests
|
|||||||
Results.FromData<JsonDocument>);
|
Results.FromData<JsonDocument>);
|
||||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("CustomSingle", new[]
|
TestList("CustomSingle",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a row is found", async () =>
|
TestCase("succeeds when a row is found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -81,9 +81,9 @@ public class PostgresCSharpExtensionTests
|
|||||||
new[] { Tuple.Create("@id", Sql.@string("eighty")) }, Results.FromData<JsonDocument>);
|
new[] { Tuple.Create("@id", Sql.@string("eighty")) }, Results.FromData<JsonDocument>);
|
||||||
Expect.isNull(doc, "There should not have been a document returned");
|
Expect.isNull(doc, "There should not have been a document returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("CustomNonQuery", new[]
|
TestList("CustomNonQuery",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when operating on data", async () =>
|
TestCase("succeeds when operating on data", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -107,7 +107,7 @@ public class PostgresCSharpExtensionTests
|
|||||||
var remaining = await conn.CountAll(PostgresDb.TableName);
|
var remaining = await conn.CountAll(PostgresDb.TableName);
|
||||||
Expect.equal(remaining, 5, "There should be 5 documents remaining in the table");
|
Expect.equal(remaining, 5, "There should be 5 documents remaining in the table");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestCase("Scalar succeeds", async () =>
|
TestCase("Scalar succeeds", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -169,8 +169,8 @@ public class PostgresCSharpExtensionTests
|
|||||||
exists = await indexExists();
|
exists = await indexExists();
|
||||||
Expect.isTrue(exists, "The index should now exist");
|
Expect.isTrue(exists, "The index should now exist");
|
||||||
}),
|
}),
|
||||||
TestList("Insert", new[]
|
TestList("Insert",
|
||||||
{
|
[
|
||||||
TestCase("succeeds", async () =>
|
TestCase("succeeds", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -198,9 +198,9 @@ public class PostgresCSharpExtensionTests
|
|||||||
// This is what should have happened
|
// This is what should have happened
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("save", new[]
|
TestList("save",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is inserted", async () =>
|
TestCase("succeeds when a document is inserted", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
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.isNotNull(after, "There should have been a document returned post-update");
|
||||||
Expect.equal(after.Sub!.Foo, "c", "The updated document is not correct");
|
Expect.equal(after.Sub!.Foo, "c", "The updated document is not correct");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestCase("CountAll succeeds", async () =>
|
TestCase("CountAll succeeds", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -240,6 +240,7 @@ public class PostgresCSharpExtensionTests
|
|||||||
var theCount = await conn.CountAll(PostgresDb.TableName);
|
var theCount = await conn.CountAll(PostgresDb.TableName);
|
||||||
Expect.equal(theCount, 5, "There should have been 5 matching documents");
|
Expect.equal(theCount, 5, "There should have been 5 matching documents");
|
||||||
}),
|
}),
|
||||||
|
#pragma warning disable CS0618
|
||||||
TestCase("CountByField succeeds", async () =>
|
TestCase("CountByField succeeds", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -249,6 +250,7 @@ public class PostgresCSharpExtensionTests
|
|||||||
var theCount = await conn.CountByField(PostgresDb.TableName, Field.EQ("Value", "purple"));
|
var theCount = await conn.CountByField(PostgresDb.TableName, Field.EQ("Value", "purple"));
|
||||||
Expect.equal(theCount, 2, "There should have been 2 matching documents");
|
Expect.equal(theCount, 2, "There should have been 2 matching documents");
|
||||||
}),
|
}),
|
||||||
|
#pragma warning restore CS0618
|
||||||
TestCase("CountByContains succeeds", async () =>
|
TestCase("CountByContains succeeds", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -267,8 +269,8 @@ public class PostgresCSharpExtensionTests
|
|||||||
var theCount = await conn.CountByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ > 5)");
|
var theCount = await conn.CountByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ > 5)");
|
||||||
Expect.equal(theCount, 3, "There should have been 3 matching documents");
|
Expect.equal(theCount, 3, "There should have been 3 matching documents");
|
||||||
}),
|
}),
|
||||||
TestList("ExistsById", new[]
|
TestList("ExistsById",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document exists", async () =>
|
TestCase("succeeds when a document exists", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -287,9 +289,10 @@ public class PostgresCSharpExtensionTests
|
|||||||
var exists = await conn.ExistsById(PostgresDb.TableName, "seven");
|
var exists = await conn.ExistsById(PostgresDb.TableName, "seven");
|
||||||
Expect.isFalse(exists, "There should not have been an existing document");
|
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 () =>
|
TestCase("succeeds when documents exist", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -308,9 +311,10 @@ public class PostgresCSharpExtensionTests
|
|||||||
var exists = await conn.ExistsByField(PostgresDb.TableName, Field.EQ("NumValue", "six"));
|
var exists = await conn.ExistsByField(PostgresDb.TableName, Field.EQ("NumValue", "six"));
|
||||||
Expect.isFalse(exists, "There should not have been existing documents");
|
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 () =>
|
TestCase("succeeds when documents exist", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -329,9 +333,9 @@ public class PostgresCSharpExtensionTests
|
|||||||
var exists = await conn.ExistsByContains(PostgresDb.TableName, new { Nothing = "none" });
|
var exists = await conn.ExistsByContains(PostgresDb.TableName, new { Nothing = "none" });
|
||||||
Expect.isFalse(exists, "There should not have been any existing documents");
|
Expect.isFalse(exists, "There should not have been any existing documents");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("ExistsByJsonPath", new[]
|
TestList("ExistsByJsonPath",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when documents exist", async () =>
|
TestCase("succeeds when documents exist", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -350,9 +354,9 @@ public class PostgresCSharpExtensionTests
|
|||||||
var exists = await conn.ExistsByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ > 1000)");
|
var exists = await conn.ExistsByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ > 1000)");
|
||||||
Expect.isFalse(exists, "There should not have been any existing documents");
|
Expect.isFalse(exists, "There should not have been any existing documents");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("FindAll", new[]
|
TestList("FindAll",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when there is data", async () =>
|
TestCase("succeeds when there is data", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -372,9 +376,9 @@ public class PostgresCSharpExtensionTests
|
|||||||
var results = await conn.FindAll<JsonDocument>(PostgresDb.TableName);
|
var results = await conn.FindAll<JsonDocument>(PostgresDb.TableName);
|
||||||
Expect.isEmpty(results, "There should have been no documents returned");
|
Expect.isEmpty(results, "There should have been no documents returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("FindById", new[]
|
TestList("FindById",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is found", async () =>
|
TestCase("succeeds when a document is found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
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");
|
var doc = await conn.FindById<string, JsonDocument>(PostgresDb.TableName, "three hundred eighty-seven");
|
||||||
Expect.isNull(doc, "There should not have been a document returned");
|
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 () =>
|
TestCase("succeeds when documents are found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
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"));
|
var docs = await conn.FindByField<JsonDocument>(PostgresDb.TableName, Field.EQ("Value", "mauve"));
|
||||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
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 () =>
|
TestCase("succeeds when documents are found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -437,9 +443,9 @@ public class PostgresCSharpExtensionTests
|
|||||||
var docs = await conn.FindByContains<JsonDocument>(PostgresDb.TableName, new { Value = "mauve" });
|
var docs = await conn.FindByContains<JsonDocument>(PostgresDb.TableName, new { Value = "mauve" });
|
||||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("FindByJsonPath", new[]
|
TestList("FindByJsonPath",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when documents are found", async () =>
|
TestCase("succeeds when documents are found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -458,9 +464,10 @@ public class PostgresCSharpExtensionTests
|
|||||||
var docs = await conn.FindByJsonPath<JsonDocument>(PostgresDb.TableName, "$.NumValue ? (@ < 0)");
|
var docs = await conn.FindByJsonPath<JsonDocument>(PostgresDb.TableName, "$.NumValue ? (@ < 0)");
|
||||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
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 () =>
|
TestCase("succeeds when a document is found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
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"));
|
var doc = await conn.FindFirstByField<JsonDocument>(PostgresDb.TableName, Field.EQ("Value", "absent"));
|
||||||
Expect.isNull(doc, "There should not have been a document returned");
|
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 () =>
|
TestCase("succeeds when a document is found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -523,9 +531,9 @@ public class PostgresCSharpExtensionTests
|
|||||||
var doc = await conn.FindFirstByContains<JsonDocument>(PostgresDb.TableName, new { Value = "absent" });
|
var doc = await conn.FindFirstByContains<JsonDocument>(PostgresDb.TableName, new { Value = "absent" });
|
||||||
Expect.isNull(doc, "There should not have been a document returned");
|
Expect.isNull(doc, "There should not have been a document returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("FindFirstByJsonPath", new[]
|
TestList("FindFirstByJsonPath",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is found", async () =>
|
TestCase("succeeds when a document is found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -557,9 +565,9 @@ public class PostgresCSharpExtensionTests
|
|||||||
var doc = await conn.FindFirstByJsonPath<JsonDocument>(PostgresDb.TableName, "$.Id ? (@ == \"nope\")");
|
var doc = await conn.FindFirstByJsonPath<JsonDocument>(PostgresDb.TableName, "$.Id ? (@ == \"nope\")");
|
||||||
Expect.isNull(doc, "There should not have been a document returned");
|
Expect.isNull(doc, "There should not have been a document returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("UpdateById", new[]
|
TestList("UpdateById",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is updated", async () =>
|
TestCase("succeeds when a document is updated", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -588,9 +596,9 @@ public class PostgresCSharpExtensionTests
|
|||||||
await conn.UpdateById(PostgresDb.TableName, "test",
|
await conn.UpdateById(PostgresDb.TableName, "test",
|
||||||
new JsonDocument { Id = "x", Sub = new() { Foo = "blue", Bar = "red" } });
|
new JsonDocument { Id = "x", Sub = new() { Foo = "blue", Bar = "red" } });
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("UpdateByFunc", new[]
|
TestList("UpdateByFunc",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is updated", async () =>
|
TestCase("succeeds when a document is updated", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -617,9 +625,9 @@ public class PostgresCSharpExtensionTests
|
|||||||
await conn.UpdateByFunc(PostgresDb.TableName, doc => doc.Id,
|
await conn.UpdateByFunc(PostgresDb.TableName, doc => doc.Id,
|
||||||
new JsonDocument { Id = "one", Value = "le un", NumValue = 1 });
|
new JsonDocument { Id = "one", Value = "le un", NumValue = 1 });
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("PatchById", new[]
|
TestList("PatchById",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is updated", async () =>
|
TestCase("succeeds when a document is updated", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -641,9 +649,10 @@ public class PostgresCSharpExtensionTests
|
|||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await conn.PatchById(PostgresDb.TableName, "test", new { Foo = "green" });
|
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 () =>
|
TestCase("succeeds when a document is updated", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -664,9 +673,10 @@ public class PostgresCSharpExtensionTests
|
|||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await conn.PatchByField(PostgresDb.TableName, Field.EQ("Value", "burgundy"), new { Foo = "green" });
|
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 () =>
|
TestCase("succeeds when a document is updated", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -687,9 +697,9 @@ public class PostgresCSharpExtensionTests
|
|||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await conn.PatchByContains(PostgresDb.TableName, new { Value = "burgundy" }, new { Foo = "green" });
|
await conn.PatchByContains(PostgresDb.TableName, new { Value = "burgundy" }, new { Foo = "green" });
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("PatchByJsonPath", new[]
|
TestList("PatchByJsonPath",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is updated", async () =>
|
TestCase("succeeds when a document is updated", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -710,9 +720,9 @@ public class PostgresCSharpExtensionTests
|
|||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await conn.PatchByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ < 0)", new { Foo = "green" });
|
await conn.PatchByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ < 0)", new { Foo = "green" });
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("RemoveFieldsById", new[]
|
TestList("RemoveFieldsById",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when multiple fields are removed", async () =>
|
TestCase("succeeds when multiple fields are removed", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -754,9 +764,10 @@ public class PostgresCSharpExtensionTests
|
|||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await conn.RemoveFieldsById(PostgresDb.TableName, "two", new[] { "Value" });
|
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 () =>
|
TestCase("succeeds when multiple fields are removed", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -800,9 +811,10 @@ public class PostgresCSharpExtensionTests
|
|||||||
await conn.RemoveFieldsByField(PostgresDb.TableName, Field.NE("Abracadabra", "apple"),
|
await conn.RemoveFieldsByField(PostgresDb.TableName, Field.NE("Abracadabra", "apple"),
|
||||||
new[] { "Value" });
|
new[] { "Value" });
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("RemoveFieldsByContains", new[]
|
#pragma warning restore CS0618
|
||||||
{
|
TestList("RemoveFieldsByContains",
|
||||||
|
[
|
||||||
TestCase("succeeds when multiple fields are removed", async () =>
|
TestCase("succeeds when multiple fields are removed", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -846,9 +858,9 @@ public class PostgresCSharpExtensionTests
|
|||||||
await conn.RemoveFieldsByContains(PostgresDb.TableName, new { Abracadabra = "apple" },
|
await conn.RemoveFieldsByContains(PostgresDb.TableName, new { Abracadabra = "apple" },
|
||||||
new[] { "Value" });
|
new[] { "Value" });
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("RemoveFieldsByJsonPath", new[]
|
TestList("RemoveFieldsByJsonPath",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when multiple fields are removed", async () =>
|
TestCase("succeeds when multiple fields are removed", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -892,9 +904,9 @@ public class PostgresCSharpExtensionTests
|
|||||||
await conn.RemoveFieldsByJsonPath(PostgresDb.TableName, "$.Abracadabra ? (@ == \"apple\")",
|
await conn.RemoveFieldsByJsonPath(PostgresDb.TableName, "$.Abracadabra ? (@ == \"apple\")",
|
||||||
new[] { "Value" });
|
new[] { "Value" });
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("DeleteById", new[]
|
TestList("DeleteById",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is deleted", async () =>
|
TestCase("succeeds when a document is deleted", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -915,9 +927,10 @@ public class PostgresCSharpExtensionTests
|
|||||||
var remaining = await conn.CountAll(PostgresDb.TableName);
|
var remaining = await conn.CountAll(PostgresDb.TableName);
|
||||||
Expect.equal(remaining, 5, "There should have been 5 documents remaining");
|
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 () =>
|
TestCase("succeeds when documents are deleted", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -938,9 +951,10 @@ public class PostgresCSharpExtensionTests
|
|||||||
var remaining = await conn.CountAll(PostgresDb.TableName);
|
var remaining = await conn.CountAll(PostgresDb.TableName);
|
||||||
Expect.equal(remaining, 5, "There should have been 5 documents remaining");
|
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 () =>
|
TestCase("succeeds when documents are deleted", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -961,9 +975,9 @@ public class PostgresCSharpExtensionTests
|
|||||||
var remaining = await conn.CountAll(PostgresDb.TableName);
|
var remaining = await conn.CountAll(PostgresDb.TableName);
|
||||||
Expect.equal(remaining, 5, "There should have been 5 documents remaining");
|
Expect.equal(remaining, 5, "There should have been 5 documents remaining");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("DeleteByJsonPath", new[]
|
TestList("DeleteByJsonPath",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when documents are deleted", async () =>
|
TestCase("succeeds when documents are deleted", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -984,6 +998,6 @@ public class PostgresCSharpExtensionTests
|
|||||||
var remaining = await conn.CountAll(PostgresDb.TableName);
|
var remaining = await conn.CountAll(PostgresDb.TableName);
|
||||||
Expect.equal(remaining, 5, "There should have been 5 documents remaining");
|
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;
|
||||||
using Npgsql.FSharp;
|
using Npgsql.FSharp;
|
||||||
using ThrowawayDb.Postgres;
|
using ThrowawayDb.Postgres;
|
||||||
@@ -131,7 +132,7 @@ public static class PostgresDb
|
|||||||
var sqlProps = Sql.connect(database.ConnectionString);
|
var sqlProps = Sql.connect(database.ConnectionString);
|
||||||
|
|
||||||
Sql.executeNonQuery(Sql.query(Postgres.Query.Definition.EnsureTable(TableName), sqlProps));
|
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));
|
Postgres.Configuration.UseDataSource(MkDataSource(database.ConnectionString));
|
||||||
|
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ public static class SqliteCSharpExtensionTests
|
|||||||
/// Integration tests for the SQLite extension methods
|
/// Integration tests for the SQLite extension methods
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Tests]
|
[Tests]
|
||||||
public static readonly Test Integration = TestList("Sqlite.C#.Extensions", new[]
|
public static readonly Test Integration = TestList("Sqlite.C#.Extensions",
|
||||||
{
|
[
|
||||||
TestList("CustomSingle", new[]
|
TestList("CustomSingle",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a row is found", async () =>
|
TestCase("succeeds when a row is found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -43,9 +43,9 @@ public static class SqliteCSharpExtensionTests
|
|||||||
new[] { Parameters.Id("eighty") }, Results.FromData<JsonDocument>);
|
new[] { Parameters.Id("eighty") }, Results.FromData<JsonDocument>);
|
||||||
Expect.isNull(doc, "There should not have been a document returned");
|
Expect.isNull(doc, "There should not have been a document returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("CustomList", new[]
|
TestList("CustomList",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when data is found", async () =>
|
TestCase("succeeds when data is found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -67,9 +67,9 @@ public static class SqliteCSharpExtensionTests
|
|||||||
new[] { new SqliteParameter("@value", 100) }, Results.FromData<JsonDocument>);
|
new[] { new SqliteParameter("@value", 100) }, Results.FromData<JsonDocument>);
|
||||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("CustomNonQuery", new[]
|
TestList("CustomNonQuery",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when operating on data", async () =>
|
TestCase("succeeds when operating on data", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -93,7 +93,7 @@ public static class SqliteCSharpExtensionTests
|
|||||||
var remaining = await conn.CountAll(SqliteDb.TableName);
|
var remaining = await conn.CountAll(SqliteDb.TableName);
|
||||||
Expect.equal(remaining, 5L, "There should be 5 documents remaining in the table");
|
Expect.equal(remaining, 5L, "There should be 5 documents remaining in the table");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestCase("CustomScalar succeeds", async () =>
|
TestCase("CustomScalar succeeds", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -140,8 +140,8 @@ public static class SqliteCSharpExtensionTests
|
|||||||
exists = await indexExists();
|
exists = await indexExists();
|
||||||
Expect.isTrue(exists, "The index should now exist");
|
Expect.isTrue(exists, "The index should now exist");
|
||||||
}),
|
}),
|
||||||
TestList("Insert", new[]
|
TestList("Insert",
|
||||||
{
|
[
|
||||||
TestCase("succeeds", async () =>
|
TestCase("succeeds", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -168,9 +168,9 @@ public static class SqliteCSharpExtensionTests
|
|||||||
// This is what is supposed to happen
|
// This is what is supposed to happen
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("Save", new[]
|
TestList("Save",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is inserted", async () =>
|
TestCase("succeeds when a document is inserted", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
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.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");
|
Expect.isNull(after.Sub, "There should not have been a sub-document in the updated document");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestCase("CountAll succeeds", async () =>
|
TestCase("CountAll succeeds", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -213,6 +213,7 @@ public static class SqliteCSharpExtensionTests
|
|||||||
var theCount = await conn.CountAll(SqliteDb.TableName);
|
var theCount = await conn.CountAll(SqliteDb.TableName);
|
||||||
Expect.equal(theCount, 5L, "There should have been 5 matching documents");
|
Expect.equal(theCount, 5L, "There should have been 5 matching documents");
|
||||||
}),
|
}),
|
||||||
|
#pragma warning disable CS0618
|
||||||
TestCase("CountByField succeeds", async () =>
|
TestCase("CountByField succeeds", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
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"));
|
var theCount = await conn.CountByField(SqliteDb.TableName, Field.EQ("Value", "purple"));
|
||||||
Expect.equal(theCount, 2L, "There should have been 2 matching documents");
|
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 () =>
|
TestCase("succeeds when a document exists", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -242,9 +244,10 @@ public static class SqliteCSharpExtensionTests
|
|||||||
var exists = await conn.ExistsById(SqliteDb.TableName, "seven");
|
var exists = await conn.ExistsById(SqliteDb.TableName, "seven");
|
||||||
Expect.isFalse(exists, "There should not have been an existing document");
|
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 () =>
|
TestCase("succeeds when documents exist", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
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"));
|
var exists = await conn.ExistsByField(SqliteDb.TableName, Field.EQ("Nothing", "none"));
|
||||||
Expect.isFalse(exists, "There should not have been any existing documents");
|
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 () =>
|
TestCase("succeeds when there is data", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -285,9 +289,9 @@ public static class SqliteCSharpExtensionTests
|
|||||||
var results = await conn.FindAll<JsonDocument>(SqliteDb.TableName);
|
var results = await conn.FindAll<JsonDocument>(SqliteDb.TableName);
|
||||||
Expect.isEmpty(results, "There should have been no documents returned");
|
Expect.isEmpty(results, "There should have been no documents returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("FindById", new[]
|
TestList("FindById",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is found", async () =>
|
TestCase("succeeds when a document is found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
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");
|
var doc = await conn.FindById<string, JsonDocument>(SqliteDb.TableName, "eighty-seven");
|
||||||
Expect.isNull(doc, "There should not have been a document returned");
|
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 () =>
|
TestCase("succeeds when documents are found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
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"));
|
var docs = await conn.FindByField<JsonDocument>(SqliteDb.TableName, Field.EQ("Value", "mauve"));
|
||||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("FindFirstByField", new[]
|
TestList("FindFirstByField",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is found", async () =>
|
TestCase("succeeds when a document is found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
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"));
|
var doc = await conn.FindFirstByField<JsonDocument>(SqliteDb.TableName, Field.EQ("Value", "absent"));
|
||||||
Expect.isNull(doc, "There should not have been a document returned");
|
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 () =>
|
TestCase("succeeds when a document is updated", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -389,9 +395,9 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await conn.UpdateById(SqliteDb.TableName, "test",
|
await conn.UpdateById(SqliteDb.TableName, "test",
|
||||||
new JsonDocument { Id = "x", Sub = new() { Foo = "blue", Bar = "red" } });
|
new JsonDocument { Id = "x", Sub = new() { Foo = "blue", Bar = "red" } });
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("UpdateByFunc", new[]
|
TestList("UpdateByFunc",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is updated", async () =>
|
TestCase("succeeds when a document is updated", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -418,9 +424,9 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await conn.UpdateByFunc(SqliteDb.TableName, doc => doc.Id,
|
await conn.UpdateByFunc(SqliteDb.TableName, doc => doc.Id,
|
||||||
new JsonDocument { Id = "one", Value = "le un", NumValue = 1 });
|
new JsonDocument { Id = "one", Value = "le un", NumValue = 1 });
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("PatchById", new[]
|
TestList("PatchById",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is updated", async () =>
|
TestCase("succeeds when a document is updated", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -443,9 +449,10 @@ public static class SqliteCSharpExtensionTests
|
|||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await conn.PatchById(SqliteDb.TableName, "test", new { Foo = "green" });
|
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 () =>
|
TestCase("succeeds when a document is updated", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -466,9 +473,10 @@ public static class SqliteCSharpExtensionTests
|
|||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await conn.PatchByField(SqliteDb.TableName, Field.EQ("Value", "burgundy"), new { Foo = "green" });
|
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 () =>
|
TestCase("succeeds when fields are removed", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -498,9 +506,10 @@ public static class SqliteCSharpExtensionTests
|
|||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await conn.RemoveFieldsById(SqliteDb.TableName, "two", new[] { "Value" });
|
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 () =>
|
TestCase("succeeds when a field is removed", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -529,9 +538,10 @@ public static class SqliteCSharpExtensionTests
|
|||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await conn.RemoveFieldsByField(SqliteDb.TableName, Field.NE("Abracadabra", "apple"), new[] { "Value" });
|
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 () =>
|
TestCase("succeeds when a document is deleted", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -552,9 +562,10 @@ public static class SqliteCSharpExtensionTests
|
|||||||
var remaining = await conn.CountAll(SqliteDb.TableName);
|
var remaining = await conn.CountAll(SqliteDb.TableName);
|
||||||
Expect.equal(remaining, 5L, "There should have been 5 documents remaining");
|
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 () =>
|
TestCase("succeeds when documents are deleted", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -575,7 +586,8 @@ public static class SqliteCSharpExtensionTests
|
|||||||
var remaining = await conn.CountAll(SqliteDb.TableName);
|
var remaining = await conn.CountAll(SqliteDb.TableName);
|
||||||
Expect.equal(remaining, 5L, "There should have been 5 documents remaining");
|
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:"))
|
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 Expecto;
|
||||||
using Microsoft.Data.Sqlite;
|
using Microsoft.Data.Sqlite;
|
||||||
using Microsoft.FSharp.Core;
|
using Microsoft.FSharp.Core;
|
||||||
@@ -17,49 +16,86 @@ public static class SqliteCSharpTests
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unit tests for the SQLite library
|
/// Unit tests for the SQLite library
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly Test Unit = TestList("Unit", new[]
|
private static readonly Test Unit = TestList("Unit",
|
||||||
{
|
[
|
||||||
TestList("Query", new[]
|
TestList("Query",
|
||||||
{
|
[
|
||||||
|
TestList("WhereByFields",
|
||||||
|
[
|
||||||
|
TestCase("succeeds for a single field when a logical operator is passed", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(
|
||||||
|
Sqlite.Query.WhereByFields(FieldMatch.Any,
|
||||||
|
[Field.GT("theField", 0).WithParameterName("@test")]),
|
||||||
|
"data->>'theField' > @test", "WHERE clause not correct");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for a single field when an existence operator is passed", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(Sqlite.Query.WhereByFields(FieldMatch.Any, [Field.NEX("thatField")]),
|
||||||
|
"data->>'thatField' IS NULL", "WHERE clause not correct");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for a single field when a between operator is passed", () =>
|
||||||
|
{
|
||||||
|
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", () =>
|
TestCase("Definition.EnsureTable succeeds", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(Sqlite.Query.Definition.EnsureTable("tbl"),
|
Expect.equal(Sqlite.Query.Definition.EnsureTable("tbl"),
|
||||||
"CREATE TABLE IF NOT EXISTS tbl (data TEXT NOT NULL)", "CREATE TABLE statement not correct");
|
"CREATE TABLE IF NOT EXISTS tbl (data TEXT NOT NULL)", "CREATE TABLE statement 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("Parameters", new[]
|
TestList("Parameters",
|
||||||
{
|
[
|
||||||
TestCase("Id succeeds", () =>
|
TestCase("Id succeeds", () =>
|
||||||
{
|
{
|
||||||
var theParam = Parameters.Id(7);
|
var theParam = Parameters.Id(7);
|
||||||
@@ -72,10 +108,10 @@ public static class SqliteCSharpTests
|
|||||||
Expect.equal(theParam.ParameterName, "@test", "The parameter name is incorrect");
|
Expect.equal(theParam.ParameterName, "@test", "The parameter name is incorrect");
|
||||||
Expect.equal(theParam.Value, "{\"Nice\":\"job\"}", "The parameter value is incorrect");
|
Expect.equal(theParam.Value, "{\"Nice\":\"job\"}", "The parameter value is incorrect");
|
||||||
}),
|
}),
|
||||||
|
#pragma warning disable CS0618
|
||||||
TestCase("AddField succeeds when adding a parameter", () =>
|
TestCase("AddField succeeds when adding a parameter", () =>
|
||||||
{
|
{
|
||||||
var paramList = Parameters.AddField("@field", Field.EQ("it", 99), Enumerable.Empty<SqliteParameter>())
|
var paramList = Parameters.AddField("@field", Field.EQ("it", 99), []).ToList();
|
||||||
.ToList();
|
|
||||||
Expect.hasLength(paramList, 1, "There should have been a parameter added");
|
Expect.hasLength(paramList, 1, "There should have been a parameter added");
|
||||||
var theParam = paramList[0];
|
var theParam = paramList[0];
|
||||||
Expect.equal(theParam.ParameterName, "@field", "The parameter name is incorrect");
|
Expect.equal(theParam.ParameterName, "@field", "The parameter name is incorrect");
|
||||||
@@ -83,25 +119,26 @@ public static class SqliteCSharpTests
|
|||||||
}),
|
}),
|
||||||
TestCase("AddField succeeds when not adding a parameter", () =>
|
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");
|
Expect.isEmpty(paramSeq, "There should not have been any parameters added");
|
||||||
}),
|
}),
|
||||||
|
#pragma warning restore CS0618
|
||||||
TestCase("None succeeds", () =>
|
TestCase("None succeeds", () =>
|
||||||
{
|
{
|
||||||
Expect.isEmpty(Parameters.None, "The parameter list should have been empty");
|
Expect.isEmpty(Parameters.None, "The parameter list should have been empty");
|
||||||
})
|
})
|
||||||
})
|
])
|
||||||
// Results are exhaustively executed in the context of other tests
|
// 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 = "one", Value = "FIRST!", NumValue = 0 },
|
||||||
new() { Id = "two", Value = "another", NumValue = 10, Sub = new() { Foo = "green", Bar = "blue" } },
|
new() { Id = "two", Value = "another", NumValue = 10, Sub = new() { Foo = "green", Bar = "blue" } },
|
||||||
new() { Id = "three", Value = "", NumValue = 4 },
|
new() { Id = "three", Value = "", NumValue = 4 },
|
||||||
new() { Id = "four", Value = "purple", NumValue = 17, Sub = new() { Foo = "green", Bar = "red" } },
|
new() { Id = "four", Value = "purple", NumValue = 17, Sub = new() { Foo = "green", Bar = "red" } },
|
||||||
new() { Id = "five", Value = "purple", NumValue = 18 }
|
new() { Id = "five", Value = "purple", NumValue = 18 }
|
||||||
};
|
];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add the test documents to the database
|
/// Add the test documents to the database
|
||||||
@@ -111,8 +148,8 @@ public static class SqliteCSharpTests
|
|||||||
foreach (var doc in TestDocuments) await Document.Insert(SqliteDb.TableName, doc);
|
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", () =>
|
TestCase("Configuration.UseConnectionString succeeds", () =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -126,10 +163,10 @@ public static class SqliteCSharpTests
|
|||||||
Sqlite.Configuration.UseConnectionString("Data Source=:memory:");
|
Sqlite.Configuration.UseConnectionString("Data Source=:memory:");
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
TestList("Custom", new[]
|
TestList("Custom",
|
||||||
{
|
[
|
||||||
TestList("Single", new[]
|
TestList("Single",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a row is found", async () =>
|
TestCase("succeeds when a row is found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -149,9 +186,9 @@ public static class SqliteCSharpTests
|
|||||||
new[] { Parameters.Id("eighty") }, Results.FromData<JsonDocument>);
|
new[] { Parameters.Id("eighty") }, Results.FromData<JsonDocument>);
|
||||||
Expect.isNull(doc, "There should not have been a document returned");
|
Expect.isNull(doc, "There should not have been a document returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("List", new[]
|
TestList("List",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when data is found", async () =>
|
TestCase("succeeds when data is found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -171,9 +208,9 @@ public static class SqliteCSharpTests
|
|||||||
new[] { new SqliteParameter("@value", 100) }, Results.FromData<JsonDocument>);
|
new[] { new SqliteParameter("@value", 100) }, Results.FromData<JsonDocument>);
|
||||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("NonQuery", new[]
|
TestList("NonQuery",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when operating on data", async () =>
|
TestCase("succeeds when operating on data", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -195,7 +232,7 @@ public static class SqliteCSharpTests
|
|||||||
var remaining = await Count.All(SqliteDb.TableName);
|
var remaining = await Count.All(SqliteDb.TableName);
|
||||||
Expect.equal(remaining, 5L, "There should be 5 documents remaining in the table");
|
Expect.equal(remaining, 5L, "There should be 5 documents remaining in the table");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestCase("Scalar succeeds", async () =>
|
TestCase("Scalar succeeds", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -203,9 +240,9 @@ public static class SqliteCSharpTests
|
|||||||
var nbr = await Custom.Scalar("SELECT 5 AS test_value", Parameters.None, rdr => rdr.GetInt32(0));
|
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");
|
Expect.equal(nbr, 5, "The query should have returned the number 5");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("Definition", new[]
|
TestList("Definition",
|
||||||
{
|
[
|
||||||
TestCase("EnsureTable succeeds", async () =>
|
TestCase("EnsureTable succeeds", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -233,21 +270,23 @@ public static class SqliteCSharpTests
|
|||||||
TestCase("EnsureFieldIndex succeeds", async () =>
|
TestCase("EnsureFieldIndex succeeds", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
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");
|
Expect.isFalse(exists, "The index should not exist already");
|
||||||
|
|
||||||
await Definition.EnsureTable("ensured");
|
await Definition.EnsureTable("ensured");
|
||||||
await Definition.EnsureFieldIndex("ensured", "test", new[] { "Id", "Category" });
|
await Definition.EnsureFieldIndex("ensured", "test", new[] { "Id", "Category" });
|
||||||
exists = await indexExists();
|
exists = await IndexExists();
|
||||||
Expect.isTrue(exists, "The index should now exist");
|
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 () =>
|
TestCase("succeeds", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -272,9 +311,9 @@ public static class SqliteCSharpTests
|
|||||||
// This is what is supposed to happen
|
// This is what is supposed to happen
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("Document.Save", new[]
|
TestList("Document.Save",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is inserted", async () =>
|
TestCase("succeeds when a document is inserted", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -305,9 +344,9 @@ public static class SqliteCSharpTests
|
|||||||
Expect.equal(after!.Id, "test", "The updated document is not correct");
|
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");
|
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 () =>
|
TestCase("All succeeds", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -316,19 +355,29 @@ public static class SqliteCSharpTests
|
|||||||
var theCount = await Count.All(SqliteDb.TableName);
|
var theCount = await Count.All(SqliteDb.TableName);
|
||||||
Expect.equal(theCount, 5L, "There should have been 5 matching documents");
|
Expect.equal(theCount, 5L, "There should have been 5 matching documents");
|
||||||
}),
|
}),
|
||||||
TestCase("ByField succeeds", async () =>
|
#pragma warning disable CS0618
|
||||||
|
TestCase("ByField succeeds for numeric range", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var theCount = await Count.ByField(SqliteDb.TableName, Field.EQ("Value", "purple"));
|
var theCount = await Count.ByField(SqliteDb.TableName, Field.BT("NumValue", 10, 20));
|
||||||
Expect.equal(theCount, 2L, "There should have been 2 matching documents");
|
Expect.equal(theCount, 3L, "There should have been 3 matching documents");
|
||||||
})
|
}),
|
||||||
}),
|
TestCase("ByField succeeds for non-numeric range", async () =>
|
||||||
TestList("Exists", new[]
|
|
||||||
{
|
|
||||||
TestList("ById", new[]
|
|
||||||
{
|
{
|
||||||
|
await using var db = await SqliteDb.BuildDb();
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var theCount = await Count.ByField(SqliteDb.TableName, Field.BT("Value", "aardvark", "apple"));
|
||||||
|
Expect.equal(theCount, 1L, "There should have been 1 matching document");
|
||||||
|
})
|
||||||
|
#pragma warning restore CS0618
|
||||||
|
]),
|
||||||
|
TestList("Exists",
|
||||||
|
[
|
||||||
|
TestList("ById",
|
||||||
|
[
|
||||||
TestCase("succeeds when a document exists", async () =>
|
TestCase("succeeds when a document exists", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -345,9 +394,10 @@ public static class SqliteCSharpTests
|
|||||||
var exists = await Exists.ById(SqliteDb.TableName, "seven");
|
var exists = await Exists.ById(SqliteDb.TableName, "seven");
|
||||||
Expect.isFalse(exists, "There should not have been an existing document");
|
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 () =>
|
TestCase("succeeds when documents exist", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -364,12 +414,13 @@ public static class SqliteCSharpTests
|
|||||||
var exists = await Exists.ByField(SqliteDb.TableName, Field.EQ("Nothing", "none"));
|
var exists = await Exists.ByField(SqliteDb.TableName, Field.EQ("Nothing", "none"));
|
||||||
Expect.isFalse(exists, "There should not have been any existing documents");
|
Expect.isFalse(exists, "There should not have been any existing documents");
|
||||||
})
|
})
|
||||||
})
|
])
|
||||||
}),
|
#pragma warning restore CS0618
|
||||||
TestList("Find", new[]
|
]),
|
||||||
{
|
TestList("Find",
|
||||||
TestList("All", new[]
|
[
|
||||||
{
|
TestList("All",
|
||||||
|
[
|
||||||
TestCase("succeeds when there is data", async () =>
|
TestCase("succeeds when there is data", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -387,9 +438,9 @@ public static class SqliteCSharpTests
|
|||||||
var results = await Find.All<SubDocument>(SqliteDb.TableName);
|
var results = await Find.All<SubDocument>(SqliteDb.TableName);
|
||||||
Expect.isEmpty(results, "There should have been no documents returned");
|
Expect.isEmpty(results, "There should have been no documents returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("ById", new[]
|
TestList("ById",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is found", async () =>
|
TestCase("succeeds when a document is found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -407,9 +458,10 @@ public static class SqliteCSharpTests
|
|||||||
var doc = await Find.ById<string, JsonDocument>(SqliteDb.TableName, "twenty two");
|
var doc = await Find.ById<string, JsonDocument>(SqliteDb.TableName, "twenty two");
|
||||||
Expect.isNull(doc, "There should not have been a document returned");
|
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 () =>
|
TestCase("succeeds when documents are found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -426,9 +478,9 @@ public static class SqliteCSharpTests
|
|||||||
var docs = await Find.ByField<JsonDocument>(SqliteDb.TableName, Field.EQ("Value", "mauve"));
|
var docs = await Find.ByField<JsonDocument>(SqliteDb.TableName, Field.EQ("Value", "mauve"));
|
||||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("FirstByField", new[]
|
TestList("FirstByField",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is found", async () =>
|
TestCase("succeeds when a document is found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -455,12 +507,13 @@ public static class SqliteCSharpTests
|
|||||||
var doc = await Find.FirstByField<JsonDocument>(SqliteDb.TableName, Field.EQ("Value", "absent"));
|
var doc = await Find.FirstByField<JsonDocument>(SqliteDb.TableName, Field.EQ("Value", "absent"));
|
||||||
Expect.isNull(doc, "There should not have been a document returned");
|
Expect.isNull(doc, "There should not have been a document returned");
|
||||||
})
|
})
|
||||||
})
|
])
|
||||||
}),
|
#pragma warning restore CS0618
|
||||||
TestList("Update", new[]
|
]),
|
||||||
{
|
TestList("Update",
|
||||||
TestList("ById", new[]
|
[
|
||||||
{
|
TestList("ById",
|
||||||
|
[
|
||||||
TestCase("succeeds when a document is updated", async () =>
|
TestCase("succeeds when a document is updated", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -486,9 +539,9 @@ public static class SqliteCSharpTests
|
|||||||
await Update.ById(SqliteDb.TableName, "test",
|
await Update.ById(SqliteDb.TableName, "test",
|
||||||
new JsonDocument { Id = "x", Sub = new() { Foo = "blue", Bar = "red" } });
|
new JsonDocument { Id = "x", Sub = new() { Foo = "blue", Bar = "red" } });
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("ByFunc", new[]
|
TestList("ByFunc",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is updated", async () =>
|
TestCase("succeeds when a document is updated", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -514,12 +567,12 @@ public static class SqliteCSharpTests
|
|||||||
await Update.ByFunc(SqliteDb.TableName, doc => doc.Id,
|
await Update.ByFunc(SqliteDb.TableName, doc => doc.Id,
|
||||||
new JsonDocument { Id = "one", Value = "le un", NumValue = 1 });
|
new JsonDocument { Id = "one", Value = "le un", NumValue = 1 });
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
}),
|
]),
|
||||||
TestList("Patch", new[]
|
TestList("Patch",
|
||||||
{
|
[
|
||||||
TestList("ById", new[]
|
TestList("ById",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is updated", async () =>
|
TestCase("succeeds when a document is updated", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -541,9 +594,10 @@ public static class SqliteCSharpTests
|
|||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await Patch.ById(SqliteDb.TableName, "test", new { Foo = "green" });
|
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 () =>
|
TestCase("succeeds when a document is updated", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -563,12 +617,13 @@ public static class SqliteCSharpTests
|
|||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await Patch.ByField(SqliteDb.TableName, Field.EQ("Value", "burgundy"), new { Foo = "green" });
|
await Patch.ByField(SqliteDb.TableName, Field.EQ("Value", "burgundy"), new { Foo = "green" });
|
||||||
})
|
})
|
||||||
})
|
])
|
||||||
}),
|
#pragma warning restore CS0618
|
||||||
TestList("RemoveFields", new[]
|
]),
|
||||||
{
|
TestList("RemoveFields",
|
||||||
TestList("ById", new[]
|
[
|
||||||
{
|
TestList("ById",
|
||||||
|
[
|
||||||
TestCase("succeeds when fields are removed", async () =>
|
TestCase("succeeds when fields are removed", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -595,9 +650,10 @@ public static class SqliteCSharpTests
|
|||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await RemoveFields.ById(SqliteDb.TableName, "two", new[] { "Value" });
|
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 () =>
|
TestCase("succeeds when a field is removed", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -623,12 +679,13 @@ public static class SqliteCSharpTests
|
|||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await RemoveFields.ByField(SqliteDb.TableName, Field.NE("Abracadabra", "apple"), new[] { "Value" });
|
await RemoveFields.ByField(SqliteDb.TableName, Field.NE("Abracadabra", "apple"), new[] { "Value" });
|
||||||
})
|
})
|
||||||
})
|
])
|
||||||
}),
|
#pragma warning restore CS0618
|
||||||
TestList("Delete", new[]
|
]),
|
||||||
{
|
TestList("Delete",
|
||||||
TestList("ById", new[]
|
[
|
||||||
{
|
TestList("ById",
|
||||||
|
[
|
||||||
TestCase("succeeds when a document is deleted", async () =>
|
TestCase("succeeds when a document is deleted", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -647,9 +704,10 @@ public static class SqliteCSharpTests
|
|||||||
var remaining = await Count.All(SqliteDb.TableName);
|
var remaining = await Count.All(SqliteDb.TableName);
|
||||||
Expect.equal(remaining, 5L, "There should have been 5 documents remaining");
|
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 () =>
|
TestCase("succeeds when documents are deleted", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -668,14 +726,15 @@ public static class SqliteCSharpTests
|
|||||||
var remaining = await Count.All(SqliteDb.TableName);
|
var remaining = await Count.All(SqliteDb.TableName);
|
||||||
Expect.equal(remaining, 5L, "There should have been 5 documents remaining");
|
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:"))
|
TestCase("Clean up database", () => Sqlite.Configuration.UseConnectionString("data source=:memory:"))
|
||||||
});
|
]);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All tests for SQLite C# functions and methods
|
/// All tests for SQLite C# functions and methods
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Tests]
|
[Tests]
|
||||||
public static readonly Test All = TestList("Sqlite.C#", new[] { Unit, TestSequenced(Integration) });
|
public static readonly Test All = TestList("Sqlite.C#", [Unit, TestSequenced(Integration)]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Expecto" Version="10.1.0" />
|
<PackageReference Include="Expecto" Version="10.2.1" />
|
||||||
|
<PackageReference Update="FSharp.Core" Version="8.0.300" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ let all =
|
|||||||
test "NE succeeds" {
|
test "NE succeeds" {
|
||||||
Expect.equal (string NE) "<>" "The not equal to operator was not correct"
|
Expect.equal (string NE) "<>" "The not equal to operator was not correct"
|
||||||
}
|
}
|
||||||
|
test "BT succeeds" {
|
||||||
|
Expect.equal (string BT) "BETWEEN" """The "between" operator was not correct"""
|
||||||
|
}
|
||||||
test "EX succeeds" {
|
test "EX succeeds" {
|
||||||
Expect.equal (string EX) "IS NOT NULL" """The "exists" operator was not correct"""
|
Expect.equal (string EX) "IS NOT NULL" """The "exists" operator was not correct"""
|
||||||
}
|
}
|
||||||
@@ -35,27 +38,149 @@ let all =
|
|||||||
Expect.equal (string NEX) "IS NULL" """The "not exists" operator was not correct"""
|
Expect.equal (string NEX) "IS NULL" """The "not exists" operator was not correct"""
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "Query" [
|
testList "Field" [
|
||||||
test "selectFromTable succeeds" {
|
test "EQ succeeds" {
|
||||||
Expect.equal (Query.selectFromTable tbl) $"SELECT data FROM {tbl}" "SELECT statement not correct"
|
let field = Field.EQ "Test" 14
|
||||||
|
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 "whereById succeeds" {
|
test "GT succeeds" {
|
||||||
Expect.equal (Query.whereById "@id") "data ->> 'Id' = @id" "WHERE clause not correct"
|
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"
|
||||||
}
|
}
|
||||||
testList "whereByField" [
|
test "GE succeeds" {
|
||||||
test "succeeds when a logical operator is passed" {
|
let field = Field.GE "Nice" 88L
|
||||||
Expect.equal
|
Expect.equal field.Name "Nice" "Field name incorrect"
|
||||||
(Query.whereByField (Field.GT "theField" 0) "@test")
|
Expect.equal field.Op GE "Operator incorrect"
|
||||||
"data ->> 'theField' > @test"
|
Expect.equal field.Value 88L "Value incorrect"
|
||||||
"WHERE clause not correct"
|
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 when an existence operator is passed" {
|
test "succeeds for a PostgreSQL single field with a qualifier" {
|
||||||
|
let field = { Field.LT "SomethingElse" 9 with Qualifier = Some "this" }
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.whereByField (Field.NEX "thatField") "")
|
"this.data->>'SomethingElse'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect"
|
||||||
"data ->> 'thatField' IS NULL"
|
}
|
||||||
"WHERE clause not correct"
|
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 "statementWhere succeeds" {
|
||||||
|
Expect.equal (Query.statementWhere "x" "y") "x WHERE y" "Statements not combined correctly"
|
||||||
|
}
|
||||||
testList "Definition" [
|
testList "Definition" [
|
||||||
test "ensureTableFor succeeds" {
|
test "ensureTableFor succeeds" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
@@ -66,25 +191,40 @@ let all =
|
|||||||
testList "ensureKey" [
|
testList "ensureKey" [
|
||||||
test "succeeds when a schema is present" {
|
test "succeeds when a schema is present" {
|
||||||
Expect.equal
|
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 UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data->>'Id'))"
|
||||||
"CREATE INDEX for key statement with schema not constructed correctly"
|
"CREATE INDEX for key statement with schema not constructed correctly"
|
||||||
}
|
}
|
||||||
test "succeeds when a schema is not present" {
|
test "succeeds when a schema is not present" {
|
||||||
Expect.equal
|
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 UNIQUE INDEX IF NOT EXISTS idx_table_key ON table ((data->>'Id'))"
|
||||||
"CREATE INDEX for key statement without schema not constructed correctly"
|
"CREATE INDEX for key statement without schema not constructed correctly"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
test "ensureIndexOn succeeds for multiple fields and directions" {
|
testList "ensureIndexOn" [
|
||||||
Expect.equal
|
test "succeeds for multiple fields and directions" {
|
||||||
(Query.Definition.ensureIndexOn "test.table" "gibberish" [ "taco"; "guac DESC"; "salsa ASC" ])
|
Expect.equal
|
||||||
([ "CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table "
|
(Query.Definition.ensureIndexOn
|
||||||
"((data ->> 'taco'), (data ->> 'guac') DESC, (data ->> 'salsa') ASC)" ]
|
"test.table" "gibberish" [ "taco"; "guac DESC"; "salsa ASC" ] PostgreSQL)
|
||||||
|> String.concat "")
|
([ "CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table "
|
||||||
"CREATE INDEX for multiple field statement incorrect"
|
"((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" {
|
test "insert succeeds" {
|
||||||
Expect.equal (Query.insert tbl) $"INSERT INTO {tbl} VALUES (@data)" "INSERT statement not correct"
|
Expect.equal (Query.insert tbl) $"INSERT INTO {tbl} VALUES (@data)" "INSERT statement not correct"
|
||||||
@@ -92,68 +232,26 @@ let all =
|
|||||||
test "save succeeds" {
|
test "save succeeds" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.save tbl)
|
(Query.save tbl)
|
||||||
$"INSERT INTO {tbl} VALUES (@data) ON CONFLICT ((data ->> 'Id')) DO UPDATE SET data = EXCLUDED.data"
|
$"INSERT INTO {tbl} VALUES (@data) ON CONFLICT ((data->>'Id')) DO UPDATE SET data = EXCLUDED.data"
|
||||||
"INSERT ON CONFLICT UPDATE statement not correct"
|
"INSERT ON CONFLICT UPDATE statement not correct"
|
||||||
}
|
}
|
||||||
test "update succeeds" {
|
test "count succeeds" {
|
||||||
Expect.equal
|
Expect.equal (Query.count tbl) $"SELECT COUNT(*) AS it FROM {tbl}" "Count query not correct"
|
||||||
(Query.update tbl)
|
}
|
||||||
$"UPDATE {tbl} SET data = @data WHERE data ->> 'Id' = @id"
|
test "exists succeeds" {
|
||||||
"UPDATE full statement not correct"
|
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"
|
||||||
}
|
}
|
||||||
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 "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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ open Expecto
|
|||||||
open Npgsql
|
open Npgsql
|
||||||
open Types
|
open Types
|
||||||
|
|
||||||
|
#nowarn "0044"
|
||||||
|
|
||||||
/// Open a connection to the throwaway database
|
/// Open a connection to the throwaway database
|
||||||
let private mkConn (db: ThrowawayPostgresDb) =
|
let private mkConn (db: ThrowawayPostgresDb) =
|
||||||
let conn = new NpgsqlConnection(db.ConnectionString)
|
let conn = new NpgsqlConnection(db.ConnectionString)
|
||||||
|
|||||||
@@ -5,41 +5,212 @@ open BitBadger.Documents
|
|||||||
open BitBadger.Documents.Postgres
|
open BitBadger.Documents.Postgres
|
||||||
open BitBadger.Documents.Tests
|
open BitBadger.Documents.Tests
|
||||||
|
|
||||||
|
#nowarn "0044"
|
||||||
|
|
||||||
/// Tests which do not hit the database
|
/// Tests which do not hit the database
|
||||||
let unitTests =
|
let unitTests =
|
||||||
testList "Unit" [
|
testList "Unit" [
|
||||||
testList "Parameters" [
|
testList "Parameters" [
|
||||||
test "idParam succeeds" {
|
testList "idParam" [
|
||||||
Expect.equal (idParam 88) ("@id", Sql.string "88") "ID parameter not constructed correctly"
|
// 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" {
|
test "jsonParam succeeds" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(jsonParam "@test" {| Something = "good" |})
|
(jsonParam "@test" {| Something = "good" |})
|
||||||
("@test", Sql.jsonb """{"Something":"good"}""")
|
("@test", Sql.jsonb """{"Something":"good"}""")
|
||||||
"JSON parameter not constructed correctly"
|
"JSON parameter not constructed correctly"
|
||||||
}
|
}
|
||||||
testList "addFieldParam" [
|
testList "addFieldParams" [
|
||||||
test "succeeds when a parameter is added" {
|
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"
|
Expect.hasLength paramList 1 "There should have been a parameter added"
|
||||||
let it = paramList[0]
|
let name, value = Seq.head paramList
|
||||||
Expect.equal (fst it) "@field" "Field parameter name not correct"
|
Expect.equal name "@field0" "Field parameter name not correct"
|
||||||
match snd it with
|
Expect.equal value (Sql.string "242") "Parameter value not correct"
|
||||||
| SqlValue.Parameter value ->
|
}
|
||||||
Expect.equal value.ParameterName "@field" "Parameter name not correct"
|
test "succeeds when multiple independent parameters are added" {
|
||||||
Expect.equal value.Value "242" "Parameter value not correct"
|
let paramList = addFieldParams [ Field.EQ "me" "you"; Field.GT "us" "them" ] [ idParam 14 ]
|
||||||
| _ -> Expect.isTrue false "The parameter was not a Parameter type"
|
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" {
|
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"
|
Expect.isEmpty paramList "There should not have been any parameters added"
|
||||||
}
|
}
|
||||||
|
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 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" {
|
test "noParams succeeds" {
|
||||||
Expect.isEmpty noParams "The no-params sequence should be empty"
|
Expect.isEmpty noParams "The no-params sequence should be empty"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "Query" [
|
testList "Query" [
|
||||||
|
testList "whereByFields" [
|
||||||
|
test "succeeds for a single field when a logical operator is passed" {
|
||||||
|
Expect.equal
|
||||||
|
(Query.whereByFields Any [ { Field.GT "theField" "0" with ParameterName = Some "@test" } ])
|
||||||
|
"data->>'theField' > @test"
|
||||||
|
"WHERE clause not correct"
|
||||||
|
}
|
||||||
|
test "succeeds for a single field when an existence operator is passed" {
|
||||||
|
Expect.equal
|
||||||
|
(Query.whereByFields Any [ Field.NEX "thatField" ])
|
||||||
|
"data->>'thatField' IS NULL"
|
||||||
|
"WHERE clause not correct"
|
||||||
|
}
|
||||||
|
test "succeeds for a single field when a between operator is passed with numeric values" {
|
||||||
|
Expect.equal
|
||||||
|
(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 for a single field when a between operator is passed with non-numeric values" {
|
||||||
|
Expect.equal
|
||||||
|
(Query.whereByFields Any [ { Field.BT "field0" "a" "b" with ParameterName = Some "@alpha" } ])
|
||||||
|
"data->>'field0' BETWEEN @alphamin AND @alphamax"
|
||||||
|
"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" [
|
testList "Definition" [
|
||||||
test "ensureTable succeeds" {
|
test "ensureTable succeeds" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
@@ -67,112 +238,34 @@ let unitTests =
|
|||||||
test "whereJsonPathMatches succeeds" {
|
test "whereJsonPathMatches succeeds" {
|
||||||
Expect.equal (Query.whereJsonPathMatches "@path") "data @? @path::jsonpath" "WHERE clause not correct"
|
Expect.equal (Query.whereJsonPathMatches "@path") "data @? @path::jsonpath" "WHERE clause not correct"
|
||||||
}
|
}
|
||||||
testList "Count" [
|
test "patch succeeds" {
|
||||||
test "byContains succeeds" {
|
Expect.equal
|
||||||
Expect.equal
|
(Query.patch PostgresDb.TableName)
|
||||||
(Query.Count.byContains PostgresDb.TableName)
|
$"UPDATE {PostgresDb.TableName} SET data = data || @data"
|
||||||
$"SELECT COUNT(*) AS it FROM {PostgresDb.TableName} WHERE data @> @criteria"
|
"Patch query not correct"
|
||||||
"JSON containment count query not correct"
|
}
|
||||||
}
|
test "removeFields succeeds" {
|
||||||
test "byJsonPath succeeds" {
|
Expect.equal
|
||||||
Expect.equal
|
(Query.removeFields PostgresDb.TableName)
|
||||||
(Query.Count.byJsonPath PostgresDb.TableName)
|
$"UPDATE {PostgresDb.TableName} SET data = data - @name"
|
||||||
$"SELECT COUNT(*) AS it FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath"
|
"Field removal query not correct"
|
||||||
"JSON Path match count query not correct"
|
}
|
||||||
}
|
test "byId succeeds" {
|
||||||
]
|
Expect.equal (Query.byId "test" "14") "test WHERE data->>'Id' = @id" "By-ID query not correct"
|
||||||
testList "Exists" [
|
}
|
||||||
test "byContains succeeds" {
|
test "byFields succeeds" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.Exists.byContains PostgresDb.TableName)
|
(Query.byFields "unit" Any [ Field.GT "That" 14 ])
|
||||||
$"SELECT EXISTS (SELECT 1 FROM {PostgresDb.TableName} WHERE data @> @criteria) AS it"
|
"unit WHERE (data->>'That')::numeric > @field0"
|
||||||
"JSON containment exists query not correct"
|
"By-Field query not correct"
|
||||||
}
|
}
|
||||||
test "byJsonPath succeeds" {
|
test "byContains succeeds" {
|
||||||
Expect.equal
|
Expect.equal (Query.byContains "exam") "exam WHERE data @> @criteria" "By-Contains query not correct"
|
||||||
(Query.Exists.byJsonPath PostgresDb.TableName)
|
}
|
||||||
$"SELECT EXISTS (SELECT 1 FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath) AS it"
|
test "byPathMach succeeds" {
|
||||||
"JSON Path match existence query not correct"
|
Expect.equal
|
||||||
}
|
(Query.byPathMatch "verify") "verify WHERE data @? @path::jsonpath" "By-JSON Path query not correct"
|
||||||
]
|
}
|
||||||
testList "Find" [
|
|
||||||
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 "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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -387,13 +480,39 @@ let integrationTests =
|
|||||||
let! theCount = Count.all PostgresDb.TableName
|
let! theCount = Count.all PostgresDb.TableName
|
||||||
Expect.equal theCount 5 "There should have been 5 matching documents"
|
Expect.equal theCount 5 "There should have been 5 matching documents"
|
||||||
}
|
}
|
||||||
testTask "byField succeeds" {
|
testList "byFields" [
|
||||||
use db = PostgresDb.BuildDb()
|
testTask "succeeds when items are found" {
|
||||||
do! loadDocs ()
|
use db = PostgresDb.BuildDb()
|
||||||
|
do! loadDocs ()
|
||||||
|
|
||||||
let! theCount = Count.byField PostgresDb.TableName (Field.EQ "Value" "purple")
|
let! theCount =
|
||||||
Expect.equal theCount 2 "There should have been 2 matching documents"
|
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 "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" {
|
testTask "byContains succeeds" {
|
||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
@@ -426,6 +545,23 @@ let integrationTests =
|
|||||||
Expect.isFalse exists "There should not have been an existing document"
|
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" [
|
testList "byField" [
|
||||||
testTask "succeeds when documents exist" {
|
testTask "succeeds when documents exist" {
|
||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
@@ -515,6 +651,26 @@ let integrationTests =
|
|||||||
Expect.isNone doc "There should not have been a document returned"
|
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" [
|
testList "byField" [
|
||||||
testTask "succeeds when documents are found" {
|
testTask "succeeds when documents are found" {
|
||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ open Expecto
|
|||||||
open Microsoft.Data.Sqlite
|
open Microsoft.Data.Sqlite
|
||||||
open Types
|
open Types
|
||||||
|
|
||||||
|
#nowarn "0044"
|
||||||
|
|
||||||
/// Integration tests for the F# extensions on the SqliteConnection data type
|
/// Integration tests for the F# extensions on the SqliteConnection data type
|
||||||
let integrationTests =
|
let integrationTests =
|
||||||
let loadDocs () = backgroundTask {
|
let loadDocs () = backgroundTask {
|
||||||
|
|||||||
@@ -8,47 +8,80 @@ open Expecto
|
|||||||
open Microsoft.Data.Sqlite
|
open Microsoft.Data.Sqlite
|
||||||
open Types
|
open Types
|
||||||
|
|
||||||
|
#nowarn "0044"
|
||||||
|
|
||||||
/// Unit tests for the SQLite library
|
/// Unit tests for the SQLite library
|
||||||
let unitTests =
|
let unitTests =
|
||||||
testList "Unit" [
|
testList "Unit" [
|
||||||
testList "Query" [
|
testList "Query" [
|
||||||
|
testList "whereByFields" [
|
||||||
|
test "succeeds for a single field when a logical operator is passed" {
|
||||||
|
Expect.equal
|
||||||
|
(Query.whereByFields Any [ { Field.GT "theField" 0 with ParameterName = Some "@test" } ])
|
||||||
|
"data->>'theField' > @test"
|
||||||
|
"WHERE clause not correct"
|
||||||
|
}
|
||||||
|
test "succeeds for a single field when an existence operator is passed" {
|
||||||
|
Expect.equal
|
||||||
|
(Query.whereByFields Any [ Field.NEX "thatField" ])
|
||||||
|
"data->>'thatField' IS NULL"
|
||||||
|
"WHERE clause not correct"
|
||||||
|
}
|
||||||
|
test "succeeds for a single field when a between operator is passed" {
|
||||||
|
Expect.equal
|
||||||
|
(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" {
|
test "Definition.ensureTable succeeds" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.Definition.ensureTable "tbl")
|
(Query.Definition.ensureTable "tbl")
|
||||||
"CREATE TABLE IF NOT EXISTS tbl (data TEXT NOT NULL)"
|
"CREATE TABLE IF NOT EXISTS tbl (data TEXT NOT NULL)"
|
||||||
"CREATE TABLE statement not correct"
|
"CREATE TABLE statement 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 "Parameters" [
|
testList "Parameters" [
|
||||||
test "idParam succeeds" {
|
test "idParam succeeds" {
|
||||||
@@ -65,7 +98,7 @@ let unitTests =
|
|||||||
test "succeeds when adding a parameter" {
|
test "succeeds when adding a parameter" {
|
||||||
let paramList = addFieldParam "@field" (Field.EQ "it" 99) []
|
let paramList = addFieldParam "@field" (Field.EQ "it" 99) []
|
||||||
Expect.hasLength paramList 1 "There should have been a parameter added"
|
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.ParameterName "@field" "The parameter name is incorrect"
|
||||||
Expect.equal theParam.Value 99 "The parameter value is incorrect"
|
Expect.equal theParam.Value 99 "The parameter value is incorrect"
|
||||||
}
|
}
|
||||||
@@ -299,12 +332,19 @@ let integrationTests =
|
|||||||
let! theCount = Count.all SqliteDb.TableName
|
let! theCount = Count.all SqliteDb.TableName
|
||||||
Expect.equal theCount 5L "There should have been 5 matching documents"
|
Expect.equal theCount 5L "There should have been 5 matching documents"
|
||||||
}
|
}
|
||||||
testTask "byField succeeds" {
|
testTask "byField succeeds for a numeric range" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! theCount = Count.byField SqliteDb.TableName (Field.EQ "Value" "purple")
|
let! theCount = Count.byField SqliteDb.TableName (Field.BT "NumValue" 10 20)
|
||||||
Expect.equal theCount 2L "There should have been 2 matching documents"
|
Expect.equal theCount 3L "There should have been 3 matching documents"
|
||||||
|
}
|
||||||
|
testTask "byField succeeds for a non-numeric range" {
|
||||||
|
use! db = SqliteDb.BuildDb()
|
||||||
|
do! loadDocs ()
|
||||||
|
|
||||||
|
let! theCount = Count.byField SqliteDb.TableName (Field.BT "Value" "aardvark" "apple")
|
||||||
|
Expect.equal theCount 1L "There should have been 1 matching document"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "Exists" [
|
testList "Exists" [
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ cd ./Tests || exit
|
|||||||
|
|
||||||
export BBDOX_PG_PORT=8301
|
export BBDOX_PG_PORT=8301
|
||||||
PG_VERSIONS=('12' '13' '14' '15' 'latest')
|
PG_VERSIONS=('12' '13' '14' '15' 'latest')
|
||||||
NET_VERSIONS=('6.0' '7.0' '8.0')
|
NET_VERSIONS=('6.0' '8.0')
|
||||||
|
|
||||||
for PG_VERSION in "${PG_VERSIONS[@]}"
|
for PG_VERSION in "${PG_VERSIONS[@]}"
|
||||||
do
|
do
|
||||||
|
|||||||
Reference in New Issue
Block a user