Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5580284910 | |||
| 147a72b476 | |||
| 740767661c | |||
| 168bf0cd14 | |||
| 3bc662c984 | |||
| b019548a4e | |||
| 27b8a83a7a | |||
| 2c24e2e912 | |||
| 039761fcca | |||
| a0da5f83b1 | |||
| d7c8cae0c7 | |||
| 1707d3ce63 | |||
| 7d7214e9f2 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -396,3 +396,7 @@ FodyWeavers.xsd
|
|||||||
|
|
||||||
# JetBrains Rider
|
# JetBrains Rider
|
||||||
*.sln.iml
|
*.sln.iml
|
||||||
|
**/.idea
|
||||||
|
|
||||||
|
# Test run files
|
||||||
|
src/*-tests.txt
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Description>Common files for PostgreSQL and SQLite document database libraries</Description>
|
<Description>Common files for PostgreSQL and SQLite document database libraries</Description>
|
||||||
<PackageReleaseNotes>v3 release</PackageReleaseNotes>
|
|
||||||
<PackageTags>JSON Document SQL</PackageTags>
|
<PackageTags>JSON Document SQL</PackageTags>
|
||||||
|
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -13,7 +13,10 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="FSharp.SystemTextJson" Version="1.2.42" />
|
<PackageReference Include="FSharp.SystemTextJson" Version="1.3.13" />
|
||||||
|
<PackageReference Update="FSharp.Core" Version="9.0.100" />
|
||||||
|
<PackageReference Include="System.Text.Encodings.Web" Version="9.0.0" />
|
||||||
|
<PackageReference Include="System.Text.Json" Version="9.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,93 +1,401 @@
|
|||||||
namespace BitBadger.Documents
|
namespace BitBadger.Documents
|
||||||
|
|
||||||
/// The types of logical operations available for JSON fields
|
open System.Security.Cryptography
|
||||||
[<Struct>]
|
|
||||||
type Op =
|
|
||||||
/// Equals (=)
|
|
||||||
| EQ
|
|
||||||
/// Greater Than (>)
|
|
||||||
| GT
|
|
||||||
/// Greater Than or Equal To (>=)
|
|
||||||
| GE
|
|
||||||
/// Less Than (<)
|
|
||||||
| LT
|
|
||||||
/// Less Than or Equal To (<=)
|
|
||||||
| LE
|
|
||||||
/// Not Equal to (<>)
|
|
||||||
| NE
|
|
||||||
/// Exists (IS NOT NULL)
|
|
||||||
| EX
|
|
||||||
/// Does Not Exist (IS NULL)
|
|
||||||
| NEX
|
|
||||||
|
|
||||||
override this.ToString() =
|
/// <summary>The types of comparisons available for JSON fields</summary>
|
||||||
|
/// <exclude />
|
||||||
|
type Comparison =
|
||||||
|
|
||||||
|
/// <summary>Equals (<tt>=</tt>)</summary>
|
||||||
|
| Equal of Value: obj
|
||||||
|
|
||||||
|
/// <summary>Greater Than (<tt>></tt>)</summary>
|
||||||
|
| Greater of Value: obj
|
||||||
|
|
||||||
|
/// <summary>Greater Than or Equal To (<tt>>=</tt>)</summary>
|
||||||
|
| GreaterOrEqual of Value: obj
|
||||||
|
|
||||||
|
/// <summary>Less Than (<tt><</tt>)</summary>
|
||||||
|
| Less of Value: obj
|
||||||
|
|
||||||
|
/// <summary>Less Than or Equal To (<tt><=</tt>)</summary>
|
||||||
|
| LessOrEqual of Value: obj
|
||||||
|
|
||||||
|
/// <summary>Not Equal to (<tt><></tt>)</summary>
|
||||||
|
| NotEqual of Value: obj
|
||||||
|
|
||||||
|
/// <summary>Between (<tt>BETWEEN</tt>)</summary>
|
||||||
|
| Between of Min: obj * Max: obj
|
||||||
|
|
||||||
|
/// <summary>In (<tt>IN</tt>)</summary>
|
||||||
|
| In of Values: obj seq
|
||||||
|
|
||||||
|
/// <summary>In Array (PostgreSQL: <tt>|?</tt>, SQLite: <tt>EXISTS / json_each / IN</tt>)</summary>
|
||||||
|
| InArray of Table: string * Values: obj seq
|
||||||
|
|
||||||
|
/// <summary>Exists (<tt>IS NOT NULL</tt>)</summary>
|
||||||
|
| Exists
|
||||||
|
|
||||||
|
/// <summary>Does Not Exist (<tt>IS NULL</tt>)</summary>
|
||||||
|
| NotExists
|
||||||
|
|
||||||
|
/// <summary>The operator SQL for this comparison</summary>
|
||||||
|
member this.OpSql =
|
||||||
match this with
|
match this with
|
||||||
| EQ -> "="
|
| Equal _ -> "="
|
||||||
| GT -> ">"
|
| Greater _ -> ">"
|
||||||
| GE -> ">="
|
| GreaterOrEqual _ -> ">="
|
||||||
| LT -> "<"
|
| Less _ -> "<"
|
||||||
| LE -> "<="
|
| LessOrEqual _ -> "<="
|
||||||
| NE -> "<>"
|
| NotEqual _ -> "<>"
|
||||||
| EX -> "IS NOT NULL"
|
| Between _ -> "BETWEEN"
|
||||||
| NEX -> "IS NULL"
|
| In _ -> "IN"
|
||||||
|
| InArray _ -> "?|" // PostgreSQL only; SQL needs a subquery for this
|
||||||
|
| Exists -> "IS NOT NULL"
|
||||||
|
| NotExists -> "IS NULL"
|
||||||
|
|
||||||
|
|
||||||
/// Criteria for a field WHERE clause
|
/// <summary>The dialect in which a command should be rendered</summary>
|
||||||
|
[<Struct>]
|
||||||
|
type Dialect =
|
||||||
|
| PostgreSQL
|
||||||
|
| SQLite
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>The format in which an element of a JSON field should be extracted</summary>
|
||||||
|
[<Struct>]
|
||||||
|
type FieldFormat =
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Use <tt>->></tt> or <tt>#>></tt>; extracts a text (PostgreSQL) or SQL (SQLite) value
|
||||||
|
/// </summary>
|
||||||
|
| AsSql
|
||||||
|
|
||||||
|
/// <summary>Use <tt>-></tt> or <tt>#></tt>; extracts a JSONB (PostgreSQL) or JSON (SQLite) value</summary>
|
||||||
|
| AsJson
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>Criteria for a field <tt>WHERE</tt> clause</summary>
|
||||||
type Field = {
|
type Field = {
|
||||||
/// The name of the field
|
|
||||||
|
/// <summary>The name of the field</summary>
|
||||||
Name: string
|
Name: string
|
||||||
|
|
||||||
/// The operation by which the field will be compared
|
/// <summary>The comparison for the field</summary>
|
||||||
Op: Op
|
Comparison: Comparison
|
||||||
|
|
||||||
/// The value of the field
|
/// <summary>The name of the parameter for this field</summary>
|
||||||
Value: obj
|
ParameterName: string option
|
||||||
|
|
||||||
|
/// <summary>The table qualifier for this field</summary>
|
||||||
|
Qualifier: string option
|
||||||
} with
|
} with
|
||||||
|
|
||||||
/// Create an equals (=) field criterion
|
/// <summary>Create a comparison against a field</summary>
|
||||||
static member EQ name (value: obj) =
|
/// <param name="name">The name of the field against which the comparison should be applied</param>
|
||||||
{ Name = name; Op = EQ; Value = value }
|
/// <param name="comparison">The comparison for the given field</param>
|
||||||
|
/// <returns>A new <tt>Field</tt> instance implementing the given comparison</returns>
|
||||||
|
static member Where name (comparison: Comparison) =
|
||||||
|
{ Name = name; Comparison = comparison; ParameterName = None; Qualifier = None }
|
||||||
|
|
||||||
/// Create a greater than (>) field criterion
|
/// <summary>Create an equals (<tt>=</tt>) field criterion</summary>
|
||||||
static member GT name (value: obj) =
|
/// <param name="name">The name of the field to be compared</param>
|
||||||
{ Name = name; Op = GT; Value = value }
|
/// <param name="value">The value for the comparison</param>
|
||||||
|
/// <returns>A field with the given comparison</returns>
|
||||||
|
static member Equal<'T> name (value: 'T) =
|
||||||
|
Field.Where name (Equal value)
|
||||||
|
|
||||||
/// Create a greater than or equal to (>=) field criterion
|
/// <summary>Create an equals (<tt>=</tt>) field criterion (alias)</summary>
|
||||||
static member GE name (value: obj) =
|
/// <param name="name">The name of the field to be compared</param>
|
||||||
{ Name = name; Op = GE; Value = value }
|
/// <param name="value">The value for the comparison</param>
|
||||||
|
/// <returns>A field with the given comparison</returns>
|
||||||
|
static member EQ<'T> name (value: 'T) = Field.Equal name value
|
||||||
|
|
||||||
/// Create a less than (<) field criterion
|
/// <summary>Create a greater than (<tt>></tt>) field criterion</summary>
|
||||||
static member LT name (value: obj) =
|
/// <param name="name">The name of the field to be compared</param>
|
||||||
{ Name = name; Op = LT; Value = value }
|
/// <param name="value">The value for the comparison</param>
|
||||||
|
/// <returns>A field with the given comparison</returns>
|
||||||
|
static member Greater<'T> name (value: 'T) =
|
||||||
|
Field.Where name (Greater value)
|
||||||
|
|
||||||
/// Create a less than or equal to (<=) field criterion
|
/// <summary>Create a greater than (<tt>></tt>) field criterion (alias)</summary>
|
||||||
static member LE name (value: obj) =
|
/// <param name="name">The name of the field to be compared</param>
|
||||||
{ Name = name; Op = LE; Value = value }
|
/// <param name="value">The value for the comparison</param>
|
||||||
|
/// <returns>A field with the given comparison</returns>
|
||||||
|
static member GT<'T> name (value: 'T) = Field.Greater name value
|
||||||
|
|
||||||
/// Create a not equals (<>) field criterion
|
/// <summary>Create a greater than or equal to (<tt>>=</tt>) field criterion</summary>
|
||||||
static member NE name (value: obj) =
|
/// <param name="name">The name of the field to be compared</param>
|
||||||
{ Name = name; Op = NE; Value = value }
|
/// <param name="value">The value for the comparison</param>
|
||||||
|
/// <returns>A field with the given comparison</returns>
|
||||||
|
static member GreaterOrEqual<'T> name (value: 'T) =
|
||||||
|
Field.Where name (GreaterOrEqual value)
|
||||||
|
|
||||||
/// Create an exists (IS NOT NULL) field criterion
|
/// <summary>Create a greater than or equal to (<tt>>=</tt>) field criterion (alias)</summary>
|
||||||
static member EX name =
|
/// <param name="name">The name of the field to be compared</param>
|
||||||
{ Name = name; Op = EX; Value = obj () }
|
/// <param name="value">The value for the comparison</param>
|
||||||
|
/// <returns>A field with the given comparison</returns>
|
||||||
|
static member GE<'T> name (value: 'T) = Field.GreaterOrEqual name value
|
||||||
|
|
||||||
/// Create an not exists (IS NULL) field criterion
|
/// <summary>Create a less than (<tt><</tt>) field criterion</summary>
|
||||||
static member NEX name =
|
/// <param name="name">The name of the field to be compared</param>
|
||||||
{ Name = name; Op = NEX; Value = obj () }
|
/// <param name="value">The value for the comparison</param>
|
||||||
|
/// <returns>A field with the given comparison</returns>
|
||||||
|
static member Less<'T> name (value: 'T) =
|
||||||
|
Field.Where name (Less value)
|
||||||
|
|
||||||
|
/// <summary>Create a less than (<tt><</tt>) field criterion (alias)</summary>
|
||||||
|
/// <param name="name">The name of the field to be compared</param>
|
||||||
|
/// <param name="value">The value for the comparison</param>
|
||||||
|
/// <returns>A field with the given comparison</returns>
|
||||||
|
static member LT<'T> name (value: 'T) = Field.Less name value
|
||||||
|
|
||||||
|
/// <summary>Create a less than or equal to (<tt><=</tt>) field criterion</summary>
|
||||||
|
/// <param name="name">The name of the field to be compared</param>
|
||||||
|
/// <param name="value">The value for the comparison</param>
|
||||||
|
/// <returns>A field with the given comparison</returns>
|
||||||
|
static member LessOrEqual<'T> name (value: 'T) =
|
||||||
|
Field.Where name (LessOrEqual value)
|
||||||
|
|
||||||
|
/// <summary>Create a less than or equal to (<tt><=</tt>) field criterion (alias)</summary>
|
||||||
|
/// <param name="name">The name of the field to be compared</param>
|
||||||
|
/// <param name="value">The value for the comparison</param>
|
||||||
|
/// <returns>A field with the given comparison</returns>
|
||||||
|
static member LE<'T> name (value: 'T) = Field.LessOrEqual name value
|
||||||
|
|
||||||
|
/// <summary>Create a not equals (<tt><></tt>) field criterion</summary>
|
||||||
|
/// <param name="name">The name of the field to be compared</param>
|
||||||
|
/// <param name="value">The value for the comparison</param>
|
||||||
|
/// <returns>A field with the given comparison</returns>
|
||||||
|
static member NotEqual<'T> name (value: 'T) =
|
||||||
|
Field.Where name (NotEqual value)
|
||||||
|
|
||||||
|
/// <summary>Create a not equals (<tt><></tt>) field criterion (alias)</summary>
|
||||||
|
/// <param name="name">The name of the field to be compared</param>
|
||||||
|
/// <param name="value">The value for the comparison</param>
|
||||||
|
/// <returns>A field with the given comparison</returns>
|
||||||
|
static member NE<'T> name (value: 'T) = Field.NotEqual name value
|
||||||
|
|
||||||
|
/// <summary>Create a Between field criterion</summary>
|
||||||
|
/// <param name="name">The name of the field to be compared</param>
|
||||||
|
/// <param name="min">The minimum value for the comparison range</param>
|
||||||
|
/// <param name="max">The maximum value for the comparison range</param>
|
||||||
|
/// <returns>A field with the given comparison</returns>
|
||||||
|
static member Between<'T> name (min: 'T) (max: 'T) =
|
||||||
|
Field.Where name (Between(min, max))
|
||||||
|
|
||||||
|
/// <summary>Create a Between field criterion (alias)</summary>
|
||||||
|
/// <param name="name">The name of the field to be compared</param>
|
||||||
|
/// <param name="min">The minimum value for the comparison range</param>
|
||||||
|
/// <param name="max">The maximum value for the comparison range</param>
|
||||||
|
/// <returns>A field with the given comparison</returns>
|
||||||
|
static member BT<'T> name (min: 'T) (max: 'T) = Field.Between name min max
|
||||||
|
|
||||||
|
/// <summary>Create an In field criterion</summary>
|
||||||
|
/// <param name="name">The name of the field to be compared</param>
|
||||||
|
/// <param name="values">The values for the comparison</param>
|
||||||
|
/// <returns>A field with the given comparison</returns>
|
||||||
|
static member In<'T> name (values: 'T seq) =
|
||||||
|
Field.Where name (In (Seq.map box values))
|
||||||
|
|
||||||
|
/// <summary>Create an In field criterion (alias)</summary>
|
||||||
|
/// <param name="name">The name of the field to be compared</param>
|
||||||
|
/// <param name="values">The values for the comparison</param>
|
||||||
|
/// <returns>A field with the given comparison</returns>
|
||||||
|
static member IN<'T> name (values: 'T seq) = Field.In name values
|
||||||
|
|
||||||
|
/// <summary>Create an InArray field criterion</summary>
|
||||||
|
/// <param name="name">The name of the field to be compared</param>
|
||||||
|
/// <param name="tableName">The name of the table in which the field's documents are stored</param>
|
||||||
|
/// <param name="values">The values for the comparison</param>
|
||||||
|
/// <returns>A field with the given comparison</returns>
|
||||||
|
static member InArray<'T> name tableName (values: 'T seq) =
|
||||||
|
Field.Where name (InArray(tableName, Seq.map box values))
|
||||||
|
|
||||||
|
/// <summary>Create an exists (<tt>IS NOT NULL</tt>) field criterion</summary>
|
||||||
|
/// <param name="name">The name of the field to be compared</param>
|
||||||
|
/// <returns>A field with the given comparison</returns>
|
||||||
|
static member Exists name =
|
||||||
|
Field.Where name Exists
|
||||||
|
|
||||||
|
/// <summary>Create an exists (<tt>IS NOT NULL</tt>) field criterion (alias)</summary>
|
||||||
|
/// <param name="name">The name of the field to be compared</param>
|
||||||
|
/// <returns>A field with the given comparison</returns>
|
||||||
|
static member EX name = Field.Exists name
|
||||||
|
|
||||||
|
/// <summary>Create a not exists (<tt>IS NULL</tt>) field criterion</summary>
|
||||||
|
/// <param name="name">The name of the field to be compared</param>
|
||||||
|
/// <returns>A field with the given comparison</returns>
|
||||||
|
static member NotExists name =
|
||||||
|
Field.Where name NotExists
|
||||||
|
|
||||||
|
/// <summary>Create a not exists (<tt>IS NULL</tt>) field criterion (alias)</summary>
|
||||||
|
/// <param name="name">The name of the field to be compared</param>
|
||||||
|
/// <returns>A field with the given comparison</returns>
|
||||||
|
static member NEX name = Field.NotExists name
|
||||||
|
|
||||||
|
/// <summary>Transform a field name (<tt>a.b.c</tt>) to a path for the given SQL dialect</summary>
|
||||||
|
/// <param name="name">The name of the field in dotted format</param>
|
||||||
|
/// <param name="dialect">The SQL dialect to use when converting the name to nested path format</param>
|
||||||
|
/// <param name="format">Whether to reference this path as a JSON value or a SQL value</param>
|
||||||
|
/// <returns>A <tt>string</tt> with the path required to address the nested document value</returns>
|
||||||
|
static member NameToPath (name: string) dialect format =
|
||||||
|
let path =
|
||||||
|
if name.Contains '.' then
|
||||||
|
match dialect with
|
||||||
|
| PostgreSQL ->
|
||||||
|
(match format with AsJson -> "#>" | AsSql -> "#>>")
|
||||||
|
+ "'{" + String.concat "," (name.Split '.') + "}'"
|
||||||
|
| 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}"
|
||||||
|
|
||||||
|
/// <summary>Create a field with a given name, but no other properties filled</summary>
|
||||||
|
/// <param name="name">The field name, along with any other qualifications if used in a sorting context</param>
|
||||||
|
/// <remarks><tt>Comparison</tt> will be <tt>Equal</tt>, value will be an empty string</remarks>
|
||||||
|
static member Named name =
|
||||||
|
Field.Where name (Equal "")
|
||||||
|
|
||||||
|
/// <summary>Specify the name of the parameter for this field</summary>
|
||||||
|
/// <param name="name">The parameter name (including <tt>:</tt> or <tt>@</tt>)</param>
|
||||||
|
/// <returns>A field with the given parameter name specified</returns>
|
||||||
|
member this.WithParameterName name =
|
||||||
|
{ this with ParameterName = Some name }
|
||||||
|
|
||||||
|
/// <summary>Specify a qualifier (alias) for the table from which this field will be referenced</summary>
|
||||||
|
/// <param name="alias">The table alias for this field comparison</param>
|
||||||
|
/// <returns>A field with the given qualifier specified</returns>
|
||||||
|
member this.WithQualifier alias =
|
||||||
|
{ this with Qualifier = Some alias }
|
||||||
|
|
||||||
|
/// <summary>Get the qualified path to the field</summary>
|
||||||
|
/// <param name="dialect">The SQL dialect to use when converting the name to nested path format</param>
|
||||||
|
/// <param name="format">Whether to reference this path as a JSON value or a SQL value</param>
|
||||||
|
/// <returns>A <tt>string</tt> with the qualified path required to address the nested document value</returns>
|
||||||
|
member this.Path dialect format =
|
||||||
|
(this.Qualifier |> Option.map (fun q -> $"{q}.") |> Option.defaultValue "")
|
||||||
|
+ Field.NameToPath this.Name dialect format
|
||||||
|
|
||||||
|
|
||||||
/// The required document serialization implementation
|
/// <summary>How fields should be matched</summary>
|
||||||
|
[<Struct>]
|
||||||
|
type FieldMatch =
|
||||||
|
|
||||||
|
/// <summary>Any field matches (<tt>OR</tt>)</summary>
|
||||||
|
| Any
|
||||||
|
|
||||||
|
/// <summary>All fields match (<tt>AND</tt>)</summary>
|
||||||
|
| All
|
||||||
|
|
||||||
|
/// <summary>The SQL value implementing each matching strategy</summary>
|
||||||
|
override this.ToString() =
|
||||||
|
match this with Any -> "OR" | All -> "AND"
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>Derive parameter names (each instance wraps a counter to uniquely name anonymous fields)</summary>
|
||||||
|
type ParameterName() =
|
||||||
|
|
||||||
|
/// The counter for the next field value
|
||||||
|
let mutable currentIdx = -1
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return the specified name for the parameter, or an anonymous parameter name if none is specified
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="paramName">The optional name of the parameter</param>
|
||||||
|
/// <returns>The name of the parameter, derived if no name was provided</returns>
|
||||||
|
member this.Derive paramName =
|
||||||
|
match paramName with
|
||||||
|
| Some it -> it
|
||||||
|
| None ->
|
||||||
|
currentIdx <- currentIdx + 1
|
||||||
|
$"@field{currentIdx}"
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>Automatically-generated document ID strategies</summary>
|
||||||
|
[<Struct>]
|
||||||
|
type AutoId =
|
||||||
|
|
||||||
|
/// <summary>No automatic IDs will be generated</summary>
|
||||||
|
| Disabled
|
||||||
|
|
||||||
|
/// <summary>Generate a MAX-plus-1 numeric value for documents</summary>
|
||||||
|
| Number
|
||||||
|
|
||||||
|
/// <summary>Generate a <tt>GUID</tt> for each document (as a lowercase, no-dashes, 32-character string)</summary>
|
||||||
|
| Guid
|
||||||
|
|
||||||
|
/// <summary>Generate a random string of hexadecimal characters for each document</summary>
|
||||||
|
| RandomString
|
||||||
|
with
|
||||||
|
/// <summary>Generate a <tt>GUID</tt> string</summary>
|
||||||
|
/// <returns>A <tt>GUID</tt> string</returns>
|
||||||
|
static member GenerateGuid() =
|
||||||
|
System.Guid.NewGuid().ToString "N"
|
||||||
|
|
||||||
|
/// <summary>Generate a string of random hexadecimal characters</summary>
|
||||||
|
/// <param name="length">The number of characters to generate</param>
|
||||||
|
/// <returns>A string of the given length with random hexadecimal characters</returns>
|
||||||
|
static member GenerateRandomString(length: int) =
|
||||||
|
RandomNumberGenerator.GetHexString(length, lowercase = true)
|
||||||
|
|
||||||
|
/// <summary>Does the given document need an automatic ID generated?</summary>
|
||||||
|
/// <param name="strategy">The auto-ID strategy currently in use</param>
|
||||||
|
/// <param name="document">The document being inserted</param>
|
||||||
|
/// <param name="idProp">The name of the ID property in the given document</param>
|
||||||
|
/// <returns>True if an auto-ID strategy is implemented and the ID has no value, false otherwise</returns>
|
||||||
|
/// <exception cref="T:System.InvalidOperationException">
|
||||||
|
/// If the ID field type and requested ID value are not compatible
|
||||||
|
/// </exception>
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>The required document serialization implementation</summary>
|
||||||
type IDocumentSerializer =
|
type IDocumentSerializer =
|
||||||
|
|
||||||
/// Serialize an object to a JSON string
|
/// <summary>Serialize an object to a JSON string</summary>
|
||||||
abstract Serialize<'T> : 'T -> string
|
abstract Serialize<'T> : 'T -> string
|
||||||
|
|
||||||
/// Deserialize a JSON string into an object
|
/// <summary>Deserialize a JSON string into an object</summary>
|
||||||
abstract Deserialize<'T> : string -> 'T
|
abstract Deserialize<'T> : string -> 'T
|
||||||
|
|
||||||
|
|
||||||
/// Document serializer defaults
|
/// <summary>Document serializer defaults</summary>
|
||||||
module DocumentSerializer =
|
module DocumentSerializer =
|
||||||
|
|
||||||
open System.Text.Json
|
open System.Text.Json
|
||||||
@@ -99,7 +407,7 @@ module DocumentSerializer =
|
|||||||
o.Converters.Add(JsonFSharpConverter())
|
o.Converters.Add(JsonFSharpConverter())
|
||||||
o
|
o
|
||||||
|
|
||||||
/// The default JSON serializer
|
/// <summary>The default JSON serializer</summary>
|
||||||
[<CompiledName "Default">]
|
[<CompiledName "Default">]
|
||||||
let ``default`` =
|
let ``default`` =
|
||||||
{ new IDocumentSerializer with
|
{ new IDocumentSerializer with
|
||||||
@@ -110,61 +418,90 @@ module DocumentSerializer =
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Configuration for document handling
|
/// <summary>Configuration for document handling</summary>
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module Configuration =
|
module Configuration =
|
||||||
|
|
||||||
/// The serializer to use for document manipulation
|
/// The serializer to use for document manipulation
|
||||||
let mutable private serializerValue = DocumentSerializer.``default``
|
let mutable private serializerValue = DocumentSerializer.``default``
|
||||||
|
|
||||||
/// Register a serializer to use for translating documents to domain types
|
/// <summary>Register a serializer to use for translating documents to domain types</summary>
|
||||||
|
/// <param name="ser">The serializer to use when manipulating documents</param>
|
||||||
[<CompiledName "UseSerializer">]
|
[<CompiledName "UseSerializer">]
|
||||||
let useSerializer ser =
|
let useSerializer ser =
|
||||||
serializerValue <- ser
|
serializerValue <- ser
|
||||||
|
|
||||||
/// Retrieve the currently configured serializer
|
/// <summary>Retrieve the currently configured serializer</summary>
|
||||||
|
/// <returns>The currently configured serializer</returns>
|
||||||
[<CompiledName "Serializer">]
|
[<CompiledName "Serializer">]
|
||||||
let serializer () =
|
let serializer () =
|
||||||
serializerValue
|
serializerValue
|
||||||
|
|
||||||
/// The serialized name of the ID field for documents
|
/// The serialized name of the ID field for documents
|
||||||
let mutable idFieldValue = "Id"
|
let mutable private idFieldValue = "Id"
|
||||||
|
|
||||||
/// Specify the name of the ID field for documents
|
/// <summary>Specify the name of the ID field for documents</summary>
|
||||||
|
/// <param name="it">The name of the ID field for documents</param>
|
||||||
[<CompiledName "UseIdField">]
|
[<CompiledName "UseIdField">]
|
||||||
let useIdField it =
|
let useIdField it =
|
||||||
idFieldValue <- it
|
idFieldValue <- it
|
||||||
|
|
||||||
/// Retrieve the currently configured ID field for documents
|
/// <summary>Retrieve the currently configured ID field for documents</summary>
|
||||||
|
/// <returns>The currently configured ID field</returns>
|
||||||
[<CompiledName "IdField">]
|
[<CompiledName "IdField">]
|
||||||
let idField () =
|
let idField () =
|
||||||
idFieldValue
|
idFieldValue
|
||||||
|
|
||||||
|
/// The automatic ID strategy used by the library
|
||||||
|
let mutable private autoIdValue = Disabled
|
||||||
|
|
||||||
/// Query construction functions
|
/// <summary>Specify the automatic ID generation strategy used by the library</summary>
|
||||||
|
/// <param name="it">The automatic ID generation strategy to use</param>
|
||||||
|
[<CompiledName "UseAutoIdStrategy">]
|
||||||
|
let useAutoIdStrategy it =
|
||||||
|
autoIdValue <- it
|
||||||
|
|
||||||
|
/// <summary>Retrieve the currently configured automatic ID generation strategy</summary>
|
||||||
|
/// <returns>The current automatic ID generation strategy</returns>
|
||||||
|
[<CompiledName "AutoIdStrategy">]
|
||||||
|
let autoIdStrategy () =
|
||||||
|
autoIdValue
|
||||||
|
|
||||||
|
/// The length of automatically generated random strings
|
||||||
|
let mutable private idStringLengthValue = 16
|
||||||
|
|
||||||
|
/// <summary>Specify the length of automatically generated random strings</summary>
|
||||||
|
/// <param name="length">The length of automatically generated random strings</param>
|
||||||
|
[<CompiledName "UseIdStringLength">]
|
||||||
|
let useIdStringLength length =
|
||||||
|
idStringLengthValue <- length
|
||||||
|
|
||||||
|
/// <summary>Retrieve the currently configured length of automatically generated random strings</summary>
|
||||||
|
/// <returns>The current length of automatically generated random strings</returns>
|
||||||
|
[<CompiledName "IdStringLength">]
|
||||||
|
let idStringLength () =
|
||||||
|
idStringLengthValue
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>Query construction functions</summary>
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module Query =
|
module Query =
|
||||||
|
|
||||||
/// Create a SELECT clause to retrieve the document data from the given table
|
/// <summary>Combine a query (<tt>SELECT</tt>, <tt>UPDATE</tt>, etc.) and a <tt>WHERE</tt> clause</summary>
|
||||||
[<CompiledName "SelectFromTable">]
|
/// <param name="statement">The first part of the statement</param>
|
||||||
let selectFromTable tableName =
|
/// <param name="where">The <tt>WHERE</tt> clause for the statement</param>
|
||||||
$"SELECT data FROM %s{tableName}"
|
/// <returns>The two parts of the query combined with <tt>WHERE</tt></returns>
|
||||||
|
[<CompiledName "StatementWhere">]
|
||||||
|
let statementWhere statement where =
|
||||||
|
$"%s{statement} WHERE %s{where}"
|
||||||
|
|
||||||
/// Create a WHERE clause fragment to implement a comparison on a field in a JSON document
|
/// <summary>Queries to define tables and indexes</summary>
|
||||||
[<CompiledName "WhereByField">]
|
|
||||||
let whereByField field paramName =
|
|
||||||
let theRest = match field.Op with EX | NEX -> string field.Op | _ -> $"{field.Op} %s{paramName}"
|
|
||||||
$"data ->> '%s{field.Name}' {theRest}"
|
|
||||||
|
|
||||||
/// Create a WHERE clause fragment to implement an ID-based query
|
|
||||||
[<CompiledName "WhereById">]
|
|
||||||
let whereById paramName =
|
|
||||||
whereByField (Field.EQ (Configuration.idField ()) 0) paramName
|
|
||||||
|
|
||||||
/// Queries to define tables and indexes
|
|
||||||
module Definition =
|
module Definition =
|
||||||
|
|
||||||
/// SQL statement to create a document table
|
/// <summary>SQL statement to create a document table</summary>
|
||||||
|
/// <param name="name">The name of the table to create (may include schema)</param>
|
||||||
|
/// <param name="dataType">The type of data for the column (<tt>JSON</tt>, <tt>JSONB</tt>, etc.)</param>
|
||||||
|
/// <returns>A query to create a document table</returns>
|
||||||
[<CompiledName "EnsureTableFor">]
|
[<CompiledName "EnsureTableFor">]
|
||||||
let ensureTableFor name dataType =
|
let ensureTableFor name dataType =
|
||||||
$"CREATE TABLE IF NOT EXISTS %s{name} (data %s{dataType} NOT NULL)"
|
$"CREATE TABLE IF NOT EXISTS %s{name} (data %s{dataType} NOT NULL)"
|
||||||
@@ -174,9 +511,14 @@ module Query =
|
|||||||
let parts = tableName.Split '.'
|
let parts = tableName.Split '.'
|
||||||
if Array.length parts = 1 then "", tableName else parts[0], parts[1]
|
if Array.length parts = 1 then "", tableName else parts[0], parts[1]
|
||||||
|
|
||||||
/// SQL statement to create an index on one or more fields in a JSON document
|
/// <summary>SQL statement to create an index on one or more fields in a JSON document</summary>
|
||||||
|
/// <param name="tableName">The table on which an index should be created (may include schema)</param>
|
||||||
|
/// <param name="indexName">The name of the index to be created</param>
|
||||||
|
/// <param name="fields">One or more fields to include in the index</param>
|
||||||
|
/// <param name="dialect">The SQL dialect to use when creating this index</param>
|
||||||
|
/// <returns>A query to create the field index</returns>
|
||||||
[<CompiledName "EnsureIndexOn">]
|
[<CompiledName "EnsureIndexOn">]
|
||||||
let ensureIndexOn tableName indexName (fields: string seq) =
|
let ensureIndexOn tableName indexName (fields: string seq) dialect =
|
||||||
let _, tbl = splitSchemaAndTable tableName
|
let _, tbl = splitSchemaAndTable tableName
|
||||||
let jsonFields =
|
let jsonFields =
|
||||||
fields
|
fields
|
||||||
@@ -184,80 +526,108 @@ module Query =
|
|||||||
let parts = it.Split ' '
|
let parts = it.Split ' '
|
||||||
let fieldName = if Array.length parts = 1 then it else parts[0]
|
let fieldName = if Array.length parts = 1 then it else parts[0]
|
||||||
let direction = if Array.length parts < 2 then "" else $" {parts[1]}"
|
let direction = if Array.length parts < 2 then "" else $" {parts[1]}"
|
||||||
$"(data ->> '{fieldName}'){direction}")
|
$"({Field.NameToPath fieldName dialect AsSql}){direction}")
|
||||||
|> String.concat ", "
|
|> String.concat ", "
|
||||||
$"CREATE INDEX IF NOT EXISTS idx_{tbl}_%s{indexName} ON {tableName} ({jsonFields})"
|
$"CREATE INDEX IF NOT EXISTS idx_{tbl}_%s{indexName} ON {tableName} ({jsonFields})"
|
||||||
|
|
||||||
/// SQL statement to create a key index for a document table
|
/// <summary>SQL statement to create a key index for a document table</summary>
|
||||||
|
/// <param name="tableName">The table on which a key index should be created (may include schema)</param>
|
||||||
|
/// <param name="dialect">The SQL dialect to use when creating this index</param>
|
||||||
|
/// <returns>A query to create the key index</returns>
|
||||||
[<CompiledName "EnsureKey">]
|
[<CompiledName "EnsureKey">]
|
||||||
let ensureKey tableName =
|
let ensureKey tableName dialect =
|
||||||
(ensureIndexOn tableName "key" [ Configuration.idField () ]).Replace("INDEX", "UNIQUE INDEX")
|
(ensureIndexOn tableName "key" [ Configuration.idField () ] dialect).Replace("INDEX", "UNIQUE INDEX")
|
||||||
|
|
||||||
/// Query to insert a document
|
/// <summary>Query to insert a document</summary>
|
||||||
|
/// <param name="tableName">The table into which to insert (may include schema)</param>
|
||||||
|
/// <returns>A query to insert a document</returns>
|
||||||
[<CompiledName "Insert">]
|
[<CompiledName "Insert">]
|
||||||
let insert tableName =
|
let insert tableName =
|
||||||
$"INSERT INTO %s{tableName} VALUES (@data)"
|
$"INSERT INTO %s{tableName} VALUES (@data)"
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
/// Query to save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
|
/// Query to save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table into which to save (may include schema)</param>
|
||||||
|
/// <returns>A query to save a document</returns>
|
||||||
[<CompiledName "Save">]
|
[<CompiledName "Save">]
|
||||||
let save tableName =
|
let save tableName =
|
||||||
sprintf
|
sprintf
|
||||||
"INSERT INTO %s VALUES (@data) ON CONFLICT ((data->>'%s')) DO UPDATE SET data = EXCLUDED.data"
|
"INSERT INTO %s VALUES (@data) ON CONFLICT ((data->>'%s')) DO UPDATE SET data = EXCLUDED.data"
|
||||||
tableName (Configuration.idField ())
|
tableName (Configuration.idField ())
|
||||||
|
|
||||||
/// Query to update a document
|
/// <summary>Query to count documents in a table</summary>
|
||||||
[<CompiledName "Update">]
|
/// <param name="tableName">The table in which to count documents (may include schema)</param>
|
||||||
let update tableName =
|
/// <returns>A query to count documents</returns>
|
||||||
$"""UPDATE %s{tableName} SET data = @data WHERE {whereById "@id"}"""
|
/// <remarks>This query has no <tt>WHERE</tt> clause</remarks>
|
||||||
|
[<CompiledName "Count">]
|
||||||
/// Queries for counting documents
|
let count tableName =
|
||||||
module Count =
|
|
||||||
|
|
||||||
/// Query to count all documents in a table
|
|
||||||
[<CompiledName "All">]
|
|
||||||
let all tableName =
|
|
||||||
$"SELECT COUNT(*) AS it FROM %s{tableName}"
|
$"SELECT COUNT(*) AS it FROM %s{tableName}"
|
||||||
|
|
||||||
/// Query to count matching documents using a text comparison on a JSON field
|
/// <summary>Query to check for document existence in a table</summary>
|
||||||
[<CompiledName "ByField">]
|
/// <param name="tableName">The table in which existence should be checked (may include schema)</param>
|
||||||
let byField tableName field =
|
/// <param name="where">The <tt>WHERE</tt> clause with the existence criteria</param>
|
||||||
$"""SELECT COUNT(*) AS it FROM %s{tableName} WHERE {whereByField field "@field"}"""
|
/// <returns>A query to check document existence</returns>
|
||||||
|
[<CompiledName "Exists">]
|
||||||
|
let exists tableName where =
|
||||||
|
$"SELECT EXISTS (SELECT 1 FROM %s{tableName} WHERE %s{where}) AS it"
|
||||||
|
|
||||||
/// Queries for determining document existence
|
/// <summary>Query to select documents from a table</summary>
|
||||||
module Exists =
|
/// <param name="tableName">The table from which documents should be found (may include schema)</param>
|
||||||
|
/// <returns>A query to retrieve documents</returns>
|
||||||
|
/// <remarks>This query has no <tt>WHERE</tt> clause</remarks>
|
||||||
|
[<CompiledName "Find">]
|
||||||
|
let find tableName =
|
||||||
|
$"SELECT data FROM %s{tableName}"
|
||||||
|
|
||||||
/// Query to determine if a document exists for the given ID
|
/// <summary>Query to update (replace) a document</summary>
|
||||||
[<CompiledName "ById">]
|
/// <param name="tableName">The table in which documents should be replaced (may include schema)</param>
|
||||||
let byId tableName =
|
/// <returns>A query to update documents</returns>
|
||||||
$"""SELECT EXISTS (SELECT 1 FROM %s{tableName} WHERE {whereById "@id"}) AS it"""
|
/// <remarks>This query has no <tt>WHERE</tt> clause</remarks>
|
||||||
|
[<CompiledName "Update">]
|
||||||
|
let update tableName =
|
||||||
|
$"UPDATE %s{tableName} SET data = @data"
|
||||||
|
|
||||||
/// Query to determine if documents exist using a comparison on a JSON field
|
/// <summary>Query to delete documents from a table</summary>
|
||||||
[<CompiledName "ByField">]
|
/// <param name="tableName">The table in which documents should be deleted (may include schema)</param>
|
||||||
let byField tableName field =
|
/// <returns>A query to delete documents</returns>
|
||||||
$"""SELECT EXISTS (SELECT 1 FROM %s{tableName} WHERE {whereByField field "@field"}) AS it"""
|
/// <remarks>This query has no <tt>WHERE</tt> clause</remarks>
|
||||||
|
[<CompiledName "Delete">]
|
||||||
|
let delete tableName =
|
||||||
|
$"DELETE FROM %s{tableName}"
|
||||||
|
|
||||||
/// Queries for retrieving documents
|
/// <summary>Create a SELECT clause to retrieve the document data from the given table</summary>
|
||||||
module Find =
|
/// <param name="tableName">The table from which documents should be found (may include schema)</param>
|
||||||
|
/// <returns>A query to retrieve documents</returns>
|
||||||
|
[<CompiledName "SelectFromTable">]
|
||||||
|
[<System.Obsolete "Use Find instead">]
|
||||||
|
let selectFromTable tableName =
|
||||||
|
find tableName
|
||||||
|
|
||||||
/// Query to retrieve a document by its ID
|
/// <summary>Create an <tt>ORDER BY</tt> clause for the given fields</summary>
|
||||||
[<CompiledName "ById">]
|
/// <param name="fields">One or more fields by which to order</param>
|
||||||
let byId tableName =
|
/// <param name="dialect">The SQL dialect for the generated clause</param>
|
||||||
$"""{selectFromTable tableName} WHERE {whereById "@id"}"""
|
/// <returns>An <tt>ORDER BY</tt> clause for the given fields</returns>
|
||||||
|
[<CompiledName "OrderBy">]
|
||||||
/// Query to retrieve documents using a comparison on a JSON field
|
let orderBy fields dialect =
|
||||||
[<CompiledName "ByField">]
|
if Seq.isEmpty fields then ""
|
||||||
let byField tableName field =
|
else
|
||||||
$"""{selectFromTable tableName} WHERE {whereByField field "@field"}"""
|
fields
|
||||||
|
|> Seq.map (fun it ->
|
||||||
/// Queries to delete documents
|
if it.Name.Contains ' ' then
|
||||||
module Delete =
|
let parts = it.Name.Split ' '
|
||||||
|
{ it with Name = parts[0] }, Some $""" {parts |> Array.skip 1 |> String.concat " "}"""
|
||||||
/// Query to delete a document by its ID
|
else it, None)
|
||||||
[<CompiledName "ById">]
|
|> Seq.map (fun (field, direction) ->
|
||||||
let byId tableName =
|
if field.Name.StartsWith "n:" then
|
||||||
$"""DELETE FROM %s{tableName} WHERE {whereById "@id"}"""
|
let f = { field with Name = field.Name[2..] }
|
||||||
|
match dialect with
|
||||||
/// Query to delete documents using a comparison on a JSON field
|
| PostgreSQL -> $"({f.Path PostgreSQL AsSql})::numeric"
|
||||||
[<CompiledName "ByField">]
|
| SQLite -> f.Path SQLite AsSql
|
||||||
let byField tableName field =
|
elif field.Name.StartsWith "i:" then
|
||||||
$"""DELETE FROM %s{tableName} WHERE {whereByField field "@field"}"""
|
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 "")
|
||||||
|
|> String.concat ", "
|
||||||
|
|> function it -> $" ORDER BY {it}"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ 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,11 +1,13 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
|
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
|
||||||
<DebugType>embedded</DebugType>
|
<DebugType>embedded</DebugType>
|
||||||
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<AssemblyVersion>3.0.0.0</AssemblyVersion>
|
<AssemblyVersion>4.0.1.0</AssemblyVersion>
|
||||||
<FileVersion>3.0.0.0</FileVersion>
|
<FileVersion>4.0.1.0</FileVersion>
|
||||||
<VersionPrefix>3.0.0</VersionPrefix>
|
<VersionPrefix>4.0.1</VersionPrefix>
|
||||||
|
<PackageReleaseNotes>From v4.0: Add XML documention (IDE support)
|
||||||
|
From v3.1: See 4.0 release for breaking changes and compatibility</PackageReleaseNotes>
|
||||||
<Authors>danieljsummers</Authors>
|
<Authors>danieljsummers</Authors>
|
||||||
<Company>Bit Badger Solutions</Company>
|
<Company>Bit Badger Solutions</Company>
|
||||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
|
|||||||
@@ -2,19 +2,24 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Description>Use PostgreSQL as a document database</Description>
|
<Description>Use PostgreSQL as a document database</Description>
|
||||||
<PackageReleaseNotes>v3 release; official replacement for BitBadger.Npgsql.Documents</PackageReleaseNotes>
|
|
||||||
<PackageTags>JSON Document PostgreSQL Npgsql</PackageTags>
|
<PackageTags>JSON Document PostgreSQL Npgsql</PackageTags>
|
||||||
|
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Library.fs" />
|
<Compile Include="Library.fs" />
|
||||||
|
<Compile Include="WithProps.fs" />
|
||||||
|
<Compile Include="Functions.fs" />
|
||||||
<Compile Include="Extensions.fs" />
|
<Compile Include="Extensions.fs" />
|
||||||
|
<Compile Include="Compat.fs" />
|
||||||
<None Include="README.md" Pack="true" PackagePath="\" />
|
<None Include="README.md" Pack="true" PackagePath="\" />
|
||||||
<None Include="..\icon.png" Pack="true" PackagePath="\" />
|
<None Include="..\icon.png" Pack="true" PackagePath="\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Npgsql.FSharp" Version="5.7.0" />
|
<PackageReference Include="Npgsql" Version="9.0.2" />
|
||||||
|
<PackageReference Include="Npgsql.FSharp" Version="8.0.0" />
|
||||||
|
<PackageReference Update="FSharp.Core" Version="9.0.100" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
270
src/Postgres/Compat.fs
Normal file
270
src/Postgres/Compat.fs
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
namespace BitBadger.Documents.Postgres.Compat
|
||||||
|
|
||||||
|
open BitBadger.Documents
|
||||||
|
open BitBadger.Documents.Postgres
|
||||||
|
|
||||||
|
[<AutoOpen>]
|
||||||
|
module Parameters =
|
||||||
|
|
||||||
|
/// Create a JSON field parameter
|
||||||
|
[<CompiledName "AddField">]
|
||||||
|
[<System.Obsolete "Use addFieldParams (F#) / AddFields (C#) instead ~ will be removed in v4.1">]
|
||||||
|
let addFieldParam name field parameters =
|
||||||
|
addFieldParams [ { field with ParameterName = Some name } ] parameters
|
||||||
|
|
||||||
|
/// Append JSON field name parameters for the given field names to the given parameters
|
||||||
|
[<CompiledName "FieldName">]
|
||||||
|
[<System.Obsolete "Use fieldNameParams (F#) / FieldNames (C#) instead ~ will be removed in v4.1">]
|
||||||
|
let fieldNameParam fieldNames =
|
||||||
|
fieldNameParams fieldNames
|
||||||
|
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Query =
|
||||||
|
|
||||||
|
/// Create a WHERE clause fragment to implement a comparison on a field in a JSON document
|
||||||
|
[<CompiledName "WhereByField">]
|
||||||
|
[<System.Obsolete "Use WhereByFields instead ~ will be removed in v4.1">]
|
||||||
|
let whereByField field paramName =
|
||||||
|
Query.whereByFields Any [ { field with ParameterName = Some paramName } ]
|
||||||
|
|
||||||
|
|
||||||
|
module WithProps =
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Count =
|
||||||
|
|
||||||
|
/// Count matching documents using a JSON field comparison (->> =)
|
||||||
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead ~ will be removed in v4.1">]
|
||||||
|
let byField tableName field sqlProps =
|
||||||
|
WithProps.Count.byFields tableName Any [ field ] sqlProps
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Exists =
|
||||||
|
|
||||||
|
/// Determine if a document exists using a JSON field comparison (->> =)
|
||||||
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead ~ will be removed in v4.1">]
|
||||||
|
let byField tableName field sqlProps =
|
||||||
|
WithProps.Exists.byFields tableName Any [ field ] sqlProps
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Find =
|
||||||
|
|
||||||
|
/// Retrieve documents matching a JSON field comparison (->> =)
|
||||||
|
[<CompiledName "FSharpByField">]
|
||||||
|
[<System.Obsolete "Use byFields instead ~ will be removed in v4.1">]
|
||||||
|
let byField<'TDoc> tableName field sqlProps =
|
||||||
|
WithProps.Find.byFields<'TDoc> tableName Any [ field ] sqlProps
|
||||||
|
|
||||||
|
/// Retrieve documents matching a JSON field comparison (->> =)
|
||||||
|
[<System.Obsolete "Use ByFields instead ~ will be removed in v4.1">]
|
||||||
|
let ByField<'TDoc>(tableName, field, sqlProps) =
|
||||||
|
WithProps.Find.ByFields<'TDoc>(tableName, Any, Seq.singleton field, sqlProps)
|
||||||
|
|
||||||
|
/// Retrieve the first document matching a JSON field comparison (->> =); returns None if not found
|
||||||
|
[<CompiledName "FSharpFirstByField">]
|
||||||
|
[<System.Obsolete "Use firstByFields instead ~ will be removed in v4.1">]
|
||||||
|
let firstByField<'TDoc> tableName field sqlProps =
|
||||||
|
WithProps.Find.firstByFields<'TDoc> tableName Any [ field ] sqlProps
|
||||||
|
|
||||||
|
/// Retrieve the first document matching a JSON field comparison (->> =); returns null if not found
|
||||||
|
[<System.Obsolete "Use FirstByFields instead ~ will be removed in v4.1">]
|
||||||
|
let FirstByField<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, field, sqlProps) =
|
||||||
|
WithProps.Find.FirstByFields<'TDoc>(tableName, Any, Seq.singleton field, sqlProps)
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Patch =
|
||||||
|
|
||||||
|
/// Patch documents using a JSON field comparison query in the WHERE clause (->> =)
|
||||||
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead ~ will be removed in v4.1">]
|
||||||
|
let byField tableName field (patch: 'TPatch) sqlProps =
|
||||||
|
WithProps.Patch.byFields tableName Any [ field ] patch sqlProps
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module RemoveFields =
|
||||||
|
|
||||||
|
/// Remove fields from documents via a comparison on a JSON field in the document
|
||||||
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead ~ will be removed in v4.1">]
|
||||||
|
let byField tableName field fieldNames sqlProps =
|
||||||
|
WithProps.RemoveFields.byFields tableName Any [ field ] fieldNames sqlProps
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Delete =
|
||||||
|
|
||||||
|
/// Delete documents by matching a JSON field comparison query (->> =)
|
||||||
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead ~ will be removed in v4.1">]
|
||||||
|
let byField tableName field sqlProps =
|
||||||
|
WithProps.Delete.byFields tableName Any [ field ] sqlProps
|
||||||
|
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Count =
|
||||||
|
|
||||||
|
/// Count matching documents using a JSON field comparison (->> =)
|
||||||
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead ~ will be removed in v4.1">]
|
||||||
|
let byField tableName field =
|
||||||
|
Count.byFields tableName Any [ field ]
|
||||||
|
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Exists =
|
||||||
|
|
||||||
|
/// Determine if a document exists using a JSON field comparison (->> =)
|
||||||
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead ~ will be removed in v4.1">]
|
||||||
|
let byField tableName field =
|
||||||
|
Exists.byFields tableName Any [ field ]
|
||||||
|
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Find =
|
||||||
|
|
||||||
|
/// Retrieve documents matching a JSON field comparison (->> =)
|
||||||
|
[<CompiledName "FSharpByField">]
|
||||||
|
[<System.Obsolete "Use byFields instead ~ will be removed in v4.1">]
|
||||||
|
let byField<'TDoc> tableName field =
|
||||||
|
Find.byFields<'TDoc> tableName Any [ field ]
|
||||||
|
|
||||||
|
/// Retrieve documents matching a JSON field comparison (->> =)
|
||||||
|
[<System.Obsolete "Use ByFields instead ~ will be removed in v4.1">]
|
||||||
|
let ByField<'TDoc>(tableName, field) =
|
||||||
|
Find.ByFields<'TDoc>(tableName, Any, Seq.singleton field)
|
||||||
|
|
||||||
|
/// Retrieve the first document matching a JSON field comparison (->> =); returns None if not found
|
||||||
|
[<CompiledName "FSharpFirstByField">]
|
||||||
|
[<System.Obsolete "Use firstByFields instead ~ will be removed in v4.1">]
|
||||||
|
let firstByField<'TDoc> tableName field =
|
||||||
|
Find.firstByFields<'TDoc> tableName Any [ field ]
|
||||||
|
|
||||||
|
/// 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">]
|
||||||
|
let FirstByField<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, field) =
|
||||||
|
Find.FirstByFields<'TDoc>(tableName, Any, Seq.singleton field)
|
||||||
|
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Patch =
|
||||||
|
|
||||||
|
/// Patch documents using a JSON field comparison query in the WHERE clause (->> =)
|
||||||
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead ~ will be removed in v4.1">]
|
||||||
|
let byField tableName field (patch: 'TPatch) =
|
||||||
|
Patch.byFields tableName Any [ field ] patch
|
||||||
|
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module RemoveFields =
|
||||||
|
|
||||||
|
/// Remove fields from documents via a comparison on a JSON field in the document
|
||||||
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead ~ will be removed in v4.1">]
|
||||||
|
let byField tableName field fieldNames =
|
||||||
|
RemoveFields.byFields tableName Any [ field ] fieldNames
|
||||||
|
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Delete =
|
||||||
|
|
||||||
|
/// Delete documents by matching a JSON field comparison query (->> =)
|
||||||
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead ~ will be removed in v4.1">]
|
||||||
|
let byField tableName field =
|
||||||
|
Delete.byFields tableName Any [ field ]
|
||||||
|
|
||||||
|
|
||||||
|
open Npgsql
|
||||||
|
|
||||||
|
/// F# Extensions for the NpgsqlConnection type
|
||||||
|
[<AutoOpen>]
|
||||||
|
module Extensions =
|
||||||
|
|
||||||
|
type NpgsqlConnection with
|
||||||
|
|
||||||
|
/// Count matching documents using a JSON field comparison query (->> =)
|
||||||
|
[<System.Obsolete "Use countByFields instead ~ will be removed in v4.1">]
|
||||||
|
member conn.countByField tableName field =
|
||||||
|
conn.countByFields tableName Any [ field ]
|
||||||
|
|
||||||
|
/// Determine if documents exist using a JSON field comparison query (->> =)
|
||||||
|
[<System.Obsolete "Use existsByFields instead ~ will be removed in v4.1">]
|
||||||
|
member conn.existsByField tableName field =
|
||||||
|
conn.existsByFields tableName Any [ field ]
|
||||||
|
|
||||||
|
/// Retrieve documents matching a JSON field comparison query (->> =)
|
||||||
|
[<System.Obsolete "Use findByFields instead ~ will be removed in v4.1">]
|
||||||
|
member conn.findByField<'TDoc> tableName field =
|
||||||
|
conn.findByFields<'TDoc> tableName Any [ field ]
|
||||||
|
|
||||||
|
/// Retrieve the first document matching a JSON field comparison query (->> =); returns None if not found
|
||||||
|
[<System.Obsolete "Use findFirstByFields instead ~ will be removed in v4.1">]
|
||||||
|
member conn.findFirstByField<'TDoc> tableName field =
|
||||||
|
conn.findFirstByFields<'TDoc> tableName Any [ field ]
|
||||||
|
|
||||||
|
/// Patch documents using a JSON field comparison query in the WHERE clause (->> =)
|
||||||
|
[<System.Obsolete "Use patchByFields instead ~ will be removed in v4.1">]
|
||||||
|
member conn.patchByField tableName field (patch: 'TPatch) =
|
||||||
|
conn.patchByFields tableName Any [ field ] patch
|
||||||
|
|
||||||
|
/// Remove fields from documents via a comparison on a JSON field in the document
|
||||||
|
[<System.Obsolete "Use removeFieldsByFields instead ~ will be removed in v4.1">]
|
||||||
|
member conn.removeFieldsByField tableName field fieldNames =
|
||||||
|
conn.removeFieldsByFields tableName Any [ field ] fieldNames
|
||||||
|
|
||||||
|
/// Delete documents by matching a JSON field comparison query (->> =)
|
||||||
|
[<System.Obsolete "Use deleteByFields instead ~ will be removed in v4.1">]
|
||||||
|
member conn.deleteByField tableName field =
|
||||||
|
conn.deleteByFields tableName Any [ field ]
|
||||||
|
|
||||||
|
|
||||||
|
open System.Runtime.CompilerServices
|
||||||
|
open Npgsql.FSharp
|
||||||
|
|
||||||
|
type NpgsqlConnectionCSharpCompatExtensions =
|
||||||
|
|
||||||
|
/// Count matching documents using a JSON field comparison query (->> =)
|
||||||
|
[<Extension>]
|
||||||
|
[<System.Obsolete "Use CountByFields instead ~ will be removed in v4.1">]
|
||||||
|
static member inline CountByField(conn, tableName, field) =
|
||||||
|
WithProps.Count.byFields tableName Any [ field ] (Sql.existingConnection conn)
|
||||||
|
|
||||||
|
/// Determine if documents exist using a JSON field comparison query (->> =)
|
||||||
|
[<Extension>]
|
||||||
|
[<System.Obsolete "Use ExistsByFields instead ~ will be removed in v4.1">]
|
||||||
|
static member inline ExistsByField(conn, tableName, field) =
|
||||||
|
WithProps.Exists.byFields tableName Any [ field ] (Sql.existingConnection conn)
|
||||||
|
|
||||||
|
/// Retrieve documents matching a JSON field comparison query (->> =)
|
||||||
|
[<Extension>]
|
||||||
|
[<System.Obsolete "Use FindByFields instead ~ will be removed in v4.1">]
|
||||||
|
static member inline FindByField<'TDoc>(conn, tableName, field) =
|
||||||
|
WithProps.Find.ByFields<'TDoc>(tableName, Any, [ field ], Sql.existingConnection conn)
|
||||||
|
|
||||||
|
/// Retrieve the first document matching a JSON field comparison query (->> =); returns null if not found
|
||||||
|
[<Extension>]
|
||||||
|
[<System.Obsolete "Use FindFirstByFields instead ~ will be removed in v4.1">]
|
||||||
|
static member inline FindFirstByField<'TDoc when 'TDoc: null and 'TDoc: not struct>(conn, tableName, field) =
|
||||||
|
WithProps.Find.FirstByFields<'TDoc>(tableName, Any, [ field ], Sql.existingConnection conn)
|
||||||
|
|
||||||
|
/// Patch documents using a JSON field comparison query in the WHERE clause (->> =)
|
||||||
|
[<Extension>]
|
||||||
|
[<System.Obsolete "Use PatchByFields instead ~ will be removed in v4.1">]
|
||||||
|
static member inline PatchByField(conn, tableName, field, patch: 'TPatch) =
|
||||||
|
WithProps.Patch.byFields tableName Any [ field ] patch (Sql.existingConnection conn)
|
||||||
|
|
||||||
|
/// Remove fields from documents via a comparison on a JSON field in the document
|
||||||
|
[<Extension>]
|
||||||
|
[<System.Obsolete "Use RemoveFieldsByFields instead ~ will be removed in v4.1">]
|
||||||
|
static member inline RemoveFieldsByField(conn, tableName, field, fieldNames) =
|
||||||
|
WithProps.RemoveFields.byFields tableName Any [ field ] fieldNames (Sql.existingConnection conn)
|
||||||
|
|
||||||
|
/// Delete documents by matching a JSON field comparison query (->> =)
|
||||||
|
[<Extension>]
|
||||||
|
[<System.Obsolete "Use DeleteByFields instead ~ will be removed in v4.1">]
|
||||||
|
static member inline DeleteByField(conn, tableName, field) =
|
||||||
|
WithProps.Delete.byFields tableName Any [ field ] (Sql.existingConnection conn)
|
||||||
File diff suppressed because it is too large
Load Diff
617
src/Postgres/Functions.fs
Normal file
617
src/Postgres/Functions.fs
Normal file
@@ -0,0 +1,617 @@
|
|||||||
|
namespace BitBadger.Documents.Postgres
|
||||||
|
|
||||||
|
/// <summary>Commands to execute custom SQL queries</summary>
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Custom =
|
||||||
|
|
||||||
|
/// <summary>Execute a query that returns a list of results</summary>
|
||||||
|
/// <param name="query">The query to retrieve the results</param>
|
||||||
|
/// <param name="parameters">Parameters to use for the query</param>
|
||||||
|
/// <param name="mapFunc">The mapping function between the document and the domain item</param>
|
||||||
|
/// <returns>A list of results for the given query</returns>
|
||||||
|
[<CompiledName "FSharpList">]
|
||||||
|
let list<'TDoc> query parameters (mapFunc: RowReader -> 'TDoc) =
|
||||||
|
WithProps.Custom.list<'TDoc> query parameters mapFunc (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Execute a query that returns a list of results</summary>
|
||||||
|
/// <param name="query">The query to retrieve the results</param>
|
||||||
|
/// <param name="parameters">Parameters to use for the query</param>
|
||||||
|
/// <param name="mapFunc">The mapping function between the document and the domain item</param>
|
||||||
|
/// <returns>A list of results for the given query</returns>
|
||||||
|
let List<'TDoc>(query, parameters, mapFunc: System.Func<RowReader, 'TDoc>) =
|
||||||
|
WithProps.Custom.List<'TDoc>(query, parameters, mapFunc, fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Execute a query that returns one or no results</summary>
|
||||||
|
/// <param name="query">The query to retrieve the results</param>
|
||||||
|
/// <param name="parameters">Parameters to use for the query</param>
|
||||||
|
/// <param name="mapFunc">The mapping function between the document and the domain item</param>
|
||||||
|
/// <returns><tt>Some</tt> with the first matching result, or <tt>None</tt> if not found</returns>
|
||||||
|
[<CompiledName "FSharpSingle">]
|
||||||
|
let single<'TDoc> query parameters (mapFunc: RowReader -> 'TDoc) =
|
||||||
|
WithProps.Custom.single<'TDoc> query parameters mapFunc (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Execute a query that returns one or no results</summary>
|
||||||
|
/// <param name="query">The query to retrieve the results</param>
|
||||||
|
/// <param name="parameters">Parameters to use for the query</param>
|
||||||
|
/// <param name="mapFunc">The mapping function between the document and the domain item</param>
|
||||||
|
/// <returns>The first matching result, or <tt>null</tt> if not found</returns>
|
||||||
|
let Single<'TDoc when 'TDoc: null and 'TDoc: not struct>(
|
||||||
|
query, parameters, mapFunc: System.Func<RowReader, 'TDoc>) =
|
||||||
|
WithProps.Custom.Single<'TDoc>(query, parameters, mapFunc, fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Execute a query that returns no results</summary>
|
||||||
|
/// <param name="query">The query to retrieve the results</param>
|
||||||
|
/// <param name="parameters">Parameters to use for the query</param>
|
||||||
|
[<CompiledName "NonQuery">]
|
||||||
|
let nonQuery query parameters =
|
||||||
|
WithProps.Custom.nonQuery query parameters (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Execute a query that returns a scalar value</summary>
|
||||||
|
/// <param name="query">The query to retrieve the value</param>
|
||||||
|
/// <param name="parameters">Parameters to use for the query</param>
|
||||||
|
/// <param name="mapFunc">The mapping function to obtain the value</param>
|
||||||
|
/// <returns>The scalar value for the query</returns>
|
||||||
|
[<CompiledName "FSharpScalar">]
|
||||||
|
let scalar<'T when 'T: struct> query parameters (mapFunc: RowReader -> 'T) =
|
||||||
|
WithProps.Custom.scalar query parameters mapFunc (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Execute a query that returns a scalar value</summary>
|
||||||
|
/// <param name="query">The query to retrieve the value</param>
|
||||||
|
/// <param name="parameters">Parameters to use for the query</param>
|
||||||
|
/// <param name="mapFunc">The mapping function to obtain the value</param>
|
||||||
|
/// <returns>The scalar value for the query</returns>
|
||||||
|
let Scalar<'T when 'T: struct>(query, parameters, mapFunc: System.Func<RowReader, 'T>) =
|
||||||
|
WithProps.Custom.Scalar<'T>(query, parameters, mapFunc, fromDataSource ())
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>Table and index definition commands</summary>
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Definition =
|
||||||
|
|
||||||
|
/// <summary>Create a document table</summary>
|
||||||
|
/// <param name="name">The table whose existence should be ensured (may include schema)</param>
|
||||||
|
[<CompiledName "EnsureTable">]
|
||||||
|
let ensureTable name =
|
||||||
|
WithProps.Definition.ensureTable name (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Create an index on documents in the specified table</summary>
|
||||||
|
/// <param name="name">The table to be indexed (may include schema)</param>
|
||||||
|
/// <param name="idxType">The type of document index to create</param>
|
||||||
|
[<CompiledName "EnsureDocumentIndex">]
|
||||||
|
let ensureDocumentIndex name idxType =
|
||||||
|
WithProps.Definition.ensureDocumentIndex name idxType (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Create an index on field(s) within documents in the specified table</summary>
|
||||||
|
/// <param name="tableName">The table to be indexed (may include schema)</param>
|
||||||
|
/// <param name="indexName">The name of the index to create</param>
|
||||||
|
/// <param name="fields">One or more fields to be indexed</param>
|
||||||
|
[<CompiledName "EnsureFieldIndex">]
|
||||||
|
let ensureFieldIndex tableName indexName fields =
|
||||||
|
WithProps.Definition.ensureFieldIndex tableName indexName fields (fromDataSource ())
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>Document writing functions</summary>
|
||||||
|
[<AutoOpen>]
|
||||||
|
module Document =
|
||||||
|
|
||||||
|
/// <summary>Insert a new document</summary>
|
||||||
|
/// <param name="tableName">The table into which the document should be inserted (may include schema)</param>
|
||||||
|
/// <param name="document">The document to be inserted</param>
|
||||||
|
[<CompiledName "Insert">]
|
||||||
|
let insert<'TDoc> tableName (document: 'TDoc) =
|
||||||
|
WithProps.Document.insert<'TDoc> tableName document (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")</summary>
|
||||||
|
/// <param name="tableName">The table into which the document should be saved (may include schema)</param>
|
||||||
|
/// <param name="document">The document to be saved</param>
|
||||||
|
[<CompiledName "Save">]
|
||||||
|
let save<'TDoc> tableName (document: 'TDoc) =
|
||||||
|
WithProps.Document.save<'TDoc> tableName document (fromDataSource ())
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>Queries to count documents</summary>
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Count =
|
||||||
|
|
||||||
|
/// <summary>Count all documents in a table</summary>
|
||||||
|
/// <param name="tableName">The table in which documents should be counted (may include schema)</param>
|
||||||
|
/// <returns>The count of the documents in the table</returns>
|
||||||
|
[<CompiledName "All">]
|
||||||
|
let all tableName =
|
||||||
|
WithProps.Count.all tableName (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Count matching documents using JSON field comparisons (<tt>->> =</tt>, etc.)</summary>
|
||||||
|
/// <param name="tableName">The table in which documents should be counted (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
/// <returns>The count of matching documents in the table</returns>
|
||||||
|
[<CompiledName "ByFields">]
|
||||||
|
let byFields tableName howMatched fields =
|
||||||
|
WithProps.Count.byFields tableName howMatched fields (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Count matching documents using a JSON containment query (<tt>@></tt>)</summary>
|
||||||
|
/// <param name="tableName">The table in which documents should be counted (may include schema)</param>
|
||||||
|
/// <param name="criteria">The document to match with the containment query</param>
|
||||||
|
/// <returns>The count of the documents in the table</returns>
|
||||||
|
[<CompiledName "ByContains">]
|
||||||
|
let byContains tableName criteria =
|
||||||
|
WithProps.Count.byContains tableName criteria (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Count matching documents using a JSON Path match query (<tt>@?</tt>)</summary>
|
||||||
|
/// <param name="tableName">The table in which documents should be counted (may include schema)</param>
|
||||||
|
/// <param name="jsonPath">The JSON Path expression to be matched</param>
|
||||||
|
/// <returns>The count of the documents in the table</returns>
|
||||||
|
[<CompiledName "ByJsonPath">]
|
||||||
|
let byJsonPath tableName jsonPath =
|
||||||
|
WithProps.Count.byJsonPath tableName jsonPath (fromDataSource ())
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>Queries to determine if documents exist</summary>
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Exists =
|
||||||
|
|
||||||
|
/// <summary>Determine if a document exists for the given ID</summary>
|
||||||
|
/// <param name="tableName">The table in which existence should be checked (may include schema)</param>
|
||||||
|
/// <param name="docId">The ID of the document whose existence should be checked</param>
|
||||||
|
/// <returns>True if a document exists, false if not</returns>
|
||||||
|
[<CompiledName "ById">]
|
||||||
|
let byId tableName docId =
|
||||||
|
WithProps.Exists.byId tableName docId (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Determine if a document exists using JSON field comparisons (<tt>->> =</tt>, etc.)</summary>
|
||||||
|
/// <param name="tableName">The table in which existence should be checked (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
/// <returns>True if any matching documents exist, false if not</returns>
|
||||||
|
[<CompiledName "ByFields">]
|
||||||
|
let byFields tableName howMatched fields =
|
||||||
|
WithProps.Exists.byFields tableName howMatched fields (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Determine if a document exists using a JSON containment query (<tt>@></tt>)</summary>
|
||||||
|
/// <param name="tableName">The table in which existence should be checked (may include schema)</param>
|
||||||
|
/// <param name="criteria">The document to match with the containment query</param>
|
||||||
|
/// <returns>True if any matching documents exist, false if not</returns>
|
||||||
|
[<CompiledName "ByContains">]
|
||||||
|
let byContains tableName criteria =
|
||||||
|
WithProps.Exists.byContains tableName criteria (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Determine if a document exists using a JSON Path match query (<tt>@?</tt>)</summary>
|
||||||
|
/// <param name="tableName">The table in which existence should be checked (may include schema)</param>
|
||||||
|
/// <param name="jsonPath">The JSON Path expression to be matched</param>
|
||||||
|
/// <returns>True if any matching documents exist, false if not</returns>
|
||||||
|
[<CompiledName "ByJsonPath">]
|
||||||
|
let byJsonPath tableName jsonPath =
|
||||||
|
WithProps.Exists.byJsonPath tableName jsonPath (fromDataSource ())
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>Commands to retrieve documents</summary>
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Find =
|
||||||
|
|
||||||
|
/// <summary>Retrieve all documents in the given table</summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <returns>All documents from the given table</returns>
|
||||||
|
[<CompiledName "FSharpAll">]
|
||||||
|
let all<'TDoc> tableName =
|
||||||
|
WithProps.Find.all<'TDoc> tableName (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Retrieve all documents in the given table</summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <returns>All documents from the given table</returns>
|
||||||
|
let All<'TDoc> tableName =
|
||||||
|
WithProps.Find.All<'TDoc>(tableName, fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Retrieve all documents in the given table ordered by the given fields in the document</summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <returns>All documents from the given table, ordered by the given fields</returns>
|
||||||
|
[<CompiledName "FSharpAllOrdered">]
|
||||||
|
let allOrdered<'TDoc> tableName orderFields =
|
||||||
|
WithProps.Find.allOrdered<'TDoc> tableName orderFields (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Retrieve all documents in the given table ordered by the given fields in the document</summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <returns>All documents from the given table, ordered by the given fields</returns>
|
||||||
|
let AllOrdered<'TDoc> tableName orderFields =
|
||||||
|
WithProps.Find.AllOrdered<'TDoc>(tableName, orderFields, fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Retrieve a document by its ID</summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="docId">The ID of the document to retrieve</param>
|
||||||
|
/// <returns><tt>Some</tt> with the document if found, <tt>None</tt> otherwise</returns>
|
||||||
|
[<CompiledName "FSharpById">]
|
||||||
|
let byId<'TKey, 'TDoc> tableName docId =
|
||||||
|
WithProps.Find.byId<'TKey, 'TDoc> tableName docId (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Retrieve a document by its ID</summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="docId">The ID of the document to retrieve</param>
|
||||||
|
/// <returns>The document if found, <tt>null</tt> otherwise</returns>
|
||||||
|
let ById<'TKey, 'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, docId: 'TKey) =
|
||||||
|
WithProps.Find.ById<'TKey, 'TDoc>(tableName, docId, fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Retrieve documents matching JSON field comparisons (<tt>->> =</tt>, etc.)</summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
/// <returns>All documents matching the given fields</returns>
|
||||||
|
[<CompiledName "FSharpByFields">]
|
||||||
|
let byFields<'TDoc> tableName howMatched fields =
|
||||||
|
WithProps.Find.byFields<'TDoc> tableName howMatched fields (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Retrieve documents matching JSON field comparisons (<tt>->> =</tt>, etc.)</summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
/// <returns>All documents matching the given fields</returns>
|
||||||
|
let ByFields<'TDoc>(tableName, howMatched, fields) =
|
||||||
|
WithProps.Find.ByFields<'TDoc>(tableName, howMatched, fields, fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve documents matching JSON field comparisons (<tt>->> =</tt>, etc.) ordered by the given fields in
|
||||||
|
/// the document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="queryFields">The field conditions to match</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <returns>All documents matching the given fields, ordered by the other given fields</returns>
|
||||||
|
[<CompiledName "FSharpByFieldsOrdered">]
|
||||||
|
let byFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields =
|
||||||
|
WithProps.Find.byFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve documents matching JSON field comparisons (<tt>->> =</tt>, etc.) ordered by the given fields in
|
||||||
|
/// the document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="queryFields">The field conditions to match</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <returns>All documents matching the given fields, ordered by the other given fields</returns>
|
||||||
|
let ByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields) =
|
||||||
|
WithProps.Find.ByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Retrieve documents matching a JSON containment query (<tt>@></tt>)</summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="criteria">The document to match with the containment query</param>
|
||||||
|
/// <returns>All documents matching the given containment query</returns>
|
||||||
|
[<CompiledName "FSharpByContains">]
|
||||||
|
let byContains<'TDoc> tableName (criteria: obj) =
|
||||||
|
WithProps.Find.byContains<'TDoc> tableName criteria (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Retrieve documents matching a JSON containment query (<tt>@></tt>)</summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="criteria">The document to match with the containment query</param>
|
||||||
|
/// <returns>All documents matching the given containment query</returns>
|
||||||
|
let ByContains<'TDoc>(tableName, criteria: obj) =
|
||||||
|
WithProps.Find.ByContains<'TDoc>(tableName, criteria, fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve documents matching a JSON containment query (<tt>@></tt>) ordered by the given fields in the
|
||||||
|
/// document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="criteria">The document to match with the containment query</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <returns>All documents matching the given containment query, ordered by the given fields</returns>
|
||||||
|
[<CompiledName "FSharpByContainsOrdered">]
|
||||||
|
let byContainsOrdered<'TDoc> tableName (criteria: obj) orderFields =
|
||||||
|
WithProps.Find.byContainsOrdered<'TDoc> tableName criteria orderFields (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve documents matching a JSON containment query (<tt>@></tt>) ordered by the given fields in the
|
||||||
|
/// document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="criteria">The document to match with the containment query</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <returns>All documents matching the given containment query, ordered by the given fields</returns>
|
||||||
|
let ByContainsOrdered<'TDoc>(tableName, criteria: obj, orderFields) =
|
||||||
|
WithProps.Find.ByContainsOrdered<'TDoc>(tableName, criteria, orderFields, fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Retrieve documents matching a JSON Path match query (<tt>@?</tt>)</summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="jsonPath">The JSON Path expression to match</param>
|
||||||
|
/// <returns>All documents matching the given JSON Path expression</returns>
|
||||||
|
[<CompiledName "FSharpByJsonPath">]
|
||||||
|
let byJsonPath<'TDoc> tableName jsonPath =
|
||||||
|
WithProps.Find.byJsonPath<'TDoc> tableName jsonPath (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Retrieve documents matching a JSON Path match query (<tt>@?</tt>)</summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="jsonPath">The JSON Path expression to match</param>
|
||||||
|
/// <returns>All documents matching the given JSON Path expression</returns>
|
||||||
|
let ByJsonPath<'TDoc>(tableName, jsonPath) =
|
||||||
|
WithProps.Find.ByJsonPath<'TDoc>(tableName, jsonPath, fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve documents matching a JSON Path match query (<tt>@?</tt>) ordered by the given fields in the document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="jsonPath">The JSON Path expression to match</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <returns>All documents matching the given JSON Path expression, ordered by the given fields</returns>
|
||||||
|
[<CompiledName "FSharpByJsonPathOrdered">]
|
||||||
|
let byJsonPathOrdered<'TDoc> tableName jsonPath orderFields =
|
||||||
|
WithProps.Find.byJsonPathOrdered<'TDoc> tableName jsonPath orderFields (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve documents matching a JSON Path match query (<tt>@?</tt>) ordered by the given fields in the document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="jsonPath">The JSON Path expression to match</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <returns>All documents matching the given JSON Path expression, ordered by the given fields</returns>
|
||||||
|
let ByJsonPathOrdered<'TDoc>(tableName, jsonPath, orderFields) =
|
||||||
|
WithProps.Find.ByJsonPathOrdered<'TDoc>(tableName, jsonPath, orderFields, fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Retrieve the first document matching JSON field comparisons (<tt>->> =</tt>, etc.)</summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
/// <returns><tt>Some</tt> with the first document, or <tt>None</tt> if not found</returns>
|
||||||
|
[<CompiledName "FSharpFirstByFields">]
|
||||||
|
let firstByFields<'TDoc> tableName howMatched fields =
|
||||||
|
WithProps.Find.firstByFields<'TDoc> tableName howMatched fields (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Retrieve the first document matching JSON field comparisons (<tt>->> =</tt>, etc.)</summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
/// <returns>The first document, or <tt>null</tt> if not found</returns>
|
||||||
|
let FirstByFields<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, howMatched, fields) =
|
||||||
|
WithProps.Find.FirstByFields<'TDoc>(tableName, howMatched, fields, fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve the first document matching JSON field comparisons (<tt>->> =</tt>, etc.) ordered by the given
|
||||||
|
/// fields in the document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="queryFields">The field conditions to match</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <tt>Some</tt> with the first document ordered by the given fields, or <tt>None</tt> if not found
|
||||||
|
/// </returns>
|
||||||
|
[<CompiledName "FSharpFirstByFieldsOrdered">]
|
||||||
|
let firstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields =
|
||||||
|
WithProps.Find.firstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve the first document matching JSON field comparisons (<tt>->> =</tt>, etc.) ordered by the given
|
||||||
|
/// fields in the document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="queryFields">The field conditions to match</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <returns>The first document ordered by the given fields, or <tt>null</tt> if not found</returns>
|
||||||
|
let FirstByFieldsOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>(
|
||||||
|
tableName, howMatched, queryFields, orderFields) =
|
||||||
|
WithProps.Find.FirstByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Retrieve the first document matching a JSON containment query (<tt>@></tt>)</summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="criteria">The document to match with the containment query</param>
|
||||||
|
/// <returns><tt>Some</tt> with the first document, or <tt>None</tt> if not found</returns>
|
||||||
|
[<CompiledName "FSharpFirstByContains">]
|
||||||
|
let firstByContains<'TDoc> tableName (criteria: obj) =
|
||||||
|
WithProps.Find.firstByContains<'TDoc> tableName criteria (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Retrieve the first document matching a JSON containment query (<tt>@></tt>)</summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="criteria">The document to match with the containment query</param>
|
||||||
|
/// <returns>The first document, or <tt>null</tt> if not found</returns>
|
||||||
|
let FirstByContains<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, criteria: obj) =
|
||||||
|
WithProps.Find.FirstByContains<'TDoc>(tableName, criteria, fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve the first document matching a JSON containment query (<tt>@></tt>) ordered by the given fields in
|
||||||
|
/// the document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="criteria">The document to match with the containment query</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <tt>Some</tt> with the first document ordered by the given fields, or <tt>None</tt> if not found
|
||||||
|
/// </returns>
|
||||||
|
[<CompiledName "FSharpFirstByContainsOrdered">]
|
||||||
|
let firstByContainsOrdered<'TDoc> tableName (criteria: obj) orderFields =
|
||||||
|
WithProps.Find.firstByContainsOrdered<'TDoc> tableName criteria orderFields (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve the first document matching a JSON containment query (<tt>@></tt>) ordered by the given fields in
|
||||||
|
/// the document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="criteria">The document to match with the containment query</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <returns>The first document ordered by the given fields, or <tt>null</tt> if not found</returns>
|
||||||
|
let FirstByContainsOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, criteria: obj, orderFields) =
|
||||||
|
WithProps.Find.FirstByContainsOrdered<'TDoc>(tableName, criteria, orderFields, fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Retrieve the first document matching a JSON Path match query (<tt>@?</tt>)</summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="jsonPath">The JSON Path expression to match</param>
|
||||||
|
/// <returns><tt>Some</tt> with the first document, or <tt>None</tt> if not found</returns>
|
||||||
|
[<CompiledName "FSharpFirstByJsonPath">]
|
||||||
|
let firstByJsonPath<'TDoc> tableName jsonPath =
|
||||||
|
WithProps.Find.firstByJsonPath<'TDoc> tableName jsonPath (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Retrieve the first document matching a JSON Path match query (<tt>@?</tt>)</summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="jsonPath">The JSON Path expression to match</param>
|
||||||
|
/// <returns>The first document, or <tt>null</tt> if not found</returns>
|
||||||
|
let FirstByJsonPath<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, jsonPath) =
|
||||||
|
WithProps.Find.FirstByJsonPath<'TDoc>(tableName, jsonPath, fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve the first document matching a JSON Path match query (<tt>@?</tt>) ordered by the given fields in the
|
||||||
|
/// document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="jsonPath">The JSON Path expression to match</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <tt>Some</tt> with the first document ordered by the given fields, or <tt>None</tt> if not found
|
||||||
|
/// </returns>
|
||||||
|
[<CompiledName "FSharpFirstByJsonPathOrdered">]
|
||||||
|
let firstByJsonPathOrdered<'TDoc> tableName jsonPath orderFields =
|
||||||
|
WithProps.Find.firstByJsonPathOrdered<'TDoc> tableName jsonPath orderFields (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve the first document matching a JSON Path match query (<tt>@?</tt>) ordered by the given fields in the
|
||||||
|
/// document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="jsonPath">The JSON Path expression to match</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <returns>The first document ordered by the given fields, or <tt>null</tt> if not found</returns>
|
||||||
|
let FirstByJsonPathOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, jsonPath, orderFields) =
|
||||||
|
WithProps.Find.FirstByJsonPathOrdered<'TDoc>(tableName, jsonPath, orderFields, fromDataSource ())
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>Commands to update documents</summary>
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Update =
|
||||||
|
|
||||||
|
/// <summary>Update (replace) an entire document by its ID</summary>
|
||||||
|
/// <param name="tableName">The table in which a document should be updated (may include schema)</param>
|
||||||
|
/// <param name="docId">The ID of the document to be updated (replaced)</param>
|
||||||
|
/// <param name="document">The new document</param>
|
||||||
|
[<CompiledName "ById">]
|
||||||
|
let byId tableName (docId: 'TKey) (document: 'TDoc) =
|
||||||
|
WithProps.Update.byId tableName docId document (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update (replace) an entire document by its ID, using the provided function to obtain the ID from the document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table in which a document should be updated (may include schema)</param>
|
||||||
|
/// <param name="idFunc">The function to obtain the ID of the document</param>
|
||||||
|
/// <param name="document">The new document</param>
|
||||||
|
[<CompiledName "FSharpFullFunc">]
|
||||||
|
let byFunc tableName (idFunc: 'TDoc -> 'TKey) (document: 'TDoc) =
|
||||||
|
WithProps.Update.byFunc tableName idFunc document (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update (replace) an entire document by its ID, using the provided function to obtain the ID from the document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table in which a document should be updated (may include schema)</param>
|
||||||
|
/// <param name="idFunc">The function to obtain the ID of the document</param>
|
||||||
|
/// <param name="document">The new document</param>
|
||||||
|
let ByFunc(tableName, idFunc: System.Func<'TDoc, 'TKey>, document: 'TDoc) =
|
||||||
|
WithProps.Update.ByFunc(tableName, idFunc, document, fromDataSource ())
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>Commands to patch (partially update) documents</summary>
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Patch =
|
||||||
|
|
||||||
|
/// <summary>Patch a document by its ID</summary>
|
||||||
|
/// <param name="tableName">The table in which a document should be patched (may include schema)</param>
|
||||||
|
/// <param name="docId">The ID of the document to patch</param>
|
||||||
|
/// <param name="patch">The partial document to patch the existing document</param>
|
||||||
|
[<CompiledName "ById">]
|
||||||
|
let byId tableName (docId: 'TKey) (patch: 'TPatch) =
|
||||||
|
WithProps.Patch.byId tableName docId patch (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Patch documents using a JSON field comparison query in the <tt>WHERE</tt> clause (<tt>->> =</tt>, etc.)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table in which documents should be patched (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
/// <param name="patch">The partial document to patch the existing document</param>
|
||||||
|
[<CompiledName "ByFields">]
|
||||||
|
let byFields tableName howMatched fields (patch: 'TPatch) =
|
||||||
|
WithProps.Patch.byFields tableName howMatched fields patch (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Patch documents using a JSON containment query in the <tt>WHERE</tt> clause (<tt>@></tt>)</summary>
|
||||||
|
/// <param name="tableName">The table in which documents should be patched (may include schema)</param>
|
||||||
|
/// <param name="criteria">The document to match the containment query</param>
|
||||||
|
/// <param name="patch">The partial document to patch the existing document</param>
|
||||||
|
[<CompiledName "ByContains">]
|
||||||
|
let byContains tableName (criteria: 'TCriteria) (patch: 'TPatch) =
|
||||||
|
WithProps.Patch.byContains tableName criteria patch (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Patch documents using a JSON Path match query in the <tt>WHERE</tt> clause (<tt>@?</tt>)</summary>
|
||||||
|
/// <param name="tableName">The table in which documents should be patched (may include schema)</param>
|
||||||
|
/// <param name="jsonPath">The JSON Path expression to match</param>
|
||||||
|
/// <param name="patch">The partial document to patch the existing document</param>
|
||||||
|
[<CompiledName "ByJsonPath">]
|
||||||
|
let byJsonPath tableName jsonPath (patch: 'TPatch) =
|
||||||
|
WithProps.Patch.byJsonPath tableName jsonPath patch (fromDataSource ())
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>Commands to remove fields from documents</summary>
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module RemoveFields =
|
||||||
|
|
||||||
|
/// <summary>Remove fields from a document by the document's ID</summary>
|
||||||
|
/// <param name="tableName">The table in which a document should be modified (may include schema)</param>
|
||||||
|
/// <param name="docId">The ID of the document to modify</param>
|
||||||
|
/// <param name="fieldNames">One or more field names to remove from the document</param>
|
||||||
|
[<CompiledName "ById">]
|
||||||
|
let byId tableName (docId: 'TKey) fieldNames =
|
||||||
|
WithProps.RemoveFields.byId tableName docId fieldNames (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Remove fields from documents via a comparison on JSON fields in the document</summary>
|
||||||
|
/// <param name="tableName">The table in which documents should be modified (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
/// <param name="fieldNames">One or more field names to remove from the matching documents</param>
|
||||||
|
[<CompiledName "ByFields">]
|
||||||
|
let byFields tableName howMatched fields fieldNames =
|
||||||
|
WithProps.RemoveFields.byFields tableName howMatched fields fieldNames (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Remove fields from documents via a JSON containment query (<tt>@></tt>)</summary>
|
||||||
|
/// <param name="tableName">The table in which documents should be modified (may include schema)</param>
|
||||||
|
/// <param name="criteria">The document to match the containment query</param>
|
||||||
|
/// <param name="fieldNames">One or more field names to remove from the matching documents</param>
|
||||||
|
[<CompiledName "ByContains">]
|
||||||
|
let byContains tableName (criteria: 'TContains) fieldNames =
|
||||||
|
WithProps.RemoveFields.byContains tableName criteria fieldNames (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Remove fields from documents via a JSON Path match query (<tt>@?</tt>)</summary>
|
||||||
|
/// <param name="tableName">The table in which documents should be modified (may include schema)</param>
|
||||||
|
/// <param name="jsonPath">The JSON Path expression to match</param>
|
||||||
|
/// <param name="fieldNames">One or more field names to remove from the matching documents</param>
|
||||||
|
[<CompiledName "ByJsonPath">]
|
||||||
|
let byJsonPath tableName jsonPath fieldNames =
|
||||||
|
WithProps.RemoveFields.byJsonPath tableName jsonPath fieldNames (fromDataSource ())
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>Commands to delete documents</summary>
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Delete =
|
||||||
|
|
||||||
|
/// <summary>Delete a document by its ID</summary>
|
||||||
|
/// <param name="tableName">The table in which a document should be deleted (may include schema)</param>
|
||||||
|
/// <param name="docId">The ID of the document to delete</param>
|
||||||
|
[<CompiledName "ById">]
|
||||||
|
let byId tableName (docId: 'TKey) =
|
||||||
|
WithProps.Delete.byId tableName docId (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Delete documents by matching a JSON field comparison query (<tt>->> =</tt>, etc.)</summary>
|
||||||
|
/// <param name="tableName">The table in which documents should be deleted (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
[<CompiledName "ByFields">]
|
||||||
|
let byFields tableName howMatched fields =
|
||||||
|
WithProps.Delete.byFields tableName howMatched fields (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Delete documents by matching a JSON contains query (<tt>@></tt>)</summary>
|
||||||
|
/// <param name="tableName">The table in which documents should be deleted (may include schema)</param>
|
||||||
|
/// <param name="criteria">The document to match the containment query</param>
|
||||||
|
[<CompiledName "ByContains">]
|
||||||
|
let byContains tableName (criteria: 'TContains) =
|
||||||
|
WithProps.Delete.byContains tableName criteria (fromDataSource ())
|
||||||
|
|
||||||
|
/// <summary>Delete documents by matching a JSON Path match query (@?)</summary>
|
||||||
|
/// <param name="tableName">The table in which documents should be deleted (may include schema)</param>
|
||||||
|
/// <param name="jsonPath">The JSON Path expression to match</param>
|
||||||
|
[<CompiledName "ByJsonPath">]
|
||||||
|
let byJsonPath tableName jsonPath =
|
||||||
|
WithProps.Delete.byJsonPath tableName jsonPath (fromDataSource ())
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -5,11 +5,16 @@ 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:
|
||||||
@@ -66,7 +71,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 in the database)_
|
_(keys are treated as strings or numbers depending on their defintion; however, they are indexed as strings)_
|
||||||
|
|
||||||
Count customers in Atlanta (using JSON containment):
|
Count customers in Atlanta (using JSON containment):
|
||||||
|
|
||||||
|
|||||||
840
src/Postgres/WithProps.fs
Normal file
840
src/Postgres/WithProps.fs
Normal file
@@ -0,0 +1,840 @@
|
|||||||
|
/// <summary>Versions of queries that accept <tt>SqlProps</tt> as the last parameter</summary>
|
||||||
|
module BitBadger.Documents.Postgres.WithProps
|
||||||
|
|
||||||
|
open BitBadger.Documents
|
||||||
|
open Npgsql.FSharp
|
||||||
|
|
||||||
|
/// <summary>Commands to execute custom SQL queries</summary>
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Custom =
|
||||||
|
|
||||||
|
module FSharpList = Microsoft.FSharp.Collections.List
|
||||||
|
|
||||||
|
/// <summary>Execute a query that returns a list of results</summary>
|
||||||
|
/// <param name="query">The query to retrieve the results</param>
|
||||||
|
/// <param name="parameters">Parameters to use for the query</param>
|
||||||
|
/// <param name="mapFunc">The mapping function between the document and the domain item</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>A list of results for the given query</returns>
|
||||||
|
[<CompiledName "FSharpList">]
|
||||||
|
let list<'TDoc> query parameters (mapFunc: RowReader -> 'TDoc) sqlProps =
|
||||||
|
Sql.query query sqlProps
|
||||||
|
|> Sql.parameters (FSharpList.ofSeq parameters)
|
||||||
|
|> Sql.executeAsync mapFunc
|
||||||
|
|
||||||
|
/// <summary>Execute a query that returns a list of results</summary>
|
||||||
|
/// <param name="query">The query to retrieve the results</param>
|
||||||
|
/// <param name="parameters">Parameters to use for the query</param>
|
||||||
|
/// <param name="mapFunc">The mapping function between the document and the domain item</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>A list of results for the given query</returns>
|
||||||
|
let List<'TDoc>(query, parameters, mapFunc: System.Func<RowReader, 'TDoc>, sqlProps) = backgroundTask {
|
||||||
|
let! results = list<'TDoc> query parameters mapFunc.Invoke sqlProps
|
||||||
|
return ResizeArray results
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Execute a query that returns one or no results</summary>
|
||||||
|
/// <param name="query">The query to retrieve the results</param>
|
||||||
|
/// <param name="parameters">Parameters to use for the query</param>
|
||||||
|
/// <param name="mapFunc">The mapping function between the document and the domain item</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns><tt>Some</tt> with the first matching result, or <tt>None</tt> if not found</returns>
|
||||||
|
[<CompiledName "FSharpSingle">]
|
||||||
|
let single<'TDoc> query parameters mapFunc sqlProps = backgroundTask {
|
||||||
|
let! results = list<'TDoc> query parameters mapFunc sqlProps
|
||||||
|
return FSharpList.tryHead results
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Execute a query that returns one or no results</summary>
|
||||||
|
/// <param name="query">The query to retrieve the results</param>
|
||||||
|
/// <param name="parameters">Parameters to use for the query</param>
|
||||||
|
/// <param name="mapFunc">The mapping function between the document and the domain item</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>The first matching result, or <tt>null</tt> if not found</returns>
|
||||||
|
let Single<'TDoc when 'TDoc: null and 'TDoc: not struct>(
|
||||||
|
query, parameters, mapFunc: System.Func<RowReader, 'TDoc>, sqlProps) = backgroundTask {
|
||||||
|
let! result = single<'TDoc> query parameters mapFunc.Invoke sqlProps
|
||||||
|
return Option.toObj result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Execute a query that returns no results</summary>
|
||||||
|
/// <param name="query">The query to retrieve the results</param>
|
||||||
|
/// <param name="parameters">Parameters to use for the query</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
[<CompiledName "NonQuery">]
|
||||||
|
let nonQuery query parameters sqlProps =
|
||||||
|
Sql.query query sqlProps
|
||||||
|
|> Sql.parameters (FSharpList.ofSeq parameters)
|
||||||
|
|> Sql.executeNonQueryAsync
|
||||||
|
|> ignoreTask
|
||||||
|
|
||||||
|
/// <summary>Execute a query that returns a scalar value</summary>
|
||||||
|
/// <param name="query">The query to retrieve the value</param>
|
||||||
|
/// <param name="parameters">Parameters to use for the query</param>
|
||||||
|
/// <param name="mapFunc">The mapping function to obtain the value</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>The scalar value for the query</returns>
|
||||||
|
[<CompiledName "FSharpScalar">]
|
||||||
|
let scalar<'T when 'T: struct> query parameters (mapFunc: RowReader -> 'T) sqlProps =
|
||||||
|
Sql.query query sqlProps
|
||||||
|
|> Sql.parameters (FSharpList.ofSeq parameters)
|
||||||
|
|> Sql.executeRowAsync mapFunc
|
||||||
|
|
||||||
|
/// <summary>Execute a query that returns a scalar value</summary>
|
||||||
|
/// <param name="query">The query to retrieve the value</param>
|
||||||
|
/// <param name="parameters">Parameters to use for the query</param>
|
||||||
|
/// <param name="mapFunc">The mapping function to obtain the value</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>The scalar value for the query</returns>
|
||||||
|
let Scalar<'T when 'T: struct>(query, parameters, mapFunc: System.Func<RowReader, 'T>, sqlProps) =
|
||||||
|
scalar<'T> query parameters mapFunc.Invoke sqlProps
|
||||||
|
|
||||||
|
/// <summary>Table and index definition commands</summary>
|
||||||
|
module Definition =
|
||||||
|
|
||||||
|
/// <summary>Create a document table</summary>
|
||||||
|
/// <param name="name">The table whose existence should be ensured (may include schema)</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
[<CompiledName "EnsureTable">]
|
||||||
|
let ensureTable name sqlProps = backgroundTask {
|
||||||
|
do! Custom.nonQuery (Query.Definition.ensureTable name) [] sqlProps
|
||||||
|
do! Custom.nonQuery (Query.Definition.ensureKey name PostgreSQL) [] sqlProps
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Create an index on documents in the specified table</summary>
|
||||||
|
/// <param name="name">The table to be indexed (may include schema)</param>
|
||||||
|
/// <param name="idxType">The type of document index to create</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
[<CompiledName "EnsureDocumentIndex">]
|
||||||
|
let ensureDocumentIndex name idxType sqlProps =
|
||||||
|
Custom.nonQuery (Query.Definition.ensureDocumentIndex name idxType) [] sqlProps
|
||||||
|
|
||||||
|
/// <summary>Create an index on field(s) within documents in the specified table</summary>
|
||||||
|
/// <param name="tableName">The table to be indexed (may include schema)</param>
|
||||||
|
/// <param name="indexName">The name of the index to create</param>
|
||||||
|
/// <param name="fields">One or more fields to be indexed</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
[<CompiledName "EnsureFieldIndex">]
|
||||||
|
let ensureFieldIndex tableName indexName fields sqlProps =
|
||||||
|
Custom.nonQuery (Query.Definition.ensureIndexOn tableName indexName fields PostgreSQL) [] sqlProps
|
||||||
|
|
||||||
|
/// <summary>Commands to add documents</summary>
|
||||||
|
[<AutoOpen>]
|
||||||
|
module Document =
|
||||||
|
|
||||||
|
/// <summary>Insert a new document</summary>
|
||||||
|
/// <param name="tableName">The table into which the document should be inserted (may include schema)</param>
|
||||||
|
/// <param name="document">The document to be inserted</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
[<CompiledName "Insert">]
|
||||||
|
let insert<'TDoc> tableName (document: 'TDoc) sqlProps =
|
||||||
|
let query =
|
||||||
|
match Configuration.autoIdStrategy () with
|
||||||
|
| Disabled -> Query.insert tableName
|
||||||
|
| strategy ->
|
||||||
|
let idField = Configuration.idField ()
|
||||||
|
let dataParam =
|
||||||
|
if AutoId.NeedsAutoId strategy document idField then
|
||||||
|
match strategy with
|
||||||
|
| Number ->
|
||||||
|
$"' || (SELECT COALESCE(MAX((data->>'{idField}')::numeric), 0) + 1 FROM {tableName}) || '"
|
||||||
|
| Guid -> $"\"{AutoId.GenerateGuid()}\""
|
||||||
|
| RandomString -> $"\"{AutoId.GenerateRandomString(Configuration.idStringLength ())}\""
|
||||||
|
| Disabled -> "@data"
|
||||||
|
|> function it -> $"""@data::jsonb || ('{{"{idField}":{it}}}')::jsonb"""
|
||||||
|
else "@data"
|
||||||
|
(Query.insert tableName).Replace("@data", dataParam)
|
||||||
|
Custom.nonQuery query [ jsonParam "@data" document ] sqlProps
|
||||||
|
|
||||||
|
/// <summary>Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")</summary>
|
||||||
|
/// <param name="tableName">The table into which the document should be saved (may include schema)</param>
|
||||||
|
/// <param name="document">The document to be saved</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
[<CompiledName "Save">]
|
||||||
|
let save<'TDoc> tableName (document: 'TDoc) sqlProps =
|
||||||
|
Custom.nonQuery (Query.save tableName) [ jsonParam "@data" document ] sqlProps
|
||||||
|
|
||||||
|
/// <summary>Commands to count documents</summary>
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Count =
|
||||||
|
|
||||||
|
/// <summary>Count all documents in a table</summary>
|
||||||
|
/// <param name="tableName">The table in which documents should be counted (may include schema)</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>The count of the documents in the table</returns>
|
||||||
|
[<CompiledName "All">]
|
||||||
|
let all tableName sqlProps =
|
||||||
|
Custom.scalar (Query.count tableName) [] toCount sqlProps
|
||||||
|
|
||||||
|
/// <summary>Count matching documents using JSON field comparisons (<tt>->> =</tt>, etc.)</summary>
|
||||||
|
/// <param name="tableName">The table in which documents should be counted (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>The count of matching documents in the table</returns>
|
||||||
|
[<CompiledName "ByFields">]
|
||||||
|
let byFields tableName howMatched fields sqlProps =
|
||||||
|
Custom.scalar
|
||||||
|
(Query.byFields (Query.count tableName) howMatched fields) (addFieldParams fields []) toCount sqlProps
|
||||||
|
|
||||||
|
/// <summary>Count matching documents using a JSON containment query (<tt>@></tt>)</summary>
|
||||||
|
/// <param name="tableName">The table in which documents should be counted (may include schema)</param>
|
||||||
|
/// <param name="criteria">The document to match with the containment query</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>The count of the documents in the table</returns>
|
||||||
|
[<CompiledName "ByContains">]
|
||||||
|
let byContains tableName (criteria: 'TContains) sqlProps =
|
||||||
|
Custom.scalar
|
||||||
|
(Query.byContains (Query.count tableName)) [ jsonParam "@criteria" criteria ] toCount sqlProps
|
||||||
|
|
||||||
|
/// <summary>Count matching documents using a JSON Path match query (<tt>@?</tt>)</summary>
|
||||||
|
/// <param name="tableName">The table in which documents should be counted (may include schema)</param>
|
||||||
|
/// <param name="jsonPath">The JSON Path expression to be matched</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>The count of the documents in the table</returns>
|
||||||
|
[<CompiledName "ByJsonPath">]
|
||||||
|
let byJsonPath tableName jsonPath sqlProps =
|
||||||
|
Custom.scalar
|
||||||
|
(Query.byPathMatch (Query.count tableName)) [ "@path", Sql.string jsonPath ] toCount sqlProps
|
||||||
|
|
||||||
|
/// <summary>Commands to determine if documents exist</summary>
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Exists =
|
||||||
|
|
||||||
|
/// <summary>Determine if a document exists for the given ID</summary>
|
||||||
|
/// <param name="tableName">The table in which existence should be checked (may include schema)</param>
|
||||||
|
/// <param name="docId">The ID of the document whose existence should be checked</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>True if a document exists, false if not</returns>
|
||||||
|
[<CompiledName "ById">]
|
||||||
|
let byId tableName (docId: 'TKey) sqlProps =
|
||||||
|
Custom.scalar (Query.exists tableName (Query.whereById docId)) [ idParam docId ] toExists sqlProps
|
||||||
|
|
||||||
|
/// <summary>Determine if a document exists using JSON field comparisons (<tt>->> =</tt>, etc.)</summary>
|
||||||
|
/// <param name="tableName">The table in which existence should be checked (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>True if any matching documents exist, false if not</returns>
|
||||||
|
[<CompiledName "ByFields">]
|
||||||
|
let byFields tableName howMatched fields sqlProps =
|
||||||
|
Custom.scalar
|
||||||
|
(Query.exists tableName (Query.whereByFields howMatched fields))
|
||||||
|
(addFieldParams fields [])
|
||||||
|
toExists
|
||||||
|
sqlProps
|
||||||
|
|
||||||
|
/// <summary>Determine if a document exists using a JSON containment query (<tt>@></tt>)</summary>
|
||||||
|
/// <param name="tableName">The table in which existence should be checked (may include schema)</param>
|
||||||
|
/// <param name="criteria">The document to match with the containment query</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>True if any matching documents exist, false if not</returns>
|
||||||
|
[<CompiledName "ByContains">]
|
||||||
|
let byContains tableName (criteria: 'TContains) sqlProps =
|
||||||
|
Custom.scalar
|
||||||
|
(Query.exists tableName (Query.whereDataContains "@criteria"))
|
||||||
|
[ jsonParam "@criteria" criteria ]
|
||||||
|
toExists
|
||||||
|
sqlProps
|
||||||
|
|
||||||
|
/// <summary>Determine if a document exists using a JSON Path match query (<tt>@?</tt>)</summary>
|
||||||
|
/// <param name="tableName">The table in which existence should be checked (may include schema)</param>
|
||||||
|
/// <param name="jsonPath">The JSON Path expression to be matched</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>True if any matching documents exist, false if not</returns>
|
||||||
|
[<CompiledName "ByJsonPath">]
|
||||||
|
let byJsonPath tableName jsonPath sqlProps =
|
||||||
|
Custom.scalar
|
||||||
|
(Query.exists tableName (Query.whereJsonPathMatches "@path"))
|
||||||
|
[ "@path", Sql.string jsonPath ]
|
||||||
|
toExists
|
||||||
|
sqlProps
|
||||||
|
|
||||||
|
/// <summary>Commands to retrieve documents</summary>
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Find =
|
||||||
|
|
||||||
|
/// <summary>Retrieve all documents in the given table</summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>All documents from the given table</returns>
|
||||||
|
[<CompiledName "FSharpAll">]
|
||||||
|
let all<'TDoc> tableName sqlProps =
|
||||||
|
Custom.list<'TDoc> (Query.find tableName) [] fromData<'TDoc> sqlProps
|
||||||
|
|
||||||
|
/// <summary>Retrieve all documents in the given table</summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>All documents from the given table</returns>
|
||||||
|
let All<'TDoc>(tableName, sqlProps) =
|
||||||
|
Custom.List<'TDoc>(Query.find tableName, [], fromData<'TDoc>, sqlProps)
|
||||||
|
|
||||||
|
/// <summary>Retrieve all documents in the given table ordered by the given fields in the document</summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>All documents from the given table, ordered by the given fields</returns>
|
||||||
|
[<CompiledName "FSharpAllOrdered">]
|
||||||
|
let allOrdered<'TDoc> tableName orderFields sqlProps =
|
||||||
|
Custom.list<'TDoc> (Query.find tableName + Query.orderBy orderFields PostgreSQL) [] fromData<'TDoc> sqlProps
|
||||||
|
|
||||||
|
/// <summary>Retrieve all documents in the given table ordered by the given fields in the document</summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>All documents from the given table, ordered by the given fields</returns>
|
||||||
|
let AllOrdered<'TDoc>(tableName, orderFields, sqlProps) =
|
||||||
|
Custom.List<'TDoc>(
|
||||||
|
Query.find tableName + Query.orderBy orderFields PostgreSQL, [], fromData<'TDoc>, sqlProps)
|
||||||
|
|
||||||
|
/// <summary>Retrieve a document by its ID</summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="docId">The ID of the document to retrieve</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns><tt>Some</tt> with the document if found, <tt>None</tt> otherwise</returns>
|
||||||
|
[<CompiledName "FSharpById">]
|
||||||
|
let byId<'TKey, 'TDoc> tableName (docId: 'TKey) sqlProps =
|
||||||
|
Custom.single (Query.byId (Query.find tableName) docId) [ idParam docId ] fromData<'TDoc> sqlProps
|
||||||
|
|
||||||
|
/// <summary>Retrieve a document by its ID</summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="docId">The ID of the document to retrieve</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>The document if found, <tt>null</tt> otherwise</returns>
|
||||||
|
let ById<'TKey, 'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, docId: 'TKey, sqlProps) =
|
||||||
|
Custom.Single<'TDoc>(
|
||||||
|
Query.byId (Query.find tableName) docId, [ idParam docId ], fromData<'TDoc>, sqlProps)
|
||||||
|
|
||||||
|
/// <summary>Retrieve documents matching JSON field comparisons (<tt>->> =</tt>, etc.)</summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>All documents matching the given fields</returns>
|
||||||
|
[<CompiledName "FSharpByFields">]
|
||||||
|
let byFields<'TDoc> tableName howMatched fields sqlProps =
|
||||||
|
Custom.list<'TDoc>
|
||||||
|
(Query.byFields (Query.find tableName) howMatched fields)
|
||||||
|
(addFieldParams fields [])
|
||||||
|
fromData<'TDoc>
|
||||||
|
sqlProps
|
||||||
|
|
||||||
|
/// <summary>Retrieve documents matching JSON field comparisons (<tt>->> =</tt>, etc.)</summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>All documents matching the given fields</returns>
|
||||||
|
let ByFields<'TDoc>(tableName, howMatched, fields, sqlProps) =
|
||||||
|
Custom.List<'TDoc>(
|
||||||
|
Query.byFields (Query.find tableName) howMatched fields,
|
||||||
|
addFieldParams fields [],
|
||||||
|
fromData<'TDoc>,
|
||||||
|
sqlProps)
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve documents matching JSON field comparisons (<tt>->> =</tt>, etc.) ordered by the given fields in
|
||||||
|
/// the document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="queryFields">The field conditions to match</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>All documents matching the given fields, ordered by the other given fields</returns>
|
||||||
|
[<CompiledName "FSharpByFieldsOrdered">]
|
||||||
|
let byFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields sqlProps =
|
||||||
|
Custom.list<'TDoc>
|
||||||
|
(Query.byFields (Query.find tableName) howMatched queryFields + Query.orderBy orderFields PostgreSQL)
|
||||||
|
(addFieldParams queryFields [])
|
||||||
|
fromData<'TDoc>
|
||||||
|
sqlProps
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve documents matching JSON field comparisons (<tt>->> =</tt>, etc.) ordered by the given fields in
|
||||||
|
/// the document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="queryFields">The field conditions to match</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>All documents matching the given fields, ordered by the other given fields</returns>
|
||||||
|
let ByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, sqlProps) =
|
||||||
|
Custom.List<'TDoc>(
|
||||||
|
Query.byFields (Query.find tableName) howMatched queryFields + Query.orderBy orderFields PostgreSQL,
|
||||||
|
addFieldParams queryFields [],
|
||||||
|
fromData<'TDoc>,
|
||||||
|
sqlProps)
|
||||||
|
|
||||||
|
/// <summary>Retrieve documents matching a JSON containment query (<tt>@></tt>)</summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="criteria">The document to match with the containment query</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>All documents matching the given containment query</returns>
|
||||||
|
[<CompiledName "FSharpByContains">]
|
||||||
|
let byContains<'TDoc> tableName (criteria: obj) sqlProps =
|
||||||
|
Custom.list<'TDoc>
|
||||||
|
(Query.byContains (Query.find tableName)) [ jsonParam "@criteria" criteria ] fromData<'TDoc> sqlProps
|
||||||
|
|
||||||
|
/// <summary>Retrieve documents matching a JSON containment query (<tt>@></tt>)</summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="criteria">The document to match with the containment query</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>All documents matching the given containment query</returns>
|
||||||
|
let ByContains<'TDoc>(tableName, criteria: obj, sqlProps) =
|
||||||
|
Custom.List<'TDoc>(
|
||||||
|
Query.byContains (Query.find tableName),
|
||||||
|
[ jsonParam "@criteria" criteria ],
|
||||||
|
fromData<'TDoc>,
|
||||||
|
sqlProps)
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve documents matching a JSON containment query (<tt>@></tt>) ordered by the given fields in the
|
||||||
|
/// document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="criteria">The document to match with the containment query</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>All documents matching the given containment query, ordered by the given fields</returns>
|
||||||
|
[<CompiledName "FSharpByContainsOrdered">]
|
||||||
|
let byContainsOrdered<'TDoc> tableName (criteria: obj) orderFields sqlProps =
|
||||||
|
Custom.list<'TDoc>
|
||||||
|
(Query.byContains (Query.find tableName) + Query.orderBy orderFields PostgreSQL)
|
||||||
|
[ jsonParam "@criteria" criteria ]
|
||||||
|
fromData<'TDoc>
|
||||||
|
sqlProps
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve documents matching a JSON containment query (<tt>@></tt>) ordered by the given fields in the
|
||||||
|
/// document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="criteria">The document to match with the containment query</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>All documents matching the given containment query, ordered by the given fields</returns>
|
||||||
|
let ByContainsOrdered<'TDoc>(tableName, criteria: obj, orderFields, sqlProps) =
|
||||||
|
Custom.List<'TDoc>(
|
||||||
|
Query.byContains (Query.find tableName) + Query.orderBy orderFields PostgreSQL,
|
||||||
|
[ jsonParam "@criteria" criteria ],
|
||||||
|
fromData<'TDoc>,
|
||||||
|
sqlProps)
|
||||||
|
|
||||||
|
/// <summary>Retrieve documents matching a JSON Path match query (<tt>@?</tt>)</summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="jsonPath">The JSON Path expression to match</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>All documents matching the given JSON Path expression</returns>
|
||||||
|
[<CompiledName "FSharpByJsonPath">]
|
||||||
|
let byJsonPath<'TDoc> tableName jsonPath sqlProps =
|
||||||
|
Custom.list<'TDoc>
|
||||||
|
(Query.byPathMatch (Query.find tableName)) [ "@path", Sql.string jsonPath ] fromData<'TDoc> sqlProps
|
||||||
|
|
||||||
|
/// <summary>Retrieve documents matching a JSON Path match query (<tt>@?</tt>)</summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="jsonPath">The JSON Path expression to match</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>All documents matching the given JSON Path expression</returns>
|
||||||
|
let ByJsonPath<'TDoc>(tableName, jsonPath, sqlProps) =
|
||||||
|
Custom.List<'TDoc>(
|
||||||
|
Query.byPathMatch (Query.find tableName),
|
||||||
|
[ "@path", Sql.string jsonPath ],
|
||||||
|
fromData<'TDoc>,
|
||||||
|
sqlProps)
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve documents matching a JSON Path match query (<tt>@?</tt>) ordered by the given fields in the document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="jsonPath">The JSON Path expression to match</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>All documents matching the given JSON Path expression, ordered by the given fields</returns>
|
||||||
|
[<CompiledName "FSharpByJsonPathOrdered">]
|
||||||
|
let byJsonPathOrdered<'TDoc> tableName jsonPath orderFields sqlProps =
|
||||||
|
Custom.list<'TDoc>
|
||||||
|
(Query.byPathMatch (Query.find tableName) + Query.orderBy orderFields PostgreSQL)
|
||||||
|
[ "@path", Sql.string jsonPath ]
|
||||||
|
fromData<'TDoc>
|
||||||
|
sqlProps
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve documents matching a JSON Path match query (<tt>@?</tt>) ordered by the given fields in the document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="jsonPath">The JSON Path expression to match</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>All documents matching the given JSON Path expression, ordered by the given fields</returns>
|
||||||
|
let ByJsonPathOrdered<'TDoc>(tableName, jsonPath, orderFields, sqlProps) =
|
||||||
|
Custom.List<'TDoc>(
|
||||||
|
Query.byPathMatch (Query.find tableName) + Query.orderBy orderFields PostgreSQL,
|
||||||
|
[ "@path", Sql.string jsonPath ],
|
||||||
|
fromData<'TDoc>,
|
||||||
|
sqlProps)
|
||||||
|
|
||||||
|
/// <summary>Retrieve the first document matching JSON field comparisons (<tt>->> =</tt>, etc.)</summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns><tt>Some</tt> with the first document, or <tt>None</tt> if not found</returns>
|
||||||
|
[<CompiledName "FSharpFirstByFields">]
|
||||||
|
let firstByFields<'TDoc> tableName howMatched fields sqlProps =
|
||||||
|
Custom.single<'TDoc>
|
||||||
|
$"{Query.byFields (Query.find tableName) howMatched fields} LIMIT 1"
|
||||||
|
(addFieldParams fields [])
|
||||||
|
fromData<'TDoc>
|
||||||
|
sqlProps
|
||||||
|
|
||||||
|
/// <summary>Retrieve the first document matching JSON field comparisons (<tt>->> =</tt>, etc.)</summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>The first document, or <tt>null</tt> if not found</returns>
|
||||||
|
let FirstByFields<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, howMatched, fields, sqlProps) =
|
||||||
|
Custom.Single<'TDoc>(
|
||||||
|
$"{Query.byFields (Query.find tableName) howMatched fields} LIMIT 1",
|
||||||
|
addFieldParams fields [],
|
||||||
|
fromData<'TDoc>,
|
||||||
|
sqlProps)
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve the first document matching JSON field comparisons (<tt>->> =</tt>, etc.) ordered by the given
|
||||||
|
/// fields in the document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="queryFields">The field conditions to match</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <tt>Some</tt> with the first document ordered by the given fields, or <tt>None</tt> if not found
|
||||||
|
/// </returns>
|
||||||
|
[<CompiledName "FSharpFirstByFieldsOrdered">]
|
||||||
|
let firstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields sqlProps =
|
||||||
|
Custom.single<'TDoc>
|
||||||
|
$"{Query.byFields (Query.find tableName) howMatched queryFields}{Query.orderBy orderFields PostgreSQL} LIMIT 1"
|
||||||
|
(addFieldParams queryFields [])
|
||||||
|
fromData<'TDoc>
|
||||||
|
sqlProps
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve the first document matching JSON field comparisons (<tt>->> =</tt>, etc.) ordered by the given
|
||||||
|
/// fields in the document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="queryFields">The field conditions to match</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>The first document ordered by the given fields, or <tt>null</tt> if not found</returns>
|
||||||
|
let FirstByFieldsOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>(
|
||||||
|
tableName, howMatched, queryFields, orderFields, sqlProps) =
|
||||||
|
Custom.Single<'TDoc>(
|
||||||
|
$"{Query.byFields (Query.find tableName) howMatched queryFields}{Query.orderBy orderFields PostgreSQL} LIMIT 1",
|
||||||
|
addFieldParams queryFields [],
|
||||||
|
fromData<'TDoc>,
|
||||||
|
sqlProps)
|
||||||
|
|
||||||
|
/// <summary>Retrieve the first document matching a JSON containment query (<tt>@></tt>)</summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="criteria">The document to match with the containment query</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns><tt>Some</tt> with the first document, or <tt>None</tt> if not found</returns>
|
||||||
|
[<CompiledName "FSharpFirstByContains">]
|
||||||
|
let firstByContains<'TDoc> tableName (criteria: obj) sqlProps =
|
||||||
|
Custom.single<'TDoc>
|
||||||
|
$"{Query.byContains (Query.find tableName)} LIMIT 1"
|
||||||
|
[ jsonParam "@criteria" criteria ]
|
||||||
|
fromData<'TDoc>
|
||||||
|
sqlProps
|
||||||
|
|
||||||
|
/// <summary>Retrieve the first document matching a JSON containment query (<tt>@></tt>)</summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="criteria">The document to match with the containment query</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>The first document, or <tt>null</tt> if not found</returns>
|
||||||
|
let FirstByContains<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, criteria: obj, sqlProps) =
|
||||||
|
Custom.Single<'TDoc>(
|
||||||
|
$"{Query.byContains (Query.find tableName)} LIMIT 1",
|
||||||
|
[ jsonParam "@criteria" criteria ],
|
||||||
|
fromData<'TDoc>,
|
||||||
|
sqlProps)
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve the first document matching a JSON containment query (<tt>@></tt>) ordered by the given fields in
|
||||||
|
/// the document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="criteria">The document to match with the containment query</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <tt>Some</tt> with the first document ordered by the given fields, or <tt>None</tt> if not found
|
||||||
|
/// </returns>
|
||||||
|
[<CompiledName "FSharpFirstByContainsOrdered">]
|
||||||
|
let firstByContainsOrdered<'TDoc> tableName (criteria: obj) orderFields sqlProps =
|
||||||
|
Custom.single<'TDoc>
|
||||||
|
$"{Query.byContains (Query.find tableName)}{Query.orderBy orderFields PostgreSQL} LIMIT 1"
|
||||||
|
[ jsonParam "@criteria" criteria ]
|
||||||
|
fromData<'TDoc>
|
||||||
|
sqlProps
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve the first document matching a JSON containment query (<tt>@></tt>) ordered by the given fields in
|
||||||
|
/// the document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="criteria">The document to match with the containment query</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>The first document ordered by the given fields, or <tt>null</tt> if not found</returns>
|
||||||
|
let FirstByContainsOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>(
|
||||||
|
tableName, criteria: obj, orderFields, sqlProps) =
|
||||||
|
Custom.Single<'TDoc>(
|
||||||
|
$"{Query.byContains (Query.find tableName)}{Query.orderBy orderFields PostgreSQL} LIMIT 1",
|
||||||
|
[ jsonParam "@criteria" criteria ],
|
||||||
|
fromData<'TDoc>,
|
||||||
|
sqlProps)
|
||||||
|
|
||||||
|
/// <summary>Retrieve the first document matching a JSON Path match query (<tt>@?</tt>)</summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="jsonPath">The JSON Path expression to match</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns><tt>Some</tt> with the first document, or <tt>None</tt> if not found</returns>
|
||||||
|
[<CompiledName "FSharpFirstByJsonPath">]
|
||||||
|
let firstByJsonPath<'TDoc> tableName jsonPath sqlProps =
|
||||||
|
Custom.single<'TDoc>
|
||||||
|
$"{Query.byPathMatch (Query.find tableName)} LIMIT 1"
|
||||||
|
[ "@path", Sql.string jsonPath ]
|
||||||
|
fromData<'TDoc>
|
||||||
|
sqlProps
|
||||||
|
|
||||||
|
/// <summary>Retrieve the first document matching a JSON Path match query (<tt>@?</tt>)</summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="jsonPath">The JSON Path expression to match</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>The first document, or <tt>null</tt> if not found</returns>
|
||||||
|
let FirstByJsonPath<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, jsonPath, sqlProps) =
|
||||||
|
Custom.Single<'TDoc>(
|
||||||
|
$"{Query.byPathMatch (Query.find tableName)} LIMIT 1",
|
||||||
|
[ "@path", Sql.string jsonPath ],
|
||||||
|
fromData<'TDoc>,
|
||||||
|
sqlProps)
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve the first document matching a JSON Path match query (<tt>@?</tt>) ordered by the given fields in the
|
||||||
|
/// document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="jsonPath">The JSON Path expression to match</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <tt>Some</tt> with the first document ordered by the given fields, or <tt>None</tt> if not found
|
||||||
|
/// </returns>
|
||||||
|
[<CompiledName "FSharpFirstByJsonPathOrdered">]
|
||||||
|
let firstByJsonPathOrdered<'TDoc> tableName jsonPath orderFields sqlProps =
|
||||||
|
Custom.single<'TDoc>
|
||||||
|
$"{Query.byPathMatch (Query.find tableName)}{Query.orderBy orderFields PostgreSQL} LIMIT 1"
|
||||||
|
[ "@path", Sql.string jsonPath ]
|
||||||
|
fromData<'TDoc>
|
||||||
|
sqlProps
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve the first document matching a JSON Path match query (<tt>@?</tt>) ordered by the given fields in the
|
||||||
|
/// document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="jsonPath">The JSON Path expression to match</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
/// <returns>The first document ordered by the given fields, or <tt>null</tt> if not found</returns>
|
||||||
|
let FirstByJsonPathOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>(
|
||||||
|
tableName, jsonPath, orderFields, sqlProps) =
|
||||||
|
Custom.Single<'TDoc>(
|
||||||
|
$"{Query.byPathMatch (Query.find tableName)}{Query.orderBy orderFields PostgreSQL} LIMIT 1",
|
||||||
|
[ "@path", Sql.string jsonPath ],
|
||||||
|
fromData<'TDoc>,
|
||||||
|
sqlProps)
|
||||||
|
|
||||||
|
/// <summary>Commands to update documents</summary>
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Update =
|
||||||
|
|
||||||
|
/// <summary>Update (replace) an entire document by its ID</summary>
|
||||||
|
/// <param name="tableName">The table in which a document should be updated (may include schema)</param>
|
||||||
|
/// <param name="docId">The ID of the document to be updated (replaced)</param>
|
||||||
|
/// <param name="document">The new document</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
[<CompiledName "ById">]
|
||||||
|
let byId tableName (docId: 'TKey) (document: 'TDoc) sqlProps =
|
||||||
|
Custom.nonQuery
|
||||||
|
(Query.byId (Query.update tableName) docId) [ idParam docId; jsonParam "@data" document ] sqlProps
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update (replace) an entire document by its ID, using the provided function to obtain the ID from the document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table in which a document should be updated (may include schema)</param>
|
||||||
|
/// <param name="idFunc">The function to obtain the ID of the document</param>
|
||||||
|
/// <param name="document">The new document</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
[<CompiledName "FSharpByFunc">]
|
||||||
|
let byFunc tableName (idFunc: 'TDoc -> 'TKey) (document: 'TDoc) sqlProps =
|
||||||
|
byId tableName (idFunc document) document sqlProps
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update (replace) an entire document by its ID, using the provided function to obtain the ID from the document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table in which a document should be updated (may include schema)</param>
|
||||||
|
/// <param name="idFunc">The function to obtain the ID of the document</param>
|
||||||
|
/// <param name="document">The new document</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
let ByFunc(tableName, idFunc: System.Func<'TDoc, 'TKey>, document: 'TDoc, sqlProps) =
|
||||||
|
byFunc tableName idFunc.Invoke document sqlProps
|
||||||
|
|
||||||
|
/// <summary>Commands to patch (partially update) documents</summary>
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Patch =
|
||||||
|
|
||||||
|
/// <summary>Patch a document by its ID</summary>
|
||||||
|
/// <param name="tableName">The table in which a document should be patched (may include schema)</param>
|
||||||
|
/// <param name="docId">The ID of the document to patch</param>
|
||||||
|
/// <param name="patch">The partial document to patch the existing document</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
[<CompiledName "ById">]
|
||||||
|
let byId tableName (docId: 'TKey) (patch: 'TPatch) sqlProps =
|
||||||
|
Custom.nonQuery
|
||||||
|
(Query.byId (Query.patch tableName) docId) [ idParam docId; jsonParam "@data" patch ] sqlProps
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Patch documents using a JSON field comparison query in the <tt>WHERE</tt> clause (<tt>->> =</tt>, etc.)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table in which documents should be patched (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
/// <param name="patch">The partial document to patch the existing document</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
[<CompiledName "ByFields">]
|
||||||
|
let byFields tableName howMatched fields (patch: 'TPatch) sqlProps =
|
||||||
|
Custom.nonQuery
|
||||||
|
(Query.byFields (Query.patch tableName) howMatched fields)
|
||||||
|
(addFieldParams fields [ jsonParam "@data" patch ])
|
||||||
|
sqlProps
|
||||||
|
|
||||||
|
/// <summary>Patch documents using a JSON containment query in the <tt>WHERE</tt> clause (<tt>@></tt>)</summary>
|
||||||
|
/// <param name="tableName">The table in which documents should be patched (may include schema)</param>
|
||||||
|
/// <param name="criteria">The document to match the containment query</param>
|
||||||
|
/// <param name="patch">The partial document to patch the existing document</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
[<CompiledName "ByContains">]
|
||||||
|
let byContains tableName (criteria: 'TContains) (patch: 'TPatch) sqlProps =
|
||||||
|
Custom.nonQuery
|
||||||
|
(Query.byContains (Query.patch tableName))
|
||||||
|
[ jsonParam "@data" patch; jsonParam "@criteria" criteria ]
|
||||||
|
sqlProps
|
||||||
|
|
||||||
|
/// <summary>Patch documents using a JSON Path match query in the <tt>WHERE</tt> clause (<tt>@?</tt>)</summary>
|
||||||
|
/// <param name="tableName">The table in which documents should be patched (may include schema)</param>
|
||||||
|
/// <param name="jsonPath">The JSON Path expression to match</param>
|
||||||
|
/// <param name="patch">The partial document to patch the existing document</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
[<CompiledName "ByJsonPath">]
|
||||||
|
let byJsonPath tableName jsonPath (patch: 'TPatch) sqlProps =
|
||||||
|
Custom.nonQuery
|
||||||
|
(Query.byPathMatch (Query.patch tableName))
|
||||||
|
[ jsonParam "@data" patch; "@path", Sql.string jsonPath ]
|
||||||
|
sqlProps
|
||||||
|
|
||||||
|
/// <summary>Commands to remove fields from documents</summary>
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module RemoveFields =
|
||||||
|
|
||||||
|
/// <summary>Remove fields from a document by the document's ID</summary>
|
||||||
|
/// <param name="tableName">The table in which a document should be modified (may include schema)</param>
|
||||||
|
/// <param name="docId">The ID of the document to modify</param>
|
||||||
|
/// <param name="fieldNames">One or more field names to remove from the document</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
[<CompiledName "ById">]
|
||||||
|
let byId tableName (docId: 'TKey) fieldNames sqlProps =
|
||||||
|
Custom.nonQuery
|
||||||
|
(Query.byId (Query.removeFields tableName) docId) [ idParam docId; fieldNameParams fieldNames ] sqlProps
|
||||||
|
|
||||||
|
/// <summary>Remove fields from documents via a comparison on JSON fields in the document</summary>
|
||||||
|
/// <param name="tableName">The table in which documents should be modified (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
/// <param name="fieldNames">One or more field names to remove from the matching documents</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
[<CompiledName "ByFields">]
|
||||||
|
let byFields tableName howMatched fields fieldNames sqlProps =
|
||||||
|
Custom.nonQuery
|
||||||
|
(Query.byFields (Query.removeFields tableName) howMatched fields)
|
||||||
|
(addFieldParams fields [ fieldNameParams fieldNames ])
|
||||||
|
sqlProps
|
||||||
|
|
||||||
|
/// <summary>Remove fields from documents via a JSON containment query (<tt>@></tt>)</summary>
|
||||||
|
/// <param name="tableName">The table in which documents should be modified (may include schema)</param>
|
||||||
|
/// <param name="criteria">The document to match the containment query</param>
|
||||||
|
/// <param name="fieldNames">One or more field names to remove from the matching documents</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
[<CompiledName "ByContains">]
|
||||||
|
let byContains tableName (criteria: 'TContains) fieldNames sqlProps =
|
||||||
|
Custom.nonQuery
|
||||||
|
(Query.byContains (Query.removeFields tableName))
|
||||||
|
[ jsonParam "@criteria" criteria; fieldNameParams fieldNames ]
|
||||||
|
sqlProps
|
||||||
|
|
||||||
|
/// <summary>Remove fields from documents via a JSON Path match query (<tt>@?</tt>)</summary>
|
||||||
|
/// <param name="tableName">The table in which documents should be modified (may include schema)</param>
|
||||||
|
/// <param name="jsonPath">The JSON Path expression to match</param>
|
||||||
|
/// <param name="fieldNames">One or more field names to remove from the matching documents</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
[<CompiledName "FSharpByJsonPath">]
|
||||||
|
let byJsonPath tableName jsonPath fieldNames sqlProps =
|
||||||
|
Custom.nonQuery
|
||||||
|
(Query.byPathMatch (Query.removeFields tableName))
|
||||||
|
[ "@path", Sql.string jsonPath; fieldNameParams fieldNames ]
|
||||||
|
sqlProps
|
||||||
|
|
||||||
|
/// <summary>Commands to delete documents</summary>
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Delete =
|
||||||
|
|
||||||
|
/// <summary>Delete a document by its ID</summary>
|
||||||
|
/// <param name="tableName">The table in which a document should be deleted (may include schema)</param>
|
||||||
|
/// <param name="docId">The ID of the document to delete</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
[<CompiledName "ById">]
|
||||||
|
let byId tableName (docId: 'TKey) sqlProps =
|
||||||
|
Custom.nonQuery (Query.byId (Query.delete tableName) docId) [ idParam docId ] sqlProps
|
||||||
|
|
||||||
|
/// <summary>Delete documents by matching a JSON field comparison query (<tt>->> =</tt>, etc.)</summary>
|
||||||
|
/// <param name="tableName">The table in which documents should be deleted (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
[<CompiledName "ByFields">]
|
||||||
|
let byFields tableName howMatched fields sqlProps =
|
||||||
|
Custom.nonQuery
|
||||||
|
(Query.byFields (Query.delete tableName) howMatched fields) (addFieldParams fields []) sqlProps
|
||||||
|
|
||||||
|
/// <summary>Delete documents by matching a JSON contains query (<tt>@></tt>)</summary>
|
||||||
|
/// <param name="tableName">The table in which documents should be deleted (may include schema)</param>
|
||||||
|
/// <param name="criteria">The document to match the containment query</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
[<CompiledName "ByContains">]
|
||||||
|
let byContains tableName (criteria: 'TCriteria) sqlProps =
|
||||||
|
Custom.nonQuery (Query.byContains (Query.delete tableName)) [ jsonParam "@criteria" criteria ] sqlProps
|
||||||
|
|
||||||
|
/// <summary>Delete documents by matching a JSON Path match query (@?)</summary>
|
||||||
|
/// <param name="tableName">The table in which documents should be deleted (may include schema)</param>
|
||||||
|
/// <param name="jsonPath">The JSON Path expression to match</param>
|
||||||
|
/// <param name="sqlProps">The <tt>SqlProps</tt> to use to execute the query</param>
|
||||||
|
[<CompiledName "ByJsonPath">]
|
||||||
|
let byJsonPath tableName jsonPath sqlProps =
|
||||||
|
Custom.nonQuery (Query.byPathMatch (Query.delete tableName)) [ "@path", Sql.string jsonPath ] sqlProps
|
||||||
@@ -2,19 +2,21 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Description>Use SQLite as a document database</Description>
|
<Description>Use SQLite as a document database</Description>
|
||||||
<PackageReleaseNotes>Overall v3 release; initial release supporting SQLite</PackageReleaseNotes>
|
|
||||||
<PackageTags>JSON Document SQLite</PackageTags>
|
<PackageTags>JSON Document SQLite</PackageTags>
|
||||||
|
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Library.fs" />
|
<Compile Include="Library.fs" />
|
||||||
<Compile Include="Extensions.fs" />
|
<Compile Include="Extensions.fs" />
|
||||||
|
<Compile Include="Compat.fs" />
|
||||||
<None Include="README.md" Pack="true" PackagePath="\" />
|
<None Include="README.md" Pack="true" PackagePath="\" />
|
||||||
<None Include="..\icon.png" Pack="true" PackagePath="\" />
|
<None Include="..\icon.png" Pack="true" PackagePath="\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.1" />
|
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.0" />
|
||||||
|
<PackageReference Update="FSharp.Core" Version="9.0.100" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
269
src/Sqlite/Compat.fs
Normal file
269
src/Sqlite/Compat.fs
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
namespace BitBadger.Documents.Sqlite.Compat
|
||||||
|
|
||||||
|
open BitBadger.Documents
|
||||||
|
open BitBadger.Documents.Sqlite
|
||||||
|
|
||||||
|
[<AutoOpen>]
|
||||||
|
module Parameters =
|
||||||
|
|
||||||
|
/// Create a JSON field parameter
|
||||||
|
[<CompiledName "AddField">]
|
||||||
|
[<System.Obsolete "Use addFieldParams (F#) / AddFields (C#) instead ~ will be removed in v4.1">]
|
||||||
|
let addFieldParam name field parameters =
|
||||||
|
addFieldParams [ { field with ParameterName = Some name } ] parameters
|
||||||
|
|
||||||
|
/// Append JSON field name parameters for the given field names to the given parameters
|
||||||
|
[<CompiledName "FieldName">]
|
||||||
|
[<System.Obsolete "Use fieldNameParams (F#) / FieldNames (C#) instead ~ will be removed in v4.1">]
|
||||||
|
let fieldNameParam fieldNames =
|
||||||
|
fieldNameParams fieldNames
|
||||||
|
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Query =
|
||||||
|
|
||||||
|
/// Create a WHERE clause fragment to implement a comparison on a field in a JSON document
|
||||||
|
[<CompiledName "WhereByField">]
|
||||||
|
[<System.Obsolete "Use WhereByFields instead ~ will be removed in v4.1">]
|
||||||
|
let whereByField field paramName =
|
||||||
|
Query.whereByFields Any [ { field with ParameterName = Some paramName } ]
|
||||||
|
|
||||||
|
|
||||||
|
module WithConn =
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Count =
|
||||||
|
|
||||||
|
/// Count matching documents using a JSON field comparison (->> =)
|
||||||
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead ~ will be removed in v4.1">]
|
||||||
|
let byField tableName field conn =
|
||||||
|
WithConn.Count.byFields tableName Any [ field ] conn
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Exists =
|
||||||
|
|
||||||
|
/// Determine if a document exists using a JSON field comparison (->> =)
|
||||||
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead ~ will be removed in v4.1">]
|
||||||
|
let byField tableName field conn =
|
||||||
|
WithConn.Exists.byFields tableName Any [ field ] conn
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Find =
|
||||||
|
|
||||||
|
/// Retrieve documents matching a JSON field comparison (->> =)
|
||||||
|
[<CompiledName "FSharpByField">]
|
||||||
|
[<System.Obsolete "Use byFields instead ~ will be removed in v4.1">]
|
||||||
|
let byField<'TDoc> tableName field conn =
|
||||||
|
WithConn.Find.byFields<'TDoc> tableName Any [ field ] conn
|
||||||
|
|
||||||
|
/// Retrieve documents matching a JSON field comparison (->> =)
|
||||||
|
[<System.Obsolete "Use ByFields instead ~ will be removed in v4.1">]
|
||||||
|
let ByField<'TDoc>(tableName, field, conn) =
|
||||||
|
WithConn.Find.ByFields<'TDoc>(tableName, Any, Seq.singleton field, conn)
|
||||||
|
|
||||||
|
/// Retrieve the first document matching a JSON field comparison (->> =); returns None if not found
|
||||||
|
[<CompiledName "FSharpFirstByField">]
|
||||||
|
[<System.Obsolete "Use firstByFields instead ~ will be removed in v4.1">]
|
||||||
|
let firstByField<'TDoc> tableName field conn =
|
||||||
|
WithConn.Find.firstByFields<'TDoc> tableName Any [ field ] conn
|
||||||
|
|
||||||
|
/// 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">]
|
||||||
|
let FirstByField<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, field, conn) =
|
||||||
|
WithConn.Find.FirstByFields<'TDoc>(tableName, Any, Seq.singleton field, conn)
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Patch =
|
||||||
|
|
||||||
|
/// Patch documents using a JSON field comparison query in the WHERE clause (->> =)
|
||||||
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead ~ will be removed in v4.1">]
|
||||||
|
let byField tableName field (patch: 'TPatch) conn =
|
||||||
|
WithConn.Patch.byFields tableName Any [ field ] patch conn
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module RemoveFields =
|
||||||
|
|
||||||
|
/// Remove fields from documents via a comparison on a JSON field in the document
|
||||||
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead ~ will be removed in v4.1">]
|
||||||
|
let byField tableName field fieldNames conn =
|
||||||
|
WithConn.RemoveFields.byFields tableName Any [ field ] fieldNames conn
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Delete =
|
||||||
|
|
||||||
|
/// Delete documents by matching a JSON field comparison query (->> =)
|
||||||
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead ~ will be removed in v4.1">]
|
||||||
|
let byField tableName field conn =
|
||||||
|
WithConn.Delete.byFields tableName Any [ field ] conn
|
||||||
|
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Count =
|
||||||
|
|
||||||
|
/// Count matching documents using a JSON field comparison (->> =)
|
||||||
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead ~ will be removed in v4.1">]
|
||||||
|
let byField tableName field =
|
||||||
|
Count.byFields tableName Any [ field ]
|
||||||
|
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Exists =
|
||||||
|
|
||||||
|
/// Determine if a document exists using a JSON field comparison (->> =)
|
||||||
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead ~ will be removed in v4.1">]
|
||||||
|
let byField tableName field =
|
||||||
|
Exists.byFields tableName Any [ field ]
|
||||||
|
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Find =
|
||||||
|
|
||||||
|
/// Retrieve documents matching a JSON field comparison (->> =)
|
||||||
|
[<CompiledName "FSharpByField">]
|
||||||
|
[<System.Obsolete "Use byFields instead ~ will be removed in v4.1">]
|
||||||
|
let byField<'TDoc> tableName field =
|
||||||
|
Find.byFields<'TDoc> tableName Any [ field ]
|
||||||
|
|
||||||
|
/// Retrieve documents matching a JSON field comparison (->> =)
|
||||||
|
[<System.Obsolete "Use ByFields instead ~ will be removed in v4.1">]
|
||||||
|
let ByField<'TDoc>(tableName, field) =
|
||||||
|
Find.ByFields<'TDoc>(tableName, Any, Seq.singleton field)
|
||||||
|
|
||||||
|
/// Retrieve the first document matching a JSON field comparison (->> =); returns None if not found
|
||||||
|
[<CompiledName "FSharpFirstByField">]
|
||||||
|
[<System.Obsolete "Use firstByFields instead ~ will be removed in v4.1">]
|
||||||
|
let firstByField<'TDoc> tableName field =
|
||||||
|
Find.firstByFields<'TDoc> tableName Any [ field ]
|
||||||
|
|
||||||
|
/// 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">]
|
||||||
|
let FirstByField<'TDoc when 'TDoc: null and 'TDoc: not struct>(tableName, field) =
|
||||||
|
Find.FirstByFields<'TDoc>(tableName, Any, Seq.singleton field)
|
||||||
|
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Patch =
|
||||||
|
|
||||||
|
/// Patch documents using a JSON field comparison query in the WHERE clause (->> =)
|
||||||
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead ~ will be removed in v4.1">]
|
||||||
|
let byField tableName field (patch: 'TPatch) =
|
||||||
|
Patch.byFields tableName Any [ field ] patch
|
||||||
|
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module RemoveFields =
|
||||||
|
|
||||||
|
/// Remove fields from documents via a comparison on a JSON field in the document
|
||||||
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead ~ will be removed in v4.1">]
|
||||||
|
let byField tableName field fieldNames =
|
||||||
|
RemoveFields.byFields tableName Any [ field ] fieldNames
|
||||||
|
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Delete =
|
||||||
|
|
||||||
|
/// Delete documents by matching a JSON field comparison query (->> =)
|
||||||
|
[<CompiledName "ByField">]
|
||||||
|
[<System.Obsolete "Use ByFields instead ~ will be removed in v4.1">]
|
||||||
|
let byField tableName field =
|
||||||
|
Delete.byFields tableName Any [ field ]
|
||||||
|
|
||||||
|
|
||||||
|
open Microsoft.Data.Sqlite
|
||||||
|
|
||||||
|
/// F# Extensions for the NpgsqlConnection type
|
||||||
|
[<AutoOpen>]
|
||||||
|
module Extensions =
|
||||||
|
|
||||||
|
type SqliteConnection with
|
||||||
|
|
||||||
|
/// Count matching documents using a JSON field comparison query (->> =)
|
||||||
|
[<System.Obsolete "Use countByFields instead ~ will be removed in v4.1">]
|
||||||
|
member conn.countByField tableName field =
|
||||||
|
conn.countByFields tableName Any [ field ]
|
||||||
|
|
||||||
|
/// Determine if documents exist using a JSON field comparison query (->> =)
|
||||||
|
[<System.Obsolete "Use existsByFields instead ~ will be removed in v4.1">]
|
||||||
|
member conn.existsByField tableName field =
|
||||||
|
conn.existsByFields tableName Any [ field ]
|
||||||
|
|
||||||
|
/// Retrieve documents matching a JSON field comparison query (->> =)
|
||||||
|
[<System.Obsolete "Use findByFields instead ~ will be removed in v4.1">]
|
||||||
|
member conn.findByField<'TDoc> tableName field =
|
||||||
|
conn.findByFields<'TDoc> tableName Any [ field ]
|
||||||
|
|
||||||
|
/// Retrieve the first document matching a JSON field comparison query (->> =); returns None if not found
|
||||||
|
[<System.Obsolete "Use findFirstByFields instead ~ will be removed in v4.1">]
|
||||||
|
member conn.findFirstByField<'TDoc> tableName field =
|
||||||
|
conn.findFirstByFields<'TDoc> tableName Any [ field ]
|
||||||
|
|
||||||
|
/// Patch documents using a JSON field comparison query in the WHERE clause (->> =)
|
||||||
|
[<System.Obsolete "Use patchByFields instead ~ will be removed in v4.1">]
|
||||||
|
member conn.patchByField tableName field (patch: 'TPatch) =
|
||||||
|
conn.patchByFields tableName Any [ field ] patch
|
||||||
|
|
||||||
|
/// Remove fields from documents via a comparison on a JSON field in the document
|
||||||
|
[<System.Obsolete "Use removeFieldsByFields instead ~ will be removed in v4.1">]
|
||||||
|
member conn.removeFieldsByField tableName field fieldNames =
|
||||||
|
conn.removeFieldsByFields tableName Any [ field ] fieldNames
|
||||||
|
|
||||||
|
/// Delete documents by matching a JSON field comparison query (->> =)
|
||||||
|
[<System.Obsolete "Use deleteByFields instead ~ will be removed in v4.1">]
|
||||||
|
member conn.deleteByField tableName field =
|
||||||
|
conn.deleteByFields tableName Any [ field ]
|
||||||
|
|
||||||
|
|
||||||
|
open System.Runtime.CompilerServices
|
||||||
|
|
||||||
|
type SqliteConnectionCSharpCompatExtensions =
|
||||||
|
|
||||||
|
/// Count matching documents using a JSON field comparison query (->> =)
|
||||||
|
[<Extension>]
|
||||||
|
[<System.Obsolete "Use CountByFields instead ~ will be removed in v4.1">]
|
||||||
|
static member inline CountByField(conn, tableName, field) =
|
||||||
|
WithConn.Count.byFields tableName Any [ field ] conn
|
||||||
|
|
||||||
|
/// Determine if documents exist using a JSON field comparison query (->> =)
|
||||||
|
[<Extension>]
|
||||||
|
[<System.Obsolete "Use ExistsByFields instead ~ will be removed in v4.1">]
|
||||||
|
static member inline ExistsByField(conn, tableName, field) =
|
||||||
|
WithConn.Exists.byFields tableName Any [ field ] conn
|
||||||
|
|
||||||
|
/// Retrieve documents matching a JSON field comparison query (->> =)
|
||||||
|
[<Extension>]
|
||||||
|
[<System.Obsolete "Use FindByFields instead ~ will be removed in v4.1">]
|
||||||
|
static member inline FindByField<'TDoc>(conn, tableName, field) =
|
||||||
|
WithConn.Find.ByFields<'TDoc>(tableName, Any, [ field ], conn)
|
||||||
|
|
||||||
|
/// Retrieve the first document matching a JSON field comparison query (->> =); returns null if not found
|
||||||
|
[<Extension>]
|
||||||
|
[<System.Obsolete "Use FindFirstByFields instead ~ will be removed in v4.1">]
|
||||||
|
static member inline FindFirstByField<'TDoc when 'TDoc: null and 'TDoc: not struct>(conn, tableName, field) =
|
||||||
|
WithConn.Find.FirstByFields<'TDoc>(tableName, Any, [ field ], conn)
|
||||||
|
|
||||||
|
/// Patch documents using a JSON field comparison query in the WHERE clause (->> =)
|
||||||
|
[<Extension>]
|
||||||
|
[<System.Obsolete "Use PatchByFields instead ~ will be removed in v4.1">]
|
||||||
|
static member inline PatchByField(conn, tableName, field, patch: 'TPatch) =
|
||||||
|
WithConn.Patch.byFields tableName Any [ field ] patch conn
|
||||||
|
|
||||||
|
/// Remove fields from documents via a comparison on a JSON field in the document
|
||||||
|
[<Extension>]
|
||||||
|
[<System.Obsolete "Use RemoveFieldsByFields instead ~ will be removed in v4.1">]
|
||||||
|
static member inline RemoveFieldsByField(conn, tableName, field, fieldNames) =
|
||||||
|
WithConn.RemoveFields.byFields tableName Any [ field ] fieldNames conn
|
||||||
|
|
||||||
|
/// Delete documents by matching a JSON field comparison query (->> =)
|
||||||
|
[<Extension>]
|
||||||
|
[<System.Obsolete "Use DeleteByFields instead ~ will be removed in v4.1">]
|
||||||
|
static member inline DeleteByField(conn, tableName, field) =
|
||||||
|
WithConn.Delete.byFields tableName Any [ field ] conn
|
||||||
@@ -2,232 +2,487 @@ namespace BitBadger.Documents.Sqlite
|
|||||||
|
|
||||||
open Microsoft.Data.Sqlite
|
open Microsoft.Data.Sqlite
|
||||||
|
|
||||||
/// F# extensions for the SqliteConnection type
|
/// <summary>F# extensions for the SqliteConnection type</summary>
|
||||||
[<AutoOpen>]
|
[<AutoOpen>]
|
||||||
module Extensions =
|
module Extensions =
|
||||||
|
|
||||||
type SqliteConnection with
|
type SqliteConnection with
|
||||||
|
|
||||||
/// Execute a query that returns a list of results
|
/// <summary>Execute a query that returns a list of results</summary>
|
||||||
|
/// <param name="query">The query to retrieve the results</param>
|
||||||
|
/// <param name="parameters">Parameters to use for the query</param>
|
||||||
|
/// <param name="mapFunc">The mapping function between the document and the domain item</param>
|
||||||
|
/// <returns>A list of results for the given query</returns>
|
||||||
member conn.customList<'TDoc> query parameters mapFunc =
|
member conn.customList<'TDoc> query parameters mapFunc =
|
||||||
WithConn.Custom.list<'TDoc> query parameters mapFunc conn
|
WithConn.Custom.list<'TDoc> query parameters mapFunc conn
|
||||||
|
|
||||||
/// Execute a query that returns one or no results
|
/// <summary>Execute a query that returns one or no results</summary>
|
||||||
|
/// <param name="query">The query to retrieve the results</param>
|
||||||
|
/// <param name="parameters">Parameters to use for the query</param>
|
||||||
|
/// <param name="mapFunc">The mapping function between the document and the domain item</param>
|
||||||
|
/// <returns><tt>Some</tt> with the first matching result, or <tt>None</tt> if not found</returns>
|
||||||
member conn.customSingle<'TDoc> query parameters mapFunc =
|
member conn.customSingle<'TDoc> query parameters mapFunc =
|
||||||
WithConn.Custom.single<'TDoc> query parameters mapFunc conn
|
WithConn.Custom.single<'TDoc> query parameters mapFunc conn
|
||||||
|
|
||||||
/// Execute a query that does not return a value
|
/// <summary>Execute a query that returns no results</summary>
|
||||||
|
/// <param name="query">The query to retrieve the results</param>
|
||||||
|
/// <param name="parameters">Parameters to use for the query</param>
|
||||||
member conn.customNonQuery query parameters =
|
member conn.customNonQuery query parameters =
|
||||||
WithConn.Custom.nonQuery query parameters conn
|
WithConn.Custom.nonQuery query parameters conn
|
||||||
|
|
||||||
/// Execute a query that returns a scalar value
|
/// <summary>Execute a query that returns a scalar value</summary>
|
||||||
|
/// <param name="query">The query to retrieve the value</param>
|
||||||
|
/// <param name="parameters">Parameters to use for the query</param>
|
||||||
|
/// <param name="mapFunc">The mapping function to obtain the value</param>
|
||||||
|
/// <returns>The scalar value for the query</returns>
|
||||||
member conn.customScalar<'T when 'T: struct> query parameters mapFunc =
|
member conn.customScalar<'T when 'T: struct> query parameters mapFunc =
|
||||||
WithConn.Custom.scalar<'T> query parameters mapFunc conn
|
WithConn.Custom.scalar<'T> query parameters mapFunc conn
|
||||||
|
|
||||||
/// Create a document table
|
/// <summary>Create a document table</summary>
|
||||||
|
/// <param name="name">The table whose existence should be ensured (may include schema)</param>
|
||||||
member conn.ensureTable name =
|
member conn.ensureTable name =
|
||||||
WithConn.Definition.ensureTable name conn
|
WithConn.Definition.ensureTable name conn
|
||||||
|
|
||||||
/// Create an index on a document table
|
/// <summary>Create an index on field(s) within documents in the specified table</summary>
|
||||||
|
/// <param name="tableName">The table to be indexed (may include schema)</param>
|
||||||
|
/// <param name="indexName">The name of the index to create</param>
|
||||||
|
/// <param name="fields">One or more fields to be indexed</param>
|
||||||
member conn.ensureFieldIndex tableName indexName fields =
|
member conn.ensureFieldIndex tableName indexName fields =
|
||||||
WithConn.Definition.ensureFieldIndex tableName indexName fields conn
|
WithConn.Definition.ensureFieldIndex tableName indexName fields conn
|
||||||
|
|
||||||
/// Insert a new document
|
/// <summary>Insert a new document</summary>
|
||||||
|
/// <param name="tableName">The table into which the document should be inserted (may include schema)</param>
|
||||||
|
/// <param name="document">The document to be inserted</param>
|
||||||
member conn.insert<'TDoc> tableName (document: 'TDoc) =
|
member conn.insert<'TDoc> tableName (document: 'TDoc) =
|
||||||
WithConn.insert<'TDoc> tableName document conn
|
WithConn.Document.insert<'TDoc> tableName document conn
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
/// 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")
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table into which the document should be saved (may include schema)</param>
|
||||||
|
/// <param name="document">The document to be saved</param>
|
||||||
member conn.save<'TDoc> tableName (document: 'TDoc) =
|
member conn.save<'TDoc> tableName (document: 'TDoc) =
|
||||||
WithConn.save tableName document conn
|
WithConn.Document.save tableName document conn
|
||||||
|
|
||||||
/// Count all documents in a table
|
/// <summary>Count all documents in a table</summary>
|
||||||
|
/// <param name="tableName">The table in which documents should be counted (may include schema)</param>
|
||||||
|
/// <returns>The count of the documents in the table</returns>
|
||||||
member conn.countAll tableName =
|
member conn.countAll tableName =
|
||||||
WithConn.Count.all tableName conn
|
WithConn.Count.all tableName conn
|
||||||
|
|
||||||
/// Count matching documents using a comparison on a JSON field
|
/// <summary>Count matching documents using JSON field comparisons (<tt>->> =</tt>, etc.)</summary>
|
||||||
member conn.countByField tableName field =
|
/// <param name="tableName">The table in which documents should be counted (may include schema)</param>
|
||||||
WithConn.Count.byField tableName field conn
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
/// <returns>The count of matching documents in the table</returns>
|
||||||
|
member conn.countByFields tableName howMatched fields =
|
||||||
|
WithConn.Count.byFields tableName howMatched fields conn
|
||||||
|
|
||||||
/// Determine if a document exists for the given ID
|
/// <summary>Determine if a document exists for the given ID</summary>
|
||||||
|
/// <param name="tableName">The table in which existence should be checked (may include schema)</param>
|
||||||
|
/// <param name="docId">The ID of the document whose existence should be checked</param>
|
||||||
|
/// <returns>True if a document exists, false if not</returns>
|
||||||
member conn.existsById tableName (docId: 'TKey) =
|
member conn.existsById tableName (docId: 'TKey) =
|
||||||
WithConn.Exists.byId tableName docId conn
|
WithConn.Exists.byId tableName docId conn
|
||||||
|
|
||||||
/// Determine if a document exists using a comparison on a JSON field
|
/// <summary>Determine if a document exists using JSON field comparisons (<tt>->> =</tt>, etc.)</summary>
|
||||||
member conn.existsByField tableName field =
|
/// <param name="tableName">The table in which existence should be checked (may include schema)</param>
|
||||||
WithConn.Exists.byField tableName field conn
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
/// <returns>True if any matching documents exist, false if not</returns>
|
||||||
|
member conn.existsByFields tableName howMatched fields =
|
||||||
|
WithConn.Exists.byFields tableName howMatched fields conn
|
||||||
|
|
||||||
/// Retrieve all documents in the given table
|
/// <summary>Retrieve all documents in the given table</summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <returns>All documents from the given table</returns>
|
||||||
member conn.findAll<'TDoc> tableName =
|
member conn.findAll<'TDoc> tableName =
|
||||||
WithConn.Find.all<'TDoc> tableName conn
|
WithConn.Find.all<'TDoc> tableName conn
|
||||||
|
|
||||||
/// Retrieve a document by its ID
|
/// <summary>Retrieve all documents in the given table ordered by the given fields in the document</summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <returns>All documents from the given table, ordered by the given fields</returns>
|
||||||
|
member conn.findAllOrdered<'TDoc> tableName orderFields =
|
||||||
|
WithConn.Find.allOrdered<'TDoc> tableName orderFields conn
|
||||||
|
|
||||||
|
/// <summary>Retrieve a document by its ID</summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="docId">The ID of the document to retrieve</param>
|
||||||
|
/// <returns><tt>Some</tt> with the document if found, <tt>None</tt> otherwise</returns>
|
||||||
member conn.findById<'TKey, 'TDoc> tableName (docId: 'TKey) =
|
member conn.findById<'TKey, 'TDoc> tableName (docId: 'TKey) =
|
||||||
WithConn.Find.byId<'TKey, 'TDoc> tableName docId conn
|
WithConn.Find.byId<'TKey, 'TDoc> tableName docId conn
|
||||||
|
|
||||||
/// Retrieve documents via a comparison on a JSON field
|
/// <summary>Retrieve documents matching JSON field comparisons (<tt>->> =</tt>, etc.)</summary>
|
||||||
member conn.findByField<'TDoc> tableName field =
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
WithConn.Find.byField<'TDoc> tableName field conn
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
/// <returns>All documents matching the given fields</returns>
|
||||||
|
member conn.findByFields<'TDoc> tableName howMatched fields =
|
||||||
|
WithConn.Find.byFields<'TDoc> tableName howMatched fields conn
|
||||||
|
|
||||||
/// Retrieve documents via a comparison on a JSON field, returning only the first result
|
/// <summary>
|
||||||
member conn.findFirstByField<'TDoc> tableName field =
|
/// Retrieve documents matching JSON field comparisons (<tt>->> =</tt>, etc.) ordered by the given fields
|
||||||
WithConn.Find.firstByField<'TDoc> tableName field conn
|
/// in the document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="queryFields">The field conditions to match</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <returns>All documents matching the given fields, ordered by the other given fields</returns>
|
||||||
|
member conn.findByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields =
|
||||||
|
WithConn.Find.byFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields conn
|
||||||
|
|
||||||
/// Update an entire document by its ID
|
/// <summary>Retrieve the first document matching JSON field comparisons (<tt>->> =</tt>, etc.)</summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
/// <returns><tt>Some</tt> with the first document, or <tt>None</tt> if not found</returns>
|
||||||
|
member conn.findFirstByFields<'TDoc> tableName howMatched fields =
|
||||||
|
WithConn.Find.firstByFields<'TDoc> tableName howMatched fields conn
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve the first document matching JSON field comparisons (<tt>->> =</tt>, etc.) ordered by the
|
||||||
|
/// given fields in the document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="queryFields">The field conditions to match</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <tt>Some</tt> with the first document ordered by the given fields, or <tt>None</tt> if not found
|
||||||
|
/// </returns>
|
||||||
|
member conn.findFirstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields =
|
||||||
|
WithConn.Find.firstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields conn
|
||||||
|
|
||||||
|
/// <summary>Update (replace) an entire document by its ID</summary>
|
||||||
|
/// <param name="tableName">The table in which a document should be updated (may include schema)</param>
|
||||||
|
/// <param name="docId">The ID of the document to be updated (replaced)</param>
|
||||||
|
/// <param name="document">The new document</param>
|
||||||
member conn.updateById tableName (docId: 'TKey) (document: 'TDoc) =
|
member conn.updateById tableName (docId: 'TKey) (document: 'TDoc) =
|
||||||
WithConn.Update.byId tableName docId document conn
|
WithConn.Update.byId tableName docId document conn
|
||||||
|
|
||||||
/// Update an entire document by its ID, using the provided function to obtain the ID from the document
|
/// <summary>
|
||||||
|
/// Update (replace) an entire document by its ID, using the provided function to obtain the ID from the
|
||||||
|
/// document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table in which a document should be updated (may include schema)</param>
|
||||||
|
/// <param name="idFunc">The function to obtain the ID of the document</param>
|
||||||
|
/// <param name="document">The new document</param>
|
||||||
member conn.updateByFunc tableName (idFunc: 'TDoc -> 'TKey) (document: 'TDoc) =
|
member conn.updateByFunc tableName (idFunc: 'TDoc -> 'TKey) (document: 'TDoc) =
|
||||||
WithConn.Update.byFunc tableName idFunc document conn
|
WithConn.Update.byFunc tableName idFunc document conn
|
||||||
|
|
||||||
/// Patch a document by its ID
|
/// <summary>Patch a document by its ID</summary>
|
||||||
|
/// <param name="tableName">The table in which a document should be patched (may include schema)</param>
|
||||||
|
/// <param name="docId">The ID of the document to patch</param>
|
||||||
|
/// <param name="patch">The partial document to patch the existing document</param>
|
||||||
member conn.patchById tableName (docId: 'TKey) (patch: 'TPatch) =
|
member conn.patchById tableName (docId: 'TKey) (patch: 'TPatch) =
|
||||||
WithConn.Patch.byId tableName docId patch conn
|
WithConn.Patch.byId tableName docId patch conn
|
||||||
|
|
||||||
/// Patch documents using a comparison on a JSON field
|
/// <summary>
|
||||||
member conn.patchByField tableName field (patch: 'TPatch) =
|
/// Patch documents using a JSON field comparison query in the <tt>WHERE</tt> clause (<tt>->> =</tt>,
|
||||||
WithConn.Patch.byField tableName field patch conn
|
/// etc.)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tableName">The table in which documents should be patched (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
/// <param name="patch">The partial document to patch the existing document</param>
|
||||||
|
member conn.patchByFields tableName howMatched fields (patch: 'TPatch) =
|
||||||
|
WithConn.Patch.byFields tableName howMatched fields patch conn
|
||||||
|
|
||||||
/// Remove fields from a document by the document's ID
|
/// <summary>Remove fields from a document by the document's ID</summary>
|
||||||
|
/// <param name="tableName">The table in which a document should be modified (may include schema)</param>
|
||||||
|
/// <param name="docId">The ID of the document to modify</param>
|
||||||
|
/// <param name="fieldNames">One or more field names to remove from the document</param>
|
||||||
member conn.removeFieldsById tableName (docId: 'TKey) fieldNames =
|
member conn.removeFieldsById tableName (docId: 'TKey) fieldNames =
|
||||||
WithConn.RemoveFields.byId tableName docId fieldNames conn
|
WithConn.RemoveFields.byId tableName docId fieldNames conn
|
||||||
|
|
||||||
/// Remove a field from a document via a comparison on a JSON field in the document
|
/// <summary>Remove fields from documents via a comparison on JSON fields in the document</summary>
|
||||||
member conn.removeFieldsByField tableName field fieldNames =
|
/// <param name="tableName">The table in which documents should be modified (may include schema)</param>
|
||||||
WithConn.RemoveFields.byField tableName field fieldNames conn
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
/// <param name="fieldNames">One or more field names to remove from the matching documents</param>
|
||||||
|
member conn.removeFieldsByFields tableName howMatched fields fieldNames =
|
||||||
|
WithConn.RemoveFields.byFields tableName howMatched fields fieldNames conn
|
||||||
|
|
||||||
/// Delete a document by its ID
|
/// <summary>Delete a document by its ID</summary>
|
||||||
|
/// <param name="tableName">The table in which a document should be deleted (may include schema)</param>
|
||||||
|
/// <param name="docId">The ID of the document to delete</param>
|
||||||
member conn.deleteById tableName (docId: 'TKey) =
|
member conn.deleteById tableName (docId: 'TKey) =
|
||||||
WithConn.Delete.byId tableName docId conn
|
WithConn.Delete.byId tableName docId conn
|
||||||
|
|
||||||
/// Delete documents by matching a comparison on a JSON field
|
/// <summary>Delete documents by matching a JSON field comparison query (<tt>->> =</tt>, etc.)</summary>
|
||||||
member conn.deleteByField tableName field =
|
/// <param name="tableName">The table in which documents should be deleted (may include schema)</param>
|
||||||
WithConn.Delete.byField tableName field conn
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
member conn.deleteByFields tableName howMatched fields =
|
||||||
|
WithConn.Delete.byFields tableName howMatched fields conn
|
||||||
|
|
||||||
|
|
||||||
open System.Runtime.CompilerServices
|
open System.Runtime.CompilerServices
|
||||||
|
|
||||||
/// C# extensions on the SqliteConnection type
|
/// <summary>C# extensions on the SqliteConnection type</summary>
|
||||||
type SqliteConnectionCSharpExtensions =
|
type SqliteConnectionCSharpExtensions =
|
||||||
|
|
||||||
/// Execute a query that returns a list of results
|
/// <summary>Execute a query that returns a list of results</summary>
|
||||||
|
/// <param name="conn">The <tt>SqliteConnection</tt> on which to run the query</param>
|
||||||
|
/// <param name="query">The query to retrieve the results</param>
|
||||||
|
/// <param name="parameters">Parameters to use for the query</param>
|
||||||
|
/// <param name="mapFunc">The mapping function between the document and the domain item</param>
|
||||||
|
/// <returns>A list of results for the given query</returns>
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline CustomList<'TDoc>(conn, query, parameters, mapFunc: System.Func<SqliteDataReader, 'TDoc>) =
|
static member inline CustomList<'TDoc>(conn, query, parameters, mapFunc: System.Func<SqliteDataReader, 'TDoc>) =
|
||||||
WithConn.Custom.List<'TDoc>(query, parameters, mapFunc, conn)
|
WithConn.Custom.List<'TDoc>(query, parameters, mapFunc, conn)
|
||||||
|
|
||||||
/// Execute a query that returns one or no results
|
/// <summary>Execute a query that returns one or no results</summary>
|
||||||
|
/// <param name="conn">The <tt>SqliteConnection</tt> on which to run the query</param>
|
||||||
|
/// <param name="query">The query to retrieve the results</param>
|
||||||
|
/// <param name="parameters">Parameters to use for the query</param>
|
||||||
|
/// <param name="mapFunc">The mapping function between the document and the domain item</param>
|
||||||
|
/// <returns>The first matching result, or <tt>null</tt> if not found</returns>
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline CustomSingle<'TDoc when 'TDoc: null>(
|
static member inline CustomSingle<'TDoc when 'TDoc: null and 'TDoc: not struct>(
|
||||||
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)
|
||||||
|
|
||||||
/// Execute a query that does not return a value
|
/// <summary>Execute a query that returns no results</summary>
|
||||||
|
/// <param name="conn">The <tt>SqliteConnection</tt> on which to run the query</param>
|
||||||
|
/// <param name="query">The query to retrieve the results</param>
|
||||||
|
/// <param name="parameters">Parameters to use for the query</param>
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline CustomNonQuery(conn, query, parameters) =
|
static member inline CustomNonQuery(conn, query, parameters) =
|
||||||
WithConn.Custom.nonQuery query parameters conn
|
WithConn.Custom.nonQuery query parameters conn
|
||||||
|
|
||||||
/// Execute a query that returns a scalar value
|
/// <summary>Execute a query that returns a scalar value</summary>
|
||||||
|
/// <param name="conn">The <tt>SqliteConnection</tt> on which to run the query</param>
|
||||||
|
/// <param name="query">The query to retrieve the value</param>
|
||||||
|
/// <param name="parameters">Parameters to use for the query</param>
|
||||||
|
/// <param name="mapFunc">The mapping function to obtain the value</param>
|
||||||
|
/// <returns>The scalar value for the query</returns>
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline CustomScalar<'T when 'T: struct>(
|
static member inline CustomScalar<'T when 'T: struct>(
|
||||||
conn, query, parameters, mapFunc: System.Func<SqliteDataReader, 'T>) =
|
conn, query, parameters, mapFunc: System.Func<SqliteDataReader, 'T>) =
|
||||||
WithConn.Custom.Scalar<'T>(query, parameters, mapFunc, conn)
|
WithConn.Custom.Scalar<'T>(query, parameters, mapFunc, conn)
|
||||||
|
|
||||||
/// Create a document table
|
/// <summary>Create a document table</summary>
|
||||||
|
/// <param name="conn">The <tt>SqliteConnection</tt> on which to run the query</param>
|
||||||
|
/// <param name="name">The table whose existence should be ensured (may include schema)</param>
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline EnsureTable(conn, name) =
|
static member inline EnsureTable(conn, name) =
|
||||||
WithConn.Definition.ensureTable name conn
|
WithConn.Definition.ensureTable name conn
|
||||||
|
|
||||||
/// Create an index on one or more fields in a document table
|
/// <summary>Create an index on field(s) within documents in the specified table</summary>
|
||||||
|
/// <param name="conn">The <tt>SqliteConnection</tt> on which to run the query</param>
|
||||||
|
/// <param name="tableName">The table to be indexed (may include schema)</param>
|
||||||
|
/// <param name="indexName">The name of the index to create</param>
|
||||||
|
/// <param name="fields">One or more fields to be indexed</param>
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline EnsureFieldIndex(conn, tableName, indexName, fields) =
|
static member inline EnsureFieldIndex(conn, tableName, indexName, fields) =
|
||||||
WithConn.Definition.ensureFieldIndex tableName indexName fields conn
|
WithConn.Definition.ensureFieldIndex tableName indexName fields conn
|
||||||
|
|
||||||
/// Insert a new document
|
/// <summary>Insert a new document</summary>
|
||||||
|
/// <param name="conn">The <tt>SqliteConnection</tt> on which to run the query</param>
|
||||||
|
/// <param name="tableName">The table into which the document should be inserted (may include schema)</param>
|
||||||
|
/// <param name="document">The document to be inserted</param>
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline Insert<'TDoc>(conn, tableName, document: 'TDoc) =
|
static member inline Insert<'TDoc>(conn, tableName, document: 'TDoc) =
|
||||||
WithConn.insert<'TDoc> tableName document conn
|
WithConn.Document.insert<'TDoc> tableName document conn
|
||||||
|
|
||||||
/// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
|
/// <summary>Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")</summary>
|
||||||
|
/// <param name="conn">The <tt>SqliteConnection</tt> on which to run the query</param>
|
||||||
|
/// <param name="tableName">The table into which the document should be saved (may include schema)</param>
|
||||||
|
/// <param name="document">The document to be saved</param>
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline Save<'TDoc>(conn, tableName, document: 'TDoc) =
|
static member inline Save<'TDoc>(conn, tableName, document: 'TDoc) =
|
||||||
WithConn.save<'TDoc> tableName document conn
|
WithConn.Document.save<'TDoc> tableName document conn
|
||||||
|
|
||||||
/// Count all documents in a table
|
/// <summary>Count all documents in a table</summary>
|
||||||
|
/// <param name="conn">The <tt>SqliteConnection</tt> on which to run the query</param>
|
||||||
|
/// <param name="tableName">The table in which documents should be counted (may include schema)</param>
|
||||||
|
/// <returns>The count of the documents in the table</returns>
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline CountAll(conn, tableName) =
|
static member inline CountAll(conn, tableName) =
|
||||||
WithConn.Count.all tableName conn
|
WithConn.Count.all tableName conn
|
||||||
|
|
||||||
/// Count matching documents using a comparison on a JSON field
|
/// <summary>Count matching documents using JSON field comparisons (<tt>->> =</tt>, etc.)</summary>
|
||||||
|
/// <param name="conn">The <tt>SqliteConnection</tt> on which to run the query</param>
|
||||||
|
/// <param name="tableName">The table in which documents should be counted (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
/// <returns>The count of matching documents in the table</returns>
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline CountByField(conn, tableName, field) =
|
static member inline CountByFields(conn, tableName, howMatched, fields) =
|
||||||
WithConn.Count.byField tableName field conn
|
WithConn.Count.byFields tableName howMatched fields conn
|
||||||
|
|
||||||
/// Determine if a document exists for the given ID
|
/// <summary>Determine if a document exists for the given ID</summary>
|
||||||
|
/// <param name="conn">The <tt>SqliteConnection</tt> on which to run the query</param>
|
||||||
|
/// <param name="tableName">The table in which existence should be checked (may include schema)</param>
|
||||||
|
/// <param name="docId">The ID of the document whose existence should be checked</param>
|
||||||
|
/// <returns>True if a document exists, false if not</returns>
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline ExistsById<'TKey>(conn, tableName, docId: 'TKey) =
|
static member inline ExistsById<'TKey>(conn, tableName, docId: 'TKey) =
|
||||||
WithConn.Exists.byId tableName docId conn
|
WithConn.Exists.byId tableName docId conn
|
||||||
|
|
||||||
/// Determine if a document exists using a comparison on a JSON field
|
/// <summary>Determine if a document exists using JSON field comparisons (<tt>->> =</tt>, etc.)</summary>
|
||||||
|
/// <param name="conn">The <tt>SqliteConnection</tt> on which to run the query</param>
|
||||||
|
/// <param name="tableName">The table in which existence should be checked (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
/// <returns>True if any matching documents exist, false if not</returns>
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline ExistsByField(conn, tableName, field) =
|
static member inline ExistsByFields(conn, tableName, howMatched, fields) =
|
||||||
WithConn.Exists.byField tableName field conn
|
WithConn.Exists.byFields tableName howMatched fields conn
|
||||||
|
|
||||||
/// Retrieve all documents in the given table
|
/// <summary>Retrieve all documents in the given table</summary>
|
||||||
|
/// <param name="conn">The <tt>SqliteConnection</tt> on which to run the query</param>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <returns>All documents from the given table</returns>
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline FindAll<'TDoc>(conn, tableName) =
|
static member inline FindAll<'TDoc>(conn, tableName) =
|
||||||
WithConn.Find.All<'TDoc>(tableName, conn)
|
WithConn.Find.All<'TDoc>(tableName, conn)
|
||||||
|
|
||||||
/// Retrieve a document by its ID
|
/// <summary>Retrieve all documents in the given table ordered by the given fields in the document</summary>
|
||||||
|
/// <param name="conn">The <tt>SqliteConnection</tt> on which to run the query</param>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <returns>All documents from the given table, ordered by the given fields</returns>
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline FindById<'TKey, 'TDoc when 'TDoc: null>(conn, tableName, docId: 'TKey) =
|
static member inline FindAllOrdered<'TDoc>(conn, tableName, orderFields) =
|
||||||
|
WithConn.Find.AllOrdered<'TDoc>(tableName, orderFields, conn)
|
||||||
|
|
||||||
|
/// <summary>Retrieve a document by its ID</summary>
|
||||||
|
/// <param name="conn">The <tt>SqliteConnection</tt> on which to run the query</param>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="docId">The ID of the document to retrieve</param>
|
||||||
|
/// <returns>The document if found, <tt>null</tt> otherwise</returns>
|
||||||
|
[<Extension>]
|
||||||
|
static member inline FindById<'TKey, 'TDoc when 'TDoc: null and 'TDoc: not struct>(conn, tableName, docId: 'TKey) =
|
||||||
WithConn.Find.ById<'TKey, 'TDoc>(tableName, docId, conn)
|
WithConn.Find.ById<'TKey, 'TDoc>(tableName, docId, conn)
|
||||||
|
|
||||||
/// Retrieve documents via a comparison on a JSON field
|
/// <summary>Retrieve documents matching JSON field comparisons (<tt>->> =</tt>, etc.)</summary>
|
||||||
|
/// <param name="conn">The <tt>SqliteConnection</tt> on which to run the query</param>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
/// <returns>All documents matching the given fields</returns>
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline FindByField<'TDoc>(conn, tableName, field) =
|
static member inline FindByFields<'TDoc>(conn, tableName, howMatched, fields) =
|
||||||
WithConn.Find.ByField<'TDoc>(tableName, field, conn)
|
WithConn.Find.ByFields<'TDoc>(tableName, howMatched, fields, conn)
|
||||||
|
|
||||||
/// Retrieve documents via a comparison on a JSON field, returning only the first result
|
/// <summary>
|
||||||
|
/// Retrieve documents matching JSON field comparisons (<tt>->> =</tt>, etc.) ordered by the given fields in
|
||||||
|
/// the document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="conn">The <tt>SqliteConnection</tt> on which to run the query</param>
|
||||||
|
/// <param name="tableName">The table from which documents should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="queryFields">The field conditions to match</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <returns>All documents matching the given fields, ordered by the other given fields</returns>
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline FindFirstByField<'TDoc when 'TDoc: null>(conn, tableName, field) =
|
static member inline FindByFieldsOrdered<'TDoc>(conn, tableName, howMatched, queryFields, orderFields) =
|
||||||
WithConn.Find.FirstByField<'TDoc>(tableName, field, conn)
|
WithConn.Find.ByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, conn)
|
||||||
|
|
||||||
/// Update an entire document by its ID
|
/// <summary>Retrieve the first document matching JSON field comparisons (<tt>->> =</tt>, etc.)</summary>
|
||||||
|
/// <param name="conn">The <tt>SqliteConnection</tt> on which to run the query</param>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
/// <returns>The first document, or <tt>null</tt> if not found</returns>
|
||||||
|
[<Extension>]
|
||||||
|
static member inline FindFirstByFields<'TDoc when 'TDoc: null and 'TDoc: not struct>(
|
||||||
|
conn, tableName, howMatched, fields) =
|
||||||
|
WithConn.Find.FirstByFields<'TDoc>(tableName, howMatched, fields, conn)
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve the first document matching JSON field comparisons (<tt>->> =</tt>, etc.) ordered by the given
|
||||||
|
/// fields in the document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="conn">The <tt>SqliteConnection</tt> on which to run the query</param>
|
||||||
|
/// <param name="tableName">The table from which a document should be retrieved (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="queryFields">The field conditions to match</param>
|
||||||
|
/// <param name="orderFields">Fields by which the results should be ordered</param>
|
||||||
|
/// <returns>The first document ordered by the given fields, or <tt>null</tt> if not found</returns>
|
||||||
|
[<Extension>]
|
||||||
|
static member inline FindFirstByFieldsOrdered<'TDoc when 'TDoc: null and 'TDoc: not struct>(
|
||||||
|
conn, tableName, howMatched, queryFields, orderFields) =
|
||||||
|
WithConn.Find.FirstByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, conn)
|
||||||
|
|
||||||
|
/// <summary>Update (replace) an entire document by its ID</summary>
|
||||||
|
/// <param name="conn">The <tt>SqliteConnection</tt> on which to run the query</param>
|
||||||
|
/// <param name="tableName">The table in which a document should be updated (may include schema)</param>
|
||||||
|
/// <param name="docId">The ID of the document to be updated (replaced)</param>
|
||||||
|
/// <param name="document">The new document</param>
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline UpdateById<'TKey, 'TDoc>(conn, tableName, docId: 'TKey, document: 'TDoc) =
|
static member inline UpdateById<'TKey, 'TDoc>(conn, tableName, docId: 'TKey, document: 'TDoc) =
|
||||||
WithConn.Update.byId tableName docId document conn
|
WithConn.Update.byId tableName docId document conn
|
||||||
|
|
||||||
/// Update an entire document by its ID, using the provided function to obtain the ID from the document
|
/// <summary>
|
||||||
|
/// Update (replace) an entire document by its ID, using the provided function to obtain the ID from the document
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="conn">The <tt>SqliteConnection</tt> on which to run the query</param>
|
||||||
|
/// <param name="tableName">The table in which a document should be updated (may include schema)</param>
|
||||||
|
/// <param name="idFunc">The function to obtain the ID of the document</param>
|
||||||
|
/// <param name="document">The new document</param>
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline UpdateByFunc<'TKey, 'TDoc>(conn, tableName, idFunc: System.Func<'TDoc, 'TKey>, doc: 'TDoc) =
|
static member inline UpdateByFunc<'TKey, 'TDoc>(
|
||||||
WithConn.Update.ByFunc(tableName, idFunc, doc, conn)
|
conn, tableName, idFunc: System.Func<'TDoc, 'TKey>, document: 'TDoc) =
|
||||||
|
WithConn.Update.ByFunc(tableName, idFunc, document, conn)
|
||||||
|
|
||||||
/// Patch a document by its ID
|
/// <summary>Patch a document by its ID</summary>
|
||||||
|
/// <param name="conn">The <tt>SqliteConnection</tt> on which to run the query</param>
|
||||||
|
/// <param name="tableName">The table in which a document should be patched (may include schema)</param>
|
||||||
|
/// <param name="docId">The ID of the document to patch</param>
|
||||||
|
/// <param name="patch">The partial document to patch the existing document</param>
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline PatchById<'TKey, 'TPatch>(conn, tableName, docId: 'TKey, patch: 'TPatch) =
|
static member inline PatchById<'TKey, 'TPatch>(conn, tableName, docId: 'TKey, patch: 'TPatch) =
|
||||||
WithConn.Patch.byId tableName docId patch conn
|
WithConn.Patch.byId tableName docId patch conn
|
||||||
|
|
||||||
/// Patch documents using a comparison on a JSON field
|
/// <summary>
|
||||||
|
/// Patch documents using a JSON field comparison query in the <tt>WHERE</tt> clause (<tt>->> =</tt>, etc.)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="conn">The <tt>SqliteConnection</tt> on which to run the query</param>
|
||||||
|
/// <param name="tableName">The table in which documents should be patched (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
/// <param name="patch">The partial document to patch the existing document</param>
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline PatchByField<'TPatch>(conn, tableName, field, patch: 'TPatch) =
|
static member inline PatchByFields<'TPatch>(conn, tableName, howMatched, fields, patch: 'TPatch) =
|
||||||
WithConn.Patch.byField tableName field patch conn
|
WithConn.Patch.byFields tableName howMatched fields patch conn
|
||||||
|
|
||||||
/// Remove fields from a document by the document's ID
|
/// <summary>Remove fields from a document by the document's ID</summary>
|
||||||
|
/// <param name="conn">The <tt>SqliteConnection</tt> on which to run the query</param>
|
||||||
|
/// <param name="tableName">The table in which a document should be modified (may include schema)</param>
|
||||||
|
/// <param name="docId">The ID of the document to modify</param>
|
||||||
|
/// <param name="fieldNames">One or more field names to remove from the document</param>
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline RemoveFieldsById<'TKey>(conn, tableName, docId: 'TKey, fieldNames) =
|
static member inline RemoveFieldsById<'TKey>(conn, tableName, docId: 'TKey, fieldNames) =
|
||||||
WithConn.RemoveFields.ById(tableName, docId, fieldNames, conn)
|
WithConn.RemoveFields.byId tableName docId fieldNames conn
|
||||||
|
|
||||||
/// Remove fields from documents via a comparison on a JSON field in the document
|
/// <summary>Remove fields from documents via a comparison on JSON fields in the document</summary>
|
||||||
|
/// <param name="conn">The <tt>SqliteConnection</tt> on which to run the query</param>
|
||||||
|
/// <param name="tableName">The table in which documents should be modified (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
|
/// <param name="fieldNames">One or more field names to remove from the matching documents</param>
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline RemoveFieldsByField(conn, tableName, field, fieldNames) =
|
static member inline RemoveFieldsByFields(conn, tableName, howMatched, fields, fieldNames) =
|
||||||
WithConn.RemoveFields.ByField(tableName, field, fieldNames, conn)
|
WithConn.RemoveFields.byFields tableName howMatched fields fieldNames conn
|
||||||
|
|
||||||
/// Delete a document by its ID
|
/// <summary>Delete a document by its ID</summary>
|
||||||
|
/// <param name="conn">The <tt>SqliteConnection</tt> on which to run the query</param>
|
||||||
|
/// <param name="tableName">The table in which a document should be deleted (may include schema)</param>
|
||||||
|
/// <param name="docId">The ID of the document to delete</param>
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline DeleteById<'TKey>(conn, tableName, docId: 'TKey) =
|
static member inline DeleteById<'TKey>(conn, tableName, docId: 'TKey) =
|
||||||
WithConn.Delete.byId tableName docId conn
|
WithConn.Delete.byId tableName docId conn
|
||||||
|
|
||||||
/// Delete documents by matching a comparison on a JSON field
|
/// <summary>Delete documents by matching a JSON field comparison query (<tt>->> =</tt>, etc.)</summary>
|
||||||
|
/// <param name="conn">The <tt>SqliteConnection</tt> on which to run the query</param>
|
||||||
|
/// <param name="tableName">The table in which documents should be deleted (may include schema)</param>
|
||||||
|
/// <param name="howMatched">Whether to match any or all of the field conditions</param>
|
||||||
|
/// <param name="fields">The field conditions to match</param>
|
||||||
[<Extension>]
|
[<Extension>]
|
||||||
static member inline DeleteByField(conn, tableName, field) =
|
static member inline DeleteByFields(conn, tableName, howMatched, fields) =
|
||||||
WithConn.Delete.byField tableName field conn
|
WithConn.Delete.byFields tableName howMatched fields conn
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,11 +5,16 @@ 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:
|
||||||
@@ -72,28 +77,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.ByField type signature is Func<string, Field, Task<long>>
|
// Count.ByFields type signature is Func<string, FieldMatch, IEnumerable<Field>, Task<long>>
|
||||||
var customerCount = await Count.ByField("customer", Field.EQ("City", "Atlanta"));
|
var customerCount = await Count.ByFields("customer", FieldMatch.Any, [Field.Equal("City", "Atlanta")]);
|
||||||
```
|
```
|
||||||
|
|
||||||
```fsharp
|
```fsharp
|
||||||
// F#
|
// F#
|
||||||
// Count.byField type signature is string -> Field -> Task<int64>
|
// Count.byFields type signature is string -> FieldMatch -> Field seq -> Task<int64>
|
||||||
let! customerCount = Count.byField "customer" (Field.EQ "City" "Atlanta")
|
let! customerCount = Count.byFields "customer" Any [ Field.Equal "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.ByField type signature is Func<string, Field, Task>
|
// Delete.ByFields type signature is Func<string, FieldMatch, IEnumerable<Field>, Task>
|
||||||
await Delete.ByField("customer", Field.EQ("City", "Chicago"));
|
await Delete.ByFields("customer", FieldMatch.Any, [Field.Equal("City", "Chicago")]);
|
||||||
```
|
```
|
||||||
|
|
||||||
```fsharp
|
```fsharp
|
||||||
// F#
|
// F#
|
||||||
// Delete.byField type signature is string -> string -> Op -> obj -> Task<unit>
|
// Delete.byFields type signature is string -> FieldMatch -> Field seq -> Task<unit>
|
||||||
do! Delete.byField "customer" (Field.EQ "City" "Chicago")
|
do! Delete.byFields "customer" Any [ Field.Equal "City" "Chicago" ]
|
||||||
```
|
```
|
||||||
|
|
||||||
## More Information
|
## More Information
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -12,7 +13,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Expecto" Version="10.1.0" />
|
<PackageReference Include="Expecto" Version="10.2.1" />
|
||||||
<PackageReference Include="ThrowawayDb.Postgres" Version="1.4.0" />
|
<PackageReference Include="ThrowawayDb.Postgres" Version="1.4.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Expecto.CSharp;
|
using Expecto.CSharp;
|
||||||
using Expecto;
|
using Expecto;
|
||||||
|
using Microsoft.FSharp.Core;
|
||||||
|
|
||||||
namespace BitBadger.Documents.Tests.CSharp;
|
namespace BitBadger.Documents.Tests.CSharp;
|
||||||
|
|
||||||
@@ -20,14 +21,408 @@ internal class TestSerializer : IDocumentSerializer
|
|||||||
public static class CommonCSharpTests
|
public static class CommonCSharpTests
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unit tests
|
/// Unit tests for the OpSql property of the Comparison discriminated union
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Tests]
|
private static readonly Test OpTests = TestList("Comparison.OpSql",
|
||||||
public static readonly Test Unit = TestList("Common.C# Unit", new[]
|
[
|
||||||
|
TestCase("Equal succeeds", () =>
|
||||||
{
|
{
|
||||||
TestSequenced(
|
Expect.equal(Comparison.NewEqual("").OpSql, "=", "The Equals SQL was not correct");
|
||||||
TestList("Configuration", new[]
|
}),
|
||||||
|
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", () =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -68,157 +463,96 @@ 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", new[]
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unit tests for the Query static class
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Test QueryTests = TestList("Query",
|
||||||
|
[
|
||||||
|
TestCase("StatementWhere succeeds", () =>
|
||||||
{
|
{
|
||||||
TestCase("EQ succeeds", () =>
|
Expect.equal(Query.StatementWhere("q", "r"), "q WHERE r", "Statements not combined correctly");
|
||||||
{
|
|
||||||
Expect.equal(Op.EQ.ToString(), "=", "The equals operator was not correct");
|
|
||||||
}),
|
}),
|
||||||
TestCase("GT succeeds", () =>
|
TestList("Definition",
|
||||||
{
|
[
|
||||||
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("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", new[]
|
|
||||||
{
|
|
||||||
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("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("Query", new[]
|
|
||||||
{
|
|
||||||
TestCase("SelectFromTable succeeds", () =>
|
|
||||||
{
|
|
||||||
Expect.equal(Query.SelectFromTable("test.table"), "SELECT data FROM test.table",
|
|
||||||
"SELECT statement not correct");
|
|
||||||
}),
|
|
||||||
TestCase("WhereById succeeds", () =>
|
|
||||||
{
|
|
||||||
Expect.equal(Query.WhereById("@id"), "data ->> 'Id' = @id", "WHERE clause not correct");
|
|
||||||
}),
|
|
||||||
TestList("WhereByField", new[]
|
|
||||||
{
|
|
||||||
TestCase("succeeds when a logical operator is passed", () =>
|
|
||||||
{
|
|
||||||
Expect.equal(Query.WhereByField(Field.GT("theField", 0), "@test"), "data ->> 'theField' > @test",
|
|
||||||
"WHERE clause not correct");
|
|
||||||
}),
|
|
||||||
TestCase("succeeds when an existence operator is passed", () =>
|
|
||||||
{
|
|
||||||
Expect.equal(Query.WhereByField(Field.NEX("thatField"), ""), "data ->> 'thatField' IS NULL",
|
|
||||||
"WHERE clause not correct");
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
TestList("Definition", new[]
|
|
||||||
{
|
|
||||||
TestCase("EnsureTableFor succeeds", () =>
|
TestCase("EnsureTableFor succeeds", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(Query.Definition.EnsureTableFor("my.table", "JSONB"),
|
Expect.equal(Query.Definition.EnsureTableFor("my.table", "JSONB"),
|
||||||
"CREATE TABLE IF NOT EXISTS my.table (data JSONB NOT NULL)",
|
"CREATE TABLE IF NOT EXISTS my.table (data JSONB NOT NULL)",
|
||||||
"CREATE TABLE statement not constructed correctly");
|
"CREATE TABLE statement not constructed correctly");
|
||||||
}),
|
}),
|
||||||
TestList("EnsureKey", new[]
|
TestList("EnsureKey",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a schema is present", () =>
|
TestCase("succeeds when a schema is present", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(Query.Definition.EnsureKey("test.table"),
|
Expect.equal(Query.Definition.EnsureKey("test.table", Dialect.SQLite),
|
||||||
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data->>'Id'))",
|
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data->>'Id'))",
|
||||||
"CREATE INDEX for key statement with schema not constructed correctly");
|
"CREATE INDEX for key statement with schema not constructed correctly");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when a schema is not present", () =>
|
TestCase("succeeds when a schema is not present", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(Query.Definition.EnsureKey("table"),
|
Expect.equal(Query.Definition.EnsureKey("table", Dialect.PostgreSQL),
|
||||||
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON table ((data->>'Id'))",
|
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON table ((data->>'Id'))",
|
||||||
"CREATE INDEX for key statement without schema not constructed correctly");
|
"CREATE INDEX for key statement without schema not constructed correctly");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestCase("EnsureIndexOn succeeds for multiple fields and directions", () =>
|
TestList("EnsureIndexOn",
|
||||||
|
[
|
||||||
|
TestCase("succeeds for multiple fields and directions", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(
|
Expect.equal(
|
||||||
Query.Definition.EnsureIndexOn("test.table", "gibberish",
|
Query.Definition.EnsureIndexOn("test.table", "gibberish",
|
||||||
new[] { "taco", "guac DESC", "salsa ASC" }),
|
["taco", "guac DESC", "salsa ASC"], Dialect.SQLite),
|
||||||
"CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table "
|
"CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table "
|
||||||
+ "((data->>'taco'), (data->>'guac') DESC, (data->>'salsa') ASC)",
|
+ "((data->>'taco'), (data->>'guac') DESC, (data->>'salsa') ASC)",
|
||||||
"CREATE INDEX for multiple field statement incorrect");
|
"CREATE INDEX for multiple field statement incorrect");
|
||||||
})
|
|
||||||
}),
|
}),
|
||||||
|
TestCase("succeeds for nested PostgreSQL field", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(
|
||||||
|
Query.Definition.EnsureIndexOn("tbl", "nest", ["a.b.c"], Dialect.PostgreSQL),
|
||||||
|
"CREATE INDEX IF NOT EXISTS idx_tbl_nest ON tbl ((data#>>'{a,b,c}'))",
|
||||||
|
"CREATE INDEX for nested PostgreSQL field incorrect");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for nested SQLite field", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(
|
||||||
|
Query.Definition.EnsureIndexOn("tbl", "nest", ["a.b.c"], Dialect.SQLite),
|
||||||
|
"CREATE INDEX IF NOT EXISTS idx_tbl_nest ON tbl ((data->'a'->'b'->>'c'))",
|
||||||
|
"CREATE INDEX for nested SQLite field incorrect");
|
||||||
|
})
|
||||||
|
])
|
||||||
|
]),
|
||||||
TestCase("Insert succeeds", () =>
|
TestCase("Insert succeeds", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(Query.Insert("tbl"), "INSERT INTO tbl VALUES (@data)", "INSERT statement not correct");
|
Expect.equal(Query.Insert("tbl"), "INSERT INTO tbl VALUES (@data)", "INSERT statement not correct");
|
||||||
@@ -226,70 +560,106 @@ public static class CommonCSharpTests
|
|||||||
TestCase("Save succeeds", () =>
|
TestCase("Save succeeds", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(Query.Save("tbl"),
|
Expect.equal(Query.Save("tbl"),
|
||||||
$"INSERT INTO tbl VALUES (@data) ON CONFLICT ((data ->> 'Id')) DO UPDATE SET data = EXCLUDED.data",
|
"INSERT INTO tbl VALUES (@data) ON CONFLICT ((data->>'Id')) DO UPDATE SET data = EXCLUDED.data",
|
||||||
"INSERT ON CONFLICT UPDATE statement not correct");
|
"INSERT ON CONFLICT UPDATE statement not correct");
|
||||||
}),
|
}),
|
||||||
|
TestCase("Count succeeds", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(Query.Count("tbl"), "SELECT COUNT(*) AS it FROM tbl", "Count query not correct");
|
||||||
|
}),
|
||||||
|
TestCase("Exists succeeds", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(Query.Exists("tbl", "chicken"), "SELECT EXISTS (SELECT 1 FROM tbl WHERE chicken) AS it",
|
||||||
|
"Exists query not correct");
|
||||||
|
}),
|
||||||
|
TestCase("Find succeeds", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(Query.Find("test.table"), "SELECT data FROM test.table", "Find query not correct");
|
||||||
|
}),
|
||||||
TestCase("Update succeeds", () =>
|
TestCase("Update succeeds", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(Query.Update("tbl"), "UPDATE tbl SET data = @data WHERE data ->> 'Id' = @id",
|
Expect.equal(Query.Update("tbl"), "UPDATE tbl SET data = @data", "Update query not correct");
|
||||||
"UPDATE full statement not correct");
|
|
||||||
}),
|
}),
|
||||||
TestList("Count", new[]
|
TestCase("Delete succeeds", () =>
|
||||||
{
|
{
|
||||||
TestCase("All succeeds", () =>
|
Expect.equal(Query.Delete("tbl"), "DELETE FROM tbl", "Delete query not correct");
|
||||||
{
|
|
||||||
Expect.equal(Query.Count.All("tbl"), "SELECT COUNT(*) AS it FROM tbl", "Count query not correct");
|
|
||||||
}),
|
}),
|
||||||
TestCase("ByField succeeds", () =>
|
TestList("OrderBy",
|
||||||
|
[
|
||||||
|
TestCase("succeeds for no fields", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(Query.Count.ByField("tbl", Field.EQ("thatField", 0)),
|
Expect.equal(Query.OrderBy([], Dialect.PostgreSQL), "", "Order By should have been blank (PostgreSQL)");
|
||||||
"SELECT COUNT(*) AS it FROM tbl WHERE data ->> 'thatField' = @field",
|
Expect.equal(Query.OrderBy([], Dialect.SQLite), "", "Order By should have been blank (SQLite)");
|
||||||
"JSON field text comparison count query not correct");
|
}),
|
||||||
|
TestCase("succeeds for PostgreSQL with one field and no direction", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(Query.OrderBy([Field.Named("TestField")], Dialect.PostgreSQL),
|
||||||
|
" ORDER BY data->>'TestField'", "Order By not constructed correctly");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for SQLite with one field and no direction", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(Query.OrderBy([Field.Named("TestField")], Dialect.SQLite),
|
||||||
|
" ORDER BY data->>'TestField'", "Order By not constructed correctly");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for PostgreSQL with multiple fields and direction", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(
|
||||||
|
Query.OrderBy(
|
||||||
|
[
|
||||||
|
Field.Named("Nested.Test.Field DESC"), Field.Named("AnotherField"),
|
||||||
|
Field.Named("It DESC")
|
||||||
|
], Dialect.PostgreSQL),
|
||||||
|
" ORDER BY data#>>'{Nested,Test,Field}' DESC, data->>'AnotherField', data->>'It' DESC",
|
||||||
|
"Order By not constructed correctly");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for SQLite with multiple fields and direction", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(
|
||||||
|
Query.OrderBy(
|
||||||
|
[
|
||||||
|
Field.Named("Nested.Test.Field DESC"), Field.Named("AnotherField"),
|
||||||
|
Field.Named("It DESC")
|
||||||
|
], Dialect.SQLite),
|
||||||
|
" ORDER BY data->'Nested'->'Test'->>'Field' DESC, data->>'AnotherField', data->>'It' DESC",
|
||||||
|
"Order By not constructed correctly");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for PostgreSQL numeric fields", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(Query.OrderBy([Field.Named("n:Test")], Dialect.PostgreSQL),
|
||||||
|
" ORDER BY (data->>'Test')::numeric", "Order By not constructed correctly for numeric field");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for SQLite numeric fields", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(Query.OrderBy([Field.Named("n:Test")], Dialect.SQLite), " ORDER BY data->>'Test'",
|
||||||
|
"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");
|
||||||
})
|
})
|
||||||
}),
|
])
|
||||||
TestList("Exists", new[]
|
]);
|
||||||
{
|
|
||||||
TestCase("ById succeeds", () =>
|
/// <summary>
|
||||||
{
|
/// Unit tests
|
||||||
Expect.equal(Query.Exists.ById("tbl"),
|
/// </summary>
|
||||||
"SELECT EXISTS (SELECT 1 FROM tbl WHERE data ->> 'Id' = @id) AS it",
|
[Tests]
|
||||||
"ID existence query not correct");
|
public static readonly Test Unit = TestList("Common.C# Unit",
|
||||||
}),
|
[
|
||||||
TestCase("ByField succeeds", () =>
|
OpTests,
|
||||||
{
|
FieldTests,
|
||||||
Expect.equal(Query.Exists.ByField("tbl", Field.LT("Test", 0)),
|
FieldMatchTests,
|
||||||
"SELECT EXISTS (SELECT 1 FROM tbl WHERE data ->> 'Test' < @field) AS it",
|
ParameterNameTests,
|
||||||
"JSON field text comparison exists query not correct");
|
AutoIdTests,
|
||||||
})
|
QueryTests,
|
||||||
}),
|
TestSequenced(ConfigurationTests)
|
||||||
TestList("Find", new[]
|
]);
|
||||||
{
|
|
||||||
TestCase("ById succeeds", () =>
|
|
||||||
{
|
|
||||||
Expect.equal(Query.Find.ById("tbl"), "SELECT data FROM tbl WHERE data ->> 'Id' = @id",
|
|
||||||
"SELECT by ID query not correct");
|
|
||||||
}),
|
|
||||||
TestCase("ByField succeeds", () =>
|
|
||||||
{
|
|
||||||
Expect.equal(Query.Find.ByField("tbl", Field.GE("Golf", 0)),
|
|
||||||
"SELECT data FROM tbl WHERE data ->> 'Golf' >= @field",
|
|
||||||
"SELECT by JSON comparison query not correct");
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
TestList("Delete", new[]
|
|
||||||
{
|
|
||||||
TestCase("ById succeeds", () =>
|
|
||||||
{
|
|
||||||
Expect.equal(Query.Delete.ById("tbl"), "DELETE FROM tbl WHERE data ->> 'Id' = @id",
|
|
||||||
"DELETE by ID query not correct");
|
|
||||||
}),
|
|
||||||
TestCase("ByField succeeds", () =>
|
|
||||||
{
|
|
||||||
Expect.equal(Query.Delete.ByField("tbl", Field.NEX("gone")),
|
|
||||||
"DELETE FROM tbl WHERE data ->> 'gone' IS NULL",
|
|
||||||
"DELETE by JSON comparison query not correct");
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,17 +31,17 @@ public class PostgresCSharpExtensionTests
|
|||||||
/// Integration tests for the SQLite extension methods
|
/// Integration tests for the SQLite extension methods
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Tests]
|
[Tests]
|
||||||
public static readonly Test Integration = TestList("Postgres.C#.Extensions", new[]
|
public static readonly Test Integration = TestList("Postgres.C#.Extensions",
|
||||||
{
|
[
|
||||||
TestList("CustomList", new[]
|
TestList("CustomList",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when data is found", async () =>
|
TestCase("succeeds when data is found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var docs = await conn.CustomList(Query.SelectFromTable(PostgresDb.TableName), Parameters.None,
|
var docs = await conn.CustomList(Query.Find(PostgresDb.TableName), Parameters.None,
|
||||||
Results.FromData<JsonDocument>);
|
Results.FromData<JsonDocument>);
|
||||||
Expect.equal(docs.Count, 5, "There should have been 5 documents returned");
|
Expect.equal(docs.Count, 5, "There should have been 5 documents returned");
|
||||||
}),
|
}),
|
||||||
@@ -53,13 +53,13 @@ public class PostgresCSharpExtensionTests
|
|||||||
|
|
||||||
var docs = await conn.CustomList(
|
var docs = await conn.CustomList(
|
||||||
$"SELECT data FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath",
|
$"SELECT data FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath",
|
||||||
new[] { Tuple.Create("@path", Sql.@string("$.NumValue ? (@ > 100)")) },
|
[Tuple.Create("@path", Sql.@string("$.NumValue ? (@ > 100)"))],
|
||||||
Results.FromData<JsonDocument>);
|
Results.FromData<JsonDocument>);
|
||||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("CustomSingle", new[]
|
TestList("CustomSingle",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a row is found", async () =>
|
TestCase("succeeds when a row is found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -67,7 +67,7 @@ public class PostgresCSharpExtensionTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await conn.CustomSingle($"SELECT data FROM {PostgresDb.TableName} WHERE data ->> 'Id' = @id",
|
var doc = await conn.CustomSingle($"SELECT data FROM {PostgresDb.TableName} WHERE data ->> 'Id' = @id",
|
||||||
new[] { Tuple.Create("@id", Sql.@string("one")) }, Results.FromData<JsonDocument>);
|
[Tuple.Create("@id", Sql.@string("one"))], Results.FromData<JsonDocument>);
|
||||||
Expect.isNotNull(doc, "There should have been a document returned");
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
Expect.equal(doc.Id, "one", "The incorrect document was returned");
|
Expect.equal(doc.Id, "one", "The incorrect document was returned");
|
||||||
}),
|
}),
|
||||||
@@ -78,12 +78,12 @@ public class PostgresCSharpExtensionTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await conn.CustomSingle($"SELECT data FROM {PostgresDb.TableName} WHERE data ->> 'Id' = @id",
|
var doc = await conn.CustomSingle($"SELECT data FROM {PostgresDb.TableName} WHERE data ->> 'Id' = @id",
|
||||||
new[] { Tuple.Create("@id", Sql.@string("eighty")) }, Results.FromData<JsonDocument>);
|
[Tuple.Create("@id", Sql.@string("eighty"))], Results.FromData<JsonDocument>);
|
||||||
Expect.isNull(doc, "There should not have been a document returned");
|
Expect.isNull(doc, "There should not have been a document returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("CustomNonQuery", new[]
|
TestList("CustomNonQuery",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when operating on data", async () =>
|
TestCase("succeeds when operating on data", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -102,12 +102,12 @@ public class PostgresCSharpExtensionTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await conn.CustomNonQuery($"DELETE FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath",
|
await conn.CustomNonQuery($"DELETE FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath",
|
||||||
new[] { Tuple.Create("@path", Sql.@string("$.NumValue ? (@ > 100)")) });
|
[Tuple.Create("@path", Sql.@string("$.NumValue ? (@ > 100)"))]);
|
||||||
|
|
||||||
var remaining = await conn.CountAll(PostgresDb.TableName);
|
var remaining = await conn.CountAll(PostgresDb.TableName);
|
||||||
Expect.equal(remaining, 5, "There should be 5 documents remaining in the table");
|
Expect.equal(remaining, 5, "There should be 5 documents remaining in the table");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestCase("Scalar succeeds", async () =>
|
TestCase("Scalar succeeds", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -119,58 +119,64 @@ public class PostgresCSharpExtensionTests
|
|||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
var tableExists = () => conn.CustomScalar(
|
|
||||||
"SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'ensured') AS it", Parameters.None,
|
|
||||||
Results.ToExists);
|
|
||||||
var keyExists = () => conn.CustomScalar(
|
|
||||||
"SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_key') AS it", Parameters.None,
|
|
||||||
Results.ToExists);
|
|
||||||
|
|
||||||
var exists = await tableExists();
|
var exists = await TableExists();
|
||||||
var alsoExists = await keyExists();
|
var alsoExists = await KeyExists();
|
||||||
Expect.isFalse(exists, "The table should not exist already");
|
Expect.isFalse(exists, "The table should not exist already");
|
||||||
Expect.isFalse(alsoExists, "The key index should not exist already");
|
Expect.isFalse(alsoExists, "The key index should not exist already");
|
||||||
|
|
||||||
await conn.EnsureTable("ensured");
|
await conn.EnsureTable("ensured");
|
||||||
exists = await tableExists();
|
exists = await TableExists();
|
||||||
alsoExists = await keyExists();
|
alsoExists = await KeyExists();
|
||||||
Expect.isTrue(exists, "The table should now exist");
|
Expect.isTrue(exists, "The table should now exist");
|
||||||
Expect.isTrue(alsoExists, "The key index should now exist");
|
Expect.isTrue(alsoExists, "The key index should now exist");
|
||||||
|
return;
|
||||||
|
|
||||||
|
Task<bool> KeyExists() =>
|
||||||
|
conn.CustomScalar("SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_key') AS it",
|
||||||
|
Parameters.None, Results.ToExists);
|
||||||
|
Task<bool> TableExists() =>
|
||||||
|
conn.CustomScalar("SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'ensured') AS it",
|
||||||
|
Parameters.None, Results.ToExists);
|
||||||
}),
|
}),
|
||||||
TestCase("EnsureDocumentIndex succeeds", async () =>
|
TestCase("EnsureDocumentIndex succeeds", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
var indexExists = () => conn.CustomScalar(
|
|
||||||
"SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_document') AS it", Parameters.None,
|
|
||||||
Results.ToExists);
|
|
||||||
|
|
||||||
var exists = await indexExists();
|
var exists = await IndexExists();
|
||||||
Expect.isFalse(exists, "The index should not exist already");
|
Expect.isFalse(exists, "The index should not exist already");
|
||||||
|
|
||||||
await conn.EnsureTable("ensured");
|
await conn.EnsureTable("ensured");
|
||||||
await conn.EnsureDocumentIndex("ensured", DocumentIndex.Optimized);
|
await conn.EnsureDocumentIndex("ensured", DocumentIndex.Optimized);
|
||||||
exists = await indexExists();
|
exists = await IndexExists();
|
||||||
Expect.isTrue(exists, "The index should now exist");
|
Expect.isTrue(exists, "The index should now exist");
|
||||||
|
return;
|
||||||
|
|
||||||
|
Task<bool> IndexExists() =>
|
||||||
|
conn.CustomScalar("SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_document') AS it",
|
||||||
|
Parameters.None, Results.ToExists);
|
||||||
}),
|
}),
|
||||||
TestCase("EnsureFieldIndex succeeds", async () =>
|
TestCase("EnsureFieldIndex succeeds", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
var indexExists = () => conn.CustomScalar(
|
|
||||||
"SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_test') AS it", Parameters.None,
|
|
||||||
Results.ToExists);
|
|
||||||
|
|
||||||
var exists = await indexExists();
|
var exists = await IndexExists();
|
||||||
Expect.isFalse(exists, "The index should not exist already");
|
Expect.isFalse(exists, "The index should not exist already");
|
||||||
|
|
||||||
await conn.EnsureTable("ensured");
|
await conn.EnsureTable("ensured");
|
||||||
await conn.EnsureFieldIndex("ensured", "test", new[] { "Id", "Category" });
|
await conn.EnsureFieldIndex("ensured", "test", ["Id", "Category"]);
|
||||||
exists = await indexExists();
|
exists = await IndexExists();
|
||||||
Expect.isTrue(exists, "The index should now exist");
|
Expect.isTrue(exists, "The index should now exist");
|
||||||
|
return;
|
||||||
|
|
||||||
|
Task<bool> IndexExists() =>
|
||||||
|
conn.CustomScalar("SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_test') AS it",
|
||||||
|
Parameters.None, Results.ToExists);
|
||||||
}),
|
}),
|
||||||
TestList("Insert", new[]
|
TestList("Insert",
|
||||||
{
|
[
|
||||||
TestCase("succeeds", async () =>
|
TestCase("succeeds", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -198,9 +204,9 @@ public class PostgresCSharpExtensionTests
|
|||||||
// This is what should have happened
|
// This is what should have happened
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("save", new[]
|
TestList("Save",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is inserted", async () =>
|
TestCase("succeeds when a document is inserted", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -230,7 +236,7 @@ public class PostgresCSharpExtensionTests
|
|||||||
Expect.isNotNull(after, "There should have been a document returned post-update");
|
Expect.isNotNull(after, "There should have been a document returned post-update");
|
||||||
Expect.equal(after.Sub!.Foo, "c", "The updated document is not correct");
|
Expect.equal(after.Sub!.Foo, "c", "The updated document is not correct");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestCase("CountAll succeeds", async () =>
|
TestCase("CountAll succeeds", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -240,13 +246,14 @@ public class PostgresCSharpExtensionTests
|
|||||||
var theCount = await conn.CountAll(PostgresDb.TableName);
|
var theCount = await conn.CountAll(PostgresDb.TableName);
|
||||||
Expect.equal(theCount, 5, "There should have been 5 matching documents");
|
Expect.equal(theCount, 5, "There should have been 5 matching documents");
|
||||||
}),
|
}),
|
||||||
TestCase("CountByField succeeds", async () =>
|
TestCase("CountByFields succeeds", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var theCount = await conn.CountByField(PostgresDb.TableName, Field.EQ("Value", "purple"));
|
var theCount = await conn.CountByFields(PostgresDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.Equal("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 () =>
|
||||||
@@ -267,8 +274,8 @@ public class PostgresCSharpExtensionTests
|
|||||||
var theCount = await conn.CountByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ > 5)");
|
var theCount = await conn.CountByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ > 5)");
|
||||||
Expect.equal(theCount, 3, "There should have been 3 matching documents");
|
Expect.equal(theCount, 3, "There should have been 3 matching documents");
|
||||||
}),
|
}),
|
||||||
TestList("ExistsById", new[]
|
TestList("ExistsById",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document exists", async () =>
|
TestCase("succeeds when a document exists", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -287,16 +294,16 @@ public class PostgresCSharpExtensionTests
|
|||||||
var exists = await conn.ExistsById(PostgresDb.TableName, "seven");
|
var exists = await conn.ExistsById(PostgresDb.TableName, "seven");
|
||||||
Expect.isFalse(exists, "There should not have been an existing document");
|
Expect.isFalse(exists, "There should not have been an existing document");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("ExistsByField", new[]
|
TestList("ExistsByField",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when documents exist", async () =>
|
TestCase("succeeds when documents exist", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var exists = await conn.ExistsByField(PostgresDb.TableName, Field.EX("Sub"));
|
var exists = await conn.ExistsByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Exists("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 () =>
|
||||||
@@ -305,12 +312,13 @@ public class PostgresCSharpExtensionTests
|
|||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var exists = await conn.ExistsByField(PostgresDb.TableName, Field.EQ("NumValue", "six"));
|
var exists =
|
||||||
|
await conn.ExistsByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", "six")]);
|
||||||
Expect.isFalse(exists, "There should not have been existing documents");
|
Expect.isFalse(exists, "There should not have been existing documents");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("ExistsByContains", new[]
|
TestList("ExistsByContains",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when documents exist", async () =>
|
TestCase("succeeds when documents exist", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -329,9 +337,9 @@ public class PostgresCSharpExtensionTests
|
|||||||
var exists = await conn.ExistsByContains(PostgresDb.TableName, new { Nothing = "none" });
|
var exists = await conn.ExistsByContains(PostgresDb.TableName, new { Nothing = "none" });
|
||||||
Expect.isFalse(exists, "There should not have been any existing documents");
|
Expect.isFalse(exists, "There should not have been any existing documents");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("ExistsByJsonPath", new[]
|
TestList("ExistsByJsonPath",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when documents exist", async () =>
|
TestCase("succeeds when documents exist", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -350,9 +358,9 @@ public class PostgresCSharpExtensionTests
|
|||||||
var exists = await conn.ExistsByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ > 1000)");
|
var exists = await conn.ExistsByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ > 1000)");
|
||||||
Expect.isFalse(exists, "There should not have been any existing documents");
|
Expect.isFalse(exists, "There should not have been any existing documents");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("FindAll", new[]
|
TestList("FindAll",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when there is data", async () =>
|
TestCase("succeeds when there is data", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -372,9 +380,47 @@ public class PostgresCSharpExtensionTests
|
|||||||
var results = await conn.FindAll<JsonDocument>(PostgresDb.TableName);
|
var results = await conn.FindAll<JsonDocument>(PostgresDb.TableName);
|
||||||
Expect.isEmpty(results, "There should have been no documents returned");
|
Expect.isEmpty(results, "There should have been no documents returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("FindById", new[]
|
TestList("FindAllOrdered",
|
||||||
|
[
|
||||||
|
TestCase("succeeds when ordering numerically", async () =>
|
||||||
{
|
{
|
||||||
|
await using var db = PostgresDb.BuildDb();
|
||||||
|
await using var conn = MkConn(db);
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var results =
|
||||||
|
await conn.FindAllOrdered<JsonDocument>(PostgresDb.TableName, [Field.Named("n:NumValue")]);
|
||||||
|
Expect.hasLength(results, 5, "There should have been 5 documents returned");
|
||||||
|
Expect.equal(string.Join('|', results.Select(x => x.Id)), "one|three|two|four|five",
|
||||||
|
"The documents were not ordered correctly");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds when ordering numerically descending", async () =>
|
||||||
|
{
|
||||||
|
await using var db = PostgresDb.BuildDb();
|
||||||
|
await using var conn = MkConn(db);
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var results =
|
||||||
|
await conn.FindAllOrdered<JsonDocument>(PostgresDb.TableName, [Field.Named("n:NumValue DESC")]);
|
||||||
|
Expect.hasLength(results, 5, "There should have been 5 documents returned");
|
||||||
|
Expect.equal(string.Join('|', results.Select(x => x.Id)), "five|four|two|three|one",
|
||||||
|
"The documents were not ordered correctly");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds when ordering alphabetically", async () =>
|
||||||
|
{
|
||||||
|
await using var db = PostgresDb.BuildDb();
|
||||||
|
await using var conn = MkConn(db);
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var results = await conn.FindAllOrdered<JsonDocument>(PostgresDb.TableName, [Field.Named("Id DESC")]);
|
||||||
|
Expect.hasLength(results, 5, "There should have been 5 documents returned");
|
||||||
|
Expect.equal(string.Join('|', results.Select(x => x.Id)), "two|three|one|four|five",
|
||||||
|
"The documents were not ordered correctly");
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
TestList("FindById",
|
||||||
|
[
|
||||||
TestCase("succeeds when a document is found", async () =>
|
TestCase("succeeds when a document is found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -394,16 +440,17 @@ public class PostgresCSharpExtensionTests
|
|||||||
var doc = await conn.FindById<string, JsonDocument>(PostgresDb.TableName, "three hundred eighty-seven");
|
var doc = await conn.FindById<string, JsonDocument>(PostgresDb.TableName, "three hundred eighty-seven");
|
||||||
Expect.isNull(doc, "There should not have been a document returned");
|
Expect.isNull(doc, "There should not have been a document returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("FindByField", new[]
|
TestList("FindByFields",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when documents are found", async () =>
|
TestCase("succeeds when documents are found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var docs = await conn.FindByField<JsonDocument>(PostgresDb.TableName, Field.EQ("Value", "another"));
|
var docs = await conn.FindByFields<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.Equal("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 () =>
|
||||||
@@ -412,12 +459,40 @@ public class PostgresCSharpExtensionTests
|
|||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var docs = await conn.FindByField<JsonDocument>(PostgresDb.TableName, Field.EQ("Value", "mauve"));
|
var docs = await conn.FindByFields<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.Equal("Value", "mauve")]);
|
||||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("FindByContains", new[]
|
TestList("FindByFieldsOrdered",
|
||||||
|
[
|
||||||
|
TestCase("succeeds when documents are found", async () =>
|
||||||
{
|
{
|
||||||
|
await using var db = PostgresDb.BuildDb();
|
||||||
|
await using var conn = MkConn(db);
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var docs = await conn.FindByFieldsOrdered<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.Equal("Value", "purple")], [Field.Named("Id")]);
|
||||||
|
Expect.hasLength(docs, 2, "There should have been two document returned");
|
||||||
|
Expect.equal(string.Join('|', docs.Select(x => x.Id)), "five|four",
|
||||||
|
"The documents were not ordered correctly");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds when documents are not found", async () =>
|
||||||
|
{
|
||||||
|
await using var db = PostgresDb.BuildDb();
|
||||||
|
await using var conn = MkConn(db);
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var docs = await conn.FindByFieldsOrdered<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.Equal("Value", "purple")], [Field.Named("Id DESC")]);
|
||||||
|
Expect.hasLength(docs, 2, "There should have been two document returned");
|
||||||
|
Expect.equal(string.Join('|', docs.Select(x => x.Id)), "four|five",
|
||||||
|
"The documents were not ordered correctly");
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
TestList("FindByContains",
|
||||||
|
[
|
||||||
TestCase("succeeds when documents are found", async () =>
|
TestCase("succeeds when documents are found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -437,9 +512,37 @@ public class PostgresCSharpExtensionTests
|
|||||||
var docs = await conn.FindByContains<JsonDocument>(PostgresDb.TableName, new { Value = "mauve" });
|
var docs = await conn.FindByContains<JsonDocument>(PostgresDb.TableName, new { Value = "mauve" });
|
||||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("FindByJsonPath", new[]
|
TestList("FindByContainsOrdered",
|
||||||
|
[
|
||||||
|
// Id = two, Sub.Bar = blue; Id = four, Sub.Bar = red
|
||||||
|
TestCase("succeeds when sorting ascending", async () =>
|
||||||
{
|
{
|
||||||
|
await using var db = PostgresDb.BuildDb();
|
||||||
|
await using var conn = MkConn(db);
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var docs = await conn.FindByContainsOrdered<JsonDocument>(PostgresDb.TableName,
|
||||||
|
new { Sub = new { Foo = "green" } }, [Field.Named("Sub.Bar")]);
|
||||||
|
Expect.hasLength(docs, 2, "There should have been two documents returned");
|
||||||
|
Expect.equal(string.Join('|', docs.Select(x => x.Id)), "two|four",
|
||||||
|
"Documents not ordered correctly");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds when sorting descending", async () =>
|
||||||
|
{
|
||||||
|
await using var db = PostgresDb.BuildDb();
|
||||||
|
await using var conn = MkConn(db);
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var docs = await conn.FindByContainsOrdered<JsonDocument>(PostgresDb.TableName,
|
||||||
|
new { Sub = new { Foo = "green" } }, [Field.Named("Sub.Bar DESC")]);
|
||||||
|
Expect.hasLength(docs, 2, "There should have been two documents returned");
|
||||||
|
Expect.equal(string.Join('|', docs.Select(x => x.Id)), "four|two",
|
||||||
|
"Documents not ordered correctly");
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
TestList("FindByJsonPath",
|
||||||
|
[
|
||||||
TestCase("succeeds when documents are found", async () =>
|
TestCase("succeeds when documents are found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -458,16 +561,45 @@ public class PostgresCSharpExtensionTests
|
|||||||
var docs = await conn.FindByJsonPath<JsonDocument>(PostgresDb.TableName, "$.NumValue ? (@ < 0)");
|
var docs = await conn.FindByJsonPath<JsonDocument>(PostgresDb.TableName, "$.NumValue ? (@ < 0)");
|
||||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("FindFirstByField", new[]
|
TestList("FindByJsonPathOrdered",
|
||||||
|
[
|
||||||
|
// Id = one, NumValue = 0; Id = two, NumValue = 10; Id = three, NumValue = 4
|
||||||
|
TestCase("succeeds when sorting ascending", async () =>
|
||||||
{
|
{
|
||||||
|
await using var db = PostgresDb.BuildDb();
|
||||||
|
await using var conn = MkConn(db);
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var docs = await conn.FindByJsonPathOrdered<JsonDocument>(PostgresDb.TableName, "$.NumValue ? (@ < 15)",
|
||||||
|
[Field.Named("n:NumValue")]);
|
||||||
|
Expect.hasLength(docs, 3, "There should have been 3 documents returned");
|
||||||
|
Expect.equal(string.Join('|', docs.Select(x => x.Id)), "one|three|two",
|
||||||
|
"Documents not ordered correctly");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds when sorting descending", async () =>
|
||||||
|
{
|
||||||
|
await using var db = PostgresDb.BuildDb();
|
||||||
|
await using var conn = MkConn(db);
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var docs = await conn.FindByJsonPathOrdered<JsonDocument>(PostgresDb.TableName, "$.NumValue ? (@ < 15)",
|
||||||
|
[Field.Named("n:NumValue DESC")]);
|
||||||
|
Expect.hasLength(docs, 3, "There should have been 3 documents returned");
|
||||||
|
Expect.equal(string.Join('|', docs.Select(x => x.Id)), "two|three|one",
|
||||||
|
"Documents not ordered correctly");
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
TestList("FindFirstByFields",
|
||||||
|
[
|
||||||
TestCase("succeeds when a document is found", async () =>
|
TestCase("succeeds when a document is found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await conn.FindFirstByField<JsonDocument>(PostgresDb.TableName, Field.EQ("Value", "another"));
|
var doc = await conn.FindFirstByFields<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.Equal("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");
|
||||||
}),
|
}),
|
||||||
@@ -477,9 +609,10 @@ public class PostgresCSharpExtensionTests
|
|||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await conn.FindFirstByField<JsonDocument>(PostgresDb.TableName, Field.EQ("Value", "purple"));
|
var doc = await conn.FindFirstByFields<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.Equal("Value", "purple")]);
|
||||||
Expect.isNotNull(doc, "There should have been a document returned");
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
Expect.contains(new[] { "five", "four" }, doc.Id, "An incorrect document was returned");
|
Expect.contains(["five", "four"], doc.Id, "An incorrect document was returned");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when a document is not found", async () =>
|
TestCase("succeeds when a document is not found", async () =>
|
||||||
{
|
{
|
||||||
@@ -487,12 +620,38 @@ public class PostgresCSharpExtensionTests
|
|||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await conn.FindFirstByField<JsonDocument>(PostgresDb.TableName, Field.EQ("Value", "absent"));
|
var doc = await conn.FindFirstByFields<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.Equal("Value", "absent")]);
|
||||||
Expect.isNull(doc, "There should not have been a document returned");
|
Expect.isNull(doc, "There should not have been a document returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("FindFirstByContains", new[]
|
TestList("FindFirstByFieldsOrdered",
|
||||||
|
[
|
||||||
|
TestCase("succeeds when sorting ascending", async () =>
|
||||||
{
|
{
|
||||||
|
await using var db = PostgresDb.BuildDb();
|
||||||
|
await using var conn = MkConn(db);
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var doc = await conn.FindFirstByFieldsOrdered<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.Equal("Value", "purple")], [Field.Named("Id")]);
|
||||||
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
|
Expect.equal("five", doc.Id, "An incorrect document was returned");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds when a document is not found", async () =>
|
||||||
|
{
|
||||||
|
await using var db = PostgresDb.BuildDb();
|
||||||
|
await using var conn = MkConn(db);
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var doc = await conn.FindFirstByFieldsOrdered<JsonDocument>(PostgresDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.Equal("Value", "purple")], [Field.Named("Id DESC")]);
|
||||||
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
|
Expect.equal("four", doc.Id, "An incorrect document was returned");
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
TestList("FindFirstByContains",
|
||||||
|
[
|
||||||
TestCase("succeeds when a document is found", async () =>
|
TestCase("succeeds when a document is found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -512,7 +671,7 @@ public class PostgresCSharpExtensionTests
|
|||||||
var doc = await conn.FindFirstByContains<JsonDocument>(PostgresDb.TableName,
|
var doc = await conn.FindFirstByContains<JsonDocument>(PostgresDb.TableName,
|
||||||
new { Sub = new { Foo = "green" } });
|
new { Sub = new { Foo = "green" } });
|
||||||
Expect.isNotNull(doc, "There should have been a document returned");
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
Expect.contains(new[] { "two", "four" }, doc.Id, "An incorrect document was returned");
|
Expect.contains(["two", "four"], doc.Id, "An incorrect document was returned");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when a document is not found", async () =>
|
TestCase("succeeds when a document is not found", async () =>
|
||||||
{
|
{
|
||||||
@@ -523,9 +682,34 @@ public class PostgresCSharpExtensionTests
|
|||||||
var doc = await conn.FindFirstByContains<JsonDocument>(PostgresDb.TableName, new { Value = "absent" });
|
var doc = await conn.FindFirstByContains<JsonDocument>(PostgresDb.TableName, new { Value = "absent" });
|
||||||
Expect.isNull(doc, "There should not have been a document returned");
|
Expect.isNull(doc, "There should not have been a document returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("FindFirstByJsonPath", new[]
|
TestList("FindFirstByContainsOrdered",
|
||||||
|
[
|
||||||
|
TestCase("succeeds when sorting ascending", async () =>
|
||||||
{
|
{
|
||||||
|
await using var db = PostgresDb.BuildDb();
|
||||||
|
await using var conn = MkConn(db);
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var doc = await conn.FindFirstByContainsOrdered<JsonDocument>(PostgresDb.TableName,
|
||||||
|
new { Sub = new { Foo = "green" } }, [Field.Named("Value")]);
|
||||||
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
|
Expect.equal("two", doc.Id, "An incorrect document was returned");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds when sorting descending", async () =>
|
||||||
|
{
|
||||||
|
await using var db = PostgresDb.BuildDb();
|
||||||
|
await using var conn = MkConn(db);
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var doc = await conn.FindFirstByContainsOrdered<JsonDocument>(PostgresDb.TableName,
|
||||||
|
new { Sub = new { Foo = "green" } }, [Field.Named("Value DESC")]);
|
||||||
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
|
Expect.equal("four", doc.Id, "An incorrect document was returned");
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
TestList("FindFirstByJsonPath",
|
||||||
|
[
|
||||||
TestCase("succeeds when a document is found", async () =>
|
TestCase("succeeds when a document is found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -546,7 +730,7 @@ public class PostgresCSharpExtensionTests
|
|||||||
var doc = await conn.FindFirstByJsonPath<JsonDocument>(PostgresDb.TableName,
|
var doc = await conn.FindFirstByJsonPath<JsonDocument>(PostgresDb.TableName,
|
||||||
"$.Sub.Foo ? (@ == \"green\")");
|
"$.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(new[] { "two", "four" }, doc.Id, "An incorrect document was returned");
|
Expect.contains(["two", "four"], doc.Id, "An incorrect document was returned");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when a document is not found", async () =>
|
TestCase("succeeds when a document is not found", async () =>
|
||||||
{
|
{
|
||||||
@@ -557,9 +741,34 @@ public class PostgresCSharpExtensionTests
|
|||||||
var doc = await conn.FindFirstByJsonPath<JsonDocument>(PostgresDb.TableName, "$.Id ? (@ == \"nope\")");
|
var doc = await conn.FindFirstByJsonPath<JsonDocument>(PostgresDb.TableName, "$.Id ? (@ == \"nope\")");
|
||||||
Expect.isNull(doc, "There should not have been a document returned");
|
Expect.isNull(doc, "There should not have been a document returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("UpdateById", new[]
|
TestList("FindFirstByJsonPathOrdered",
|
||||||
|
[
|
||||||
|
TestCase("succeeds when sorting ascending", async () =>
|
||||||
{
|
{
|
||||||
|
await using var db = PostgresDb.BuildDb();
|
||||||
|
await using var conn = MkConn(db);
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var doc = await conn.FindFirstByJsonPathOrdered<JsonDocument>(PostgresDb.TableName,
|
||||||
|
"$.Sub.Foo ? (@ == \"green\")", [Field.Named("Sub.Bar")]);
|
||||||
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
|
Expect.equal("two", doc.Id, "An incorrect document was returned");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds when sorting descending", async () =>
|
||||||
|
{
|
||||||
|
await using var db = PostgresDb.BuildDb();
|
||||||
|
await using var conn = MkConn(db);
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var doc = await conn.FindFirstByJsonPathOrdered<JsonDocument>(PostgresDb.TableName,
|
||||||
|
"$.Sub.Foo ? (@ == \"green\")", [Field.Named("Sub.Bar DESC")]);
|
||||||
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
|
Expect.equal("four", doc.Id, "An incorrect document was returned");
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
TestList("UpdateById",
|
||||||
|
[
|
||||||
TestCase("succeeds when a document is updated", async () =>
|
TestCase("succeeds when a document is updated", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -588,9 +797,9 @@ public class PostgresCSharpExtensionTests
|
|||||||
await conn.UpdateById(PostgresDb.TableName, "test",
|
await conn.UpdateById(PostgresDb.TableName, "test",
|
||||||
new JsonDocument { Id = "x", Sub = new() { Foo = "blue", Bar = "red" } });
|
new JsonDocument { Id = "x", Sub = new() { Foo = "blue", Bar = "red" } });
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("UpdateByFunc", new[]
|
TestList("UpdateByFunc",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is updated", async () =>
|
TestCase("succeeds when a document is updated", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -617,9 +826,9 @@ public class PostgresCSharpExtensionTests
|
|||||||
await conn.UpdateByFunc(PostgresDb.TableName, doc => doc.Id,
|
await conn.UpdateByFunc(PostgresDb.TableName, doc => doc.Id,
|
||||||
new JsonDocument { Id = "one", Value = "le un", NumValue = 1 });
|
new JsonDocument { Id = "one", Value = "le un", NumValue = 1 });
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("PatchById", new[]
|
TestList("PatchById",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is updated", async () =>
|
TestCase("succeeds when a document is updated", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -641,17 +850,19 @@ public class PostgresCSharpExtensionTests
|
|||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await conn.PatchById(PostgresDb.TableName, "test", new { Foo = "green" });
|
await conn.PatchById(PostgresDb.TableName, "test", new { Foo = "green" });
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("PatchByField", new[]
|
TestList("PatchByFields",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is updated", async () =>
|
TestCase("succeeds when a document is updated", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await conn.PatchByField(PostgresDb.TableName, Field.EQ("Value", "purple"), new { NumValue = 77 });
|
await conn.PatchByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("Value", "purple")],
|
||||||
var after = await conn.CountByField(PostgresDb.TableName, Field.EQ("NumValue", "77"));
|
new { NumValue = 77 });
|
||||||
|
var after = await conn.CountByFields(PostgresDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.Equal("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 () =>
|
||||||
@@ -662,11 +873,12 @@ 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.PatchByField(PostgresDb.TableName, Field.EQ("Value", "burgundy"), new { Foo = "green" });
|
await conn.PatchByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("Value", "burgundy")],
|
||||||
|
new { Foo = "green" });
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("PatchByContains", new[]
|
TestList("PatchByContains",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is updated", async () =>
|
TestCase("succeeds when a document is updated", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -687,9 +899,9 @@ public class PostgresCSharpExtensionTests
|
|||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await conn.PatchByContains(PostgresDb.TableName, new { Value = "burgundy" }, new { Foo = "green" });
|
await conn.PatchByContains(PostgresDb.TableName, new { Value = "burgundy" }, new { Foo = "green" });
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("PatchByJsonPath", new[]
|
TestList("PatchByJsonPath",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is updated", async () =>
|
TestCase("succeeds when a document is updated", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -710,16 +922,16 @@ public class PostgresCSharpExtensionTests
|
|||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await conn.PatchByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ < 0)", new { Foo = "green" });
|
await conn.PatchByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ < 0)", new { Foo = "green" });
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("RemoveFieldsById", new[]
|
TestList("RemoveFieldsById",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when multiple fields are removed", async () =>
|
TestCase("succeeds when multiple fields are removed", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await conn.RemoveFieldsById(PostgresDb.TableName, "two", new[] { "Sub", "Value" });
|
await conn.RemoveFieldsById(PostgresDb.TableName, "two", ["Sub", "Value"]);
|
||||||
var updated = await Find.ById<string, JsonDocument>(PostgresDb.TableName, "two");
|
var updated = await Find.ById<string, JsonDocument>(PostgresDb.TableName, "two");
|
||||||
Expect.isNotNull(updated, "The updated document should have been retrieved");
|
Expect.isNotNull(updated, "The updated document should have been retrieved");
|
||||||
Expect.equal(updated.Value, "", "The string value should have been removed");
|
Expect.equal(updated.Value, "", "The string value should have been removed");
|
||||||
@@ -731,7 +943,7 @@ public class PostgresCSharpExtensionTests
|
|||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await conn.RemoveFieldsById(PostgresDb.TableName, "two", new[] { "Sub" });
|
await conn.RemoveFieldsById(PostgresDb.TableName, "two", ["Sub"]);
|
||||||
var updated = await Find.ById<string, JsonDocument>(PostgresDb.TableName, "two");
|
var updated = await Find.ById<string, JsonDocument>(PostgresDb.TableName, "two");
|
||||||
Expect.isNotNull(updated, "The updated document should have been retrieved");
|
Expect.isNotNull(updated, "The updated document should have been retrieved");
|
||||||
Expect.notEqual(updated.Value, "", "The string value should not have been removed");
|
Expect.notEqual(updated.Value, "", "The string value should not have been removed");
|
||||||
@@ -744,7 +956,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.RemoveFieldsById(PostgresDb.TableName, "two", new[] { "AFieldThatIsNotThere" });
|
await conn.RemoveFieldsById(PostgresDb.TableName, "two", ["AFieldThatIsNotThere"]);
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when no document is matched", async () =>
|
TestCase("succeeds when no document is matched", async () =>
|
||||||
{
|
{
|
||||||
@@ -752,19 +964,19 @@ public class PostgresCSharpExtensionTests
|
|||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await conn.RemoveFieldsById(PostgresDb.TableName, "two", new[] { "Value" });
|
await conn.RemoveFieldsById(PostgresDb.TableName, "two", ["Value"]);
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("RemoveFieldsByField", new[]
|
TestList("RemoveFieldsByFields",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when multiple fields are removed", async () =>
|
TestCase("succeeds when multiple fields are removed", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await conn.RemoveFieldsByField(PostgresDb.TableName, Field.EQ("NumValue", "17"),
|
await conn.RemoveFieldsByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", "17")],
|
||||||
new[] { "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");
|
||||||
Expect.equal(updated.Value, "", "The string value should have been removed");
|
Expect.equal(updated.Value, "", "The string value should have been removed");
|
||||||
@@ -776,7 +988,8 @@ public class PostgresCSharpExtensionTests
|
|||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await conn.RemoveFieldsByField(PostgresDb.TableName, Field.EQ("NumValue", "17"), new[] { "Sub" });
|
await conn.RemoveFieldsByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", "17")],
|
||||||
|
["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");
|
||||||
Expect.notEqual(updated.Value, "", "The string value should not have been removed");
|
Expect.notEqual(updated.Value, "", "The string value should not have been removed");
|
||||||
@@ -789,7 +1002,8 @@ 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.RemoveFieldsByField(PostgresDb.TableName, Field.EQ("NumValue", "17"), new[] { "Nothing" });
|
await conn.RemoveFieldsByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", "17")],
|
||||||
|
["Nothing"]);
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when no document is matched", async () =>
|
TestCase("succeeds when no document is matched", async () =>
|
||||||
{
|
{
|
||||||
@@ -797,20 +1011,19 @@ public class PostgresCSharpExtensionTests
|
|||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await conn.RemoveFieldsByField(PostgresDb.TableName, Field.NE("Abracadabra", "apple"),
|
await conn.RemoveFieldsByFields(PostgresDb.TableName, FieldMatch.Any,
|
||||||
new[] { "Value" });
|
[Field.NotEqual("Abracadabra", "apple")], ["Value"]);
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("RemoveFieldsByContains", new[]
|
TestList("RemoveFieldsByContains",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when multiple fields are removed", async () =>
|
TestCase("succeeds when multiple fields are removed", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await conn.RemoveFieldsByContains(PostgresDb.TableName, new { NumValue = 17 },
|
await conn.RemoveFieldsByContains(PostgresDb.TableName, new { NumValue = 17 }, ["Sub", "Value"]);
|
||||||
new[] { "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");
|
||||||
Expect.equal(updated.Value, "", "The string value should have been removed");
|
Expect.equal(updated.Value, "", "The string value should have been removed");
|
||||||
@@ -822,7 +1035,7 @@ public class PostgresCSharpExtensionTests
|
|||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await conn.RemoveFieldsByContains(PostgresDb.TableName, new { NumValue = 17 }, new[] { "Sub" });
|
await conn.RemoveFieldsByContains(PostgresDb.TableName, new { NumValue = 17 }, ["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");
|
||||||
Expect.notEqual(updated.Value, "", "The string value should not have been removed");
|
Expect.notEqual(updated.Value, "", "The string value should not have been removed");
|
||||||
@@ -835,7 +1048,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.RemoveFieldsByContains(PostgresDb.TableName, new { NumValue = 17 }, new[] { "Nothing" });
|
await conn.RemoveFieldsByContains(PostgresDb.TableName, new { NumValue = 17 }, ["Nothing"]);
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when no document is matched", async () =>
|
TestCase("succeeds when no document is matched", async () =>
|
||||||
{
|
{
|
||||||
@@ -843,20 +1056,18 @@ public class PostgresCSharpExtensionTests
|
|||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await conn.RemoveFieldsByContains(PostgresDb.TableName, new { Abracadabra = "apple" },
|
await conn.RemoveFieldsByContains(PostgresDb.TableName, new { Abracadabra = "apple" }, ["Value"]);
|
||||||
new[] { "Value" });
|
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("RemoveFieldsByJsonPath", new[]
|
TestList("RemoveFieldsByJsonPath",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when multiple fields are removed", async () =>
|
TestCase("succeeds when multiple fields are removed", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await conn.RemoveFieldsByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ == 17)",
|
await conn.RemoveFieldsByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ == 17)", ["Sub", "Value"]);
|
||||||
new[] { "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");
|
||||||
Expect.equal(updated.Value, "", "The string value should have been removed");
|
Expect.equal(updated.Value, "", "The string value should have been removed");
|
||||||
@@ -868,7 +1079,7 @@ public class PostgresCSharpExtensionTests
|
|||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await conn.RemoveFieldsByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ == 17)", new[] { "Sub" });
|
await conn.RemoveFieldsByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ == 17)", ["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");
|
||||||
Expect.notEqual(updated.Value, "", "The string value should not have been removed");
|
Expect.notEqual(updated.Value, "", "The string value should not have been removed");
|
||||||
@@ -881,7 +1092,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.RemoveFieldsByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ == 17)", new[] { "Nothing" });
|
await conn.RemoveFieldsByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ == 17)", ["Nothing"]);
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when no document is matched", async () =>
|
TestCase("succeeds when no document is matched", async () =>
|
||||||
{
|
{
|
||||||
@@ -889,12 +1100,11 @@ public class PostgresCSharpExtensionTests
|
|||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await conn.RemoveFieldsByJsonPath(PostgresDb.TableName, "$.Abracadabra ? (@ == \"apple\")",
|
await conn.RemoveFieldsByJsonPath(PostgresDb.TableName, "$.Abracadabra ? (@ == \"apple\")", ["Value"]);
|
||||||
new[] { "Value" });
|
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("DeleteById", new[]
|
TestList("DeleteById",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is deleted", async () =>
|
TestCase("succeeds when a document is deleted", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -915,16 +1125,16 @@ public class PostgresCSharpExtensionTests
|
|||||||
var remaining = await conn.CountAll(PostgresDb.TableName);
|
var remaining = await conn.CountAll(PostgresDb.TableName);
|
||||||
Expect.equal(remaining, 5, "There should have been 5 documents remaining");
|
Expect.equal(remaining, 5, "There should have been 5 documents remaining");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("DeleteByField", new[]
|
TestList("DeleteByFields",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when documents are deleted", async () =>
|
TestCase("succeeds when documents are deleted", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await conn.DeleteByField(PostgresDb.TableName, Field.NE("Value", "purple"));
|
await conn.DeleteByFields(PostgresDb.TableName, FieldMatch.Any, [Field.NotEqual("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");
|
||||||
}),
|
}),
|
||||||
@@ -934,13 +1144,13 @@ public class PostgresCSharpExtensionTests
|
|||||||
await using var conn = MkConn(db);
|
await using var conn = MkConn(db);
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await conn.DeleteByField(PostgresDb.TableName, Field.EQ("Value", "crimson"));
|
await conn.DeleteByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("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");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("DeleteByContains", new[]
|
TestList("DeleteByContains",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when documents are deleted", async () =>
|
TestCase("succeeds when documents are deleted", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -961,9 +1171,9 @@ public class PostgresCSharpExtensionTests
|
|||||||
var remaining = await conn.CountAll(PostgresDb.TableName);
|
var remaining = await conn.CountAll(PostgresDb.TableName);
|
||||||
Expect.equal(remaining, 5, "There should have been 5 documents remaining");
|
Expect.equal(remaining, 5, "There should have been 5 documents remaining");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("DeleteByJsonPath", new[]
|
TestList("DeleteByJsonPath",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when documents are deleted", async () =>
|
TestCase("succeeds when documents are deleted", async () =>
|
||||||
{
|
{
|
||||||
await using var db = PostgresDb.BuildDb();
|
await using var db = PostgresDb.BuildDb();
|
||||||
@@ -984,6 +1194,6 @@ public class PostgresCSharpExtensionTests
|
|||||||
var remaining = await conn.CountAll(PostgresDb.TableName);
|
var remaining = await conn.CountAll(PostgresDb.TableName);
|
||||||
Expect.equal(remaining, 5, "There should have been 5 documents remaining");
|
Expect.equal(remaining, 5, "There should have been 5 documents remaining");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
});
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -131,7 +131,7 @@ public static class PostgresDb
|
|||||||
var sqlProps = Sql.connect(database.ConnectionString);
|
var sqlProps = Sql.connect(database.ConnectionString);
|
||||||
|
|
||||||
Sql.executeNonQuery(Sql.query(Postgres.Query.Definition.EnsureTable(TableName), sqlProps));
|
Sql.executeNonQuery(Sql.query(Postgres.Query.Definition.EnsureTable(TableName), sqlProps));
|
||||||
Sql.executeNonQuery(Sql.query(Query.Definition.EnsureKey(TableName), sqlProps));
|
Sql.executeNonQuery(Sql.query(Query.Definition.EnsureKey(TableName, Dialect.PostgreSQL), sqlProps));
|
||||||
|
|
||||||
Postgres.Configuration.UseDataSource(MkDataSource(database.ConnectionString));
|
Postgres.Configuration.UseDataSource(MkDataSource(database.ConnectionString));
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Expecto.CSharp;
|
using Expecto.CSharp;
|
||||||
using Expecto;
|
using Expecto;
|
||||||
using Microsoft.Data.Sqlite;
|
|
||||||
using BitBadger.Documents.Sqlite;
|
using BitBadger.Documents.Sqlite;
|
||||||
|
|
||||||
namespace BitBadger.Documents.Tests.CSharp;
|
namespace BitBadger.Documents.Tests.CSharp;
|
||||||
@@ -18,10 +17,10 @@ public static class SqliteCSharpExtensionTests
|
|||||||
/// Integration tests for the SQLite extension methods
|
/// Integration tests for the SQLite extension methods
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Tests]
|
[Tests]
|
||||||
public static readonly Test Integration = TestList("Sqlite.C#.Extensions", new[]
|
public static readonly Test Integration = TestList("Sqlite.C#.Extensions",
|
||||||
{
|
[
|
||||||
TestList("CustomSingle", new[]
|
TestList("CustomSingle",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a row is found", async () =>
|
TestCase("succeeds when a row is found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -29,7 +28,7 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await conn.CustomSingle($"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id",
|
var doc = await conn.CustomSingle($"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id",
|
||||||
new[] { Parameters.Id("one") }, Results.FromData<JsonDocument>);
|
[Parameters.Id("one")], Results.FromData<JsonDocument>);
|
||||||
Expect.isNotNull(doc, "There should have been a document returned");
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
Expect.equal(doc!.Id, "one", "The incorrect document was returned");
|
Expect.equal(doc!.Id, "one", "The incorrect document was returned");
|
||||||
}),
|
}),
|
||||||
@@ -40,19 +39,19 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await conn.CustomSingle($"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id",
|
var doc = await conn.CustomSingle($"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id",
|
||||||
new[] { Parameters.Id("eighty") }, Results.FromData<JsonDocument>);
|
[Parameters.Id("eighty")], Results.FromData<JsonDocument>);
|
||||||
Expect.isNull(doc, "There should not have been a document returned");
|
Expect.isNull(doc, "There should not have been a document returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("CustomList", new[]
|
TestList("CustomList",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when data is found", async () =>
|
TestCase("succeeds when data is found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await using var conn = Sqlite.Configuration.DbConn();
|
await using var conn = Sqlite.Configuration.DbConn();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var docs = await conn.CustomList(Query.SelectFromTable(SqliteDb.TableName), Parameters.None,
|
var docs = await conn.CustomList(Query.Find(SqliteDb.TableName), Parameters.None,
|
||||||
Results.FromData<JsonDocument>);
|
Results.FromData<JsonDocument>);
|
||||||
Expect.equal(docs.Count, 5, "There should have been 5 documents returned");
|
Expect.equal(docs.Count, 5, "There should have been 5 documents returned");
|
||||||
}),
|
}),
|
||||||
@@ -63,13 +62,13 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var docs = await conn.CustomList(
|
var docs = await conn.CustomList(
|
||||||
$"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value",
|
$"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value", [new("@value", 100)],
|
||||||
new[] { new SqliteParameter("@value", 100) }, Results.FromData<JsonDocument>);
|
Results.FromData<JsonDocument>);
|
||||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("CustomNonQuery", new[]
|
TestList("CustomNonQuery",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when operating on data", async () =>
|
TestCase("succeeds when operating on data", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -88,12 +87,12 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await conn.CustomNonQuery($"DELETE FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value",
|
await conn.CustomNonQuery($"DELETE FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value",
|
||||||
new[] { new SqliteParameter("@value", 100) });
|
[new("@value", 100)]);
|
||||||
|
|
||||||
var remaining = await conn.CountAll(SqliteDb.TableName);
|
var remaining = await conn.CountAll(SqliteDb.TableName);
|
||||||
Expect.equal(remaining, 5L, "There should be 5 documents remaining in the table");
|
Expect.equal(remaining, 5L, "There should be 5 documents remaining in the table");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestCase("CustomScalar succeeds", async () =>
|
TestCase("CustomScalar succeeds", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -107,41 +106,44 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await using var conn = Sqlite.Configuration.DbConn();
|
await using var conn = Sqlite.Configuration.DbConn();
|
||||||
|
|
||||||
Func<string, ValueTask<bool>> itExists = async name =>
|
var exists = await ItExists("ensured");
|
||||||
await conn.CustomScalar(
|
var alsoExists = await ItExists("idx_ensured_key");
|
||||||
$"SELECT EXISTS (SELECT 1 FROM {SqliteDb.Catalog} WHERE name = @name) AS it",
|
|
||||||
new SqliteParameter[] { new("@name", name) }, Results.ToExists);
|
|
||||||
|
|
||||||
var exists = await itExists("ensured");
|
|
||||||
var alsoExists = await itExists("idx_ensured_key");
|
|
||||||
Expect.isFalse(exists, "The table should not exist already");
|
Expect.isFalse(exists, "The table should not exist already");
|
||||||
Expect.isFalse(alsoExists, "The key index should not exist already");
|
Expect.isFalse(alsoExists, "The key index should not exist already");
|
||||||
|
|
||||||
await conn.EnsureTable("ensured");
|
await conn.EnsureTable("ensured");
|
||||||
|
|
||||||
exists = await itExists("ensured");
|
exists = await ItExists("ensured");
|
||||||
alsoExists = await itExists("idx_ensured_key");
|
alsoExists = await ItExists("idx_ensured_key");
|
||||||
Expect.isTrue(exists, "The table should now exist");
|
Expect.isTrue(exists, "The table should now exist");
|
||||||
Expect.isTrue(alsoExists, "The key index should now exist");
|
Expect.isTrue(alsoExists, "The key index should now exist");
|
||||||
|
return;
|
||||||
|
|
||||||
|
Task<bool> ItExists(string name) =>
|
||||||
|
conn.CustomScalar($"SELECT EXISTS (SELECT 1 FROM {SqliteDb.Catalog} WHERE name = @name) AS it",
|
||||||
|
[new("@name", name)], Results.ToExists);
|
||||||
}),
|
}),
|
||||||
TestCase("EnsureFieldIndex succeeds", async () =>
|
TestCase("EnsureFieldIndex succeeds", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await using var conn = Sqlite.Configuration.DbConn();
|
await using var conn = Sqlite.Configuration.DbConn();
|
||||||
var indexExists = () => conn.CustomScalar(
|
|
||||||
$"SELECT EXISTS (SELECT 1 FROM {SqliteDb.Catalog} WHERE name = 'idx_ensured_test') AS it",
|
|
||||||
Parameters.None, Results.ToExists);
|
|
||||||
|
|
||||||
var exists = await indexExists();
|
var exists = await IndexExists();
|
||||||
Expect.isFalse(exists, "The index should not exist already");
|
Expect.isFalse(exists, "The index should not exist already");
|
||||||
|
|
||||||
await conn.EnsureTable("ensured");
|
await conn.EnsureTable("ensured");
|
||||||
await conn.EnsureFieldIndex("ensured", "test", new[] { "Id", "Category" });
|
await conn.EnsureFieldIndex("ensured", "test", ["Id", "Category"]);
|
||||||
exists = await indexExists();
|
exists = await IndexExists();
|
||||||
Expect.isTrue(exists, "The index should now exist");
|
Expect.isTrue(exists, "The index should now exist");
|
||||||
|
return;
|
||||||
|
|
||||||
|
Task<bool> IndexExists() =>
|
||||||
|
conn.CustomScalar(
|
||||||
|
$"SELECT EXISTS (SELECT 1 FROM {SqliteDb.Catalog} WHERE name = 'idx_ensured_test') AS it",
|
||||||
|
Parameters.None, Results.ToExists);
|
||||||
}),
|
}),
|
||||||
TestList("Insert", new[]
|
TestList("Insert",
|
||||||
{
|
[
|
||||||
TestCase("succeeds", async () =>
|
TestCase("succeeds", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -168,9 +170,9 @@ public static class SqliteCSharpExtensionTests
|
|||||||
// This is what is supposed to happen
|
// This is what is supposed to happen
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("Save", new[]
|
TestList("Save",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is inserted", async () =>
|
TestCase("succeeds when a document is inserted", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -203,7 +205,7 @@ public static class SqliteCSharpExtensionTests
|
|||||||
Expect.equal(after!.Id, "test", "The updated document is not correct");
|
Expect.equal(after!.Id, "test", "The updated document is not correct");
|
||||||
Expect.isNull(after.Sub, "There should not have been a sub-document in the updated document");
|
Expect.isNull(after.Sub, "There should not have been a sub-document in the updated document");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestCase("CountAll succeeds", async () =>
|
TestCase("CountAll succeeds", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -219,11 +221,12 @@ 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.CountByField(SqliteDb.TableName, Field.EQ("Value", "purple"));
|
var theCount = await conn.CountByFields(SqliteDb.TableName, FieldMatch.Any,
|
||||||
|
[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", new[]
|
TestList("ExistsById",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document exists", async () =>
|
TestCase("succeeds when a document exists", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -242,16 +245,17 @@ public static class SqliteCSharpExtensionTests
|
|||||||
var exists = await conn.ExistsById(SqliteDb.TableName, "seven");
|
var exists = await conn.ExistsById(SqliteDb.TableName, "seven");
|
||||||
Expect.isFalse(exists, "There should not have been an existing document");
|
Expect.isFalse(exists, "There should not have been an existing document");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("ExistsByField", new[]
|
TestList("ExistsByFields",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when documents exist", async () =>
|
TestCase("succeeds when documents exist", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await using var conn = Sqlite.Configuration.DbConn();
|
await using var conn = Sqlite.Configuration.DbConn();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var exists = await conn.ExistsByField(SqliteDb.TableName, Field.GE("NumValue", 10));
|
var exists = await conn.ExistsByFields(SqliteDb.TableName, FieldMatch.Any,
|
||||||
|
[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 () =>
|
||||||
@@ -260,12 +264,13 @@ 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.ExistsByField(SqliteDb.TableName, Field.EQ("Nothing", "none"));
|
var exists =
|
||||||
|
await conn.ExistsByFields(SqliteDb.TableName, FieldMatch.Any, [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");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("FindAll", new[]
|
TestList("FindAll",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when there is data", async () =>
|
TestCase("succeeds when there is data", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -285,9 +290,46 @@ public static class SqliteCSharpExtensionTests
|
|||||||
var results = await conn.FindAll<JsonDocument>(SqliteDb.TableName);
|
var results = await conn.FindAll<JsonDocument>(SqliteDb.TableName);
|
||||||
Expect.isEmpty(results, "There should have been no documents returned");
|
Expect.isEmpty(results, "There should have been no documents returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("FindById", new[]
|
TestList("FindAllOrdered",
|
||||||
|
[
|
||||||
|
TestCase("succeeds when ordering numerically", async () =>
|
||||||
{
|
{
|
||||||
|
await using var db = await SqliteDb.BuildDb();
|
||||||
|
await using var conn = Sqlite.Configuration.DbConn();
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var results = await conn.FindAllOrdered<JsonDocument>(SqliteDb.TableName, [Field.Named("n:NumValue")]);
|
||||||
|
Expect.hasLength(results, 5, "There should have been 5 documents returned");
|
||||||
|
Expect.equal(string.Join('|', results.Select(x => x.Id)), "one|three|two|four|five",
|
||||||
|
"The documents were not ordered correctly");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds when ordering numerically descending", async () =>
|
||||||
|
{
|
||||||
|
await using var db = await SqliteDb.BuildDb();
|
||||||
|
await using var conn = Sqlite.Configuration.DbConn();
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var results =
|
||||||
|
await conn.FindAllOrdered<JsonDocument>(SqliteDb.TableName, [Field.Named("n:NumValue DESC")]);
|
||||||
|
Expect.hasLength(results, 5, "There should have been 5 documents returned");
|
||||||
|
Expect.equal(string.Join('|', results.Select(x => x.Id)), "five|four|two|three|one",
|
||||||
|
"The documents were not ordered correctly");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds when ordering alphabetically", async () =>
|
||||||
|
{
|
||||||
|
await using var db = await SqliteDb.BuildDb();
|
||||||
|
await using var conn = Sqlite.Configuration.DbConn();
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var results = await conn.FindAllOrdered<JsonDocument>(SqliteDb.TableName, [Field.Named("Id DESC")]);
|
||||||
|
Expect.hasLength(results, 5, "There should have been 5 documents returned");
|
||||||
|
Expect.equal(string.Join('|', results.Select(x => x.Id)), "two|three|one|four|five",
|
||||||
|
"The documents were not ordered correctly");
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
TestList("FindById",
|
||||||
|
[
|
||||||
TestCase("succeeds when a document is found", async () =>
|
TestCase("succeeds when a document is found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -307,16 +349,17 @@ public static class SqliteCSharpExtensionTests
|
|||||||
var doc = await conn.FindById<string, JsonDocument>(SqliteDb.TableName, "eighty-seven");
|
var doc = await conn.FindById<string, JsonDocument>(SqliteDb.TableName, "eighty-seven");
|
||||||
Expect.isNull(doc, "There should not have been a document returned");
|
Expect.isNull(doc, "There should not have been a document returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("FindByField", new[]
|
TestList("FindByFields",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when documents are found", async () =>
|
TestCase("succeeds when documents are found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await using var conn = Sqlite.Configuration.DbConn();
|
await using var conn = Sqlite.Configuration.DbConn();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var docs = await conn.FindByField<JsonDocument>(SqliteDb.TableName, Field.GT("NumValue", 15));
|
var docs = await conn.FindByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.Greater("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 () =>
|
||||||
@@ -325,19 +368,46 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await using var conn = Sqlite.Configuration.DbConn();
|
await using var conn = Sqlite.Configuration.DbConn();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var docs = await conn.FindByField<JsonDocument>(SqliteDb.TableName, Field.EQ("Value", "mauve"));
|
var docs = await conn.FindByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.Equal("Value", "mauve")]);
|
||||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("FindFirstByField", new[]
|
TestList("ByFieldsOrdered",
|
||||||
|
[
|
||||||
|
TestCase("succeeds when documents are found", async () =>
|
||||||
{
|
{
|
||||||
|
await using var db = await SqliteDb.BuildDb();
|
||||||
|
await using var conn = Sqlite.Configuration.DbConn();
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var docs = await conn.FindByFieldsOrdered<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.Greater("NumValue", 15)], [Field.Named("Id")]);
|
||||||
|
Expect.equal(string.Join('|', docs.Select(x => x.Id)), "five|four",
|
||||||
|
"There should have been two documents returned");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds when documents are not found", async () =>
|
||||||
|
{
|
||||||
|
await using var db = await SqliteDb.BuildDb();
|
||||||
|
await using var conn = Sqlite.Configuration.DbConn();
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var docs = await conn.FindByFieldsOrdered<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.Greater("NumValue", 15)], [Field.Named("Id DESC")]);
|
||||||
|
Expect.equal(string.Join('|', docs.Select(x => x.Id)), "four|five",
|
||||||
|
"There should have been two documents returned");
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
TestList("FindFirstByFields",
|
||||||
|
[
|
||||||
TestCase("succeeds when a document is found", async () =>
|
TestCase("succeeds when a document is found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await using var conn = Sqlite.Configuration.DbConn();
|
await using var conn = Sqlite.Configuration.DbConn();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await conn.FindFirstByField<JsonDocument>(SqliteDb.TableName, Field.EQ("Value", "another"));
|
var doc = await conn.FindFirstByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.Equal("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");
|
||||||
}),
|
}),
|
||||||
@@ -347,9 +417,10 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await using var conn = Sqlite.Configuration.DbConn();
|
await using var conn = Sqlite.Configuration.DbConn();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await conn.FindFirstByField<JsonDocument>(SqliteDb.TableName, Field.EQ("Sub.Foo", "green"));
|
var doc = await conn.FindFirstByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.Equal("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(new[] { "two", "four" }, doc!.Id, "An incorrect document was returned");
|
Expect.contains(["two", "four"], doc!.Id, "An incorrect document was returned");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when a document is not found", async () =>
|
TestCase("succeeds when a document is not found", async () =>
|
||||||
{
|
{
|
||||||
@@ -357,12 +428,38 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await using var conn = Sqlite.Configuration.DbConn();
|
await using var conn = Sqlite.Configuration.DbConn();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await conn.FindFirstByField<JsonDocument>(SqliteDb.TableName, Field.EQ("Value", "absent"));
|
var doc = await conn.FindFirstByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.Equal("Value", "absent")]);
|
||||||
Expect.isNull(doc, "There should not have been a document returned");
|
Expect.isNull(doc, "There should not have been a document returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("UpdateById", new[]
|
TestList("FindFirstByFieldsOrdered",
|
||||||
|
[
|
||||||
|
TestCase("succeeds when sorting ascending", async () =>
|
||||||
{
|
{
|
||||||
|
await using var db = await SqliteDb.BuildDb();
|
||||||
|
await using var conn = Sqlite.Configuration.DbConn();
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var doc = await conn.FindFirstByFieldsOrdered<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.Equal("Sub.Foo", "green")], [Field.Named("Sub.Bar")]);
|
||||||
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
|
Expect.equal("two", doc!.Id, "An incorrect document was returned");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds when sorting descending", async () =>
|
||||||
|
{
|
||||||
|
await using var db = await SqliteDb.BuildDb();
|
||||||
|
await using var conn = Sqlite.Configuration.DbConn();
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var doc = await conn.FindFirstByFieldsOrdered<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.Equal("Sub.Foo", "green")], [Field.Named("Sub.Bar DESC")]);
|
||||||
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
|
Expect.equal("four", doc!.Id, "An incorrect document was returned");
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
TestList("UpdateById",
|
||||||
|
[
|
||||||
TestCase("succeeds when a document is updated", async () =>
|
TestCase("succeeds when a document is updated", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -389,9 +486,9 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await conn.UpdateById(SqliteDb.TableName, "test",
|
await conn.UpdateById(SqliteDb.TableName, "test",
|
||||||
new JsonDocument { Id = "x", Sub = new() { Foo = "blue", Bar = "red" } });
|
new JsonDocument { Id = "x", Sub = new() { Foo = "blue", Bar = "red" } });
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("UpdateByFunc", new[]
|
TestList("UpdateByFunc",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is updated", async () =>
|
TestCase("succeeds when a document is updated", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -418,9 +515,9 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await conn.UpdateByFunc(SqliteDb.TableName, doc => doc.Id,
|
await conn.UpdateByFunc(SqliteDb.TableName, doc => doc.Id,
|
||||||
new JsonDocument { Id = "one", Value = "le un", NumValue = 1 });
|
new JsonDocument { Id = "one", Value = "le un", NumValue = 1 });
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("PatchById", new[]
|
TestList("PatchById",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is updated", async () =>
|
TestCase("succeeds when a document is updated", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -443,17 +540,18 @@ public static class SqliteCSharpExtensionTests
|
|||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await conn.PatchById(SqliteDb.TableName, "test", new { Foo = "green" });
|
await conn.PatchById(SqliteDb.TableName, "test", new { Foo = "green" });
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("PatchByField", new[]
|
TestList("PatchByFields",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is updated", async () =>
|
TestCase("succeeds when a document is updated", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await using var conn = Sqlite.Configuration.DbConn();
|
await using var conn = Sqlite.Configuration.DbConn();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await conn.PatchByField(SqliteDb.TableName, Field.EQ("Value", "purple"), new { NumValue = 77 });
|
await conn.PatchByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("Value", "purple")],
|
||||||
var after = await conn.CountByField(SqliteDb.TableName, Field.EQ("NumValue", 77));
|
new { NumValue = 77 });
|
||||||
|
var after = await conn.CountByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("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 () =>
|
||||||
@@ -464,18 +562,19 @@ 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.PatchByField(SqliteDb.TableName, Field.EQ("Value", "burgundy"), new { Foo = "green" });
|
await conn.PatchByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("Value", "burgundy")],
|
||||||
|
new { Foo = "green" });
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("RemoveFieldsById", new[]
|
TestList("RemoveFieldsById",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when fields are removed", async () =>
|
TestCase("succeeds when fields are removed", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await using var conn = Sqlite.Configuration.DbConn();
|
await using var conn = Sqlite.Configuration.DbConn();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await conn.RemoveFieldsById(SqliteDb.TableName, "two", new[] { "Sub", "Value" });
|
await conn.RemoveFieldsById(SqliteDb.TableName, "two", ["Sub", "Value"]);
|
||||||
var updated = await Find.ById<string, JsonDocument>(SqliteDb.TableName, "two");
|
var updated = await Find.ById<string, JsonDocument>(SqliteDb.TableName, "two");
|
||||||
Expect.isNotNull(updated, "The updated document should have been retrieved");
|
Expect.isNotNull(updated, "The updated document should have been retrieved");
|
||||||
Expect.equal(updated.Value, "", "The string value should have been removed");
|
Expect.equal(updated.Value, "", "The string value should have been removed");
|
||||||
@@ -488,7 +587,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.RemoveFieldsById(SqliteDb.TableName, "two", new[] { "AFieldThatIsNotThere" });
|
await conn.RemoveFieldsById(SqliteDb.TableName, "two", ["AFieldThatIsNotThere"]);
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when no document is matched", async () =>
|
TestCase("succeeds when no document is matched", async () =>
|
||||||
{
|
{
|
||||||
@@ -496,18 +595,19 @@ 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.RemoveFieldsById(SqliteDb.TableName, "two", new[] { "Value" });
|
await conn.RemoveFieldsById(SqliteDb.TableName, "two", ["Value"]);
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("RemoveFieldsByField", new[]
|
TestList("RemoveFieldsByFields",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a field is removed", async () =>
|
TestCase("succeeds when a field is removed", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await using var conn = Sqlite.Configuration.DbConn();
|
await using var conn = Sqlite.Configuration.DbConn();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await conn.RemoveFieldsByField(SqliteDb.TableName, Field.EQ("NumValue", 17), new[] { "Sub" });
|
await conn.RemoveFieldsByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("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");
|
||||||
@@ -519,7 +619,8 @@ 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.RemoveFieldsByField(SqliteDb.TableName, Field.EQ("NumValue", 17), new[] { "Nothing" });
|
await conn.RemoveFieldsByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", 17)],
|
||||||
|
["Nothing"]);
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when no document is matched", async () =>
|
TestCase("succeeds when no document is matched", async () =>
|
||||||
{
|
{
|
||||||
@@ -527,11 +628,12 @@ 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.RemoveFieldsByField(SqliteDb.TableName, Field.NE("Abracadabra", "apple"), new[] { "Value" });
|
await conn.RemoveFieldsByFields(SqliteDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.NotEqual("Abracadabra", "apple")], ["Value"]);
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("DeleteById", new[]
|
TestList("DeleteById",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is deleted", async () =>
|
TestCase("succeeds when a document is deleted", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -552,16 +654,16 @@ public static class SqliteCSharpExtensionTests
|
|||||||
var remaining = await conn.CountAll(SqliteDb.TableName);
|
var remaining = await conn.CountAll(SqliteDb.TableName);
|
||||||
Expect.equal(remaining, 5L, "There should have been 5 documents remaining");
|
Expect.equal(remaining, 5L, "There should have been 5 documents remaining");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("DeleteByField", new[]
|
TestList("DeleteByFields",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when documents are deleted", async () =>
|
TestCase("succeeds when documents are deleted", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await using var conn = Sqlite.Configuration.DbConn();
|
await using var conn = Sqlite.Configuration.DbConn();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await conn.DeleteByField(SqliteDb.TableName, Field.NE("Value", "purple"));
|
await conn.DeleteByFields(SqliteDb.TableName, FieldMatch.Any, [Field.NotEqual("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");
|
||||||
}),
|
}),
|
||||||
@@ -571,11 +673,11 @@ public static class SqliteCSharpExtensionTests
|
|||||||
await using var conn = Sqlite.Configuration.DbConn();
|
await using var conn = Sqlite.Configuration.DbConn();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await conn.DeleteByField(SqliteDb.TableName, Field.EQ("Value", "crimson"));
|
await conn.DeleteByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("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");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestCase("Clean up database", () => Sqlite.Configuration.UseConnectionString("data source=:memory:"))
|
TestCase("Clean up database", () => Sqlite.Configuration.UseConnectionString("data source=:memory:"))
|
||||||
});
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
using System.Text.Json;
|
using Expecto.CSharp;
|
||||||
using Expecto.CSharp;
|
|
||||||
using Expecto;
|
using Expecto;
|
||||||
using Microsoft.Data.Sqlite;
|
|
||||||
using Microsoft.FSharp.Core;
|
using Microsoft.FSharp.Core;
|
||||||
using BitBadger.Documents.Sqlite;
|
using BitBadger.Documents.Sqlite;
|
||||||
|
|
||||||
@@ -15,51 +13,102 @@ using static Runner;
|
|||||||
public static class SqliteCSharpTests
|
public static class SqliteCSharpTests
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unit tests for the SQLite library
|
/// Unit tests for the Query module of the SQLite library
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly Test Unit = TestList("Unit", new[]
|
private static readonly Test QueryTests = TestList("Query",
|
||||||
|
[
|
||||||
|
TestList("WhereByFields",
|
||||||
|
[
|
||||||
|
TestCase("succeeds for a single field when a logical operator is passed", () =>
|
||||||
{
|
{
|
||||||
TestList("Query", new[]
|
Expect.equal(
|
||||||
|
Sqlite.Query.WhereByFields(FieldMatch.Any,
|
||||||
|
[Field.Greater("theField", 0).WithParameterName("@test")]),
|
||||||
|
"data->>'theField' > @test", "WHERE clause not correct");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for a single field when an existence operator is passed", () =>
|
||||||
{
|
{
|
||||||
|
Expect.equal(Sqlite.Query.WhereByFields(FieldMatch.Any, [Field.NotExists("thatField")]),
|
||||||
|
"data->>'thatField' IS NULL", "WHERE clause not correct");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for a single field when a between operator is passed", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(
|
||||||
|
Sqlite.Query.WhereByFields(FieldMatch.All,
|
||||||
|
[Field.Between("aField", 50, 99).WithParameterName("@range")]),
|
||||||
|
"data->>'aField' BETWEEN @rangemin AND @rangemax", "WHERE clause not correct");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for all multiple fields with logical operators", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(
|
||||||
|
Sqlite.Query.WhereByFields(FieldMatch.All,
|
||||||
|
[Field.Equal("theFirst", "1"), Field.Equal("numberTwo", "2")]),
|
||||||
|
"data->>'theFirst' = @field0 AND data->>'numberTwo' = @field1", "WHERE clause not correct");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for any multiple fields with an existence operator", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(
|
||||||
|
Sqlite.Query.WhereByFields(FieldMatch.Any,
|
||||||
|
[Field.NotExists("thatField"), Field.GreaterOrEqual("thisField", 18)]),
|
||||||
|
"data->>'thatField' IS NULL OR data->>'thisField' >= @field0", "WHERE clause not correct");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds for all multiple fields with between operators", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(
|
||||||
|
Sqlite.Query.WhereByFields(FieldMatch.All,
|
||||||
|
[Field.Between("aField", 50, 99), Field.Between("anotherField", "a", "b")]),
|
||||||
|
"data->>'aField' BETWEEN @field0min AND @field0max AND data->>'anotherField' BETWEEN @field1min AND @field1max",
|
||||||
|
"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", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(Sqlite.Query.WhereById("abc"), "data->>'Id' = @id", "WHERE clause not correct");
|
||||||
|
}),
|
||||||
|
TestCase("Patch succeeds", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(Sqlite.Query.Patch(SqliteDb.TableName),
|
||||||
|
$"UPDATE {SqliteDb.TableName} SET data = json_patch(data, json(@data))", "Patch query not correct");
|
||||||
|
}),
|
||||||
|
TestCase("RemoveFields succeeds", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(Sqlite.Query.RemoveFields(SqliteDb.TableName, [new("@a", "a"), new("@b", "b")]),
|
||||||
|
$"UPDATE {SqliteDb.TableName} SET data = json_remove(data, @a, @b)",
|
||||||
|
"Field removal query not correct");
|
||||||
|
}),
|
||||||
|
TestCase("ById succeeds", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(Sqlite.Query.ById("test", "14"), "test WHERE data->>'Id' = @id", "By-ID query not correct");
|
||||||
|
}),
|
||||||
|
TestCase("ByFields succeeds", () =>
|
||||||
|
{
|
||||||
|
Expect.equal(Sqlite.Query.ByFields("unit", FieldMatch.Any, [Field.Greater("That", 14)]),
|
||||||
|
"unit WHERE data->>'That' > @field0", "By-Field query not correct");
|
||||||
|
}),
|
||||||
TestCase("Definition.EnsureTable succeeds", () =>
|
TestCase("Definition.EnsureTable succeeds", () =>
|
||||||
{
|
{
|
||||||
Expect.equal(Sqlite.Query.Definition.EnsureTable("tbl"),
|
Expect.equal(Sqlite.Query.Definition.EnsureTable("tbl"),
|
||||||
"CREATE TABLE IF NOT EXISTS tbl (data TEXT NOT NULL)", "CREATE TABLE statement not correct");
|
"CREATE TABLE IF NOT EXISTS tbl (data TEXT NOT NULL)", "CREATE TABLE statement not correct");
|
||||||
}),
|
|
||||||
TestList("Patch", new[]
|
|
||||||
{
|
|
||||||
TestCase("ById succeeds", () =>
|
|
||||||
{
|
|
||||||
Expect.equal(Sqlite.Query.Patch.ById("tbl"),
|
|
||||||
"UPDATE tbl SET data = json_patch(data, json(@data)) WHERE data ->> 'Id' = @id",
|
|
||||||
"UPDATE partial by ID statement not correct");
|
|
||||||
}),
|
|
||||||
TestCase("ByField succeeds", () =>
|
|
||||||
{
|
|
||||||
Expect.equal(Sqlite.Query.Patch.ByField("tbl", Field.NE("Part", 0)),
|
|
||||||
"UPDATE tbl SET data = json_patch(data, json(@data)) WHERE data ->> 'Part' <> @field",
|
|
||||||
"UPDATE partial by JSON comparison query not correct");
|
|
||||||
})
|
})
|
||||||
}),
|
]);
|
||||||
TestList("RemoveFields", new[]
|
|
||||||
{
|
/// <summary>
|
||||||
TestCase("ById succeeds", () =>
|
/// Unit tests for the Parameters module of the SQLite library
|
||||||
{
|
/// </summary>
|
||||||
Expect.equal(Sqlite.Query.RemoveFields.ById("tbl", new[] { new SqliteParameter("@name", "one") }),
|
private static readonly Test ParametersTests = TestList("Parameters",
|
||||||
"UPDATE tbl SET data = json_remove(data, @name) WHERE data ->> 'Id' = @id",
|
[
|
||||||
"Remove field by ID query not correct");
|
|
||||||
}),
|
|
||||||
TestCase("ByField succeeds", () =>
|
|
||||||
{
|
|
||||||
Expect.equal(Sqlite.Query.RemoveFields.ByField("tbl", Field.LT("Fly", 0),
|
|
||||||
new[] { new SqliteParameter("@name0", "one"), new SqliteParameter("@name1", "two") }),
|
|
||||||
"UPDATE tbl SET data = json_remove(data, @name0, @name1) WHERE data ->> 'Fly' < @field",
|
|
||||||
"Remove field by field query not correct");
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
TestList("Parameters", new[]
|
|
||||||
{
|
|
||||||
TestCase("Id succeeds", () =>
|
TestCase("Id succeeds", () =>
|
||||||
{
|
{
|
||||||
var theParam = Parameters.Id(7);
|
var theParam = Parameters.Id(7);
|
||||||
@@ -72,10 +121,10 @@ public static class SqliteCSharpTests
|
|||||||
Expect.equal(theParam.ParameterName, "@test", "The parameter name is incorrect");
|
Expect.equal(theParam.ParameterName, "@test", "The parameter name is incorrect");
|
||||||
Expect.equal(theParam.Value, "{\"Nice\":\"job\"}", "The parameter value is incorrect");
|
Expect.equal(theParam.Value, "{\"Nice\":\"job\"}", "The parameter value is incorrect");
|
||||||
}),
|
}),
|
||||||
|
#pragma warning disable CS0618
|
||||||
TestCase("AddField succeeds when adding a parameter", () =>
|
TestCase("AddField succeeds when adding a parameter", () =>
|
||||||
{
|
{
|
||||||
var paramList = Parameters.AddField("@field", Field.EQ("it", 99), Enumerable.Empty<SqliteParameter>())
|
var paramList = Parameters.AddField("@field", Field.Equal("it", 99), []).ToList();
|
||||||
.ToList();
|
|
||||||
Expect.hasLength(paramList, 1, "There should have been a parameter added");
|
Expect.hasLength(paramList, 1, "There should have been a parameter added");
|
||||||
var theParam = paramList[0];
|
var theParam = paramList[0];
|
||||||
Expect.equal(theParam.ParameterName, "@field", "The parameter name is incorrect");
|
Expect.equal(theParam.ParameterName, "@field", "The parameter name is incorrect");
|
||||||
@@ -83,37 +132,30 @@ public static class SqliteCSharpTests
|
|||||||
}),
|
}),
|
||||||
TestCase("AddField succeeds when not adding a parameter", () =>
|
TestCase("AddField succeeds when not adding a parameter", () =>
|
||||||
{
|
{
|
||||||
var paramSeq = Parameters.AddField("@it", Field.EX("Coffee"), Enumerable.Empty<SqliteParameter>());
|
var paramSeq = Parameters.AddField("@it", Field.Exists("Coffee"), []);
|
||||||
Expect.isEmpty(paramSeq, "There should not have been any parameters added");
|
Expect.isEmpty(paramSeq, "There should not have been any parameters added");
|
||||||
}),
|
}),
|
||||||
|
#pragma warning restore CS0618
|
||||||
TestCase("None succeeds", () =>
|
TestCase("None succeeds", () =>
|
||||||
{
|
{
|
||||||
Expect.isEmpty(Parameters.None, "The parameter list should have been empty");
|
Expect.isEmpty(Parameters.None, "The parameter list should have been empty");
|
||||||
})
|
})
|
||||||
})
|
]);
|
||||||
// Results are exhaustively executed in the context of other tests
|
|
||||||
});
|
|
||||||
|
|
||||||
private static readonly List<JsonDocument> TestDocuments = new()
|
// Results are exhaustively executed in the context of other tests
|
||||||
{
|
|
||||||
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 TestDocuments) await Document.Insert(SqliteDb.TableName, doc);
|
foreach (var doc in JsonDocument.TestDocuments) await Document.Insert(SqliteDb.TableName, doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly Test Integration = TestList("Integration", new[]
|
/// <summary>
|
||||||
{
|
/// Integration tests for the Configuration module of the SQLite library
|
||||||
TestCase("Configuration.UseConnectionString succeeds", () =>
|
/// </summary>
|
||||||
|
private static readonly Test ConfigurationTests = TestCase("Configuration.UseConnectionString succeeds", () =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -125,18 +167,22 @@ public static class SqliteCSharpTests
|
|||||||
{
|
{
|
||||||
Sqlite.Configuration.UseConnectionString("Data Source=:memory:");
|
Sqlite.Configuration.UseConnectionString("Data Source=:memory:");
|
||||||
}
|
}
|
||||||
}),
|
});
|
||||||
TestList("Custom", new[]
|
|
||||||
{
|
/// <summary>
|
||||||
TestList("Single", new[]
|
/// Integration tests for the Custom module of the SQLite library
|
||||||
{
|
/// </summary>
|
||||||
|
private static readonly Test CustomTests = TestList("Custom",
|
||||||
|
[
|
||||||
|
TestList("Single",
|
||||||
|
[
|
||||||
TestCase("succeeds when a row is found", async () =>
|
TestCase("succeeds when a row is found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await Custom.Single($"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id",
|
var doc = await Custom.Single($"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id",
|
||||||
new[] { Parameters.Id("one") }, Results.FromData<JsonDocument>);
|
[Parameters.Id("one")], Results.FromData<JsonDocument>);
|
||||||
Expect.isNotNull(doc, "There should have been a document returned");
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
Expect.equal(doc!.Id, "one", "The incorrect document was returned");
|
Expect.equal(doc!.Id, "one", "The incorrect document was returned");
|
||||||
}),
|
}),
|
||||||
@@ -146,18 +192,18 @@ public static class SqliteCSharpTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await Custom.Single($"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id",
|
var doc = await Custom.Single($"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id",
|
||||||
new[] { Parameters.Id("eighty") }, Results.FromData<JsonDocument>);
|
[Parameters.Id("eighty")], Results.FromData<JsonDocument>);
|
||||||
Expect.isNull(doc, "There should not have been a document returned");
|
Expect.isNull(doc, "There should not have been a document returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("List", new[]
|
TestList("List",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when data is found", async () =>
|
TestCase("succeeds when data is found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var docs = await Custom.List(Query.SelectFromTable(SqliteDb.TableName), Parameters.None,
|
var docs = await Custom.List(Query.Find(SqliteDb.TableName), Parameters.None,
|
||||||
Results.FromData<JsonDocument>);
|
Results.FromData<JsonDocument>);
|
||||||
Expect.equal(docs.Count, 5, "There should have been 5 documents returned");
|
Expect.equal(docs.Count, 5, "There should have been 5 documents returned");
|
||||||
}),
|
}),
|
||||||
@@ -167,13 +213,13 @@ public static class SqliteCSharpTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var docs = await Custom.List(
|
var docs = await Custom.List(
|
||||||
$"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value",
|
$"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value", [new("@value", 100)],
|
||||||
new[] { new SqliteParameter("@value", 100) }, Results.FromData<JsonDocument>);
|
Results.FromData<JsonDocument>);
|
||||||
Expect.isEmpty(docs, "There should have been no documents returned");
|
Expect.isEmpty(docs, "There should have been no documents returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("NonQuery", new[]
|
TestList("NonQuery",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when operating on data", async () =>
|
TestCase("succeeds when operating on data", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -190,12 +236,12 @@ public static class SqliteCSharpTests
|
|||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await Custom.NonQuery($"DELETE FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value",
|
await Custom.NonQuery($"DELETE FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value",
|
||||||
new[] { new SqliteParameter("@value", 100) });
|
[new("@value", 100)]);
|
||||||
|
|
||||||
var remaining = await Count.All(SqliteDb.TableName);
|
var remaining = await Count.All(SqliteDb.TableName);
|
||||||
Expect.equal(remaining, 5L, "There should be 5 documents remaining in the table");
|
Expect.equal(remaining, 5L, "There should be 5 documents remaining in the table");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestCase("Scalar succeeds", async () =>
|
TestCase("Scalar succeeds", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -203,9 +249,13 @@ public static class SqliteCSharpTests
|
|||||||
var nbr = await Custom.Scalar("SELECT 5 AS test_value", Parameters.None, rdr => rdr.GetInt32(0));
|
var nbr = await Custom.Scalar("SELECT 5 AS test_value", Parameters.None, rdr => rdr.GetInt32(0));
|
||||||
Expect.equal(nbr, 5, "The query should have returned the number 5");
|
Expect.equal(nbr, 5, "The query should have returned the number 5");
|
||||||
})
|
})
|
||||||
}),
|
]);
|
||||||
TestList("Definition", new[]
|
|
||||||
{
|
/// <summary>
|
||||||
|
/// Integration tests for the Definition module of the SQLite library
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Test DefinitionTests = TestList("Definition",
|
||||||
|
[
|
||||||
TestCase("EnsureTable succeeds", async () =>
|
TestCase("EnsureTable succeeds", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -225,29 +275,36 @@ public static class SqliteCSharpTests
|
|||||||
|
|
||||||
async ValueTask<bool> ItExists(string name)
|
async ValueTask<bool> ItExists(string name)
|
||||||
{
|
{
|
||||||
return await Custom.Scalar(
|
return await Custom.Scalar($"SELECT EXISTS (SELECT 1 FROM {SqliteDb.Catalog} WHERE name = @name) AS it",
|
||||||
$"SELECT EXISTS (SELECT 1 FROM {SqliteDb.Catalog} WHERE name = @name) AS it",
|
[new("@name", name)], Results.ToExists);
|
||||||
new SqliteParameter[] { new("@name", name) }, Results.ToExists);
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
TestCase("EnsureFieldIndex succeeds", async () =>
|
TestCase("EnsureFieldIndex succeeds", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
var indexExists = () => Custom.Scalar(
|
|
||||||
$"SELECT EXISTS (SELECT 1 FROM {SqliteDb.Catalog} WHERE name = 'idx_ensured_test') AS it",
|
|
||||||
Parameters.None, Results.ToExists);
|
|
||||||
|
|
||||||
var exists = await indexExists();
|
var exists = await IndexExists();
|
||||||
Expect.isFalse(exists, "The index should not exist already");
|
Expect.isFalse(exists, "The index should not exist already");
|
||||||
|
|
||||||
await Definition.EnsureTable("ensured");
|
await Definition.EnsureTable("ensured");
|
||||||
await Definition.EnsureFieldIndex("ensured", "test", new[] { "Id", "Category" });
|
await Definition.EnsureFieldIndex("ensured", "test", ["Id", "Category"]);
|
||||||
exists = await indexExists();
|
exists = await IndexExists();
|
||||||
Expect.isTrue(exists, "The index should now exist");
|
Expect.isTrue(exists, "The index should now exist");
|
||||||
|
return;
|
||||||
|
|
||||||
|
Task<bool> IndexExists() => Custom.Scalar(
|
||||||
|
$"SELECT EXISTS (SELECT 1 FROM {SqliteDb.Catalog} WHERE name = 'idx_ensured_test') AS it",
|
||||||
|
Parameters.None, Results.ToExists);
|
||||||
})
|
})
|
||||||
}),
|
]);
|
||||||
TestList("Document.Insert", new[]
|
|
||||||
{
|
/// <summary>
|
||||||
|
/// Integration tests for the Document module of the SQLite library
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Test DocumentTests = TestList("Document",
|
||||||
|
[
|
||||||
|
TestList("Insert",
|
||||||
|
[
|
||||||
TestCase("succeeds", async () =>
|
TestCase("succeeds", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -271,10 +328,87 @@ public static class SqliteCSharpTests
|
|||||||
{
|
{
|
||||||
// This is what is supposed to happen
|
// This is what is supposed to happen
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}),
|
}),
|
||||||
TestList("Document.Save", new[]
|
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",
|
||||||
|
[
|
||||||
TestCase("succeeds when a document is inserted", async () =>
|
TestCase("succeeds when a document is inserted", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -305,9 +439,14 @@ public static class SqliteCSharpTests
|
|||||||
Expect.equal(after!.Id, "test", "The updated document is not correct");
|
Expect.equal(after!.Id, "test", "The updated document is not correct");
|
||||||
Expect.isNull(after.Sub, "There should not have been a sub-document in the updated document");
|
Expect.isNull(after.Sub, "There should not have been a sub-document in the updated document");
|
||||||
})
|
})
|
||||||
}),
|
])
|
||||||
TestList("Count", new[]
|
]);
|
||||||
{
|
|
||||||
|
/// <summary>
|
||||||
|
/// Integration tests for the Count module of the SQLite library
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Test CountTests = TestList("Count",
|
||||||
|
[
|
||||||
TestCase("All succeeds", async () =>
|
TestCase("All succeeds", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -316,19 +455,36 @@ public static class SqliteCSharpTests
|
|||||||
var theCount = await Count.All(SqliteDb.TableName);
|
var theCount = await Count.All(SqliteDb.TableName);
|
||||||
Expect.equal(theCount, 5L, "There should have been 5 matching documents");
|
Expect.equal(theCount, 5L, "There should have been 5 matching documents");
|
||||||
}),
|
}),
|
||||||
TestCase("ByField succeeds", async () =>
|
TestList("ByFields",
|
||||||
|
[
|
||||||
|
TestCase("succeeds for numeric range", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var theCount = await Count.ByField(SqliteDb.TableName, Field.EQ("Value", "purple"));
|
var theCount = await Count.ByFields(SqliteDb.TableName, FieldMatch.Any,
|
||||||
Expect.equal(theCount, 2L, "There should have been 2 matching documents");
|
[Field.Between("NumValue", 10, 20)]);
|
||||||
})
|
Expect.equal(theCount, 3L, "There should have been 3 matching documents");
|
||||||
}),
|
}),
|
||||||
TestList("Exists", new[]
|
TestCase("succeeds for non-numeric range", async () =>
|
||||||
{
|
|
||||||
TestList("ById", new[]
|
|
||||||
{
|
{
|
||||||
|
await using var db = await SqliteDb.BuildDb();
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var theCount = await Count.ByFields(SqliteDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.Between("Value", "aardvark", "apple")]);
|
||||||
|
Expect.equal(theCount, 1L, "There should have been 1 matching document");
|
||||||
|
})
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Integration tests for the Exists module of the SQLite library
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Test ExistsTests = TestList("Exists",
|
||||||
|
[
|
||||||
|
TestList("ById",
|
||||||
|
[
|
||||||
TestCase("succeeds when a document exists", async () =>
|
TestCase("succeeds when a document exists", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -345,15 +501,16 @@ public static class SqliteCSharpTests
|
|||||||
var exists = await Exists.ById(SqliteDb.TableName, "seven");
|
var exists = await Exists.ById(SqliteDb.TableName, "seven");
|
||||||
Expect.isFalse(exists, "There should not have been an existing document");
|
Expect.isFalse(exists, "There should not have been an existing document");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("ByField", new[]
|
TestList("ByFields",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when documents exist", async () =>
|
TestCase("succeeds when documents exist", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var exists = await Exists.ByField(SqliteDb.TableName, Field.GE("NumValue", 10));
|
var exists = await Exists.ByFields(SqliteDb.TableName, FieldMatch.Any,
|
||||||
|
[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 () =>
|
||||||
@@ -361,15 +518,20 @@ 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.ByField(SqliteDb.TableName, Field.EQ("Nothing", "none"));
|
var exists = await Exists.ByFields(SqliteDb.TableName, FieldMatch.Any,
|
||||||
|
[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");
|
||||||
})
|
})
|
||||||
})
|
])
|
||||||
}),
|
]);
|
||||||
TestList("Find", new[]
|
|
||||||
{
|
/// <summary>
|
||||||
TestList("All", new[]
|
/// Integration tests for the Find module of the SQLite library
|
||||||
{
|
/// </summary>
|
||||||
|
private static readonly Test FindTests = TestList("Find",
|
||||||
|
[
|
||||||
|
TestList("All",
|
||||||
|
[
|
||||||
TestCase("succeeds when there is data", async () =>
|
TestCase("succeeds when there is data", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -387,9 +549,42 @@ public static class SqliteCSharpTests
|
|||||||
var results = await Find.All<SubDocument>(SqliteDb.TableName);
|
var results = await Find.All<SubDocument>(SqliteDb.TableName);
|
||||||
Expect.isEmpty(results, "There should have been no documents returned");
|
Expect.isEmpty(results, "There should have been no documents returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("ById", new[]
|
TestList("AllOrdered",
|
||||||
|
[
|
||||||
|
TestCase("succeeds when ordering numerically", async () =>
|
||||||
{
|
{
|
||||||
|
await using var db = await SqliteDb.BuildDb();
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var results = await Find.AllOrdered<JsonDocument>(SqliteDb.TableName, [Field.Named("n:NumValue")]);
|
||||||
|
Expect.hasLength(results, 5, "There should have been 5 documents returned");
|
||||||
|
Expect.equal(string.Join('|', results.Select(x => x.Id)), "one|three|two|four|five",
|
||||||
|
"The documents were not ordered correctly");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds when ordering numerically descending", async () =>
|
||||||
|
{
|
||||||
|
await using var db = await SqliteDb.BuildDb();
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var results = await Find.AllOrdered<JsonDocument>(SqliteDb.TableName, [Field.Named("n:NumValue DESC")]);
|
||||||
|
Expect.hasLength(results, 5, "There should have been 5 documents returned");
|
||||||
|
Expect.equal(string.Join('|', results.Select(x => x.Id)), "five|four|two|three|one",
|
||||||
|
"The documents were not ordered correctly");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds when ordering alphabetically", async () =>
|
||||||
|
{
|
||||||
|
await using var db = await SqliteDb.BuildDb();
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var results = await Find.AllOrdered<JsonDocument>(SqliteDb.TableName, [Field.Named("Id DESC")]);
|
||||||
|
Expect.hasLength(results, 5, "There should have been 5 documents returned");
|
||||||
|
Expect.equal(string.Join('|', results.Select(x => x.Id)), "two|three|one|four|five",
|
||||||
|
"The documents were not ordered correctly");
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
TestList("ById",
|
||||||
|
[
|
||||||
TestCase("succeeds when a document is found", async () =>
|
TestCase("succeeds when a document is found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -407,34 +602,113 @@ public static class SqliteCSharpTests
|
|||||||
var doc = await Find.ById<string, JsonDocument>(SqliteDb.TableName, "twenty two");
|
var doc = await Find.ById<string, JsonDocument>(SqliteDb.TableName, "twenty two");
|
||||||
Expect.isNull(doc, "There should not have been a document returned");
|
Expect.isNull(doc, "There should not have been a document returned");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("ByField", new[]
|
TestList("ByFields",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when documents are found", async () =>
|
TestCase("succeeds when documents are found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var docs = await Find.ByField<JsonDocument>(SqliteDb.TableName, Field.GT("NumValue", 15));
|
var docs = await Find.ByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.Greater("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.ByField<JsonDocument>(SqliteDb.TableName, Field.EQ("Value", "mauve"));
|
var docs = await Find.ByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.Equal("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");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("FirstByField", new[]
|
TestList("ByFieldsOrdered",
|
||||||
|
[
|
||||||
|
TestCase("succeeds when documents are found", async () =>
|
||||||
{
|
{
|
||||||
|
await using var db = await SqliteDb.BuildDb();
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var docs = await Find.ByFieldsOrdered<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.Greater("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",
|
||||||
|
"The documents were not sorted correctly");
|
||||||
|
}),
|
||||||
|
TestCase("succeeds when documents are not found", async () =>
|
||||||
|
{
|
||||||
|
await using var db = await SqliteDb.BuildDb();
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var docs = await Find.ByFieldsOrdered<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.Greater("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",
|
||||||
|
"The documents were not sorted correctly");
|
||||||
|
}),
|
||||||
|
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",
|
||||||
|
[
|
||||||
TestCase("succeeds when a document is found", async () =>
|
TestCase("succeeds when a document is found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await Find.FirstByField<JsonDocument>(SqliteDb.TableName, Field.EQ("Value", "another"));
|
var doc = await Find.FirstByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.Equal("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");
|
||||||
}),
|
}),
|
||||||
@@ -443,24 +717,53 @@ public static class SqliteCSharpTests
|
|||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await Find.FirstByField<JsonDocument>(SqliteDb.TableName, Field.EQ("Sub.Foo", "green"));
|
var doc = await Find.FirstByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.Equal("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(new[] { "two", "four" }, doc!.Id, "An incorrect document was returned");
|
Expect.contains(["two", "four"], doc!.Id, "An incorrect document was returned");
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when a document is not found", async () =>
|
TestCase("succeeds when a document is not found", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
var doc = await Find.FirstByField<JsonDocument>(SqliteDb.TableName, Field.EQ("Value", "absent"));
|
var doc = await Find.FirstByFields<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.Equal("Value", "absent")]);
|
||||||
Expect.isNull(doc, "There should not have been a document returned");
|
Expect.isNull(doc, "There should not have been a document returned");
|
||||||
})
|
})
|
||||||
})
|
]),
|
||||||
|
TestList("FirstByFieldsOrdered",
|
||||||
|
[
|
||||||
|
TestCase("succeeds when sorting ascending", async () =>
|
||||||
|
{
|
||||||
|
await using var db = await SqliteDb.BuildDb();
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var doc = await Find.FirstByFieldsOrdered<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.Equal("Sub.Foo", "green")], [Field.Named("Sub.Bar")]);
|
||||||
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
|
Expect.equal("two", doc!.Id, "An incorrect document was returned");
|
||||||
}),
|
}),
|
||||||
TestList("Update", new[]
|
TestCase("succeeds when sorting descending", async () =>
|
||||||
{
|
|
||||||
TestList("ById", new[]
|
|
||||||
{
|
{
|
||||||
|
await using var db = await SqliteDb.BuildDb();
|
||||||
|
await LoadDocs();
|
||||||
|
|
||||||
|
var doc = await Find.FirstByFieldsOrdered<JsonDocument>(SqliteDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.Equal("Sub.Foo", "green")], [Field.Named("Sub.Bar DESC")]);
|
||||||
|
Expect.isNotNull(doc, "There should have been a document returned");
|
||||||
|
Expect.equal("four", doc!.Id, "An incorrect document was returned");
|
||||||
|
})
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Integration tests for the Update module of the SQLite library
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Test UpdateTests = TestList("Update",
|
||||||
|
[
|
||||||
|
TestList("ById",
|
||||||
|
[
|
||||||
TestCase("succeeds when a document is updated", async () =>
|
TestCase("succeeds when a document is updated", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -486,9 +789,9 @@ public static class SqliteCSharpTests
|
|||||||
await Update.ById(SqliteDb.TableName, "test",
|
await Update.ById(SqliteDb.TableName, "test",
|
||||||
new JsonDocument { Id = "x", Sub = new() { Foo = "blue", Bar = "red" } });
|
new JsonDocument { Id = "x", Sub = new() { Foo = "blue", Bar = "red" } });
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("ByFunc", new[]
|
TestList("ByFunc",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is updated", async () =>
|
TestCase("succeeds when a document is updated", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -514,12 +817,16 @@ public static class SqliteCSharpTests
|
|||||||
await Update.ByFunc(SqliteDb.TableName, doc => doc.Id,
|
await Update.ByFunc(SqliteDb.TableName, doc => doc.Id,
|
||||||
new JsonDocument { Id = "one", Value = "le un", NumValue = 1 });
|
new JsonDocument { Id = "one", Value = "le un", NumValue = 1 });
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
}),
|
]);
|
||||||
TestList("Patch", new[]
|
|
||||||
{
|
/// <summary>
|
||||||
TestList("ById", new[]
|
/// Integration tests for the Patch module of the SQLite library
|
||||||
{
|
/// </summary>
|
||||||
|
private static readonly Test PatchTests = TestList("Patch",
|
||||||
|
[
|
||||||
|
TestList("ById",
|
||||||
|
[
|
||||||
TestCase("succeeds when a document is updated", async () =>
|
TestCase("succeeds when a document is updated", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -541,16 +848,17 @@ public static class SqliteCSharpTests
|
|||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
await Patch.ById(SqliteDb.TableName, "test", new { Foo = "green" });
|
await Patch.ById(SqliteDb.TableName, "test", new { Foo = "green" });
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("ByField", new[]
|
TestList("ByFields",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a document is updated", async () =>
|
TestCase("succeeds when a document is updated", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await Patch.ByField(SqliteDb.TableName, Field.EQ("Value", "purple"), new { NumValue = 77 });
|
await Patch.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("Value", "purple")],
|
||||||
var after = await Count.ByField(SqliteDb.TableName, Field.EQ("NumValue", 77));
|
new { NumValue = 77 });
|
||||||
|
var after = await Count.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("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 () =>
|
||||||
@@ -561,20 +869,25 @@ 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.ByField(SqliteDb.TableName, Field.EQ("Value", "burgundy"), new { Foo = "green" });
|
await Patch.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("Value", "burgundy")],
|
||||||
|
new { Foo = "green" });
|
||||||
})
|
})
|
||||||
})
|
])
|
||||||
}),
|
]);
|
||||||
TestList("RemoveFields", new[]
|
|
||||||
{
|
/// <summary>
|
||||||
TestList("ById", new[]
|
/// Integration tests for the RemoveFields module of the SQLite library
|
||||||
{
|
/// </summary>
|
||||||
|
private static readonly Test RemoveFieldsTests = TestList("RemoveFields",
|
||||||
|
[
|
||||||
|
TestList("ById",
|
||||||
|
[
|
||||||
TestCase("succeeds when fields are removed", async () =>
|
TestCase("succeeds when fields are removed", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await RemoveFields.ById(SqliteDb.TableName, "two", new[] { "Sub", "Value" });
|
await RemoveFields.ById(SqliteDb.TableName, "two", ["Sub", "Value"]);
|
||||||
var updated = await Find.ById<string, JsonDocument>(SqliteDb.TableName, "two");
|
var updated = await Find.ById<string, JsonDocument>(SqliteDb.TableName, "two");
|
||||||
Expect.isNotNull(updated, "The updated document should have been retrieved");
|
Expect.isNotNull(updated, "The updated document should have been retrieved");
|
||||||
Expect.equal(updated.Value, "", "The string value should have been removed");
|
Expect.equal(updated.Value, "", "The string value should have been removed");
|
||||||
@@ -586,24 +899,24 @@ 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.ById(SqliteDb.TableName, "two", new[] { "AFieldThatIsNotThere" });
|
await RemoveFields.ById(SqliteDb.TableName, "two", ["AFieldThatIsNotThere"]);
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when no document is matched", async () =>
|
TestCase("succeeds when no document is matched", async () =>
|
||||||
{
|
{
|
||||||
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.ById(SqliteDb.TableName, "two", new[] { "Value" });
|
await RemoveFields.ById(SqliteDb.TableName, "two", ["Value"]);
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("ByField", new[]
|
TestList("ByFields",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when a field is removed", async () =>
|
TestCase("succeeds when a field is removed", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await RemoveFields.ByField(SqliteDb.TableName, Field.EQ("NumValue", 17), new[] { "Sub" });
|
await RemoveFields.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("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");
|
||||||
@@ -614,21 +927,27 @@ 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.ByField(SqliteDb.TableName, Field.EQ("NumValue", 17), new[] { "Nothing" });
|
await RemoveFields.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", 17)],
|
||||||
|
["Nothing"]);
|
||||||
}),
|
}),
|
||||||
TestCase("succeeds when no document is matched", async () =>
|
TestCase("succeeds when no document is matched", async () =>
|
||||||
{
|
{
|
||||||
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.ByField(SqliteDb.TableName, Field.NE("Abracadabra", "apple"), new[] { "Value" });
|
await RemoveFields.ByFields(SqliteDb.TableName, FieldMatch.Any,
|
||||||
|
[Field.NotEqual("Abracadabra", "apple")], ["Value"]);
|
||||||
})
|
})
|
||||||
})
|
])
|
||||||
}),
|
]);
|
||||||
TestList("Delete", new[]
|
|
||||||
{
|
/// <summary>
|
||||||
TestList("ById", new[]
|
/// Integration tests for the Delete module of the SQLite library
|
||||||
{
|
/// </summary>
|
||||||
|
private static readonly Test DeleteTests = TestList("Delete",
|
||||||
|
[
|
||||||
|
TestList("ById",
|
||||||
|
[
|
||||||
TestCase("succeeds when a document is deleted", async () =>
|
TestCase("succeeds when a document is deleted", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
@@ -647,15 +966,15 @@ public static class SqliteCSharpTests
|
|||||||
var remaining = await Count.All(SqliteDb.TableName);
|
var remaining = await Count.All(SqliteDb.TableName);
|
||||||
Expect.equal(remaining, 5L, "There should have been 5 documents remaining");
|
Expect.equal(remaining, 5L, "There should have been 5 documents remaining");
|
||||||
})
|
})
|
||||||
}),
|
]),
|
||||||
TestList("ByField", new[]
|
TestList("ByFields",
|
||||||
{
|
[
|
||||||
TestCase("succeeds when documents are deleted", async () =>
|
TestCase("succeeds when documents are deleted", async () =>
|
||||||
{
|
{
|
||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await Delete.ByField(SqliteDb.TableName, Field.NE("Value", "purple"));
|
await Delete.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.NotEqual("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");
|
||||||
}),
|
}),
|
||||||
@@ -664,18 +983,34 @@ public static class SqliteCSharpTests
|
|||||||
await using var db = await SqliteDb.BuildDb();
|
await using var db = await SqliteDb.BuildDb();
|
||||||
await LoadDocs();
|
await LoadDocs();
|
||||||
|
|
||||||
await Delete.ByField(SqliteDb.TableName, Field.EQ("Value", "crimson"));
|
await Delete.ByFields(SqliteDb.TableName, FieldMatch.All, [Field.Equal("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");
|
||||||
})
|
})
|
||||||
})
|
])
|
||||||
}),
|
]);
|
||||||
TestCase("Clean up database", () => Sqlite.Configuration.UseConnectionString("data source=:memory:"))
|
|
||||||
});
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All tests for SQLite C# functions and methods
|
/// All tests for SQLite C# functions and methods
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Tests]
|
[Tests]
|
||||||
public static readonly Test All = TestList("Sqlite.C#", new[] { Unit, TestSequenced(Integration) });
|
public static readonly Test All = TestList("Sqlite.C#",
|
||||||
|
[
|
||||||
|
TestList("Unit", [QueryTests, ParametersTests]),
|
||||||
|
TestSequenced(TestList("Integration",
|
||||||
|
[
|
||||||
|
ConfigurationTests,
|
||||||
|
CustomTests,
|
||||||
|
DefinitionTests,
|
||||||
|
DocumentTests,
|
||||||
|
CountTests,
|
||||||
|
ExistsTests,
|
||||||
|
FindTests,
|
||||||
|
UpdateTests,
|
||||||
|
PatchTests,
|
||||||
|
RemoveFieldsTests,
|
||||||
|
DeleteTests,
|
||||||
|
TestCase("Clean up database", () => Sqlite.Configuration.UseConnectionString("data source=:memory:"))
|
||||||
|
]))
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
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; } = "";
|
||||||
@@ -12,4 +18,32 @@ 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"] }
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,12 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
|
<NoWarn>1182</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="CommonTests.fs" />
|
|
||||||
<Compile Include="Types.fs" />
|
<Compile Include="Types.fs" />
|
||||||
|
<Compile Include="CommonTests.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" />
|
||||||
@@ -15,7 +16,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Expecto" Version="10.1.0" />
|
<PackageReference Include="Expecto" Version="10.2.1" />
|
||||||
|
<PackageReference Update="FSharp.Core" Version="9.0.100" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -6,56 +6,352 @@ open Expecto
|
|||||||
/// Test table name
|
/// Test table name
|
||||||
let tbl = "test_table"
|
let tbl = "test_table"
|
||||||
|
|
||||||
/// Tests which do not hit the database
|
/// Unit tests for the Op DU
|
||||||
let all =
|
let comparisonTests = testList "Comparison.OpSql" [
|
||||||
testList "Common" [
|
test "Equal succeeds" {
|
||||||
testList "Op" [
|
Expect.equal (Equal "").OpSql "=" "The Equals SQL was not correct"
|
||||||
test "EQ succeeds" {
|
|
||||||
Expect.equal (string EQ) "=" "The equals operator was not correct"
|
|
||||||
}
|
}
|
||||||
test "GT succeeds" {
|
test "Greater succeeds" {
|
||||||
Expect.equal (string GT) ">" "The greater than operator was not correct"
|
Expect.equal (Greater "").OpSql ">" "The Greater SQL was not correct"
|
||||||
}
|
}
|
||||||
test "GE succeeds" {
|
test "GreaterOrEqual succeeds" {
|
||||||
Expect.equal (string GE) ">=" "The greater than or equal to operator was not correct"
|
Expect.equal (GreaterOrEqual "").OpSql ">=" "The GreaterOrEqual SQL was not correct"
|
||||||
}
|
}
|
||||||
test "LT succeeds" {
|
test "Less succeeds" {
|
||||||
Expect.equal (string LT) "<" "The less than operator was not correct"
|
Expect.equal (Less "").OpSql "<" "The Less SQL was not correct"
|
||||||
}
|
}
|
||||||
test "LE succeeds" {
|
test "LessOrEqual succeeds" {
|
||||||
Expect.equal (string LE) "<=" "The less than or equal to operator was not correct"
|
Expect.equal (LessOrEqual "").OpSql "<=" "The LessOrEqual SQL was not correct"
|
||||||
}
|
}
|
||||||
test "NE succeeds" {
|
test "NotEqual succeeds" {
|
||||||
Expect.equal (string NE) "<>" "The not equal to operator was not correct"
|
Expect.equal (NotEqual "").OpSql "<>" "The NotEqual SQL was not correct"
|
||||||
}
|
}
|
||||||
test "EX succeeds" {
|
test "Between succeeds" {
|
||||||
Expect.equal (string EX) "IS NOT NULL" """The "exists" operator was not correct"""
|
Expect.equal (Between("", "")).OpSql "BETWEEN" "The Between SQL was not correct"
|
||||||
}
|
}
|
||||||
test "NEX succeeds" {
|
test "In succeeds" {
|
||||||
Expect.equal (string NEX) "IS NULL" """The "not exists" operator was not correct"""
|
Expect.equal (In []).OpSql "IN" "The In SQL was not correct"
|
||||||
|
}
|
||||||
|
test "InArray succeeds" {
|
||||||
|
Expect.equal (InArray("", [])).OpSql "?|" "The InArray SQL 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 "Query" [
|
|
||||||
test "selectFromTable succeeds" {
|
/// Unit tests for the Field class
|
||||||
Expect.equal (Query.selectFromTable tbl) $"SELECT data FROM {tbl}" "SELECT statement not correct"
|
let fieldTests = testList "Field" [
|
||||||
|
test "Equal succeeds" {
|
||||||
|
let field = Field.Equal "Test" 14
|
||||||
|
Expect.equal field.Name "Test" "Field name incorrect"
|
||||||
|
Expect.equal field.Comparison (Equal 14) "Comparison incorrect"
|
||||||
|
Expect.isNone field.ParameterName "The default parameter name should be None"
|
||||||
|
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
||||||
}
|
}
|
||||||
test "whereById succeeds" {
|
test "Greater succeeds" {
|
||||||
Expect.equal (Query.whereById "@id") "data ->> 'Id' = @id" "WHERE clause not correct"
|
let field = Field.Greater "Great" "night"
|
||||||
|
Expect.equal field.Name "Great" "Field name incorrect"
|
||||||
|
Expect.equal field.Comparison (Greater "night") "Comparison incorrect"
|
||||||
|
Expect.isNone field.ParameterName "The default parameter name should be None"
|
||||||
|
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
||||||
}
|
}
|
||||||
testList "whereByField" [
|
test "GreaterOrEqual succeeds" {
|
||||||
test "succeeds when a logical operator is passed" {
|
let field = Field.GreaterOrEqual "Nice" 88L
|
||||||
|
Expect.equal field.Name "Nice" "Field name incorrect"
|
||||||
|
Expect.equal field.Comparison (GreaterOrEqual 88L) "Comparison incorrect"
|
||||||
|
Expect.isNone field.ParameterName "The default parameter name should be None"
|
||||||
|
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
||||||
|
}
|
||||||
|
test "Less succeeds" {
|
||||||
|
let field = Field.Less "Lesser" "seven"
|
||||||
|
Expect.equal field.Name "Lesser" "Field name incorrect"
|
||||||
|
Expect.equal field.Comparison (Less "seven") "Comparison incorrect"
|
||||||
|
Expect.isNone field.ParameterName "The default parameter name should be None"
|
||||||
|
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
||||||
|
}
|
||||||
|
test "LessOrEqual succeeds" {
|
||||||
|
let field = Field.LessOrEqual "Nobody" "KNOWS";
|
||||||
|
Expect.equal field.Name "Nobody" "Field name incorrect"
|
||||||
|
Expect.equal field.Comparison (LessOrEqual "KNOWS") "Comparison incorrect"
|
||||||
|
Expect.isNone field.ParameterName "The default parameter name should be None"
|
||||||
|
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
||||||
|
}
|
||||||
|
test "NotEqual succeeds" {
|
||||||
|
let field = Field.NotEqual "Park" "here"
|
||||||
|
Expect.equal field.Name "Park" "Field name incorrect"
|
||||||
|
Expect.equal field.Comparison (NotEqual "here") "Comparison incorrect"
|
||||||
|
Expect.isNone field.ParameterName "The default parameter name should be None"
|
||||||
|
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
||||||
|
}
|
||||||
|
test "Between succeeds" {
|
||||||
|
let field = Field.Between "Age" 18 49
|
||||||
|
Expect.equal field.Name "Age" "Field name incorrect"
|
||||||
|
Expect.equal field.Comparison (Between(18, 49)) "Comparison incorrect"
|
||||||
|
Expect.isNone field.ParameterName "The default parameter name should be None"
|
||||||
|
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
||||||
|
}
|
||||||
|
test "In succeeds" {
|
||||||
|
let field = Field.In "Here" [| 8; 16; 32 |]
|
||||||
|
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.Comparison Exists "Comparison incorrect"
|
||||||
|
Expect.isNone field.ParameterName "The default parameter name should be None"
|
||||||
|
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
||||||
|
}
|
||||||
|
test "NotExists succeeds" {
|
||||||
|
let field = Field.NotExists "Rad"
|
||||||
|
Expect.equal field.Name "Rad" "Field name incorrect"
|
||||||
|
Expect.equal field.Comparison NotExists "Comparison incorrect"
|
||||||
|
Expect.isNone field.ParameterName "The default parameter name should be None"
|
||||||
|
Expect.isNone field.Qualifier "The default table qualifier should be None"
|
||||||
|
}
|
||||||
|
testList "NameToPath" [
|
||||||
|
test "succeeds for PostgreSQL and a simple name" {
|
||||||
|
Expect.equal "data->>'Simple'" (Field.NameToPath "Simple" PostgreSQL AsSql) "Path not constructed correctly"
|
||||||
|
}
|
||||||
|
test "succeeds for SQLite and a simple name" {
|
||||||
|
Expect.equal "data->>'Simple'" (Field.NameToPath "Simple" SQLite AsSql) "Path not constructed correctly"
|
||||||
|
}
|
||||||
|
test "succeeds for PostgreSQL and a nested name" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.whereByField (Field.GT "theField" 0) "@test")
|
"data#>>'{A,Long,Path,to,the,Property}'"
|
||||||
"data ->> 'theField' > @test"
|
(Field.NameToPath "A.Long.Path.to.the.Property" PostgreSQL AsSql)
|
||||||
"WHERE clause not correct"
|
"Path not constructed correctly"
|
||||||
}
|
}
|
||||||
test "succeeds when an existence operator is passed" {
|
test "succeeds for SQLite and a nested name" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.whereByField (Field.NEX "thatField") "")
|
"data->'A'->'Long'->'Path'->'to'->'the'->>'Property'"
|
||||||
"data ->> 'thatField' IS NULL"
|
(Field.NameToPath "A.Long.Path.to.the.Property" SQLite AsSql)
|
||||||
"WHERE clause not correct"
|
"Path not constructed correctly"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
test "WithParameterName succeeds" {
|
||||||
|
let 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"
|
||||||
|
}
|
||||||
|
test "WithQualifier succeeds" {
|
||||||
|
let 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" [
|
||||||
|
test "succeeds for a PostgreSQL single field with no qualifier" {
|
||||||
|
let field = Field.GreaterOrEqual "SomethingCool" 18
|
||||||
|
Expect.equal "data->>'SomethingCool'" (field.Path PostgreSQL AsSql) "The PostgreSQL path is incorrect"
|
||||||
|
}
|
||||||
|
test "succeeds for a PostgreSQL single field with a qualifier" {
|
||||||
|
let field = { Field.Less "SomethingElse" 9 with Qualifier = Some "this" }
|
||||||
|
Expect.equal "this.data->>'SomethingElse'" (field.Path PostgreSQL AsSql) "The PostgreSQL path is incorrect"
|
||||||
|
}
|
||||||
|
test "succeeds for a PostgreSQL nested field with no qualifier" {
|
||||||
|
let field = Field.Equal "My.Nested.Field" "howdy"
|
||||||
|
Expect.equal "data#>>'{My,Nested,Field}'" (field.Path PostgreSQL AsSql) "The PostgreSQL path is incorrect"
|
||||||
|
}
|
||||||
|
test "succeeds for a PostgreSQL nested field with a qualifier" {
|
||||||
|
let field = { Field.Equal "Nest.Away" "doc" with Qualifier = Some "bird" }
|
||||||
|
Expect.equal "bird.data#>>'{Nest,Away}'" (field.Path PostgreSQL AsSql) "The PostgreSQL path is incorrect"
|
||||||
|
}
|
||||||
|
test "succeeds for a SQLite single field with no qualifier" {
|
||||||
|
let field = Field.GreaterOrEqual "SomethingCool" 18
|
||||||
|
Expect.equal "data->>'SomethingCool'" (field.Path SQLite AsSql) "The SQLite path is incorrect"
|
||||||
|
}
|
||||||
|
test "succeeds for a SQLite single field with a qualifier" {
|
||||||
|
let field = { Field.Less "SomethingElse" 9 with Qualifier = Some "this" }
|
||||||
|
Expect.equal "this.data->>'SomethingElse'" (field.Path SQLite AsSql) "The SQLite path is incorrect"
|
||||||
|
}
|
||||||
|
test "succeeds for a SQLite nested field with no qualifier" {
|
||||||
|
let field = Field.Equal "My.Nested.Field" "howdy"
|
||||||
|
Expect.equal "data->'My'->'Nested'->>'Field'" (field.Path SQLite AsSql) "The SQLite path is incorrect"
|
||||||
|
}
|
||||||
|
test "succeeds for a SQLite nested field with a qualifier" {
|
||||||
|
let field = { Field.Equal "Nest.Away" "doc" with Qualifier = Some "bird" }
|
||||||
|
Expect.equal "bird.data->'Nest'->>'Away'" (field.Path SQLite AsSql) "The SQLite path is incorrect"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
/// Unit tests for the FieldMatch DU
|
||||||
|
let fieldMatchTests = testList "FieldMatch.ToString" [
|
||||||
|
test "succeeds for Any" {
|
||||||
|
Expect.equal (string Any) "OR" "SQL for Any is incorrect"
|
||||||
|
}
|
||||||
|
test "succeeds for All" {
|
||||||
|
Expect.equal (string All) "AND" "SQL for All is incorrect"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
/// Unit tests for the ParameterName class
|
||||||
|
let parameterNameTests = testList "ParameterName.Derive" [
|
||||||
|
test "succeeds with existing name" {
|
||||||
|
let name = ParameterName()
|
||||||
|
Expect.equal (name.Derive(Some "@taco")) "@taco" "Name should have been @taco"
|
||||||
|
Expect.equal (name.Derive None) "@field0" "Counter should not have advanced for named field"
|
||||||
|
}
|
||||||
|
test "succeeds with non-existent name" {
|
||||||
|
let name = ParameterName()
|
||||||
|
Expect.equal (name.Derive None) "@field0" "Anonymous field name should have been returned"
|
||||||
|
Expect.equal (name.Derive None) "@field1" "Counter should have advanced from previous call"
|
||||||
|
Expect.equal (name.Derive None) "@field2" "Counter should have advanced from previous call"
|
||||||
|
Expect.equal (name.Derive None) "@field3" "Counter should have advanced from previous call"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
/// 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" {
|
||||||
|
Expect.equal (Query.statementWhere "x" "y") "x WHERE y" "Statements not combined correctly"
|
||||||
|
}
|
||||||
testList "Definition" [
|
testList "Definition" [
|
||||||
test "ensureTableFor succeeds" {
|
test "ensureTableFor succeeds" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
@@ -66,25 +362,40 @@ let all =
|
|||||||
testList "ensureKey" [
|
testList "ensureKey" [
|
||||||
test "succeeds when a schema is present" {
|
test "succeeds when a schema is present" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.Definition.ensureKey "test.table")
|
(Query.Definition.ensureKey "test.table" PostgreSQL)
|
||||||
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data->>'Id'))"
|
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data->>'Id'))"
|
||||||
"CREATE INDEX for key statement with schema not constructed correctly"
|
"CREATE INDEX for key statement with schema not constructed correctly"
|
||||||
}
|
}
|
||||||
test "succeeds when a schema is not present" {
|
test "succeeds when a schema is not present" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.Definition.ensureKey "table")
|
(Query.Definition.ensureKey "table" SQLite)
|
||||||
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON table ((data->>'Id'))"
|
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON table ((data->>'Id'))"
|
||||||
"CREATE INDEX for key statement without schema not constructed correctly"
|
"CREATE INDEX for key statement without schema not constructed correctly"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
test "ensureIndexOn succeeds for multiple fields and directions" {
|
testList "ensureIndexOn" [
|
||||||
|
test "succeeds for multiple fields and directions" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.Definition.ensureIndexOn "test.table" "gibberish" [ "taco"; "guac DESC"; "salsa ASC" ])
|
(Query.Definition.ensureIndexOn
|
||||||
|
"test.table" "gibberish" [ "taco"; "guac DESC"; "salsa ASC" ] PostgreSQL)
|
||||||
([ "CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table "
|
([ "CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table "
|
||||||
"((data->>'taco'), (data->>'guac') DESC, (data->>'salsa') ASC)" ]
|
"((data->>'taco'), (data->>'guac') DESC, (data->>'salsa') ASC)" ]
|
||||||
|> String.concat "")
|
|> String.concat "")
|
||||||
"CREATE INDEX for multiple field statement incorrect"
|
"CREATE INDEX for multiple field statement incorrect"
|
||||||
}
|
}
|
||||||
|
test "succeeds for nested PostgreSQL field" {
|
||||||
|
Expect.equal
|
||||||
|
(Query.Definition.ensureIndexOn tbl "nest" [ "a.b.c" ] PostgreSQL)
|
||||||
|
$"CREATE INDEX IF NOT EXISTS idx_{tbl}_nest ON {tbl} ((data#>>'{{a,b,c}}'))"
|
||||||
|
"CREATE INDEX for nested PostgreSQL field incorrect"
|
||||||
|
}
|
||||||
|
test "succeeds for nested SQLite field" {
|
||||||
|
Expect.equal
|
||||||
|
(Query.Definition.ensureIndexOn tbl "nest" [ "a.b.c" ] SQLite)
|
||||||
|
$"CREATE INDEX IF NOT EXISTS idx_{tbl}_nest ON {tbl} ((data->'a'->'b'->>'c'))"
|
||||||
|
"CREATE INDEX for nested SQLite field incorrect"
|
||||||
|
}
|
||||||
|
]
|
||||||
]
|
]
|
||||||
test "insert succeeds" {
|
test "insert succeeds" {
|
||||||
Expect.equal (Query.insert tbl) $"INSERT INTO {tbl} VALUES (@data)" "INSERT statement not correct"
|
Expect.equal (Query.insert tbl) $"INSERT INTO {tbl} VALUES (@data)" "INSERT statement not correct"
|
||||||
@@ -95,65 +406,91 @@ let all =
|
|||||||
$"INSERT INTO {tbl} VALUES (@data) ON CONFLICT ((data->>'Id')) DO UPDATE SET data = EXCLUDED.data"
|
$"INSERT INTO {tbl} VALUES (@data) ON CONFLICT ((data->>'Id')) DO UPDATE SET data = EXCLUDED.data"
|
||||||
"INSERT ON CONFLICT UPDATE statement not correct"
|
"INSERT ON CONFLICT UPDATE statement not correct"
|
||||||
}
|
}
|
||||||
|
test "count succeeds" {
|
||||||
|
Expect.equal (Query.count tbl) $"SELECT COUNT(*) AS it FROM {tbl}" "Count query not correct"
|
||||||
|
}
|
||||||
|
test "exists succeeds" {
|
||||||
|
Expect.equal
|
||||||
|
(Query.exists tbl "turkey")
|
||||||
|
$"SELECT EXISTS (SELECT 1 FROM {tbl} WHERE turkey) AS it"
|
||||||
|
"Exists query not correct"
|
||||||
|
}
|
||||||
|
test "find succeeds" {
|
||||||
|
Expect.equal (Query.find tbl) $"SELECT data FROM {tbl}" "Find query not correct"
|
||||||
|
}
|
||||||
test "update succeeds" {
|
test "update succeeds" {
|
||||||
|
Expect.equal (Query.update tbl) $"UPDATE {tbl} SET data = @data" "Update query not correct"
|
||||||
|
}
|
||||||
|
test "delete succeeds" {
|
||||||
|
Expect.equal (Query.delete tbl) $"DELETE FROM {tbl}" "Delete query not correct"
|
||||||
|
}
|
||||||
|
testList "orderBy" [
|
||||||
|
test "succeeds for no fields" {
|
||||||
|
Expect.equal (Query.orderBy [] PostgreSQL) "" "Order By should have been blank (PostgreSQL)"
|
||||||
|
Expect.equal (Query.orderBy [] SQLite) "" "Order By should have been blank (SQLite)"
|
||||||
|
}
|
||||||
|
test "succeeds for PostgreSQL with one field and no direction" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.update tbl)
|
(Query.orderBy [ Field.Named "TestField" ] PostgreSQL)
|
||||||
$"UPDATE {tbl} SET data = @data WHERE data ->> 'Id' = @id"
|
" ORDER BY data->>'TestField'"
|
||||||
"UPDATE full statement not correct"
|
"Order By not constructed correctly"
|
||||||
}
|
}
|
||||||
testList "Count" [
|
test "succeeds for SQLite with one field and no direction" {
|
||||||
test "all succeeds" {
|
|
||||||
Expect.equal (Query.Count.all tbl) $"SELECT COUNT(*) AS it FROM {tbl}" "Count query not correct"
|
|
||||||
}
|
|
||||||
test "byField succeeds" {
|
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.Count.byField tbl (Field.EQ "thatField" 0))
|
(Query.orderBy [ Field.Named "TestField" ] SQLite)
|
||||||
$"SELECT COUNT(*) AS it FROM {tbl} WHERE data ->> 'thatField' = @field"
|
" ORDER BY data->>'TestField'"
|
||||||
"JSON field text comparison count query not correct"
|
"Order By not constructed correctly"
|
||||||
}
|
}
|
||||||
]
|
test "succeeds for PostgreSQL with multiple fields and direction" {
|
||||||
testList "Exists" [
|
|
||||||
test "byId succeeds" {
|
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.Exists.byId tbl)
|
(Query.orderBy
|
||||||
$"SELECT EXISTS (SELECT 1 FROM {tbl} WHERE data ->> 'Id' = @id) AS it"
|
[ Field.Named "Nested.Test.Field DESC"; Field.Named "AnotherField"; Field.Named "It DESC" ]
|
||||||
"ID existence query not correct"
|
PostgreSQL)
|
||||||
|
" ORDER BY data#>>'{Nested,Test,Field}' DESC, data->>'AnotherField', data->>'It' DESC"
|
||||||
|
"Order By not constructed correctly"
|
||||||
}
|
}
|
||||||
test "byField succeeds" {
|
test "succeeds for SQLite with multiple fields and direction" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.Exists.byField tbl (Field.LT "Test" 0))
|
(Query.orderBy
|
||||||
$"SELECT EXISTS (SELECT 1 FROM {tbl} WHERE data ->> 'Test' < @field) AS it"
|
[ Field.Named "Nested.Test.Field DESC"; Field.Named "AnotherField"; Field.Named "It DESC" ]
|
||||||
"JSON field text comparison exists query not correct"
|
SQLite)
|
||||||
|
" ORDER BY data->'Nested'->'Test'->>'Field' DESC, data->>'AnotherField', data->>'It' DESC"
|
||||||
|
"Order By not constructed correctly"
|
||||||
}
|
}
|
||||||
]
|
test "succeeds for PostgreSQL numeric fields" {
|
||||||
testList "Find" [
|
|
||||||
test "byId succeeds" {
|
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.Find.byId tbl)
|
(Query.orderBy [ Field.Named "n:Test" ] PostgreSQL)
|
||||||
$"SELECT data FROM {tbl} WHERE data ->> 'Id' = @id"
|
" ORDER BY (data->>'Test')::numeric"
|
||||||
"SELECT by ID query not correct"
|
"Order By not constructed correctly for numeric field"
|
||||||
}
|
}
|
||||||
test "byField succeeds" {
|
test "succeeds for SQLite numeric fields" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.Find.byField tbl (Field.GE "Golf" 0))
|
(Query.orderBy [ Field.Named "n:Test" ] SQLite)
|
||||||
$"SELECT data FROM {tbl} WHERE data ->> 'Golf' >= @field"
|
" ORDER BY data->>'Test'"
|
||||||
"SELECT by JSON comparison query not correct"
|
"Order By not constructed correctly for numeric field"
|
||||||
}
|
}
|
||||||
]
|
test "succeeds for PostgreSQL case-insensitive ordering" {
|
||||||
testList "Delete" [
|
|
||||||
test "byId succeeds" {
|
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.Delete.byId tbl)
|
(Query.orderBy [ Field.Named "i:Test.Field DESC NULLS FIRST" ] PostgreSQL)
|
||||||
$"DELETE FROM {tbl} WHERE data ->> 'Id' = @id"
|
" ORDER BY LOWER(data#>>'{Test,Field}') DESC NULLS FIRST"
|
||||||
"DELETE by ID query not correct"
|
"Order By not constructed correctly for case-insensitive field"
|
||||||
}
|
}
|
||||||
test "byField succeeds" {
|
test "succeeds for SQLite case-insensitive ordering" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.Delete.byField tbl (Field.NEX "gone"))
|
(Query.orderBy [ Field.Named "i:Test.Field ASC NULLS LAST" ] SQLite)
|
||||||
$"DELETE FROM {tbl} WHERE data ->> 'gone' IS NULL"
|
" ORDER BY data->'Test'->>'Field' COLLATE NOCASE ASC NULLS LAST"
|
||||||
"DELETE by JSON comparison query not correct"
|
"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
|
||||||
|
]
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ let integrationTests =
|
|||||||
use conn = mkConn db
|
use conn = mkConn db
|
||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
let! docs = conn.customList (Query.selectFromTable PostgresDb.TableName) [] fromData<JsonDocument>
|
let! docs = conn.customList (Query.find PostgresDb.TableName) [] fromData<JsonDocument>
|
||||||
Expect.equal (List.length docs) 5 "There should have been 5 documents returned"
|
Expect.equal (List.length docs) 5 "There should have been 5 documents returned"
|
||||||
}
|
}
|
||||||
testTask "succeeds when data is not found" {
|
testTask "succeeds when data is not found" {
|
||||||
@@ -209,12 +209,12 @@ let integrationTests =
|
|||||||
let! theCount = conn.countAll PostgresDb.TableName
|
let! theCount = conn.countAll PostgresDb.TableName
|
||||||
Expect.equal theCount 5 "There should have been 5 matching documents"
|
Expect.equal theCount 5 "There should have been 5 matching documents"
|
||||||
}
|
}
|
||||||
testTask "countByField succeeds" {
|
testTask "countByFields succeeds" {
|
||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
use conn = mkConn db
|
use conn = mkConn db
|
||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
let! theCount = conn.countByField PostgresDb.TableName (Field.EQ "Value" "purple")
|
let! theCount = conn.countByFields PostgresDb.TableName Any [ Field.Equal "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" {
|
||||||
@@ -251,13 +251,13 @@ let integrationTests =
|
|||||||
Expect.isFalse exists "There should not have been an existing document"
|
Expect.isFalse exists "There should not have been an existing document"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "existsByField" [
|
testList "existsByFields" [
|
||||||
testTask "succeeds when documents exist" {
|
testTask "succeeds when documents exist" {
|
||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
use conn = mkConn db
|
use conn = mkConn db
|
||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
let! exists = conn.existsByField PostgresDb.TableName (Field.EX "Sub")
|
let! exists = conn.existsByFields PostgresDb.TableName Any [ Field.Exists "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.existsByField PostgresDb.TableName (Field.EQ "NumValue" "six")
|
let! exists = conn.existsByFields PostgresDb.TableName Any [ Field.Equal "NumValue" "six" ]
|
||||||
Expect.isFalse exists "There should not have been existing documents"
|
Expect.isFalse exists "There should not have been existing documents"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -315,12 +315,10 @@ let integrationTests =
|
|||||||
do! conn.insert PostgresDb.TableName { Foo = "five"; Bar = "six" }
|
do! conn.insert PostgresDb.TableName { Foo = "five"; Bar = "six" }
|
||||||
|
|
||||||
let! results = conn.findAll<SubDocument> PostgresDb.TableName
|
let! results = conn.findAll<SubDocument> PostgresDb.TableName
|
||||||
let expected = [
|
Expect.equal
|
||||||
{ Foo = "one"; Bar = "two" }
|
results
|
||||||
{ Foo = "three"; Bar = "four" }
|
[ { Foo = "one"; Bar = "two" }; { Foo = "three"; Bar = "four" }; { Foo = "five"; Bar = "six" } ]
|
||||||
{ Foo = "five"; Bar = "six" }
|
"There should have been 3 documents returned"
|
||||||
]
|
|
||||||
Expect.equal results expected "There should have been 3 documents returned"
|
|
||||||
}
|
}
|
||||||
testTask "succeeds when there is no data" {
|
testTask "succeeds when there is no data" {
|
||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
@@ -329,6 +327,44 @@ let integrationTests =
|
|||||||
Expect.equal results [] "There should have been no documents returned"
|
Expect.equal results [] "There should have been no documents returned"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
testList "findAllOrdered" [
|
||||||
|
testTask "succeeds when ordering numerically" {
|
||||||
|
use db = PostgresDb.BuildDb()
|
||||||
|
use conn = mkConn db
|
||||||
|
do! loadDocs conn
|
||||||
|
|
||||||
|
let! results = conn.findAllOrdered<JsonDocument> PostgresDb.TableName [ Field.Named "n:NumValue" ]
|
||||||
|
Expect.hasLength results 5 "There should have been 5 documents returned"
|
||||||
|
Expect.equal
|
||||||
|
(results |> List.map _.Id |> String.concat "|")
|
||||||
|
"one|three|two|four|five"
|
||||||
|
"The documents were not ordered correctly"
|
||||||
|
}
|
||||||
|
testTask "succeeds when ordering numerically descending" {
|
||||||
|
use db = PostgresDb.BuildDb()
|
||||||
|
use conn = mkConn db
|
||||||
|
do! loadDocs conn
|
||||||
|
|
||||||
|
let! results = conn.findAllOrdered<JsonDocument> PostgresDb.TableName [ Field.Named "n:NumValue DESC" ]
|
||||||
|
Expect.hasLength results 5 "There should have been 5 documents returned"
|
||||||
|
Expect.equal
|
||||||
|
(results |> List.map _.Id |> String.concat "|")
|
||||||
|
"five|four|two|three|one"
|
||||||
|
"The documents were not ordered correctly"
|
||||||
|
}
|
||||||
|
testTask "succeeds when ordering alphabetically" {
|
||||||
|
use db = PostgresDb.BuildDb()
|
||||||
|
use conn = mkConn db
|
||||||
|
do! loadDocs conn
|
||||||
|
|
||||||
|
let! results = conn.findAllOrdered<JsonDocument> PostgresDb.TableName [ Field.Named "Id DESC" ]
|
||||||
|
Expect.hasLength results 5 "There should have been 5 documents returned"
|
||||||
|
Expect.equal
|
||||||
|
(results |> List.map _.Id |> String.concat "|")
|
||||||
|
"two|three|one|four|five"
|
||||||
|
"The documents were not ordered correctly"
|
||||||
|
}
|
||||||
|
]
|
||||||
testList "findById" [
|
testList "findById" [
|
||||||
testTask "succeeds when a document is found" {
|
testTask "succeeds when a document is found" {
|
||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
@@ -348,13 +384,13 @@ let integrationTests =
|
|||||||
Expect.isNone doc "There should not have been a document returned"
|
Expect.isNone doc "There should not have been a document returned"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "findByField" [
|
testList "findByFields" [
|
||||||
testTask "succeeds when documents are found" {
|
testTask "succeeds when documents are found" {
|
||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
use conn = mkConn db
|
use conn = mkConn db
|
||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
let! docs = conn.findByField<JsonDocument> PostgresDb.TableName (Field.EQ "Value" "another")
|
let! docs = conn.findByFields<JsonDocument> PostgresDb.TableName Any [ Field.Equal "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" {
|
||||||
@@ -362,10 +398,36 @@ let integrationTests =
|
|||||||
use conn = mkConn db
|
use conn = mkConn db
|
||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
let! docs = conn.findByField<JsonDocument> PostgresDb.TableName (Field.EQ "Value" "mauve")
|
let! docs = conn.findByFields<JsonDocument> PostgresDb.TableName Any [ Field.Equal "Value" "mauve" ]
|
||||||
Expect.isEmpty docs "There should have been no documents returned"
|
Expect.isEmpty docs "There should have been no documents returned"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
testList "findByFieldsOrdered" [
|
||||||
|
testTask "succeeds when sorting ascending" {
|
||||||
|
use db = PostgresDb.BuildDb()
|
||||||
|
use conn = mkConn db
|
||||||
|
do! loadDocs conn
|
||||||
|
|
||||||
|
let! docs =
|
||||||
|
conn.findByFieldsOrdered<JsonDocument>
|
||||||
|
PostgresDb.TableName All [ Field.Equal "Value" "purple" ] [ Field.Named "Id" ]
|
||||||
|
Expect.hasLength docs 2 "There should have been two documents returned"
|
||||||
|
Expect.equal
|
||||||
|
(docs |> List.map _.Id |> String.concat "|") "five|four" "Documents not ordered correctly"
|
||||||
|
}
|
||||||
|
testTask "succeeds when sorting descending" {
|
||||||
|
use db = PostgresDb.BuildDb()
|
||||||
|
use conn = mkConn db
|
||||||
|
do! loadDocs conn
|
||||||
|
|
||||||
|
let! docs =
|
||||||
|
conn.findByFieldsOrdered<JsonDocument>
|
||||||
|
PostgresDb.TableName All [ Field.Equal "Value" "purple" ] [ Field.Named "Id DESC" ]
|
||||||
|
Expect.hasLength docs 2 "There should have been two documents returned"
|
||||||
|
Expect.equal
|
||||||
|
(docs |> List.map _.Id |> String.concat "|") "four|five" "Documents not ordered correctly"
|
||||||
|
}
|
||||||
|
]
|
||||||
testList "findByContains" [
|
testList "findByContains" [
|
||||||
testTask "succeeds when documents are found" {
|
testTask "succeeds when documents are found" {
|
||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
@@ -384,6 +446,33 @@ let integrationTests =
|
|||||||
Expect.isEmpty docs "There should have been no documents returned"
|
Expect.isEmpty docs "There should have been no documents returned"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
testList "findByContainsOrdered" [
|
||||||
|
// Id = two, Sub.Bar = blue; Id = four, Sub.Bar = red
|
||||||
|
testTask "succeeds when sorting ascending" {
|
||||||
|
use db = PostgresDb.BuildDb()
|
||||||
|
use conn = mkConn db
|
||||||
|
do! loadDocs conn
|
||||||
|
|
||||||
|
let! docs =
|
||||||
|
conn.findByContainsOrdered<JsonDocument>
|
||||||
|
PostgresDb.TableName {| Sub = {| Foo = "green" |} |} [ Field.Named "Sub.Bar" ]
|
||||||
|
Expect.hasLength docs 2 "There should have been two documents returned"
|
||||||
|
Expect.equal
|
||||||
|
(docs |> List.map _.Id |> String.concat "|") "two|four" "Documents not ordered correctly"
|
||||||
|
}
|
||||||
|
testTask "succeeds when sorting descending" {
|
||||||
|
use db = PostgresDb.BuildDb()
|
||||||
|
use conn = mkConn db
|
||||||
|
do! loadDocs conn
|
||||||
|
|
||||||
|
let! docs =
|
||||||
|
conn.findByContainsOrdered<JsonDocument>
|
||||||
|
PostgresDb.TableName {| Sub = {| Foo = "green" |} |} [ Field.Named "Sub.Bar DESC" ]
|
||||||
|
Expect.hasLength docs 2 "There should have been two documents returned"
|
||||||
|
Expect.equal
|
||||||
|
(docs |> List.map _.Id |> String.concat "|") "four|two" "Documents not ordered correctly"
|
||||||
|
}
|
||||||
|
]
|
||||||
testList "findByJsonPath" [
|
testList "findByJsonPath" [
|
||||||
testTask "succeeds when documents are found" {
|
testTask "succeeds when documents are found" {
|
||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
@@ -402,13 +491,41 @@ let integrationTests =
|
|||||||
Expect.isEmpty docs "There should have been no documents returned"
|
Expect.isEmpty docs "There should have been no documents returned"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "findFirstByField" [
|
testList "findByJsonPathOrdered" [
|
||||||
|
// Id = one, NumValue = 0; Id = two, NumValue = 10; Id = three, NumValue = 4
|
||||||
|
testTask "succeeds when sorting ascending" {
|
||||||
|
use db = PostgresDb.BuildDb()
|
||||||
|
use conn = mkConn db
|
||||||
|
do! loadDocs conn
|
||||||
|
|
||||||
|
let! docs =
|
||||||
|
conn.findByJsonPathOrdered<JsonDocument>
|
||||||
|
PostgresDb.TableName "$.NumValue ? (@ < 15)" [ Field.Named "n:NumValue" ]
|
||||||
|
Expect.hasLength docs 3 "There should have been 3 documents returned"
|
||||||
|
Expect.equal
|
||||||
|
(docs |> List.map _.Id |> String.concat "|") "one|three|two" "Documents not ordered correctly"
|
||||||
|
}
|
||||||
|
testTask "succeeds when sorting descending" {
|
||||||
|
use db = PostgresDb.BuildDb()
|
||||||
|
use conn = mkConn db
|
||||||
|
do! loadDocs conn
|
||||||
|
|
||||||
|
let! docs =
|
||||||
|
conn.findByJsonPathOrdered<JsonDocument>
|
||||||
|
PostgresDb.TableName "$.NumValue ? (@ < 15)" [ Field.Named "n:NumValue DESC" ]
|
||||||
|
Expect.hasLength docs 3 "There should have been 3 documents returned"
|
||||||
|
Expect.equal
|
||||||
|
(docs |> List.map _.Id |> String.concat "|") "two|three|one" "Documents not ordered correctly"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "findFirstByFields" [
|
||||||
testTask "succeeds when a document is found" {
|
testTask "succeeds when a document is found" {
|
||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
use conn = mkConn db
|
use conn = mkConn db
|
||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
let! doc = conn.findFirstByField<JsonDocument> PostgresDb.TableName (Field.EQ "Value" "another")
|
let! doc =
|
||||||
|
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"
|
||||||
}
|
}
|
||||||
@@ -417,7 +534,8 @@ let integrationTests =
|
|||||||
use conn = mkConn db
|
use conn = mkConn db
|
||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
let! doc = conn.findFirstByField<JsonDocument> PostgresDb.TableName (Field.EQ "Value" "purple")
|
let! doc =
|
||||||
|
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"
|
||||||
}
|
}
|
||||||
@@ -426,10 +544,35 @@ let integrationTests =
|
|||||||
use conn = mkConn db
|
use conn = mkConn db
|
||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
let! doc = conn.findFirstByField<JsonDocument> PostgresDb.TableName (Field.EQ "Value" "absent")
|
let! doc =
|
||||||
|
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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
testList "findFirstByFieldsOrdered" [
|
||||||
|
testTask "succeeds when sorting ascending" {
|
||||||
|
use db = PostgresDb.BuildDb()
|
||||||
|
use conn = mkConn db
|
||||||
|
do! loadDocs conn
|
||||||
|
|
||||||
|
let! doc =
|
||||||
|
conn.findFirstByFieldsOrdered<JsonDocument>
|
||||||
|
PostgresDb.TableName Any [ Field.Equal "Value" "purple" ] [ Field.Named "Id" ]
|
||||||
|
Expect.isSome doc "There should have been a document returned"
|
||||||
|
Expect.equal "five" doc.Value.Id "An incorrect document was returned"
|
||||||
|
}
|
||||||
|
testTask "succeeds when sorting descending" {
|
||||||
|
use db = PostgresDb.BuildDb()
|
||||||
|
use conn = mkConn db
|
||||||
|
do! loadDocs conn
|
||||||
|
|
||||||
|
let! doc =
|
||||||
|
conn.findFirstByFieldsOrdered<JsonDocument>
|
||||||
|
PostgresDb.TableName Any [ Field.Equal "Value" "purple" ] [ Field.Named "Id DESC" ]
|
||||||
|
Expect.isSome doc "There should have been a document returned"
|
||||||
|
Expect.equal "four" doc.Value.Id "An incorrect document was returned"
|
||||||
|
}
|
||||||
|
]
|
||||||
testList "findFirstByContains" [
|
testList "findFirstByContains" [
|
||||||
testTask "succeeds when a document is found" {
|
testTask "succeeds when a document is found" {
|
||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
@@ -458,6 +601,30 @@ let integrationTests =
|
|||||||
Expect.isNone doc "There should not have been a document returned"
|
Expect.isNone doc "There should not have been a document returned"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
testList "findFirstByContainsOrdered" [
|
||||||
|
testTask "succeeds when sorting ascending" {
|
||||||
|
use db = PostgresDb.BuildDb()
|
||||||
|
use conn = mkConn db
|
||||||
|
do! loadDocs conn
|
||||||
|
|
||||||
|
let! doc =
|
||||||
|
conn.findFirstByContainsOrdered<JsonDocument>
|
||||||
|
PostgresDb.TableName {| Sub = {| Foo = "green" |} |} [ Field.Named "Value" ]
|
||||||
|
Expect.isSome doc "There should have been a document returned"
|
||||||
|
Expect.equal "two" doc.Value.Id "An incorrect document was returned"
|
||||||
|
}
|
||||||
|
testTask "succeeds when sorting descending" {
|
||||||
|
use db = PostgresDb.BuildDb()
|
||||||
|
use conn = mkConn db
|
||||||
|
do! loadDocs conn
|
||||||
|
|
||||||
|
let! doc =
|
||||||
|
conn.findFirstByContainsOrdered<JsonDocument>
|
||||||
|
PostgresDb.TableName {| Sub = {| Foo = "green" |} |} [ Field.Named "Value DESC" ]
|
||||||
|
Expect.isSome doc "There should have been a document returned"
|
||||||
|
Expect.equal "four" doc.Value.Id "An incorrect document was returned"
|
||||||
|
}
|
||||||
|
]
|
||||||
testList "findFirstByJsonPath" [
|
testList "findFirstByJsonPath" [
|
||||||
testTask "succeeds when a document is found" {
|
testTask "succeeds when a document is found" {
|
||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
@@ -486,6 +653,30 @@ let integrationTests =
|
|||||||
Expect.isNone doc "There should not have been a document returned"
|
Expect.isNone doc "There should not have been a document returned"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
testList "findFirstByJsonPathOrdered" [
|
||||||
|
testTask "succeeds when sorting ascending" {
|
||||||
|
use db = PostgresDb.BuildDb()
|
||||||
|
use conn = mkConn db
|
||||||
|
do! loadDocs conn
|
||||||
|
|
||||||
|
let! doc =
|
||||||
|
conn.findFirstByJsonPathOrdered<JsonDocument>
|
||||||
|
PostgresDb.TableName """$.Sub.Foo ? (@ == "green")""" [ Field.Named "Sub.Bar" ]
|
||||||
|
Expect.isSome doc "There should have been a document returned"
|
||||||
|
Expect.equal "two" doc.Value.Id "An incorrect document was returned"
|
||||||
|
}
|
||||||
|
testTask "succeeds when sorting descending" {
|
||||||
|
use db = PostgresDb.BuildDb()
|
||||||
|
use conn = mkConn db
|
||||||
|
do! loadDocs conn
|
||||||
|
|
||||||
|
let! doc =
|
||||||
|
conn.findFirstByJsonPathOrdered<JsonDocument>
|
||||||
|
PostgresDb.TableName """$.Sub.Foo ? (@ == "green")""" [ Field.Named "Sub.Bar DESC" ]
|
||||||
|
Expect.isSome doc "There should have been a document returned"
|
||||||
|
Expect.equal "four" doc.Value.Id "An incorrect document was returned"
|
||||||
|
}
|
||||||
|
]
|
||||||
testList "updateById" [
|
testList "updateById" [
|
||||||
testTask "succeeds when a document is updated" {
|
testTask "succeeds when a document is updated" {
|
||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
@@ -556,14 +747,14 @@ let integrationTests =
|
|||||||
do! conn.patchById PostgresDb.TableName "test" {| Foo = "green" |}
|
do! conn.patchById PostgresDb.TableName "test" {| Foo = "green" |}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "patchByField" [
|
testList "patchByFields" [
|
||||||
testTask "succeeds when a document is updated" {
|
testTask "succeeds when a document is updated" {
|
||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
use conn = mkConn db
|
use conn = mkConn db
|
||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
do! conn.patchByField PostgresDb.TableName (Field.EQ "Value" "purple") {| NumValue = 77 |}
|
do! conn.patchByFields PostgresDb.TableName Any [ Field.Equal "Value" "purple" ] {| NumValue = 77 |}
|
||||||
let! after = conn.countByField PostgresDb.TableName (Field.EQ "NumValue" "77")
|
let! after = conn.countByFields PostgresDb.TableName Any [ Field.Equal "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" {
|
||||||
@@ -573,7 +764,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.patchByField PostgresDb.TableName (Field.EQ "Value" "burgundy") {| Foo = "green" |}
|
do! conn.patchByFields PostgresDb.TableName Any [ Field.Equal "Value" "burgundy" ] {| Foo = "green" |}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "patchByContains" [
|
testList "patchByContains" [
|
||||||
@@ -623,9 +814,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.countByField PostgresDb.TableName (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.countByField PostgresDb.TableName (Field.NEX "Value")
|
let! noValue = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "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" {
|
||||||
@@ -634,9 +825,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.countByField PostgresDb.TableName (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.countByField PostgresDb.TableName (Field.NEX "Value")
|
let! noValue = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "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" {
|
||||||
@@ -655,16 +846,17 @@ let integrationTests =
|
|||||||
do! conn.removeFieldsById PostgresDb.TableName "two" [ "Value" ]
|
do! conn.removeFieldsById PostgresDb.TableName "two" [ "Value" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "removeFieldsByField" [
|
testList "removeFieldsByFields" [
|
||||||
testTask "succeeds when multiple fields are removed" {
|
testTask "succeeds when multiple fields are removed" {
|
||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
use conn = mkConn db
|
use conn = mkConn db
|
||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
do! conn.removeFieldsByField PostgresDb.TableName (Field.EQ "NumValue" "17") [ "Sub"; "Value" ]
|
do! conn.removeFieldsByFields
|
||||||
let! noSubs = conn.countByField PostgresDb.TableName (Field.NEX "Sub")
|
PostgresDb.TableName Any [ Field.Equal "NumValue" "17" ] [ "Sub"; "Value" ]
|
||||||
|
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.countByField PostgresDb.TableName (Field.NEX "Value")
|
let! noValue = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "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" {
|
||||||
@@ -672,10 +864,10 @@ let integrationTests =
|
|||||||
use conn = mkConn db
|
use conn = mkConn db
|
||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
do! conn.removeFieldsByField PostgresDb.TableName (Field.EQ "NumValue" "17") [ "Sub" ]
|
do! conn.removeFieldsByFields PostgresDb.TableName Any [ Field.Equal "NumValue" "17" ] [ "Sub" ]
|
||||||
let! noSubs = conn.countByField PostgresDb.TableName (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.countByField PostgresDb.TableName (Field.NEX "Value")
|
let! noValue = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "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" {
|
||||||
@@ -684,14 +876,15 @@ 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.removeFieldsByField PostgresDb.TableName (Field.EQ "NumValue" "17") [ "Nothing" ]
|
do! conn.removeFieldsByFields PostgresDb.TableName Any [ Field.Equal "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.removeFieldsByField PostgresDb.TableName (Field.NE "Abracadabra" "apple") [ "Value" ]
|
do! conn.removeFieldsByFields
|
||||||
|
PostgresDb.TableName Any [ Field.NotEqual "Abracadabra" "apple" ] [ "Value" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "removeFieldsByContains" [
|
testList "removeFieldsByContains" [
|
||||||
@@ -701,9 +894,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.countByField PostgresDb.TableName (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.countByField PostgresDb.TableName (Field.NEX "Value")
|
let! noValue = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "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" {
|
||||||
@@ -712,9 +905,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.countByField PostgresDb.TableName (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.countByField PostgresDb.TableName (Field.NEX "Value")
|
let! noValue = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "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" {
|
||||||
@@ -740,9 +933,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.countByField PostgresDb.TableName (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.countByField PostgresDb.TableName (Field.NEX "Value")
|
let! noValue = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "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" {
|
||||||
@@ -751,9 +944,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.countByField PostgresDb.TableName (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.countByField PostgresDb.TableName (Field.NEX "Value")
|
let! noValue = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "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" {
|
||||||
@@ -792,13 +985,13 @@ let integrationTests =
|
|||||||
Expect.equal remaining 5 "There should have been 5 documents remaining"
|
Expect.equal remaining 5 "There should have been 5 documents remaining"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "deleteByField" [
|
testList "deleteByFields" [
|
||||||
testTask "succeeds when documents are deleted" {
|
testTask "succeeds when documents are deleted" {
|
||||||
use db = PostgresDb.BuildDb()
|
use db = PostgresDb.BuildDb()
|
||||||
use conn = mkConn db
|
use conn = mkConn db
|
||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
do! conn.deleteByField PostgresDb.TableName (Field.EQ "Value" "purple")
|
do! conn.deleteByFields PostgresDb.TableName Any [ Field.Equal "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"
|
||||||
}
|
}
|
||||||
@@ -807,7 +1000,7 @@ let integrationTests =
|
|||||||
use conn = mkConn db
|
use conn = mkConn db
|
||||||
do! loadDocs conn
|
do! loadDocs conn
|
||||||
|
|
||||||
do! conn.deleteByField PostgresDb.TableName (Field.EQ "Value" "crimson")
|
do! conn.deleteByFields PostgresDb.TableName Any [ Field.Equal "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"
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -113,12 +113,12 @@ let integrationTests =
|
|||||||
let! theCount = conn.countAll SqliteDb.TableName
|
let! theCount = conn.countAll SqliteDb.TableName
|
||||||
Expect.equal theCount 5L "There should have been 5 matching documents"
|
Expect.equal theCount 5L "There should have been 5 matching documents"
|
||||||
}
|
}
|
||||||
testTask "countByField succeeds" {
|
testTask "countByFields succeeds" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! theCount = conn.countByField SqliteDb.TableName (Field.EQ "Value" "purple")
|
let! theCount = conn.countByFields SqliteDb.TableName Any [ 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" [
|
||||||
@@ -139,13 +139,13 @@ let integrationTests =
|
|||||||
Expect.isFalse exists "There should not have been an existing document"
|
Expect.isFalse exists "There should not have been an existing document"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "existsByField" [
|
testList "existsByFields" [
|
||||||
testTask "succeeds when documents exist" {
|
testTask "succeeds when documents exist" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! exists = conn.existsByField SqliteDb.TableName (Field.EQ "NumValue" 10)
|
let! exists = conn.existsByFields SqliteDb.TableName Any [ Field.Equal "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.existsByField SqliteDb.TableName (Field.EQ "Nothing" "none")
|
let! exists = conn.existsByFields SqliteDb.TableName Any [ 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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -181,6 +181,44 @@ let integrationTests =
|
|||||||
Expect.equal results [] "There should have been no documents returned"
|
Expect.equal results [] "There should have been no documents returned"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
testList "findAllOrdered" [
|
||||||
|
testTask "succeeds when ordering numerically" {
|
||||||
|
use! db = SqliteDb.BuildDb()
|
||||||
|
use conn = Configuration.dbConn ()
|
||||||
|
do! loadDocs ()
|
||||||
|
|
||||||
|
let! results = conn.findAllOrdered<JsonDocument> SqliteDb.TableName [ Field.Named "n:NumValue" ]
|
||||||
|
Expect.hasLength results 5 "There should have been 5 documents returned"
|
||||||
|
Expect.equal
|
||||||
|
(results |> List.map _.Id |> String.concat "|")
|
||||||
|
"one|three|two|four|five"
|
||||||
|
"The documents were not ordered correctly"
|
||||||
|
}
|
||||||
|
testTask "succeeds when ordering numerically descending" {
|
||||||
|
use! db = SqliteDb.BuildDb()
|
||||||
|
use conn = Configuration.dbConn ()
|
||||||
|
do! loadDocs ()
|
||||||
|
|
||||||
|
let! results = conn.findAllOrdered<JsonDocument> SqliteDb.TableName [ Field.Named "n:NumValue DESC" ]
|
||||||
|
Expect.hasLength results 5 "There should have been 5 documents returned"
|
||||||
|
Expect.equal
|
||||||
|
(results |> List.map _.Id |> String.concat "|")
|
||||||
|
"five|four|two|three|one"
|
||||||
|
"The documents were not ordered correctly"
|
||||||
|
}
|
||||||
|
testTask "succeeds when ordering alphabetically" {
|
||||||
|
use! db = SqliteDb.BuildDb()
|
||||||
|
use conn = Configuration.dbConn ()
|
||||||
|
do! loadDocs ()
|
||||||
|
|
||||||
|
let! results = conn.findAllOrdered<JsonDocument> SqliteDb.TableName [ Field.Named "Id DESC" ]
|
||||||
|
Expect.hasLength results 5 "There should have been 5 documents returned"
|
||||||
|
Expect.equal
|
||||||
|
(results |> List.map _.Id |> String.concat "|")
|
||||||
|
"two|three|one|four|five"
|
||||||
|
"The documents were not ordered correctly"
|
||||||
|
}
|
||||||
|
]
|
||||||
testList "findById" [
|
testList "findById" [
|
||||||
testTask "succeeds when a document is found" {
|
testTask "succeeds when a document is found" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
@@ -188,7 +226,7 @@ let integrationTests =
|
|||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! doc = conn.findById<string, JsonDocument> SqliteDb.TableName "two"
|
let! doc = conn.findById<string, JsonDocument> SqliteDb.TableName "two"
|
||||||
Expect.isTrue (Option.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"
|
||||||
}
|
}
|
||||||
testTask "succeeds when a document is not found" {
|
testTask "succeeds when a document is not found" {
|
||||||
@@ -197,35 +235,59 @@ let integrationTests =
|
|||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! doc = conn.findById<string, JsonDocument> SqliteDb.TableName "three hundred eighty-seven"
|
let! doc = conn.findById<string, JsonDocument> SqliteDb.TableName "three hundred eighty-seven"
|
||||||
Expect.isFalse (Option.isSome doc) "There should not have been a document returned"
|
Expect.isNone doc "There should not have been a document returned"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "findByField" [
|
testList "findByFields" [
|
||||||
testTask "succeeds when documents are found" {
|
testTask "succeeds when documents are found" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! docs = conn.findByField<JsonDocument> SqliteDb.TableName (Field.EQ "Sub.Foo" "green")
|
let! docs = conn.findByFields<JsonDocument> SqliteDb.TableName Any [ Field.Equal "Sub.Foo" "green" ]
|
||||||
Expect.equal (List.length 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" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! docs = conn.findByField<JsonDocument> SqliteDb.TableName (Field.EQ "Value" "mauve")
|
let! docs = conn.findByFields<JsonDocument> SqliteDb.TableName Any [ Field.Equal "Value" "mauve" ]
|
||||||
Expect.isTrue (List.isEmpty docs) "There should have been no documents returned"
|
Expect.isEmpty docs "There should have been no documents returned"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "findFirstByField" [
|
testList "findByFieldsOrdered" [
|
||||||
|
testTask "succeeds when sorting ascending" {
|
||||||
|
use! db = SqliteDb.BuildDb()
|
||||||
|
use conn = Configuration.dbConn ()
|
||||||
|
do! loadDocs ()
|
||||||
|
|
||||||
|
let! docs =
|
||||||
|
conn.findByFieldsOrdered<JsonDocument>
|
||||||
|
SqliteDb.TableName Any [ Field.Greater "NumValue" 15 ] [ Field.Named "Id" ]
|
||||||
|
Expect.equal
|
||||||
|
(docs |> List.map _.Id |> String.concat "|") "five|four" "The documents were not ordered correctly"
|
||||||
|
}
|
||||||
|
testTask "succeeds when sorting descending" {
|
||||||
|
use! db = SqliteDb.BuildDb()
|
||||||
|
use conn = Configuration.dbConn ()
|
||||||
|
do! loadDocs ()
|
||||||
|
|
||||||
|
let! docs =
|
||||||
|
conn.findByFieldsOrdered<JsonDocument>
|
||||||
|
SqliteDb.TableName Any [ Field.Greater "NumValue" 15 ] [ Field.Named "Id DESC" ]
|
||||||
|
Expect.equal
|
||||||
|
(docs |> List.map _.Id |> String.concat "|") "four|five" "The documents were not ordered correctly"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "findFirstByFields" [
|
||||||
testTask "succeeds when a document is found" {
|
testTask "succeeds when a document is found" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! doc = conn.findFirstByField<JsonDocument> SqliteDb.TableName (Field.EQ "Value" "another")
|
let! doc = conn.findFirstByFields<JsonDocument> SqliteDb.TableName Any [ Field.Equal "Value" "another" ]
|
||||||
Expect.isTrue (Option.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"
|
||||||
}
|
}
|
||||||
testTask "succeeds when multiple documents are found" {
|
testTask "succeeds when multiple documents are found" {
|
||||||
@@ -233,8 +295,8 @@ let integrationTests =
|
|||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! doc = conn.findFirstByField<JsonDocument> SqliteDb.TableName (Field.EQ "Sub.Foo" "green")
|
let! doc = conn.findFirstByFields<JsonDocument> SqliteDb.TableName Any [ Field.Equal "Sub.Foo" "green" ]
|
||||||
Expect.isTrue (Option.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"
|
||||||
}
|
}
|
||||||
testTask "succeeds when a document is not found" {
|
testTask "succeeds when a document is not found" {
|
||||||
@@ -242,8 +304,32 @@ let integrationTests =
|
|||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! doc = conn.findFirstByField<JsonDocument> SqliteDb.TableName (Field.EQ "Value" "absent")
|
let! doc = conn.findFirstByFields<JsonDocument> SqliteDb.TableName Any [ Field.Equal "Value" "absent" ]
|
||||||
Expect.isFalse (Option.isSome doc) "There should not have been a document returned"
|
Expect.isNone doc "There should not have been a document returned"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "findFirstByFieldsOrdered" [
|
||||||
|
testTask "succeeds when sorting ascending" {
|
||||||
|
use! db = SqliteDb.BuildDb()
|
||||||
|
use conn = Configuration.dbConn ()
|
||||||
|
do! loadDocs ()
|
||||||
|
|
||||||
|
let! doc =
|
||||||
|
conn.findFirstByFieldsOrdered<JsonDocument>
|
||||||
|
SqliteDb.TableName Any [ Field.Equal "Sub.Foo" "green" ] [ Field.Named "Sub.Bar" ]
|
||||||
|
Expect.isSome doc "There should have been a document returned"
|
||||||
|
Expect.equal "two" doc.Value.Id "An incorrect document was returned"
|
||||||
|
}
|
||||||
|
testTask "succeeds when sorting descending" {
|
||||||
|
use! db = SqliteDb.BuildDb()
|
||||||
|
use conn = Configuration.dbConn ()
|
||||||
|
do! loadDocs ()
|
||||||
|
|
||||||
|
let! doc =
|
||||||
|
conn.findFirstByFieldsOrdered<JsonDocument>
|
||||||
|
SqliteDb.TableName Any [ Field.Equal "Sub.Foo" "green" ] [ Field.Named "Sub.Bar DESC" ]
|
||||||
|
Expect.isSome doc "There should have been a document returned"
|
||||||
|
Expect.equal "four" doc.Value.Id "An incorrect document was returned"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "updateById" [
|
testList "updateById" [
|
||||||
@@ -324,14 +410,14 @@ let integrationTests =
|
|||||||
do! conn.patchById SqliteDb.TableName "test" {| Foo = "green" |}
|
do! conn.patchById SqliteDb.TableName "test" {| Foo = "green" |}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "patchByField" [
|
testList "patchByFields" [
|
||||||
testTask "succeeds when a document is updated" {
|
testTask "succeeds when a document is updated" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
do! conn.patchByField SqliteDb.TableName (Field.EQ "Value" "purple") {| NumValue = 77 |}
|
do! conn.patchByFields SqliteDb.TableName Any [ Field.Equal "Value" "purple" ] {| NumValue = 77 |}
|
||||||
let! after = conn.countByField SqliteDb.TableName (Field.EQ "NumValue" 77)
|
let! after = conn.countByFields SqliteDb.TableName Any [ Field.Equal "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" {
|
||||||
@@ -342,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.patchByField SqliteDb.TableName (Field.EQ "Value" "burgundy") {| Foo = "green" |}
|
do! conn.patchByFields SqliteDb.TableName Any [ Field.Equal "Value" "burgundy" ] {| Foo = "green" |}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "removeFieldsById" [
|
testList "removeFieldsById" [
|
||||||
@@ -375,13 +461,13 @@ let integrationTests =
|
|||||||
do! conn.removeFieldsById SqliteDb.TableName "two" [ "Value" ]
|
do! conn.removeFieldsById SqliteDb.TableName "two" [ "Value" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "removeFieldByField" [
|
testList "removeFieldByFields" [
|
||||||
testTask "succeeds when a field is removed" {
|
testTask "succeeds when a field is removed" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
do! conn.removeFieldsByField SqliteDb.TableName (Field.EQ "NumValue" 17) [ "Sub" ]
|
do! conn.removeFieldsByFields SqliteDb.TableName Any [ Field.Equal "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"
|
||||||
@@ -395,14 +481,15 @@ let integrationTests =
|
|||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
do! conn.removeFieldsByField SqliteDb.TableName (Field.EQ "NumValue" 17) [ "Nothing" ]
|
do! conn.removeFieldsByFields SqliteDb.TableName Any [ Field.Equal "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.removeFieldsByField SqliteDb.TableName (Field.NE "Abracadabra" "apple") [ "Value" ]
|
do! conn.removeFieldsByFields
|
||||||
|
SqliteDb.TableName Any [ Field.NotEqual "Abracadabra" "apple" ] [ "Value" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "deleteById" [
|
testList "deleteById" [
|
||||||
@@ -425,13 +512,13 @@ let integrationTests =
|
|||||||
Expect.equal remaining 5L "There should have been 5 documents remaining"
|
Expect.equal remaining 5L "There should have been 5 documents remaining"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "deleteByField" [
|
testList "deleteByFields" [
|
||||||
testTask "succeeds when documents are deleted" {
|
testTask "succeeds when documents are deleted" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
do! conn.deleteByField SqliteDb.TableName (Field.NE "Value" "purple")
|
do! conn.deleteByFields SqliteDb.TableName Any [ Field.NotEqual "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"
|
||||||
}
|
}
|
||||||
@@ -440,7 +527,7 @@ let integrationTests =
|
|||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
do! conn.deleteByField SqliteDb.TableName (Field.EQ "Value" "crimson")
|
do! conn.deleteByFields SqliteDb.TableName Any [ Field.Equal "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"
|
||||||
}
|
}
|
||||||
@@ -478,8 +565,8 @@ let integrationTests =
|
|||||||
use conn = Configuration.dbConn ()
|
use conn = Configuration.dbConn ()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! docs = conn.customList (Query.selectFromTable SqliteDb.TableName) [] fromData<JsonDocument>
|
let! docs = conn.customList (Query.find SqliteDb.TableName) [] fromData<JsonDocument>
|
||||||
Expect.hasCountOf docs 5u (fun _ -> true) "There should have been 5 documents returned"
|
Expect.hasLength docs 5 "There should have been 5 documents returned"
|
||||||
}
|
}
|
||||||
testTask "succeeds when data is not found" {
|
testTask "succeeds when data is not found" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
|
|||||||
@@ -8,49 +8,96 @@ open Expecto
|
|||||||
open Microsoft.Data.Sqlite
|
open Microsoft.Data.Sqlite
|
||||||
open Types
|
open Types
|
||||||
|
|
||||||
/// Unit tests for the SQLite library
|
#nowarn "0044"
|
||||||
let unitTests =
|
|
||||||
testList "Unit" [
|
(** UNIT TESTS **)
|
||||||
testList "Query" [
|
|
||||||
|
/// Unit tests for the Query module of the SQLite library
|
||||||
|
let queryTests = testList "Query" [
|
||||||
|
testList "whereByFields" [
|
||||||
|
test "succeeds for a single field when a logical comparison is passed" {
|
||||||
|
Expect.equal
|
||||||
|
(Query.whereByFields Any [ { Field.Greater "theField" 0 with ParameterName = Some "@test" } ])
|
||||||
|
"data->>'theField' > @test"
|
||||||
|
"WHERE clause not correct"
|
||||||
|
}
|
||||||
|
test "succeeds for a single field when an existence comparison is passed" {
|
||||||
|
Expect.equal
|
||||||
|
(Query.whereByFields Any [ Field.NotExists "thatField" ])
|
||||||
|
"data->>'thatField' IS NULL"
|
||||||
|
"WHERE clause not correct"
|
||||||
|
}
|
||||||
|
test "succeeds for a single field when a between comparison is passed" {
|
||||||
|
Expect.equal
|
||||||
|
(Query.whereByFields All [ { Field.Between "aField" 50 99 with ParameterName = Some "@range" } ])
|
||||||
|
"data->>'aField' BETWEEN @rangemin AND @rangemax"
|
||||||
|
"WHERE clause not correct"
|
||||||
|
}
|
||||||
|
test "succeeds for all multiple fields with logical comparisons" {
|
||||||
|
Expect.equal
|
||||||
|
(Query.whereByFields All [ Field.Equal "theFirst" "1"; Field.Equal "numberTwo" "2" ])
|
||||||
|
"data->>'theFirst' = @field0 AND data->>'numberTwo' = @field1"
|
||||||
|
"WHERE clause not correct"
|
||||||
|
}
|
||||||
|
test "succeeds for any multiple fields with an existence comparison" {
|
||||||
|
Expect.equal
|
||||||
|
(Query.whereByFields Any [ Field.NotExists "thatField"; Field.GreaterOrEqual "thisField" 18 ])
|
||||||
|
"data->>'thatField' IS NULL OR data->>'thisField' >= @field0"
|
||||||
|
"WHERE clause not correct"
|
||||||
|
}
|
||||||
|
test "succeeds for all multiple fields with between comparisons" {
|
||||||
|
Expect.equal
|
||||||
|
(Query.whereByFields All [ Field.Between "aField" 50 99; Field.Between "anotherField" "a" "b" ])
|
||||||
|
"data->>'aField' BETWEEN @field0min AND @field0max AND data->>'anotherField' BETWEEN @field1min AND @field1max"
|
||||||
|
"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" {
|
||||||
|
Expect.equal (Query.whereById "abc") "data->>'Id' = @id" "WHERE clause not correct"
|
||||||
|
}
|
||||||
|
test "patch succeeds" {
|
||||||
|
Expect.equal
|
||||||
|
(Query.patch SqliteDb.TableName)
|
||||||
|
$"UPDATE {SqliteDb.TableName} SET data = json_patch(data, json(@data))"
|
||||||
|
"Patch query not correct"
|
||||||
|
}
|
||||||
|
test "removeFields succeeds" {
|
||||||
|
Expect.equal
|
||||||
|
(Query.removeFields SqliteDb.TableName [ SqliteParameter("@a", "a"); SqliteParameter("@b", "b") ])
|
||||||
|
$"UPDATE {SqliteDb.TableName} SET data = json_remove(data, @a, @b)"
|
||||||
|
"Field removal query not correct"
|
||||||
|
}
|
||||||
|
test "byId succeeds" {
|
||||||
|
Expect.equal (Query.byId "test" "14") "test WHERE data->>'Id' = @id" "By-ID query not correct"
|
||||||
|
}
|
||||||
|
test "byFields succeeds" {
|
||||||
|
Expect.equal
|
||||||
|
(Query.byFields "unit" Any [ Field.Greater "That" 14 ])
|
||||||
|
"unit WHERE data->>'That' > @field0"
|
||||||
|
"By-Field query not correct"
|
||||||
|
}
|
||||||
test "Definition.ensureTable succeeds" {
|
test "Definition.ensureTable succeeds" {
|
||||||
Expect.equal
|
Expect.equal
|
||||||
(Query.Definition.ensureTable "tbl")
|
(Query.Definition.ensureTable "tbl")
|
||||||
"CREATE TABLE IF NOT EXISTS tbl (data TEXT NOT NULL)"
|
"CREATE TABLE IF NOT EXISTS tbl (data TEXT NOT NULL)"
|
||||||
"CREATE TABLE statement not correct"
|
"CREATE TABLE statement not correct"
|
||||||
}
|
}
|
||||||
testList "Patch" [
|
|
||||||
test "byId succeeds" {
|
|
||||||
Expect.equal
|
|
||||||
(Query.Patch.byId "tbl")
|
|
||||||
"UPDATE tbl SET data = json_patch(data, json(@data)) WHERE data ->> 'Id' = @id"
|
|
||||||
"UPDATE partial by ID statement not correct"
|
|
||||||
}
|
|
||||||
test "byField succeeds" {
|
|
||||||
Expect.equal
|
|
||||||
(Query.Patch.byField "tbl" (Field.NE "Part" 0))
|
|
||||||
"UPDATE tbl SET data = json_patch(data, json(@data)) WHERE data ->> 'Part' <> @field"
|
|
||||||
"UPDATE partial by JSON comparison query not correct"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
testList "RemoveFields" [
|
|
||||||
test "byId succeeds" {
|
/// Unit tests for the Parameters module of the SQLite library
|
||||||
Expect.equal
|
let parametersTests = testList "Parameters" [
|
||||||
(Query.RemoveFields.byId "tbl" [ SqliteParameter("@name", "one") ])
|
|
||||||
"UPDATE tbl SET data = json_remove(data, @name) WHERE data ->> 'Id' = @id"
|
|
||||||
"Remove field by ID query not correct"
|
|
||||||
}
|
|
||||||
test "byField succeeds" {
|
|
||||||
Expect.equal
|
|
||||||
(Query.RemoveFields.byField
|
|
||||||
"tbl"
|
|
||||||
(Field.GT "Fly" 0)
|
|
||||||
[ SqliteParameter("@name0", "one"); SqliteParameter("@name1", "two") ])
|
|
||||||
"UPDATE tbl SET data = json_remove(data, @name0, @name1) WHERE data ->> 'Fly' > @field"
|
|
||||||
"Remove field by field query not correct"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
testList "Parameters" [
|
|
||||||
test "idParam succeeds" {
|
test "idParam succeeds" {
|
||||||
let theParam = idParam 7
|
let theParam = idParam 7
|
||||||
Expect.equal theParam.ParameterName "@id" "The parameter name is incorrect"
|
Expect.equal theParam.ParameterName "@id" "The parameter name is incorrect"
|
||||||
@@ -63,14 +110,14 @@ let unitTests =
|
|||||||
}
|
}
|
||||||
testList "addFieldParam" [
|
testList "addFieldParam" [
|
||||||
test "succeeds when adding a parameter" {
|
test "succeeds when adding a parameter" {
|
||||||
let paramList = addFieldParam "@field" (Field.EQ "it" 99) []
|
let paramList = addFieldParam "@field" (Field.Equal "it" 99) []
|
||||||
Expect.hasLength paramList 1 "There should have been a parameter added"
|
Expect.hasLength paramList 1 "There should have been a parameter added"
|
||||||
let theParam = paramList[0]
|
let theParam = Seq.head paramList
|
||||||
Expect.equal theParam.ParameterName "@field" "The parameter name is incorrect"
|
Expect.equal theParam.ParameterName "@field" "The parameter name is incorrect"
|
||||||
Expect.equal theParam.Value 99 "The parameter value is incorrect"
|
Expect.equal theParam.Value 99 "The parameter value is incorrect"
|
||||||
}
|
}
|
||||||
test "succeeds when not adding a parameter" {
|
test "succeeds when not adding a parameter" {
|
||||||
let paramList = addFieldParam "@it" (Field.NEX "Coffee") []
|
let paramList = addFieldParam "@it" (Field.NotExists "Coffee") []
|
||||||
Expect.isEmpty paramList "There should not have been any parameters added"
|
Expect.isEmpty paramList "There should not have been any parameters added"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -79,22 +126,17 @@ let unitTests =
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
// Results are exhaustively executed in the context of other tests
|
// Results are exhaustively executed in the context of other tests
|
||||||
]
|
|
||||||
|
|
||||||
/// These tests each use a fresh copy of a SQLite database
|
|
||||||
let integrationTests =
|
(** INTEGRATION TESTS **)
|
||||||
let documents = [
|
|
||||||
{ Id = "one"; Value = "FIRST!"; NumValue = 0; Sub = None }
|
/// Load a table with the test documents
|
||||||
{ 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 }
|
|
||||||
]
|
|
||||||
let loadDocs () = backgroundTask {
|
let loadDocs () = backgroundTask {
|
||||||
for doc in documents do do! insert SqliteDb.TableName doc
|
for doc in testDocuments do do! insert SqliteDb.TableName doc
|
||||||
}
|
}
|
||||||
testList "Integration" [
|
|
||||||
testList "Configuration" [
|
/// Integration tests for the Configuration module of the SQLite library
|
||||||
|
let configurationTests = testList "Configuration" [
|
||||||
test "useConnectionString / connectionString succeed" {
|
test "useConnectionString / connectionString succeed" {
|
||||||
try
|
try
|
||||||
Configuration.useConnectionString "Data Source=test.db"
|
Configuration.useConnectionString "Data Source=test.db"
|
||||||
@@ -105,34 +147,10 @@ let integrationTests =
|
|||||||
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"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
testList "Custom" [
|
|
||||||
|
/// Integration tests for the Custom module of the SQLite library
|
||||||
|
let customTests = testList "Custom" [
|
||||||
testList "single" [
|
testList "single" [
|
||||||
testTask "succeeds when a row is found" {
|
testTask "succeeds when a row is found" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
@@ -163,7 +181,7 @@ let integrationTests =
|
|||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! docs = Custom.list (Query.selectFromTable SqliteDb.TableName) [] fromData<JsonDocument>
|
let! docs = Custom.list (Query.find SqliteDb.TableName) [] fromData<JsonDocument>
|
||||||
Expect.hasCountOf docs 5u (fun _ -> true) "There should have been 5 documents returned"
|
Expect.hasCountOf docs 5u (fun _ -> true) "There should have been 5 documents returned"
|
||||||
}
|
}
|
||||||
testTask "succeeds when data is not found" {
|
testTask "succeeds when data is not found" {
|
||||||
@@ -207,7 +225,9 @@ let integrationTests =
|
|||||||
Expect.equal nbr 5 "The query should have returned the number 5"
|
Expect.equal nbr 5 "The query should have returned the number 5"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "Definition" [
|
|
||||||
|
/// Integration tests for the Definition module of the SQLite library
|
||||||
|
let definitionTests = testList "Definition" [
|
||||||
testTask "ensureTable succeeds" {
|
testTask "ensureTable succeeds" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
let itExists (name: string) =
|
let itExists (name: string) =
|
||||||
@@ -244,6 +264,9 @@ let integrationTests =
|
|||||||
Expect.isTrue exists' "The index should now exist"
|
Expect.isTrue exists' "The index should now exist"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
/// Integration tests for the (auto-opened) Document module of the SQLite library
|
||||||
|
let documentTests = testList "Document" [
|
||||||
testList "insert" [
|
testList "insert" [
|
||||||
testTask "succeeds" {
|
testTask "succeeds" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
@@ -263,6 +286,68 @@ let integrationTests =
|
|||||||
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" {
|
||||||
@@ -291,7 +376,10 @@ let integrationTests =
|
|||||||
Expect.equal after.Value upd8Doc "The updated document is not correct"
|
Expect.equal after.Value upd8Doc "The updated document is not correct"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "Count" [
|
]
|
||||||
|
|
||||||
|
/// Integration tests for the Count module of the SQLite library
|
||||||
|
let countTests = testList "Count" [
|
||||||
testTask "all succeeds" {
|
testTask "all succeeds" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
@@ -299,15 +387,26 @@ let integrationTests =
|
|||||||
let! theCount = Count.all SqliteDb.TableName
|
let! theCount = Count.all SqliteDb.TableName
|
||||||
Expect.equal theCount 5L "There should have been 5 matching documents"
|
Expect.equal theCount 5L "There should have been 5 matching documents"
|
||||||
}
|
}
|
||||||
testTask "byField succeeds" {
|
testList "byFields" [
|
||||||
|
testTask "succeeds for a numeric range" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! theCount = Count.byField SqliteDb.TableName (Field.EQ "Value" "purple")
|
let! theCount = Count.byFields SqliteDb.TableName Any [ Field.Between "NumValue" 10 20 ]
|
||||||
Expect.equal theCount 2L "There should have been 2 matching documents"
|
Expect.equal theCount 3L "There should have been 3 matching documents"
|
||||||
|
}
|
||||||
|
testTask "succeeds for a non-numeric range" {
|
||||||
|
use! db = SqliteDb.BuildDb()
|
||||||
|
do! loadDocs ()
|
||||||
|
|
||||||
|
let! theCount = Count.byFields SqliteDb.TableName Any [ Field.Between "Value" "aardvark" "apple" ]
|
||||||
|
Expect.equal theCount 1L "There should have been 1 matching document"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "Exists" [
|
]
|
||||||
|
|
||||||
|
/// Integration tests for the Exists module of the SQLite library
|
||||||
|
let existsTests = testList "Exists" [
|
||||||
testList "byId" [
|
testList "byId" [
|
||||||
testTask "succeeds when a document exists" {
|
testTask "succeeds when a document exists" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
@@ -324,24 +423,26 @@ let integrationTests =
|
|||||||
Expect.isFalse exists "There should not have been an existing document"
|
Expect.isFalse exists "There should not have been an existing document"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "byField" [
|
testList "byFields" [
|
||||||
testTask "succeeds when documents exist" {
|
testTask "succeeds when documents exist" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! exists = Exists.byField SqliteDb.TableName (Field.EQ "NumValue" 10)
|
let! exists = Exists.byFields SqliteDb.TableName Any [ Field.Equal "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.byField SqliteDb.TableName (Field.LT "Nothing" "none")
|
let! exists = Exists.byFields SqliteDb.TableName Any [ Field.Less "Nothing" "none" ]
|
||||||
Expect.isFalse exists "There should not have been any existing documents"
|
Expect.isFalse exists "There should not have been any existing documents"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
testList "Find" [
|
|
||||||
|
/// Integration tests for the Find module of the SQLite library
|
||||||
|
let findTests = testList "Find" [
|
||||||
testList "all" [
|
testList "all" [
|
||||||
testTask "succeeds when there is data" {
|
testTask "succeeds when there is data" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
@@ -351,12 +452,10 @@ let integrationTests =
|
|||||||
do! insert SqliteDb.TableName { Foo = "five"; Bar = "six" }
|
do! insert SqliteDb.TableName { Foo = "five"; Bar = "six" }
|
||||||
|
|
||||||
let! results = Find.all<SubDocument> SqliteDb.TableName
|
let! results = Find.all<SubDocument> SqliteDb.TableName
|
||||||
let expected = [
|
Expect.equal
|
||||||
{ Foo = "one"; Bar = "two" }
|
results
|
||||||
{ Foo = "three"; Bar = "four" }
|
[ { Foo = "one"; Bar = "two" }; { Foo = "three"; Bar = "four" }; { Foo = "five"; Bar = "six" } ]
|
||||||
{ Foo = "five"; Bar = "six" }
|
"There should have been 3 documents returned"
|
||||||
]
|
|
||||||
Expect.equal results expected "There should have been 3 documents returned"
|
|
||||||
}
|
}
|
||||||
testTask "succeeds when there is no data" {
|
testTask "succeeds when there is no data" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
@@ -364,13 +463,48 @@ let integrationTests =
|
|||||||
Expect.equal results [] "There should have been no documents returned"
|
Expect.equal results [] "There should have been no documents returned"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
testList "allOrdered" [
|
||||||
|
testTask "succeeds when ordering numerically" {
|
||||||
|
use! db = SqliteDb.BuildDb()
|
||||||
|
do! loadDocs ()
|
||||||
|
|
||||||
|
let! results = Find.allOrdered<JsonDocument> SqliteDb.TableName [ Field.Named "n:NumValue" ]
|
||||||
|
Expect.hasLength results 5 "There should have been 5 documents returned"
|
||||||
|
Expect.equal
|
||||||
|
(results |> List.map _.Id |> String.concat "|")
|
||||||
|
"one|three|two|four|five"
|
||||||
|
"The documents were not ordered correctly"
|
||||||
|
}
|
||||||
|
testTask "succeeds when ordering numerically descending" {
|
||||||
|
use! db = SqliteDb.BuildDb()
|
||||||
|
do! loadDocs ()
|
||||||
|
|
||||||
|
let! results = Find.allOrdered<JsonDocument> SqliteDb.TableName [ Field.Named "n:NumValue DESC" ]
|
||||||
|
Expect.hasLength results 5 "There should have been 5 documents returned"
|
||||||
|
Expect.equal
|
||||||
|
(results |> List.map _.Id |> String.concat "|")
|
||||||
|
"five|four|two|three|one"
|
||||||
|
"The documents were not ordered correctly"
|
||||||
|
}
|
||||||
|
testTask "succeeds when ordering alphabetically" {
|
||||||
|
use! db = SqliteDb.BuildDb()
|
||||||
|
do! loadDocs ()
|
||||||
|
|
||||||
|
let! results = Find.allOrdered<JsonDocument> SqliteDb.TableName [ Field.Named "Id DESC" ]
|
||||||
|
Expect.hasLength results 5 "There should have been 5 documents returned"
|
||||||
|
Expect.equal
|
||||||
|
(results |> List.map _.Id |> String.concat "|")
|
||||||
|
"two|three|one|four|five"
|
||||||
|
"The documents were not ordered correctly"
|
||||||
|
}
|
||||||
|
]
|
||||||
testList "byId" [
|
testList "byId" [
|
||||||
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.byId<string, JsonDocument> SqliteDb.TableName "two"
|
let! doc = Find.byId<string, JsonDocument> SqliteDb.TableName "two"
|
||||||
Expect.isTrue (Option.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"
|
||||||
}
|
}
|
||||||
testTask "succeeds when a document is not found" {
|
testTask "succeeds when a document is not found" {
|
||||||
@@ -378,52 +512,149 @@ let integrationTests =
|
|||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! doc = Find.byId<string, JsonDocument> SqliteDb.TableName "three hundred eighty-seven"
|
let! doc = Find.byId<string, JsonDocument> SqliteDb.TableName "three hundred eighty-seven"
|
||||||
Expect.isFalse (Option.isSome doc) "There should not have been a document returned"
|
Expect.isNone doc "There should not have been a document returned"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "byField" [
|
testList "byFields" [
|
||||||
testTask "succeeds when documents are found" {
|
testTask "succeeds when documents are found" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! docs = Find.byField<JsonDocument> SqliteDb.TableName (Field.GT "NumValue" 15)
|
let! docs = Find.byFields<JsonDocument> SqliteDb.TableName Any [ Field.Greater "NumValue" 15 ]
|
||||||
Expect.equal (List.length 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 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.byField<JsonDocument> SqliteDb.TableName (Field.GT "NumValue" 100)
|
let! docs = Find.byFields<JsonDocument> SqliteDb.TableName Any [ Field.Greater "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 "firstByField" [
|
testList "byFieldsOrdered" [
|
||||||
|
testTask "succeeds when sorting ascending" {
|
||||||
|
use! db = SqliteDb.BuildDb()
|
||||||
|
do! loadDocs ()
|
||||||
|
|
||||||
|
let! docs =
|
||||||
|
Find.byFieldsOrdered<JsonDocument>
|
||||||
|
SqliteDb.TableName Any [ Field.Greater "NumValue" 15 ] [ Field.Named "Id" ]
|
||||||
|
Expect.hasLength docs 2 "There should have been two documents returned"
|
||||||
|
Expect.equal
|
||||||
|
(docs |> List.map _.Id |> String.concat "|") "five|four" "The documents were not ordered correctly"
|
||||||
|
}
|
||||||
|
testTask "succeeds when sorting descending" {
|
||||||
|
use! db = SqliteDb.BuildDb()
|
||||||
|
do! loadDocs ()
|
||||||
|
|
||||||
|
let! docs =
|
||||||
|
Find.byFieldsOrdered<JsonDocument>
|
||||||
|
SqliteDb.TableName Any [ Field.Greater "NumValue" 15 ] [ Field.Named "Id DESC" ]
|
||||||
|
Expect.hasLength docs 2 "There should have been two documents returned"
|
||||||
|
Expect.equal
|
||||||
|
(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" [
|
||||||
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.firstByField<JsonDocument> SqliteDb.TableName (Field.EQ "Value" "another")
|
let! doc = Find.firstByFields<JsonDocument> SqliteDb.TableName Any [ Field.Equal "Value" "another" ]
|
||||||
Expect.isTrue (Option.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"
|
||||||
}
|
}
|
||||||
testTask "succeeds when multiple documents are found" {
|
testTask "succeeds when multiple documents are found" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! doc = Find.firstByField<JsonDocument> SqliteDb.TableName (Field.EQ "Sub.Foo" "green")
|
let! doc = Find.firstByFields<JsonDocument> SqliteDb.TableName Any [ Field.Equal "Sub.Foo" "green" ]
|
||||||
Expect.isTrue (Option.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"
|
||||||
}
|
}
|
||||||
testTask "succeeds when a document is not found" {
|
testTask "succeeds when a document is not found" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
let! doc = Find.firstByField<JsonDocument> SqliteDb.TableName (Field.EQ "Value" "absent")
|
let! doc = Find.firstByFields<JsonDocument> SqliteDb.TableName Any [ Field.Equal "Value" "absent" ]
|
||||||
Expect.isFalse (Option.isSome doc) "There should not have been a document returned"
|
Expect.isNone doc "There should not have been a document returned"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testList "firstByFieldsOrdered" [
|
||||||
|
testTask "succeeds when sorting ascending" {
|
||||||
|
use! db = SqliteDb.BuildDb()
|
||||||
|
do! loadDocs ()
|
||||||
|
|
||||||
|
let! doc =
|
||||||
|
Find.firstByFieldsOrdered<JsonDocument>
|
||||||
|
SqliteDb.TableName Any [ Field.Equal "Sub.Foo" "green" ] [ Field.Named "Sub.Bar" ]
|
||||||
|
Expect.isSome doc "There should have been a document returned"
|
||||||
|
Expect.equal "two" doc.Value.Id "An incorrect document was returned"
|
||||||
|
}
|
||||||
|
testTask "succeeds when sorting descending" {
|
||||||
|
use! db = SqliteDb.BuildDb()
|
||||||
|
do! loadDocs ()
|
||||||
|
|
||||||
|
let! doc =
|
||||||
|
Find.firstByFieldsOrdered<JsonDocument>
|
||||||
|
SqliteDb.TableName Any [ Field.Equal "Sub.Foo" "green" ] [ Field.Named "Sub.Bar DESC" ]
|
||||||
|
Expect.isSome doc "There should have been a document returned"
|
||||||
|
Expect.equal "four" doc.Value.Id "An incorrect document was returned"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
testList "Update" [
|
|
||||||
|
/// Integration tests for the Update module of the SQLite library
|
||||||
|
let updateTests = testList "Update" [
|
||||||
testList "byId" [
|
testList "byId" [
|
||||||
testTask "succeeds when a document is updated" {
|
testTask "succeeds when a document is updated" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
@@ -443,9 +674,7 @@ let integrationTests =
|
|||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
do! Update.byId
|
do! Update.byId
|
||||||
SqliteDb.TableName
|
SqliteDb.TableName "test" { emptyDoc with Id = "x"; Sub = Some { Foo = "blue"; Bar = "red" } }
|
||||||
"test"
|
|
||||||
{ emptyDoc with Id = "x"; Sub = Some { Foo = "blue"; Bar = "red" } }
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "byFunc" [
|
testList "byFunc" [
|
||||||
@@ -453,8 +682,7 @@ let integrationTests =
|
|||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
do! Update.byFunc
|
do! Update.byFunc SqliteDb.TableName (_.Id) { Id = "one"; Value = "le un"; NumValue = 1; Sub = None }
|
||||||
SqliteDb.TableName (_.Id) { Id = "one"; Value = "le un"; NumValue = 1; Sub = None }
|
|
||||||
let! after = Find.byId<string, JsonDocument> SqliteDb.TableName "one"
|
let! after = Find.byId<string, JsonDocument> SqliteDb.TableName "one"
|
||||||
Expect.isSome after "There should have been a document returned post-update"
|
Expect.isSome after "There should have been a document returned post-update"
|
||||||
Expect.equal
|
Expect.equal
|
||||||
@@ -469,12 +697,13 @@ 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! Update.byFunc
|
do! Update.byFunc SqliteDb.TableName (_.Id) { Id = "one"; Value = "le un"; NumValue = 1; Sub = None }
|
||||||
SqliteDb.TableName (_.Id) { Id = "one"; Value = "le un"; NumValue = 1; Sub = None }
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
testList "Patch" [
|
|
||||||
|
/// Integration tests for the Patch module of the SQLite library
|
||||||
|
let patchTests = testList "Patch" [
|
||||||
testList "byId" [
|
testList "byId" [
|
||||||
testTask "succeeds when a document is updated" {
|
testTask "succeeds when a document is updated" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
@@ -495,13 +724,13 @@ let integrationTests =
|
|||||||
do! Patch.byId SqliteDb.TableName "test" {| Foo = "green" |}
|
do! Patch.byId SqliteDb.TableName "test" {| Foo = "green" |}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "byField" [
|
testList "byFields" [
|
||||||
testTask "succeeds when a document is updated" {
|
testTask "succeeds when a document is updated" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
do! Patch.byField SqliteDb.TableName (Field.EQ "Value" "purple") {| NumValue = 77 |}
|
do! Patch.byFields SqliteDb.TableName Any [ Field.Equal "Value" "purple" ] {| NumValue = 77 |}
|
||||||
let! after = Count.byField SqliteDb.TableName (Field.EQ "NumValue" 77)
|
let! after = Count.byFields SqliteDb.TableName Any [ Field.Equal "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" {
|
||||||
@@ -511,11 +740,13 @@ 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! Patch.byField SqliteDb.TableName (Field.EQ "Value" "burgundy") {| Foo = "green" |}
|
do! Patch.byFields SqliteDb.TableName Any [ Field.Equal "Value" "burgundy" ] {| Foo = "green" |}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
testList "RemoveFields" [
|
|
||||||
|
/// Integration tests for the RemoveFields module of the SQLite library
|
||||||
|
let removeFieldsTests = testList "RemoveFields" [
|
||||||
testList "byId" [
|
testList "byId" [
|
||||||
testTask "succeeds when fields is removed" {
|
testTask "succeeds when fields is removed" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
@@ -543,12 +774,12 @@ let integrationTests =
|
|||||||
do! RemoveFields.byId SqliteDb.TableName "two" [ "Value" ]
|
do! RemoveFields.byId SqliteDb.TableName "two" [ "Value" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "byField" [
|
testList "byFields" [
|
||||||
testTask "succeeds when a field is removed" {
|
testTask "succeeds when a field is removed" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
do! RemoveFields.byField SqliteDb.TableName (Field.EQ "NumValue" 17) [ "Sub" ]
|
do! RemoveFields.byFields SqliteDb.TableName Any [ Field.Equal "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"
|
||||||
@@ -561,17 +792,19 @@ let integrationTests =
|
|||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
// This not raising an exception is the test
|
// This not raising an exception is the test
|
||||||
do! RemoveFields.byField SqliteDb.TableName (Field.EQ "NumValue" 17) [ "Nothing" ]
|
do! RemoveFields.byFields SqliteDb.TableName Any [ Field.Equal "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.byField SqliteDb.TableName (Field.NE "Abracadabra" "apple") [ "Value" ]
|
do! RemoveFields.byFields SqliteDb.TableName Any [ Field.NotEqual "Abracadabra" "apple" ] [ "Value" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
testList "Delete" [
|
|
||||||
|
/// Integration tests for the Delete module of the SQLite library
|
||||||
|
let deleteTests = testList "Delete" [
|
||||||
testList "byId" [
|
testList "byId" [
|
||||||
testTask "succeeds when a document is deleted" {
|
testTask "succeeds when a document is deleted" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
@@ -590,12 +823,12 @@ let integrationTests =
|
|||||||
Expect.equal remaining 5L "There should have been 5 documents remaining"
|
Expect.equal remaining 5L "There should have been 5 documents remaining"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
testList "byField" [
|
testList "byFields" [
|
||||||
testTask "succeeds when documents are deleted" {
|
testTask "succeeds when documents are deleted" {
|
||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
do! Delete.byField SqliteDb.TableName (Field.NE "Value" "purple")
|
do! Delete.byFields SqliteDb.TableName Any [ Field.NotEqual "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"
|
||||||
}
|
}
|
||||||
@@ -603,16 +836,28 @@ let integrationTests =
|
|||||||
use! db = SqliteDb.BuildDb()
|
use! db = SqliteDb.BuildDb()
|
||||||
do! loadDocs ()
|
do! loadDocs ()
|
||||||
|
|
||||||
do! Delete.byField SqliteDb.TableName (Field.EQ "Value" "crimson")
|
do! Delete.byFields SqliteDb.TableName Any [ Field.Equal "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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
test "clean up database" {
|
|
||||||
Configuration.useConnectionString "data source=:memory:"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|> testSequenced
|
|
||||||
|
|
||||||
let all = testList "Sqlite" [ unitTests; integrationTests ]
|
/// All tests for the SQLite library
|
||||||
|
let all = testList "Sqlite" [
|
||||||
|
testList "Unit" [ queryTests; parametersTests ]
|
||||||
|
testSequenced <| testList "Integration" [
|
||||||
|
configurationTests
|
||||||
|
customTests
|
||||||
|
definitionTests
|
||||||
|
documentTests
|
||||||
|
countTests
|
||||||
|
existsTests
|
||||||
|
findTests
|
||||||
|
updateTests
|
||||||
|
patchTests
|
||||||
|
removeFieldsTests
|
||||||
|
deleteTests
|
||||||
|
test "clean up database" { Configuration.useConnectionString "data source=:memory:" }
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|||||||
@@ -1,23 +1,39 @@
|
|||||||
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,13 +1,16 @@
|
|||||||
#!/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=('12' '13' '14' '15' 'latest')
|
PG_VERSIONS=('13' '14' '15' '16' 'latest')
|
||||||
NET_VERSIONS=('6.0' '7.0' '8.0')
|
NET_VERSIONS=('8.0' '9.0')
|
||||||
|
|
||||||
for PG_VERSION in "${PG_VERSIONS[@]}"
|
for PG_VERSION in "${PG_VERSIONS[@]}"
|
||||||
do
|
do
|
||||||
|
|||||||
Reference in New Issue
Block a user