Compare commits
26 Commits
v4
...
88f11a854f
| Author | SHA1 | Date | |
|---|---|---|---|
| 88f11a854f | |||
| e2948ea6a1 | |||
| d993f71788 | |||
| fba8ec7bc5 | |||
| 8e33299e22 | |||
| e07844570a | |||
| bac3bd2ef0 | |||
| edd4d006da | |||
| 96f5e1515d | |||
| d382699a17 | |||
| 0c308c5f33 | |||
| 77a6aaa583 | |||
| d9d37f110d | |||
| e0fb793ec9 | |||
| 74e5b77edb | |||
| 98bc83ac17 | |||
| 35755df99a | |||
| b1c3991e11 | |||
| 85750e19f2 | |||
| d8f64417e5 | |||
| 202fca272e | |||
| 8a15bdce2e | |||
| d131eda56e | |||
| e2232e91bb | |||
| e96c449324 | |||
| 433302d995 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -397,6 +397,3 @@ FodyWeavers.xsd
|
|||||||
# JetBrains Rider
|
# JetBrains Rider
|
||||||
*.sln.iml
|
*.sln.iml
|
||||||
**/.idea
|
**/.idea
|
||||||
|
|
||||||
# Test run files
|
|
||||||
src/*-tests.txt
|
|
||||||
|
|||||||
@@ -13,9 +13,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="FSharp.SystemTextJson" Version="1.3.13" />
|
<PackageReference Include="FSharp.SystemTextJson" Version="1.3.13" />
|
||||||
<PackageReference Update="FSharp.Core" Version="9.0.100" />
|
<PackageReference Update="FSharp.Core" Version="8.0.300" />
|
||||||
<PackageReference Include="System.Text.Encodings.Web" Version="9.0.0" />
|
|
||||||
<PackageReference Include="System.Text.Json" Version="9.0.0" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,57 +1,38 @@
|
|||||||
namespace BitBadger.Documents
|
namespace BitBadger.Documents
|
||||||
|
|
||||||
open System.Security.Cryptography
|
/// The types of logical operations available for JSON fields
|
||||||
|
[<Struct>]
|
||||||
/// The types of comparisons available for JSON fields
|
type Op =
|
||||||
type Comparison =
|
|
||||||
|
|
||||||
/// Equals (=)
|
/// Equals (=)
|
||||||
| Equal of Value: obj
|
| EQ
|
||||||
|
|
||||||
/// Greater Than (>)
|
/// Greater Than (>)
|
||||||
| Greater of Value: obj
|
| GT
|
||||||
|
|
||||||
/// Greater Than or Equal To (>=)
|
/// Greater Than or Equal To (>=)
|
||||||
| GreaterOrEqual of Value: obj
|
| GE
|
||||||
|
|
||||||
/// Less Than (<)
|
/// Less Than (<)
|
||||||
| Less of Value: obj
|
| LT
|
||||||
|
|
||||||
/// Less Than or Equal To (<=)
|
/// Less Than or Equal To (<=)
|
||||||
| LessOrEqual of Value: obj
|
| LE
|
||||||
|
|
||||||
/// Not Equal to (<>)
|
/// Not Equal to (<>)
|
||||||
| NotEqual of Value: obj
|
| NE
|
||||||
|
|
||||||
/// Between (BETWEEN)
|
/// Between (BETWEEN)
|
||||||
| Between of Min: obj * Max: obj
|
| BT
|
||||||
|
|
||||||
/// In (IN)
|
|
||||||
| In of Values: obj seq
|
|
||||||
|
|
||||||
/// In Array (PostgreSQL: |?, SQLite: EXISTS / json_each / IN)
|
|
||||||
| InArray of Table: string * Values: obj seq
|
|
||||||
|
|
||||||
/// Exists (IS NOT NULL)
|
/// Exists (IS NOT NULL)
|
||||||
| Exists
|
| EX
|
||||||
|
|
||||||
/// Does Not Exist (IS NULL)
|
/// Does Not Exist (IS NULL)
|
||||||
| NotExists
|
| NEX
|
||||||
|
|
||||||
/// Get the operator SQL for this comparison
|
override this.ToString() =
|
||||||
member this.OpSql =
|
|
||||||
match this with
|
match this with
|
||||||
| Equal _ -> "="
|
| EQ -> "="
|
||||||
| Greater _ -> ">"
|
| GT -> ">"
|
||||||
| GreaterOrEqual _ -> ">="
|
| GE -> ">="
|
||||||
| Less _ -> "<"
|
| LT -> "<"
|
||||||
| LessOrEqual _ -> "<="
|
| LE -> "<="
|
||||||
| NotEqual _ -> "<>"
|
| NE -> "<>"
|
||||||
| Between _ -> "BETWEEN"
|
| BT -> "BETWEEN"
|
||||||
| In _ -> "IN"
|
| EX -> "IS NOT NULL"
|
||||||
| InArray _ -> "?|" // PostgreSQL only; SQL needs a subquery for this
|
| NEX -> "IS NULL"
|
||||||
| Exists -> "IS NOT NULL"
|
|
||||||
| NotExists -> "IS NULL"
|
|
||||||
|
|
||||||
|
|
||||||
/// The dialect in which a command should be rendered
|
/// The dialect in which a command should be rendered
|
||||||
@@ -60,26 +41,16 @@ type Dialect =
|
|||||||
| PostgreSQL
|
| PostgreSQL
|
||||||
| SQLite
|
| SQLite
|
||||||
|
|
||||||
|
|
||||||
/// The format in which an element of a JSON field should be extracted
|
|
||||||
[<Struct>]
|
|
||||||
type FieldFormat =
|
|
||||||
|
|
||||||
/// Use ->> or #>>; extracts a text (PostgreSQL) or SQL (SQLite) value
|
|
||||||
| AsSql
|
|
||||||
|
|
||||||
/// Use -> or #>; extracts a JSONB (PostgreSQL) or JSON (SQLite) value
|
|
||||||
| AsJson
|
|
||||||
|
|
||||||
|
|
||||||
/// Criteria for a field WHERE clause
|
/// Criteria for a field WHERE clause
|
||||||
type Field = {
|
type Field = {
|
||||||
|
|
||||||
/// The name of the field
|
/// The name of the field
|
||||||
Name: string
|
Name: string
|
||||||
|
|
||||||
/// The comparison for the field
|
/// The operation by which the field will be compared
|
||||||
Comparison: Comparison
|
Op: Op
|
||||||
|
|
||||||
|
/// The value of the field
|
||||||
|
Value: obj
|
||||||
|
|
||||||
/// The name of the parameter for this field
|
/// The name of the parameter for this field
|
||||||
ParameterName: string option
|
ParameterName: string option
|
||||||
@@ -88,104 +59,55 @@ type Field = {
|
|||||||
Qualifier: string option
|
Qualifier: string option
|
||||||
} with
|
} with
|
||||||
|
|
||||||
/// Create a comparison against a field
|
|
||||||
static member Where name (comparison: Comparison) =
|
|
||||||
{ Name = name; Comparison = comparison; ParameterName = None; Qualifier = None }
|
|
||||||
|
|
||||||
/// Create an equals (=) field criterion
|
/// Create an equals (=) field criterion
|
||||||
static member Equal<'T> name (value: 'T) =
|
static member EQ name (value: obj) =
|
||||||
Field.Where name (Equal value)
|
{ Name = name; Op = EQ; Value = value; ParameterName = None; Qualifier = None }
|
||||||
|
|
||||||
/// Create an equals (=) field criterion (alias)
|
|
||||||
static member EQ<'T> name (value: 'T) = Field.Equal name value
|
|
||||||
|
|
||||||
/// Create a greater than (>) field criterion
|
/// Create a greater than (>) field criterion
|
||||||
static member Greater<'T> name (value: 'T) =
|
static member GT name (value: obj) =
|
||||||
Field.Where name (Greater value)
|
{ Name = name; Op = GT; Value = value; ParameterName = None; Qualifier = None }
|
||||||
|
|
||||||
/// Create a greater than (>) field criterion (alias)
|
|
||||||
static member GT<'T> name (value: 'T) = Field.Greater name value
|
|
||||||
|
|
||||||
/// Create a greater than or equal to (>=) field criterion
|
/// Create a greater than or equal to (>=) field criterion
|
||||||
static member GreaterOrEqual<'T> name (value: 'T) =
|
static member GE name (value: obj) =
|
||||||
Field.Where name (GreaterOrEqual value)
|
{ Name = name; Op = GE; Value = value; ParameterName = None; Qualifier = None }
|
||||||
|
|
||||||
/// Create a greater than or equal to (>=) field criterion (alias)
|
|
||||||
static member GE<'T> name (value: 'T) = Field.GreaterOrEqual name value
|
|
||||||
|
|
||||||
/// Create a less than (<) field criterion
|
/// Create a less than (<) field criterion
|
||||||
static member Less<'T> name (value: 'T) =
|
static member LT name (value: obj) =
|
||||||
Field.Where name (Less value)
|
{ Name = name; Op = LT; Value = value; ParameterName = None; Qualifier = None }
|
||||||
|
|
||||||
/// Create a less than (<) field criterion (alias)
|
|
||||||
static member LT<'T> name (value: 'T) = Field.Less name value
|
|
||||||
|
|
||||||
/// Create a less than or equal to (<=) field criterion
|
/// Create a less than or equal to (<=) field criterion
|
||||||
static member LessOrEqual<'T> name (value: 'T) =
|
static member LE name (value: obj) =
|
||||||
Field.Where name (LessOrEqual value)
|
{ Name = name; Op = LE; Value = value; ParameterName = None; Qualifier = None }
|
||||||
|
|
||||||
/// Create a less than or equal to (<=) field criterion (alias)
|
|
||||||
static member LE<'T> name (value: 'T) = Field.LessOrEqual name value
|
|
||||||
|
|
||||||
/// Create a not equals (<>) field criterion
|
/// Create a not equals (<>) field criterion
|
||||||
static member NotEqual<'T> name (value: 'T) =
|
static member NE name (value: obj) =
|
||||||
Field.Where name (NotEqual value)
|
{ Name = name; Op = NE; Value = value; ParameterName = None; Qualifier = None }
|
||||||
|
|
||||||
/// Create a not equals (<>) field criterion (alias)
|
/// Create a BETWEEN field criterion
|
||||||
static member NE<'T> name (value: 'T) = Field.NotEqual name value
|
static member BT name (min: obj) (max: obj) =
|
||||||
|
{ Name = name; Op = BT; Value = [ min; max ]; ParameterName = None; Qualifier = None }
|
||||||
/// Create a Between field criterion
|
|
||||||
static member Between<'T> name (min: 'T) (max: 'T) =
|
|
||||||
Field.Where name (Between(min, max))
|
|
||||||
|
|
||||||
/// Create a Between field criterion (alias)
|
|
||||||
static member BT<'T> name (min: 'T) (max: 'T) = Field.Between name min max
|
|
||||||
|
|
||||||
/// Create an In field criterion
|
|
||||||
static member In<'T> name (values: 'T seq) =
|
|
||||||
Field.Where name (In (Seq.map box values))
|
|
||||||
|
|
||||||
/// Create an In field criterion (alias)
|
|
||||||
static member IN<'T> name (values: 'T seq) = Field.In name values
|
|
||||||
|
|
||||||
/// Create an InArray field criterion
|
|
||||||
static member InArray<'T> name tableName (values: 'T seq) =
|
|
||||||
Field.Where name (InArray(tableName, Seq.map box values))
|
|
||||||
|
|
||||||
/// Create an exists (IS NOT NULL) field criterion
|
/// Create an exists (IS NOT NULL) field criterion
|
||||||
static member Exists name =
|
static member EX name =
|
||||||
Field.Where name Exists
|
{ Name = name; Op = EX; Value = obj (); ParameterName = None; Qualifier = None }
|
||||||
|
|
||||||
/// Create an exists (IS NOT NULL) field criterion (alias)
|
|
||||||
static member EX name = Field.Exists name
|
|
||||||
|
|
||||||
/// Create a not exists (IS NULL) field criterion
|
/// Create a not exists (IS NULL) field criterion
|
||||||
static member NotExists name =
|
static member NEX name =
|
||||||
Field.Where name NotExists
|
{ Name = name; Op = NEX; Value = obj (); ParameterName = None; Qualifier = None }
|
||||||
|
|
||||||
/// Create a not exists (IS NULL) field criterion (alias)
|
|
||||||
static member NEX name = Field.NotExists name
|
|
||||||
|
|
||||||
/// Transform a field name (a.b.c) to a path for the given SQL dialect
|
/// Transform a field name (a.b.c) to a path for the given SQL dialect
|
||||||
static member NameToPath (name: string) dialect format =
|
static member NameToPath (name: string) dialect =
|
||||||
let path =
|
let path =
|
||||||
if name.Contains '.' then
|
if name.Contains '.' then
|
||||||
match dialect with
|
match dialect with
|
||||||
| PostgreSQL ->
|
| PostgreSQL -> "#>>'{" + String.concat "," (name.Split '.') + "}'"
|
||||||
(match format with AsJson -> "#>" | AsSql -> "#>>")
|
| SQLite -> "->>'" + String.concat "'->>'" (name.Split '.') + "'"
|
||||||
+ "'{" + String.concat "," (name.Split '.') + "}'"
|
else $"->>'{name}'"
|
||||||
| SQLite ->
|
|
||||||
let parts = name.Split '.'
|
|
||||||
let last = Array.last parts
|
|
||||||
let final = (match format with AsJson -> "'->'" | AsSql -> "'->>'") + $"{last}'"
|
|
||||||
"->'" + String.concat "'->'" (Array.truncate (Array.length parts - 1) parts) + final
|
|
||||||
else
|
|
||||||
match format with AsJson -> $"->'{name}'" | AsSql -> $"->>'{name}'"
|
|
||||||
$"data{path}"
|
$"data{path}"
|
||||||
|
|
||||||
/// Create a field with a given name, but no other properties filled (op will be EQ, value will be "")
|
/// Create a field with a given name, but no other properties filled (op will be EQ, value will be "")
|
||||||
static member Named name =
|
static member Named name =
|
||||||
Field.Where name (Equal "")
|
{ Name = name; Op = EQ; Value = ""; ParameterName = None; Qualifier = None }
|
||||||
|
|
||||||
/// Specify the name of the parameter for this field
|
/// Specify the name of the parameter for this field
|
||||||
member this.WithParameterName name =
|
member this.WithParameterName name =
|
||||||
@@ -196,18 +118,15 @@ type Field = {
|
|||||||
{ this with Qualifier = Some alias }
|
{ this with Qualifier = Some alias }
|
||||||
|
|
||||||
/// Get the qualified path to the field
|
/// Get the qualified path to the field
|
||||||
member this.Path dialect format =
|
member this.Path dialect =
|
||||||
(this.Qualifier |> Option.map (fun q -> $"{q}.") |> Option.defaultValue "")
|
(this.Qualifier |> Option.map (fun q -> $"{q}.") |> Option.defaultValue "") + Field.NameToPath this.Name dialect
|
||||||
+ Field.NameToPath this.Name dialect format
|
|
||||||
|
|
||||||
|
|
||||||
/// How fields should be matched
|
/// How fields should be matched
|
||||||
[<Struct>]
|
[<Struct>]
|
||||||
type FieldMatch =
|
type FieldMatch =
|
||||||
|
|
||||||
/// Any field matches (OR)
|
/// Any field matches (OR)
|
||||||
| Any
|
| Any
|
||||||
|
|
||||||
/// All fields match (AND)
|
/// All fields match (AND)
|
||||||
| All
|
| All
|
||||||
|
|
||||||
@@ -218,7 +137,6 @@ type FieldMatch =
|
|||||||
|
|
||||||
/// Derive parameter names (each instance wraps a counter to uniquely name anonymous fields)
|
/// Derive parameter names (each instance wraps a counter to uniquely name anonymous fields)
|
||||||
type ParameterName() =
|
type ParameterName() =
|
||||||
|
|
||||||
/// The counter for the next field value
|
/// The counter for the next field value
|
||||||
let mutable currentIdx = -1
|
let mutable currentIdx = -1
|
||||||
|
|
||||||
@@ -231,65 +149,6 @@ type ParameterName() =
|
|||||||
$"@field{currentIdx}"
|
$"@field{currentIdx}"
|
||||||
|
|
||||||
|
|
||||||
/// Automatically-generated document ID strategies
|
|
||||||
[<Struct>]
|
|
||||||
type AutoId =
|
|
||||||
|
|
||||||
/// No automatic IDs will be generated
|
|
||||||
| Disabled
|
|
||||||
|
|
||||||
/// Generate a MAX-plus-1 numeric value for documents
|
|
||||||
| Number
|
|
||||||
|
|
||||||
/// Generate a GUID for each document (as a lowercase, no-dashes, 32-character string)
|
|
||||||
| Guid
|
|
||||||
|
|
||||||
/// Generate a random string of hexadecimal characters for each document
|
|
||||||
| RandomString
|
|
||||||
with
|
|
||||||
/// Generate a GUID string
|
|
||||||
static member GenerateGuid() =
|
|
||||||
System.Guid.NewGuid().ToString "N"
|
|
||||||
|
|
||||||
/// Generate a string of random hexadecimal characters
|
|
||||||
static member GenerateRandomString(length: int) =
|
|
||||||
RandomNumberGenerator.GetHexString(length, lowercase = true)
|
|
||||||
|
|
||||||
/// Does the given document need an automatic ID generated?
|
|
||||||
static member NeedsAutoId<'T> strategy (document: 'T) idProp =
|
|
||||||
match strategy with
|
|
||||||
| Disabled -> false
|
|
||||||
| _ ->
|
|
||||||
let prop = document.GetType().GetProperty idProp
|
|
||||||
if isNull prop then invalidOp $"{idProp} not found in document"
|
|
||||||
else
|
|
||||||
match strategy with
|
|
||||||
| Number ->
|
|
||||||
if prop.PropertyType = typeof<int8> then
|
|
||||||
let value = prop.GetValue document :?> int8
|
|
||||||
value = int8 0
|
|
||||||
elif prop.PropertyType = typeof<int16> then
|
|
||||||
let value = prop.GetValue document :?> int16
|
|
||||||
value = int16 0
|
|
||||||
elif prop.PropertyType = typeof<int> then
|
|
||||||
let value = prop.GetValue document :?> int
|
|
||||||
value = 0
|
|
||||||
elif prop.PropertyType = typeof<int64> then
|
|
||||||
let value = prop.GetValue document :?> int64
|
|
||||||
value = int64 0
|
|
||||||
else invalidOp "Document ID was not a number; cannot auto-generate a Number ID"
|
|
||||||
| Guid | RandomString ->
|
|
||||||
if prop.PropertyType = typeof<string> then
|
|
||||||
let value =
|
|
||||||
prop.GetValue document
|
|
||||||
|> Option.ofObj
|
|
||||||
|> Option.map (fun it -> it :?> string)
|
|
||||||
|> Option.defaultValue ""
|
|
||||||
value = ""
|
|
||||||
else invalidOp "Document ID was not a string; cannot auto-generate GUID or random string"
|
|
||||||
| Disabled -> false
|
|
||||||
|
|
||||||
|
|
||||||
/// The required document serialization implementation
|
/// The required document serialization implementation
|
||||||
type IDocumentSerializer =
|
type IDocumentSerializer =
|
||||||
|
|
||||||
@@ -341,7 +200,7 @@ module Configuration =
|
|||||||
serializerValue
|
serializerValue
|
||||||
|
|
||||||
/// The serialized name of the ID field for documents
|
/// The serialized name of the ID field for documents
|
||||||
let mutable private idFieldValue = "Id"
|
let mutable idFieldValue = "Id"
|
||||||
|
|
||||||
/// Specify the name of the ID field for documents
|
/// Specify the name of the ID field for documents
|
||||||
[<CompiledName "UseIdField">]
|
[<CompiledName "UseIdField">]
|
||||||
@@ -353,32 +212,6 @@ module Configuration =
|
|||||||
let idField () =
|
let idField () =
|
||||||
idFieldValue
|
idFieldValue
|
||||||
|
|
||||||
/// The automatic ID strategy used by the library
|
|
||||||
let mutable private autoIdValue = Disabled
|
|
||||||
|
|
||||||
/// Specify the automatic ID generation strategy used by the library
|
|
||||||
[<CompiledName "UseAutoIdStrategy">]
|
|
||||||
let useAutoIdStrategy it =
|
|
||||||
autoIdValue <- it
|
|
||||||
|
|
||||||
/// Retrieve the currently configured automatic ID generation strategy
|
|
||||||
[<CompiledName "AutoIdStrategy">]
|
|
||||||
let autoIdStrategy () =
|
|
||||||
autoIdValue
|
|
||||||
|
|
||||||
/// The length of automatically generated random strings
|
|
||||||
let mutable private idStringLengthValue = 16
|
|
||||||
|
|
||||||
/// Specify the length of automatically generated random strings
|
|
||||||
[<CompiledName "UseIdStringLength">]
|
|
||||||
let useIdStringLength length =
|
|
||||||
idStringLengthValue <- length
|
|
||||||
|
|
||||||
/// Retrieve the currently configured length of automatically generated random strings
|
|
||||||
[<CompiledName "IdStringLength">]
|
|
||||||
let idStringLength () =
|
|
||||||
idStringLengthValue
|
|
||||||
|
|
||||||
|
|
||||||
/// Query construction functions
|
/// Query construction functions
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
@@ -412,7 +245,7 @@ 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]}"
|
||||||
$"({Field.NameToPath fieldName dialect AsSql}){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})"
|
||||||
|
|
||||||
@@ -473,18 +306,13 @@ module Query =
|
|||||||
|> Seq.map (fun it ->
|
|> Seq.map (fun it ->
|
||||||
if it.Name.Contains ' ' then
|
if it.Name.Contains ' ' then
|
||||||
let parts = it.Name.Split ' '
|
let parts = it.Name.Split ' '
|
||||||
{ it with Name = parts[0] }, Some $""" {parts |> Array.skip 1 |> String.concat " "}"""
|
{ it with Name = parts[0] }, Some $" {parts[1]}"
|
||||||
else it, None)
|
else it, None)
|
||||||
|> Seq.map (fun (field, direction) ->
|
|> Seq.map (fun (field, direction) ->
|
||||||
if field.Name.StartsWith "n:" then
|
match dialect, field.Name.StartsWith "n:" with
|
||||||
let f = { field with Name = field.Name[2..] }
|
| PostgreSQL, true -> $"({ { field with Name = field.Name[2..] }.Path PostgreSQL})::numeric"
|
||||||
match dialect with
|
| SQLite, true -> { field with Name = field.Name[2..] }.Path SQLite
|
||||||
| PostgreSQL -> $"({f.Path PostgreSQL AsSql})::numeric"
|
| _, _ -> field.Path dialect
|
||||||
| SQLite -> f.Path SQLite AsSql
|
|
||||||
elif field.Name.StartsWith "i:" then
|
|
||||||
let p = { field with Name = field.Name[2..] }.Path dialect AsSql
|
|
||||||
match dialect with PostgreSQL -> $"LOWER({p})" | SQLite -> $"{p} COLLATE NOCASE"
|
|
||||||
else field.Path dialect AsSql
|
|
||||||
|> function path -> path + defaultArg direction "")
|
|> function path -> path + defaultArg direction "")
|
||||||
|> String.concat ", "
|
|> String.concat ", "
|
||||||
|> function it -> $" ORDER BY {it}"
|
|> function it -> $" ORDER BY {it}"
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ This package provides common definitions and functionality for `BitBadger.Docume
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Select, insert, update, save (upsert), delete, count, and check existence of documents, and create tables and indexes for these documents
|
- Select, insert, update, save (upsert), delete, count, and check existence of documents, and create tables and indexes for these documents
|
||||||
- Automatically generate IDs for documents (numeric IDs, GUIDs, or random strings)
|
|
||||||
- Addresses documents via ID and via comparison on any field (for PostgreSQL, also via equality on any property by using JSON containment, or via condition on any property using JSON Path queries)
|
- Addresses documents via ID and via comparison on any field (for PostgreSQL, also via equality on any property by using JSON containment, or via condition on any property using JSON Path queries)
|
||||||
- Accesses documents as your domain models (<abbr title="Plain Old CLR Objects">POCO</abbr>s)
|
- Accesses documents as your domain models (<abbr title="Plain Old CLR Objects">POCO</abbr>s)
|
||||||
- Uses `Task`-based async for all data access functions
|
- Uses `Task`-based async for all data access functions
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
|
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
|
||||||
<DebugType>embedded</DebugType>
|
<DebugType>embedded</DebugType>
|
||||||
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
||||||
<AssemblyVersion>4.0.0.0</AssemblyVersion>
|
<AssemblyVersion>4.0.0.0</AssemblyVersion>
|
||||||
<FileVersion>4.0.0.0</FileVersion>
|
<FileVersion>4.0.0.0</FileVersion>
|
||||||
<VersionPrefix>4.0.0</VersionPrefix>
|
<VersionPrefix>4.0.0</VersionPrefix>
|
||||||
<PackageReleaseNotes>From v3.1: (see project site for breaking changes and compatibility)
|
<PackageReleaseNotes>Change ByField to ByFields; support dot-access to nested document fields; add Find*Ordered functions/methods; see project site for breaking changes and compatibility</PackageReleaseNotes>
|
||||||
- Change ByField to ByFields
|
|
||||||
- Support dot-access to nested document fields
|
|
||||||
- Add Find*Ordered functions/methods
|
|
||||||
- Add case-insensitive ordering (as of rc2)
|
|
||||||
- Preserve additional ORDER BY qualifiers (as of rc3)
|
|
||||||
- Add In / InArray comparisons (as of rc4)
|
|
||||||
- Field construction functions are generic (as of rc5)</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>
|
||||||
|
|||||||
@@ -14,9 +14,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Npgsql" Version="9.0.2" />
|
<PackageReference Include="Npgsql.FSharp" Version="5.7.0" />
|
||||||
<PackageReference Include="Npgsql.FSharp" Version="8.0.0" />
|
<PackageReference Update="FSharp.Core" Version="8.0.300" />
|
||||||
<PackageReference Update="FSharp.Core" Version="9.0.100" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ module WithProps =
|
|||||||
|
|
||||||
/// Retrieve the first document matching a JSON field comparison (->> =); returns null if not found
|
/// Retrieve the first document matching a JSON field comparison (->> =); returns null if not found
|
||||||
[<System.Obsolete "Use FirstByFields instead ~ will be removed in v4.1">]
|
[<System.Obsolete "Use FirstByFields instead ~ will be removed in v4.1">]
|
||||||
let FirstByField<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, field, sqlProps) =
|
let FirstByField<'TDoc when 'TDoc: null>(tableName, field, sqlProps) =
|
||||||
WithProps.Find.FirstByFields<'TDoc>(tableName, Any, Seq.singleton field, sqlProps)
|
WithProps.Find.FirstByFields<'TDoc>(tableName, Any, Seq.singleton field, sqlProps)
|
||||||
|
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
@@ -144,7 +144,7 @@ module Find =
|
|||||||
|
|
||||||
/// Retrieve the first document matching a JSON field comparison (->> =); returns null if not found
|
/// Retrieve the first document matching a JSON field comparison (->> =); returns null if not found
|
||||||
[<System.Obsolete "Use FirstByFields instead ~ will be removed in v4.1">]
|
[<System.Obsolete "Use FirstByFields instead ~ will be removed in v4.1">]
|
||||||
let FirstByField<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, field) =
|
let FirstByField<'TDoc when 'TDoc: null>(tableName, field) =
|
||||||
Find.FirstByFields<'TDoc>(tableName, Any, Seq.singleton field)
|
Find.FirstByFields<'TDoc>(tableName, Any, Seq.singleton field)
|
||||||
|
|
||||||
|
|
||||||
@@ -248,7 +248,7 @@ type NpgsqlConnectionCSharpCompatExtensions =
|
|||||||
/// 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
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
[<System.Obsolete "Use FindFirstByFields instead ~ will be removed in v4.1">]
|
[<System.Obsolete "Use FindFirstByFields instead ~ will be removed in v4.1">]
|
||||||
static member inline FindFirstByField<'TDoc when 'TDoc: null and 'TDoc: not struct>(conn, tableName, field) =
|
static member inline FindFirstByField<'TDoc when 'TDoc: null>(conn, tableName, field) =
|
||||||
WithProps.Find.FirstByFields<'TDoc>(tableName, Any, [ field ], Sql.existingConnection conn)
|
WithProps.Find.FirstByFields<'TDoc>(tableName, Any, [ field ], 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 (->> =)
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ type NpgsqlConnectionCSharpExtensions =
|
|||||||
|
|
||||||
/// Execute a query that returns one or no results; returns None if not found
|
/// Execute a query that returns one or no results; returns None if not found
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline CustomSingle<'TDoc when 'TDoc: null and 'TDoc: not struct>(
|
static member inline CustomSingle<'TDoc when 'TDoc: null>(
|
||||||
conn, query, parameters, mapFunc: System.Func<RowReader, 'TDoc>) =
|
conn, query, parameters, mapFunc: System.Func<RowReader, 'TDoc>) =
|
||||||
WithProps.Custom.Single<'TDoc>(query, parameters, mapFunc, Sql.existingConnection conn)
|
WithProps.Custom.Single<'TDoc>(query, parameters, mapFunc, Sql.existingConnection conn)
|
||||||
|
|
||||||
@@ -303,7 +303,7 @@ type NpgsqlConnectionCSharpExtensions =
|
|||||||
|
|
||||||
/// Retrieve a document by its ID; returns None if not found
|
/// Retrieve a document by its ID; returns None if not found
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline FindById<'TKey, 'TDoc when 'TDoc: null and 'TDoc: not struct>(conn, tableName, docId: 'TKey) =
|
static member inline FindById<'TKey, 'TDoc when 'TDoc: null>(conn, tableName, docId: 'TKey) =
|
||||||
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 (->> =)
|
||||||
@@ -339,41 +339,38 @@ type NpgsqlConnectionCSharpExtensions =
|
|||||||
|
|
||||||
/// 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
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline FindFirstByFields<'TDoc when 'TDoc: null and 'TDoc: not struct>(
|
static member inline FindFirstByFields<'TDoc when 'TDoc: null>(conn, tableName, howMatched, fields) =
|
||||||
conn, tableName, howMatched, fields) =
|
|
||||||
WithProps.Find.FirstByFields<'TDoc>(tableName, howMatched, fields, Sql.existingConnection conn)
|
WithProps.Find.FirstByFields<'TDoc>(tableName, howMatched, fields, Sql.existingConnection conn)
|
||||||
|
|
||||||
/// Retrieve the first document matching a JSON field comparison query (->> =) ordered by the given fields in the
|
/// Retrieve the first document matching a JSON field comparison query (->> =) ordered by the given fields in the
|
||||||
/// document; returns null if not found
|
/// document; returns null if not found
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline FindFirstByFieldsOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>(
|
static member inline FindFirstByFieldsOrdered<'TDoc when 'TDoc: null>(
|
||||||
conn, tableName, howMatched, queryFields, orderFields) =
|
conn, tableName, howMatched, queryFields, orderFields) =
|
||||||
WithProps.Find.FirstByFieldsOrdered<'TDoc>(
|
WithProps.Find.FirstByFieldsOrdered<'TDoc>(
|
||||||
tableName, howMatched, queryFields, orderFields, Sql.existingConnection conn)
|
tableName, howMatched, queryFields, orderFields, Sql.existingConnection conn)
|
||||||
|
|
||||||
/// Retrieve the first document matching a JSON containment query (@>); returns None if not found
|
/// Retrieve the first document matching a JSON containment query (@>); returns None if not found
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline FindFirstByContains<'TDoc when 'TDoc: null and 'TDoc: not struct>(
|
static member inline FindFirstByContains<'TDoc when 'TDoc: null>(conn, tableName, criteria: obj) =
|
||||||
conn, tableName, criteria: obj) =
|
|
||||||
WithProps.Find.FirstByContains<'TDoc>(tableName, criteria, Sql.existingConnection conn)
|
WithProps.Find.FirstByContains<'TDoc>(tableName, criteria, Sql.existingConnection conn)
|
||||||
|
|
||||||
/// Retrieve the first document matching a JSON containment query (@>) ordered by the given fields in the document;
|
/// Retrieve the first document matching a JSON containment query (@>) ordered by the given fields in the document;
|
||||||
/// returns None if not found
|
/// returns None if not found
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline FindFirstByContainsOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>(
|
static member inline FindFirstByContainsOrdered<'TDoc when 'TDoc: null>(
|
||||||
conn, tableName, criteria: obj, orderFields) =
|
conn, tableName, criteria: obj, orderFields) =
|
||||||
WithProps.Find.FirstByContainsOrdered<'TDoc>(tableName, criteria, orderFields, Sql.existingConnection conn)
|
WithProps.Find.FirstByContainsOrdered<'TDoc>(tableName, criteria, orderFields, Sql.existingConnection conn)
|
||||||
|
|
||||||
/// Retrieve the first document matching a JSON Path match query (@?); returns None if not found
|
/// Retrieve the first document matching a JSON Path match query (@?); returns None if not found
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline FindFirstByJsonPath<'TDoc when 'TDoc: null and 'TDoc: not struct>(conn, tableName, jsonPath) =
|
static member inline FindFirstByJsonPath<'TDoc when 'TDoc: null>(conn, tableName, jsonPath) =
|
||||||
WithProps.Find.FirstByJsonPath<'TDoc>(tableName, jsonPath, Sql.existingConnection conn)
|
WithProps.Find.FirstByJsonPath<'TDoc>(tableName, jsonPath, Sql.existingConnection conn)
|
||||||
|
|
||||||
/// Retrieve the first document matching a JSON Path match query (@?) ordered by the given fields in the document;
|
/// Retrieve the first document matching a JSON Path match query (@?) ordered by the given fields in the document;
|
||||||
/// returns None if not found
|
/// returns None if not found
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline FindFirstByJsonPathOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>(
|
static member inline FindFirstByJsonPathOrdered<'TDoc when 'TDoc: null>(conn, tableName, jsonPath, orderFields) =
|
||||||
conn, tableName, jsonPath, orderFields) =
|
|
||||||
WithProps.Find.FirstByJsonPathOrdered<'TDoc>(tableName, jsonPath, orderFields, Sql.existingConnection conn)
|
WithProps.Find.FirstByJsonPathOrdered<'TDoc>(tableName, jsonPath, orderFields, Sql.existingConnection conn)
|
||||||
|
|
||||||
/// Update an entire document by its ID
|
/// Update an entire document by its ID
|
||||||
|
|||||||
@@ -3,10 +3,8 @@
|
|||||||
/// The type of index to generate for the document
|
/// The type of index to generate for the document
|
||||||
[<Struct>]
|
[<Struct>]
|
||||||
type DocumentIndex =
|
type DocumentIndex =
|
||||||
|
|
||||||
/// A GIN index with standard operations (all operators supported)
|
/// A GIN index with standard operations (all operators supported)
|
||||||
| Full
|
| Full
|
||||||
|
|
||||||
/// A GIN index with JSONPath operations (optimized for @>, @?, @@ operators)
|
/// A GIN index with JSONPath operations (optimized for @>, @?, @@ operators)
|
||||||
| Optimized
|
| Optimized
|
||||||
|
|
||||||
@@ -38,7 +36,6 @@ open Npgsql.FSharp
|
|||||||
/// Helper functions
|
/// Helper functions
|
||||||
[<AutoOpen>]
|
[<AutoOpen>]
|
||||||
module private Helpers =
|
module private Helpers =
|
||||||
|
|
||||||
/// Shorthand to retrieve the data source as SqlProps
|
/// Shorthand to retrieve the data source as SqlProps
|
||||||
let internal fromDataSource () =
|
let internal fromDataSource () =
|
||||||
Configuration.dataSource () |> Sql.fromDataSource
|
Configuration.dataSource () |> Sql.fromDataSource
|
||||||
@@ -90,25 +87,18 @@ module Parameters =
|
|||||||
fields
|
fields
|
||||||
|> Seq.map (fun it ->
|
|> Seq.map (fun it ->
|
||||||
seq {
|
seq {
|
||||||
match it.Comparison with
|
match it.Op with
|
||||||
| Exists | NotExists -> ()
|
| EX | NEX -> ()
|
||||||
| Between (min, max) ->
|
| BT ->
|
||||||
let p = name.Derive it.ParameterName
|
let p = name.Derive it.ParameterName
|
||||||
yield ($"{p}min", parameterFor min (fun v -> Sql.parameter (NpgsqlParameter($"{p}min", v))))
|
let values = it.Value :?> obj list
|
||||||
yield ($"{p}max", parameterFor max (fun v -> Sql.parameter (NpgsqlParameter($"{p}max", v))))
|
yield ($"{p}min",
|
||||||
| In values ->
|
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
|
let p = name.Derive it.ParameterName
|
||||||
yield!
|
yield (p, parameterFor it.Value (fun v -> Sql.parameter (NpgsqlParameter(p, v)))) })
|
||||||
values
|
|
||||||
|> Seq.mapi (fun idx v ->
|
|
||||||
let paramName = $"{p}_{idx}"
|
|
||||||
paramName, Sql.parameter (NpgsqlParameter(paramName, v)))
|
|
||||||
| InArray (_, values) ->
|
|
||||||
let p = name.Derive it.ParameterName
|
|
||||||
yield (p, Sql.stringArray (values |> Seq.map string |> Array.ofSeq))
|
|
||||||
| Equal v | Greater v | GreaterOrEqual v | Less v | LessOrEqual v | NotEqual v ->
|
|
||||||
let p = name.Derive it.ParameterName
|
|
||||||
yield (p, parameterFor v (fun l -> Sql.parameter (NpgsqlParameter(p, l)))) })
|
|
||||||
|> Seq.collect id
|
|> Seq.collect id
|
||||||
|> Seq.append parameters
|
|> Seq.append parameters
|
||||||
|> Seq.toList
|
|> Seq.toList
|
||||||
@@ -141,28 +131,23 @@ module Query =
|
|||||||
| _ -> false
|
| _ -> false
|
||||||
fields
|
fields
|
||||||
|> Seq.map (fun it ->
|
|> Seq.map (fun it ->
|
||||||
match it.Comparison with
|
match it.Op with
|
||||||
| Exists | NotExists -> $"{it.Path PostgreSQL AsSql} {it.Comparison.OpSql}"
|
| EX | NEX -> $"{it.Path PostgreSQL} {it.Op}"
|
||||||
| InArray _ -> $"{it.Path PostgreSQL AsJson} {it.Comparison.OpSql} {name.Derive it.ParameterName}"
|
|
||||||
| _ ->
|
| _ ->
|
||||||
let p = name.Derive it.ParameterName
|
let p = name.Derive it.ParameterName
|
||||||
let param, value =
|
let param, value =
|
||||||
match it.Comparison with
|
match it.Op with
|
||||||
| Between (min, _) -> $"{p}min AND {p}max", min
|
| BT -> $"{p}min AND {p}max", (it.Value :?> obj list)[0]
|
||||||
| In values ->
|
| _ -> p, it.Value
|
||||||
let paramNames = values |> Seq.mapi (fun idx _ -> $"{p}_{idx}") |> String.concat ", "
|
|
||||||
$"({paramNames})", defaultArg (Seq.tryHead values) (obj ())
|
|
||||||
| Equal v | Greater v | GreaterOrEqual v | Less v | LessOrEqual v | NotEqual v -> p, v
|
|
||||||
| _ -> p, ""
|
|
||||||
if isNumeric value then
|
if isNumeric value then
|
||||||
$"({it.Path PostgreSQL AsSql})::numeric {it.Comparison.OpSql} {param}"
|
$"({it.Path PostgreSQL})::numeric {it.Op} {param}"
|
||||||
else $"{it.Path PostgreSQL AsSql} {it.Comparison.OpSql} {param}")
|
else $"{it.Path PostgreSQL} {it.Op} {param}")
|
||||||
|> String.concat $" {howMatched} "
|
|> String.concat $" {howMatched} "
|
||||||
|
|
||||||
/// Create a WHERE clause fragment to implement an ID-based query
|
/// Create a WHERE clause fragment to implement an ID-based query
|
||||||
[<CompiledName "WhereById">]
|
[<CompiledName "WhereById">]
|
||||||
let whereById<'TKey> (docId: 'TKey) =
|
let whereById<'TKey> (docId: 'TKey) =
|
||||||
whereByFields Any [ { Field.Equal (Configuration.idField ()) docId with ParameterName = Some "@id" } ]
|
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 =
|
||||||
@@ -275,7 +260,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 and 'TDoc: not struct>(
|
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 parameters mapFunc.Invoke sqlProps
|
let! result = single<'TDoc> query parameters mapFunc.Invoke sqlProps
|
||||||
return Option.toObj result
|
return Option.toObj result
|
||||||
@@ -327,23 +312,7 @@ module WithProps =
|
|||||||
/// Insert a new document
|
/// Insert a new document
|
||||||
[<CompiledName "Insert">]
|
[<CompiledName "Insert">]
|
||||||
let insert<'TDoc> tableName (document: 'TDoc) sqlProps =
|
let insert<'TDoc> tableName (document: 'TDoc) sqlProps =
|
||||||
let query =
|
Custom.nonQuery (Query.insert tableName) [ jsonParam "@data" document ] sqlProps
|
||||||
match Configuration.autoIdStrategy () with
|
|
||||||
| Disabled -> Query.insert tableName
|
|
||||||
| strategy ->
|
|
||||||
let idField = Configuration.idField ()
|
|
||||||
let dataParam =
|
|
||||||
if AutoId.NeedsAutoId strategy document idField then
|
|
||||||
match strategy with
|
|
||||||
| Number ->
|
|
||||||
$"' || (SELECT COALESCE(MAX((data->>'{idField}')::numeric), 0) + 1 FROM {tableName}) || '"
|
|
||||||
| Guid -> $"\"{AutoId.GenerateGuid()}\""
|
|
||||||
| RandomString -> $"\"{AutoId.GenerateRandomString(Configuration.idStringLength ())}\""
|
|
||||||
| Disabled -> "@data"
|
|
||||||
|> function it -> $"""@data::jsonb || ('{{"{idField}":{it}}}')::jsonb"""
|
|
||||||
else "@data"
|
|
||||||
(Query.insert tableName).Replace("@data", dataParam)
|
|
||||||
Custom.nonQuery query [ jsonParam "@data" document ] sqlProps
|
|
||||||
|
|
||||||
/// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
|
/// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
|
||||||
[<CompiledName "Save">]
|
[<CompiledName "Save">]
|
||||||
@@ -442,7 +411,7 @@ module WithProps =
|
|||||||
Custom.single (Query.byId (Query.find tableName) docId) [ 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 and 'TDoc: not struct>(tableName, docId: 'TKey, sqlProps) =
|
let ById<'TKey, 'TDoc when 'TDoc: null>(tableName, docId: 'TKey, sqlProps) =
|
||||||
Custom.Single<'TDoc>(
|
Custom.Single<'TDoc>(
|
||||||
Query.byId (Query.find tableName) docId, [ idParam docId ], fromData<'TDoc>, sqlProps)
|
Query.byId (Query.find tableName) docId, [ idParam docId ], fromData<'TDoc>, sqlProps)
|
||||||
|
|
||||||
@@ -552,7 +521,7 @@ module WithProps =
|
|||||||
sqlProps
|
sqlProps
|
||||||
|
|
||||||
/// Retrieve the first document matching JSON field comparisons (->> =); returns null if not found
|
/// Retrieve the first document matching JSON field comparisons (->> =); returns null if not found
|
||||||
let FirstByFields<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, howMatched, fields, sqlProps) =
|
let FirstByFields<'TDoc when 'TDoc: null>(tableName, howMatched, fields, sqlProps) =
|
||||||
Custom.Single<'TDoc>(
|
Custom.Single<'TDoc>(
|
||||||
$"{Query.byFields (Query.find tableName) howMatched fields} LIMIT 1",
|
$"{Query.byFields (Query.find tableName) howMatched fields} LIMIT 1",
|
||||||
addFieldParams fields [],
|
addFieldParams fields [],
|
||||||
@@ -571,8 +540,7 @@ module WithProps =
|
|||||||
|
|
||||||
/// Retrieve the first document matching JSON field comparisons (->> =) ordered by the given fields in the
|
/// Retrieve the first document matching JSON field comparisons (->> =) ordered by the given fields in the
|
||||||
/// document; returns null if not found
|
/// document; returns null if not found
|
||||||
let FirstByFieldsOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>(
|
let FirstByFieldsOrdered<'TDoc when 'TDoc: null>(tableName, howMatched, queryFields, orderFields, sqlProps) =
|
||||||
tableName, howMatched, queryFields, orderFields, sqlProps) =
|
|
||||||
Custom.Single<'TDoc>(
|
Custom.Single<'TDoc>(
|
||||||
$"{Query.byFields (Query.find tableName) howMatched queryFields}{Query.orderBy orderFields PostgreSQL} LIMIT 1",
|
$"{Query.byFields (Query.find tableName) howMatched queryFields}{Query.orderBy orderFields PostgreSQL} LIMIT 1",
|
||||||
addFieldParams queryFields [],
|
addFieldParams queryFields [],
|
||||||
@@ -589,7 +557,7 @@ module WithProps =
|
|||||||
sqlProps
|
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 and 'TDoc: not struct>(tableName, criteria: obj, sqlProps) =
|
let FirstByContains<'TDoc when 'TDoc: null>(tableName, criteria: obj, sqlProps) =
|
||||||
Custom.Single<'TDoc>(
|
Custom.Single<'TDoc>(
|
||||||
$"{Query.byContains (Query.find tableName)} LIMIT 1",
|
$"{Query.byContains (Query.find tableName)} LIMIT 1",
|
||||||
[ jsonParam "@criteria" criteria ],
|
[ jsonParam "@criteria" criteria ],
|
||||||
@@ -608,8 +576,7 @@ module WithProps =
|
|||||||
|
|
||||||
/// Retrieve the first document matching a JSON containment query (@>) ordered by the given fields in the
|
/// Retrieve the first document matching a JSON containment query (@>) ordered by the given fields in the
|
||||||
/// document; returns null if not found
|
/// document; returns null if not found
|
||||||
let FirstByContainsOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>(
|
let FirstByContainsOrdered<'TDoc when 'TDoc: null>(tableName, criteria: obj, orderFields, sqlProps) =
|
||||||
tableName, criteria: obj, orderFields, sqlProps) =
|
|
||||||
Custom.Single<'TDoc>(
|
Custom.Single<'TDoc>(
|
||||||
$"{Query.byContains (Query.find tableName)}{Query.orderBy orderFields PostgreSQL} LIMIT 1",
|
$"{Query.byContains (Query.find tableName)}{Query.orderBy orderFields PostgreSQL} LIMIT 1",
|
||||||
[ jsonParam "@criteria" criteria ],
|
[ jsonParam "@criteria" criteria ],
|
||||||
@@ -626,7 +593,7 @@ module WithProps =
|
|||||||
sqlProps
|
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 and 'TDoc: not struct>(tableName, jsonPath, sqlProps) =
|
let FirstByJsonPath<'TDoc when 'TDoc: null>(tableName, jsonPath, sqlProps) =
|
||||||
Custom.Single<'TDoc>(
|
Custom.Single<'TDoc>(
|
||||||
$"{Query.byPathMatch (Query.find tableName)} LIMIT 1",
|
$"{Query.byPathMatch (Query.find tableName)} LIMIT 1",
|
||||||
[ "@path", Sql.string jsonPath ],
|
[ "@path", Sql.string jsonPath ],
|
||||||
@@ -645,8 +612,7 @@ module WithProps =
|
|||||||
|
|
||||||
/// Retrieve the first document matching a JSON Path match query (@?) ordered by the given fields in the
|
/// Retrieve the first document matching a JSON Path match query (@?) ordered by the given fields in the
|
||||||
/// document; returns null if not found
|
/// document; returns null if not found
|
||||||
let FirstByJsonPathOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>(
|
let FirstByJsonPathOrdered<'TDoc when 'TDoc: null>(tableName, jsonPath, orderFields, sqlProps) =
|
||||||
tableName, jsonPath, orderFields, sqlProps) =
|
|
||||||
Custom.Single<'TDoc>(
|
Custom.Single<'TDoc>(
|
||||||
$"{Query.byPathMatch (Query.find tableName)}{Query.orderBy orderFields PostgreSQL} LIMIT 1",
|
$"{Query.byPathMatch (Query.find tableName)}{Query.orderBy orderFields PostgreSQL} LIMIT 1",
|
||||||
[ "@path", Sql.string jsonPath ],
|
[ "@path", Sql.string jsonPath ],
|
||||||
@@ -785,8 +751,7 @@ module Custom =
|
|||||||
WithProps.Custom.single<'TDoc> query parameters mapFunc (fromDataSource ())
|
WithProps.Custom.single<'TDoc> query parameters mapFunc (fromDataSource ())
|
||||||
|
|
||||||
/// 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 and 'TDoc: not struct>(
|
let Single<'TDoc when 'TDoc: null>(query, parameters, mapFunc: System.Func<RowReader, 'TDoc>) =
|
||||||
query, parameters, mapFunc: System.Func<RowReader, 'TDoc>) =
|
|
||||||
WithProps.Custom.Single<'TDoc>(query, parameters, mapFunc, fromDataSource ())
|
WithProps.Custom.Single<'TDoc>(query, parameters, mapFunc, fromDataSource ())
|
||||||
|
|
||||||
/// Execute a query that returns no results
|
/// Execute a query that returns no results
|
||||||
@@ -917,7 +882,7 @@ module Find =
|
|||||||
WithProps.Find.byId<'TKey, 'TDoc> tableName docId (fromDataSource ())
|
WithProps.Find.byId<'TKey, 'TDoc> tableName docId (fromDataSource ())
|
||||||
|
|
||||||
/// 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 and 'TDoc: not struct>(tableName, docId: 'TKey) =
|
let ById<'TKey, 'TDoc when 'TDoc: null>(tableName, docId: 'TKey) =
|
||||||
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 (->> =)
|
||||||
@@ -980,7 +945,7 @@ module Find =
|
|||||||
WithProps.Find.firstByFields<'TDoc> tableName howMatched fields (fromDataSource ())
|
WithProps.Find.firstByFields<'TDoc> tableName howMatched fields (fromDataSource ())
|
||||||
|
|
||||||
/// 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 and 'TDoc: not struct>(tableName, howMatched, fields) =
|
let FirstByFields<'TDoc when 'TDoc: null>(tableName, howMatched, fields) =
|
||||||
WithProps.Find.FirstByFields<'TDoc>(tableName, howMatched, fields, fromDataSource ())
|
WithProps.Find.FirstByFields<'TDoc>(tableName, howMatched, fields, fromDataSource ())
|
||||||
|
|
||||||
/// Retrieve the first document matching a JSON field comparison query (->> =) ordered by the given fields in the
|
/// Retrieve the first document matching a JSON field comparison query (->> =) ordered by the given fields in the
|
||||||
@@ -991,8 +956,7 @@ module Find =
|
|||||||
|
|
||||||
/// Retrieve the first document matching a JSON field comparison query (->> =) ordered by the given fields in the
|
/// Retrieve the first document matching a JSON field comparison query (->> =) ordered by the given fields in the
|
||||||
/// document; returns null if not found
|
/// document; returns null if not found
|
||||||
let FirstByFieldsOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>(
|
let FirstByFieldsOrdered<'TDoc when 'TDoc: null>(tableName, howMatched, queryFields, orderFields) =
|
||||||
tableName, howMatched, queryFields, orderFields) =
|
|
||||||
WithProps.Find.FirstByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, fromDataSource ())
|
WithProps.Find.FirstByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, fromDataSource ())
|
||||||
|
|
||||||
/// 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
|
||||||
@@ -1001,7 +965,7 @@ module Find =
|
|||||||
WithProps.Find.firstByContains<'TDoc> tableName criteria (fromDataSource ())
|
WithProps.Find.firstByContains<'TDoc> tableName criteria (fromDataSource ())
|
||||||
|
|
||||||
/// 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 and 'TDoc: not struct>(tableName, criteria: obj) =
|
let FirstByContains<'TDoc when 'TDoc: null>(tableName, criteria: obj) =
|
||||||
WithProps.Find.FirstByContains<'TDoc>(tableName, criteria, fromDataSource ())
|
WithProps.Find.FirstByContains<'TDoc>(tableName, criteria, fromDataSource ())
|
||||||
|
|
||||||
/// Retrieve the first document matching a JSON containment query (@>) ordered by the given fields in the document;
|
/// Retrieve the first document matching a JSON containment query (@>) ordered by the given fields in the document;
|
||||||
@@ -1012,7 +976,7 @@ module Find =
|
|||||||
|
|
||||||
/// Retrieve the first document matching a JSON containment query (@>) ordered by the given fields in the document;
|
/// Retrieve the first document matching a JSON containment query (@>) ordered by the given fields in the document;
|
||||||
/// returns null if not found
|
/// returns null if not found
|
||||||
let FirstByContainsOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, criteria: obj, orderFields) =
|
let FirstByContainsOrdered<'TDoc when 'TDoc: null>(tableName, criteria: obj, orderFields) =
|
||||||
WithProps.Find.FirstByContainsOrdered<'TDoc>(tableName, criteria, orderFields, fromDataSource ())
|
WithProps.Find.FirstByContainsOrdered<'TDoc>(tableName, criteria, orderFields, fromDataSource ())
|
||||||
|
|
||||||
/// Retrieve the first document matching a JSON Path match query (@?); returns None if not found
|
/// Retrieve the first document matching a JSON Path match query (@?); returns None if not found
|
||||||
@@ -1021,7 +985,7 @@ module Find =
|
|||||||
WithProps.Find.firstByJsonPath<'TDoc> tableName jsonPath (fromDataSource ())
|
WithProps.Find.firstByJsonPath<'TDoc> tableName jsonPath (fromDataSource ())
|
||||||
|
|
||||||
/// 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 and 'TDoc: not struct>(tableName, jsonPath) =
|
let FirstByJsonPath<'TDoc when 'TDoc: null>(tableName, jsonPath) =
|
||||||
WithProps.Find.FirstByJsonPath<'TDoc>(tableName, jsonPath, fromDataSource ())
|
WithProps.Find.FirstByJsonPath<'TDoc>(tableName, jsonPath, fromDataSource ())
|
||||||
|
|
||||||
/// Retrieve the first document matching a JSON Path match query (@?) ordered by the given fields in the document;
|
/// Retrieve the first document matching a JSON Path match query (@?) ordered by the given fields in the document;
|
||||||
@@ -1032,7 +996,7 @@ module Find =
|
|||||||
|
|
||||||
/// Retrieve the first document matching a JSON Path match query (@?) ordered by the given fields in the document;
|
/// Retrieve the first document matching a JSON Path match query (@?) ordered by the given fields in the document;
|
||||||
/// returns null if not found
|
/// returns null if not found
|
||||||
let FirstByJsonPathOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, jsonPath, orderFields) =
|
let FirstByJsonPathOrdered<'TDoc when 'TDoc: null>(tableName, jsonPath, orderFields) =
|
||||||
WithProps.Find.FirstByJsonPathOrdered<'TDoc>(tableName, jsonPath, orderFields, fromDataSource ())
|
WithProps.Find.FirstByJsonPathOrdered<'TDoc>(tableName, jsonPath, orderFields, fromDataSource ())
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,16 +5,11 @@ This package provides a lightweight document library backed by [PostgreSQL](http
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Select, insert, update, save (upsert), delete, count, and check existence of documents, and create tables and indexes for these documents
|
- Select, insert, update, save (upsert), delete, count, and check existence of documents, and create tables and indexes for these documents
|
||||||
- Automatically generate IDs for documents (numeric IDs, GUIDs, or random strings)
|
|
||||||
- Address documents via ID, via comparison on any field, via equality on any property (using JSON containment, on a likely indexed field), or via condition on any property (using JSON Path queries)
|
- Address documents via ID, via comparison on any field, via equality on any property (using JSON containment, on a likely indexed field), or via condition on any property (using JSON Path queries)
|
||||||
- Access documents as your domain models (<abbr title="Plain Old CLR Objects">POCO</abbr>s)
|
- Access documents as your domain models (<abbr title="Plain Old CLR Objects">POCO</abbr>s)
|
||||||
- Use `Task`-based async for all data access functions
|
- Use `Task`-based async for all data access functions
|
||||||
- Use building blocks for more complex queries
|
- Use building blocks for more complex queries
|
||||||
|
|
||||||
## Upgrading from v3
|
|
||||||
|
|
||||||
There is a breaking API change for `ByField` (C#) / `byField` (F#), along with a compatibility namespace that can mitigate the impact of these changes. See [the migration guide](https://bitbadger.solutions/open-source/relational-documents/upgrade-from-v3-to-v4.html) for full details.
|
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
Once the package is installed, the library needs a data source. Construct an `NpgsqlDataSource` instance, and provide it to the library:
|
Once the package is installed, the library needs a data source. Construct an `NpgsqlDataSource` instance, and provide it to the library:
|
||||||
@@ -71,7 +66,7 @@ var customer = await Find.ById<string, Customer>("customer", "123");
|
|||||||
// Find.byId type signature is string -> 'TKey -> Task<'TDoc option>
|
// Find.byId type signature is string -> 'TKey -> Task<'TDoc option>
|
||||||
let! customer = Find.byId<string, Customer> "customer" "123"
|
let! customer = Find.byId<string, Customer> "customer" "123"
|
||||||
```
|
```
|
||||||
_(keys are treated as strings or numbers depending on their defintion; however, they are indexed as strings)_
|
_(keys are treated as strings in the database)_
|
||||||
|
|
||||||
Count customers in Atlanta (using JSON containment):
|
Count customers in Atlanta (using JSON containment):
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.6" />
|
||||||
<PackageReference Update="FSharp.Core" Version="9.0.100" />
|
<PackageReference Update="FSharp.Core" Version="8.0.300" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ module WithConn =
|
|||||||
|
|
||||||
/// Retrieve the first document matching a JSON field comparison (->> =); returns null if not found
|
/// Retrieve the first document matching a JSON field comparison (->> =); returns null if not found
|
||||||
[<System.Obsolete "Use FirstByFields instead ~ will be removed in v4.1">]
|
[<System.Obsolete "Use FirstByFields instead ~ will be removed in v4.1">]
|
||||||
let FirstByField<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, field, conn) =
|
let FirstByField<'TDoc when 'TDoc: null>(tableName, field, conn) =
|
||||||
WithConn.Find.FirstByFields<'TDoc>(tableName, Any, Seq.singleton field, conn)
|
WithConn.Find.FirstByFields<'TDoc>(tableName, Any, Seq.singleton field, conn)
|
||||||
|
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
@@ -144,7 +144,7 @@ module Find =
|
|||||||
|
|
||||||
/// Retrieve the first document matching a JSON field comparison (->> =); returns null if not found
|
/// Retrieve the first document matching a JSON field comparison (->> =); returns null if not found
|
||||||
[<System.Obsolete "Use FirstByFields instead ~ will be removed in v4.1">]
|
[<System.Obsolete "Use FirstByFields instead ~ will be removed in v4.1">]
|
||||||
let FirstByField<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, field) =
|
let FirstByField<'TDoc when 'TDoc: null>(tableName, field) =
|
||||||
Find.FirstByFields<'TDoc>(tableName, Any, Seq.singleton field)
|
Find.FirstByFields<'TDoc>(tableName, Any, Seq.singleton field)
|
||||||
|
|
||||||
|
|
||||||
@@ -247,7 +247,7 @@ type SqliteConnectionCSharpCompatExtensions =
|
|||||||
/// 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
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
[<System.Obsolete "Use FindFirstByFields instead ~ will be removed in v4.1">]
|
[<System.Obsolete "Use FindFirstByFields instead ~ will be removed in v4.1">]
|
||||||
static member inline FindFirstByField<'TDoc when 'TDoc: null and 'TDoc: not struct>(conn, tableName, field) =
|
static member inline FindFirstByField<'TDoc when 'TDoc: null>(conn, tableName, field) =
|
||||||
WithConn.Find.FirstByFields<'TDoc>(tableName, Any, [ field ], conn)
|
WithConn.Find.FirstByFields<'TDoc>(tableName, Any, [ field ], 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 (->> =)
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -34,11 +35,11 @@ module Extensions =
|
|||||||
|
|
||||||
/// Insert a new document
|
/// Insert a new document
|
||||||
member conn.insert<'TDoc> tableName (document: 'TDoc) =
|
member conn.insert<'TDoc> tableName (document: 'TDoc) =
|
||||||
WithConn.Document.insert<'TDoc> tableName document conn
|
WithConn.insert<'TDoc> tableName document conn
|
||||||
|
|
||||||
/// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
|
/// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
|
||||||
member conn.save<'TDoc> tableName (document: 'TDoc) =
|
member conn.save<'TDoc> tableName (document: 'TDoc) =
|
||||||
WithConn.Document.save tableName document conn
|
WithConn.save tableName document conn
|
||||||
|
|
||||||
/// Count all documents in a table
|
/// Count all documents in a table
|
||||||
member conn.countAll tableName =
|
member conn.countAll tableName =
|
||||||
@@ -130,7 +131,7 @@ type SqliteConnectionCSharpExtensions =
|
|||||||
|
|
||||||
/// Execute a query that returns one or no results
|
/// Execute a query that returns one or no results
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline CustomSingle<'TDoc when 'TDoc: null and 'TDoc: not struct>(
|
static member inline CustomSingle<'TDoc when 'TDoc: null>(
|
||||||
conn, query, parameters, mapFunc: System.Func<SqliteDataReader, 'TDoc>) =
|
conn, query, parameters, mapFunc: System.Func<SqliteDataReader, 'TDoc>) =
|
||||||
WithConn.Custom.Single<'TDoc>(query, parameters, mapFunc, conn)
|
WithConn.Custom.Single<'TDoc>(query, parameters, mapFunc, conn)
|
||||||
|
|
||||||
@@ -158,12 +159,12 @@ type SqliteConnectionCSharpExtensions =
|
|||||||
/// Insert a new document
|
/// Insert a new document
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline Insert<'TDoc>(conn, tableName, document: 'TDoc) =
|
static member inline Insert<'TDoc>(conn, tableName, document: 'TDoc) =
|
||||||
WithConn.Document.insert<'TDoc> tableName document conn
|
WithConn.insert<'TDoc> tableName document conn
|
||||||
|
|
||||||
/// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
|
/// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline Save<'TDoc>(conn, tableName, document: 'TDoc) =
|
static member inline Save<'TDoc>(conn, tableName, document: 'TDoc) =
|
||||||
WithConn.Document.save<'TDoc> tableName document conn
|
WithConn.save<'TDoc> tableName document conn
|
||||||
|
|
||||||
/// Count all documents in a table
|
/// Count all documents in a table
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
@@ -197,7 +198,7 @@ type SqliteConnectionCSharpExtensions =
|
|||||||
|
|
||||||
/// Retrieve a document by its ID
|
/// Retrieve a document by its ID
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline FindById<'TKey, 'TDoc when 'TDoc: null and 'TDoc: not struct>(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
|
/// Retrieve documents via a comparison on JSON fields
|
||||||
@@ -212,14 +213,13 @@ type SqliteConnectionCSharpExtensions =
|
|||||||
|
|
||||||
/// Retrieve documents via a comparison on JSON fields, returning only the first result
|
/// Retrieve documents via a comparison on JSON fields, returning only the first result
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline FindFirstByFields<'TDoc when 'TDoc: null and 'TDoc: not struct>(
|
static member inline FindFirstByFields<'TDoc when 'TDoc: null>(conn, tableName, howMatched, fields) =
|
||||||
conn, tableName, howMatched, fields) =
|
|
||||||
WithConn.Find.FirstByFields<'TDoc>(tableName, howMatched, fields, conn)
|
WithConn.Find.FirstByFields<'TDoc>(tableName, howMatched, fields, conn)
|
||||||
|
|
||||||
/// Retrieve documents via a comparison on JSON fields ordered by the given fields in the document, returning only
|
/// Retrieve documents via a comparison on JSON fields ordered by the given fields in the document, returning only
|
||||||
/// the first result
|
/// the first result
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline FindFirstByFieldsOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>(
|
static member inline FindFirstByFieldsOrdered<'TDoc when 'TDoc: null>(
|
||||||
conn, tableName, howMatched, queryFields, orderFields) =
|
conn, tableName, howMatched, queryFields, orderFields) =
|
||||||
WithConn.Find.FirstByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, conn)
|
WithConn.Find.FirstByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, conn)
|
||||||
|
|
||||||
@@ -262,3 +262,9 @@ type SqliteConnectionCSharpExtensions =
|
|||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline DeleteByFields(conn, tableName, howMatched, fields) =
|
static member inline DeleteByFields(conn, tableName, howMatched, fields) =
|
||||||
WithConn.Delete.byFields tableName howMatched fields conn
|
WithConn.Delete.byFields tableName howMatched fields conn
|
||||||
|
|
||||||
|
/// Delete documents by matching a comparison on a JSON field
|
||||||
|
[<Extension>]
|
||||||
|
[<System.Obsolete "Use DeleteByFields instead; will be removed in v4">]
|
||||||
|
static member inline DeleteByField(conn, tableName, field) =
|
||||||
|
conn.DeleteByFields(tableName, Any, [ field ])
|
||||||
|
|||||||
@@ -37,26 +37,18 @@ module Query =
|
|||||||
let name = ParameterName()
|
let name = ParameterName()
|
||||||
fields
|
fields
|
||||||
|> Seq.map (fun it ->
|
|> Seq.map (fun it ->
|
||||||
match it.Comparison with
|
match it.Op with
|
||||||
| Exists | NotExists -> $"{it.Path SQLite AsSql} {it.Comparison.OpSql}"
|
| EX | NEX -> $"{it.Path SQLite} {it.Op}"
|
||||||
| Between _ ->
|
| BT ->
|
||||||
let p = name.Derive it.ParameterName
|
let p = name.Derive it.ParameterName
|
||||||
$"{it.Path SQLite AsSql} {it.Comparison.OpSql} {p}min AND {p}max"
|
$"{it.Path SQLite} {it.Op} {p}min AND {p}max"
|
||||||
| In values ->
|
| _ -> $"{it.Path SQLite} {it.Op} {name.Derive it.ParameterName}")
|
||||||
let p = name.Derive it.ParameterName
|
|
||||||
let paramNames = values |> Seq.mapi (fun idx _ -> $"{p}_{idx}") |> String.concat ", "
|
|
||||||
$"{it.Path SQLite AsSql} {it.Comparison.OpSql} ({paramNames})"
|
|
||||||
| InArray (table, values) ->
|
|
||||||
let p = name.Derive it.ParameterName
|
|
||||||
let paramNames = values |> Seq.mapi (fun idx _ -> $"{p}_{idx}") |> String.concat ", "
|
|
||||||
$"EXISTS (SELECT 1 FROM json_each({table}.data, '$.{it.Name}') WHERE value IN ({paramNames}))"
|
|
||||||
| _ -> $"{it.Path SQLite AsSql} {it.Comparison.OpSql} {name.Derive it.ParameterName}")
|
|
||||||
|> String.concat $" {howMatched} "
|
|> String.concat $" {howMatched} "
|
||||||
|
|
||||||
/// Create a WHERE clause fragment to implement an ID-based query
|
/// Create a WHERE clause fragment to implement an ID-based query
|
||||||
[<CompiledName "WhereById">]
|
[<CompiledName "WhereById">]
|
||||||
let whereById paramName =
|
let whereById paramName =
|
||||||
whereByFields Any [ { Field.Equal (Configuration.idField ()) 0 with ParameterName = Some paramName } ]
|
whereByFields Any [ { Field.EQ (Configuration.idField ()) 0 with ParameterName = Some paramName } ]
|
||||||
|
|
||||||
/// Create an UPDATE statement to patch documents
|
/// Create an UPDATE statement to patch documents
|
||||||
[<CompiledName "Patch">]
|
[<CompiledName "Patch">]
|
||||||
@@ -74,7 +66,7 @@ module Query =
|
|||||||
let byId<'TKey> statement (docId: 'TKey) =
|
let byId<'TKey> statement (docId: 'TKey) =
|
||||||
Query.statementWhere
|
Query.statementWhere
|
||||||
statement
|
statement
|
||||||
(whereByFields Any [ { Field.Equal (Configuration.idField ()) docId with ParameterName = Some "@id" } ])
|
(whereByFields Any [ { Field.EQ (Configuration.idField ()) docId with ParameterName = Some "@id" } ])
|
||||||
|
|
||||||
/// Create a query on JSON fields
|
/// Create a query on JSON fields
|
||||||
[<CompiledName "ByFields">]
|
[<CompiledName "ByFields">]
|
||||||
@@ -111,16 +103,14 @@ module Parameters =
|
|||||||
fields
|
fields
|
||||||
|> Seq.map (fun it ->
|
|> Seq.map (fun it ->
|
||||||
seq {
|
seq {
|
||||||
match it.Comparison with
|
match it.Op with
|
||||||
| Exists | NotExists -> ()
|
| EX | NEX -> ()
|
||||||
| Between (min, max) ->
|
| BT ->
|
||||||
let p = name.Derive it.ParameterName
|
let p = name.Derive it.ParameterName
|
||||||
yield! [ SqliteParameter($"{p}min", min); SqliteParameter($"{p}max", max) ]
|
let values = it.Value :?> obj list
|
||||||
| In values | InArray (_, values) ->
|
yield SqliteParameter($"{p}min", List.head values)
|
||||||
let p = name.Derive it.ParameterName
|
yield SqliteParameter($"{p}max", List.last values)
|
||||||
yield! values |> Seq.mapi (fun idx v -> SqliteParameter($"{p}_{idx}", v))
|
| _ -> yield SqliteParameter(name.Derive it.ParameterName, it.Value) })
|
||||||
| Equal v | Greater v | GreaterOrEqual v | Less v | LessOrEqual v | NotEqual v ->
|
|
||||||
yield SqliteParameter(name.Derive it.ParameterName, v) })
|
|
||||||
|> Seq.collect id
|
|> Seq.collect id
|
||||||
|> Seq.append parameters
|
|> Seq.append parameters
|
||||||
|> Seq.toList
|
|> Seq.toList
|
||||||
@@ -153,7 +143,7 @@ module Results =
|
|||||||
/// Create a domain item from a document, specifying the field in which the document is found
|
/// Create a domain item from a document, specifying the field in which the document is found
|
||||||
[<CompiledName "FromDocument">]
|
[<CompiledName "FromDocument">]
|
||||||
let fromDocument<'TDoc> field (rdr: SqliteDataReader) : 'TDoc =
|
let fromDocument<'TDoc> field (rdr: SqliteDataReader) : 'TDoc =
|
||||||
Configuration.serializer().Deserialize<'TDoc>(rdr.GetString(rdr.GetOrdinal field))
|
Configuration.serializer().Deserialize<'TDoc>(rdr.GetString(rdr.GetOrdinal(field)))
|
||||||
|
|
||||||
/// Create a domain item from a document
|
/// Create a domain item from a document
|
||||||
[<CompiledName "FromData">]
|
[<CompiledName "FromData">]
|
||||||
@@ -221,7 +211,7 @@ module WithConn =
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 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 and 'TDoc: not struct>(
|
let Single<'TDoc when 'TDoc: null>(
|
||||||
query, parameters, mapFunc: System.Func<SqliteDataReader, 'TDoc>, conn
|
query, parameters, mapFunc: System.Func<SqliteDataReader, 'TDoc>, conn
|
||||||
) = backgroundTask {
|
) = backgroundTask {
|
||||||
let! result = single<'TDoc> query parameters mapFunc.Invoke conn
|
let! result = single<'TDoc> query parameters mapFunc.Invoke conn
|
||||||
@@ -268,29 +258,10 @@ module WithConn =
|
|||||||
let ensureFieldIndex tableName indexName fields conn =
|
let ensureFieldIndex tableName indexName fields conn =
|
||||||
Custom.nonQuery (Query.Definition.ensureIndexOn tableName indexName fields SQLite) [] conn
|
Custom.nonQuery (Query.Definition.ensureIndexOn tableName indexName fields SQLite) [] conn
|
||||||
|
|
||||||
/// Commands to add documents
|
|
||||||
[<AutoOpen>]
|
|
||||||
module Document =
|
|
||||||
|
|
||||||
/// Insert a new document
|
/// Insert a new document
|
||||||
[<CompiledName "Insert">]
|
[<CompiledName "Insert">]
|
||||||
let insert<'TDoc> tableName (document: 'TDoc) conn =
|
let insert<'TDoc> tableName (document: 'TDoc) conn =
|
||||||
let query =
|
Custom.nonQuery (Query.insert tableName) [ jsonParam "@data" document ] conn
|
||||||
match Configuration.autoIdStrategy () with
|
|
||||||
| Disabled -> Query.insert tableName
|
|
||||||
| strategy ->
|
|
||||||
let idField = Configuration.idField ()
|
|
||||||
let dataParam =
|
|
||||||
if AutoId.NeedsAutoId strategy document idField then
|
|
||||||
match strategy with
|
|
||||||
| Number -> $"(SELECT coalesce(max(data->>'{idField}'), 0) + 1 FROM {tableName})"
|
|
||||||
| Guid -> $"'{AutoId.GenerateGuid()}'"
|
|
||||||
| RandomString -> $"'{AutoId.GenerateRandomString(Configuration.idStringLength ())}'"
|
|
||||||
| Disabled -> "@data"
|
|
||||||
|> function it -> $"json_set(@data, '$.{idField}', {it})"
|
|
||||||
else "@data"
|
|
||||||
(Query.insert tableName).Replace("@data", dataParam)
|
|
||||||
Custom.nonQuery query [ jsonParam "@data" document ] conn
|
|
||||||
|
|
||||||
/// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
|
/// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
|
||||||
[<CompiledName "Save">]
|
[<CompiledName "Save">]
|
||||||
@@ -358,7 +329,7 @@ module WithConn =
|
|||||||
Custom.single<'TDoc> (Query.byId (Query.find tableName) docId) [ 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 and 'TDoc: not struct>(tableName, docId: 'TKey, conn) =
|
let ById<'TKey, 'TDoc when 'TDoc: null>(tableName, docId: 'TKey, conn) =
|
||||||
Custom.Single<'TDoc>(Query.byId (Query.find tableName) docId, [ 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
|
/// Retrieve documents via a comparison on JSON fields
|
||||||
@@ -405,7 +376,7 @@ module WithConn =
|
|||||||
conn
|
conn
|
||||||
|
|
||||||
/// Retrieve documents via a comparison on JSON fields, returning only the first result
|
/// Retrieve documents via a comparison on JSON fields, returning only the first result
|
||||||
let FirstByFields<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, howMatched, fields, conn) =
|
let FirstByFields<'TDoc when 'TDoc: null>(tableName, howMatched, fields, conn) =
|
||||||
Custom.Single(
|
Custom.Single(
|
||||||
$"{Query.byFields (Query.find tableName) howMatched fields} LIMIT 1",
|
$"{Query.byFields (Query.find tableName) howMatched fields} LIMIT 1",
|
||||||
addFieldParams fields [],
|
addFieldParams fields [],
|
||||||
@@ -424,8 +395,7 @@ module WithConn =
|
|||||||
|
|
||||||
/// Retrieve documents via a comparison on JSON fields ordered by the given fields in the document, returning
|
/// Retrieve documents via a comparison on JSON fields ordered by the given fields in the document, returning
|
||||||
/// only the first result
|
/// only the first result
|
||||||
let FirstByFieldsOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>(
|
let FirstByFieldsOrdered<'TDoc when 'TDoc: null>(tableName, howMatched, queryFields, orderFields, conn) =
|
||||||
tableName, howMatched, queryFields, orderFields, conn) =
|
|
||||||
Custom.Single(
|
Custom.Single(
|
||||||
$"{Query.byFields (Query.find tableName) howMatched queryFields}{Query.orderBy orderFields SQLite} LIMIT 1",
|
$"{Query.byFields (Query.find tableName) howMatched queryFields}{Query.orderBy orderFields SQLite} LIMIT 1",
|
||||||
addFieldParams queryFields [],
|
addFieldParams queryFields [],
|
||||||
@@ -530,8 +500,7 @@ module Custom =
|
|||||||
WithConn.Custom.single<'TDoc> query parameters mapFunc conn
|
WithConn.Custom.single<'TDoc> query parameters mapFunc conn
|
||||||
|
|
||||||
/// Execute a query that returns one or no results (returns null if not found)
|
/// Execute a query that returns one or no results (returns null if not found)
|
||||||
let Single<'TDoc when 'TDoc: null and 'TDoc: not struct>(
|
let Single<'TDoc when 'TDoc: null>(query, parameters, mapFunc: System.Func<SqliteDataReader, 'TDoc>) =
|
||||||
query, parameters, mapFunc: System.Func<SqliteDataReader, 'TDoc>) =
|
|
||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
WithConn.Custom.Single<'TDoc>(query, parameters, mapFunc, conn)
|
WithConn.Custom.Single<'TDoc>(query, parameters, mapFunc, conn)
|
||||||
|
|
||||||
@@ -578,13 +547,13 @@ module Document =
|
|||||||
[<CompiledName "Insert">]
|
[<CompiledName "Insert">]
|
||||||
let insert<'TDoc> tableName (document: 'TDoc) =
|
let insert<'TDoc> tableName (document: 'TDoc) =
|
||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
WithConn.Document.insert tableName document conn
|
WithConn.insert tableName document conn
|
||||||
|
|
||||||
/// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
|
/// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
|
||||||
[<CompiledName "Save">]
|
[<CompiledName "Save">]
|
||||||
let save<'TDoc> tableName (document: 'TDoc) =
|
let save<'TDoc> tableName (document: 'TDoc) =
|
||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
WithConn.Document.save tableName document conn
|
WithConn.save tableName document conn
|
||||||
|
|
||||||
|
|
||||||
/// Commands to count documents
|
/// Commands to count documents
|
||||||
@@ -654,7 +623,7 @@ module Find =
|
|||||||
WithConn.Find.byId<'TKey, 'TDoc> tableName docId conn
|
WithConn.Find.byId<'TKey, 'TDoc> tableName docId conn
|
||||||
|
|
||||||
/// Retrieve a document by its ID (returns null if not found)
|
/// Retrieve a document by its ID (returns null if not found)
|
||||||
let ById<'TKey, 'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, docId) =
|
let ById<'TKey, 'TDoc when 'TDoc: null>(tableName, docId) =
|
||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
WithConn.Find.ById<'TKey, 'TDoc>(tableName, docId, conn)
|
WithConn.Find.ById<'TKey, 'TDoc>(tableName, docId, conn)
|
||||||
|
|
||||||
@@ -687,7 +656,7 @@ module Find =
|
|||||||
WithConn.Find.firstByFields<'TDoc> tableName howMatched fields conn
|
WithConn.Find.firstByFields<'TDoc> tableName howMatched fields conn
|
||||||
|
|
||||||
/// Retrieve documents via a comparison on JSON fields, returning only the first result
|
/// Retrieve documents via a comparison on JSON fields, returning only the first result
|
||||||
let FirstByFields<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, howMatched, fields) =
|
let FirstByFields<'TDoc when 'TDoc: null>(tableName, howMatched, fields) =
|
||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
WithConn.Find.FirstByFields<'TDoc>(tableName, howMatched, fields, conn)
|
WithConn.Find.FirstByFields<'TDoc>(tableName, howMatched, fields, conn)
|
||||||
|
|
||||||
@@ -700,8 +669,7 @@ module Find =
|
|||||||
|
|
||||||
/// Retrieve documents via a comparison on JSON fields ordered by the given fields in the document, returning only
|
/// Retrieve documents via a comparison on JSON fields ordered by the given fields in the document, returning only
|
||||||
/// the first result
|
/// the first result
|
||||||
let FirstByFieldsOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>(
|
let FirstByFieldsOrdered<'TDoc when 'TDoc: null>(tableName, howMatched, queryFields, orderFields) =
|
||||||
tableName, howMatched, queryFields, orderFields) =
|
|
||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
WithConn.Find.FirstByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, conn)
|
WithConn.Find.FirstByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, conn)
|
||||||
|
|
||||||
|
|||||||
@@ -5,16 +5,11 @@ This package provides a lightweight document library backed by [SQLite](https://
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Select, insert, update, save (upsert), delete, count, and check existence of documents, and create tables and indexes for these documents
|
- Select, insert, update, save (upsert), delete, count, and check existence of documents, and create tables and indexes for these documents
|
||||||
- Automatically generate IDs for documents (numeric IDs, GUIDs, or random strings)
|
|
||||||
- Address documents via ID or via comparison on any field
|
- Address documents via ID or via comparison on any field
|
||||||
- Access documents as your domain models (<abbr title="Plain Old CLR Objects">POCO</abbr>s)
|
- Access documents as your domain models (<abbr title="Plain Old CLR Objects">POCO</abbr>s)
|
||||||
- Use `Task`-based async for all data access functions
|
- Use `Task`-based async for all data access functions
|
||||||
- Use building blocks for more complex queries
|
- Use building blocks for more complex queries
|
||||||
|
|
||||||
## Upgrading from v3
|
|
||||||
|
|
||||||
There is a breaking API change for `ByField` (C#) / `byField` (F#), along with a compatibility namespace that can mitigate the impact of these changes. See [the migration guide](https://bitbadger.solutions/open-source/relational-documents/upgrade-from-v3-to-v4.html) for full details.
|
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
Once the package is installed, the library needs a connection string. Once it has been obtained / constructed, provide it to the library:
|
Once the package is installed, the library needs a connection string. Once it has been obtained / constructed, provide it to the library:
|
||||||
@@ -77,28 +72,28 @@ Count customers in Atlanta:
|
|||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
// C#; parameters are table name, field, operator, and value
|
// C#; parameters are table name, field, operator, and value
|
||||||
// Count.ByFields type signature is Func<string, FieldMatch, IEnumerable<Field>, Task<long>>
|
// Count.ByField type signature is Func<string, Field, Task<long>>
|
||||||
var customerCount = await Count.ByFields("customer", FieldMatch.Any, [Field.Equal("City", "Atlanta")]);
|
var customerCount = await Count.ByField("customer", Field.EQ("City", "Atlanta"));
|
||||||
```
|
```
|
||||||
|
|
||||||
```fsharp
|
```fsharp
|
||||||
// F#
|
// F#
|
||||||
// Count.byFields type signature is string -> FieldMatch -> Field seq -> Task<int64>
|
// Count.byField type signature is string -> Field -> Task<int64>
|
||||||
let! customerCount = Count.byFields "customer" Any [ Field.Equal "City" "Atlanta" ]
|
let! customerCount = Count.byField "customer" (Field.EQ "City" "Atlanta")
|
||||||
```
|
```
|
||||||
|
|
||||||
Delete customers in Chicago: _(no offense, Second City; just an example...)_
|
Delete customers in Chicago: _(no offense, Second City; just an example...)_
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
// C#; parameters are same as above, except return is void
|
// C#; parameters are same as above, except return is void
|
||||||
// Delete.ByFields type signature is Func<string, FieldMatch, IEnumerable<Field>, Task>
|
// Delete.ByField type signature is Func<string, Field, Task>
|
||||||
await Delete.ByFields("customer", FieldMatch.Any, [Field.Equal("City", "Chicago")]);
|
await Delete.ByField("customer", Field.EQ("City", "Chicago"));
|
||||||
```
|
```
|
||||||
|
|
||||||
```fsharp
|
```fsharp
|
||||||
// F#
|
// F#
|
||||||
// Delete.byFields type signature is string -> FieldMatch -> Field seq -> Task<unit>
|
// Delete.byField type signature is string -> string -> Op -> obj -> Task<unit>
|
||||||
do! Delete.byFields "customer" Any [ Field.Equal "City" "Chicago" ]
|
do! Delete.byField "customer" (Field.EQ "City" "Chicago")
|
||||||
```
|
```
|
||||||
|
|
||||||
## More Information
|
## More Information
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Expecto.CSharp;
|
using Expecto.CSharp;
|
||||||
using Expecto;
|
using Expecto;
|
||||||
|
using Microsoft.FSharp.Collections;
|
||||||
using Microsoft.FSharp.Core;
|
using Microsoft.FSharp.Core;
|
||||||
|
|
||||||
namespace BitBadger.Documents.Tests.CSharp;
|
namespace BitBadger.Documents.Tests.CSharp;
|
||||||
@@ -21,407 +22,13 @@ internal class TestSerializer : IDocumentSerializer
|
|||||||
public static class CommonCSharpTests
|
public static class CommonCSharpTests
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unit tests for the OpSql property of the Comparison discriminated union
|
/// Unit tests
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly Test OpTests = TestList("Comparison.OpSql",
|
[Tests]
|
||||||
|
public static readonly Test Unit = TestList("Common.C# Unit",
|
||||||
[
|
[
|
||||||
TestCase("Equal succeeds", () =>
|
TestSequenced(
|
||||||
{
|
TestList("Configuration",
|
||||||
Expect.equal(Comparison.NewEqual("").OpSql, "=", "The Equals SQL was not correct");
|
|
||||||
}),
|
|
||||||
TestCase("Greater succeeds", () =>
|
|
||||||
{
|
|
||||||
Expect.equal(Comparison.NewGreater("").OpSql, ">", "The Greater SQL was not correct");
|
|
||||||
}),
|
|
||||||
TestCase("GreaterOrEqual succeeds", () =>
|
|
||||||
{
|
|
||||||
Expect.equal(Comparison.NewGreaterOrEqual("").OpSql, ">=", "The GreaterOrEqual SQL was not correct");
|
|
||||||
}),
|
|
||||||
TestCase("Less succeeds", () =>
|
|
||||||
{
|
|
||||||
Expect.equal(Comparison.NewLess("").OpSql, "<", "The Less SQL was not correct");
|
|
||||||
}),
|
|
||||||
TestCase("LessOrEqual succeeds", () =>
|
|
||||||
{
|
|
||||||
Expect.equal(Comparison.NewLessOrEqual("").OpSql, "<=", "The LessOrEqual SQL was not correct");
|
|
||||||
}),
|
|
||||||
TestCase("NotEqual succeeds", () =>
|
|
||||||
{
|
|
||||||
Expect.equal(Comparison.NewNotEqual("").OpSql, "<>", "The NotEqual SQL was not correct");
|
|
||||||
}),
|
|
||||||
TestCase("Between succeeds", () =>
|
|
||||||
{
|
|
||||||
Expect.equal(Comparison.NewBetween("", "").OpSql, "BETWEEN", "The Between SQL was not correct");
|
|
||||||
}),
|
|
||||||
TestCase("In succeeds", () =>
|
|
||||||
{
|
|
||||||
Expect.equal(Comparison.NewIn([]).OpSql, "IN", "The In SQL was not correct");
|
|
||||||
}),
|
|
||||||
TestCase("InArray succeeds", () =>
|
|
||||||
{
|
|
||||||
Expect.equal(Comparison.NewInArray("", []).OpSql, "?|", "The InArray SQL was not correct");
|
|
||||||
}),
|
|
||||||
TestCase("Exists succeeds", () =>
|
|
||||||
{
|
|
||||||
Expect.equal(Comparison.Exists.OpSql, "IS NOT NULL", "The Exists SQL was not correct");
|
|
||||||
}),
|
|
||||||
TestCase("NotExists succeeds", () =>
|
|
||||||
{
|
|
||||||
Expect.equal(Comparison.NotExists.OpSql, "IS NULL", "The NotExists SQL was not correct");
|
|
||||||
})
|
|
||||||
]);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unit tests for the Field class
|
|
||||||
/// </summary>
|
|
||||||
private static readonly Test FieldTests = TestList("Field",
|
|
||||||
[
|
|
||||||
TestCase("Equal succeeds", () =>
|
|
||||||
{
|
|
||||||
var field = Field.Equal("Test", 14);
|
|
||||||
Expect.equal(field.Name, "Test", "Field name incorrect");
|
|
||||||
Expect.equal(field.Comparison, Comparison.NewEqual(14), "Comparison incorrect");
|
|
||||||
}),
|
|
||||||
TestCase("Greater succeeds", () =>
|
|
||||||
{
|
|
||||||
var field = Field.Greater("Great", "night");
|
|
||||||
Expect.equal(field.Name, "Great", "Field name incorrect");
|
|
||||||
Expect.equal(field.Comparison, Comparison.NewGreater("night"), "Comparison incorrect");
|
|
||||||
}),
|
|
||||||
TestCase("GreaterOrEqual succeeds", () =>
|
|
||||||
{
|
|
||||||
var field = Field.GreaterOrEqual("Nice", 88L);
|
|
||||||
Expect.equal(field.Name, "Nice", "Field name incorrect");
|
|
||||||
Expect.equal(field.Comparison, Comparison.NewGreaterOrEqual(88L), "Comparison incorrect");
|
|
||||||
}),
|
|
||||||
TestCase("Less succeeds", () =>
|
|
||||||
{
|
|
||||||
var field = Field.Less("Lesser", "seven");
|
|
||||||
Expect.equal(field.Name, "Lesser", "Field name incorrect");
|
|
||||||
Expect.equal(field.Comparison, Comparison.NewLess("seven"), "Comparison incorrect");
|
|
||||||
}),
|
|
||||||
TestCase("LessOrEqual succeeds", () =>
|
|
||||||
{
|
|
||||||
var field = Field.LessOrEqual("Nobody", "KNOWS");
|
|
||||||
Expect.equal(field.Name, "Nobody", "Field name incorrect");
|
|
||||||
Expect.equal(field.Comparison, Comparison.NewLessOrEqual("KNOWS"), "Comparison incorrect");
|
|
||||||
}),
|
|
||||||
TestCase("NotEqual succeeds", () =>
|
|
||||||
{
|
|
||||||
var field = Field.NotEqual("Park", "here");
|
|
||||||
Expect.equal(field.Name, "Park", "Field name incorrect");
|
|
||||||
Expect.equal(field.Comparison, Comparison.NewNotEqual("here"), "Comparison incorrect");
|
|
||||||
}),
|
|
||||||
TestCase("Between succeeds", () =>
|
|
||||||
{
|
|
||||||
var field = Field.Between("Age", 18, 49);
|
|
||||||
Expect.equal(field.Name, "Age", "Field name incorrect");
|
|
||||||
Expect.equal(field.Comparison, Comparison.NewBetween(18, 49), "Comparison incorrect");
|
|
||||||
}),
|
|
||||||
TestCase("In succeeds", () =>
|
|
||||||
{
|
|
||||||
var field = Field.In("Here", [8, 16, 32]);
|
|
||||||
Expect.equal(field.Name, "Here", "Field name incorrect");
|
|
||||||
Expect.isTrue(field.Comparison.IsIn, "Comparison incorrect");
|
|
||||||
Expect.sequenceEqual(((Comparison.In)field.Comparison).Values, [8, 16, 32], "Value incorrect");
|
|
||||||
}),
|
|
||||||
TestCase("InArray succeeds", () =>
|
|
||||||
{
|
|
||||||
var field = Field.InArray("ArrayField", "table", ["x", "y", "z"]);
|
|
||||||
Expect.equal(field.Name, "ArrayField", "Field name incorrect");
|
|
||||||
Expect.isTrue(field.Comparison.IsInArray, "Comparison incorrect");
|
|
||||||
var it = (Comparison.InArray)field.Comparison;
|
|
||||||
Expect.equal(it.Table, "table", "Table name incorrect");
|
|
||||||
Expect.sequenceEqual(it.Values, ["x", "y", "z"], "Value incorrect");
|
|
||||||
}),
|
|
||||||
TestCase("Exists succeeds", () =>
|
|
||||||
{
|
|
||||||
var field = Field.Exists("Groovy");
|
|
||||||
Expect.equal(field.Name, "Groovy", "Field name incorrect");
|
|
||||||
Expect.isTrue(field.Comparison.IsExists, "Comparison incorrect");
|
|
||||||
}),
|
|
||||||
TestCase("NotExists succeeds", () =>
|
|
||||||
{
|
|
||||||
var field = Field.NotExists("Rad");
|
|
||||||
Expect.equal(field.Name, "Rad", "Field name incorrect");
|
|
||||||
Expect.isTrue(field.Comparison.IsNotExists, "Comparison incorrect");
|
|
||||||
}),
|
|
||||||
TestList("NameToPath",
|
|
||||||
[
|
|
||||||
TestCase("succeeds for PostgreSQL and a simple name", () =>
|
|
||||||
{
|
|
||||||
Expect.equal("data->>'Simple'", Field.NameToPath("Simple", Dialect.PostgreSQL, FieldFormat.AsSql),
|
|
||||||
"Path not constructed correctly");
|
|
||||||
}),
|
|
||||||
TestCase("succeeds for SQLite and a simple name", () =>
|
|
||||||
{
|
|
||||||
Expect.equal("data->>'Simple'", Field.NameToPath("Simple", Dialect.SQLite, FieldFormat.AsSql),
|
|
||||||
"Path not constructed correctly");
|
|
||||||
}),
|
|
||||||
TestCase("succeeds for PostgreSQL and a nested name", () =>
|
|
||||||
{
|
|
||||||
Expect.equal("data#>>'{A,Long,Path,to,the,Property}'",
|
|
||||||
Field.NameToPath("A.Long.Path.to.the.Property", Dialect.PostgreSQL, FieldFormat.AsSql),
|
|
||||||
"Path not constructed correctly");
|
|
||||||
}),
|
|
||||||
TestCase("succeeds for SQLite and a nested name", () =>
|
|
||||||
{
|
|
||||||
Expect.equal("data->'A'->'Long'->'Path'->'to'->'the'->>'Property'",
|
|
||||||
Field.NameToPath("A.Long.Path.to.the.Property", Dialect.SQLite, FieldFormat.AsSql),
|
|
||||||
"Path not constructed correctly");
|
|
||||||
})
|
|
||||||
]),
|
|
||||||
TestCase("WithParameterName succeeds", () =>
|
|
||||||
{
|
|
||||||
var field = Field.Equal("Bob", "Tom").WithParameterName("@name");
|
|
||||||
Expect.isSome(field.ParameterName, "The parameter name should have been filled");
|
|
||||||
Expect.equal("@name", field.ParameterName.Value, "The parameter name is incorrect");
|
|
||||||
}),
|
|
||||||
TestCase("WithQualifier succeeds", () =>
|
|
||||||
{
|
|
||||||
var field = Field.Equal("Bill", "Matt").WithQualifier("joe");
|
|
||||||
Expect.isSome(field.Qualifier, "The table qualifier should have been filled");
|
|
||||||
Expect.equal("joe", field.Qualifier.Value, "The table qualifier is incorrect");
|
|
||||||
}),
|
|
||||||
TestList("Path",
|
|
||||||
[
|
|
||||||
TestCase("succeeds for a PostgreSQL single field with no qualifier", () =>
|
|
||||||
{
|
|
||||||
var field = Field.GreaterOrEqual("SomethingCool", 18);
|
|
||||||
Expect.equal("data->>'SomethingCool'", field.Path(Dialect.PostgreSQL, FieldFormat.AsSql),
|
|
||||||
"The PostgreSQL path is incorrect");
|
|
||||||
}),
|
|
||||||
TestCase("succeeds for a PostgreSQL single field with a qualifier", () =>
|
|
||||||
{
|
|
||||||
var field = Field.Less("SomethingElse", 9).WithQualifier("this");
|
|
||||||
Expect.equal("this.data->>'SomethingElse'", field.Path(Dialect.PostgreSQL, FieldFormat.AsSql),
|
|
||||||
"The PostgreSQL path is incorrect");
|
|
||||||
}),
|
|
||||||
TestCase("succeeds for a PostgreSQL nested field with no qualifier", () =>
|
|
||||||
{
|
|
||||||
var field = Field.Equal("My.Nested.Field", "howdy");
|
|
||||||
Expect.equal("data#>>'{My,Nested,Field}'", field.Path(Dialect.PostgreSQL, FieldFormat.AsSql),
|
|
||||||
"The PostgreSQL path is incorrect");
|
|
||||||
}),
|
|
||||||
TestCase("succeeds for a PostgreSQL nested field with a qualifier", () =>
|
|
||||||
{
|
|
||||||
var field = Field.Equal("Nest.Away", "doc").WithQualifier("bird");
|
|
||||||
Expect.equal("bird.data#>>'{Nest,Away}'", field.Path(Dialect.PostgreSQL, FieldFormat.AsSql),
|
|
||||||
"The PostgreSQL path is incorrect");
|
|
||||||
}),
|
|
||||||
TestCase("succeeds for a SQLite single field with no qualifier", () =>
|
|
||||||
{
|
|
||||||
var field = Field.GreaterOrEqual("SomethingCool", 18);
|
|
||||||
Expect.equal("data->>'SomethingCool'", field.Path(Dialect.SQLite, FieldFormat.AsSql),
|
|
||||||
"The SQLite path is incorrect");
|
|
||||||
}),
|
|
||||||
TestCase("succeeds for a SQLite single field with a qualifier", () =>
|
|
||||||
{
|
|
||||||
var field = Field.Less("SomethingElse", 9).WithQualifier("this");
|
|
||||||
Expect.equal("this.data->>'SomethingElse'", field.Path(Dialect.SQLite, FieldFormat.AsSql),
|
|
||||||
"The SQLite path is incorrect");
|
|
||||||
}),
|
|
||||||
TestCase("succeeds for a SQLite nested field with no qualifier", () =>
|
|
||||||
{
|
|
||||||
var field = Field.Equal("My.Nested.Field", "howdy");
|
|
||||||
Expect.equal("data->'My'->'Nested'->>'Field'", field.Path(Dialect.SQLite, FieldFormat.AsSql),
|
|
||||||
"The SQLite path is incorrect");
|
|
||||||
}),
|
|
||||||
TestCase("succeeds for a SQLite nested field with a qualifier", () =>
|
|
||||||
{
|
|
||||||
var field = Field.Equal("Nest.Away", "doc").WithQualifier("bird");
|
|
||||||
Expect.equal("bird.data->'Nest'->>'Away'", field.Path(Dialect.SQLite, FieldFormat.AsSql),
|
|
||||||
"The SQLite path is incorrect");
|
|
||||||
})
|
|
||||||
])
|
|
||||||
]);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unit tests for the FieldMatch enum
|
|
||||||
/// </summary>
|
|
||||||
private static readonly Test FieldMatchTests = 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");
|
|
||||||
})
|
|
||||||
]);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unit tests for the ParameterName class
|
|
||||||
/// </summary>
|
|
||||||
private static readonly Test ParameterNameTests = 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");
|
|
||||||
})
|
|
||||||
]);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unit tests for the AutoId enum
|
|
||||||
/// </summary>
|
|
||||||
private static readonly Test AutoIdTests = TestList("AutoId",
|
|
||||||
[
|
|
||||||
TestCase("GenerateGuid succeeds", () =>
|
|
||||||
{
|
|
||||||
var autoId = AutoId.GenerateGuid();
|
|
||||||
Expect.isNotNull(autoId, "The GUID auto-ID should not have been null");
|
|
||||||
Expect.stringHasLength(autoId, 32, "The GUID auto-ID should have been 32 characters long");
|
|
||||||
Expect.equal(autoId, autoId.ToLowerInvariant(), "The GUID auto-ID should have been lowercase");
|
|
||||||
}),
|
|
||||||
TestCase("GenerateRandomString succeeds", () =>
|
|
||||||
{
|
|
||||||
foreach (var length in (int[]) [6, 8, 12, 20, 32, 57, 64])
|
|
||||||
{
|
|
||||||
var autoId = AutoId.GenerateRandomString(length);
|
|
||||||
Expect.isNotNull(autoId, $"Random string ({length}) should not have been null");
|
|
||||||
Expect.stringHasLength(autoId, length, $"Random string should have been {length} characters long");
|
|
||||||
Expect.equal(autoId, autoId.ToLowerInvariant(),
|
|
||||||
$"Random string ({length}) should have been lowercase");
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
TestList("NeedsAutoId",
|
|
||||||
[
|
|
||||||
TestCase("succeeds when no auto ID is configured", () =>
|
|
||||||
{
|
|
||||||
Expect.isFalse(AutoId.NeedsAutoId(AutoId.Disabled, new object(), "id"),
|
|
||||||
"Disabled auto-ID never needs an automatic ID");
|
|
||||||
}),
|
|
||||||
TestCase("fails for any when the ID property is not found", () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_ = AutoId.NeedsAutoId(AutoId.Number, new { Key = "" }, "Id");
|
|
||||||
Expect.isTrue(false, "Non-existent ID property should have thrown an exception");
|
|
||||||
}
|
|
||||||
catch (InvalidOperationException)
|
|
||||||
{
|
|
||||||
// pass
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
TestCase("succeeds for byte when the ID is zero", () =>
|
|
||||||
{
|
|
||||||
Expect.isTrue(AutoId.NeedsAutoId(AutoId.Number, new { Id = (sbyte)0 }, "Id"),
|
|
||||||
"Zero ID should have returned true");
|
|
||||||
}),
|
|
||||||
TestCase("succeeds for byte when the ID is non-zero", () =>
|
|
||||||
{
|
|
||||||
Expect.isFalse(AutoId.NeedsAutoId(AutoId.Number, new { Id = (sbyte)4 }, "Id"),
|
|
||||||
"Non-zero ID should have returned false");
|
|
||||||
}),
|
|
||||||
TestCase("succeeds for short when the ID is zero", () =>
|
|
||||||
{
|
|
||||||
Expect.isTrue(AutoId.NeedsAutoId(AutoId.Number, new { Id = (short)0 }, "Id"),
|
|
||||||
"Zero ID should have returned true");
|
|
||||||
}),
|
|
||||||
TestCase("succeeds for short when the ID is non-zero", () =>
|
|
||||||
{
|
|
||||||
Expect.isFalse(AutoId.NeedsAutoId(AutoId.Number, new { Id = (short)7 }, "Id"),
|
|
||||||
"Non-zero ID should have returned false");
|
|
||||||
}),
|
|
||||||
TestCase("succeeds for int when the ID is zero", () =>
|
|
||||||
{
|
|
||||||
Expect.isTrue(AutoId.NeedsAutoId(AutoId.Number, new { Id = 0 }, "Id"),
|
|
||||||
"Zero ID should have returned true");
|
|
||||||
}),
|
|
||||||
TestCase("succeeds for int when the ID is non-zero", () =>
|
|
||||||
{
|
|
||||||
Expect.isFalse(AutoId.NeedsAutoId(AutoId.Number, new { Id = 32 }, "Id"),
|
|
||||||
"Non-zero ID should have returned false");
|
|
||||||
}),
|
|
||||||
TestCase("succeeds for long when the ID is zero", () =>
|
|
||||||
{
|
|
||||||
Expect.isTrue(AutoId.NeedsAutoId(AutoId.Number, new { Id = 0L }, "Id"),
|
|
||||||
"Zero ID should have returned true");
|
|
||||||
}),
|
|
||||||
TestCase("succeeds for long when the ID is non-zero", () =>
|
|
||||||
{
|
|
||||||
Expect.isFalse(AutoId.NeedsAutoId(AutoId.Number, new { Id = 80L }, "Id"),
|
|
||||||
"Non-zero ID should have returned false");
|
|
||||||
}),
|
|
||||||
TestCase("fails for number when the ID is not a number", () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_ = AutoId.NeedsAutoId(AutoId.Number, new { Id = "" }, "Id");
|
|
||||||
Expect.isTrue(false, "Numeric ID against a string should have thrown an exception");
|
|
||||||
}
|
|
||||||
catch (InvalidOperationException)
|
|
||||||
{
|
|
||||||
// pass
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
TestCase("succeeds for GUID when the ID is blank", () =>
|
|
||||||
{
|
|
||||||
Expect.isTrue(AutoId.NeedsAutoId(AutoId.Guid, new { Id = "" }, "Id"),
|
|
||||||
"Blank ID should have returned true");
|
|
||||||
}),
|
|
||||||
TestCase("succeeds for GUID when the ID is filled", () =>
|
|
||||||
{
|
|
||||||
Expect.isFalse(AutoId.NeedsAutoId(AutoId.Guid, new { Id = "abc" }, "Id"),
|
|
||||||
"Filled ID should have returned false");
|
|
||||||
}),
|
|
||||||
TestCase("fails for GUID when the ID is not a string", () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_ = AutoId.NeedsAutoId(AutoId.Guid, new { Id = 8 }, "Id");
|
|
||||||
Expect.isTrue(false, "String ID against a number should have thrown an exception");
|
|
||||||
}
|
|
||||||
catch (InvalidOperationException)
|
|
||||||
{
|
|
||||||
// pass
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
TestCase("succeeds for RandomString when the ID is blank", () =>
|
|
||||||
{
|
|
||||||
Expect.isTrue(AutoId.NeedsAutoId(AutoId.RandomString, new { Id = "" }, "Id"),
|
|
||||||
"Blank ID should have returned true");
|
|
||||||
}),
|
|
||||||
TestCase("succeeds for RandomString when the ID is filled", () =>
|
|
||||||
{
|
|
||||||
Expect.isFalse(AutoId.NeedsAutoId(AutoId.RandomString, new { Id = "x" }, "Id"),
|
|
||||||
"Filled ID should have returned false");
|
|
||||||
}),
|
|
||||||
TestCase("fails for RandomString when the ID is not a string", () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_ = AutoId.NeedsAutoId(AutoId.RandomString, new { Id = 33 }, "Id");
|
|
||||||
Expect.isTrue(false, "String ID against a number should have thrown an exception");
|
|
||||||
}
|
|
||||||
catch (InvalidOperationException)
|
|
||||||
{
|
|
||||||
// pass
|
|
||||||
}
|
|
||||||
})
|
|
||||||
])
|
|
||||||
]);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unit tests for the Configuration static class
|
|
||||||
/// </summary>
|
|
||||||
private static readonly Test ConfigurationTests = TestList("Configuration",
|
|
||||||
[
|
[
|
||||||
TestCase("UseSerializer succeeds", () =>
|
TestCase("UseSerializer succeeds", () =>
|
||||||
{
|
{
|
||||||
@@ -463,41 +70,232 @@ public static class CommonCSharpTests
|
|||||||
{
|
{
|
||||||
Configuration.UseIdField("Id");
|
Configuration.UseIdField("Id");
|
||||||
}
|
}
|
||||||
}),
|
|
||||||
TestCase("UseAutoIdStrategy / AutoIdStrategy succeeds", () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Expect.equal(Configuration.AutoIdStrategy(), AutoId.Disabled,
|
|
||||||
"The default auto-ID strategy was incorrect");
|
|
||||||
Configuration.UseAutoIdStrategy(AutoId.Guid);
|
|
||||||
Expect.equal(Configuration.AutoIdStrategy(), AutoId.Guid,
|
|
||||||
"The auto-ID strategy was not set correctly");
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Configuration.UseAutoIdStrategy(AutoId.Disabled);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
TestCase("UseIdStringLength / IdStringLength succeeds", () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Expect.equal(Configuration.IdStringLength(), 16, "The default ID string length was incorrect");
|
|
||||||
Configuration.UseIdStringLength(33);
|
|
||||||
Expect.equal(Configuration.IdStringLength(), 33, "The ID string length was not set correctly");
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Configuration.UseIdStringLength(16);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
]);
|
])),
|
||||||
|
TestList("Op",
|
||||||
/// <summary>
|
[
|
||||||
/// Unit tests for the Query static class
|
TestCase("EQ succeeds", () =>
|
||||||
/// </summary>
|
{
|
||||||
private static readonly Test QueryTests = TestList("Query",
|
Expect.equal(Op.EQ.ToString(), "=", "The equals operator was not correct");
|
||||||
|
}),
|
||||||
|
TestCase("GT succeeds", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(Op.GT.ToString(), ">", "The greater than operator was not correct");
|
||||||
|
}),
|
||||||
|
TestCase("GE succeeds", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(Op.GE.ToString(), ">=", "The greater than or equal to operator was not correct");
|
||||||
|
}),
|
||||||
|
TestCase("LT succeeds", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(Op.LT.ToString(), "<", "The less than operator was not correct");
|
||||||
|
}),
|
||||||
|
TestCase("LE succeeds", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(Op.LE.ToString(), "<=", "The less than or equal to operator was not correct");
|
||||||
|
}),
|
||||||
|
TestCase("NE succeeds", () =>
|
||||||
|
{
|
||||||
|
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", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(Op.EX.ToString(), "IS NOT NULL", "The \"exists\" operator was not correct");
|
||||||
|
}),
|
||||||
|
TestCase("NEX succeeds", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(Op.NEX.ToString(), "IS NULL", "The \"not exists\" operator was not correct");
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
TestList("Field",
|
||||||
|
[
|
||||||
|
TestCase("EQ succeeds", () =>
|
||||||
|
{
|
||||||
|
var field = Field.EQ("Test", 14);
|
||||||
|
Expect.equal(field.Name, "Test", "Field name incorrect");
|
||||||
|
Expect.equal(field.Op, Op.EQ, "Operator incorrect");
|
||||||
|
Expect.equal(field.Value, 14, "Value incorrect");
|
||||||
|
}),
|
||||||
|
TestCase("GT succeeds", () =>
|
||||||
|
{
|
||||||
|
var field = Field.GT("Great", "night");
|
||||||
|
Expect.equal(field.Name, "Great", "Field name incorrect");
|
||||||
|
Expect.equal(field.Op, Op.GT, "Operator incorrect");
|
||||||
|
Expect.equal(field.Value, "night", "Value incorrect");
|
||||||
|
}),
|
||||||
|
TestCase("GE succeeds", () =>
|
||||||
|
{
|
||||||
|
var field = Field.GE("Nice", 88L);
|
||||||
|
Expect.equal(field.Name, "Nice", "Field name incorrect");
|
||||||
|
Expect.equal(field.Op, Op.GE, "Operator incorrect");
|
||||||
|
Expect.equal(field.Value, 88L, "Value incorrect");
|
||||||
|
}),
|
||||||
|
TestCase("LT succeeds", () =>
|
||||||
|
{
|
||||||
|
var field = Field.LT("Lesser", "seven");
|
||||||
|
Expect.equal(field.Name, "Lesser", "Field name incorrect");
|
||||||
|
Expect.equal(field.Op, Op.LT, "Operator incorrect");
|
||||||
|
Expect.equal(field.Value, "seven", "Value incorrect");
|
||||||
|
}),
|
||||||
|
TestCase("LE succeeds", () =>
|
||||||
|
{
|
||||||
|
var field = Field.LE("Nobody", "KNOWS");
|
||||||
|
Expect.equal(field.Name, "Nobody", "Field name incorrect");
|
||||||
|
Expect.equal(field.Op, Op.LE, "Operator incorrect");
|
||||||
|
Expect.equal(field.Value, "KNOWS", "Value incorrect");
|
||||||
|
}),
|
||||||
|
TestCase("NE succeeds", () =>
|
||||||
|
{
|
||||||
|
var field = Field.NE("Park", "here");
|
||||||
|
Expect.equal(field.Name, "Park", "Field name incorrect");
|
||||||
|
Expect.equal(field.Op, Op.NE, "Operator 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", () =>
|
||||||
|
{
|
||||||
|
var field = Field.EX("Groovy");
|
||||||
|
Expect.equal(field.Name, "Groovy", "Field name incorrect");
|
||||||
|
Expect.equal(field.Op, Op.EX, "Operator incorrect");
|
||||||
|
}),
|
||||||
|
TestCase("NEX succeeds", () =>
|
||||||
|
{
|
||||||
|
var field = Field.NEX("Rad");
|
||||||
|
Expect.equal(field.Name, "Rad", "Field name incorrect");
|
||||||
|
Expect.equal(field.Op, Op.NEX, "Operator incorrect");
|
||||||
|
}),
|
||||||
|
TestList("NameToPath",
|
||||||
|
[
|
||||||
|
TestCase("succeeds for PostgreSQL and a simple name", () =>
|
||||||
|
{
|
||||||
|
Expect.equal("data->>'Simple'", Field.NameToPath("Simple", Dialect.PostgreSQL),
|
||||||
|
"Path not constructed correctly");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for SQLite and a simple name", () =>
|
||||||
|
{
|
||||||
|
Expect.equal("data->>'Simple'", Field.NameToPath("Simple", Dialect.SQLite),
|
||||||
|
"Path not constructed correctly");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for PostgreSQL and a nested name", () =>
|
||||||
|
{
|
||||||
|
Expect.equal("data#>>'{A,Long,Path,to,the,Property}'",
|
||||||
|
Field.NameToPath("A.Long.Path.to.the.Property", Dialect.PostgreSQL),
|
||||||
|
"Path not constructed correctly");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for SQLite and a nested name", () =>
|
||||||
|
{
|
||||||
|
Expect.equal("data->>'A'->>'Long'->>'Path'->>'to'->>'the'->>'Property'",
|
||||||
|
Field.NameToPath("A.Long.Path.to.the.Property", Dialect.SQLite),
|
||||||
|
"Path not constructed correctly");
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
TestCase("WithParameterName succeeds", () =>
|
||||||
|
{
|
||||||
|
var field = Field.EQ("Bob", "Tom").WithParameterName("@name");
|
||||||
|
Expect.isSome(field.ParameterName, "The parameter name should have been filled");
|
||||||
|
Expect.equal("@name", field.ParameterName.Value, "The parameter name is incorrect");
|
||||||
|
}),
|
||||||
|
TestCase("WithQualifier succeeds", () =>
|
||||||
|
{
|
||||||
|
var field = Field.EQ("Bill", "Matt").WithQualifier("joe");
|
||||||
|
Expect.isSome(field.Qualifier, "The table qualifier should have been filled");
|
||||||
|
Expect.equal("joe", field.Qualifier.Value, "The table qualifier is incorrect");
|
||||||
|
}),
|
||||||
|
TestList("Path",
|
||||||
|
[
|
||||||
|
TestCase("succeeds for a PostgreSQL single field with no qualifier", () =>
|
||||||
|
{
|
||||||
|
var field = Field.GE("SomethingCool", 18);
|
||||||
|
Expect.equal("data->>'SomethingCool'", field.Path(Dialect.PostgreSQL),
|
||||||
|
"The PostgreSQL path is incorrect");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for a PostgreSQL single field with a qualifier", () =>
|
||||||
|
{
|
||||||
|
var field = Field.LT("SomethingElse", 9).WithQualifier("this");
|
||||||
|
Expect.equal("this.data->>'SomethingElse'", field.Path(Dialect.PostgreSQL),
|
||||||
|
"The PostgreSQL path is incorrect");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for a PostgreSQL nested field with no qualifier", () =>
|
||||||
|
{
|
||||||
|
var field = Field.EQ("My.Nested.Field", "howdy");
|
||||||
|
Expect.equal("data#>>'{My,Nested,Field}'", field.Path(Dialect.PostgreSQL),
|
||||||
|
"The PostgreSQL path is incorrect");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for a PostgreSQL nested field with a qualifier", () =>
|
||||||
|
{
|
||||||
|
var field = Field.EQ("Nest.Away", "doc").WithQualifier("bird");
|
||||||
|
Expect.equal("bird.data#>>'{Nest,Away}'", field.Path(Dialect.PostgreSQL),
|
||||||
|
"The PostgreSQL path is incorrect");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for a SQLite single field with no qualifier", () =>
|
||||||
|
{
|
||||||
|
var field = Field.GE("SomethingCool", 18);
|
||||||
|
Expect.equal("data->>'SomethingCool'", field.Path(Dialect.SQLite), "The SQLite path is incorrect");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for a SQLite single field with a qualifier", () =>
|
||||||
|
{
|
||||||
|
var field = Field.LT("SomethingElse", 9).WithQualifier("this");
|
||||||
|
Expect.equal("this.data->>'SomethingElse'", field.Path(Dialect.SQLite),
|
||||||
|
"The SQLite path is incorrect");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for a SQLite nested field with no qualifier", () =>
|
||||||
|
{
|
||||||
|
var field = Field.EQ("My.Nested.Field", "howdy");
|
||||||
|
Expect.equal("data->>'My'->>'Nested'->>'Field'", field.Path(Dialect.SQLite),
|
||||||
|
"The SQLite path is incorrect");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for a SQLite nested field with a qualifier", () =>
|
||||||
|
{
|
||||||
|
var field = Field.EQ("Nest.Away", "doc").WithQualifier("bird");
|
||||||
|
Expect.equal("bird.data->>'Nest'->>'Away'", field.Path(Dialect.SQLite),
|
||||||
|
"The SQLite path is incorrect");
|
||||||
|
})
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
TestList("FieldMatch.ToString",
|
||||||
|
[
|
||||||
|
TestCase("succeeds for Any", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(FieldMatch.Any.ToString(), "OR", "SQL for Any is incorrect");
|
||||||
|
}),
|
||||||
|
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", () =>
|
TestCase("StatementWhere succeeds", () =>
|
||||||
{
|
{
|
||||||
@@ -548,7 +346,7 @@ public static class CommonCSharpTests
|
|||||||
{
|
{
|
||||||
Expect.equal(
|
Expect.equal(
|
||||||
Query.Definition.EnsureIndexOn("tbl", "nest", ["a.b.c"], Dialect.SQLite),
|
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 IF NOT EXISTS idx_tbl_nest ON tbl ((data->>'a'->>'b'->>'c'))",
|
||||||
"CREATE INDEX for nested SQLite field incorrect");
|
"CREATE INDEX for nested SQLite field incorrect");
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
@@ -588,7 +386,8 @@ public static class CommonCSharpTests
|
|||||||
[
|
[
|
||||||
TestCase("succeeds for no fields", () =>
|
TestCase("succeeds for no fields", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(Query.OrderBy([], Dialect.PostgreSQL), "", "Order By should have been blank (PostgreSQL)");
|
Expect.equal(Query.OrderBy([], Dialect.PostgreSQL), "",
|
||||||
|
"Order By should have been blank (PostgreSQL)");
|
||||||
Expect.equal(Query.OrderBy([], Dialect.SQLite), "", "Order By should have been blank (SQLite)");
|
Expect.equal(Query.OrderBy([], Dialect.SQLite), "", "Order By should have been blank (SQLite)");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds for PostgreSQL with one field and no direction", () =>
|
TestCase("succeeds for PostgreSQL with one field and no direction", () =>
|
||||||
@@ -608,7 +407,8 @@ public static class CommonCSharpTests
|
|||||||
[
|
[
|
||||||
Field.Named("Nested.Test.Field DESC"), Field.Named("AnotherField"),
|
Field.Named("Nested.Test.Field DESC"), Field.Named("AnotherField"),
|
||||||
Field.Named("It DESC")
|
Field.Named("It DESC")
|
||||||
], Dialect.PostgreSQL),
|
],
|
||||||
|
Dialect.PostgreSQL),
|
||||||
" ORDER BY data#>>'{Nested,Test,Field}' DESC, data->>'AnotherField', data->>'It' DESC",
|
" ORDER BY data#>>'{Nested,Test,Field}' DESC, data->>'AnotherField', data->>'It' DESC",
|
||||||
"Order By not constructed correctly");
|
"Order By not constructed correctly");
|
||||||
}),
|
}),
|
||||||
@@ -619,8 +419,9 @@ public static class CommonCSharpTests
|
|||||||
[
|
[
|
||||||
Field.Named("Nested.Test.Field DESC"), Field.Named("AnotherField"),
|
Field.Named("Nested.Test.Field DESC"), Field.Named("AnotherField"),
|
||||||
Field.Named("It DESC")
|
Field.Named("It DESC")
|
||||||
], Dialect.SQLite),
|
],
|
||||||
" ORDER BY data->'Nested'->'Test'->>'Field' DESC, data->>'AnotherField', data->>'It' DESC",
|
Dialect.SQLite),
|
||||||
|
" ORDER BY data->>'Nested'->>'Test'->>'Field' DESC, data->>'AnotherField', data->>'It' DESC",
|
||||||
"Order By not constructed correctly");
|
"Order By not constructed correctly");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds for PostgreSQL numeric fields", () =>
|
TestCase("succeeds for PostgreSQL numeric fields", () =>
|
||||||
@@ -632,34 +433,8 @@ public static class CommonCSharpTests
|
|||||||
{
|
{
|
||||||
Expect.equal(Query.OrderBy([Field.Named("n:Test")], Dialect.SQLite), " ORDER BY data->>'Test'",
|
Expect.equal(Query.OrderBy([Field.Named("n:Test")], Dialect.SQLite), " ORDER BY data->>'Test'",
|
||||||
"Order By not constructed correctly for numeric field");
|
"Order By not constructed correctly for numeric field");
|
||||||
}),
|
|
||||||
TestCase("succeeds for PostgreSQL case-insensitive ordering", () =>
|
|
||||||
{
|
|
||||||
Expect.equal(Query.OrderBy([Field.Named("i:Test.Field DESC NULLS FIRST")], Dialect.PostgreSQL),
|
|
||||||
" ORDER BY LOWER(data#>>'{Test,Field}') DESC NULLS FIRST",
|
|
||||||
"Order By not constructed correctly for case-insensitive field");
|
|
||||||
}),
|
|
||||||
TestCase("succeeds for SQLite case-insensitive ordering", () =>
|
|
||||||
{
|
|
||||||
Expect.equal(Query.OrderBy([Field.Named("i:Test.Field ASC NULLS LAST")], Dialect.SQLite),
|
|
||||||
" ORDER BY data->'Test'->>'Field' COLLATE NOCASE ASC NULLS LAST",
|
|
||||||
"Order By not constructed correctly for case-insensitive field");
|
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
]);
|
])
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unit tests
|
|
||||||
/// </summary>
|
|
||||||
[Tests]
|
|
||||||
public static readonly Test Unit = TestList("Common.C# Unit",
|
|
||||||
[
|
|
||||||
OpTests,
|
|
||||||
FieldTests,
|
|
||||||
FieldMatchTests,
|
|
||||||
ParameterNameTests,
|
|
||||||
AutoIdTests,
|
|
||||||
QueryTests,
|
|
||||||
TestSequenced(ConfigurationTests)
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ public class PostgresCSharpExtensionTests
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
TestList("Save",
|
TestList("save",
|
||||||
[
|
[
|
||||||
TestCase("succeeds when a document is inserted", async () =>
|
TestCase("succeeds when a document is inserted", async () =>
|
||||||
{
|
{
|
||||||
@@ -253,7 +253,7 @@ public class PostgresCSharpExtensionTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var theCount = await conn.CountByFields(PostgresDb.TableName, FieldMatch.Any,
|
var theCount = await conn.CountByFields(PostgresDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Value", "purple")]);
|
[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");
|
||||||
}),
|
}),
|
||||||
TestCase("CountByContains succeeds", async () =>
|
TestCase("CountByContains succeeds", async () =>
|
||||||
@@ -303,7 +303,7 @@ public class PostgresCSharpExtensionTests
|
|||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var exists = await conn.ExistsByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Exists("Sub")]);
|
var exists = await conn.ExistsByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EX("Sub")]);
|
||||||
Expect.isTrue(exists, "There should have been existing documents");
|
Expect.isTrue(exists, "There should have been existing documents");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when documents do not exist", async () =>
|
TestCase("succeeds when documents do not exist", async () =>
|
||||||
@@ -313,7 +313,7 @@ public class PostgresCSharpExtensionTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var exists =
|
var exists =
|
||||||
await conn.ExistsByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", "six")]);
|
await conn.ExistsByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", "six")]);
|
||||||
Expect.isFalse(exists, "There should not have been existing documents");
|
Expect.isFalse(exists, "There should not have been existing documents");
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
@@ -450,7 +450,7 @@ public class PostgresCSharpExtensionTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var docs = await conn.FindByFields<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
var docs = await conn.FindByFields<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Value", "another")]);
|
[Field.EQ("Value", "another")]);
|
||||||
Expect.equal(docs.Count, 1, "There should have been one document returned");
|
Expect.equal(docs.Count, 1, "There should have been one document returned");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when documents are not found", async () =>
|
TestCase("succeeds when documents are not found", async () =>
|
||||||
@@ -460,7 +460,7 @@ public class PostgresCSharpExtensionTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var docs = await conn.FindByFields<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
var docs = await conn.FindByFields<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Value", "mauve")]);
|
[Field.EQ("Value", "mauve")]);
|
||||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
@@ -473,7 +473,7 @@ public class PostgresCSharpExtensionTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var docs = await conn.FindByFieldsOrdered<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
var docs = await conn.FindByFieldsOrdered<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Value", "purple")], [Field.Named("Id")]);
|
[Field.EQ("Value", "purple")], [Field.Named("Id")]);
|
||||||
Expect.hasLength(docs, 2, "There should have been two document returned");
|
Expect.hasLength(docs, 2, "There should have been two document returned");
|
||||||
Expect.equal(string.Join('|', docs.Select(x => x.Id)), "five|four",
|
Expect.equal(string.Join('|', docs.Select(x => x.Id)), "five|four",
|
||||||
"The documents were not ordered correctly");
|
"The documents were not ordered correctly");
|
||||||
@@ -485,7 +485,7 @@ public class PostgresCSharpExtensionTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var docs = await conn.FindByFieldsOrdered<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
var docs = await conn.FindByFieldsOrdered<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Value", "purple")], [Field.Named("Id DESC")]);
|
[Field.EQ("Value", "purple")], [Field.Named("Id DESC")]);
|
||||||
Expect.hasLength(docs, 2, "There should have been two document returned");
|
Expect.hasLength(docs, 2, "There should have been two document returned");
|
||||||
Expect.equal(string.Join('|', docs.Select(x => x.Id)), "four|five",
|
Expect.equal(string.Join('|', docs.Select(x => x.Id)), "four|five",
|
||||||
"The documents were not ordered correctly");
|
"The documents were not ordered correctly");
|
||||||
@@ -599,7 +599,7 @@ public class PostgresCSharpExtensionTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await conn.FindFirstByFields<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
var doc = await conn.FindFirstByFields<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Value", "another")]);
|
[Field.EQ("Value", "another")]);
|
||||||
Expect.isNotNull(doc, "There should have been a document returned");
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
Expect.equal(doc.Id, "two", "The incorrect document was returned");
|
Expect.equal(doc.Id, "two", "The incorrect document was returned");
|
||||||
}),
|
}),
|
||||||
@@ -610,7 +610,7 @@ public class PostgresCSharpExtensionTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await conn.FindFirstByFields<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
var doc = await conn.FindFirstByFields<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Value", "purple")]);
|
[Field.EQ("Value", "purple")]);
|
||||||
Expect.isNotNull(doc, "There should have been a document returned");
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
Expect.contains(["five", "four"], doc.Id, "An incorrect document was returned");
|
Expect.contains(["five", "four"], doc.Id, "An incorrect document was returned");
|
||||||
}),
|
}),
|
||||||
@@ -621,7 +621,7 @@ public class PostgresCSharpExtensionTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await conn.FindFirstByFields<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
var doc = await conn.FindFirstByFields<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Value", "absent")]);
|
[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");
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
@@ -634,7 +634,7 @@ public class PostgresCSharpExtensionTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await conn.FindFirstByFieldsOrdered<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
var doc = await conn.FindFirstByFieldsOrdered<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Value", "purple")], [Field.Named("Id")]);
|
[Field.EQ("Value", "purple")], [Field.Named("Id")]);
|
||||||
Expect.isNotNull(doc, "There should have been a document returned");
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
Expect.equal("five", doc.Id, "An incorrect document was returned");
|
Expect.equal("five", doc.Id, "An incorrect document was returned");
|
||||||
}),
|
}),
|
||||||
@@ -645,7 +645,7 @@ public class PostgresCSharpExtensionTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await conn.FindFirstByFieldsOrdered<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
var doc = await conn.FindFirstByFieldsOrdered<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Value", "purple")], [Field.Named("Id DESC")]);
|
[Field.EQ("Value", "purple")], [Field.Named("Id DESC")]);
|
||||||
Expect.isNotNull(doc, "There should have been a document returned");
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
Expect.equal("four", doc.Id, "An incorrect document was returned");
|
Expect.equal("four", doc.Id, "An incorrect document was returned");
|
||||||
})
|
})
|
||||||
@@ -859,10 +859,10 @@ public class PostgresCSharpExtensionTests
|
|||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await conn.PatchByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("Value", "purple")],
|
await conn.PatchByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("Value", "purple")],
|
||||||
new { NumValue = 77 });
|
new { NumValue = 77 });
|
||||||
var after = await conn.CountByFields(PostgresDb.TableName, FieldMatch.Any,
|
var after = await conn.CountByFields(PostgresDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("NumValue", 77)]);
|
[Field.EQ("NumValue", "77")]);
|
||||||
Expect.equal(after, 2, "There should have been 2 documents returned");
|
Expect.equal(after, 2, "There should have been 2 documents returned");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when no document is updated", async () =>
|
TestCase("succeeds when no document is updated", async () =>
|
||||||
@@ -873,7 +873,7 @@ public class PostgresCSharpExtensionTests
|
|||||||
Expect.equal(before, 0, "There should have been no documents returned");
|
Expect.equal(before, 0, "There should have been no documents returned");
|
||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await conn.PatchByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("Value", "burgundy")],
|
await conn.PatchByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("Value", "burgundy")],
|
||||||
new { Foo = "green" });
|
new { Foo = "green" });
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
@@ -975,7 +975,7 @@ public class PostgresCSharpExtensionTests
|
|||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await conn.RemoveFieldsByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", "17")],
|
await conn.RemoveFieldsByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", "17")],
|
||||||
["Sub", "Value"]);
|
["Sub", "Value"]);
|
||||||
var updated = await Find.ById<string, JsonDocument>(PostgresDb.TableName, "four");
|
var updated = await Find.ById<string, JsonDocument>(PostgresDb.TableName, "four");
|
||||||
Expect.isNotNull(updated, "The updated document should have been retrieved");
|
Expect.isNotNull(updated, "The updated document should have been retrieved");
|
||||||
@@ -988,7 +988,7 @@ public class PostgresCSharpExtensionTests
|
|||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await conn.RemoveFieldsByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", "17")],
|
await conn.RemoveFieldsByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", "17")],
|
||||||
["Sub"]);
|
["Sub"]);
|
||||||
var updated = await Find.ById<string, JsonDocument>(PostgresDb.TableName, "four");
|
var updated = await Find.ById<string, JsonDocument>(PostgresDb.TableName, "four");
|
||||||
Expect.isNotNull(updated, "The updated document should have been retrieved");
|
Expect.isNotNull(updated, "The updated document should have been retrieved");
|
||||||
@@ -1002,7 +1002,7 @@ public class PostgresCSharpExtensionTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await conn.RemoveFieldsByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", "17")],
|
await conn.RemoveFieldsByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", "17")],
|
||||||
["Nothing"]);
|
["Nothing"]);
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when no document is matched", async () =>
|
TestCase("succeeds when no document is matched", async () =>
|
||||||
@@ -1012,7 +1012,7 @@ public class PostgresCSharpExtensionTests
|
|||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await conn.RemoveFieldsByFields(PostgresDb.TableName, FieldMatch.Any,
|
await conn.RemoveFieldsByFields(PostgresDb.TableName, FieldMatch.Any,
|
||||||
[Field.NotEqual("Abracadabra", "apple")], ["Value"]);
|
[Field.NE("Abracadabra", "apple")], ["Value"]);
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
TestList("RemoveFieldsByContains",
|
TestList("RemoveFieldsByContains",
|
||||||
@@ -1134,7 +1134,7 @@ public class PostgresCSharpExtensionTests
|
|||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await conn.DeleteByFields(PostgresDb.TableName, FieldMatch.Any, [Field.NotEqual("Value", "purple")]);
|
await conn.DeleteByFields(PostgresDb.TableName, FieldMatch.Any, [Field.NE("Value", "purple")]);
|
||||||
var remaining = await conn.CountAll(PostgresDb.TableName);
|
var remaining = await conn.CountAll(PostgresDb.TableName);
|
||||||
Expect.equal(remaining, 2, "There should have been 2 documents remaining");
|
Expect.equal(remaining, 2, "There should have been 2 documents remaining");
|
||||||
}),
|
}),
|
||||||
@@ -1144,7 +1144,7 @@ public class PostgresCSharpExtensionTests
|
|||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await conn.DeleteByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("Value", "crimson")]);
|
await conn.DeleteByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("Value", "crimson")]);
|
||||||
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");
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ public static class PostgresCSharpTests
|
|||||||
[
|
[
|
||||||
TestCase("succeeds when a parameter is added", () =>
|
TestCase("succeeds when a parameter is added", () =>
|
||||||
{
|
{
|
||||||
var paramList = Parameters.AddFields([Field.Equal("it", "242")], []).ToList();
|
var paramList = Parameters.AddFields([Field.EQ("it", "242")], []).ToList();
|
||||||
Expect.hasLength(paramList, 1, "There should have been a parameter added");
|
Expect.hasLength(paramList, 1, "There should have been a parameter added");
|
||||||
var (name, value) = paramList[0];
|
var (name, value) = paramList[0];
|
||||||
Expect.equal(name, "@field0", "Field parameter name not correct");
|
Expect.equal(name, "@field0", "Field parameter name not correct");
|
||||||
@@ -119,7 +119,7 @@ public static class PostgresCSharpTests
|
|||||||
}),
|
}),
|
||||||
TestCase("succeeds when multiple independent parameters are added", () =>
|
TestCase("succeeds when multiple independent parameters are added", () =>
|
||||||
{
|
{
|
||||||
var paramList = Parameters.AddFields([Field.Equal("me", "you"), Field.Greater("us", "them")],
|
var paramList = Parameters.AddFields([Field.EQ("me", "you"), Field.GT("us", "them")],
|
||||||
[Parameters.Id(14)]).ToList();
|
[Parameters.Id(14)]).ToList();
|
||||||
Expect.hasLength(paramList, 3, "There should have been 2 parameters added");
|
Expect.hasLength(paramList, 3, "There should have been 2 parameters added");
|
||||||
var (name, value) = paramList[0];
|
var (name, value) = paramList[0];
|
||||||
@@ -134,13 +134,13 @@ public static class PostgresCSharpTests
|
|||||||
}),
|
}),
|
||||||
TestCase("succeeds when a parameter is not added", () =>
|
TestCase("succeeds when a parameter is not added", () =>
|
||||||
{
|
{
|
||||||
var paramList = Parameters.AddFields([Field.Exists("tacos")], []).ToList();
|
var paramList = Parameters.AddFields([Field.EX("tacos")], []).ToList();
|
||||||
Expect.isEmpty(paramList, "There should not have been any parameters added");
|
Expect.isEmpty(paramList, "There should not have been any parameters added");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when two parameters are added for one field", () =>
|
TestCase("succeeds when two parameters are added for one field", () =>
|
||||||
{
|
{
|
||||||
var paramList =
|
var paramList =
|
||||||
Parameters.AddFields([Field.Between("that", "eh", "zed").WithParameterName("@test")], []).ToList();
|
Parameters.AddFields([Field.BT("that", "eh", "zed").WithParameterName("@test")], []).ToList();
|
||||||
Expect.hasLength(paramList, 2, "There should have been 2 parameters added");
|
Expect.hasLength(paramList, 2, "There should have been 2 parameters added");
|
||||||
var (name, value) = paramList[0];
|
var (name, value) = paramList[0];
|
||||||
Expect.equal(name, "@testmin", "Minimum field name not correct");
|
Expect.equal(name, "@testmin", "Minimum field name not correct");
|
||||||
@@ -170,11 +170,7 @@ public static class PostgresCSharpTests
|
|||||||
Expect.isTrue(false, "The parameter was not a StringArray type");
|
Expect.isTrue(false, "The parameter was not a StringArray type");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
]),
|
])
|
||||||
TestCase("None succeeds", () =>
|
|
||||||
{
|
|
||||||
Expect.isEmpty(Parameters.None, "The no-params sequence should be empty");
|
|
||||||
})
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -184,60 +180,54 @@ public static class PostgresCSharpTests
|
|||||||
[
|
[
|
||||||
TestList("WhereByFields",
|
TestList("WhereByFields",
|
||||||
[
|
[
|
||||||
TestCase("succeeds for a single field when a logical comparison is passed", () =>
|
TestCase("succeeds for a single field when a logical operator is passed", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(
|
Expect.equal(
|
||||||
Postgres.Query.WhereByFields(FieldMatch.Any,
|
Postgres.Query.WhereByFields(FieldMatch.Any,
|
||||||
[Field.Greater("theField", "0").WithParameterName("@test")]),
|
[Field.GT("theField", "0").WithParameterName("@test")]),
|
||||||
"data->>'theField' > @test", "WHERE clause not correct");
|
"data->>'theField' > @test", "WHERE clause not correct");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds for a single field when an existence comparison is passed", () =>
|
TestCase("succeeds for a single field when an existence operator is passed", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(Postgres.Query.WhereByFields(FieldMatch.Any, [Field.NotExists("thatField")]),
|
Expect.equal(Postgres.Query.WhereByFields(FieldMatch.Any, [Field.NEX("thatField")]),
|
||||||
"data->>'thatField' IS NULL", "WHERE clause not correct");
|
"data->>'thatField' IS NULL", "WHERE clause not correct");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds for a single field when a between comparison is passed with numeric values", () =>
|
TestCase("succeeds for a single field when a between operator is passed with numeric values", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(
|
Expect.equal(
|
||||||
Postgres.Query.WhereByFields(FieldMatch.All,
|
Postgres.Query.WhereByFields(FieldMatch.All,
|
||||||
[Field.Between("aField", 50, 99).WithParameterName("@range")]),
|
[Field.BT("aField", 50, 99).WithParameterName("@range")]),
|
||||||
"(data->>'aField')::numeric BETWEEN @rangemin AND @rangemax", "WHERE clause not correct");
|
"(data->>'aField')::numeric BETWEEN @rangemin AND @rangemax", "WHERE clause not correct");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds for a single field when a between comparison is passed with non-numeric values", () =>
|
TestCase("succeeds for a single field when a between operator is passed with non-numeric values", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(
|
Expect.equal(
|
||||||
Postgres.Query.WhereByFields(FieldMatch.Any,
|
Postgres.Query.WhereByFields(FieldMatch.Any,
|
||||||
[Field.Between("field0", "a", "b").WithParameterName("@alpha")]),
|
[Field.BT("field0", "a", "b").WithParameterName("@alpha")]),
|
||||||
"data->>'field0' BETWEEN @alphamin AND @alphamax", "WHERE clause not correct");
|
"data->>'field0' BETWEEN @alphamin AND @alphamax", "WHERE clause not correct");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds for all multiple fields with logical comparisons", () =>
|
TestCase("succeeds for all multiple fields with logical operators", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(
|
Expect.equal(
|
||||||
Postgres.Query.WhereByFields(FieldMatch.All,
|
Postgres.Query.WhereByFields(FieldMatch.All,
|
||||||
[Field.Equal("theFirst", "1"), Field.Equal("numberTwo", "2")]),
|
[Field.EQ("theFirst", "1"), Field.EQ("numberTwo", "2")]),
|
||||||
"data->>'theFirst' = @field0 AND data->>'numberTwo' = @field1", "WHERE clause not correct");
|
"data->>'theFirst' = @field0 AND data->>'numberTwo' = @field1", "WHERE clause not correct");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds for any multiple fields with an existence comparison", () =>
|
TestCase("succeeds for any multiple fields with an existence operator", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(
|
Expect.equal(
|
||||||
Postgres.Query.WhereByFields(FieldMatch.Any,
|
Postgres.Query.WhereByFields(FieldMatch.Any,
|
||||||
[Field.NotExists("thatField"), Field.GreaterOrEqual("thisField", 18)]),
|
[Field.NEX("thatField"), Field.GE("thisField", 18)]),
|
||||||
"data->>'thatField' IS NULL OR (data->>'thisField')::numeric >= @field0",
|
"data->>'thatField' IS NULL OR (data->>'thisField')::numeric >= @field0",
|
||||||
"WHERE clause not correct");
|
"WHERE clause not correct");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds for all multiple fields with between comparisons", () =>
|
TestCase("succeeds for all multiple fields with between operators", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(
|
Expect.equal(
|
||||||
Postgres.Query.WhereByFields(FieldMatch.All,
|
Postgres.Query.WhereByFields(FieldMatch.All,
|
||||||
[Field.Between("aField", 50, 99), Field.Between("anotherField", "a", "b")]),
|
[Field.BT("aField", 50, 99), Field.BT("anotherField", "a", "b")]),
|
||||||
"(data->>'aField')::numeric BETWEEN @field0min AND @field0max AND data->>'anotherField' BETWEEN @field1min AND @field1max",
|
"(data->>'aField')::numeric BETWEEN @field0min AND @field0max AND data->>'anotherField' BETWEEN @field1min AND @field1max",
|
||||||
"WHERE clause not correct");
|
"WHERE clause not correct");
|
||||||
}),
|
|
||||||
TestCase("succeeds for a field with an InArray comparison", () =>
|
|
||||||
{
|
|
||||||
Expect.equal(
|
|
||||||
Postgres.Query.WhereByFields(FieldMatch.All, [Field.InArray("theField", "the_table", ["q", "r"])]),
|
|
||||||
"data->'theField' ?| @field0", "WHERE clause not correct");
|
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
TestList("WhereById",
|
TestList("WhereById",
|
||||||
@@ -305,7 +295,7 @@ public static class PostgresCSharpTests
|
|||||||
}),
|
}),
|
||||||
TestCase("ByFields succeeds", () =>
|
TestCase("ByFields succeeds", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(Postgres.Query.ByFields("unit", FieldMatch.Any, [Field.Greater("That", 14)]),
|
Expect.equal(Postgres.Query.ByFields("unit", FieldMatch.Any, [Field.GT("That", 14)]),
|
||||||
"unit WHERE (data->>'That')::numeric > @field0", "By-Field query not correct");
|
"unit WHERE (data->>'That')::numeric > @field0", "By-Field query not correct");
|
||||||
}),
|
}),
|
||||||
TestCase("ByContains succeeds", () =>
|
TestCase("ByContains succeeds", () =>
|
||||||
@@ -320,12 +310,30 @@ public static class PostgresCSharpTests
|
|||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests which do not hit the database
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Test Unit = TestList("Unit",
|
||||||
|
[
|
||||||
|
ParametersTests,
|
||||||
|
QueryTests
|
||||||
|
]);
|
||||||
|
|
||||||
|
private static readonly List<JsonDocument> TestDocuments =
|
||||||
|
[
|
||||||
|
new() { Id = "one", Value = "FIRST!", NumValue = 0 },
|
||||||
|
new() { Id = "two", Value = "another", NumValue = 10, Sub = new() { Foo = "green", Bar = "blue" } },
|
||||||
|
new() { Id = "three", Value = "", NumValue = 4 },
|
||||||
|
new() { Id = "four", Value = "purple", NumValue = 17, Sub = new() { Foo = "green", Bar = "red" } },
|
||||||
|
new() { Id = "five", Value = "purple", NumValue = 18 }
|
||||||
|
];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add the test documents to the database
|
/// Add the test documents to the database
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static async Task LoadDocs()
|
internal static async Task LoadDocs()
|
||||||
{
|
{
|
||||||
foreach (var doc in JsonDocument.TestDocuments) await Document.Insert(SqliteDb.TableName, doc);
|
foreach (var doc in TestDocuments) await Document.Insert(SqliteDb.TableName, doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -540,83 +548,6 @@ public static class PostgresCSharpTests
|
|||||||
{
|
{
|
||||||
// This is what should have happened
|
// This is what should have happened
|
||||||
}
|
}
|
||||||
}),
|
|
||||||
TestCase("succeeds when adding a numeric auto ID", async () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Configuration.UseAutoIdStrategy(AutoId.Number);
|
|
||||||
Configuration.UseIdField("Key");
|
|
||||||
await using var db = PostgresDb.BuildDb();
|
|
||||||
var before = await Count.All(PostgresDb.TableName);
|
|
||||||
Expect.equal(before, 0, "There should be no documents in the table");
|
|
||||||
|
|
||||||
await Document.Insert(PostgresDb.TableName, new NumIdDocument { Text = "one" });
|
|
||||||
await Document.Insert(PostgresDb.TableName, new NumIdDocument { Text = "two" });
|
|
||||||
await Document.Insert(PostgresDb.TableName, new NumIdDocument { Key = 77, Text = "three" });
|
|
||||||
await Document.Insert(PostgresDb.TableName, new NumIdDocument { Text = "four" });
|
|
||||||
|
|
||||||
var after = await Find.AllOrdered<NumIdDocument>(PostgresDb.TableName, [Field.Named("n:Key")]);
|
|
||||||
Expect.hasLength(after, 4, "There should have been 4 documents returned");
|
|
||||||
Expect.sequenceEqual(after.Select(x => x.Key), [1, 2, 77, 78],
|
|
||||||
"The IDs were not generated correctly");
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Configuration.UseAutoIdStrategy(AutoId.Disabled);
|
|
||||||
Configuration.UseIdField("Id");
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
TestCase("succeeds when adding a GUID auto ID", async () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Configuration.UseAutoIdStrategy(AutoId.Guid);
|
|
||||||
await using var db = PostgresDb.BuildDb();
|
|
||||||
var before = await Count.All(PostgresDb.TableName);
|
|
||||||
Expect.equal(before, 0, "There should be no documents in the table");
|
|
||||||
|
|
||||||
await Document.Insert(PostgresDb.TableName, new JsonDocument { Value = "one" });
|
|
||||||
await Document.Insert(PostgresDb.TableName, new JsonDocument { Value = "two" });
|
|
||||||
await Document.Insert(PostgresDb.TableName, new JsonDocument { Id = "abc123", Value = "three" });
|
|
||||||
await Document.Insert(PostgresDb.TableName, new JsonDocument { Value = "four" });
|
|
||||||
|
|
||||||
var after = await Find.All<JsonDocument>(PostgresDb.TableName);
|
|
||||||
Expect.hasLength(after, 4, "There should have been 4 documents returned");
|
|
||||||
Expect.equal(after.Count(x => x.Id.Length == 32), 3, "Three of the IDs should have been GUIDs");
|
|
||||||
Expect.equal(after.Count(x => x.Id == "abc123"), 1, "The provided ID should have been used as-is");
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Configuration.UseAutoIdStrategy(AutoId.Disabled);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
TestCase("succeeds when adding a RandomString auto ID", async () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Configuration.UseAutoIdStrategy(AutoId.RandomString);
|
|
||||||
Configuration.UseIdStringLength(44);
|
|
||||||
await using var db = PostgresDb.BuildDb();
|
|
||||||
var before = await Count.All(PostgresDb.TableName);
|
|
||||||
Expect.equal(before, 0, "There should be no documents in the table");
|
|
||||||
|
|
||||||
await Document.Insert(PostgresDb.TableName, new JsonDocument { Value = "one" });
|
|
||||||
await Document.Insert(PostgresDb.TableName, new JsonDocument { Value = "two" });
|
|
||||||
await Document.Insert(PostgresDb.TableName, new JsonDocument { Id = "abc123", Value = "three" });
|
|
||||||
await Document.Insert(PostgresDb.TableName, new JsonDocument { Value = "four" });
|
|
||||||
|
|
||||||
var after = await Find.All<JsonDocument>(PostgresDb.TableName);
|
|
||||||
Expect.hasLength(after, 4, "There should have been 4 documents returned");
|
|
||||||
Expect.equal(after.Count(x => x.Id.Length == 44), 3,
|
|
||||||
"Three of the IDs should have been 44-character random strings");
|
|
||||||
Expect.equal(after.Count(x => x.Id == "abc123"), 1, "The provided ID should have been used as-is");
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Configuration.UseAutoIdStrategy(AutoId.Disabled);
|
|
||||||
Configuration.UseIdStringLength(16);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
TestList("Save",
|
TestList("Save",
|
||||||
@@ -673,7 +604,7 @@ public static class PostgresCSharpTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var theCount = await Count.ByFields(PostgresDb.TableName, FieldMatch.Any,
|
var theCount = await Count.ByFields(PostgresDb.TableName, FieldMatch.Any,
|
||||||
[Field.Between("NumValue", 10, 20)]);
|
[Field.BT("NumValue", 10, 20)]);
|
||||||
Expect.equal(theCount, 3, "There should have been 3 matching documents");
|
Expect.equal(theCount, 3, "There should have been 3 matching documents");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds for non-numeric range", async () =>
|
TestCase("succeeds for non-numeric range", async () =>
|
||||||
@@ -682,7 +613,7 @@ public static class PostgresCSharpTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var theCount = await Count.ByFields(PostgresDb.TableName, FieldMatch.All,
|
var theCount = await Count.ByFields(PostgresDb.TableName, FieldMatch.All,
|
||||||
[Field.Between("Value", "aardvark", "apple")]);
|
[Field.BT("Value", "aardvark", "apple")]);
|
||||||
Expect.equal(theCount, 1, "There should have been 1 matching document");
|
Expect.equal(theCount, 1, "There should have been 1 matching document");
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
@@ -735,7 +666,7 @@ public static class PostgresCSharpTests
|
|||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var exists = await Exists.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.NotExists("Sub")]);
|
var exists = await Exists.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.NEX("Sub")]);
|
||||||
Expect.isTrue(exists, "There should have been existing documents");
|
Expect.isTrue(exists, "There should have been existing documents");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when documents do not exist", async () =>
|
TestCase("succeeds when documents do not exist", async () =>
|
||||||
@@ -743,8 +674,7 @@ public static class PostgresCSharpTests
|
|||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var exists = await Exists.ByFields(PostgresDb.TableName, FieldMatch.Any,
|
var exists = await Exists.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", "six")]);
|
||||||
[Field.Equal("NumValue", "six")]);
|
|
||||||
Expect.isFalse(exists, "There should not have been existing documents");
|
Expect.isFalse(exists, "There should not have been existing documents");
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
@@ -876,16 +806,7 @@ public static class PostgresCSharpTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var docs = await Find.ByFields<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
var docs = await Find.ByFields<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Value", "another")]);
|
[Field.EQ("Value", "another")]);
|
||||||
Expect.hasLength(docs, 1, "There should have been one document returned");
|
|
||||||
}),
|
|
||||||
TestCase("succeeds when documents are found using IN with numeric field", async () =>
|
|
||||||
{
|
|
||||||
await using var db = PostgresDb.BuildDb();
|
|
||||||
await LoadDocs();
|
|
||||||
|
|
||||||
var docs = await Find.ByFields<JsonDocument>(PostgresDb.TableName, FieldMatch.All,
|
|
||||||
[Field.In("NumValue", [2, 4, 6, 8])]);
|
|
||||||
Expect.hasLength(docs, 1, "There should have been one document returned");
|
Expect.hasLength(docs, 1, "There should have been one document returned");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when documents are not found", async () =>
|
TestCase("succeeds when documents are not found", async () =>
|
||||||
@@ -894,27 +815,7 @@ public static class PostgresCSharpTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var docs = await Find.ByFields<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
var docs = await Find.ByFields<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Value", "mauve")]);
|
[Field.EQ("Value", "mauve")]);
|
||||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
|
||||||
}),
|
|
||||||
TestCase("succeeds for InArray when matching documents exist", async () =>
|
|
||||||
{
|
|
||||||
await using var db = PostgresDb.BuildDb();
|
|
||||||
await Definition.EnsureTable(PostgresDb.TableName);
|
|
||||||
foreach (var doc in ArrayDocument.TestDocuments) await Document.Insert(PostgresDb.TableName, doc);
|
|
||||||
|
|
||||||
var docs = await Find.ByFields<ArrayDocument>(PostgresDb.TableName, FieldMatch.All,
|
|
||||||
[Field.InArray("Values", PostgresDb.TableName, ["c"])]);
|
|
||||||
Expect.hasLength(docs, 2, "There should have been two document returned");
|
|
||||||
}),
|
|
||||||
TestCase("succeeds for InArray when no matching documents exist", async () =>
|
|
||||||
{
|
|
||||||
await using var db = PostgresDb.BuildDb();
|
|
||||||
await Definition.EnsureTable(PostgresDb.TableName);
|
|
||||||
foreach (var doc in ArrayDocument.TestDocuments) await Document.Insert(PostgresDb.TableName, doc);
|
|
||||||
|
|
||||||
var docs = await Find.ByFields<ArrayDocument>(PostgresDb.TableName, FieldMatch.All,
|
|
||||||
[Field.InArray("Values", PostgresDb.TableName, ["j"])]);
|
|
||||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
@@ -926,7 +827,7 @@ public static class PostgresCSharpTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var docs = await Find.ByFieldsOrdered<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
var docs = await Find.ByFieldsOrdered<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Value", "purple")], [Field.Named("Id")]);
|
[Field.EQ("Value", "purple")], [Field.Named("Id")]);
|
||||||
Expect.hasLength(docs, 2, "There should have been two document returned");
|
Expect.hasLength(docs, 2, "There should have been two document returned");
|
||||||
Expect.equal(string.Join('|', docs.Select(x => x.Id)), "five|four",
|
Expect.equal(string.Join('|', docs.Select(x => x.Id)), "five|four",
|
||||||
"The documents were not ordered correctly");
|
"The documents were not ordered correctly");
|
||||||
@@ -937,7 +838,7 @@ public static class PostgresCSharpTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var docs = await Find.ByFieldsOrdered<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
var docs = await Find.ByFieldsOrdered<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Value", "purple")], [Field.Named("Id DESC")]);
|
[Field.EQ("Value", "purple")], [Field.Named("Id DESC")]);
|
||||||
Expect.hasLength(docs, 2, "There should have been two document returned");
|
Expect.hasLength(docs, 2, "There should have been two document returned");
|
||||||
Expect.equal(string.Join('|', docs.Select(x => x.Id)), "four|five",
|
Expect.equal(string.Join('|', docs.Select(x => x.Id)), "four|five",
|
||||||
"The documents were not ordered correctly");
|
"The documents were not ordered correctly");
|
||||||
@@ -1042,7 +943,7 @@ public static class PostgresCSharpTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await Find.FirstByFields<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
var doc = await Find.FirstByFields<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Value", "another")]);
|
[Field.EQ("Value", "another")]);
|
||||||
Expect.isNotNull(doc, "There should have been a document returned");
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
Expect.equal(doc.Id, "two", "The incorrect document was returned");
|
Expect.equal(doc.Id, "two", "The incorrect document was returned");
|
||||||
}),
|
}),
|
||||||
@@ -1052,7 +953,7 @@ public static class PostgresCSharpTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await Find.FirstByFields<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
var doc = await Find.FirstByFields<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Value", "purple")]);
|
[Field.EQ("Value", "purple")]);
|
||||||
Expect.isNotNull(doc, "There should have been a document returned");
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
Expect.contains(["five", "four"], doc.Id, "An incorrect document was returned");
|
Expect.contains(["five", "four"], doc.Id, "An incorrect document was returned");
|
||||||
}),
|
}),
|
||||||
@@ -1062,7 +963,7 @@ public static class PostgresCSharpTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await Find.FirstByFields<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
var doc = await Find.FirstByFields<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Value", "absent")]);
|
[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");
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
@@ -1074,7 +975,7 @@ public static class PostgresCSharpTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await Find.FirstByFieldsOrdered<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
var doc = await Find.FirstByFieldsOrdered<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Value", "purple")], [Field.Named("Id")]);
|
[Field.EQ("Value", "purple")], [Field.Named("Id")]);
|
||||||
Expect.isNotNull(doc, "There should have been a document returned");
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
Expect.equal("five", doc.Id, "An incorrect document was returned");
|
Expect.equal("five", doc.Id, "An incorrect document was returned");
|
||||||
}),
|
}),
|
||||||
@@ -1084,7 +985,7 @@ public static class PostgresCSharpTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await Find.FirstByFieldsOrdered<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
var doc = await Find.FirstByFieldsOrdered<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Value", "purple")], [Field.Named("Id DESC")]);
|
[Field.EQ("Value", "purple")], [Field.Named("Id DESC")]);
|
||||||
Expect.isNotNull(doc, "There should have been a document returned");
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
Expect.equal("four", doc.Id, "An incorrect document was returned");
|
Expect.equal("four", doc.Id, "An incorrect document was returned");
|
||||||
})
|
})
|
||||||
@@ -1298,9 +1199,9 @@ public static class PostgresCSharpTests
|
|||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await Patch.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("Value", "purple")],
|
await Patch.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("Value", "purple")],
|
||||||
new { NumValue = 77 });
|
new { NumValue = 77 });
|
||||||
var after = await Count.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", "77")]);
|
var after = await Count.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", "77")]);
|
||||||
Expect.equal(after, 2, "There should have been 2 documents returned");
|
Expect.equal(after, 2, "There should have been 2 documents returned");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when no document is updated", async () =>
|
TestCase("succeeds when no document is updated", async () =>
|
||||||
@@ -1311,7 +1212,7 @@ public static class PostgresCSharpTests
|
|||||||
Expect.equal(before, 0, "There should have been no documents returned");
|
Expect.equal(before, 0, "There should have been no documents returned");
|
||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await Patch.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("Value", "burgundy")],
|
await Patch.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("Value", "burgundy")],
|
||||||
new { Foo = "green" });
|
new { Foo = "green" });
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
@@ -1413,7 +1314,7 @@ public static class PostgresCSharpTests
|
|||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await RemoveFields.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", "17")],
|
await RemoveFields.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", "17")],
|
||||||
["Sub", "Value"]);
|
["Sub", "Value"]);
|
||||||
var updated = await Find.ById<string, JsonDocument>(PostgresDb.TableName, "four");
|
var updated = await Find.ById<string, JsonDocument>(PostgresDb.TableName, "four");
|
||||||
Expect.isNotNull(updated, "The updated document should have been retrieved");
|
Expect.isNotNull(updated, "The updated document should have been retrieved");
|
||||||
@@ -1425,7 +1326,7 @@ public static class PostgresCSharpTests
|
|||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await RemoveFields.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", "17")],
|
await RemoveFields.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", "17")],
|
||||||
["Sub"]);
|
["Sub"]);
|
||||||
var updated = await Find.ById<string, JsonDocument>(PostgresDb.TableName, "four");
|
var updated = await Find.ById<string, JsonDocument>(PostgresDb.TableName, "four");
|
||||||
Expect.isNotNull(updated, "The updated document should have been retrieved");
|
Expect.isNotNull(updated, "The updated document should have been retrieved");
|
||||||
@@ -1438,7 +1339,7 @@ public static class PostgresCSharpTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await RemoveFields.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", "17")],
|
await RemoveFields.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", "17")],
|
||||||
["Nothing"]);
|
["Nothing"]);
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when no document is matched", async () =>
|
TestCase("succeeds when no document is matched", async () =>
|
||||||
@@ -1446,8 +1347,8 @@ public static class PostgresCSharpTests
|
|||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await RemoveFields.ByFields(PostgresDb.TableName, FieldMatch.Any,
|
await RemoveFields.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.NE("Abracadabra", "apple")],
|
||||||
[Field.NotEqual("Abracadabra", "apple")], ["Value"]);
|
["Value"]);
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
TestList("ByContains",
|
TestList("ByContains",
|
||||||
@@ -1565,7 +1466,7 @@ public static class PostgresCSharpTests
|
|||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await Delete.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("Value", "purple")]);
|
await Delete.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("Value", "purple")]);
|
||||||
var remaining = await Count.All(PostgresDb.TableName);
|
var remaining = await Count.All(PostgresDb.TableName);
|
||||||
Expect.equal(remaining, 3, "There should have been 3 documents remaining");
|
Expect.equal(remaining, 3, "There should have been 3 documents remaining");
|
||||||
}),
|
}),
|
||||||
@@ -1574,7 +1475,7 @@ public static class PostgresCSharpTests
|
|||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await Delete.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("Value", "crimson")]);
|
await Delete.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("Value", "crimson")]);
|
||||||
var remaining = await Count.All(PostgresDb.TableName);
|
var remaining = await Count.All(PostgresDb.TableName);
|
||||||
Expect.equal(remaining, 5, "There should have been 5 documents remaining");
|
Expect.equal(remaining, 5, "There should have been 5 documents remaining");
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using BitBadger.Documents.Postgres;
|
||||||
using Npgsql;
|
using Npgsql;
|
||||||
using Npgsql.FSharp;
|
using Npgsql.FSharp;
|
||||||
using ThrowawayDb.Postgres;
|
using ThrowawayDb.Postgres;
|
||||||
|
|||||||
@@ -221,8 +221,7 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await using var conn = Sqlite.Configuration.DbConn();
|
await using var conn = Sqlite.Configuration.DbConn();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var theCount = await conn.CountByFields(SqliteDb.TableName, FieldMatch.Any,
|
var theCount = await conn.CountByFields(SqliteDb.TableName, FieldMatch.Any, [Field.EQ("Value", "purple")]);
|
||||||
[Field.Equal("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",
|
TestList("ExistsById",
|
||||||
@@ -254,8 +253,7 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await using var conn = Sqlite.Configuration.DbConn();
|
await using var conn = Sqlite.Configuration.DbConn();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var exists = await conn.ExistsByFields(SqliteDb.TableName, FieldMatch.Any,
|
var exists = await conn.ExistsByFields(SqliteDb.TableName, FieldMatch.Any, [Field.GE("NumValue", 10)]);
|
||||||
[Field.GreaterOrEqual("NumValue", 10)]);
|
|
||||||
Expect.isTrue(exists, "There should have been existing documents");
|
Expect.isTrue(exists, "There should have been existing documents");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when no matching documents exist", async () =>
|
TestCase("succeeds when no matching documents exist", async () =>
|
||||||
@@ -265,7 +263,7 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var exists =
|
var exists =
|
||||||
await conn.ExistsByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("Nothing", "none")]);
|
await conn.ExistsByFields(SqliteDb.TableName, FieldMatch.Any, [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");
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
@@ -359,7 +357,7 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var docs = await conn.FindByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
var docs = await conn.FindByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
[Field.Greater("NumValue", 15)]);
|
[Field.GT("NumValue", 15)]);
|
||||||
Expect.equal(docs.Count, 2, "There should have been two documents returned");
|
Expect.equal(docs.Count, 2, "There should have been two documents returned");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when documents are not found", async () =>
|
TestCase("succeeds when documents are not found", async () =>
|
||||||
@@ -369,7 +367,7 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var docs = await conn.FindByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
var docs = await conn.FindByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Value", "mauve")]);
|
[Field.EQ("Value", "mauve")]);
|
||||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
@@ -382,7 +380,7 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var docs = await conn.FindByFieldsOrdered<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
var docs = await conn.FindByFieldsOrdered<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
[Field.Greater("NumValue", 15)], [Field.Named("Id")]);
|
[Field.GT("NumValue", 15)], [Field.Named("Id")]);
|
||||||
Expect.equal(string.Join('|', docs.Select(x => x.Id)), "five|four",
|
Expect.equal(string.Join('|', docs.Select(x => x.Id)), "five|four",
|
||||||
"There should have been two documents returned");
|
"There should have been two documents returned");
|
||||||
}),
|
}),
|
||||||
@@ -393,7 +391,7 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var docs = await conn.FindByFieldsOrdered<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
var docs = await conn.FindByFieldsOrdered<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
[Field.Greater("NumValue", 15)], [Field.Named("Id DESC")]);
|
[Field.GT("NumValue", 15)], [Field.Named("Id DESC")]);
|
||||||
Expect.equal(string.Join('|', docs.Select(x => x.Id)), "four|five",
|
Expect.equal(string.Join('|', docs.Select(x => x.Id)), "four|five",
|
||||||
"There should have been two documents returned");
|
"There should have been two documents returned");
|
||||||
})
|
})
|
||||||
@@ -407,7 +405,7 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await conn.FindFirstByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
var doc = await conn.FindFirstByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Value", "another")]);
|
[Field.EQ("Value", "another")]);
|
||||||
Expect.isNotNull(doc, "There should have been a document returned");
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
Expect.equal(doc!.Id, "two", "The incorrect document was returned");
|
Expect.equal(doc!.Id, "two", "The incorrect document was returned");
|
||||||
}),
|
}),
|
||||||
@@ -418,7 +416,7 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await conn.FindFirstByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
var doc = await conn.FindFirstByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Sub.Foo", "green")]);
|
[Field.EQ("Sub.Foo", "green")]);
|
||||||
Expect.isNotNull(doc, "There should have been a document returned");
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
Expect.contains(["two", "four"], doc!.Id, "An incorrect document was returned");
|
Expect.contains(["two", "four"], doc!.Id, "An incorrect document was returned");
|
||||||
}),
|
}),
|
||||||
@@ -429,7 +427,7 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await conn.FindFirstByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
var doc = await conn.FindFirstByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Value", "absent")]);
|
[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");
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
@@ -442,7 +440,7 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await conn.FindFirstByFieldsOrdered<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
var doc = await conn.FindFirstByFieldsOrdered<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Sub.Foo", "green")], [Field.Named("Sub.Bar")]);
|
[Field.EQ("Sub.Foo", "green")], [Field.Named("Sub.Bar")]);
|
||||||
Expect.isNotNull(doc, "There should have been a document returned");
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
Expect.equal("two", doc!.Id, "An incorrect document was returned");
|
Expect.equal("two", doc!.Id, "An incorrect document was returned");
|
||||||
}),
|
}),
|
||||||
@@ -453,7 +451,7 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await conn.FindFirstByFieldsOrdered<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
var doc = await conn.FindFirstByFieldsOrdered<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Sub.Foo", "green")], [Field.Named("Sub.Bar DESC")]);
|
[Field.EQ("Sub.Foo", "green")], [Field.Named("Sub.Bar DESC")]);
|
||||||
Expect.isNotNull(doc, "There should have been a document returned");
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
Expect.equal("four", doc!.Id, "An incorrect document was returned");
|
Expect.equal("four", doc!.Id, "An incorrect document was returned");
|
||||||
})
|
})
|
||||||
@@ -549,9 +547,9 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await using var conn = Sqlite.Configuration.DbConn();
|
await using var conn = Sqlite.Configuration.DbConn();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await conn.PatchByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("Value", "purple")],
|
await conn.PatchByFields(SqliteDb.TableName, FieldMatch.Any, [Field.EQ("Value", "purple")],
|
||||||
new { NumValue = 77 });
|
new { NumValue = 77 });
|
||||||
var after = await conn.CountByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", 77)]);
|
var after = await conn.CountByFields(SqliteDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", 77)]);
|
||||||
Expect.equal(after, 2L, "There should have been 2 documents returned");
|
Expect.equal(after, 2L, "There should have been 2 documents returned");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when no document is updated", async () =>
|
TestCase("succeeds when no document is updated", async () =>
|
||||||
@@ -562,7 +560,7 @@ public static class SqliteCSharpExtensionTests
|
|||||||
Expect.isEmpty(before, "There should have been no documents returned");
|
Expect.isEmpty(before, "There should have been no documents returned");
|
||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await conn.PatchByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("Value", "burgundy")],
|
await conn.PatchByFields(SqliteDb.TableName, FieldMatch.Any, [Field.EQ("Value", "burgundy")],
|
||||||
new { Foo = "green" });
|
new { Foo = "green" });
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
@@ -606,7 +604,7 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await using var conn = Sqlite.Configuration.DbConn();
|
await using var conn = Sqlite.Configuration.DbConn();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await conn.RemoveFieldsByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", 17)],
|
await conn.RemoveFieldsByFields(SqliteDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", 17)],
|
||||||
["Sub"]);
|
["Sub"]);
|
||||||
var updated = await Find.ById<string, JsonDocument>(SqliteDb.TableName, "four");
|
var updated = await Find.ById<string, JsonDocument>(SqliteDb.TableName, "four");
|
||||||
Expect.isNotNull(updated, "The updated document should have been retrieved");
|
Expect.isNotNull(updated, "The updated document should have been retrieved");
|
||||||
@@ -619,7 +617,7 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await conn.RemoveFieldsByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", 17)],
|
await conn.RemoveFieldsByFields(SqliteDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", 17)],
|
||||||
["Nothing"]);
|
["Nothing"]);
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when no document is matched", async () =>
|
TestCase("succeeds when no document is matched", async () =>
|
||||||
@@ -628,8 +626,8 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await using var conn = Sqlite.Configuration.DbConn();
|
await using var conn = Sqlite.Configuration.DbConn();
|
||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await conn.RemoveFieldsByFields(SqliteDb.TableName, FieldMatch.Any,
|
await conn.RemoveFieldsByFields(SqliteDb.TableName, FieldMatch.Any, [Field.NE("Abracadabra", "apple")],
|
||||||
[Field.NotEqual("Abracadabra", "apple")], ["Value"]);
|
["Value"]);
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
TestList("DeleteById",
|
TestList("DeleteById",
|
||||||
@@ -663,7 +661,7 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await using var conn = Sqlite.Configuration.DbConn();
|
await using var conn = Sqlite.Configuration.DbConn();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await conn.DeleteByFields(SqliteDb.TableName, FieldMatch.Any, [Field.NotEqual("Value", "purple")]);
|
await conn.DeleteByFields(SqliteDb.TableName, FieldMatch.Any, [Field.NE("Value", "purple")]);
|
||||||
var remaining = await conn.CountAll(SqliteDb.TableName);
|
var remaining = await conn.CountAll(SqliteDb.TableName);
|
||||||
Expect.equal(remaining, 2L, "There should have been 2 documents remaining");
|
Expect.equal(remaining, 2L, "There should have been 2 documents remaining");
|
||||||
}),
|
}),
|
||||||
@@ -673,7 +671,7 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await using var conn = Sqlite.Configuration.DbConn();
|
await using var conn = Sqlite.Configuration.DbConn();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await conn.DeleteByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("Value", "crimson")]);
|
await conn.DeleteByFields(SqliteDb.TableName, FieldMatch.Any, [Field.EQ("Value", "crimson")]);
|
||||||
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");
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -22,55 +22,40 @@ public static class SqliteCSharpTests
|
|||||||
TestCase("succeeds for a single field when a logical operator is passed", () =>
|
TestCase("succeeds for a single field when a logical operator is passed", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(
|
Expect.equal(
|
||||||
Sqlite.Query.WhereByFields(FieldMatch.Any,
|
Sqlite.Query.WhereByFields(FieldMatch.Any, [Field.GT("theField", 0).WithParameterName("@test")]),
|
||||||
[Field.Greater("theField", 0).WithParameterName("@test")]),
|
|
||||||
"data->>'theField' > @test", "WHERE clause not correct");
|
"data->>'theField' > @test", "WHERE clause not correct");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds for a single field when an existence operator is passed", () =>
|
TestCase("succeeds for a single field when an existence operator is passed", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(Sqlite.Query.WhereByFields(FieldMatch.Any, [Field.NotExists("thatField")]),
|
Expect.equal(Sqlite.Query.WhereByFields(FieldMatch.Any, [Field.NEX("thatField")]),
|
||||||
"data->>'thatField' IS NULL", "WHERE clause not correct");
|
"data->>'thatField' IS NULL", "WHERE clause not correct");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds for a single field when a between operator is passed", () =>
|
TestCase("succeeds for a single field when a between operator is passed", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(
|
Expect.equal(
|
||||||
Sqlite.Query.WhereByFields(FieldMatch.All,
|
Sqlite.Query.WhereByFields(FieldMatch.All,
|
||||||
[Field.Between("aField", 50, 99).WithParameterName("@range")]),
|
[Field.BT("aField", 50, 99).WithParameterName("@range")]),
|
||||||
"data->>'aField' BETWEEN @rangemin AND @rangemax", "WHERE clause not correct");
|
"data->>'aField' BETWEEN @rangemin AND @rangemax", "WHERE clause not correct");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds for all multiple fields with logical operators", () =>
|
TestCase("succeeds for all multiple fields with logical operators", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(
|
Expect.equal(
|
||||||
Sqlite.Query.WhereByFields(FieldMatch.All,
|
Sqlite.Query.WhereByFields(FieldMatch.All, [Field.EQ("theFirst", "1"), Field.EQ("numberTwo", "2")]),
|
||||||
[Field.Equal("theFirst", "1"), Field.Equal("numberTwo", "2")]),
|
|
||||||
"data->>'theFirst' = @field0 AND data->>'numberTwo' = @field1", "WHERE clause not correct");
|
"data->>'theFirst' = @field0 AND data->>'numberTwo' = @field1", "WHERE clause not correct");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds for any multiple fields with an existence operator", () =>
|
TestCase("succeeds for any multiple fields with an existence operator", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(
|
Expect.equal(
|
||||||
Sqlite.Query.WhereByFields(FieldMatch.Any,
|
Sqlite.Query.WhereByFields(FieldMatch.Any, [Field.NEX("thatField"), Field.GE("thisField", 18)]),
|
||||||
[Field.NotExists("thatField"), Field.GreaterOrEqual("thisField", 18)]),
|
|
||||||
"data->>'thatField' IS NULL OR data->>'thisField' >= @field0", "WHERE clause not correct");
|
"data->>'thatField' IS NULL OR data->>'thisField' >= @field0", "WHERE clause not correct");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds for all multiple fields with between operators", () =>
|
TestCase("succeeds for all multiple fields with between operators", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(
|
Expect.equal(
|
||||||
Sqlite.Query.WhereByFields(FieldMatch.All,
|
Sqlite.Query.WhereByFields(FieldMatch.All,
|
||||||
[Field.Between("aField", 50, 99), Field.Between("anotherField", "a", "b")]),
|
[Field.BT("aField", 50, 99), Field.BT("anotherField", "a", "b")]),
|
||||||
"data->>'aField' BETWEEN @field0min AND @field0max AND data->>'anotherField' BETWEEN @field1min AND @field1max",
|
"data->>'aField' BETWEEN @field0min AND @field0max AND data->>'anotherField' BETWEEN @field1min AND @field1max",
|
||||||
"WHERE clause not correct");
|
"WHERE clause not correct");
|
||||||
}),
|
|
||||||
TestCase("succeeds for a field with an In comparison", () =>
|
|
||||||
{
|
|
||||||
Expect.equal(Sqlite.Query.WhereByFields(FieldMatch.All, [Field.In("this", ["a", "b", "c"])]),
|
|
||||||
"data->>'this' IN (@field0_0, @field0_1, @field0_2)", "WHERE clause not correct");
|
|
||||||
}),
|
|
||||||
TestCase("succeeds for a field with an InArray comparison", () =>
|
|
||||||
{
|
|
||||||
Expect.equal(
|
|
||||||
Sqlite.Query.WhereByFields(FieldMatch.All, [Field.InArray("this", "the_table", ["a", "b"])]),
|
|
||||||
"EXISTS (SELECT 1 FROM json_each(the_table.data, '$.this') WHERE value IN (@field0_0, @field0_1))",
|
|
||||||
"WHERE clause not correct");
|
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
TestCase("WhereById succeeds", () =>
|
TestCase("WhereById succeeds", () =>
|
||||||
@@ -94,7 +79,7 @@ public static class SqliteCSharpTests
|
|||||||
}),
|
}),
|
||||||
TestCase("ByFields succeeds", () =>
|
TestCase("ByFields succeeds", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(Sqlite.Query.ByFields("unit", FieldMatch.Any, [Field.Greater("That", 14)]),
|
Expect.equal(Sqlite.Query.ByFields("unit", FieldMatch.Any, [Field.GT("That", 14)]),
|
||||||
"unit WHERE data->>'That' > @field0", "By-Field query not correct");
|
"unit WHERE data->>'That' > @field0", "By-Field query not correct");
|
||||||
}),
|
}),
|
||||||
TestCase("Definition.EnsureTable succeeds", () =>
|
TestCase("Definition.EnsureTable succeeds", () =>
|
||||||
@@ -124,7 +109,7 @@ public static class SqliteCSharpTests
|
|||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
TestCase("AddField succeeds when adding a parameter", () =>
|
TestCase("AddField succeeds when adding a parameter", () =>
|
||||||
{
|
{
|
||||||
var paramList = Parameters.AddField("@field", Field.Equal("it", 99), []).ToList();
|
var paramList = Parameters.AddField("@field", Field.EQ("it", 99), []).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");
|
||||||
@@ -132,7 +117,7 @@ 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.Exists("Coffee"), []);
|
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
|
#pragma warning restore CS0618
|
||||||
@@ -144,12 +129,24 @@ public static class SqliteCSharpTests
|
|||||||
|
|
||||||
// Results are exhaustively executed in the context of other tests
|
// Results are exhaustively executed in the context of other tests
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Documents used for integration tests
|
||||||
|
/// </summary>
|
||||||
|
private static readonly List<JsonDocument> TestDocuments =
|
||||||
|
[
|
||||||
|
new() { Id = "one", Value = "FIRST!", NumValue = 0 },
|
||||||
|
new() { Id = "two", Value = "another", NumValue = 10, Sub = new() { Foo = "green", Bar = "blue" } },
|
||||||
|
new() { Id = "three", Value = "", NumValue = 4 },
|
||||||
|
new() { Id = "four", Value = "purple", NumValue = 17, Sub = new() { Foo = "green", Bar = "red" } },
|
||||||
|
new() { Id = "five", Value = "purple", NumValue = 18 }
|
||||||
|
];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add the test documents to the database
|
/// Add the test documents to the database
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static async Task LoadDocs()
|
internal static async Task LoadDocs()
|
||||||
{
|
{
|
||||||
foreach (var doc in JsonDocument.TestDocuments) await Document.Insert(SqliteDb.TableName, doc);
|
foreach (var doc in TestDocuments) await Document.Insert(SqliteDb.TableName, doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -328,86 +325,9 @@ public static class SqliteCSharpTests
|
|||||||
{
|
{
|
||||||
// This is what is supposed to happen
|
// This is what is supposed to happen
|
||||||
}
|
}
|
||||||
}),
|
|
||||||
TestCase("succeeds when adding a numeric auto ID", async () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Configuration.UseAutoIdStrategy(AutoId.Number);
|
|
||||||
Configuration.UseIdField("Key");
|
|
||||||
await using var db = await SqliteDb.BuildDb();
|
|
||||||
var before = await Count.All(SqliteDb.TableName);
|
|
||||||
Expect.equal(before, 0L, "There should be no documents in the table");
|
|
||||||
|
|
||||||
await Document.Insert(SqliteDb.TableName, new NumIdDocument { Text = "one" });
|
|
||||||
await Document.Insert(SqliteDb.TableName, new NumIdDocument { Text = "two" });
|
|
||||||
await Document.Insert(SqliteDb.TableName, new NumIdDocument { Key = 77, Text = "three" });
|
|
||||||
await Document.Insert(SqliteDb.TableName, new NumIdDocument { Text = "four" });
|
|
||||||
|
|
||||||
var after = await Find.AllOrdered<NumIdDocument>(SqliteDb.TableName, [Field.Named("Key")]);
|
|
||||||
Expect.hasLength(after, 4, "There should have been 4 documents returned");
|
|
||||||
Expect.sequenceEqual(after.Select(x => x.Key), [1, 2, 77, 78],
|
|
||||||
"The IDs were not generated correctly");
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Configuration.UseAutoIdStrategy(AutoId.Disabled);
|
|
||||||
Configuration.UseIdField("Id");
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
TestCase("succeeds when adding a GUID auto ID", async () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Configuration.UseAutoIdStrategy(AutoId.Guid);
|
|
||||||
await using var db = await SqliteDb.BuildDb();
|
|
||||||
var before = await Count.All(SqliteDb.TableName);
|
|
||||||
Expect.equal(before, 0L, "There should be no documents in the table");
|
|
||||||
|
|
||||||
await Document.Insert(SqliteDb.TableName, new JsonDocument { Value = "one" });
|
|
||||||
await Document.Insert(SqliteDb.TableName, new JsonDocument { Value = "two" });
|
|
||||||
await Document.Insert(SqliteDb.TableName, new JsonDocument { Id = "abc123", Value = "three" });
|
|
||||||
await Document.Insert(SqliteDb.TableName, new JsonDocument { Value = "four" });
|
|
||||||
|
|
||||||
var after = await Find.All<JsonDocument>(SqliteDb.TableName);
|
|
||||||
Expect.hasLength(after, 4, "There should have been 4 documents returned");
|
|
||||||
Expect.equal(after.Count(x => x.Id.Length == 32), 3, "Three of the IDs should have been GUIDs");
|
|
||||||
Expect.equal(after.Count(x => x.Id == "abc123"), 1, "The provided ID should have been used as-is");
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Configuration.UseAutoIdStrategy(AutoId.Disabled);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
TestCase("succeeds when adding a RandomString auto ID", async () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Configuration.UseAutoIdStrategy(AutoId.RandomString);
|
|
||||||
Configuration.UseIdStringLength(44);
|
|
||||||
await using var db = await SqliteDb.BuildDb();
|
|
||||||
var before = await Count.All(SqliteDb.TableName);
|
|
||||||
Expect.equal(before, 0L, "There should be no documents in the table");
|
|
||||||
|
|
||||||
await Document.Insert(SqliteDb.TableName, new JsonDocument { Value = "one" });
|
|
||||||
await Document.Insert(SqliteDb.TableName, new JsonDocument { Value = "two" });
|
|
||||||
await Document.Insert(SqliteDb.TableName, new JsonDocument { Id = "abc123", Value = "three" });
|
|
||||||
await Document.Insert(SqliteDb.TableName, new JsonDocument { Value = "four" });
|
|
||||||
|
|
||||||
var after = await Find.All<JsonDocument>(SqliteDb.TableName);
|
|
||||||
Expect.hasLength(after, 4, "There should have been 4 documents returned");
|
|
||||||
Expect.equal(after.Count(x => x.Id.Length == 44), 3,
|
|
||||||
"Three of the IDs should have been 44-character random strings");
|
|
||||||
Expect.equal(after.Count(x => x.Id == "abc123"), 1, "The provided ID should have been used as-is");
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Configuration.UseAutoIdStrategy(AutoId.Disabled);
|
|
||||||
Configuration.UseIdStringLength(16);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
TestList("Save",
|
TestList("Document.Save",
|
||||||
[
|
[
|
||||||
TestCase("succeeds when a document is inserted", async () =>
|
TestCase("succeeds when a document is inserted", async () =>
|
||||||
{
|
{
|
||||||
@@ -462,8 +382,7 @@ public static class SqliteCSharpTests
|
|||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var theCount = await Count.ByFields(SqliteDb.TableName, FieldMatch.Any,
|
var theCount = await Count.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.BT("NumValue", 10, 20)]);
|
||||||
[Field.Between("NumValue", 10, 20)]);
|
|
||||||
Expect.equal(theCount, 3L, "There should have been 3 matching documents");
|
Expect.equal(theCount, 3L, "There should have been 3 matching documents");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds for non-numeric range", async () =>
|
TestCase("succeeds for non-numeric range", async () =>
|
||||||
@@ -472,7 +391,7 @@ public static class SqliteCSharpTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var theCount = await Count.ByFields(SqliteDb.TableName, FieldMatch.Any,
|
var theCount = await Count.ByFields(SqliteDb.TableName, FieldMatch.Any,
|
||||||
[Field.Between("Value", "aardvark", "apple")]);
|
[Field.BT("Value", "aardvark", "apple")]);
|
||||||
Expect.equal(theCount, 1L, "There should have been 1 matching document");
|
Expect.equal(theCount, 1L, "There should have been 1 matching document");
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
@@ -509,8 +428,7 @@ public static class SqliteCSharpTests
|
|||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var exists = await Exists.ByFields(SqliteDb.TableName, FieldMatch.Any,
|
var exists = await Exists.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.GE("NumValue", 10)]);
|
||||||
[Field.GreaterOrEqual("NumValue", 10)]);
|
|
||||||
Expect.isTrue(exists, "There should have been existing documents");
|
Expect.isTrue(exists, "There should have been existing documents");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when no matching documents exist", async () =>
|
TestCase("succeeds when no matching documents exist", async () =>
|
||||||
@@ -518,8 +436,7 @@ public static class SqliteCSharpTests
|
|||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var exists = await Exists.ByFields(SqliteDb.TableName, FieldMatch.Any,
|
var exists = await Exists.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.EQ("Nothing", "none")]);
|
||||||
[Field.Equal("Nothing", "none")]);
|
|
||||||
Expect.isFalse(exists, "There should not have been any existing documents");
|
Expect.isFalse(exists, "There should not have been any existing documents");
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
@@ -611,45 +528,16 @@ public static class SqliteCSharpTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var docs = await Find.ByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
var docs = await Find.ByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
[Field.Greater("NumValue", 15)]);
|
[Field.GT("NumValue", 15)]);
|
||||||
Expect.equal(docs.Count, 2, "There should have been two documents returned");
|
Expect.equal(docs.Count, 2, "There should have been two documents returned");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when documents are found using IN with numeric field", async () =>
|
|
||||||
{
|
|
||||||
await using var db = await SqliteDb.BuildDb();
|
|
||||||
await LoadDocs();
|
|
||||||
|
|
||||||
var docs = await Find.ByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.All,
|
|
||||||
[Field.In("NumValue", [2, 4, 6, 8])]);
|
|
||||||
Expect.hasLength(docs, 1, "There should have been one document returned");
|
|
||||||
}),
|
|
||||||
TestCase("succeeds when documents are not found", async () =>
|
TestCase("succeeds when documents are not found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var docs = await Find.ByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
var docs = await Find.ByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Value", "mauve")]);
|
[Field.EQ("Value", "mauve")]);
|
||||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
|
||||||
}),
|
|
||||||
TestCase("succeeds for InArray when matching documents exist", async () =>
|
|
||||||
{
|
|
||||||
await using var db = await SqliteDb.BuildDb();
|
|
||||||
await Definition.EnsureTable(SqliteDb.TableName);
|
|
||||||
foreach (var doc in ArrayDocument.TestDocuments) await Document.Insert(SqliteDb.TableName, doc);
|
|
||||||
|
|
||||||
var docs = await Find.ByFields<ArrayDocument>(SqliteDb.TableName, FieldMatch.All,
|
|
||||||
[Field.InArray("Values", SqliteDb.TableName, ["c"])]);
|
|
||||||
Expect.hasLength(docs, 2, "There should have been two document returned");
|
|
||||||
}),
|
|
||||||
TestCase("succeeds for InArray when no matching documents exist", async () =>
|
|
||||||
{
|
|
||||||
await using var db = await SqliteDb.BuildDb();
|
|
||||||
await Definition.EnsureTable(SqliteDb.TableName);
|
|
||||||
foreach (var doc in ArrayDocument.TestDocuments) await Document.Insert(SqliteDb.TableName, doc);
|
|
||||||
|
|
||||||
var docs = await Find.ByFields<ArrayDocument>(SqliteDb.TableName, FieldMatch.All,
|
|
||||||
[Field.InArray("Values", SqliteDb.TableName, ["j"])]);
|
|
||||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
@@ -661,10 +549,9 @@ public static class SqliteCSharpTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var docs = await Find.ByFieldsOrdered<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
var docs = await Find.ByFieldsOrdered<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
[Field.Greater("NumValue", 15)], [Field.Named("Id")]);
|
[Field.GT("NumValue", 15)], [Field.Named("Id")]);
|
||||||
Expect.hasLength(docs, 2, "There should have been two documents returned");
|
|
||||||
Expect.equal(string.Join('|', docs.Select(x => x.Id)), "five|four",
|
Expect.equal(string.Join('|', docs.Select(x => x.Id)), "five|four",
|
||||||
"The documents were not sorted correctly");
|
"There should have been two documents returned");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when documents are not found", async () =>
|
TestCase("succeeds when documents are not found", async () =>
|
||||||
{
|
{
|
||||||
@@ -672,32 +559,9 @@ public static class SqliteCSharpTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var docs = await Find.ByFieldsOrdered<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
var docs = await Find.ByFieldsOrdered<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
[Field.Greater("NumValue", 15)], [Field.Named("Id DESC")]);
|
[Field.GT("NumValue", 15)], [Field.Named("Id DESC")]);
|
||||||
Expect.hasLength(docs, 2, "There should have been two documents returned");
|
|
||||||
Expect.equal(string.Join('|', docs.Select(x => x.Id)), "four|five",
|
Expect.equal(string.Join('|', docs.Select(x => x.Id)), "four|five",
|
||||||
"The documents were not sorted correctly");
|
"There should have been two documents returned");
|
||||||
}),
|
|
||||||
TestCase("succeeds when sorting case-sensitively", async () =>
|
|
||||||
{
|
|
||||||
await using var db = await SqliteDb.BuildDb();
|
|
||||||
await LoadDocs();
|
|
||||||
|
|
||||||
var docs = await Find.ByFieldsOrdered<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
|
||||||
[Field.LessOrEqual("NumValue", 10)], [Field.Named("Value")]);
|
|
||||||
Expect.hasLength(docs, 3, "There should have been three documents returned");
|
|
||||||
Expect.equal(string.Join('|', docs.Select(x => x.Id)), "three|one|two",
|
|
||||||
"The documents were not sorted correctly");
|
|
||||||
}),
|
|
||||||
TestCase("succeeds when sorting case-insensitively", async () =>
|
|
||||||
{
|
|
||||||
await using var db = await SqliteDb.BuildDb();
|
|
||||||
await LoadDocs();
|
|
||||||
|
|
||||||
var docs = await Find.ByFieldsOrdered<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
|
||||||
[Field.LessOrEqual("NumValue", 10)], [Field.Named("i:Value")]);
|
|
||||||
Expect.hasLength(docs, 3, "There should have been three documents returned");
|
|
||||||
Expect.equal(string.Join('|', docs.Select(x => x.Id)), "three|two|one",
|
|
||||||
"The documents were not sorted correctly");
|
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
TestList("FirstByFields",
|
TestList("FirstByFields",
|
||||||
@@ -708,7 +572,7 @@ public static class SqliteCSharpTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await Find.FirstByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
var doc = await Find.FirstByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Value", "another")]);
|
[Field.EQ("Value", "another")]);
|
||||||
Expect.isNotNull(doc, "There should have been a document returned");
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
Expect.equal(doc!.Id, "two", "The incorrect document was returned");
|
Expect.equal(doc!.Id, "two", "The incorrect document was returned");
|
||||||
}),
|
}),
|
||||||
@@ -718,7 +582,7 @@ public static class SqliteCSharpTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await Find.FirstByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
var doc = await Find.FirstByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Sub.Foo", "green")]);
|
[Field.EQ("Sub.Foo", "green")]);
|
||||||
Expect.isNotNull(doc, "There should have been a document returned");
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
Expect.contains(["two", "four"], doc!.Id, "An incorrect document was returned");
|
Expect.contains(["two", "four"], doc!.Id, "An incorrect document was returned");
|
||||||
}),
|
}),
|
||||||
@@ -728,7 +592,7 @@ public static class SqliteCSharpTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await Find.FirstByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
var doc = await Find.FirstByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Value", "absent")]);
|
[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");
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
@@ -740,7 +604,7 @@ public static class SqliteCSharpTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await Find.FirstByFieldsOrdered<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
var doc = await Find.FirstByFieldsOrdered<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Sub.Foo", "green")], [Field.Named("Sub.Bar")]);
|
[Field.EQ("Sub.Foo", "green")], [Field.Named("Sub.Bar")]);
|
||||||
Expect.isNotNull(doc, "There should have been a document returned");
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
Expect.equal("two", doc!.Id, "An incorrect document was returned");
|
Expect.equal("two", doc!.Id, "An incorrect document was returned");
|
||||||
}),
|
}),
|
||||||
@@ -750,7 +614,7 @@ public static class SqliteCSharpTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await Find.FirstByFieldsOrdered<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
var doc = await Find.FirstByFieldsOrdered<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
[Field.Equal("Sub.Foo", "green")], [Field.Named("Sub.Bar DESC")]);
|
[Field.EQ("Sub.Foo", "green")], [Field.Named("Sub.Bar DESC")]);
|
||||||
Expect.isNotNull(doc, "There should have been a document returned");
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
Expect.equal("four", doc!.Id, "An incorrect document was returned");
|
Expect.equal("four", doc!.Id, "An incorrect document was returned");
|
||||||
})
|
})
|
||||||
@@ -856,9 +720,9 @@ public static class SqliteCSharpTests
|
|||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await Patch.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("Value", "purple")],
|
await Patch.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.EQ("Value", "purple")],
|
||||||
new { NumValue = 77 });
|
new { NumValue = 77 });
|
||||||
var after = await Count.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", 77)]);
|
var after = await Count.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", 77)]);
|
||||||
Expect.equal(after, 2L, "There should have been 2 documents returned");
|
Expect.equal(after, 2L, "There should have been 2 documents returned");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when no document is updated", async () =>
|
TestCase("succeeds when no document is updated", async () =>
|
||||||
@@ -869,7 +733,7 @@ public static class SqliteCSharpTests
|
|||||||
Expect.isEmpty(before, "There should have been no documents returned");
|
Expect.isEmpty(before, "There should have been no documents returned");
|
||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await Patch.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("Value", "burgundy")],
|
await Patch.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.EQ("Value", "burgundy")],
|
||||||
new { Foo = "green" });
|
new { Foo = "green" });
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
@@ -916,7 +780,7 @@ public static class SqliteCSharpTests
|
|||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await RemoveFields.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", 17)], ["Sub"]);
|
await RemoveFields.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", 17)], ["Sub"]);
|
||||||
var updated = await Find.ById<string, JsonDocument>(SqliteDb.TableName, "four");
|
var updated = await Find.ById<string, JsonDocument>(SqliteDb.TableName, "four");
|
||||||
Expect.isNotNull(updated, "The updated document should have been retrieved");
|
Expect.isNotNull(updated, "The updated document should have been retrieved");
|
||||||
Expect.isNull(updated.Sub, "The sub-document should have been removed");
|
Expect.isNull(updated.Sub, "The sub-document should have been removed");
|
||||||
@@ -927,7 +791,7 @@ public static class SqliteCSharpTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await RemoveFields.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", 17)],
|
await RemoveFields.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", 17)],
|
||||||
["Nothing"]);
|
["Nothing"]);
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when no document is matched", async () =>
|
TestCase("succeeds when no document is matched", async () =>
|
||||||
@@ -935,8 +799,8 @@ public static class SqliteCSharpTests
|
|||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await RemoveFields.ByFields(SqliteDb.TableName, FieldMatch.Any,
|
await RemoveFields.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.NE("Abracadabra", "apple")],
|
||||||
[Field.NotEqual("Abracadabra", "apple")], ["Value"]);
|
["Value"]);
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
]);
|
]);
|
||||||
@@ -974,7 +838,7 @@ public static class SqliteCSharpTests
|
|||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await Delete.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.NotEqual("Value", "purple")]);
|
await Delete.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.NE("Value", "purple")]);
|
||||||
var remaining = await Count.All(SqliteDb.TableName);
|
var remaining = await Count.All(SqliteDb.TableName);
|
||||||
Expect.equal(remaining, 2L, "There should have been 2 documents remaining");
|
Expect.equal(remaining, 2L, "There should have been 2 documents remaining");
|
||||||
}),
|
}),
|
||||||
@@ -983,7 +847,7 @@ public static class SqliteCSharpTests
|
|||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await Delete.ByFields(SqliteDb.TableName, FieldMatch.All, [Field.Equal("Value", "crimson")]);
|
await Delete.ByFields(SqliteDb.TableName, FieldMatch.All, [Field.EQ("Value", "crimson")]);
|
||||||
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");
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
namespace BitBadger.Documents.Tests.CSharp;
|
namespace BitBadger.Documents.Tests.CSharp;
|
||||||
|
|
||||||
public class NumIdDocument
|
|
||||||
{
|
|
||||||
public int Key { get; set; } = 0;
|
|
||||||
public string Text { get; set; } = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SubDocument
|
public class SubDocument
|
||||||
{
|
{
|
||||||
public string Foo { get; set; } = "";
|
public string Foo { get; set; } = "";
|
||||||
@@ -18,32 +12,4 @@ public class JsonDocument
|
|||||||
public string Value { get; set; } = "";
|
public string Value { get; set; } = "";
|
||||||
public int NumValue { get; set; } = 0;
|
public int NumValue { get; set; } = 0;
|
||||||
public SubDocument? Sub { get; set; } = null;
|
public SubDocument? Sub { get; set; } = null;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A set of documents used for integration tests
|
|
||||||
/// </summary>
|
|
||||||
public static readonly List<JsonDocument> TestDocuments =
|
|
||||||
[
|
|
||||||
new() { Id = "one", Value = "FIRST!", NumValue = 0 },
|
|
||||||
new() { Id = "two", Value = "another", NumValue = 10, Sub = new() { Foo = "green", Bar = "blue" } },
|
|
||||||
new() { Id = "three", Value = "", NumValue = 4 },
|
|
||||||
new() { Id = "four", Value = "purple", NumValue = 17, Sub = new() { Foo = "green", Bar = "red" } },
|
|
||||||
new() { Id = "five", Value = "purple", NumValue = 18 }
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ArrayDocument
|
|
||||||
{
|
|
||||||
public string Id { get; set; } = "";
|
|
||||||
public string[] Values { get; set; } = [];
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A set of documents used for integration tests
|
|
||||||
/// </summary>
|
|
||||||
public static readonly List<ArrayDocument> TestDocuments =
|
|
||||||
[
|
|
||||||
new() { Id = "first", Values = ["a", "b", "c"] },
|
|
||||||
new() { Id = "second", Values = ["c", "d", "e"] },
|
|
||||||
new() { Id = "third", Values = ["x", "y", "z"] }
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Types.fs" />
|
|
||||||
<Compile Include="CommonTests.fs" />
|
<Compile Include="CommonTests.fs" />
|
||||||
|
<Compile Include="Types.fs" />
|
||||||
<Compile Include="PostgresTests.fs" />
|
<Compile Include="PostgresTests.fs" />
|
||||||
<Compile Include="PostgresExtensionTests.fs" />
|
<Compile Include="PostgresExtensionTests.fs" />
|
||||||
<Compile Include="SqliteTests.fs" />
|
<Compile Include="SqliteTests.fs" />
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Expecto" Version="10.2.1" />
|
<PackageReference Include="Expecto" Version="10.2.1" />
|
||||||
<PackageReference Update="FSharp.Core" Version="9.0.100" />
|
<PackageReference Update="FSharp.Core" Version="8.0.300" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -6,196 +6,178 @@ open Expecto
|
|||||||
/// Test table name
|
/// Test table name
|
||||||
let tbl = "test_table"
|
let tbl = "test_table"
|
||||||
|
|
||||||
/// Unit tests for the Op DU
|
/// Tests which do not hit the database
|
||||||
let comparisonTests = testList "Comparison.OpSql" [
|
let all =
|
||||||
test "Equal succeeds" {
|
testList "Common" [
|
||||||
Expect.equal (Equal "").OpSql "=" "The Equals SQL was not correct"
|
testList "Op" [
|
||||||
|
test "EQ succeeds" {
|
||||||
|
Expect.equal (string EQ) "=" "The equals operator was not correct"
|
||||||
}
|
}
|
||||||
test "Greater succeeds" {
|
test "GT succeeds" {
|
||||||
Expect.equal (Greater "").OpSql ">" "The Greater SQL was not correct"
|
Expect.equal (string GT) ">" "The greater than operator was not correct"
|
||||||
}
|
}
|
||||||
test "GreaterOrEqual succeeds" {
|
test "GE succeeds" {
|
||||||
Expect.equal (GreaterOrEqual "").OpSql ">=" "The GreaterOrEqual SQL was not correct"
|
Expect.equal (string GE) ">=" "The greater than or equal to operator was not correct"
|
||||||
}
|
}
|
||||||
test "Less succeeds" {
|
test "LT succeeds" {
|
||||||
Expect.equal (Less "").OpSql "<" "The Less SQL was not correct"
|
Expect.equal (string LT) "<" "The less than operator was not correct"
|
||||||
}
|
}
|
||||||
test "LessOrEqual succeeds" {
|
test "LE succeeds" {
|
||||||
Expect.equal (LessOrEqual "").OpSql "<=" "The LessOrEqual SQL was not correct"
|
Expect.equal (string LE) "<=" "The less than or equal to operator was not correct"
|
||||||
}
|
}
|
||||||
test "NotEqual succeeds" {
|
test "NE succeeds" {
|
||||||
Expect.equal (NotEqual "").OpSql "<>" "The NotEqual SQL was not correct"
|
Expect.equal (string NE) "<>" "The not equal to operator was not correct"
|
||||||
}
|
}
|
||||||
test "Between succeeds" {
|
test "BT succeeds" {
|
||||||
Expect.equal (Between("", "")).OpSql "BETWEEN" "The Between SQL was not correct"
|
Expect.equal (string BT) "BETWEEN" """The "between" operator was not correct"""
|
||||||
}
|
}
|
||||||
test "In succeeds" {
|
test "EX succeeds" {
|
||||||
Expect.equal (In []).OpSql "IN" "The In SQL was not correct"
|
Expect.equal (string EX) "IS NOT NULL" """The "exists" operator was not correct"""
|
||||||
}
|
}
|
||||||
test "InArray succeeds" {
|
test "NEX succeeds" {
|
||||||
Expect.equal (InArray("", [])).OpSql "?|" "The InArray SQL was not correct"
|
Expect.equal (string NEX) "IS NULL" """The "not exists" operator was not correct"""
|
||||||
}
|
|
||||||
test "Exists succeeds" {
|
|
||||||
Expect.equal Exists.OpSql "IS NOT NULL" "The Exists SQL was not correct"
|
|
||||||
}
|
|
||||||
test "NotExists succeeds" {
|
|
||||||
Expect.equal NotExists.OpSql "IS NULL" "The NotExists SQL was not correct"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
testList "Field" [
|
||||||
/// Unit tests for the Field class
|
test "EQ succeeds" {
|
||||||
let fieldTests = testList "Field" [
|
let field = Field.EQ "Test" 14
|
||||||
test "Equal succeeds" {
|
|
||||||
let field = Field.Equal "Test" 14
|
|
||||||
Expect.equal field.Name "Test" "Field name incorrect"
|
Expect.equal field.Name "Test" "Field name incorrect"
|
||||||
Expect.equal field.Comparison (Equal 14) "Comparison 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.ParameterName "The default parameter name should be None"
|
||||||
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
||||||
}
|
}
|
||||||
test "Greater succeeds" {
|
test "GT succeeds" {
|
||||||
let field = Field.Greater "Great" "night"
|
let field = Field.GT "Great" "night"
|
||||||
Expect.equal field.Name "Great" "Field name incorrect"
|
Expect.equal field.Name "Great" "Field name incorrect"
|
||||||
Expect.equal field.Comparison (Greater "night") "Comparison 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.ParameterName "The default parameter name should be None"
|
||||||
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
||||||
}
|
}
|
||||||
test "GreaterOrEqual succeeds" {
|
test "GE succeeds" {
|
||||||
let field = Field.GreaterOrEqual "Nice" 88L
|
let field = Field.GE "Nice" 88L
|
||||||
Expect.equal field.Name "Nice" "Field name incorrect"
|
Expect.equal field.Name "Nice" "Field name incorrect"
|
||||||
Expect.equal field.Comparison (GreaterOrEqual 88L) "Comparison incorrect"
|
Expect.equal field.Op GE "Operator incorrect"
|
||||||
|
Expect.equal field.Value 88L "Value incorrect"
|
||||||
Expect.isNone field.ParameterName "The default parameter name should be None"
|
Expect.isNone field.ParameterName "The default parameter name should be None"
|
||||||
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
||||||
}
|
}
|
||||||
test "Less succeeds" {
|
test "LT succeeds" {
|
||||||
let field = Field.Less "Lesser" "seven"
|
let field = Field.LT "Lesser" "seven"
|
||||||
Expect.equal field.Name "Lesser" "Field name incorrect"
|
Expect.equal field.Name "Lesser" "Field name incorrect"
|
||||||
Expect.equal field.Comparison (Less "seven") "Comparison 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.ParameterName "The default parameter name should be None"
|
||||||
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
||||||
}
|
}
|
||||||
test "LessOrEqual succeeds" {
|
test "LE succeeds" {
|
||||||
let field = Field.LessOrEqual "Nobody" "KNOWS";
|
let field = Field.LE "Nobody" "KNOWS";
|
||||||
Expect.equal field.Name "Nobody" "Field name incorrect"
|
Expect.equal field.Name "Nobody" "Field name incorrect"
|
||||||
Expect.equal field.Comparison (LessOrEqual "KNOWS") "Comparison 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.ParameterName "The default parameter name should be None"
|
||||||
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
||||||
}
|
}
|
||||||
test "NotEqual succeeds" {
|
test "NE succeeds" {
|
||||||
let field = Field.NotEqual "Park" "here"
|
let field = Field.NE "Park" "here"
|
||||||
Expect.equal field.Name "Park" "Field name incorrect"
|
Expect.equal field.Name "Park" "Field name incorrect"
|
||||||
Expect.equal field.Comparison (NotEqual "here") "Comparison 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.ParameterName "The default parameter name should be None"
|
||||||
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
||||||
}
|
}
|
||||||
test "Between succeeds" {
|
test "BT succeeds" {
|
||||||
let field = Field.Between "Age" 18 49
|
let field = Field.BT "Age" 18 49
|
||||||
Expect.equal field.Name "Age" "Field name incorrect"
|
Expect.equal field.Name "Age" "Field name incorrect"
|
||||||
Expect.equal field.Comparison (Between(18, 49)) "Comparison 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.ParameterName "The default parameter name should be None"
|
||||||
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
||||||
}
|
}
|
||||||
test "In succeeds" {
|
test "EX succeeds" {
|
||||||
let field = Field.In "Here" [| 8; 16; 32 |]
|
let field = Field.EX "Groovy"
|
||||||
Expect.equal field.Name "Here" "Field name incorrect"
|
|
||||||
match field.Comparison with
|
|
||||||
| In values -> Expect.equal (List.ofSeq values) [ box 8; box 16; box 32 ] "Comparison incorrect"
|
|
||||||
| it -> Expect.isTrue false $"Expected In, received %A{it}"
|
|
||||||
Expect.isNone field.ParameterName "The default parameter name should be None"
|
|
||||||
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
|
||||||
}
|
|
||||||
test "InArray succeeds" {
|
|
||||||
let field = Field.InArray "ArrayField" "table" [| "z" |]
|
|
||||||
Expect.equal field.Name "ArrayField" "Field name incorrect"
|
|
||||||
match field.Comparison with
|
|
||||||
| InArray (table, values) ->
|
|
||||||
Expect.equal table "table" "Comparison table incorrect"
|
|
||||||
Expect.equal (List.ofSeq values) [ box "z" ] "Comparison values incorrect"
|
|
||||||
| it -> Expect.isTrue false $"Expected InArray, received %A{it}"
|
|
||||||
Expect.isNone field.ParameterName "The default parameter name should be None"
|
|
||||||
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
|
||||||
}
|
|
||||||
test "Exists succeeds" {
|
|
||||||
let field = Field.Exists "Groovy"
|
|
||||||
Expect.equal field.Name "Groovy" "Field name incorrect"
|
Expect.equal field.Name "Groovy" "Field name incorrect"
|
||||||
Expect.equal field.Comparison Exists "Comparison incorrect"
|
Expect.equal field.Op EX "Operator incorrect"
|
||||||
Expect.isNone field.ParameterName "The default parameter name should be None"
|
Expect.isNone field.ParameterName "The default parameter name should be None"
|
||||||
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
||||||
}
|
}
|
||||||
test "NotExists succeeds" {
|
test "NEX succeeds" {
|
||||||
let field = Field.NotExists "Rad"
|
let field = Field.NEX "Rad"
|
||||||
Expect.equal field.Name "Rad" "Field name incorrect"
|
Expect.equal field.Name "Rad" "Field name incorrect"
|
||||||
Expect.equal field.Comparison NotExists "Comparison incorrect"
|
Expect.equal field.Op NEX "Operator incorrect"
|
||||||
Expect.isNone field.ParameterName "The default parameter name should be None"
|
Expect.isNone field.ParameterName "The default parameter name should be None"
|
||||||
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
||||||
}
|
}
|
||||||
testList "NameToPath" [
|
testList "NameToPath" [
|
||||||
test "succeeds for PostgreSQL and a simple name" {
|
test "succeeds for PostgreSQL and a simple name" {
|
||||||
Expect.equal "data->>'Simple'" (Field.NameToPath "Simple" PostgreSQL AsSql) "Path not constructed correctly"
|
Expect.equal
|
||||||
|
"data->>'Simple'" (Field.NameToPath "Simple" PostgreSQL) "Path not constructed correctly"
|
||||||
}
|
}
|
||||||
test "succeeds for SQLite and a simple name" {
|
test "succeeds for SQLite and a simple name" {
|
||||||
Expect.equal "data->>'Simple'" (Field.NameToPath "Simple" SQLite AsSql) "Path not constructed correctly"
|
Expect.equal
|
||||||
|
"data->>'Simple'" (Field.NameToPath "Simple" SQLite) "Path not constructed correctly"
|
||||||
}
|
}
|
||||||
test "succeeds for PostgreSQL and a nested name" {
|
test "succeeds for PostgreSQL and a nested name" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
"data#>>'{A,Long,Path,to,the,Property}'"
|
"data#>>'{A,Long,Path,to,the,Property}'"
|
||||||
(Field.NameToPath "A.Long.Path.to.the.Property" PostgreSQL AsSql)
|
(Field.NameToPath "A.Long.Path.to.the.Property" PostgreSQL)
|
||||||
"Path not constructed correctly"
|
"Path not constructed correctly"
|
||||||
}
|
}
|
||||||
test "succeeds for SQLite and a nested name" {
|
test "succeeds for SQLite and a nested name" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
"data->'A'->'Long'->'Path'->'to'->'the'->>'Property'"
|
"data->>'A'->>'Long'->>'Path'->>'to'->>'the'->>'Property'"
|
||||||
(Field.NameToPath "A.Long.Path.to.the.Property" SQLite AsSql)
|
(Field.NameToPath "A.Long.Path.to.the.Property" SQLite)
|
||||||
"Path not constructed correctly"
|
"Path not constructed correctly"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
test "WithParameterName succeeds" {
|
test "WithParameterName succeeds" {
|
||||||
let field = (Field.Equal "Bob" "Tom").WithParameterName "@name"
|
let field = (Field.EQ "Bob" "Tom").WithParameterName "@name"
|
||||||
Expect.isSome field.ParameterName "The parameter name should have been filled"
|
Expect.isSome field.ParameterName "The parameter name should have been filled"
|
||||||
Expect.equal "@name" field.ParameterName.Value "The parameter name is incorrect"
|
Expect.equal "@name" field.ParameterName.Value "The parameter name is incorrect"
|
||||||
}
|
}
|
||||||
test "WithQualifier succeeds" {
|
test "WithQualifier succeeds" {
|
||||||
let field = (Field.Equal "Bill" "Matt").WithQualifier "joe"
|
let field = (Field.EQ "Bill" "Matt").WithQualifier "joe"
|
||||||
Expect.isSome field.Qualifier "The table qualifier should have been filled"
|
Expect.isSome field.Qualifier "The table qualifier should have been filled"
|
||||||
Expect.equal "joe" field.Qualifier.Value "The table qualifier is incorrect"
|
Expect.equal "joe" field.Qualifier.Value "The table qualifier is incorrect"
|
||||||
}
|
}
|
||||||
testList "Path" [
|
testList "Path" [
|
||||||
test "succeeds for a PostgreSQL single field with no qualifier" {
|
test "succeeds for a PostgreSQL single field with no qualifier" {
|
||||||
let field = Field.GreaterOrEqual "SomethingCool" 18
|
let field = Field.GE "SomethingCool" 18
|
||||||
Expect.equal "data->>'SomethingCool'" (field.Path PostgreSQL AsSql) "The PostgreSQL path is incorrect"
|
Expect.equal "data->>'SomethingCool'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect"
|
||||||
}
|
}
|
||||||
test "succeeds for a PostgreSQL single field with a qualifier" {
|
test "succeeds for a PostgreSQL single field with a qualifier" {
|
||||||
let field = { Field.Less "SomethingElse" 9 with Qualifier = Some "this" }
|
let field = { Field.LT "SomethingElse" 9 with Qualifier = Some "this" }
|
||||||
Expect.equal "this.data->>'SomethingElse'" (field.Path PostgreSQL AsSql) "The PostgreSQL path is incorrect"
|
Expect.equal
|
||||||
|
"this.data->>'SomethingElse'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect"
|
||||||
}
|
}
|
||||||
test "succeeds for a PostgreSQL nested field with no qualifier" {
|
test "succeeds for a PostgreSQL nested field with no qualifier" {
|
||||||
let field = Field.Equal "My.Nested.Field" "howdy"
|
let field = Field.EQ "My.Nested.Field" "howdy"
|
||||||
Expect.equal "data#>>'{My,Nested,Field}'" (field.Path PostgreSQL AsSql) "The PostgreSQL path is incorrect"
|
Expect.equal "data#>>'{My,Nested,Field}'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect"
|
||||||
}
|
}
|
||||||
test "succeeds for a PostgreSQL nested field with a qualifier" {
|
test "succeeds for a PostgreSQL nested field with a qualifier" {
|
||||||
let field = { Field.Equal "Nest.Away" "doc" with Qualifier = Some "bird" }
|
let field = { Field.EQ "Nest.Away" "doc" with Qualifier = Some "bird" }
|
||||||
Expect.equal "bird.data#>>'{Nest,Away}'" (field.Path PostgreSQL AsSql) "The PostgreSQL path is incorrect"
|
Expect.equal "bird.data#>>'{Nest,Away}'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect"
|
||||||
}
|
}
|
||||||
test "succeeds for a SQLite single field with no qualifier" {
|
test "succeeds for a SQLite single field with no qualifier" {
|
||||||
let field = Field.GreaterOrEqual "SomethingCool" 18
|
let field = Field.GE "SomethingCool" 18
|
||||||
Expect.equal "data->>'SomethingCool'" (field.Path SQLite AsSql) "The SQLite path is incorrect"
|
Expect.equal "data->>'SomethingCool'" (field.Path SQLite) "The SQLite path is incorrect"
|
||||||
}
|
}
|
||||||
test "succeeds for a SQLite single field with a qualifier" {
|
test "succeeds for a SQLite single field with a qualifier" {
|
||||||
let field = { Field.Less "SomethingElse" 9 with Qualifier = Some "this" }
|
let field = { Field.LT "SomethingElse" 9 with Qualifier = Some "this" }
|
||||||
Expect.equal "this.data->>'SomethingElse'" (field.Path SQLite AsSql) "The SQLite path is incorrect"
|
Expect.equal "this.data->>'SomethingElse'" (field.Path SQLite) "The SQLite path is incorrect"
|
||||||
}
|
}
|
||||||
test "succeeds for a SQLite nested field with no qualifier" {
|
test "succeeds for a SQLite nested field with no qualifier" {
|
||||||
let field = Field.Equal "My.Nested.Field" "howdy"
|
let field = Field.EQ "My.Nested.Field" "howdy"
|
||||||
Expect.equal "data->'My'->'Nested'->>'Field'" (field.Path SQLite AsSql) "The SQLite path is incorrect"
|
Expect.equal "data->>'My'->>'Nested'->>'Field'" (field.Path SQLite) "The SQLite path is incorrect"
|
||||||
}
|
}
|
||||||
test "succeeds for a SQLite nested field with a qualifier" {
|
test "succeeds for a SQLite nested field with a qualifier" {
|
||||||
let field = { Field.Equal "Nest.Away" "doc" with Qualifier = Some "bird" }
|
let field = { Field.EQ "Nest.Away" "doc" with Qualifier = Some "bird" }
|
||||||
Expect.equal "bird.data->'Nest'->>'Away'" (field.Path SQLite AsSql) "The SQLite path is incorrect"
|
Expect.equal "bird.data->>'Nest'->>'Away'" (field.Path SQLite) "The SQLite path is incorrect"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
testList "FieldMatch.ToString" [
|
||||||
/// Unit tests for the FieldMatch DU
|
|
||||||
let fieldMatchTests = testList "FieldMatch.ToString" [
|
|
||||||
test "succeeds for Any" {
|
test "succeeds for Any" {
|
||||||
Expect.equal (string Any) "OR" "SQL for Any is incorrect"
|
Expect.equal (string Any) "OR" "SQL for Any is incorrect"
|
||||||
}
|
}
|
||||||
@@ -203,9 +185,7 @@ let fieldMatchTests = testList "FieldMatch.ToString" [
|
|||||||
Expect.equal (string All) "AND" "SQL for All is incorrect"
|
Expect.equal (string All) "AND" "SQL for All is incorrect"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
testList "ParameterName.Derive" [
|
||||||
/// Unit tests for the ParameterName class
|
|
||||||
let parameterNameTests = testList "ParameterName.Derive" [
|
|
||||||
test "succeeds with existing name" {
|
test "succeeds with existing name" {
|
||||||
let name = ParameterName()
|
let name = ParameterName()
|
||||||
Expect.equal (name.Derive(Some "@taco")) "@taco" "Name should have been @taco"
|
Expect.equal (name.Derive(Some "@taco")) "@taco" "Name should have been @taco"
|
||||||
@@ -219,136 +199,7 @@ let parameterNameTests = testList "ParameterName.Derive" [
|
|||||||
Expect.equal (name.Derive None) "@field3" "Counter should have advanced from previous call"
|
Expect.equal (name.Derive None) "@field3" "Counter should have advanced from previous call"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
testList "Query" [
|
||||||
/// Unit tests for the AutoId DU
|
|
||||||
let autoIdTests = testList "AutoId" [
|
|
||||||
test "GenerateGuid succeeds" {
|
|
||||||
let autoId = AutoId.GenerateGuid()
|
|
||||||
Expect.isNotNull autoId "The GUID auto-ID should not have been null"
|
|
||||||
Expect.stringHasLength autoId 32 "The GUID auto-ID should have been 32 characters long"
|
|
||||||
Expect.equal autoId (autoId.ToLowerInvariant ()) "The GUID auto-ID should have been lowercase"
|
|
||||||
}
|
|
||||||
test "GenerateRandomString succeeds" {
|
|
||||||
[ 6; 8; 12; 20; 32; 57; 64 ]
|
|
||||||
|> List.iter (fun length ->
|
|
||||||
let autoId = AutoId.GenerateRandomString length
|
|
||||||
Expect.isNotNull autoId $"Random string ({length}) should not have been null"
|
|
||||||
Expect.stringHasLength autoId length $"Random string should have been {length} characters long"
|
|
||||||
Expect.equal autoId (autoId.ToLowerInvariant ()) $"Random string ({length}) should have been lowercase")
|
|
||||||
}
|
|
||||||
testList "NeedsAutoId" [
|
|
||||||
test "succeeds when no auto ID is configured" {
|
|
||||||
Expect.isFalse (AutoId.NeedsAutoId Disabled (obj ()) "id") "Disabled auto-ID never needs an automatic ID"
|
|
||||||
}
|
|
||||||
test "fails for any when the ID property is not found" {
|
|
||||||
Expect.throwsT<System.InvalidOperationException>
|
|
||||||
(fun () -> AutoId.NeedsAutoId Number {| Key = "" |} "Id" |> ignore)
|
|
||||||
"Non-existent ID property should have thrown an exception"
|
|
||||||
}
|
|
||||||
test "succeeds for byte when the ID is zero" {
|
|
||||||
Expect.isTrue (AutoId.NeedsAutoId Number {| Id = int8 0 |} "Id") "Zero ID should have returned true"
|
|
||||||
}
|
|
||||||
test "succeeds for byte when the ID is non-zero" {
|
|
||||||
Expect.isFalse (AutoId.NeedsAutoId Number {| Id = int8 4 |} "Id") "Non-zero ID should have returned false"
|
|
||||||
}
|
|
||||||
test "succeeds for short when the ID is zero" {
|
|
||||||
Expect.isTrue (AutoId.NeedsAutoId Number {| Id = int16 0 |} "Id") "Zero ID should have returned true"
|
|
||||||
}
|
|
||||||
test "succeeds for short when the ID is non-zero" {
|
|
||||||
Expect.isFalse (AutoId.NeedsAutoId Number {| Id = int16 7 |} "Id") "Non-zero ID should have returned false"
|
|
||||||
}
|
|
||||||
test "succeeds for int when the ID is zero" {
|
|
||||||
Expect.isTrue (AutoId.NeedsAutoId Number {| Id = 0 |} "Id") "Zero ID should have returned true"
|
|
||||||
}
|
|
||||||
test "succeeds for int when the ID is non-zero" {
|
|
||||||
Expect.isFalse (AutoId.NeedsAutoId Number {| Id = 32 |} "Id") "Non-zero ID should have returned false"
|
|
||||||
}
|
|
||||||
test "succeeds for long when the ID is zero" {
|
|
||||||
Expect.isTrue (AutoId.NeedsAutoId Number {| Id = 0L |} "Id") "Zero ID should have returned true"
|
|
||||||
}
|
|
||||||
test "succeeds for long when the ID is non-zero" {
|
|
||||||
Expect.isFalse (AutoId.NeedsAutoId Number {| Id = 80L |} "Id") "Non-zero ID should have returned false"
|
|
||||||
}
|
|
||||||
test "fails for number when the ID is not a number" {
|
|
||||||
Expect.throwsT<System.InvalidOperationException>
|
|
||||||
(fun () -> AutoId.NeedsAutoId Number {| Id = "" |} "Id" |> ignore)
|
|
||||||
"Numeric ID against a string should have thrown an exception"
|
|
||||||
}
|
|
||||||
test "succeeds for GUID when the ID is blank" {
|
|
||||||
Expect.isTrue (AutoId.NeedsAutoId Guid {| Id = "" |} "Id") "Blank ID should have returned true"
|
|
||||||
}
|
|
||||||
test "succeeds for GUID when the ID is filled" {
|
|
||||||
Expect.isFalse (AutoId.NeedsAutoId Guid {| Id = "abc" |} "Id") "Filled ID should have returned false"
|
|
||||||
}
|
|
||||||
test "fails for GUID when the ID is not a string" {
|
|
||||||
Expect.throwsT<System.InvalidOperationException>
|
|
||||||
(fun () -> AutoId.NeedsAutoId Guid {| Id = 8 |} "Id" |> ignore)
|
|
||||||
"String ID against a number should have thrown an exception"
|
|
||||||
}
|
|
||||||
test "succeeds for RandomString when the ID is blank" {
|
|
||||||
Expect.isTrue (AutoId.NeedsAutoId RandomString {| Id = "" |} "Id") "Blank ID should have returned true"
|
|
||||||
}
|
|
||||||
test "succeeds for RandomString when the ID is filled" {
|
|
||||||
Expect.isFalse (AutoId.NeedsAutoId RandomString {| Id = "x" |} "Id") "Filled ID should have returned false"
|
|
||||||
}
|
|
||||||
test "fails for RandomString when the ID is not a string" {
|
|
||||||
Expect.throwsT<System.InvalidOperationException>
|
|
||||||
(fun () -> AutoId.NeedsAutoId RandomString {| Id = 33 |} "Id" |> ignore)
|
|
||||||
"String ID against a number should have thrown an exception"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
|
|
||||||
/// Unit tests for the Configuration module
|
|
||||||
let configurationTests = testList "Configuration" [
|
|
||||||
test "useSerializer succeeds" {
|
|
||||||
try
|
|
||||||
Configuration.useSerializer
|
|
||||||
{ new IDocumentSerializer with
|
|
||||||
member _.Serialize<'T>(it: 'T) : string = """{"Overridden":true}"""
|
|
||||||
member _.Deserialize<'T>(it: string) : 'T = Unchecked.defaultof<'T>
|
|
||||||
}
|
|
||||||
|
|
||||||
let serialized = Configuration.serializer().Serialize {| Foo = "howdy"; Bar = "bye" |}
|
|
||||||
Expect.equal serialized """{"Overridden":true}""" "Specified serializer was not used"
|
|
||||||
|
|
||||||
let deserialized = Configuration.serializer().Deserialize<obj> """{"Something":"here"}"""
|
|
||||||
Expect.isNull deserialized "Specified serializer should have returned null"
|
|
||||||
finally
|
|
||||||
Configuration.useSerializer DocumentSerializer.``default``
|
|
||||||
}
|
|
||||||
test "serializer returns configured serializer" {
|
|
||||||
Expect.isTrue (obj.ReferenceEquals(DocumentSerializer.``default``, Configuration.serializer ()))
|
|
||||||
"Serializer should have been the same"
|
|
||||||
}
|
|
||||||
test "useIdField / idField succeeds" {
|
|
||||||
try
|
|
||||||
Expect.equal (Configuration.idField ()) "Id" "The default configured ID field was incorrect"
|
|
||||||
Configuration.useIdField "id"
|
|
||||||
Expect.equal (Configuration.idField ()) "id" "useIdField did not set the ID field"
|
|
||||||
finally
|
|
||||||
Configuration.useIdField "Id"
|
|
||||||
}
|
|
||||||
test "useAutoIdStrategy / autoIdStrategy succeeds" {
|
|
||||||
try
|
|
||||||
Expect.equal (Configuration.autoIdStrategy ()) Disabled "The default auto-ID strategy was incorrect"
|
|
||||||
Configuration.useAutoIdStrategy Guid
|
|
||||||
Expect.equal (Configuration.autoIdStrategy ()) Guid "The auto-ID strategy was not set correctly"
|
|
||||||
finally
|
|
||||||
Configuration.useAutoIdStrategy Disabled
|
|
||||||
}
|
|
||||||
test "useIdStringLength / idStringLength succeeds" {
|
|
||||||
try
|
|
||||||
Expect.equal (Configuration.idStringLength ()) 16 "The default ID string length was incorrect"
|
|
||||||
Configuration.useIdStringLength 33
|
|
||||||
Expect.equal (Configuration.idStringLength ()) 33 "The ID string length was not set correctly"
|
|
||||||
finally
|
|
||||||
Configuration.useIdStringLength 16
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
/// Unit tests for the Query module
|
|
||||||
let queryTests = testList "Query" [
|
|
||||||
test "statementWhere succeeds" {
|
test "statementWhere succeeds" {
|
||||||
Expect.equal (Query.statementWhere "x" "y") "x WHERE y" "Statements not combined correctly"
|
Expect.equal (Query.statementWhere "x" "y") "x WHERE y" "Statements not combined correctly"
|
||||||
}
|
}
|
||||||
@@ -392,7 +243,7 @@ let queryTests = testList "Query" [
|
|||||||
test "succeeds for nested SQLite field" {
|
test "succeeds for nested SQLite field" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.Definition.ensureIndexOn tbl "nest" [ "a.b.c" ] SQLite)
|
(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 IF NOT EXISTS idx_{tbl}_nest ON {tbl} ((data->>'a'->>'b'->>'c'))"
|
||||||
"CREATE INDEX for nested SQLite field incorrect"
|
"CREATE INDEX for nested SQLite field incorrect"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -454,7 +305,7 @@ let queryTests = testList "Query" [
|
|||||||
(Query.orderBy
|
(Query.orderBy
|
||||||
[ Field.Named "Nested.Test.Field DESC"; Field.Named "AnotherField"; Field.Named "It DESC" ]
|
[ Field.Named "Nested.Test.Field DESC"; Field.Named "AnotherField"; Field.Named "It DESC" ]
|
||||||
SQLite)
|
SQLite)
|
||||||
" ORDER BY data->'Nested'->'Test'->>'Field' DESC, data->>'AnotherField', data->>'It' DESC"
|
" ORDER BY data->>'Nested'->>'Test'->>'Field' DESC, data->>'AnotherField', data->>'It' DESC"
|
||||||
"Order By not constructed correctly"
|
"Order By not constructed correctly"
|
||||||
}
|
}
|
||||||
test "succeeds for PostgreSQL numeric fields" {
|
test "succeeds for PostgreSQL numeric fields" {
|
||||||
@@ -469,28 +320,6 @@ let queryTests = testList "Query" [
|
|||||||
" ORDER BY data->>'Test'"
|
" ORDER BY data->>'Test'"
|
||||||
"Order By not constructed correctly for numeric field"
|
"Order By not constructed correctly for numeric field"
|
||||||
}
|
}
|
||||||
test "succeeds for PostgreSQL case-insensitive ordering" {
|
|
||||||
Expect.equal
|
|
||||||
(Query.orderBy [ Field.Named "i:Test.Field DESC NULLS FIRST" ] PostgreSQL)
|
|
||||||
" ORDER BY LOWER(data#>>'{Test,Field}') DESC NULLS FIRST"
|
|
||||||
"Order By not constructed correctly for case-insensitive field"
|
|
||||||
}
|
|
||||||
test "succeeds for SQLite case-insensitive ordering" {
|
|
||||||
Expect.equal
|
|
||||||
(Query.orderBy [ Field.Named "i:Test.Field ASC NULLS LAST" ] SQLite)
|
|
||||||
" ORDER BY data->'Test'->>'Field' COLLATE NOCASE ASC NULLS LAST"
|
|
||||||
"Order By not constructed correctly for case-insensitive field"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
/// Tests which do not hit the database
|
|
||||||
let all = testList "Common" [
|
|
||||||
comparisonTests
|
|
||||||
fieldTests
|
|
||||||
fieldMatchTests
|
|
||||||
parameterNameTests
|
|
||||||
autoIdTests
|
|
||||||
queryTests
|
|
||||||
testSequenced configurationTests
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ let integrationTests =
|
|||||||
use conn = mkConn db
|
use conn = mkConn db
|
||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
let! theCount = conn.countByFields PostgresDb.TableName Any [ Field.Equal "Value" "purple" ]
|
let! theCount = conn.countByFields PostgresDb.TableName Any [ 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"
|
||||||
}
|
}
|
||||||
testTask "countByContains succeeds" {
|
testTask "countByContains succeeds" {
|
||||||
@@ -257,7 +257,7 @@ let integrationTests =
|
|||||||
use conn = mkConn db
|
use conn = mkConn db
|
||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
let! exists = conn.existsByFields PostgresDb.TableName Any [ Field.Exists "Sub" ]
|
let! exists = conn.existsByFields PostgresDb.TableName Any [ Field.EX "Sub" ]
|
||||||
Expect.isTrue exists "There should have been existing documents"
|
Expect.isTrue exists "There should have been existing documents"
|
||||||
}
|
}
|
||||||
testTask "succeeds when documents do not exist" {
|
testTask "succeeds when documents do not exist" {
|
||||||
@@ -265,7 +265,7 @@ let integrationTests =
|
|||||||
use conn = mkConn db
|
use conn = mkConn db
|
||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
let! exists = conn.existsByFields PostgresDb.TableName Any [ Field.Equal "NumValue" "six" ]
|
let! exists = conn.existsByFields PostgresDb.TableName Any [ Field.EQ "NumValue" "six" ]
|
||||||
Expect.isFalse exists "There should not have been existing documents"
|
Expect.isFalse exists "There should not have been existing documents"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -390,7 +390,7 @@ let integrationTests =
|
|||||||
use conn = mkConn db
|
use conn = mkConn db
|
||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
let! docs = conn.findByFields<JsonDocument> PostgresDb.TableName Any [ Field.Equal "Value" "another" ]
|
let! docs = conn.findByFields<JsonDocument> PostgresDb.TableName Any [ Field.EQ "Value" "another" ]
|
||||||
Expect.equal (List.length docs) 1 "There should have been one document returned"
|
Expect.equal (List.length docs) 1 "There should have been one document returned"
|
||||||
}
|
}
|
||||||
testTask "succeeds when documents are not found" {
|
testTask "succeeds when documents are not found" {
|
||||||
@@ -398,7 +398,7 @@ let integrationTests =
|
|||||||
use conn = mkConn db
|
use conn = mkConn db
|
||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
let! docs = conn.findByFields<JsonDocument> PostgresDb.TableName Any [ Field.Equal "Value" "mauve" ]
|
let! docs = conn.findByFields<JsonDocument> PostgresDb.TableName Any [ Field.EQ "Value" "mauve" ]
|
||||||
Expect.isEmpty docs "There should have been no documents returned"
|
Expect.isEmpty docs "There should have been no documents returned"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -410,7 +410,7 @@ let integrationTests =
|
|||||||
|
|
||||||
let! docs =
|
let! docs =
|
||||||
conn.findByFieldsOrdered<JsonDocument>
|
conn.findByFieldsOrdered<JsonDocument>
|
||||||
PostgresDb.TableName All [ Field.Equal "Value" "purple" ] [ Field.Named "Id" ]
|
PostgresDb.TableName All [ Field.EQ "Value" "purple" ] [ Field.Named "Id" ]
|
||||||
Expect.hasLength docs 2 "There should have been two documents returned"
|
Expect.hasLength docs 2 "There should have been two documents returned"
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(docs |> List.map _.Id |> String.concat "|") "five|four" "Documents not ordered correctly"
|
(docs |> List.map _.Id |> String.concat "|") "five|four" "Documents not ordered correctly"
|
||||||
@@ -422,7 +422,7 @@ let integrationTests =
|
|||||||
|
|
||||||
let! docs =
|
let! docs =
|
||||||
conn.findByFieldsOrdered<JsonDocument>
|
conn.findByFieldsOrdered<JsonDocument>
|
||||||
PostgresDb.TableName All [ Field.Equal "Value" "purple" ] [ Field.Named "Id DESC" ]
|
PostgresDb.TableName All [ Field.EQ "Value" "purple" ] [ Field.Named "Id DESC" ]
|
||||||
Expect.hasLength docs 2 "There should have been two documents returned"
|
Expect.hasLength docs 2 "There should have been two documents returned"
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(docs |> List.map _.Id |> String.concat "|") "four|five" "Documents not ordered correctly"
|
(docs |> List.map _.Id |> String.concat "|") "four|five" "Documents not ordered correctly"
|
||||||
@@ -524,8 +524,7 @@ let integrationTests =
|
|||||||
use conn = mkConn db
|
use conn = mkConn db
|
||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
let! doc =
|
let! doc = conn.findFirstByFields<JsonDocument> PostgresDb.TableName Any [ Field.EQ "Value" "another" ]
|
||||||
conn.findFirstByFields<JsonDocument> PostgresDb.TableName Any [ Field.Equal "Value" "another" ]
|
|
||||||
Expect.isSome doc "There should have been a document returned"
|
Expect.isSome doc "There should have been a document returned"
|
||||||
Expect.equal doc.Value.Id "two" "The incorrect document was returned"
|
Expect.equal doc.Value.Id "two" "The incorrect document was returned"
|
||||||
}
|
}
|
||||||
@@ -534,8 +533,7 @@ let integrationTests =
|
|||||||
use conn = mkConn db
|
use conn = mkConn db
|
||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
let! doc =
|
let! doc = conn.findFirstByFields<JsonDocument> PostgresDb.TableName Any [ Field.EQ "Value" "purple" ]
|
||||||
conn.findFirstByFields<JsonDocument> PostgresDb.TableName Any [ Field.Equal "Value" "purple" ]
|
|
||||||
Expect.isSome doc "There should have been a document returned"
|
Expect.isSome doc "There should have been a document returned"
|
||||||
Expect.contains [ "five"; "four" ] doc.Value.Id "An incorrect document was returned"
|
Expect.contains [ "five"; "four" ] doc.Value.Id "An incorrect document was returned"
|
||||||
}
|
}
|
||||||
@@ -544,8 +542,7 @@ let integrationTests =
|
|||||||
use conn = mkConn db
|
use conn = mkConn db
|
||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
let! doc =
|
let! doc = conn.findFirstByFields<JsonDocument> PostgresDb.TableName Any [ Field.EQ "Value" "absent" ]
|
||||||
conn.findFirstByFields<JsonDocument> PostgresDb.TableName Any [ Field.Equal "Value" "absent" ]
|
|
||||||
Expect.isNone doc "There should not have been a document returned"
|
Expect.isNone doc "There should not have been a document returned"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -557,7 +554,7 @@ let integrationTests =
|
|||||||
|
|
||||||
let! doc =
|
let! doc =
|
||||||
conn.findFirstByFieldsOrdered<JsonDocument>
|
conn.findFirstByFieldsOrdered<JsonDocument>
|
||||||
PostgresDb.TableName Any [ Field.Equal "Value" "purple" ] [ Field.Named "Id" ]
|
PostgresDb.TableName Any [ Field.EQ "Value" "purple" ] [ Field.Named "Id" ]
|
||||||
Expect.isSome doc "There should have been a document returned"
|
Expect.isSome doc "There should have been a document returned"
|
||||||
Expect.equal "five" doc.Value.Id "An incorrect document was returned"
|
Expect.equal "five" doc.Value.Id "An incorrect document was returned"
|
||||||
}
|
}
|
||||||
@@ -568,7 +565,7 @@ let integrationTests =
|
|||||||
|
|
||||||
let! doc =
|
let! doc =
|
||||||
conn.findFirstByFieldsOrdered<JsonDocument>
|
conn.findFirstByFieldsOrdered<JsonDocument>
|
||||||
PostgresDb.TableName Any [ Field.Equal "Value" "purple" ] [ Field.Named "Id DESC" ]
|
PostgresDb.TableName Any [ Field.EQ "Value" "purple" ] [ Field.Named "Id DESC" ]
|
||||||
Expect.isSome doc "There should have been a document returned"
|
Expect.isSome doc "There should have been a document returned"
|
||||||
Expect.equal "four" doc.Value.Id "An incorrect document was returned"
|
Expect.equal "four" doc.Value.Id "An incorrect document was returned"
|
||||||
}
|
}
|
||||||
@@ -753,8 +750,8 @@ let integrationTests =
|
|||||||
use conn = mkConn db
|
use conn = mkConn db
|
||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
do! conn.patchByFields PostgresDb.TableName Any [ Field.Equal "Value" "purple" ] {| NumValue = 77 |}
|
do! conn.patchByFields PostgresDb.TableName Any [ Field.EQ "Value" "purple" ] {| NumValue = 77 |}
|
||||||
let! after = conn.countByFields PostgresDb.TableName Any [ Field.Equal "NumValue" "77" ]
|
let! after = conn.countByFields PostgresDb.TableName Any [ Field.EQ "NumValue" "77" ]
|
||||||
Expect.equal after 2 "There should have been 2 documents returned"
|
Expect.equal after 2 "There should have been 2 documents returned"
|
||||||
}
|
}
|
||||||
testTask "succeeds when no document is updated" {
|
testTask "succeeds when no document is updated" {
|
||||||
@@ -764,7 +761,7 @@ let integrationTests =
|
|||||||
Expect.equal before 0 "There should have been no documents returned"
|
Expect.equal before 0 "There should have been no documents returned"
|
||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
do! conn.patchByFields PostgresDb.TableName Any [ Field.Equal "Value" "burgundy" ] {| Foo = "green" |}
|
do! conn.patchByFields PostgresDb.TableName Any [ Field.EQ "Value" "burgundy" ] {| Foo = "green" |}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "patchByContains" [
|
testList "patchByContains" [
|
||||||
@@ -814,9 +811,9 @@ let integrationTests =
|
|||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
do! conn.removeFieldsById PostgresDb.TableName "two" [ "Sub"; "Value" ]
|
do! conn.removeFieldsById PostgresDb.TableName "two" [ "Sub"; "Value" ]
|
||||||
let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "Sub" ]
|
let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NEX "Sub" ]
|
||||||
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
||||||
let! noValue = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "Value" ]
|
let! noValue = conn.countByFields PostgresDb.TableName Any [ Field.NEX "Value" ]
|
||||||
Expect.equal noValue 1 "There should be 1 document without Value fields"
|
Expect.equal noValue 1 "There should be 1 document without Value fields"
|
||||||
}
|
}
|
||||||
testTask "succeeds when a single field is removed" {
|
testTask "succeeds when a single field is removed" {
|
||||||
@@ -825,9 +822,9 @@ let integrationTests =
|
|||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
do! conn.removeFieldsById PostgresDb.TableName "two" [ "Sub" ]
|
do! conn.removeFieldsById PostgresDb.TableName "two" [ "Sub" ]
|
||||||
let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "Sub" ]
|
let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NEX "Sub" ]
|
||||||
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
||||||
let! noValue = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "Value" ]
|
let! noValue = conn.countByFields PostgresDb.TableName Any [ Field.NEX "Value" ]
|
||||||
Expect.equal noValue 0 "There should be no documents without Value fields"
|
Expect.equal noValue 0 "There should be no documents without Value fields"
|
||||||
}
|
}
|
||||||
testTask "succeeds when a field is not removed" {
|
testTask "succeeds when a field is not removed" {
|
||||||
@@ -852,11 +849,10 @@ let integrationTests =
|
|||||||
use conn = mkConn db
|
use conn = mkConn db
|
||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
do! conn.removeFieldsByFields
|
do! conn.removeFieldsByFields PostgresDb.TableName Any [ Field.EQ "NumValue" "17" ] [ "Sub"; "Value" ]
|
||||||
PostgresDb.TableName Any [ Field.Equal "NumValue" "17" ] [ "Sub"; "Value" ]
|
let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NEX "Sub" ]
|
||||||
let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "Sub" ]
|
|
||||||
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
||||||
let! noValue = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "Value" ]
|
let! noValue = conn.countByFields PostgresDb.TableName Any [ Field.NEX "Value" ]
|
||||||
Expect.equal noValue 1 "There should be 1 document without Value fields"
|
Expect.equal noValue 1 "There should be 1 document without Value fields"
|
||||||
}
|
}
|
||||||
testTask "succeeds when a single field is removed" {
|
testTask "succeeds when a single field is removed" {
|
||||||
@@ -864,10 +860,10 @@ let integrationTests =
|
|||||||
use conn = mkConn db
|
use conn = mkConn db
|
||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
do! conn.removeFieldsByFields PostgresDb.TableName Any [ Field.Equal "NumValue" "17" ] [ "Sub" ]
|
do! conn.removeFieldsByFields PostgresDb.TableName Any [ Field.EQ "NumValue" "17" ] [ "Sub" ]
|
||||||
let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "Sub" ]
|
let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NEX "Sub" ]
|
||||||
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
||||||
let! noValue = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "Value" ]
|
let! noValue = conn.countByFields PostgresDb.TableName Any [ Field.NEX "Value" ]
|
||||||
Expect.equal noValue 0 "There should be no documents without Value fields"
|
Expect.equal noValue 0 "There should be no documents without Value fields"
|
||||||
}
|
}
|
||||||
testTask "succeeds when a field is not removed" {
|
testTask "succeeds when a field is not removed" {
|
||||||
@@ -876,15 +872,14 @@ let integrationTests =
|
|||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
do! conn.removeFieldsByFields PostgresDb.TableName Any [ Field.Equal "NumValue" "17" ] [ "Nothing" ]
|
do! conn.removeFieldsByFields PostgresDb.TableName Any [ Field.EQ "NumValue" "17" ] [ "Nothing" ]
|
||||||
}
|
}
|
||||||
testTask "succeeds when no document is matched" {
|
testTask "succeeds when no document is matched" {
|
||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
use conn = mkConn db
|
use conn = mkConn db
|
||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
do! conn.removeFieldsByFields
|
do! conn.removeFieldsByFields PostgresDb.TableName Any [ Field.NE "Abracadabra" "apple" ] [ "Value" ]
|
||||||
PostgresDb.TableName Any [ Field.NotEqual "Abracadabra" "apple" ] [ "Value" ]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "removeFieldsByContains" [
|
testList "removeFieldsByContains" [
|
||||||
@@ -894,9 +889,9 @@ let integrationTests =
|
|||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
do! conn.removeFieldsByContains PostgresDb.TableName {| NumValue = 17 |} [ "Sub"; "Value" ]
|
do! conn.removeFieldsByContains PostgresDb.TableName {| NumValue = 17 |} [ "Sub"; "Value" ]
|
||||||
let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "Sub" ]
|
let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NEX "Sub" ]
|
||||||
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
||||||
let! noValue = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "Value" ]
|
let! noValue = conn.countByFields PostgresDb.TableName Any [ Field.NEX "Value" ]
|
||||||
Expect.equal noValue 1 "There should be 1 document without Value fields"
|
Expect.equal noValue 1 "There should be 1 document without Value fields"
|
||||||
}
|
}
|
||||||
testTask "succeeds when a single field is removed" {
|
testTask "succeeds when a single field is removed" {
|
||||||
@@ -905,9 +900,9 @@ let integrationTests =
|
|||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
do! conn.removeFieldsByContains PostgresDb.TableName {| NumValue = 17 |} [ "Sub" ]
|
do! conn.removeFieldsByContains PostgresDb.TableName {| NumValue = 17 |} [ "Sub" ]
|
||||||
let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "Sub" ]
|
let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NEX "Sub" ]
|
||||||
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
||||||
let! noValue = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "Value" ]
|
let! noValue = conn.countByFields PostgresDb.TableName Any [ Field.NEX "Value" ]
|
||||||
Expect.equal noValue 0 "There should be no documents without Value fields"
|
Expect.equal noValue 0 "There should be no documents without Value fields"
|
||||||
}
|
}
|
||||||
testTask "succeeds when a field is not removed" {
|
testTask "succeeds when a field is not removed" {
|
||||||
@@ -933,9 +928,9 @@ let integrationTests =
|
|||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
do! conn.removeFieldsByJsonPath PostgresDb.TableName "$.NumValue ? (@ == 17)" [ "Sub"; "Value" ]
|
do! conn.removeFieldsByJsonPath PostgresDb.TableName "$.NumValue ? (@ == 17)" [ "Sub"; "Value" ]
|
||||||
let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "Sub" ]
|
let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NEX "Sub" ]
|
||||||
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
||||||
let! noValue = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "Value" ]
|
let! noValue = conn.countByFields PostgresDb.TableName Any [ Field.NEX "Value" ]
|
||||||
Expect.equal noValue 1 "There should be 1 document without Value fields"
|
Expect.equal noValue 1 "There should be 1 document without Value fields"
|
||||||
}
|
}
|
||||||
testTask "succeeds when a single field is removed" {
|
testTask "succeeds when a single field is removed" {
|
||||||
@@ -944,9 +939,9 @@ let integrationTests =
|
|||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
do! conn.removeFieldsByJsonPath PostgresDb.TableName "$.NumValue ? (@ == 17)" [ "Sub" ]
|
do! conn.removeFieldsByJsonPath PostgresDb.TableName "$.NumValue ? (@ == 17)" [ "Sub" ]
|
||||||
let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "Sub" ]
|
let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NEX "Sub" ]
|
||||||
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
||||||
let! noValue = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "Value" ]
|
let! noValue = conn.countByFields PostgresDb.TableName Any [ Field.NEX "Value" ]
|
||||||
Expect.equal noValue 0 "There should be no documents without Value fields"
|
Expect.equal noValue 0 "There should be no documents without Value fields"
|
||||||
}
|
}
|
||||||
testTask "succeeds when a field is not removed" {
|
testTask "succeeds when a field is not removed" {
|
||||||
@@ -991,7 +986,7 @@ let integrationTests =
|
|||||||
use conn = mkConn db
|
use conn = mkConn db
|
||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
do! conn.deleteByFields PostgresDb.TableName Any [ Field.Equal "Value" "purple" ]
|
do! conn.deleteByFields PostgresDb.TableName Any [ Field.EQ "Value" "purple" ]
|
||||||
let! remaining = conn.countAll PostgresDb.TableName
|
let! remaining = conn.countAll PostgresDb.TableName
|
||||||
Expect.equal remaining 3 "There should have been 3 documents remaining"
|
Expect.equal remaining 3 "There should have been 3 documents remaining"
|
||||||
}
|
}
|
||||||
@@ -1000,7 +995,7 @@ let integrationTests =
|
|||||||
use conn = mkConn db
|
use conn = mkConn db
|
||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
do! conn.deleteByFields PostgresDb.TableName Any [ Field.Equal "Value" "crimson" ]
|
do! conn.deleteByFields PostgresDb.TableName Any [ Field.EQ "Value" "crimson" ]
|
||||||
let! remaining = conn.countAll PostgresDb.TableName
|
let! remaining = 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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,14 +83,14 @@ let parametersTests = testList "Parameters" [
|
|||||||
}
|
}
|
||||||
testList "addFieldParams" [
|
testList "addFieldParams" [
|
||||||
test "succeeds when a parameter is added" {
|
test "succeeds when a parameter is added" {
|
||||||
let paramList = addFieldParams [ Field.Where "it" (Equal "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 name, value = Seq.head paramList
|
let name, value = Seq.head paramList
|
||||||
Expect.equal name "@field0" "Field parameter name not correct"
|
Expect.equal name "@field0" "Field parameter name not correct"
|
||||||
Expect.equal value (Sql.string "242") "Parameter value not correct"
|
Expect.equal value (Sql.string "242") "Parameter value not correct"
|
||||||
}
|
}
|
||||||
test "succeeds when multiple independent parameters are added" {
|
test "succeeds when multiple independent parameters are added" {
|
||||||
let paramList = addFieldParams [ Field.Equal "me" "you"; Field.Greater "us" "them" ] [ idParam 14 ]
|
let paramList = addFieldParams [ Field.EQ "me" "you"; Field.GT "us" "them" ] [ idParam 14 ]
|
||||||
Expect.hasLength paramList 3 "There should have been 2 parameters added"
|
Expect.hasLength paramList 3 "There should have been 2 parameters added"
|
||||||
let p = Array.ofSeq paramList
|
let p = Array.ofSeq paramList
|
||||||
Expect.equal (fst p[0]) "@id" "First field parameter name not correct"
|
Expect.equal (fst p[0]) "@id" "First field parameter name not correct"
|
||||||
@@ -101,11 +101,11 @@ let parametersTests = testList "Parameters" [
|
|||||||
Expect.equal (snd p[2]) (Sql.string "them") "Third parameter value 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 = addFieldParams [ Field.Exists "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" {
|
test "succeeds when two parameters are added for one field" {
|
||||||
let paramList = addFieldParams [ { Field.Between "that" "eh" "zed" with ParameterName = Some "@test" } ] []
|
let paramList = addFieldParams [ { Field.BT "that" "eh" "zed" with ParameterName = Some "@test" } ] []
|
||||||
Expect.hasLength paramList 2 "There should have been 2 parameters added"
|
Expect.hasLength paramList 2 "There should have been 2 parameters added"
|
||||||
let name, value = Seq.head paramList
|
let name, value = Seq.head paramList
|
||||||
Expect.equal name "@testmin" "Minimum field name not correct"
|
Expect.equal name "@testmin" "Minimum field name not correct"
|
||||||
@@ -135,66 +135,48 @@ let parametersTests = testList "Parameters" [
|
|||||||
/// Unit tests for the Query module of the PostgreSQL library
|
/// Unit tests for the Query module of the PostgreSQL library
|
||||||
let queryTests = testList "Query" [
|
let queryTests = testList "Query" [
|
||||||
testList "whereByFields" [
|
testList "whereByFields" [
|
||||||
test "succeeds for a single field when a logical comparison is passed" {
|
test "succeeds for a single field when a logical operator is passed" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.whereByFields Any [ { Field.Greater "theField" "0" with ParameterName = Some "@test" } ])
|
(Query.whereByFields Any [ { Field.GT "theField" "0" with ParameterName = Some "@test" } ])
|
||||||
"data->>'theField' > @test"
|
"data->>'theField' > @test"
|
||||||
"WHERE clause not correct"
|
"WHERE clause not correct"
|
||||||
}
|
}
|
||||||
test "succeeds for a single field when an existence comparison is passed" {
|
test "succeeds for a single field when an existence operator is passed" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.whereByFields Any [ Field.NotExists "thatField" ])
|
(Query.whereByFields Any [ Field.NEX "thatField" ])
|
||||||
"data->>'thatField' IS NULL"
|
"data->>'thatField' IS NULL"
|
||||||
"WHERE clause not correct"
|
"WHERE clause not correct"
|
||||||
}
|
}
|
||||||
test "succeeds for a single field when a between comparison is passed with numeric values" {
|
test "succeeds for a single field when a between operator is passed with numeric values" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.whereByFields All [ { Field.Between "aField" 50 99 with ParameterName = Some "@range" } ])
|
(Query.whereByFields All [ { Field.BT "aField" 50 99 with ParameterName = Some "@range" } ])
|
||||||
"(data->>'aField')::numeric BETWEEN @rangemin AND @rangemax"
|
"(data->>'aField')::numeric BETWEEN @rangemin AND @rangemax"
|
||||||
"WHERE clause not correct"
|
"WHERE clause not correct"
|
||||||
}
|
}
|
||||||
test "succeeds for a single field when a between comparison is passed with non-numeric values" {
|
test "succeeds for a single field when a between operator is passed with non-numeric values" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.whereByFields Any [ { Field.Between "field0" "a" "b" with ParameterName = Some "@alpha" } ])
|
(Query.whereByFields Any [ { Field.BT "field0" "a" "b" with ParameterName = Some "@alpha" } ])
|
||||||
"data->>'field0' BETWEEN @alphamin AND @alphamax"
|
"data->>'field0' BETWEEN @alphamin AND @alphamax"
|
||||||
"WHERE clause not correct"
|
"WHERE clause not correct"
|
||||||
}
|
}
|
||||||
test "succeeds for all multiple fields with logical comparisons" {
|
test "succeeds for all multiple fields with logical operators" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.whereByFields All [ Field.Equal "theFirst" "1"; Field.Equal "numberTwo" "2" ])
|
(Query.whereByFields All [ Field.EQ "theFirst" "1"; Field.EQ "numberTwo" "2" ])
|
||||||
"data->>'theFirst' = @field0 AND data->>'numberTwo' = @field1"
|
"data->>'theFirst' = @field0 AND data->>'numberTwo' = @field1"
|
||||||
"WHERE clause not correct"
|
"WHERE clause not correct"
|
||||||
}
|
}
|
||||||
test "succeeds for any multiple fields with an existence comparisons" {
|
test "succeeds for any multiple fields with an existence operator" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.whereByFields Any [ Field.NotExists "thatField"; Field.GreaterOrEqual "thisField" 18 ])
|
(Query.whereByFields Any [ Field.NEX "thatField"; Field.GE "thisField" 18 ])
|
||||||
"data->>'thatField' IS NULL OR (data->>'thisField')::numeric >= @field0"
|
"data->>'thatField' IS NULL OR (data->>'thisField')::numeric >= @field0"
|
||||||
"WHERE clause not correct"
|
"WHERE clause not correct"
|
||||||
}
|
}
|
||||||
test "succeeds for all multiple fields with between comparisons" {
|
test "succeeds for all multiple fields with between operators" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.whereByFields All [ Field.Between "aField" 50 99; Field.Between "anotherField" "a" "b" ])
|
(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"
|
"(data->>'aField')::numeric BETWEEN @field0min AND @field0max AND data->>'anotherField' BETWEEN @field1min AND @field1max"
|
||||||
"WHERE clause not correct"
|
"WHERE clause not correct"
|
||||||
}
|
}
|
||||||
test "succeeds for a field with an In comparison with alphanumeric values" {
|
|
||||||
Expect.equal
|
|
||||||
(Query.whereByFields All [ Field.In "this" [ "a"; "b"; "c" ] ])
|
|
||||||
"data->>'this' IN (@field0_0, @field0_1, @field0_2)"
|
|
||||||
"WHERE clause not correct"
|
|
||||||
}
|
|
||||||
test "succeeds for a field with an In comparison with numeric values" {
|
|
||||||
Expect.equal
|
|
||||||
(Query.whereByFields All [ Field.In "this" [ 7; 14; 21 ] ])
|
|
||||||
"(data->>'this')::numeric IN (@field0_0, @field0_1, @field0_2)"
|
|
||||||
"WHERE clause not correct"
|
|
||||||
}
|
|
||||||
test "succeeds for a field with an InArray comparison" {
|
|
||||||
Expect.equal
|
|
||||||
(Query.whereByFields All [ Field.InArray "theField" "the_table" [ "q", "r" ] ])
|
|
||||||
"data->'theField' ?| @field0"
|
|
||||||
"WHERE clause not correct"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
testList "whereById" [
|
testList "whereById" [
|
||||||
test "succeeds for numeric ID" {
|
test "succeeds for numeric ID" {
|
||||||
@@ -252,7 +234,7 @@ let queryTests = testList "Query" [
|
|||||||
}
|
}
|
||||||
test "byFields succeeds" {
|
test "byFields succeeds" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.byFields "unit" Any [ Field.Greater "That" 14 ])
|
(Query.byFields "unit" Any [ Field.GT "That" 14 ])
|
||||||
"unit WHERE (data->>'That')::numeric > @field0"
|
"unit WHERE (data->>'That')::numeric > @field0"
|
||||||
"By-Field query not correct"
|
"By-Field query not correct"
|
||||||
}
|
}
|
||||||
@@ -270,9 +252,18 @@ let queryTests = testList "Query" [
|
|||||||
open ThrowawayDb.Postgres
|
open ThrowawayDb.Postgres
|
||||||
open Types
|
open Types
|
||||||
|
|
||||||
|
/// Documents to use for integration tests
|
||||||
|
let documents = [
|
||||||
|
{ Id = "one"; Value = "FIRST!"; NumValue = 0; Sub = None }
|
||||||
|
{ Id = "two"; Value = "another"; NumValue = 10; Sub = Some { Foo = "green"; Bar = "blue" } }
|
||||||
|
{ Id = "three"; Value = ""; NumValue = 4; Sub = None }
|
||||||
|
{ Id = "four"; Value = "purple"; NumValue = 17; Sub = Some { Foo = "green"; Bar = "red" } }
|
||||||
|
{ Id = "five"; Value = "purple"; NumValue = 18; Sub = None }
|
||||||
|
]
|
||||||
|
|
||||||
/// Load the test documents into the database
|
/// Load the test documents into the database
|
||||||
let loadDocs () = backgroundTask {
|
let loadDocs () = backgroundTask {
|
||||||
for doc in testDocuments do do! insert PostgresDb.TableName doc
|
for doc in documents do do! insert PostgresDb.TableName doc
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Integration tests for the Configuration module of the PostgreSQL library
|
/// Integration tests for the Configuration module of the PostgreSQL library
|
||||||
@@ -444,68 +435,6 @@ let documentTests = testList "Document" [
|
|||||||
|> Async.RunSynchronously)
|
|> Async.RunSynchronously)
|
||||||
"An exception should have been raised for duplicate document ID insert"
|
"An exception should have been raised for duplicate document ID insert"
|
||||||
}
|
}
|
||||||
testTask "succeeds when adding a numeric auto ID" {
|
|
||||||
try
|
|
||||||
Configuration.useAutoIdStrategy Number
|
|
||||||
Configuration.useIdField "Key"
|
|
||||||
use db = PostgresDb.BuildDb()
|
|
||||||
let! before = Count.all PostgresDb.TableName
|
|
||||||
Expect.equal before 0 "There should be no documents in the table"
|
|
||||||
|
|
||||||
do! insert PostgresDb.TableName { Key = 0; Text = "one" }
|
|
||||||
do! insert PostgresDb.TableName { Key = 0; Text = "two" }
|
|
||||||
do! insert PostgresDb.TableName { Key = 77; Text = "three" }
|
|
||||||
do! insert PostgresDb.TableName { Key = 0; Text = "four" }
|
|
||||||
|
|
||||||
let! after = Find.allOrdered<NumIdDocument> PostgresDb.TableName [ Field.Named "n:Key" ]
|
|
||||||
Expect.hasLength after 4 "There should have been 4 documents returned"
|
|
||||||
Expect.equal (after |> List.map _.Key) [ 1; 2; 77; 78 ] "The IDs were not generated correctly"
|
|
||||||
finally
|
|
||||||
Configuration.useAutoIdStrategy Disabled
|
|
||||||
Configuration.useIdField "Id"
|
|
||||||
}
|
|
||||||
testTask "succeeds when adding a GUID auto ID" {
|
|
||||||
try
|
|
||||||
Configuration.useAutoIdStrategy Guid
|
|
||||||
use db = PostgresDb.BuildDb()
|
|
||||||
let! before = Count.all PostgresDb.TableName
|
|
||||||
Expect.equal before 0 "There should be no documents in the table"
|
|
||||||
|
|
||||||
do! insert PostgresDb.TableName { emptyDoc with Value = "one" }
|
|
||||||
do! insert PostgresDb.TableName { emptyDoc with Value = "two" }
|
|
||||||
do! insert PostgresDb.TableName { emptyDoc with Id = "abc123"; Value = "three" }
|
|
||||||
do! insert PostgresDb.TableName { emptyDoc with Value = "four" }
|
|
||||||
|
|
||||||
let! after = Find.all<JsonDocument> PostgresDb.TableName
|
|
||||||
Expect.hasLength after 4 "There should have been 4 documents returned"
|
|
||||||
Expect.hasCountOf after 3u (fun doc -> doc.Id.Length = 32) "Three of the IDs should have been GUIDs"
|
|
||||||
Expect.hasCountOf after 1u (fun doc -> doc.Id = "abc123") "The provided ID should have been used as-is"
|
|
||||||
finally
|
|
||||||
Configuration.useAutoIdStrategy Disabled
|
|
||||||
}
|
|
||||||
testTask "succeeds when adding a RandomString auto ID" {
|
|
||||||
try
|
|
||||||
Configuration.useAutoIdStrategy RandomString
|
|
||||||
Configuration.useIdStringLength 44
|
|
||||||
use db = PostgresDb.BuildDb()
|
|
||||||
let! before = Count.all PostgresDb.TableName
|
|
||||||
Expect.equal before 0 "There should be no documents in the table"
|
|
||||||
|
|
||||||
do! insert PostgresDb.TableName { emptyDoc with Value = "one" }
|
|
||||||
do! insert PostgresDb.TableName { emptyDoc with Value = "two" }
|
|
||||||
do! insert PostgresDb.TableName { emptyDoc with Id = "abc123"; Value = "three" }
|
|
||||||
do! insert PostgresDb.TableName { emptyDoc with Value = "four" }
|
|
||||||
|
|
||||||
let! after = Find.all<JsonDocument> PostgresDb.TableName
|
|
||||||
Expect.hasLength after 4 "There should have been 4 documents returned"
|
|
||||||
Expect.hasCountOf
|
|
||||||
after 3u (fun doc -> doc.Id.Length = 44)
|
|
||||||
"Three of the IDs should have been 44-character random strings"
|
|
||||||
Expect.hasCountOf after 1u (fun doc -> doc.Id = "abc123") "The provided ID should have been used as-is"
|
|
||||||
finally
|
|
||||||
Configuration.useAutoIdStrategy Disabled
|
|
||||||
Configuration.useIdStringLength 16
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
testList "save" [
|
testList "save" [
|
||||||
testTask "succeeds when a document is inserted" {
|
testTask "succeeds when a document is inserted" {
|
||||||
@@ -550,15 +479,14 @@ let countTests = testList "Count" [
|
|||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! theCount =
|
let! theCount = Count.byFields PostgresDb.TableName Any [ Field.BT "NumValue" 15 20; Field.EQ "NumValue" 0 ]
|
||||||
Count.byFields PostgresDb.TableName Any [ Field.Between "NumValue" 15 20; Field.Equal "NumValue" 0 ]
|
|
||||||
Expect.equal theCount 3 "There should have been 3 matching documents"
|
Expect.equal theCount 3 "There should have been 3 matching documents"
|
||||||
}
|
}
|
||||||
testTask "succeeds when items are not found" {
|
testTask "succeeds when items are not found" {
|
||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! theCount = Count.byFields PostgresDb.TableName All [ Field.Exists "Sub"; Field.Greater "NumValue" 100 ]
|
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"
|
Expect.equal theCount 0 "There should have been no matching documents"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -601,14 +529,14 @@ let existsTests = testList "Exists" [
|
|||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! exists = Exists.byFields PostgresDb.TableName Any [ Field.Exists "Sub"; Field.Exists "Boo" ]
|
let! exists = Exists.byFields PostgresDb.TableName Any [ Field.EX "Sub"; Field.EX "Boo" ]
|
||||||
Expect.isTrue exists "There should have been existing documents"
|
Expect.isTrue exists "There should have been existing documents"
|
||||||
}
|
}
|
||||||
testTask "succeeds when documents do not exist" {
|
testTask "succeeds when documents do not exist" {
|
||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! exists = Exists.byFields PostgresDb.TableName All [ Field.Equal "NumValue" "six"; Field.Exists "Nope" ]
|
let! exists = Exists.byFields PostgresDb.TableName All [ Field.EQ "NumValue" "six"; Field.EX "Nope" ]
|
||||||
Expect.isFalse exists "There should not have been existing documents"
|
Expect.isFalse exists "There should not have been existing documents"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -726,16 +654,8 @@ let findTests = testList "Find" [
|
|||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! docs =
|
let! docs =
|
||||||
Find.byFields<JsonDocument>
|
Find.byFields<JsonDocument> PostgresDb.TableName All [ Field.EQ "Value" "purple"; Field.EX "Sub" ]
|
||||||
PostgresDb.TableName All [ Field.In "Value" [ "purple"; "blue" ]; Field.Exists "Sub" ]
|
Expect.equal (List.length docs) 1 "There should have been one document returned"
|
||||||
Expect.hasLength docs 1 "There should have been one document returned"
|
|
||||||
}
|
|
||||||
testTask "succeeds when documents are found using IN with numeric field" {
|
|
||||||
use db = PostgresDb.BuildDb()
|
|
||||||
do! loadDocs ()
|
|
||||||
|
|
||||||
let! docs = Find.byFields<JsonDocument> PostgresDb.TableName All [ Field.In "NumValue" [ 2; 4; 6; 8 ] ]
|
|
||||||
Expect.hasLength docs 1 "There should have been one document returned"
|
|
||||||
}
|
}
|
||||||
testTask "succeeds when documents are not found" {
|
testTask "succeeds when documents are not found" {
|
||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
@@ -743,27 +663,7 @@ let findTests = testList "Find" [
|
|||||||
|
|
||||||
let! docs =
|
let! docs =
|
||||||
Find.byFields<JsonDocument>
|
Find.byFields<JsonDocument>
|
||||||
PostgresDb.TableName All [ Field.Equal "Value" "mauve"; Field.NotEqual "NumValue" 40 ]
|
PostgresDb.TableName All [ Field.EQ "Value" "mauve"; Field.NE "NumValue" 40 ]
|
||||||
Expect.isEmpty docs "There should have been no documents returned"
|
|
||||||
}
|
|
||||||
testTask "succeeds for InArray when matching documents exist" {
|
|
||||||
use db = PostgresDb.BuildDb()
|
|
||||||
do! Definition.ensureTable PostgresDb.TableName
|
|
||||||
for doc in ArrayDocument.TestDocuments do do! insert PostgresDb.TableName doc
|
|
||||||
|
|
||||||
let! docs =
|
|
||||||
Find.byFields<ArrayDocument>
|
|
||||||
PostgresDb.TableName All [ Field.InArray "Values" PostgresDb.TableName [ "c" ] ]
|
|
||||||
Expect.hasLength docs 2 "There should have been two documents returned"
|
|
||||||
}
|
|
||||||
testTask "succeeds for InArray when no matching documents exist" {
|
|
||||||
use db = PostgresDb.BuildDb()
|
|
||||||
do! Definition.ensureTable PostgresDb.TableName
|
|
||||||
for doc in ArrayDocument.TestDocuments do do! insert PostgresDb.TableName doc
|
|
||||||
|
|
||||||
let! docs =
|
|
||||||
Find.byFields<ArrayDocument>
|
|
||||||
PostgresDb.TableName All [ Field.InArray "Values" PostgresDb.TableName [ "j" ] ]
|
|
||||||
Expect.isEmpty docs "There should have been no documents returned"
|
Expect.isEmpty docs "There should have been no documents returned"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -774,7 +674,7 @@ let findTests = testList "Find" [
|
|||||||
|
|
||||||
let! docs =
|
let! docs =
|
||||||
Find.byFieldsOrdered<JsonDocument>
|
Find.byFieldsOrdered<JsonDocument>
|
||||||
PostgresDb.TableName All [ Field.Equal "Value" "purple" ] [ Field.Named "Id" ]
|
PostgresDb.TableName All [ Field.EQ "Value" "purple" ] [ Field.Named "Id" ]
|
||||||
Expect.hasLength docs 2 "There should have been two documents returned"
|
Expect.hasLength docs 2 "There should have been two documents returned"
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(docs |> List.map _.Id |> String.concat "|") "five|four" "Documents not ordered correctly"
|
(docs |> List.map _.Id |> String.concat "|") "five|four" "Documents not ordered correctly"
|
||||||
@@ -785,7 +685,7 @@ let findTests = testList "Find" [
|
|||||||
|
|
||||||
let! docs =
|
let! docs =
|
||||||
Find.byFieldsOrdered<JsonDocument>
|
Find.byFieldsOrdered<JsonDocument>
|
||||||
PostgresDb.TableName All [ Field.Equal "Value" "purple" ] [ Field.Named "Id DESC" ]
|
PostgresDb.TableName All [ Field.EQ "Value" "purple" ] [ Field.Named "Id DESC" ]
|
||||||
Expect.hasLength docs 2 "There should have been two documents returned"
|
Expect.hasLength docs 2 "There should have been two documents returned"
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(docs |> List.map _.Id |> String.concat "|") "four|five" "Documents not ordered correctly"
|
(docs |> List.map _.Id |> String.concat "|") "four|five" "Documents not ordered correctly"
|
||||||
@@ -878,7 +778,7 @@ let findTests = testList "Find" [
|
|||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! doc = Find.firstByFields<JsonDocument> PostgresDb.TableName Any [ Field.Equal "Value" "another" ]
|
let! doc = Find.firstByFields<JsonDocument> PostgresDb.TableName Any [ Field.EQ "Value" "another" ]
|
||||||
Expect.isSome doc "There should have been a document returned"
|
Expect.isSome doc "There should have been a document returned"
|
||||||
Expect.equal doc.Value.Id "two" "The incorrect document was returned"
|
Expect.equal doc.Value.Id "two" "The incorrect document was returned"
|
||||||
}
|
}
|
||||||
@@ -886,7 +786,7 @@ let findTests = testList "Find" [
|
|||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! doc = Find.firstByFields<JsonDocument> PostgresDb.TableName Any [ Field.Equal "Value" "purple" ]
|
let! doc = Find.firstByFields<JsonDocument> PostgresDb.TableName Any [ Field.EQ "Value" "purple" ]
|
||||||
Expect.isSome doc "There should have been a document returned"
|
Expect.isSome doc "There should have been a document returned"
|
||||||
Expect.contains [ "five"; "four" ] doc.Value.Id "An incorrect document was returned"
|
Expect.contains [ "five"; "four" ] doc.Value.Id "An incorrect document was returned"
|
||||||
}
|
}
|
||||||
@@ -894,7 +794,7 @@ let findTests = testList "Find" [
|
|||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! doc = Find.firstByFields<JsonDocument> PostgresDb.TableName Any [ Field.Equal "Value" "absent" ]
|
let! doc = Find.firstByFields<JsonDocument> PostgresDb.TableName Any [ Field.EQ "Value" "absent" ]
|
||||||
Expect.isNone doc "There should not have been a document returned"
|
Expect.isNone doc "There should not have been a document returned"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -905,7 +805,7 @@ let findTests = testList "Find" [
|
|||||||
|
|
||||||
let! doc =
|
let! doc =
|
||||||
Find.firstByFieldsOrdered<JsonDocument>
|
Find.firstByFieldsOrdered<JsonDocument>
|
||||||
PostgresDb.TableName Any [ Field.Equal "Value" "purple" ] [ Field.Named "Id" ]
|
PostgresDb.TableName Any [ Field.EQ "Value" "purple" ] [ Field.Named "Id" ]
|
||||||
Expect.isSome doc "There should have been a document returned"
|
Expect.isSome doc "There should have been a document returned"
|
||||||
Expect.equal "five" doc.Value.Id "An incorrect document was returned"
|
Expect.equal "five" doc.Value.Id "An incorrect document was returned"
|
||||||
}
|
}
|
||||||
@@ -915,7 +815,7 @@ let findTests = testList "Find" [
|
|||||||
|
|
||||||
let! doc =
|
let! doc =
|
||||||
Find.firstByFieldsOrdered<JsonDocument>
|
Find.firstByFieldsOrdered<JsonDocument>
|
||||||
PostgresDb.TableName Any [ Field.Equal "Value" "purple" ] [ Field.Named "Id DESC" ]
|
PostgresDb.TableName Any [ Field.EQ "Value" "purple" ] [ Field.Named "Id DESC" ]
|
||||||
Expect.isSome doc "There should have been a document returned"
|
Expect.isSome doc "There should have been a document returned"
|
||||||
Expect.equal "four" doc.Value.Id "An incorrect document was returned"
|
Expect.equal "four" doc.Value.Id "An incorrect document was returned"
|
||||||
}
|
}
|
||||||
@@ -1092,8 +992,8 @@ let patchTests = testList "Patch" [
|
|||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
do! Patch.byFields PostgresDb.TableName Any [ Field.Equal "Value" "purple" ] {| NumValue = 77 |}
|
do! Patch.byFields PostgresDb.TableName Any [ Field.EQ "Value" "purple" ] {| NumValue = 77 |}
|
||||||
let! after = Count.byFields PostgresDb.TableName Any [ Field.Equal "NumValue" 77 ]
|
let! after = Count.byFields PostgresDb.TableName Any [ Field.EQ "NumValue" 77 ]
|
||||||
Expect.equal after 2 "There should have been 2 documents returned"
|
Expect.equal after 2 "There should have been 2 documents returned"
|
||||||
}
|
}
|
||||||
testTask "succeeds when no document is updated" {
|
testTask "succeeds when no document is updated" {
|
||||||
@@ -1103,7 +1003,7 @@ let patchTests = testList "Patch" [
|
|||||||
Expect.equal before 0 "There should have been no documents returned"
|
Expect.equal before 0 "There should have been no documents returned"
|
||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
do! Patch.byFields PostgresDb.TableName Any [ Field.Equal "Value" "burgundy" ] {| Foo = "green" |}
|
do! Patch.byFields PostgresDb.TableName Any [ Field.EQ "Value" "burgundy" ] {| Foo = "green" |}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "byContains" [
|
testList "byContains" [
|
||||||
@@ -1154,9 +1054,9 @@ let removeFieldsTests = testList "RemoveFields" [
|
|||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
do! RemoveFields.byId PostgresDb.TableName "two" [ "Sub"; "Value" ]
|
do! RemoveFields.byId PostgresDb.TableName "two" [ "Sub"; "Value" ]
|
||||||
let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Sub" ]
|
let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ]
|
||||||
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
||||||
let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Value" ]
|
let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ]
|
||||||
Expect.equal noValue 1 "There should be 1 document without Value fields"
|
Expect.equal noValue 1 "There should be 1 document without Value fields"
|
||||||
}
|
}
|
||||||
testTask "succeeds when a single field is removed" {
|
testTask "succeeds when a single field is removed" {
|
||||||
@@ -1164,9 +1064,9 @@ let removeFieldsTests = testList "RemoveFields" [
|
|||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
do! RemoveFields.byId PostgresDb.TableName "two" [ "Sub" ]
|
do! RemoveFields.byId PostgresDb.TableName "two" [ "Sub" ]
|
||||||
let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Sub" ]
|
let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ]
|
||||||
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
||||||
let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Value" ]
|
let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ]
|
||||||
Expect.equal noValue 0 "There should be no documents without Value fields"
|
Expect.equal noValue 0 "There should be no documents without Value fields"
|
||||||
}
|
}
|
||||||
testTask "succeeds when a field is not removed" {
|
testTask "succeeds when a field is not removed" {
|
||||||
@@ -1188,20 +1088,20 @@ let removeFieldsTests = testList "RemoveFields" [
|
|||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
do! RemoveFields.byFields PostgresDb.TableName Any [ Field.Equal "NumValue" 17 ] [ "Sub"; "Value" ]
|
do! RemoveFields.byFields PostgresDb.TableName Any [ Field.EQ "NumValue" "17" ] [ "Sub"; "Value" ]
|
||||||
let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Sub" ]
|
let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ]
|
||||||
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
||||||
let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Value" ]
|
let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ]
|
||||||
Expect.equal noValue 1 "There should be 1 document without Value fields"
|
Expect.equal noValue 1 "There should be 1 document without Value fields"
|
||||||
}
|
}
|
||||||
testTask "succeeds when a single field is removed" {
|
testTask "succeeds when a single field is removed" {
|
||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
do! RemoveFields.byFields PostgresDb.TableName Any [ Field.Equal "NumValue" 17 ] [ "Sub" ]
|
do! RemoveFields.byFields PostgresDb.TableName Any [ Field.EQ "NumValue" "17" ] [ "Sub" ]
|
||||||
let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Sub" ]
|
let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ]
|
||||||
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
||||||
let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Value" ]
|
let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ]
|
||||||
Expect.equal noValue 0 "There should be no documents without Value fields"
|
Expect.equal noValue 0 "There should be no documents without Value fields"
|
||||||
}
|
}
|
||||||
testTask "succeeds when a field is not removed" {
|
testTask "succeeds when a field is not removed" {
|
||||||
@@ -1209,13 +1109,13 @@ let removeFieldsTests = testList "RemoveFields" [
|
|||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
do! RemoveFields.byFields PostgresDb.TableName Any [ Field.Equal "NumValue" 17 ] [ "Nothing" ]
|
do! RemoveFields.byFields PostgresDb.TableName Any [ Field.EQ "NumValue" "17" ] [ "Nothing" ]
|
||||||
}
|
}
|
||||||
testTask "succeeds when no document is matched" {
|
testTask "succeeds when no document is matched" {
|
||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
do! RemoveFields.byFields PostgresDb.TableName Any [ Field.NotEqual "Abracadabra" "apple" ] [ "Value" ]
|
do! RemoveFields.byFields PostgresDb.TableName Any [ Field.NE "Abracadabra" "apple" ] [ "Value" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "byContains" [
|
testList "byContains" [
|
||||||
@@ -1224,9 +1124,9 @@ let removeFieldsTests = testList "RemoveFields" [
|
|||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
do! RemoveFields.byContains PostgresDb.TableName {| NumValue = 17 |} [ "Sub"; "Value" ]
|
do! RemoveFields.byContains PostgresDb.TableName {| NumValue = 17 |} [ "Sub"; "Value" ]
|
||||||
let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Sub" ]
|
let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ]
|
||||||
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
||||||
let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Value" ]
|
let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ]
|
||||||
Expect.equal noValue 1 "There should be 1 document without Value fields"
|
Expect.equal noValue 1 "There should be 1 document without Value fields"
|
||||||
}
|
}
|
||||||
testTask "succeeds when a single field is removed" {
|
testTask "succeeds when a single field is removed" {
|
||||||
@@ -1234,9 +1134,9 @@ let removeFieldsTests = testList "RemoveFields" [
|
|||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
do! RemoveFields.byContains PostgresDb.TableName {| NumValue = 17 |} [ "Sub" ]
|
do! RemoveFields.byContains PostgresDb.TableName {| NumValue = 17 |} [ "Sub" ]
|
||||||
let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Sub" ]
|
let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ]
|
||||||
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
||||||
let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Value" ]
|
let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ]
|
||||||
Expect.equal noValue 0 "There should be no documents without Value fields"
|
Expect.equal noValue 0 "There should be no documents without Value fields"
|
||||||
}
|
}
|
||||||
testTask "succeeds when a field is not removed" {
|
testTask "succeeds when a field is not removed" {
|
||||||
@@ -1259,9 +1159,9 @@ let removeFieldsTests = testList "RemoveFields" [
|
|||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
do! RemoveFields.byJsonPath PostgresDb.TableName "$.NumValue ? (@ == 17)" [ "Sub"; "Value" ]
|
do! RemoveFields.byJsonPath PostgresDb.TableName "$.NumValue ? (@ == 17)" [ "Sub"; "Value" ]
|
||||||
let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Sub" ]
|
let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ]
|
||||||
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
||||||
let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Value" ]
|
let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ]
|
||||||
Expect.equal noValue 1 "There should be 1 document without Value fields"
|
Expect.equal noValue 1 "There should be 1 document without Value fields"
|
||||||
}
|
}
|
||||||
testTask "succeeds when a single field is removed" {
|
testTask "succeeds when a single field is removed" {
|
||||||
@@ -1269,9 +1169,9 @@ let removeFieldsTests = testList "RemoveFields" [
|
|||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
do! RemoveFields.byJsonPath PostgresDb.TableName "$.NumValue ? (@ == 17)" [ "Sub" ]
|
do! RemoveFields.byJsonPath PostgresDb.TableName "$.NumValue ? (@ == 17)" [ "Sub" ]
|
||||||
let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Sub" ]
|
let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ]
|
||||||
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
Expect.equal noSubs 4 "There should now be 4 documents without Sub fields"
|
||||||
let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Value" ]
|
let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ]
|
||||||
Expect.equal noValue 0 "There should be no documents without Value fields"
|
Expect.equal noValue 0 "There should be no documents without Value fields"
|
||||||
}
|
}
|
||||||
testTask "succeeds when a field is not removed" {
|
testTask "succeeds when a field is not removed" {
|
||||||
@@ -1315,7 +1215,7 @@ let deleteTests = testList "Delete" [
|
|||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
do! Delete.byFields PostgresDb.TableName Any [ Field.Equal "Value" "purple" ]
|
do! Delete.byFields PostgresDb.TableName Any [ Field.EQ "Value" "purple" ]
|
||||||
let! remaining = Count.all PostgresDb.TableName
|
let! remaining = Count.all PostgresDb.TableName
|
||||||
Expect.equal remaining 3 "There should have been 3 documents remaining"
|
Expect.equal remaining 3 "There should have been 3 documents remaining"
|
||||||
}
|
}
|
||||||
@@ -1323,7 +1223,7 @@ let deleteTests = testList "Delete" [
|
|||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
do! Delete.byFields PostgresDb.TableName Any [ Field.Equal "Value" "crimson" ]
|
do! Delete.byFields PostgresDb.TableName Any [ Field.EQ "Value" "crimson" ]
|
||||||
let! remaining = Count.all PostgresDb.TableName
|
let! remaining = Count.all PostgresDb.TableName
|
||||||
Expect.equal remaining 5 "There should have been 5 documents remaining"
|
Expect.equal remaining 5 "There should have been 5 documents remaining"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ let integrationTests =
|
|||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! theCount = conn.countByFields SqliteDb.TableName Any [ Field.Equal "Value" "purple" ]
|
let! theCount = conn.countByFields SqliteDb.TableName Any [ 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" [
|
testList "existsById" [
|
||||||
@@ -145,7 +145,7 @@ let integrationTests =
|
|||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! exists = conn.existsByFields SqliteDb.TableName Any [ Field.Equal "NumValue" 10 ]
|
let! exists = conn.existsByFields SqliteDb.TableName Any [ Field.EQ "NumValue" 10 ]
|
||||||
Expect.isTrue exists "There should have been existing documents"
|
Expect.isTrue exists "There should have been existing documents"
|
||||||
}
|
}
|
||||||
testTask "succeeds when no matching documents exist" {
|
testTask "succeeds when no matching documents exist" {
|
||||||
@@ -153,7 +153,7 @@ let integrationTests =
|
|||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! exists = conn.existsByFields SqliteDb.TableName Any [ Field.Equal "Nothing" "none" ]
|
let! exists = conn.existsByFields SqliteDb.TableName Any [ 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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -244,7 +244,7 @@ let integrationTests =
|
|||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! docs = conn.findByFields<JsonDocument> SqliteDb.TableName Any [ Field.Equal "Sub.Foo" "green" ]
|
let! docs = conn.findByFields<JsonDocument> SqliteDb.TableName Any [ Field.EQ "Sub.Foo" "green" ]
|
||||||
Expect.hasLength docs 2 "There should have been two documents returned"
|
Expect.hasLength docs 2 "There should have been two documents returned"
|
||||||
}
|
}
|
||||||
testTask "succeeds when documents are not found" {
|
testTask "succeeds when documents are not found" {
|
||||||
@@ -252,7 +252,7 @@ let integrationTests =
|
|||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! docs = conn.findByFields<JsonDocument> SqliteDb.TableName Any [ Field.Equal "Value" "mauve" ]
|
let! docs = conn.findByFields<JsonDocument> SqliteDb.TableName Any [ Field.EQ "Value" "mauve" ]
|
||||||
Expect.isEmpty docs "There should have been no documents returned"
|
Expect.isEmpty docs "There should have been no documents returned"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -264,7 +264,7 @@ let integrationTests =
|
|||||||
|
|
||||||
let! docs =
|
let! docs =
|
||||||
conn.findByFieldsOrdered<JsonDocument>
|
conn.findByFieldsOrdered<JsonDocument>
|
||||||
SqliteDb.TableName Any [ Field.Greater "NumValue" 15 ] [ Field.Named "Id" ]
|
SqliteDb.TableName Any [ Field.GT "NumValue" 15 ] [ Field.Named "Id" ]
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(docs |> List.map _.Id |> String.concat "|") "five|four" "The documents were not ordered correctly"
|
(docs |> List.map _.Id |> String.concat "|") "five|four" "The documents were not ordered correctly"
|
||||||
}
|
}
|
||||||
@@ -275,7 +275,7 @@ let integrationTests =
|
|||||||
|
|
||||||
let! docs =
|
let! docs =
|
||||||
conn.findByFieldsOrdered<JsonDocument>
|
conn.findByFieldsOrdered<JsonDocument>
|
||||||
SqliteDb.TableName Any [ Field.Greater "NumValue" 15 ] [ Field.Named "Id DESC" ]
|
SqliteDb.TableName Any [ Field.GT "NumValue" 15 ] [ Field.Named "Id DESC" ]
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(docs |> List.map _.Id |> String.concat "|") "four|five" "The documents were not ordered correctly"
|
(docs |> List.map _.Id |> String.concat "|") "four|five" "The documents were not ordered correctly"
|
||||||
}
|
}
|
||||||
@@ -286,7 +286,7 @@ let integrationTests =
|
|||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! doc = conn.findFirstByFields<JsonDocument> SqliteDb.TableName Any [ Field.Equal "Value" "another" ]
|
let! doc = conn.findFirstByFields<JsonDocument> SqliteDb.TableName Any [ Field.EQ "Value" "another" ]
|
||||||
Expect.isSome doc "There should have been a document returned"
|
Expect.isSome doc "There should have been a document returned"
|
||||||
Expect.equal doc.Value.Id "two" "The incorrect document was returned"
|
Expect.equal doc.Value.Id "two" "The incorrect document was returned"
|
||||||
}
|
}
|
||||||
@@ -295,7 +295,7 @@ let integrationTests =
|
|||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! doc = conn.findFirstByFields<JsonDocument> SqliteDb.TableName Any [ Field.Equal "Sub.Foo" "green" ]
|
let! doc = conn.findFirstByFields<JsonDocument> SqliteDb.TableName Any [ Field.EQ "Sub.Foo" "green" ]
|
||||||
Expect.isSome doc "There should have been a document returned"
|
Expect.isSome doc "There should have been a document returned"
|
||||||
Expect.contains [ "two"; "four" ] doc.Value.Id "An incorrect document was returned"
|
Expect.contains [ "two"; "four" ] doc.Value.Id "An incorrect document was returned"
|
||||||
}
|
}
|
||||||
@@ -304,7 +304,7 @@ let integrationTests =
|
|||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! doc = conn.findFirstByFields<JsonDocument> SqliteDb.TableName Any [ Field.Equal "Value" "absent" ]
|
let! doc = conn.findFirstByFields<JsonDocument> SqliteDb.TableName Any [ Field.EQ "Value" "absent" ]
|
||||||
Expect.isNone doc "There should not have been a document returned"
|
Expect.isNone doc "There should not have been a document returned"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -316,7 +316,7 @@ let integrationTests =
|
|||||||
|
|
||||||
let! doc =
|
let! doc =
|
||||||
conn.findFirstByFieldsOrdered<JsonDocument>
|
conn.findFirstByFieldsOrdered<JsonDocument>
|
||||||
SqliteDb.TableName Any [ Field.Equal "Sub.Foo" "green" ] [ Field.Named "Sub.Bar" ]
|
SqliteDb.TableName Any [ Field.EQ "Sub.Foo" "green" ] [ Field.Named "Sub.Bar" ]
|
||||||
Expect.isSome doc "There should have been a document returned"
|
Expect.isSome doc "There should have been a document returned"
|
||||||
Expect.equal "two" doc.Value.Id "An incorrect document was returned"
|
Expect.equal "two" doc.Value.Id "An incorrect document was returned"
|
||||||
}
|
}
|
||||||
@@ -327,7 +327,7 @@ let integrationTests =
|
|||||||
|
|
||||||
let! doc =
|
let! doc =
|
||||||
conn.findFirstByFieldsOrdered<JsonDocument>
|
conn.findFirstByFieldsOrdered<JsonDocument>
|
||||||
SqliteDb.TableName Any [ Field.Equal "Sub.Foo" "green" ] [ Field.Named "Sub.Bar DESC" ]
|
SqliteDb.TableName Any [ Field.EQ "Sub.Foo" "green" ] [ Field.Named "Sub.Bar DESC" ]
|
||||||
Expect.isSome doc "There should have been a document returned"
|
Expect.isSome doc "There should have been a document returned"
|
||||||
Expect.equal "four" doc.Value.Id "An incorrect document was returned"
|
Expect.equal "four" doc.Value.Id "An incorrect document was returned"
|
||||||
}
|
}
|
||||||
@@ -416,8 +416,8 @@ let integrationTests =
|
|||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
do! conn.patchByFields SqliteDb.TableName Any [ Field.Equal "Value" "purple" ] {| NumValue = 77 |}
|
do! conn.patchByFields SqliteDb.TableName Any [ Field.EQ "Value" "purple" ] {| NumValue = 77 |}
|
||||||
let! after = conn.countByFields SqliteDb.TableName Any [ Field.Equal "NumValue" 77 ]
|
let! after = conn.countByFields SqliteDb.TableName Any [ Field.EQ "NumValue" 77 ]
|
||||||
Expect.equal after 2L "There should have been 2 documents returned"
|
Expect.equal after 2L "There should have been 2 documents returned"
|
||||||
}
|
}
|
||||||
testTask "succeeds when no document is updated" {
|
testTask "succeeds when no document is updated" {
|
||||||
@@ -428,7 +428,7 @@ let integrationTests =
|
|||||||
Expect.isEmpty before "There should have been no documents returned"
|
Expect.isEmpty before "There should have been no documents returned"
|
||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
do! conn.patchByFields SqliteDb.TableName Any [ Field.Equal "Value" "burgundy" ] {| Foo = "green" |}
|
do! conn.patchByFields SqliteDb.TableName Any [ Field.EQ "Value" "burgundy" ] {| Foo = "green" |}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "removeFieldsById" [
|
testList "removeFieldsById" [
|
||||||
@@ -467,7 +467,7 @@ let integrationTests =
|
|||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
do! conn.removeFieldsByFields SqliteDb.TableName Any [ Field.Equal "NumValue" 17 ] [ "Sub" ]
|
do! conn.removeFieldsByFields SqliteDb.TableName Any [ Field.EQ "NumValue" 17 ] [ "Sub" ]
|
||||||
try
|
try
|
||||||
let! _ = conn.findById<string, JsonDocument> SqliteDb.TableName "four"
|
let! _ = conn.findById<string, JsonDocument> SqliteDb.TableName "four"
|
||||||
Expect.isTrue false "The updated document should have failed to parse"
|
Expect.isTrue false "The updated document should have failed to parse"
|
||||||
@@ -481,15 +481,14 @@ let integrationTests =
|
|||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
do! conn.removeFieldsByFields SqliteDb.TableName Any [ Field.Equal "NumValue" 17 ] [ "Nothing" ]
|
do! conn.removeFieldsByFields SqliteDb.TableName Any [ Field.EQ "NumValue" 17 ] [ "Nothing" ]
|
||||||
}
|
}
|
||||||
testTask "succeeds when no document is matched" {
|
testTask "succeeds when no document is matched" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
do! conn.removeFieldsByFields
|
do! conn.removeFieldsByFields SqliteDb.TableName Any [ Field.NE "Abracadabra" "apple" ] [ "Value" ]
|
||||||
SqliteDb.TableName Any [ Field.NotEqual "Abracadabra" "apple" ] [ "Value" ]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "deleteById" [
|
testList "deleteById" [
|
||||||
@@ -518,7 +517,7 @@ let integrationTests =
|
|||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
do! conn.deleteByFields SqliteDb.TableName Any [ Field.NotEqual "Value" "purple" ]
|
do! conn.deleteByFields SqliteDb.TableName Any [ Field.NE "Value" "purple" ]
|
||||||
let! remaining = conn.countAll SqliteDb.TableName
|
let! remaining = conn.countAll SqliteDb.TableName
|
||||||
Expect.equal remaining 2L "There should have been 2 documents remaining"
|
Expect.equal remaining 2L "There should have been 2 documents remaining"
|
||||||
}
|
}
|
||||||
@@ -527,7 +526,7 @@ let integrationTests =
|
|||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
do! conn.deleteByFields SqliteDb.TableName Any [ Field.Equal "Value" "crimson" ]
|
do! conn.deleteByFields SqliteDb.TableName Any [ Field.EQ "Value" "crimson" ]
|
||||||
let! remaining = conn.countAll SqliteDb.TableName
|
let! remaining = 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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,54 +15,42 @@ open Types
|
|||||||
/// Unit tests for the Query module of the SQLite library
|
/// Unit tests for the Query module of the SQLite library
|
||||||
let queryTests = testList "Query" [
|
let queryTests = testList "Query" [
|
||||||
testList "whereByFields" [
|
testList "whereByFields" [
|
||||||
test "succeeds for a single field when a logical comparison is passed" {
|
test "succeeds for a single field when a logical operator is passed" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.whereByFields Any [ { Field.Greater "theField" 0 with ParameterName = Some "@test" } ])
|
(Query.whereByFields Any [ { Field.GT "theField" 0 with ParameterName = Some "@test" } ])
|
||||||
"data->>'theField' > @test"
|
"data->>'theField' > @test"
|
||||||
"WHERE clause not correct"
|
"WHERE clause not correct"
|
||||||
}
|
}
|
||||||
test "succeeds for a single field when an existence comparison is passed" {
|
test "succeeds for a single field when an existence operator is passed" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.whereByFields Any [ Field.NotExists "thatField" ])
|
(Query.whereByFields Any [ Field.NEX "thatField" ])
|
||||||
"data->>'thatField' IS NULL"
|
"data->>'thatField' IS NULL"
|
||||||
"WHERE clause not correct"
|
"WHERE clause not correct"
|
||||||
}
|
}
|
||||||
test "succeeds for a single field when a between comparison is passed" {
|
test "succeeds for a single field when a between operator is passed" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.whereByFields All [ { Field.Between "aField" 50 99 with ParameterName = Some "@range" } ])
|
(Query.whereByFields All [ { Field.BT "aField" 50 99 with ParameterName = Some "@range" } ])
|
||||||
"data->>'aField' BETWEEN @rangemin AND @rangemax"
|
"data->>'aField' BETWEEN @rangemin AND @rangemax"
|
||||||
"WHERE clause not correct"
|
"WHERE clause not correct"
|
||||||
}
|
}
|
||||||
test "succeeds for all multiple fields with logical comparisons" {
|
test "succeeds for all multiple fields with logical operators" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.whereByFields All [ Field.Equal "theFirst" "1"; Field.Equal "numberTwo" "2" ])
|
(Query.whereByFields All [ Field.EQ "theFirst" "1"; Field.EQ "numberTwo" "2" ])
|
||||||
"data->>'theFirst' = @field0 AND data->>'numberTwo' = @field1"
|
"data->>'theFirst' = @field0 AND data->>'numberTwo' = @field1"
|
||||||
"WHERE clause not correct"
|
"WHERE clause not correct"
|
||||||
}
|
}
|
||||||
test "succeeds for any multiple fields with an existence comparison" {
|
test "succeeds for any multiple fields with an existence operator" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.whereByFields Any [ Field.NotExists "thatField"; Field.GreaterOrEqual "thisField" 18 ])
|
(Query.whereByFields Any [ Field.NEX "thatField"; Field.GE "thisField" 18 ])
|
||||||
"data->>'thatField' IS NULL OR data->>'thisField' >= @field0"
|
"data->>'thatField' IS NULL OR data->>'thisField' >= @field0"
|
||||||
"WHERE clause not correct"
|
"WHERE clause not correct"
|
||||||
}
|
}
|
||||||
test "succeeds for all multiple fields with between comparisons" {
|
test "succeeds for all multiple fields with between operators" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.whereByFields All [ Field.Between "aField" 50 99; Field.Between "anotherField" "a" "b" ])
|
(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"
|
"data->>'aField' BETWEEN @field0min AND @field0max AND data->>'anotherField' BETWEEN @field1min AND @field1max"
|
||||||
"WHERE clause not correct"
|
"WHERE clause not correct"
|
||||||
}
|
}
|
||||||
test "succeeds for a field with an In comparison" {
|
|
||||||
Expect.equal
|
|
||||||
(Query.whereByFields All [ Field.In "this" [ "a"; "b"; "c" ] ])
|
|
||||||
"data->>'this' IN (@field0_0, @field0_1, @field0_2)"
|
|
||||||
"WHERE clause not correct"
|
|
||||||
}
|
|
||||||
test "succeeds for a field with an InArray comparison" {
|
|
||||||
Expect.equal
|
|
||||||
(Query.whereByFields All [ Field.InArray "this" "the_table" [ "a"; "b" ] ])
|
|
||||||
"EXISTS (SELECT 1 FROM json_each(the_table.data, '$.this') WHERE value IN (@field0_0, @field0_1))"
|
|
||||||
"WHERE clause not correct"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
test "whereById succeeds" {
|
test "whereById succeeds" {
|
||||||
Expect.equal (Query.whereById "@id") "data->>'Id' = @id" "WHERE clause not correct"
|
Expect.equal (Query.whereById "@id") "data->>'Id' = @id" "WHERE clause not correct"
|
||||||
@@ -84,7 +72,7 @@ let queryTests = testList "Query" [
|
|||||||
}
|
}
|
||||||
test "byFields succeeds" {
|
test "byFields succeeds" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.byFields "unit" Any [ Field.Greater "That" 14 ])
|
(Query.byFields "unit" Any [ Field.GT "That" 14 ])
|
||||||
"unit WHERE data->>'That' > @field0"
|
"unit WHERE data->>'That' > @field0"
|
||||||
"By-Field query not correct"
|
"By-Field query not correct"
|
||||||
}
|
}
|
||||||
@@ -110,14 +98,14 @@ let parametersTests = testList "Parameters" [
|
|||||||
}
|
}
|
||||||
testList "addFieldParam" [
|
testList "addFieldParam" [
|
||||||
test "succeeds when adding a parameter" {
|
test "succeeds when adding a parameter" {
|
||||||
let paramList = addFieldParam "@field" (Field.Equal "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 = Seq.head paramList
|
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"
|
||||||
}
|
}
|
||||||
test "succeeds when not adding a parameter" {
|
test "succeeds when not adding a parameter" {
|
||||||
let paramList = addFieldParam "@it" (Field.NotExists "Coffee") []
|
let paramList = addFieldParam "@it" (Field.NEX "Coffee") []
|
||||||
Expect.isEmpty paramList "There should not have been any parameters added"
|
Expect.isEmpty paramList "There should not have been any parameters added"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -130,9 +118,18 @@ let parametersTests = testList "Parameters" [
|
|||||||
|
|
||||||
(** INTEGRATION TESTS **)
|
(** INTEGRATION TESTS **)
|
||||||
|
|
||||||
|
/// Documents used for integration tests
|
||||||
|
let documents = [
|
||||||
|
{ Id = "one"; Value = "FIRST!"; NumValue = 0; Sub = None }
|
||||||
|
{ Id = "two"; Value = "another"; NumValue = 10; Sub = Some { Foo = "green"; Bar = "blue" } }
|
||||||
|
{ Id = "three"; Value = ""; NumValue = 4; Sub = None }
|
||||||
|
{ Id = "four"; Value = "purple"; NumValue = 17; Sub = Some { Foo = "green"; Bar = "red" } }
|
||||||
|
{ Id = "five"; Value = "purple"; NumValue = 18; Sub = None }
|
||||||
|
]
|
||||||
|
|
||||||
/// Load a table with the test documents
|
/// Load a table with the test documents
|
||||||
let loadDocs () = backgroundTask {
|
let loadDocs () = backgroundTask {
|
||||||
for doc in testDocuments do do! insert SqliteDb.TableName doc
|
for doc in documents do do! insert SqliteDb.TableName doc
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Integration tests for the Configuration module of the SQLite library
|
/// Integration tests for the Configuration module of the SQLite library
|
||||||
@@ -147,6 +144,32 @@ let configurationTests = testList "Configuration" [
|
|||||||
finally
|
finally
|
||||||
Configuration.useConnectionString "Data Source=:memory:"
|
Configuration.useConnectionString "Data Source=:memory:"
|
||||||
}
|
}
|
||||||
|
test "useSerializer succeeds" {
|
||||||
|
try
|
||||||
|
Configuration.useSerializer
|
||||||
|
{ new IDocumentSerializer with
|
||||||
|
member _.Serialize<'T>(it: 'T) : string = """{"Overridden":true}"""
|
||||||
|
member _.Deserialize<'T>(it: string) : 'T = Unchecked.defaultof<'T>
|
||||||
|
}
|
||||||
|
|
||||||
|
let serialized = Configuration.serializer().Serialize { Foo = "howdy"; Bar = "bye"}
|
||||||
|
Expect.equal serialized """{"Overridden":true}""" "Specified serializer was not used"
|
||||||
|
|
||||||
|
let deserialized = Configuration.serializer().Deserialize<obj> """{"Something":"here"}"""
|
||||||
|
Expect.isNull deserialized "Specified serializer should have returned null"
|
||||||
|
finally
|
||||||
|
Configuration.useSerializer DocumentSerializer.``default``
|
||||||
|
}
|
||||||
|
test "serializer returns configured serializer" {
|
||||||
|
Expect.isTrue (obj.ReferenceEquals(DocumentSerializer.``default``, Configuration.serializer ()))
|
||||||
|
"Serializer should have been the same"
|
||||||
|
}
|
||||||
|
test "useIdField / idField succeeds" {
|
||||||
|
Expect.equal (Configuration.idField ()) "Id" "The default configured ID field was incorrect"
|
||||||
|
Configuration.useIdField "id"
|
||||||
|
Expect.equal (Configuration.idField ()) "id" "useIdField did not set the ID field"
|
||||||
|
Configuration.useIdField "Id"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
/// Integration tests for the Custom module of the SQLite library
|
/// Integration tests for the Custom module of the SQLite library
|
||||||
@@ -286,68 +309,6 @@ let documentTests = testList "Document" [
|
|||||||
insert SqliteDb.TableName {emptyDoc with Id = "test" } |> Async.AwaitTask |> Async.RunSynchronously)
|
insert SqliteDb.TableName {emptyDoc with Id = "test" } |> Async.AwaitTask |> Async.RunSynchronously)
|
||||||
"An exception should have been raised for duplicate document ID insert"
|
"An exception should have been raised for duplicate document ID insert"
|
||||||
}
|
}
|
||||||
testTask "succeeds when adding a numeric auto ID" {
|
|
||||||
try
|
|
||||||
Configuration.useAutoIdStrategy Number
|
|
||||||
Configuration.useIdField "Key"
|
|
||||||
use! db = SqliteDb.BuildDb()
|
|
||||||
let! before = Count.all SqliteDb.TableName
|
|
||||||
Expect.equal before 0L "There should be no documents in the table"
|
|
||||||
|
|
||||||
do! insert SqliteDb.TableName { Key = 0; Text = "one" }
|
|
||||||
do! insert SqliteDb.TableName { Key = 0; Text = "two" }
|
|
||||||
do! insert SqliteDb.TableName { Key = 77; Text = "three" }
|
|
||||||
do! insert SqliteDb.TableName { Key = 0; Text = "four" }
|
|
||||||
|
|
||||||
let! after = Find.allOrdered<NumIdDocument> SqliteDb.TableName [ Field.Named "Key" ]
|
|
||||||
Expect.hasLength after 4 "There should have been 4 documents returned"
|
|
||||||
Expect.equal (after |> List.map _.Key) [ 1; 2; 77; 78 ] "The IDs were not generated correctly"
|
|
||||||
finally
|
|
||||||
Configuration.useAutoIdStrategy Disabled
|
|
||||||
Configuration.useIdField "Id"
|
|
||||||
}
|
|
||||||
testTask "succeeds when adding a GUID auto ID" {
|
|
||||||
try
|
|
||||||
Configuration.useAutoIdStrategy Guid
|
|
||||||
use! db = SqliteDb.BuildDb()
|
|
||||||
let! before = Count.all SqliteDb.TableName
|
|
||||||
Expect.equal before 0L "There should be no documents in the table"
|
|
||||||
|
|
||||||
do! insert SqliteDb.TableName { emptyDoc with Value = "one" }
|
|
||||||
do! insert SqliteDb.TableName { emptyDoc with Value = "two" }
|
|
||||||
do! insert SqliteDb.TableName { emptyDoc with Id = "abc123"; Value = "three" }
|
|
||||||
do! insert SqliteDb.TableName { emptyDoc with Value = "four" }
|
|
||||||
|
|
||||||
let! after = Find.all<JsonDocument> SqliteDb.TableName
|
|
||||||
Expect.hasLength after 4 "There should have been 4 documents returned"
|
|
||||||
Expect.hasCountOf after 3u (fun doc -> doc.Id.Length = 32) "Three of the IDs should have been GUIDs"
|
|
||||||
Expect.hasCountOf after 1u (fun doc -> doc.Id = "abc123") "The provided ID should have been used as-is"
|
|
||||||
finally
|
|
||||||
Configuration.useAutoIdStrategy Disabled
|
|
||||||
}
|
|
||||||
testTask "succeeds when adding a RandomString auto ID" {
|
|
||||||
try
|
|
||||||
Configuration.useAutoIdStrategy RandomString
|
|
||||||
Configuration.useIdStringLength 44
|
|
||||||
use! db = SqliteDb.BuildDb()
|
|
||||||
let! before = Count.all SqliteDb.TableName
|
|
||||||
Expect.equal before 0L "There should be no documents in the table"
|
|
||||||
|
|
||||||
do! insert SqliteDb.TableName { emptyDoc with Value = "one" }
|
|
||||||
do! insert SqliteDb.TableName { emptyDoc with Value = "two" }
|
|
||||||
do! insert SqliteDb.TableName { emptyDoc with Id = "abc123"; Value = "three" }
|
|
||||||
do! insert SqliteDb.TableName { emptyDoc with Value = "four" }
|
|
||||||
|
|
||||||
let! after = Find.all<JsonDocument> SqliteDb.TableName
|
|
||||||
Expect.hasLength after 4 "There should have been 4 documents returned"
|
|
||||||
Expect.hasCountOf
|
|
||||||
after 3u (fun doc -> doc.Id.Length = 44)
|
|
||||||
"Three of the IDs should have been 44-character random strings"
|
|
||||||
Expect.hasCountOf after 1u (fun doc -> doc.Id = "abc123") "The provided ID should have been used as-is"
|
|
||||||
finally
|
|
||||||
Configuration.useAutoIdStrategy Disabled
|
|
||||||
Configuration.useIdStringLength 16
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
testList "save" [
|
testList "save" [
|
||||||
testTask "succeeds when a document is inserted" {
|
testTask "succeeds when a document is inserted" {
|
||||||
@@ -392,14 +353,14 @@ let countTests = testList "Count" [
|
|||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! theCount = Count.byFields SqliteDb.TableName Any [ Field.Between "NumValue" 10 20 ]
|
let! theCount = Count.byFields SqliteDb.TableName Any [ Field.BT "NumValue" 10 20 ]
|
||||||
Expect.equal theCount 3L "There should have been 3 matching documents"
|
Expect.equal theCount 3L "There should have been 3 matching documents"
|
||||||
}
|
}
|
||||||
testTask "succeeds for a non-numeric range" {
|
testTask "succeeds for a non-numeric range" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! theCount = Count.byFields SqliteDb.TableName Any [ Field.Between "Value" "aardvark" "apple" ]
|
let! theCount = Count.byFields SqliteDb.TableName Any [ Field.BT "Value" "aardvark" "apple" ]
|
||||||
Expect.equal theCount 1L "There should have been 1 matching document"
|
Expect.equal theCount 1L "There should have been 1 matching document"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -428,14 +389,14 @@ let existsTests = testList "Exists" [
|
|||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! exists = Exists.byFields SqliteDb.TableName Any [ Field.Equal "NumValue" 10 ]
|
let! exists = Exists.byFields SqliteDb.TableName Any [ Field.EQ "NumValue" 10 ]
|
||||||
Expect.isTrue exists "There should have been existing documents"
|
Expect.isTrue exists "There should have been existing documents"
|
||||||
}
|
}
|
||||||
testTask "succeeds when no matching documents exist" {
|
testTask "succeeds when no matching documents exist" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! exists = Exists.byFields SqliteDb.TableName Any [ Field.Less "Nothing" "none" ]
|
let! exists = Exists.byFields SqliteDb.TableName Any [ Field.LT "Nothing" "none" ]
|
||||||
Expect.isFalse exists "There should not have been any existing documents"
|
Expect.isFalse exists "There should not have been any existing documents"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -520,43 +481,16 @@ let findTests = testList "Find" [
|
|||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! docs = Find.byFields<JsonDocument> SqliteDb.TableName Any [ Field.Greater "NumValue" 15 ]
|
let! docs = Find.byFields<JsonDocument> SqliteDb.TableName Any [ Field.GT "NumValue" 15 ]
|
||||||
Expect.hasLength docs 2 "There should have been two documents returned"
|
Expect.equal (List.length docs) 2 "There should have been two documents returned"
|
||||||
}
|
|
||||||
testTask "succeeds when documents are found using IN with numeric field" {
|
|
||||||
use! db = SqliteDb.BuildDb()
|
|
||||||
do! loadDocs ()
|
|
||||||
|
|
||||||
let! docs = Find.byFields<JsonDocument> SqliteDb.TableName All [ Field.In "NumValue" [ 2; 4; 6; 8 ] ]
|
|
||||||
Expect.hasLength docs 1 "There should have been one document returned"
|
|
||||||
}
|
}
|
||||||
testTask "succeeds when documents are not found" {
|
testTask "succeeds when documents are not found" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! docs = Find.byFields<JsonDocument> SqliteDb.TableName Any [ Field.Greater "NumValue" 100 ]
|
let! docs = Find.byFields<JsonDocument> SqliteDb.TableName Any [ Field.GT "NumValue" 100 ]
|
||||||
Expect.isTrue (List.isEmpty docs) "There should have been no documents returned"
|
Expect.isTrue (List.isEmpty docs) "There should have been no documents returned"
|
||||||
}
|
}
|
||||||
testTask "succeeds for InArray when matching documents exist" {
|
|
||||||
use! db = SqliteDb.BuildDb()
|
|
||||||
do! Definition.ensureTable SqliteDb.TableName
|
|
||||||
for doc in ArrayDocument.TestDocuments do do! insert SqliteDb.TableName doc
|
|
||||||
|
|
||||||
let! docs =
|
|
||||||
Find.byFields<ArrayDocument>
|
|
||||||
SqliteDb.TableName All [ Field.InArray "Values" SqliteDb.TableName [ "c" ] ]
|
|
||||||
Expect.hasLength docs 2 "There should have been two documents returned"
|
|
||||||
}
|
|
||||||
testTask "succeeds for InArray when no matching documents exist" {
|
|
||||||
use! db = SqliteDb.BuildDb()
|
|
||||||
do! Definition.ensureTable SqliteDb.TableName
|
|
||||||
for doc in ArrayDocument.TestDocuments do do! insert SqliteDb.TableName doc
|
|
||||||
|
|
||||||
let! docs =
|
|
||||||
Find.byFields<ArrayDocument>
|
|
||||||
SqliteDb.TableName All [ Field.InArray "Values" SqliteDb.TableName [ "j" ] ]
|
|
||||||
Expect.isEmpty docs "There should have been no documents returned"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
testList "byFieldsOrdered" [
|
testList "byFieldsOrdered" [
|
||||||
testTask "succeeds when sorting ascending" {
|
testTask "succeeds when sorting ascending" {
|
||||||
@@ -565,8 +499,7 @@ let findTests = testList "Find" [
|
|||||||
|
|
||||||
let! docs =
|
let! docs =
|
||||||
Find.byFieldsOrdered<JsonDocument>
|
Find.byFieldsOrdered<JsonDocument>
|
||||||
SqliteDb.TableName Any [ Field.Greater "NumValue" 15 ] [ Field.Named "Id" ]
|
SqliteDb.TableName Any [ Field.GT "NumValue" 15 ] [ Field.Named "Id" ]
|
||||||
Expect.hasLength docs 2 "There should have been two documents returned"
|
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(docs |> List.map _.Id |> String.concat "|") "five|four" "The documents were not ordered correctly"
|
(docs |> List.map _.Id |> String.concat "|") "five|four" "The documents were not ordered correctly"
|
||||||
}
|
}
|
||||||
@@ -576,40 +509,17 @@ let findTests = testList "Find" [
|
|||||||
|
|
||||||
let! docs =
|
let! docs =
|
||||||
Find.byFieldsOrdered<JsonDocument>
|
Find.byFieldsOrdered<JsonDocument>
|
||||||
SqliteDb.TableName Any [ Field.Greater "NumValue" 15 ] [ Field.Named "Id DESC" ]
|
SqliteDb.TableName Any [ Field.GT "NumValue" 15 ] [ Field.Named "Id DESC" ]
|
||||||
Expect.hasLength docs 2 "There should have been two documents returned"
|
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(docs |> List.map _.Id |> String.concat "|") "four|five" "The documents were not ordered correctly"
|
(docs |> List.map _.Id |> String.concat "|") "four|five" "The documents were not ordered correctly"
|
||||||
}
|
}
|
||||||
testTask "succeeds when sorting case-sensitively" {
|
|
||||||
use! db = SqliteDb.BuildDb()
|
|
||||||
do! loadDocs ()
|
|
||||||
|
|
||||||
let! docs =
|
|
||||||
Find.byFieldsOrdered<JsonDocument>
|
|
||||||
SqliteDb.TableName All [ Field.LessOrEqual "NumValue" 10 ] [ Field.Named "Value" ]
|
|
||||||
Expect.hasLength docs 3 "There should have been three documents returned"
|
|
||||||
Expect.equal
|
|
||||||
(docs |> List.map _.Id |> String.concat "|") "three|one|two" "Documents not ordered correctly"
|
|
||||||
}
|
|
||||||
testTask "succeeds when sorting case-insensitively" {
|
|
||||||
use! db = SqliteDb.BuildDb()
|
|
||||||
do! loadDocs ()
|
|
||||||
|
|
||||||
let! docs =
|
|
||||||
Find.byFieldsOrdered<JsonDocument>
|
|
||||||
SqliteDb.TableName All [ Field.LessOrEqual "NumValue" 10 ] [ Field.Named "i:Value" ]
|
|
||||||
Expect.hasLength docs 3 "There should have been three documents returned"
|
|
||||||
Expect.equal
|
|
||||||
(docs |> List.map _.Id |> String.concat "|") "three|two|one" "Documents not ordered correctly"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
testList "firstByFields" [
|
testList "firstByFields" [
|
||||||
testTask "succeeds when a document is found" {
|
testTask "succeeds when a document is found" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! doc = Find.firstByFields<JsonDocument> SqliteDb.TableName Any [ Field.Equal "Value" "another" ]
|
let! doc = Find.firstByFields<JsonDocument> SqliteDb.TableName Any [ Field.EQ "Value" "another" ]
|
||||||
Expect.isSome doc "There should have been a document returned"
|
Expect.isSome doc "There should have been a document returned"
|
||||||
Expect.equal doc.Value.Id "two" "The incorrect document was returned"
|
Expect.equal doc.Value.Id "two" "The incorrect document was returned"
|
||||||
}
|
}
|
||||||
@@ -617,7 +527,7 @@ let findTests = testList "Find" [
|
|||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! doc = Find.firstByFields<JsonDocument> SqliteDb.TableName Any [ Field.Equal "Sub.Foo" "green" ]
|
let! doc = Find.firstByFields<JsonDocument> SqliteDb.TableName Any [ Field.EQ "Sub.Foo" "green" ]
|
||||||
Expect.isSome doc "There should have been a document returned"
|
Expect.isSome doc "There should have been a document returned"
|
||||||
Expect.contains [ "two"; "four" ] doc.Value.Id "An incorrect document was returned"
|
Expect.contains [ "two"; "four" ] doc.Value.Id "An incorrect document was returned"
|
||||||
}
|
}
|
||||||
@@ -625,7 +535,7 @@ let findTests = testList "Find" [
|
|||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! doc = Find.firstByFields<JsonDocument> SqliteDb.TableName Any [ Field.Equal "Value" "absent" ]
|
let! doc = Find.firstByFields<JsonDocument> SqliteDb.TableName Any [ Field.EQ "Value" "absent" ]
|
||||||
Expect.isNone doc "There should not have been a document returned"
|
Expect.isNone doc "There should not have been a document returned"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -636,7 +546,7 @@ let findTests = testList "Find" [
|
|||||||
|
|
||||||
let! doc =
|
let! doc =
|
||||||
Find.firstByFieldsOrdered<JsonDocument>
|
Find.firstByFieldsOrdered<JsonDocument>
|
||||||
SqliteDb.TableName Any [ Field.Equal "Sub.Foo" "green" ] [ Field.Named "Sub.Bar" ]
|
SqliteDb.TableName Any [ Field.EQ "Sub.Foo" "green" ] [ Field.Named "Sub.Bar" ]
|
||||||
Expect.isSome doc "There should have been a document returned"
|
Expect.isSome doc "There should have been a document returned"
|
||||||
Expect.equal "two" doc.Value.Id "An incorrect document was returned"
|
Expect.equal "two" doc.Value.Id "An incorrect document was returned"
|
||||||
}
|
}
|
||||||
@@ -646,7 +556,7 @@ let findTests = testList "Find" [
|
|||||||
|
|
||||||
let! doc =
|
let! doc =
|
||||||
Find.firstByFieldsOrdered<JsonDocument>
|
Find.firstByFieldsOrdered<JsonDocument>
|
||||||
SqliteDb.TableName Any [ Field.Equal "Sub.Foo" "green" ] [ Field.Named "Sub.Bar DESC" ]
|
SqliteDb.TableName Any [ Field.EQ "Sub.Foo" "green" ] [ Field.Named "Sub.Bar DESC" ]
|
||||||
Expect.isSome doc "There should have been a document returned"
|
Expect.isSome doc "There should have been a document returned"
|
||||||
Expect.equal "four" doc.Value.Id "An incorrect document was returned"
|
Expect.equal "four" doc.Value.Id "An incorrect document was returned"
|
||||||
}
|
}
|
||||||
@@ -729,8 +639,8 @@ let patchTests = testList "Patch" [
|
|||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
do! Patch.byFields SqliteDb.TableName Any [ Field.Equal "Value" "purple" ] {| NumValue = 77 |}
|
do! Patch.byFields SqliteDb.TableName Any [ Field.EQ "Value" "purple" ] {| NumValue = 77 |}
|
||||||
let! after = Count.byFields SqliteDb.TableName Any [ Field.Equal "NumValue" 77 ]
|
let! after = Count.byFields SqliteDb.TableName Any [ Field.EQ "NumValue" 77 ]
|
||||||
Expect.equal after 2L "There should have been 2 documents returned"
|
Expect.equal after 2L "There should have been 2 documents returned"
|
||||||
}
|
}
|
||||||
testTask "succeeds when no document is updated" {
|
testTask "succeeds when no document is updated" {
|
||||||
@@ -740,7 +650,7 @@ let patchTests = testList "Patch" [
|
|||||||
Expect.isEmpty before "There should have been no documents returned"
|
Expect.isEmpty before "There should have been no documents returned"
|
||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
do! Patch.byFields SqliteDb.TableName Any [ Field.Equal "Value" "burgundy" ] {| Foo = "green" |}
|
do! Patch.byFields SqliteDb.TableName Any [ Field.EQ "Value" "burgundy" ] {| Foo = "green" |}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
@@ -779,7 +689,7 @@ let removeFieldsTests = testList "RemoveFields" [
|
|||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
do! RemoveFields.byFields SqliteDb.TableName Any [ Field.Equal "NumValue" 17 ] [ "Sub" ]
|
do! RemoveFields.byFields SqliteDb.TableName Any [ Field.EQ "NumValue" 17 ] [ "Sub" ]
|
||||||
try
|
try
|
||||||
let! _ = Find.byId<string, JsonDocument> SqliteDb.TableName "four"
|
let! _ = Find.byId<string, JsonDocument> SqliteDb.TableName "four"
|
||||||
Expect.isTrue false "The updated document should have failed to parse"
|
Expect.isTrue false "The updated document should have failed to parse"
|
||||||
@@ -792,13 +702,13 @@ let removeFieldsTests = testList "RemoveFields" [
|
|||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
do! RemoveFields.byFields SqliteDb.TableName Any [ Field.Equal "NumValue" 17 ] [ "Nothing" ]
|
do! RemoveFields.byFields SqliteDb.TableName Any [ Field.EQ "NumValue" 17 ] [ "Nothing" ]
|
||||||
}
|
}
|
||||||
testTask "succeeds when no document is matched" {
|
testTask "succeeds when no document is matched" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
do! RemoveFields.byFields SqliteDb.TableName Any [ Field.NotEqual "Abracadabra" "apple" ] [ "Value" ]
|
do! RemoveFields.byFields SqliteDb.TableName Any [ Field.NE "Abracadabra" "apple" ] [ "Value" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
@@ -828,7 +738,7 @@ let deleteTests = testList "Delete" [
|
|||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
do! Delete.byFields SqliteDb.TableName Any [ Field.NotEqual "Value" "purple" ]
|
do! Delete.byFields SqliteDb.TableName Any [ Field.NE "Value" "purple" ]
|
||||||
let! remaining = Count.all SqliteDb.TableName
|
let! remaining = Count.all SqliteDb.TableName
|
||||||
Expect.equal remaining 2L "There should have been 2 documents remaining"
|
Expect.equal remaining 2L "There should have been 2 documents remaining"
|
||||||
}
|
}
|
||||||
@@ -836,7 +746,7 @@ let deleteTests = testList "Delete" [
|
|||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
do! Delete.byFields SqliteDb.TableName Any [ Field.Equal "Value" "crimson" ]
|
do! Delete.byFields SqliteDb.TableName Any [ Field.EQ "Value" "crimson" ]
|
||||||
let! remaining = Count.all SqliteDb.TableName
|
let! remaining = 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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +1,23 @@
|
|||||||
module Types
|
module Types
|
||||||
|
|
||||||
type NumIdDocument =
|
|
||||||
{ Key: int
|
|
||||||
Text: string }
|
|
||||||
|
|
||||||
type SubDocument =
|
type SubDocument =
|
||||||
{ Foo: string
|
{ Foo: string
|
||||||
Bar: string }
|
Bar: string }
|
||||||
|
|
||||||
type ArrayDocument =
|
|
||||||
{ Id: string
|
|
||||||
Values: string list }
|
|
||||||
with
|
|
||||||
/// <summary>
|
|
||||||
/// A set of documents used for integration tests
|
|
||||||
/// </summary>
|
|
||||||
static member TestDocuments =
|
|
||||||
[ { Id = "first"; Values = [ "a"; "b"; "c" ] }
|
|
||||||
{ Id = "second"; Values = [ "c"; "d"; "e" ] }
|
|
||||||
{ Id = "third"; Values = [ "x"; "y"; "z" ] } ]
|
|
||||||
|
|
||||||
type JsonDocument =
|
type JsonDocument =
|
||||||
{ Id: string
|
{ Id: string
|
||||||
Value: string
|
Value: string
|
||||||
NumValue: int
|
NumValue: int
|
||||||
Sub: SubDocument option }
|
Sub: SubDocument option }
|
||||||
|
|
||||||
|
|
||||||
/// An empty JsonDocument
|
/// An empty JsonDocument
|
||||||
let emptyDoc = { Id = ""; Value = ""; NumValue = 0; Sub = None }
|
let emptyDoc = { Id = ""; Value = ""; NumValue = 0; Sub = None }
|
||||||
|
|
||||||
/// Documents to use for testing
|
/// Documents to use for testing
|
||||||
let testDocuments =
|
let testDocuments = [
|
||||||
[ { Id = "one"; Value = "FIRST!"; NumValue = 0; Sub = None }
|
{ Id = "one"; Value = "FIRST!"; NumValue = 0; Sub = None }
|
||||||
{ Id = "two"; Value = "another"; NumValue = 10; Sub = Some { Foo = "green"; Bar = "blue" } }
|
{ Id = "two"; Value = "another"; NumValue = 10; Sub = Some { Foo = "green"; Bar = "blue" } }
|
||||||
{ Id = "three"; Value = ""; NumValue = 4; Sub = None }
|
{ Id = "three"; Value = ""; NumValue = 4; Sub = None }
|
||||||
{ Id = "four"; Value = "purple"; NumValue = 17; Sub = Some { Foo = "green"; Bar = "red" } }
|
{ Id = "four"; Value = "purple"; NumValue = 17; Sub = Some { Foo = "green"; Bar = "red" } }
|
||||||
{ Id = "five"; Value = "purple"; NumValue = 18; Sub = None } ]
|
{ Id = "five"; Value = "purple"; NumValue = 18; Sub = None }
|
||||||
|
]
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
echo --- Package Common library
|
echo --- Package Common library
|
||||||
rm Common/bin/Release/BitBadger.Documents.Common.*.nupkg || true
|
|
||||||
dotnet pack Common/BitBadger.Documents.Common.fsproj -c Release
|
dotnet pack Common/BitBadger.Documents.Common.fsproj -c Release
|
||||||
cp Common/bin/Release/BitBadger.Documents.Common.*.nupkg .
|
cp Common/bin/Release/BitBadger.Documents.Common.*.nupkg .
|
||||||
|
|
||||||
echo --- Package PostgreSQL library
|
echo --- Package PostgreSQL library
|
||||||
rm Postgres/bin/Release/BitBadger.Documents.Postgres*.nupkg || true
|
|
||||||
dotnet pack Postgres/BitBadger.Documents.Postgres.fsproj -c Release
|
dotnet pack Postgres/BitBadger.Documents.Postgres.fsproj -c Release
|
||||||
cp Postgres/bin/Release/BitBadger.Documents.Postgres.*.nupkg .
|
cp Postgres/bin/Release/BitBadger.Documents.Postgres.*.nupkg .
|
||||||
|
|
||||||
echo --- Package SQLite library
|
echo --- Package SQLite library
|
||||||
rm Sqlite/bin/Release/BitBadger.Documents.Sqlite*.nupkg || true
|
|
||||||
dotnet pack Sqlite/BitBadger.Documents.Sqlite.fsproj -c Release
|
dotnet pack Sqlite/BitBadger.Documents.Sqlite.fsproj -c Release
|
||||||
cp Sqlite/bin/Release/BitBadger.Documents.Sqlite.*.nupkg .
|
cp Sqlite/bin/Release/BitBadger.Documents.Sqlite.*.nupkg .
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ dotnet build BitBadger.Documents.sln --no-restore
|
|||||||
cd ./Tests || exit
|
cd ./Tests || exit
|
||||||
|
|
||||||
export BBDOX_PG_PORT=8301
|
export BBDOX_PG_PORT=8301
|
||||||
PG_VERSIONS=('13' '14' '15' '16' 'latest')
|
PG_VERSIONS=('12' '13' '14' '15' 'latest')
|
||||||
NET_VERSIONS=('8.0' '9.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