diff --git a/src/Common/Library.fs b/src/Common/Library.fs index bf6f9bd..6853457 100644 --- a/src/Common/Library.fs +++ b/src/Common/Library.fs @@ -2,39 +2,45 @@ open System.Security.Cryptography -/// The types of logical operations available for JSON fields -[] -type Op = +/// The types of comparisons available for JSON fields +type Comparison = /// Equals (=) - | EQ + | Equal of Value: obj /// Greater Than (>) - | GT + | Greater of Value: obj /// Greater Than or Equal To (>=) - | GE + | GreaterOrEqual of Value: obj /// Less Than (<) - | LT + | Less of Value: obj /// Less Than or Equal To (<=) - | LE + | LessOrEqual of Value: obj /// Not Equal to (<>) - | NE + | NotEqual of Value: obj /// Between (BETWEEN) - | BT + | Between of Min: obj * Max: obj + /// In (IN) + | In of Values: obj seq + /// In Array (PostgreSQL: |?, SQLite: EXISTS / json_each / IN) + | InArray of Table: string * Values: obj seq /// Exists (IS NOT NULL) - | EX + | Exists /// Does Not Exist (IS NULL) - | NEX + | NotExists - override this.ToString() = + /// Get the operator SQL for this comparison + member this.OpSql = match this with - | EQ -> "=" - | GT -> ">" - | GE -> ">=" - | LT -> "<" - | LE -> "<=" - | NE -> "<>" - | BT -> "BETWEEN" - | EX -> "IS NOT NULL" - | NEX -> "IS NULL" + | Equal _ -> "=" + | Greater _ -> ">" + | GreaterOrEqual _ -> ">=" + | Less _ -> "<" + | LessOrEqual _ -> "<=" + | NotEqual _ -> "<>" + | Between _ -> "BETWEEN" + | In _ -> "IN" + | InArray _ -> "?|" // PostgreSQL only; SQL needs a subquery for this + | Exists -> "IS NOT NULL" + | NotExists -> "IS NULL" /// The dialect in which a command should be rendered @@ -43,73 +49,129 @@ type Dialect = | PostgreSQL | SQLite + +/// The format in which an element of a JSON field should be extracted +[] +type FieldFormat = + /// Use ->> or #>>; extracts a text (PostgreSQL) or SQL (SQLite) value + | AsSql + /// Use -> or #>; extracts a JSONB (PostgreSQL) or JSON (SQLite) value + | AsJson + + /// Criteria for a field WHERE clause -type Field = { - /// The name of the field - Name: string +type Field = + { /// The name of the field + Name: string - /// The operation by which the field will be compared - Op: Op + /// The comparison for the field + Comparison: Comparison - /// The value of the field - Value: obj + /// The name of the parameter for this field + ParameterName: string option - /// The name of the parameter for this field - ParameterName: string option + /// The table qualifier for this field + Qualifier: string option } +with - /// The table qualifier for this field - Qualifier: string option -} with + /// Create a comparison against a field + static member Where name comparison = + { Name = name; Comparison = comparison; ParameterName = None; Qualifier = None } /// Create an equals (=) field criterion - static member EQ name (value: obj) = - { Name = name; Op = EQ; Value = value; ParameterName = None; Qualifier = None } + static member Equal name (value: obj) = + Field.Where name (Equal value) + + /// Create an equals (=) field criterion (alias) + static member EQ name (value: obj) = Field.Equal name value /// Create a greater than (>) field criterion - static member GT name (value: obj) = - { Name = name; Op = GT; Value = value; ParameterName = None; Qualifier = None } + static member Greater name (value: obj) = + Field.Where name (Greater value) + + /// Create a greater than (>) field criterion (alias) + static member GT name (value: obj) = Field.Greater name value /// Create a greater than or equal to (>=) field criterion - static member GE name (value: obj) = - { Name = name; Op = GE; Value = value; ParameterName = None; Qualifier = None } + static member GreaterOrEqual name (value: obj) = + Field.Where name (GreaterOrEqual value) + + /// Create a greater than or equal to (>=) field criterion (alias) + static member GE name (value: obj) = Field.GreaterOrEqual name value /// Create a less than (<) field criterion - static member LT name (value: obj) = - { Name = name; Op = LT; Value = value; ParameterName = None; Qualifier = None } + static member Less name (value: obj) = + Field.Where name (Less value) + + /// Create a less than (<) field criterion (alias) + static member LT name (value: obj) = Field.Less name value /// Create a less than or equal to (<=) field criterion - static member LE name (value: obj) = - { Name = name; Op = LE; Value = value; ParameterName = None; Qualifier = None } + static member LessOrEqual name (value: obj) = + Field.Where name (LessOrEqual value) + + /// Create a less than or equal to (<=) field criterion (alias) + static member LE name (value: obj) = Field.LessOrEqual name value /// Create a not equals (<>) field criterion - static member NE name (value: obj) = - { Name = name; Op = NE; Value = value; ParameterName = None; Qualifier = None } + static member NotEqual name (value: obj) = + Field.Where name (NotEqual value) - /// Create a BETWEEN field criterion - static member BT name (min: obj) (max: obj) = - { Name = name; Op = BT; Value = [ min; max ]; ParameterName = None; Qualifier = None } + /// Create a not equals (<>) field criterion (alias) + static member NE name (value: obj) = Field.NotEqual name value + + /// Create a Between field criterion + static member Between name (min: obj) (max: obj) = + Field.Where name (Between(min, max)) + + /// Create a Between field criterion (alias) + static member BT name (min: obj) (max: obj) = Field.Between name min max + + /// Create an In field criterion + static member In name (values: obj seq) = + Field.Where name (In values) + + /// Create an In field criterion (alias) + static member IN name (values: obj seq) = Field.In name values + + /// Create an InArray field criterion + static member InArray name tableName (values: obj seq) = + Field.Where name (InArray(tableName, values)) /// Create an exists (IS NOT NULL) field criterion - static member EX name = - { Name = name; Op = EX; Value = obj (); ParameterName = None; Qualifier = None } + static member Exists name = + Field.Where name Exists + + /// Create an exists (IS NOT NULL) field criterion (alias) + static member EX name = Field.Exists name /// Create a not exists (IS NULL) field criterion - static member NEX name = - { Name = name; Op = NEX; Value = obj (); ParameterName = None; Qualifier = None } + static member NotExists name = + Field.Where name NotExists + + /// Create a not exists (IS NULL) field criterion (alias) + static member NEX name = Field.NotExists name /// Transform a field name (a.b.c) to a path for the given SQL dialect - static member NameToPath (name: string) dialect = + static member NameToPath (name: string) dialect format = let path = if name.Contains '.' then match dialect with - | PostgreSQL -> "#>>'{" + String.concat "," (name.Split '.') + "}'" - | SQLite -> "->>'" + String.concat "'->>'" (name.Split '.') + "'" - else $"->>'{name}'" + | 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}" /// Create a field with a given name, but no other properties filled (op will be EQ, value will be "") static member Named name = - { Name = name; Op = EQ; Value = ""; ParameterName = None; Qualifier = None } + Field.Where name (Equal "") /// Specify the name of the parameter for this field member this.WithParameterName name = @@ -120,8 +182,9 @@ type Field = { { this with Qualifier = Some alias } /// Get the qualified path to the field - member this.Path dialect = - (this.Qualifier |> Option.map (fun q -> $"{q}.") |> Option.defaultValue "") + Field.NameToPath this.Name dialect + member this.Path dialect format = + (this.Qualifier |> Option.map (fun q -> $"{q}.") |> Option.defaultValue "") + + Field.NameToPath this.Name dialect format /// How fields should be matched @@ -337,7 +400,7 @@ module Query = let parts = it.Split ' ' let fieldName = if Array.length parts = 1 then it else parts[0] let direction = if Array.length parts < 2 then "" else $" {parts[1]}" - $"({Field.NameToPath fieldName dialect}){direction}") + $"({Field.NameToPath fieldName dialect AsSql}){direction}") |> String.concat ", " $"CREATE INDEX IF NOT EXISTS idx_{tbl}_%s{indexName} ON {tableName} ({jsonFields})" @@ -403,11 +466,13 @@ module Query = |> Seq.map (fun (field, direction) -> if field.Name.StartsWith "n:" then let f = { field with Name = field.Name[2..] } - match dialect with PostgreSQL -> $"({f.Path PostgreSQL})::numeric" | SQLite -> f.Path SQLite + match dialect with + | PostgreSQL -> $"({f.Path PostgreSQL AsSql})::numeric" + | SQLite -> f.Path SQLite AsSql elif field.Name.StartsWith "i:" then - let p = { field with Name = field.Name[2..] }.Path dialect + 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 + else field.Path dialect AsSql |> function path -> path + defaultArg direction "") |> String.concat ", " |> function it -> $" ORDER BY {it}" diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 0c4f795..1a956d9 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -6,8 +6,8 @@ 4.0.0.0 4.0.0.0 4.0.0 - rc3 - From rc2: preserve additional ORDER BY qualifiers. From rc1: add case-insensitive ordering. From v3.1: Change ByField to ByFields; support dot-access to nested document fields; add Find*Ordered functions/methods; see project site for breaking changes and compatibility + rc4 + From rc3: Add In/InArray field comparisons, revamp internal comparison handling. From rc2: preserve additional ORDER BY qualifiers. From rc1: add case-insensitive ordering. From v3.1: Change ByField to ByFields; support dot-access to nested document fields; add Find*Ordered functions/methods; see project site for breaking changes and compatibility danieljsummers Bit Badger Solutions README.md diff --git a/src/Postgres/Library.fs b/src/Postgres/Library.fs index 4095549..4b4b3c2 100644 --- a/src/Postgres/Library.fs +++ b/src/Postgres/Library.fs @@ -87,18 +87,25 @@ module Parameters = fields |> Seq.map (fun it -> seq { - match it.Op with - | EX | NEX -> () - | BT -> - let p = name.Derive it.ParameterName - let values = it.Value :?> obj list - yield ($"{p}min", - parameterFor (List.head values) (fun v -> Sql.parameter (NpgsqlParameter($"{p}min", v)))) - yield ($"{p}max", - parameterFor (List.last values) (fun v -> Sql.parameter (NpgsqlParameter($"{p}max", v)))) - | _ -> + match it.Comparison with + | Exists | NotExists -> () + | Between (min, max) -> let p = name.Derive it.ParameterName - yield (p, parameterFor it.Value (fun v -> Sql.parameter (NpgsqlParameter(p, v)))) }) + yield ($"{p}min", parameterFor min (fun v -> Sql.parameter (NpgsqlParameter($"{p}min", v)))) + yield ($"{p}max", parameterFor max (fun v -> Sql.parameter (NpgsqlParameter($"{p}max", v)))) + | In values -> + let p = name.Derive it.ParameterName + yield! + values + |> Seq.mapi (fun idx v -> + let paramName = $"{p}_{idx}" + paramName, Sql.parameter (NpgsqlParameter(paramName, v))) + | InArray (_, values) -> + let p = name.Derive it.ParameterName + yield (p, Sql.stringArray (values |> Seq.map string |> Array.ofSeq)) + | Equal v | Greater v | GreaterOrEqual v | Less v | LessOrEqual v | NotEqual v -> + let p = name.Derive it.ParameterName + yield (p, parameterFor v (fun l -> Sql.parameter (NpgsqlParameter(p, l)))) }) |> Seq.collect id |> Seq.append parameters |> Seq.toList @@ -131,23 +138,28 @@ module Query = | _ -> false fields |> Seq.map (fun it -> - match it.Op with - | EX | NEX -> $"{it.Path PostgreSQL} {it.Op}" + match it.Comparison with + | Exists | NotExists -> $"{it.Path PostgreSQL AsSql} {it.Comparison.OpSql}" + | InArray _ -> $"{it.Path PostgreSQL AsJson} {it.Comparison.OpSql} {name.Derive it.ParameterName}" | _ -> let p = name.Derive it.ParameterName let param, value = - match it.Op with - | BT -> $"{p}min AND {p}max", (it.Value :?> obj list)[0] - | _ -> p, it.Value + match it.Comparison with + | Between (min, _) -> $"{p}min AND {p}max", min + | In values -> + let paramNames = values |> Seq.mapi (fun idx _ -> $"{p}_{idx}") |> String.concat ", " + $"({paramNames})", defaultArg (Seq.tryHead values) (obj ()) + | Equal v | Greater v | GreaterOrEqual v | Less v | LessOrEqual v | NotEqual v -> p, v + | _ -> p, "" if isNumeric value then - $"({it.Path PostgreSQL})::numeric {it.Op} {param}" - else $"{it.Path PostgreSQL} {it.Op} {param}") + $"({it.Path PostgreSQL AsSql})::numeric {it.Comparison.OpSql} {param}" + else $"{it.Path PostgreSQL AsSql} {it.Comparison.OpSql} {param}") |> String.concat $" {howMatched} " /// Create a WHERE clause fragment to implement an ID-based query [] let whereById<'TKey> (docId: 'TKey) = - whereByFields Any [ { Field.EQ (Configuration.idField ()) docId with ParameterName = Some "@id" } ] + whereByFields Any [ { Field.Equal (Configuration.idField ()) docId with ParameterName = Some "@id" } ] /// Table and index definition queries module Definition = diff --git a/src/Postgres/README.md b/src/Postgres/README.md index 25b493d..baa1161 100644 --- a/src/Postgres/README.md +++ b/src/Postgres/README.md @@ -66,7 +66,7 @@ var customer = await Find.ById("customer", "123"); // Find.byId type signature is string -> 'TKey -> Task<'TDoc option> let! customer = Find.byId "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): diff --git a/src/Sqlite/Library.fs b/src/Sqlite/Library.fs index d34d3f1..28e8080 100644 --- a/src/Sqlite/Library.fs +++ b/src/Sqlite/Library.fs @@ -37,18 +37,26 @@ module Query = let name = ParameterName() fields |> Seq.map (fun it -> - match it.Op with - | EX | NEX -> $"{it.Path SQLite} {it.Op}" - | BT -> + match it.Comparison with + | Exists | NotExists -> $"{it.Path SQLite AsSql} {it.Comparison.OpSql}" + | Between _ -> let p = name.Derive it.ParameterName - $"{it.Path SQLite} {it.Op} {p}min AND {p}max" - | _ -> $"{it.Path SQLite} {it.Op} {name.Derive it.ParameterName}") + $"{it.Path SQLite AsSql} {it.Comparison.OpSql} {p}min AND {p}max" + | In values -> + let p = name.Derive it.ParameterName + let paramNames = values |> Seq.mapi (fun idx _ -> $"{p}_{idx}") |> String.concat ", " + $"{it.Path SQLite AsSql} {it.Comparison.OpSql} ({paramNames})" + | InArray (table, values) -> + let p = name.Derive it.ParameterName + let paramNames = values |> Seq.mapi (fun idx _ -> $"{p}_{idx}") |> String.concat ", " + $"EXISTS (SELECT 1 FROM json_each({table}.data, '$.{it.Name}') WHERE value IN ({paramNames}))" + | _ -> $"{it.Path SQLite AsSql} {it.Comparison.OpSql} {name.Derive it.ParameterName}") |> String.concat $" {howMatched} " /// Create a WHERE clause fragment to implement an ID-based query [] let whereById paramName = - whereByFields Any [ { Field.EQ (Configuration.idField ()) 0 with ParameterName = Some paramName } ] + whereByFields Any [ { Field.Equal (Configuration.idField ()) 0 with ParameterName = Some paramName } ] /// Create an UPDATE statement to patch documents [] @@ -66,7 +74,7 @@ module Query = let byId<'TKey> statement (docId: 'TKey) = Query.statementWhere statement - (whereByFields Any [ { Field.EQ (Configuration.idField ()) docId with ParameterName = Some "@id" } ]) + (whereByFields Any [ { Field.Equal (Configuration.idField ()) docId with ParameterName = Some "@id" } ]) /// Create a query on JSON fields [] @@ -103,14 +111,16 @@ module Parameters = fields |> Seq.map (fun it -> seq { - match it.Op with - | EX | NEX -> () - | BT -> - let p = name.Derive it.ParameterName - let values = it.Value :?> obj list - yield SqliteParameter($"{p}min", List.head values) - yield SqliteParameter($"{p}max", List.last values) - | _ -> yield SqliteParameter(name.Derive it.ParameterName, it.Value) }) + match it.Comparison with + | Exists | NotExists -> () + | Between (min, max) -> + let p = name.Derive it.ParameterName + yield! [ SqliteParameter($"{p}min", min); SqliteParameter($"{p}max", max) ] + | In values | InArray (_, values) -> + let p = name.Derive it.ParameterName + yield! values |> Seq.mapi (fun idx v -> SqliteParameter($"{p}_{idx}", v)) + | Equal v | Greater v | GreaterOrEqual v | Less v | LessOrEqual v | NotEqual v -> + yield SqliteParameter(name.Derive it.ParameterName, v) }) |> Seq.collect id |> Seq.append parameters |> Seq.toList diff --git a/src/Sqlite/README.md b/src/Sqlite/README.md index 56546c0..6d069d3 100644 --- a/src/Sqlite/README.md +++ b/src/Sqlite/README.md @@ -73,13 +73,13 @@ Count customers in Atlanta: ```csharp // C#; parameters are table name, field, operator, and value // Count.ByField type signature is Func> -var customerCount = await Count.ByField("customer", Field.EQ("City", "Atlanta")); +var customerCount = await Count.ByField("customer", Field.Equal("City", "Atlanta")); ``` ```fsharp // F# // Count.byField type signature is string -> Field -> Task -let! customerCount = Count.byField "customer" (Field.EQ "City" "Atlanta") +let! customerCount = Count.byField "customer" (Field.Equal "City" "Atlanta") ``` Delete customers in Chicago: _(no offense, Second City; just an example...)_ @@ -87,13 +87,13 @@ Delete customers in Chicago: _(no offense, Second City; just an example...)_ ```csharp // C#; parameters are same as above, except return is void // Delete.ByField type signature is Func -await Delete.ByField("customer", Field.EQ("City", "Chicago")); +await Delete.ByField("customer", Field.Equal("City", "Chicago")); ``` ```fsharp // F# // Delete.byField type signature is string -> string -> Op -> obj -> Task -do! Delete.byField "customer" (Field.EQ "City" "Chicago") +do! Delete.byField "customer" (Field.Equal "City" "Chicago") ``` ## More Information diff --git a/src/Tests.CSharp/CommonCSharpTests.cs b/src/Tests.CSharp/CommonCSharpTests.cs index 0e33791..911b732 100644 --- a/src/Tests.CSharp/CommonCSharpTests.cs +++ b/src/Tests.CSharp/CommonCSharpTests.cs @@ -1,6 +1,5 @@ using Expecto.CSharp; using Expecto; -using Microsoft.FSharp.Collections; using Microsoft.FSharp.Core; namespace BitBadger.Documents.Tests.CSharp; @@ -22,45 +21,53 @@ internal class TestSerializer : IDocumentSerializer public static class CommonCSharpTests { /// - /// Unit tests for the Op enum + /// Unit tests for the OpSql property of the Comparison discriminated union /// - private static readonly Test OpTests = TestList("Op", + private static readonly Test OpTests = TestList("Comparison.OpSql", [ - TestCase("EQ succeeds", () => + TestCase("Equal succeeds", () => { - Expect.equal(Op.EQ.ToString(), "=", "The equals operator was not correct"); + Expect.equal(Comparison.NewEqual("").OpSql, "=", "The Equals SQL was not correct"); }), - TestCase("GT succeeds", () => + TestCase("Greater succeeds", () => { - Expect.equal(Op.GT.ToString(), ">", "The greater than operator was not correct"); + Expect.equal(Comparison.NewGreater("").OpSql, ">", "The Greater SQL was not correct"); }), - TestCase("GE succeeds", () => + TestCase("GreaterOrEqual succeeds", () => { - Expect.equal(Op.GE.ToString(), ">=", "The greater than or equal to operator was not correct"); + Expect.equal(Comparison.NewGreaterOrEqual("").OpSql, ">=", "The GreaterOrEqual SQL was not correct"); }), - TestCase("LT succeeds", () => + TestCase("Less succeeds", () => { - Expect.equal(Op.LT.ToString(), "<", "The less than operator was not correct"); + Expect.equal(Comparison.NewLess("").OpSql, "<", "The Less SQL was not correct"); }), - TestCase("LE succeeds", () => + TestCase("LessOrEqual succeeds", () => { - Expect.equal(Op.LE.ToString(), "<=", "The less than or equal to operator was not correct"); + Expect.equal(Comparison.NewLessOrEqual("").OpSql, "<=", "The LessOrEqual SQL was not correct"); }), - TestCase("NE succeeds", () => + TestCase("NotEqual succeeds", () => { - Expect.equal(Op.NE.ToString(), "<>", "The not equal to operator was not correct"); + Expect.equal(Comparison.NewNotEqual("").OpSql, "<>", "The NotEqual SQL was not correct"); }), - TestCase("BT succeeds", () => + TestCase("Between succeeds", () => { - Expect.equal(Op.BT.ToString(), "BETWEEN", "The \"between\" operator was not correct"); + Expect.equal(Comparison.NewBetween("", "").OpSql, "BETWEEN", "The Between SQL was not correct"); }), - TestCase("EX succeeds", () => + TestCase("In succeeds", () => { - Expect.equal(Op.EX.ToString(), "IS NOT NULL", "The \"exists\" operator was not correct"); + Expect.equal(Comparison.NewIn([]).OpSql, "IN", "The In SQL was not correct"); }), - TestCase("NEX succeeds", () => + TestCase("InArray succeeds", () => { - Expect.equal(Op.NEX.ToString(), "IS NULL", "The \"not exists\" operator was not correct"); + 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"); }) ]); @@ -69,101 +76,110 @@ public static class CommonCSharpTests /// private static readonly Test FieldTests = TestList("Field", [ - TestCase("EQ succeeds", () => + TestCase("Equal succeeds", () => { - var field = Field.EQ("Test", 14); + var field = Field.Equal("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"); + Expect.equal(field.Comparison, Comparison.NewEqual(14), "Comparison incorrect"); }), - TestCase("GT succeeds", () => + TestCase("Greater succeeds", () => { - var field = Field.GT("Great", "night"); + var field = Field.Greater("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"); + Expect.equal(field.Comparison, Comparison.NewGreater("night"), "Comparison incorrect"); }), - TestCase("GE succeeds", () => + TestCase("GreaterOrEqual succeeds", () => { - var field = Field.GE("Nice", 88L); + var field = Field.GreaterOrEqual("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"); + Expect.equal(field.Comparison, Comparison.NewGreaterOrEqual(88L), "Comparison incorrect"); }), - TestCase("LT succeeds", () => + TestCase("Less succeeds", () => { - var field = Field.LT("Lesser", "seven"); + var field = Field.Less("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"); + Expect.equal(field.Comparison, Comparison.NewLess("seven"), "Comparison incorrect"); }), - TestCase("LE succeeds", () => + TestCase("LessOrEqual succeeds", () => { - var field = Field.LE("Nobody", "KNOWS"); + var field = Field.LessOrEqual("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"); + Expect.equal(field.Comparison, Comparison.NewLessOrEqual("KNOWS"), "Comparison incorrect"); }), - TestCase("NE succeeds", () => + TestCase("NotEqual succeeds", () => { - var field = Field.NE("Park", "here"); + var field = Field.NotEqual("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"); + Expect.equal(field.Comparison, Comparison.NewNotEqual("here"), "Comparison incorrect"); }), - TestCase("BT succeeds", () => + TestCase("Between succeeds", () => { - var field = Field.BT("Age", 18, 49); + var field = Field.Between("Age", 18, 49); Expect.equal(field.Name, "Age", "Field name incorrect"); - Expect.equal(field.Op, Op.BT, "Operator incorrect"); - Expect.equal(((FSharpList)field.Value).ToArray(), [18, 49], "Value incorrect"); + Expect.equal(field.Comparison, Comparison.NewBetween(18, 49), "Comparison incorrect"); }), - TestCase("EX succeeds", () => + TestCase("In succeeds", () => { - var field = Field.EX("Groovy"); + 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.equal(field.Op, Op.EX, "Operator incorrect"); + Expect.isTrue(field.Comparison.IsExists, "Comparison incorrect"); }), - TestCase("NEX succeeds", () => + TestCase("NotExists succeeds", () => { - var field = Field.NEX("Rad"); + var field = Field.NotExists("Rad"); Expect.equal(field.Name, "Rad", "Field name incorrect"); - Expect.equal(field.Op, Op.NEX, "Operator 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), + 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), + 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), + 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), + 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.EQ("Bob", "Tom").WithParameterName("@name"); + 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.EQ("Bill", "Matt").WithQualifier("joe"); + 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"); }), @@ -171,48 +187,51 @@ public static class CommonCSharpTests [ TestCase("succeeds for a PostgreSQL single field with no qualifier", () => { - var field = Field.GE("SomethingCool", 18); - Expect.equal("data->>'SomethingCool'", field.Path(Dialect.PostgreSQL), + 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.LT("SomethingElse", 9).WithQualifier("this"); - Expect.equal("this.data->>'SomethingElse'", field.Path(Dialect.PostgreSQL), + 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.EQ("My.Nested.Field", "howdy"); - Expect.equal("data#>>'{My,Nested,Field}'", field.Path(Dialect.PostgreSQL), + 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.EQ("Nest.Away", "doc").WithQualifier("bird"); - Expect.equal("bird.data#>>'{Nest,Away}'", field.Path(Dialect.PostgreSQL), + 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.GE("SomethingCool", 18); - Expect.equal("data->>'SomethingCool'", field.Path(Dialect.SQLite), "The SQLite path is incorrect"); + 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.LT("SomethingElse", 9).WithQualifier("this"); - Expect.equal("this.data->>'SomethingElse'", field.Path(Dialect.SQLite), "The SQLite path is incorrect"); + 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.EQ("My.Nested.Field", "howdy"); - Expect.equal("data->>'My'->>'Nested'->>'Field'", field.Path(Dialect.SQLite), + 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.EQ("Nest.Away", "doc").WithQualifier("bird"); - Expect.equal("bird.data->>'Nest'->>'Away'", field.Path(Dialect.SQLite), "The SQLite path is incorrect"); + 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"); }) ]) ]); @@ -529,7 +548,7 @@ public static class CommonCSharpTests { 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 IF NOT EXISTS idx_tbl_nest ON tbl ((data->'a'->'b'->>'c'))", "CREATE INDEX for nested SQLite field incorrect"); }) ]) @@ -601,7 +620,7 @@ public static class CommonCSharpTests 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 data->'Nested'->'Test'->>'Field' DESC, data->>'AnotherField', data->>'It' DESC", "Order By not constructed correctly"); }), TestCase("succeeds for PostgreSQL numeric fields", () => @@ -623,7 +642,7 @@ public static class CommonCSharpTests 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 data->'Test'->>'Field' COLLATE NOCASE ASC NULLS LAST", "Order By not constructed correctly for case-insensitive field"); }) ]) diff --git a/src/Tests.CSharp/PostgresCSharpExtensionTests.cs b/src/Tests.CSharp/PostgresCSharpExtensionTests.cs index 054143a..65b0743 100644 --- a/src/Tests.CSharp/PostgresCSharpExtensionTests.cs +++ b/src/Tests.CSharp/PostgresCSharpExtensionTests.cs @@ -205,7 +205,7 @@ public class PostgresCSharpExtensionTests } }) ]), - TestList("save", + TestList("Save", [ TestCase("succeeds when a document is inserted", async () => { @@ -253,7 +253,7 @@ public class PostgresCSharpExtensionTests await LoadDocs(); var theCount = await conn.CountByFields(PostgresDb.TableName, FieldMatch.Any, - [Field.EQ("Value", "purple")]); + [Field.Equal("Value", "purple")]); Expect.equal(theCount, 2, "There should have been 2 matching documents"); }), TestCase("CountByContains succeeds", async () => @@ -303,7 +303,7 @@ public class PostgresCSharpExtensionTests await using var conn = MkConn(db); await LoadDocs(); - var exists = await conn.ExistsByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EX("Sub")]); + var exists = await conn.ExistsByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Exists("Sub")]); Expect.isTrue(exists, "There should have been existing documents"); }), TestCase("succeeds when documents do not exist", async () => @@ -313,7 +313,7 @@ public class PostgresCSharpExtensionTests await LoadDocs(); var exists = - await conn.ExistsByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", "six")]); + await conn.ExistsByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", "six")]); Expect.isFalse(exists, "There should not have been existing documents"); }) ]), @@ -450,7 +450,7 @@ public class PostgresCSharpExtensionTests await LoadDocs(); var docs = await conn.FindByFields(PostgresDb.TableName, FieldMatch.Any, - [Field.EQ("Value", "another")]); + [Field.Equal("Value", "another")]); Expect.equal(docs.Count, 1, "There should have been one document returned"); }), TestCase("succeeds when documents are not found", async () => @@ -460,7 +460,7 @@ public class PostgresCSharpExtensionTests await LoadDocs(); var docs = await conn.FindByFields(PostgresDb.TableName, FieldMatch.Any, - [Field.EQ("Value", "mauve")]); + [Field.Equal("Value", "mauve")]); Expect.isEmpty(docs, "There should have been no documents returned"); }) ]), @@ -473,7 +473,7 @@ public class PostgresCSharpExtensionTests await LoadDocs(); var docs = await conn.FindByFieldsOrdered(PostgresDb.TableName, FieldMatch.Any, - [Field.EQ("Value", "purple")], [Field.Named("Id")]); + [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"); @@ -485,7 +485,7 @@ public class PostgresCSharpExtensionTests await LoadDocs(); var docs = await conn.FindByFieldsOrdered(PostgresDb.TableName, FieldMatch.Any, - [Field.EQ("Value", "purple")], [Field.Named("Id DESC")]); + [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"); @@ -599,7 +599,7 @@ public class PostgresCSharpExtensionTests await LoadDocs(); var doc = await conn.FindFirstByFields(PostgresDb.TableName, FieldMatch.Any, - [Field.EQ("Value", "another")]); + [Field.Equal("Value", "another")]); Expect.isNotNull(doc, "There should have been a document returned"); Expect.equal(doc.Id, "two", "The incorrect document was returned"); }), @@ -610,7 +610,7 @@ public class PostgresCSharpExtensionTests await LoadDocs(); var doc = await conn.FindFirstByFields(PostgresDb.TableName, FieldMatch.Any, - [Field.EQ("Value", "purple")]); + [Field.Equal("Value", "purple")]); Expect.isNotNull(doc, "There should have been a document returned"); Expect.contains(["five", "four"], doc.Id, "An incorrect document was returned"); }), @@ -621,7 +621,7 @@ public class PostgresCSharpExtensionTests await LoadDocs(); var doc = await conn.FindFirstByFields(PostgresDb.TableName, FieldMatch.Any, - [Field.EQ("Value", "absent")]); + [Field.Equal("Value", "absent")]); Expect.isNull(doc, "There should not have been a document returned"); }) ]), @@ -634,7 +634,7 @@ public class PostgresCSharpExtensionTests await LoadDocs(); var doc = await conn.FindFirstByFieldsOrdered(PostgresDb.TableName, FieldMatch.Any, - [Field.EQ("Value", "purple")], [Field.Named("Id")]); + [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"); }), @@ -645,7 +645,7 @@ public class PostgresCSharpExtensionTests await LoadDocs(); var doc = await conn.FindFirstByFieldsOrdered(PostgresDb.TableName, FieldMatch.Any, - [Field.EQ("Value", "purple")], [Field.Named("Id DESC")]); + [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"); }) @@ -859,10 +859,10 @@ public class PostgresCSharpExtensionTests await using var conn = MkConn(db); await LoadDocs(); - await conn.PatchByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("Value", "purple")], + await conn.PatchByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("Value", "purple")], new { NumValue = 77 }); var after = await conn.CountByFields(PostgresDb.TableName, FieldMatch.Any, - [Field.EQ("NumValue", "77")]); + [Field.Equal("NumValue", 77)]); Expect.equal(after, 2, "There should have been 2 documents returned"); }), TestCase("succeeds when no document is updated", async () => @@ -873,7 +873,7 @@ public class PostgresCSharpExtensionTests Expect.equal(before, 0, "There should have been no documents returned"); // This not raising an exception is the test - await conn.PatchByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("Value", "burgundy")], + await conn.PatchByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("Value", "burgundy")], new { Foo = "green" }); }) ]), @@ -975,7 +975,7 @@ public class PostgresCSharpExtensionTests await using var conn = MkConn(db); await LoadDocs(); - await conn.RemoveFieldsByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", "17")], + await conn.RemoveFieldsByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", "17")], ["Sub", "Value"]); var updated = await Find.ById(PostgresDb.TableName, "four"); Expect.isNotNull(updated, "The updated document should have been retrieved"); @@ -988,7 +988,7 @@ public class PostgresCSharpExtensionTests await using var conn = MkConn(db); await LoadDocs(); - await conn.RemoveFieldsByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", "17")], + await conn.RemoveFieldsByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", "17")], ["Sub"]); var updated = await Find.ById(PostgresDb.TableName, "four"); Expect.isNotNull(updated, "The updated document should have been retrieved"); @@ -1002,7 +1002,7 @@ public class PostgresCSharpExtensionTests await LoadDocs(); // This not raising an exception is the test - await conn.RemoveFieldsByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", "17")], + await conn.RemoveFieldsByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", "17")], ["Nothing"]); }), TestCase("succeeds when no document is matched", async () => @@ -1012,7 +1012,7 @@ public class PostgresCSharpExtensionTests // This not raising an exception is the test await conn.RemoveFieldsByFields(PostgresDb.TableName, FieldMatch.Any, - [Field.NE("Abracadabra", "apple")], ["Value"]); + [Field.NotEqual("Abracadabra", "apple")], ["Value"]); }) ]), TestList("RemoveFieldsByContains", @@ -1134,7 +1134,7 @@ public class PostgresCSharpExtensionTests await using var conn = MkConn(db); await LoadDocs(); - await conn.DeleteByFields(PostgresDb.TableName, FieldMatch.Any, [Field.NE("Value", "purple")]); + await conn.DeleteByFields(PostgresDb.TableName, FieldMatch.Any, [Field.NotEqual("Value", "purple")]); var remaining = await conn.CountAll(PostgresDb.TableName); Expect.equal(remaining, 2, "There should have been 2 documents remaining"); }), @@ -1144,7 +1144,7 @@ public class PostgresCSharpExtensionTests await using var conn = MkConn(db); await LoadDocs(); - await conn.DeleteByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("Value", "crimson")]); + await conn.DeleteByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("Value", "crimson")]); var remaining = await conn.CountAll(PostgresDb.TableName); Expect.equal(remaining, 5, "There should have been 5 documents remaining"); }) diff --git a/src/Tests.CSharp/PostgresCSharpTests.cs b/src/Tests.CSharp/PostgresCSharpTests.cs index a224094..3a95a2c 100644 --- a/src/Tests.CSharp/PostgresCSharpTests.cs +++ b/src/Tests.CSharp/PostgresCSharpTests.cs @@ -111,7 +111,7 @@ public static class PostgresCSharpTests [ TestCase("succeeds when a parameter is added", () => { - var paramList = Parameters.AddFields([Field.EQ("it", "242")], []).ToList(); + var paramList = Parameters.AddFields([Field.Equal("it", "242")], []).ToList(); Expect.hasLength(paramList, 1, "There should have been a parameter added"); var (name, value) = paramList[0]; Expect.equal(name, "@field0", "Field parameter name not correct"); @@ -119,7 +119,7 @@ public static class PostgresCSharpTests }), TestCase("succeeds when multiple independent parameters are added", () => { - var paramList = Parameters.AddFields([Field.EQ("me", "you"), Field.GT("us", "them")], + var paramList = Parameters.AddFields([Field.Equal("me", "you"), Field.Greater("us", "them")], [Parameters.Id(14)]).ToList(); Expect.hasLength(paramList, 3, "There should have been 2 parameters added"); var (name, value) = paramList[0]; @@ -134,13 +134,13 @@ public static class PostgresCSharpTests }), TestCase("succeeds when a parameter is not added", () => { - var paramList = Parameters.AddFields([Field.EX("tacos")], []).ToList(); + var paramList = Parameters.AddFields([Field.Exists("tacos")], []).ToList(); Expect.isEmpty(paramList, "There should not have been any parameters added"); }), TestCase("succeeds when two parameters are added for one field", () => { var paramList = - Parameters.AddFields([Field.BT("that", "eh", "zed").WithParameterName("@test")], []).ToList(); + Parameters.AddFields([Field.Between("that", "eh", "zed").WithParameterName("@test")], []).ToList(); Expect.hasLength(paramList, 2, "There should have been 2 parameters added"); var (name, value) = paramList[0]; Expect.equal(name, "@testmin", "Minimum field name not correct"); @@ -184,54 +184,60 @@ public static class PostgresCSharpTests [ TestList("WhereByFields", [ - TestCase("succeeds for a single field when a logical operator is passed", () => + TestCase("succeeds for a single field when a logical comparison is passed", () => { Expect.equal( Postgres.Query.WhereByFields(FieldMatch.Any, - [Field.GT("theField", "0").WithParameterName("@test")]), + [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", () => + TestCase("succeeds for a single field when an existence comparison is passed", () => { - Expect.equal(Postgres.Query.WhereByFields(FieldMatch.Any, [Field.NEX("thatField")]), + Expect.equal(Postgres.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 with numeric values", () => + TestCase("succeeds for a single field when a between comparison is passed with numeric values", () => { Expect.equal( Postgres.Query.WhereByFields(FieldMatch.All, - [Field.BT("aField", 50, 99).WithParameterName("@range")]), + [Field.Between("aField", 50, 99).WithParameterName("@range")]), "(data->>'aField')::numeric BETWEEN @rangemin AND @rangemax", "WHERE clause not correct"); }), - TestCase("succeeds for a single field when a between operator is passed with non-numeric values", () => + TestCase("succeeds for a single field when a between comparison is passed with non-numeric values", () => { Expect.equal( Postgres.Query.WhereByFields(FieldMatch.Any, - [Field.BT("field0", "a", "b").WithParameterName("@alpha")]), + [Field.Between("field0", "a", "b").WithParameterName("@alpha")]), "data->>'field0' BETWEEN @alphamin AND @alphamax", "WHERE clause not correct"); }), - TestCase("succeeds for all multiple fields with logical operators", () => + TestCase("succeeds for all multiple fields with logical comparisons", () => { Expect.equal( Postgres.Query.WhereByFields(FieldMatch.All, - [Field.EQ("theFirst", "1"), Field.EQ("numberTwo", "2")]), + [Field.Equal("theFirst", "1"), Field.Equal("numberTwo", "2")]), "data->>'theFirst' = @field0 AND data->>'numberTwo' = @field1", "WHERE clause not correct"); }), - TestCase("succeeds for any multiple fields with an existence operator", () => + TestCase("succeeds for any multiple fields with an existence comparison", () => { Expect.equal( Postgres.Query.WhereByFields(FieldMatch.Any, - [Field.NEX("thatField"), Field.GE("thisField", 18)]), + [Field.NotExists("thatField"), Field.GreaterOrEqual("thisField", 18)]), "data->>'thatField' IS NULL OR (data->>'thisField')::numeric >= @field0", "WHERE clause not correct"); }), - TestCase("succeeds for all multiple fields with between operators", () => + TestCase("succeeds for all multiple fields with between comparisons", () => { Expect.equal( Postgres.Query.WhereByFields(FieldMatch.All, - [Field.BT("aField", 50, 99), Field.BT("anotherField", "a", "b")]), + [Field.Between("aField", 50, 99), Field.Between("anotherField", "a", "b")]), "(data->>'aField')::numeric BETWEEN @field0min AND @field0max AND data->>'anotherField' BETWEEN @field1min AND @field1max", "WHERE clause not correct"); + }), + TestCase("succeeds for a field with an InArray comparison", () => + { + Expect.equal( + Postgres.Query.WhereByFields(FieldMatch.All, [Field.InArray("theField", "the_table", ["q", "r"])]), + "data->'theField' ?| @field0", "WHERE clause not correct"); }) ]), TestList("WhereById", @@ -299,7 +305,7 @@ public static class PostgresCSharpTests }), TestCase("ByFields succeeds", () => { - Expect.equal(Postgres.Query.ByFields("unit", FieldMatch.Any, [Field.GT("That", 14)]), + Expect.equal(Postgres.Query.ByFields("unit", FieldMatch.Any, [Field.Greater("That", 14)]), "unit WHERE (data->>'That')::numeric > @field0", "By-Field query not correct"); }), TestCase("ByContains succeeds", () => @@ -314,21 +320,12 @@ public static class PostgresCSharpTests }) ]); - private static readonly List 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 } - ]; - /// /// Add the test documents to the database /// 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); } /// @@ -676,7 +673,7 @@ public static class PostgresCSharpTests await LoadDocs(); var theCount = await Count.ByFields(PostgresDb.TableName, FieldMatch.Any, - [Field.BT("NumValue", 10, 20)]); + [Field.Between("NumValue", 10, 20)]); Expect.equal(theCount, 3, "There should have been 3 matching documents"); }), TestCase("succeeds for non-numeric range", async () => @@ -685,7 +682,7 @@ public static class PostgresCSharpTests await LoadDocs(); var theCount = await Count.ByFields(PostgresDb.TableName, FieldMatch.All, - [Field.BT("Value", "aardvark", "apple")]); + [Field.Between("Value", "aardvark", "apple")]); Expect.equal(theCount, 1, "There should have been 1 matching document"); }) ]), @@ -738,7 +735,7 @@ public static class PostgresCSharpTests await using var db = PostgresDb.BuildDb(); await LoadDocs(); - var exists = await Exists.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.NEX("Sub")]); + var exists = await Exists.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.NotExists("Sub")]); Expect.isTrue(exists, "There should have been existing documents"); }), TestCase("succeeds when documents do not exist", async () => @@ -746,7 +743,8 @@ public static class PostgresCSharpTests await using var db = PostgresDb.BuildDb(); await LoadDocs(); - var exists = await Exists.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", "six")]); + var exists = await Exists.ByFields(PostgresDb.TableName, FieldMatch.Any, + [Field.Equal("NumValue", "six")]); Expect.isFalse(exists, "There should not have been existing documents"); }) ]), @@ -878,7 +876,16 @@ public static class PostgresCSharpTests await LoadDocs(); var docs = await Find.ByFields(PostgresDb.TableName, FieldMatch.Any, - [Field.EQ("Value", "another")]); + [Field.Equal("Value", "another")]); + Expect.hasLength(docs, 1, "There should have been one document returned"); + }), + TestCase("succeeds when documents are found using IN with numeric field", async () => + { + await using var db = PostgresDb.BuildDb(); + await LoadDocs(); + + var docs = await Find.ByFields(PostgresDb.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 () => @@ -887,7 +894,27 @@ public static class PostgresCSharpTests await LoadDocs(); var docs = await Find.ByFields(PostgresDb.TableName, FieldMatch.Any, - [Field.EQ("Value", "mauve")]); + [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 = PostgresDb.BuildDb(); + await Definition.EnsureTable(PostgresDb.TableName); + foreach (var doc in ArrayDocument.TestDocuments) await Document.Insert(PostgresDb.TableName, doc); + + var docs = await Find.ByFields(PostgresDb.TableName, FieldMatch.All, + [Field.InArray("Values", PostgresDb.TableName, ["c"])]); + Expect.hasLength(docs, 2, "There should have been two document returned"); + }), + TestCase("succeeds for InArray when no matching documents exist", async () => + { + await using var db = PostgresDb.BuildDb(); + await Definition.EnsureTable(PostgresDb.TableName); + foreach (var doc in ArrayDocument.TestDocuments) await Document.Insert(PostgresDb.TableName, doc); + + var docs = await Find.ByFields(PostgresDb.TableName, FieldMatch.All, + [Field.InArray("Values", PostgresDb.TableName, ["j"])]); Expect.isEmpty(docs, "There should have been no documents returned"); }) ]), @@ -899,7 +926,7 @@ public static class PostgresCSharpTests await LoadDocs(); var docs = await Find.ByFieldsOrdered(PostgresDb.TableName, FieldMatch.Any, - [Field.EQ("Value", "purple")], [Field.Named("Id")]); + [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"); @@ -910,7 +937,7 @@ public static class PostgresCSharpTests await LoadDocs(); var docs = await Find.ByFieldsOrdered(PostgresDb.TableName, FieldMatch.Any, - [Field.EQ("Value", "purple")], [Field.Named("Id DESC")]); + [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"); @@ -1015,7 +1042,7 @@ public static class PostgresCSharpTests await LoadDocs(); var doc = await Find.FirstByFields(PostgresDb.TableName, FieldMatch.Any, - [Field.EQ("Value", "another")]); + [Field.Equal("Value", "another")]); Expect.isNotNull(doc, "There should have been a document returned"); Expect.equal(doc.Id, "two", "The incorrect document was returned"); }), @@ -1025,7 +1052,7 @@ public static class PostgresCSharpTests await LoadDocs(); var doc = await Find.FirstByFields(PostgresDb.TableName, FieldMatch.Any, - [Field.EQ("Value", "purple")]); + [Field.Equal("Value", "purple")]); Expect.isNotNull(doc, "There should have been a document returned"); Expect.contains(["five", "four"], doc.Id, "An incorrect document was returned"); }), @@ -1035,7 +1062,7 @@ public static class PostgresCSharpTests await LoadDocs(); var doc = await Find.FirstByFields(PostgresDb.TableName, FieldMatch.Any, - [Field.EQ("Value", "absent")]); + [Field.Equal("Value", "absent")]); Expect.isNull(doc, "There should not have been a document returned"); }) ]), @@ -1047,7 +1074,7 @@ public static class PostgresCSharpTests await LoadDocs(); var doc = await Find.FirstByFieldsOrdered(PostgresDb.TableName, FieldMatch.Any, - [Field.EQ("Value", "purple")], [Field.Named("Id")]); + [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"); }), @@ -1057,7 +1084,7 @@ public static class PostgresCSharpTests await LoadDocs(); var doc = await Find.FirstByFieldsOrdered(PostgresDb.TableName, FieldMatch.Any, - [Field.EQ("Value", "purple")], [Field.Named("Id DESC")]); + [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"); }) @@ -1271,9 +1298,9 @@ public static class PostgresCSharpTests await using var db = PostgresDb.BuildDb(); await LoadDocs(); - await Patch.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("Value", "purple")], + await Patch.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("Value", "purple")], new { NumValue = 77 }); - var after = await Count.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", "77")]); + var after = await Count.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", "77")]); Expect.equal(after, 2, "There should have been 2 documents returned"); }), TestCase("succeeds when no document is updated", async () => @@ -1284,7 +1311,7 @@ public static class PostgresCSharpTests Expect.equal(before, 0, "There should have been no documents returned"); // This not raising an exception is the test - await Patch.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("Value", "burgundy")], + await Patch.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("Value", "burgundy")], new { Foo = "green" }); }) ]), @@ -1386,7 +1413,7 @@ public static class PostgresCSharpTests await using var db = PostgresDb.BuildDb(); await LoadDocs(); - await RemoveFields.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", "17")], + await RemoveFields.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", "17")], ["Sub", "Value"]); var updated = await Find.ById(PostgresDb.TableName, "four"); Expect.isNotNull(updated, "The updated document should have been retrieved"); @@ -1398,7 +1425,7 @@ public static class PostgresCSharpTests await using var db = PostgresDb.BuildDb(); await LoadDocs(); - await RemoveFields.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", "17")], + await RemoveFields.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", "17")], ["Sub"]); var updated = await Find.ById(PostgresDb.TableName, "four"); Expect.isNotNull(updated, "The updated document should have been retrieved"); @@ -1411,7 +1438,7 @@ public static class PostgresCSharpTests await LoadDocs(); // This not raising an exception is the test - await RemoveFields.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", "17")], + await RemoveFields.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", "17")], ["Nothing"]); }), TestCase("succeeds when no document is matched", async () => @@ -1419,8 +1446,8 @@ public static class PostgresCSharpTests await using var db = PostgresDb.BuildDb(); // This not raising an exception is the test - await RemoveFields.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.NE("Abracadabra", "apple")], - ["Value"]); + await RemoveFields.ByFields(PostgresDb.TableName, FieldMatch.Any, + [Field.NotEqual("Abracadabra", "apple")], ["Value"]); }) ]), TestList("ByContains", @@ -1538,7 +1565,7 @@ public static class PostgresCSharpTests await using var db = PostgresDb.BuildDb(); await LoadDocs(); - await Delete.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("Value", "purple")]); + await Delete.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("Value", "purple")]); var remaining = await Count.All(PostgresDb.TableName); Expect.equal(remaining, 3, "There should have been 3 documents remaining"); }), @@ -1547,7 +1574,7 @@ public static class PostgresCSharpTests await using var db = PostgresDb.BuildDb(); await LoadDocs(); - await Delete.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.EQ("Value", "crimson")]); + await Delete.ByFields(PostgresDb.TableName, FieldMatch.Any, [Field.Equal("Value", "crimson")]); var remaining = await Count.All(PostgresDb.TableName); Expect.equal(remaining, 5, "There should have been 5 documents remaining"); }) diff --git a/src/Tests.CSharp/PostgresDb.cs b/src/Tests.CSharp/PostgresDb.cs index 4d84e2c..063c299 100644 --- a/src/Tests.CSharp/PostgresDb.cs +++ b/src/Tests.CSharp/PostgresDb.cs @@ -1,4 +1,3 @@ -using BitBadger.Documents.Postgres; using Npgsql; using Npgsql.FSharp; using ThrowawayDb.Postgres; diff --git a/src/Tests.CSharp/SqliteCSharpExtensionTests.cs b/src/Tests.CSharp/SqliteCSharpExtensionTests.cs index 7290183..5d23375 100644 --- a/src/Tests.CSharp/SqliteCSharpExtensionTests.cs +++ b/src/Tests.CSharp/SqliteCSharpExtensionTests.cs @@ -221,7 +221,8 @@ public static class SqliteCSharpExtensionTests await using var conn = Sqlite.Configuration.DbConn(); await LoadDocs(); - var theCount = await conn.CountByFields(SqliteDb.TableName, FieldMatch.Any, [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"); }), TestList("ExistsById", @@ -253,7 +254,8 @@ public static class SqliteCSharpExtensionTests await using var conn = Sqlite.Configuration.DbConn(); await LoadDocs(); - var exists = await conn.ExistsByFields(SqliteDb.TableName, FieldMatch.Any, [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"); }), TestCase("succeeds when no matching documents exist", async () => @@ -263,7 +265,7 @@ public static class SqliteCSharpExtensionTests await LoadDocs(); var exists = - await conn.ExistsByFields(SqliteDb.TableName, FieldMatch.Any, [Field.EQ("Nothing", "none")]); + await conn.ExistsByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("Nothing", "none")]); Expect.isFalse(exists, "There should not have been any existing documents"); }) ]), @@ -357,7 +359,7 @@ public static class SqliteCSharpExtensionTests await LoadDocs(); var docs = await conn.FindByFields(SqliteDb.TableName, FieldMatch.Any, - [Field.GT("NumValue", 15)]); + [Field.Greater("NumValue", 15)]); Expect.equal(docs.Count, 2, "There should have been two documents returned"); }), TestCase("succeeds when documents are not found", async () => @@ -367,7 +369,7 @@ public static class SqliteCSharpExtensionTests await LoadDocs(); var docs = await conn.FindByFields(SqliteDb.TableName, FieldMatch.Any, - [Field.EQ("Value", "mauve")]); + [Field.Equal("Value", "mauve")]); Expect.isEmpty(docs, "There should have been no documents returned"); }) ]), @@ -380,7 +382,7 @@ public static class SqliteCSharpExtensionTests await LoadDocs(); var docs = await conn.FindByFieldsOrdered(SqliteDb.TableName, FieldMatch.Any, - [Field.GT("NumValue", 15)], [Field.Named("Id")]); + [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"); }), @@ -391,7 +393,7 @@ public static class SqliteCSharpExtensionTests await LoadDocs(); var docs = await conn.FindByFieldsOrdered(SqliteDb.TableName, FieldMatch.Any, - [Field.GT("NumValue", 15)], [Field.Named("Id DESC")]); + [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"); }) @@ -405,7 +407,7 @@ public static class SqliteCSharpExtensionTests await LoadDocs(); var doc = await conn.FindFirstByFields(SqliteDb.TableName, FieldMatch.Any, - [Field.EQ("Value", "another")]); + [Field.Equal("Value", "another")]); Expect.isNotNull(doc, "There should have been a document returned"); Expect.equal(doc!.Id, "two", "The incorrect document was returned"); }), @@ -416,7 +418,7 @@ public static class SqliteCSharpExtensionTests await LoadDocs(); var doc = await conn.FindFirstByFields(SqliteDb.TableName, FieldMatch.Any, - [Field.EQ("Sub.Foo", "green")]); + [Field.Equal("Sub.Foo", "green")]); Expect.isNotNull(doc, "There should have been a document returned"); Expect.contains(["two", "four"], doc!.Id, "An incorrect document was returned"); }), @@ -427,7 +429,7 @@ public static class SqliteCSharpExtensionTests await LoadDocs(); var doc = await conn.FindFirstByFields(SqliteDb.TableName, FieldMatch.Any, - [Field.EQ("Value", "absent")]); + [Field.Equal("Value", "absent")]); Expect.isNull(doc, "There should not have been a document returned"); }) ]), @@ -440,7 +442,7 @@ public static class SqliteCSharpExtensionTests await LoadDocs(); var doc = await conn.FindFirstByFieldsOrdered(SqliteDb.TableName, FieldMatch.Any, - [Field.EQ("Sub.Foo", "green")], [Field.Named("Sub.Bar")]); + [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"); }), @@ -451,7 +453,7 @@ public static class SqliteCSharpExtensionTests await LoadDocs(); var doc = await conn.FindFirstByFieldsOrdered(SqliteDb.TableName, FieldMatch.Any, - [Field.EQ("Sub.Foo", "green")], [Field.Named("Sub.Bar DESC")]); + [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"); }) @@ -547,9 +549,9 @@ public static class SqliteCSharpExtensionTests await using var conn = Sqlite.Configuration.DbConn(); await LoadDocs(); - await conn.PatchByFields(SqliteDb.TableName, FieldMatch.Any, [Field.EQ("Value", "purple")], + await conn.PatchByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("Value", "purple")], new { NumValue = 77 }); - var after = await conn.CountByFields(SqliteDb.TableName, FieldMatch.Any, [Field.EQ("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"); }), TestCase("succeeds when no document is updated", async () => @@ -560,7 +562,7 @@ public static class SqliteCSharpExtensionTests Expect.isEmpty(before, "There should have been no documents returned"); // This not raising an exception is the test - await conn.PatchByFields(SqliteDb.TableName, FieldMatch.Any, [Field.EQ("Value", "burgundy")], + await conn.PatchByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("Value", "burgundy")], new { Foo = "green" }); }) ]), @@ -604,7 +606,7 @@ public static class SqliteCSharpExtensionTests await using var conn = Sqlite.Configuration.DbConn(); await LoadDocs(); - await conn.RemoveFieldsByFields(SqliteDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", 17)], + await conn.RemoveFieldsByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", 17)], ["Sub"]); var updated = await Find.ById(SqliteDb.TableName, "four"); Expect.isNotNull(updated, "The updated document should have been retrieved"); @@ -617,7 +619,7 @@ public static class SqliteCSharpExtensionTests await LoadDocs(); // This not raising an exception is the test - await conn.RemoveFieldsByFields(SqliteDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", 17)], + await conn.RemoveFieldsByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", 17)], ["Nothing"]); }), TestCase("succeeds when no document is matched", async () => @@ -626,8 +628,8 @@ public static class SqliteCSharpExtensionTests await using var conn = Sqlite.Configuration.DbConn(); // This not raising an exception is the test - await conn.RemoveFieldsByFields(SqliteDb.TableName, FieldMatch.Any, [Field.NE("Abracadabra", "apple")], - ["Value"]); + await conn.RemoveFieldsByFields(SqliteDb.TableName, FieldMatch.Any, + [Field.NotEqual("Abracadabra", "apple")], ["Value"]); }) ]), TestList("DeleteById", @@ -661,7 +663,7 @@ public static class SqliteCSharpExtensionTests await using var conn = Sqlite.Configuration.DbConn(); await LoadDocs(); - await conn.DeleteByFields(SqliteDb.TableName, FieldMatch.Any, [Field.NE("Value", "purple")]); + await conn.DeleteByFields(SqliteDb.TableName, FieldMatch.Any, [Field.NotEqual("Value", "purple")]); var remaining = await conn.CountAll(SqliteDb.TableName); Expect.equal(remaining, 2L, "There should have been 2 documents remaining"); }), @@ -671,7 +673,7 @@ public static class SqliteCSharpExtensionTests await using var conn = Sqlite.Configuration.DbConn(); await LoadDocs(); - await conn.DeleteByFields(SqliteDb.TableName, FieldMatch.Any, [Field.EQ("Value", "crimson")]); + await conn.DeleteByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("Value", "crimson")]); var remaining = await conn.CountAll(SqliteDb.TableName); Expect.equal(remaining, 5L, "There should have been 5 documents remaining"); }) diff --git a/src/Tests.CSharp/SqliteCSharpTests.cs b/src/Tests.CSharp/SqliteCSharpTests.cs index 71d38b4..a9cce70 100644 --- a/src/Tests.CSharp/SqliteCSharpTests.cs +++ b/src/Tests.CSharp/SqliteCSharpTests.cs @@ -22,40 +22,55 @@ public static class SqliteCSharpTests TestCase("succeeds for a single field when a logical operator is passed", () => { Expect.equal( - Sqlite.Query.WhereByFields(FieldMatch.Any, [Field.GT("theField", 0).WithParameterName("@test")]), + 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.NEX("thatField")]), + 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.BT("aField", 50, 99).WithParameterName("@range")]), + [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.EQ("theFirst", "1"), Field.EQ("numberTwo", "2")]), + 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.NEX("thatField"), Field.GE("thisField", 18)]), + 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.BT("aField", 50, 99), Field.BT("anotherField", "a", "b")]), + [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", () => @@ -79,7 +94,7 @@ public static class SqliteCSharpTests }), TestCase("ByFields succeeds", () => { - Expect.equal(Sqlite.Query.ByFields("unit", FieldMatch.Any, [Field.GT("That", 14)]), + 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", () => @@ -109,7 +124,7 @@ public static class SqliteCSharpTests #pragma warning disable CS0618 TestCase("AddField succeeds when adding a parameter", () => { - var paramList = Parameters.AddField("@field", Field.EQ("it", 99), []).ToList(); + var paramList = Parameters.AddField("@field", Field.Equal("it", 99), []).ToList(); Expect.hasLength(paramList, 1, "There should have been a parameter added"); var theParam = paramList[0]; Expect.equal(theParam.ParameterName, "@field", "The parameter name is incorrect"); @@ -117,7 +132,7 @@ public static class SqliteCSharpTests }), TestCase("AddField succeeds when not adding a parameter", () => { - var paramSeq = Parameters.AddField("@it", Field.EX("Coffee"), []); + var paramSeq = Parameters.AddField("@it", Field.Exists("Coffee"), []); Expect.isEmpty(paramSeq, "There should not have been any parameters added"); }), #pragma warning restore CS0618 @@ -129,24 +144,12 @@ public static class SqliteCSharpTests // Results are exhaustively executed in the context of other tests - /// - /// Documents used for integration tests - /// - private static readonly List 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 } - ]; - /// /// Add the test documents to the database /// 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); } /// @@ -459,7 +462,8 @@ public static class SqliteCSharpTests await using var db = await SqliteDb.BuildDb(); await LoadDocs(); - var theCount = await Count.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.BT("NumValue", 10, 20)]); + var theCount = await Count.ByFields(SqliteDb.TableName, FieldMatch.Any, + [Field.Between("NumValue", 10, 20)]); Expect.equal(theCount, 3L, "There should have been 3 matching documents"); }), TestCase("succeeds for non-numeric range", async () => @@ -468,7 +472,7 @@ public static class SqliteCSharpTests await LoadDocs(); var theCount = await Count.ByFields(SqliteDb.TableName, FieldMatch.Any, - [Field.BT("Value", "aardvark", "apple")]); + [Field.Between("Value", "aardvark", "apple")]); Expect.equal(theCount, 1L, "There should have been 1 matching document"); }) ]) @@ -505,7 +509,8 @@ public static class SqliteCSharpTests await using var db = await SqliteDb.BuildDb(); await LoadDocs(); - var exists = await Exists.ByFields(SqliteDb.TableName, FieldMatch.Any, [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"); }), TestCase("succeeds when no matching documents exist", async () => @@ -513,7 +518,8 @@ public static class SqliteCSharpTests await using var db = await SqliteDb.BuildDb(); await LoadDocs(); - var exists = await Exists.ByFields(SqliteDb.TableName, FieldMatch.Any, [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"); }) ]) @@ -605,16 +611,45 @@ public static class SqliteCSharpTests await LoadDocs(); var docs = await Find.ByFields(SqliteDb.TableName, FieldMatch.Any, - [Field.GT("NumValue", 15)]); + [Field.Greater("NumValue", 15)]); 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(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 () => { await using var db = await SqliteDb.BuildDb(); await LoadDocs(); var docs = await Find.ByFields(SqliteDb.TableName, FieldMatch.Any, - [Field.EQ("Value", "mauve")]); + [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(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(SqliteDb.TableName, FieldMatch.All, + [Field.InArray("Values", SqliteDb.TableName, ["j"])]); Expect.isEmpty(docs, "There should have been no documents returned"); }) ]), @@ -626,7 +661,7 @@ public static class SqliteCSharpTests await LoadDocs(); var docs = await Find.ByFieldsOrdered(SqliteDb.TableName, FieldMatch.Any, - [Field.GT("NumValue", 15)], [Field.Named("Id")]); + [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"); @@ -637,7 +672,7 @@ public static class SqliteCSharpTests await LoadDocs(); var docs = await Find.ByFieldsOrdered(SqliteDb.TableName, FieldMatch.Any, - [Field.GT("NumValue", 15)], [Field.Named("Id DESC")]); + [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"); @@ -648,7 +683,7 @@ public static class SqliteCSharpTests await LoadDocs(); var docs = await Find.ByFieldsOrdered(SqliteDb.TableName, FieldMatch.Any, - [Field.LE("NumValue", 10)], [Field.Named("Value")]); + [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"); @@ -659,7 +694,7 @@ public static class SqliteCSharpTests await LoadDocs(); var docs = await Find.ByFieldsOrdered(SqliteDb.TableName, FieldMatch.Any, - [Field.LE("NumValue", 10)], [Field.Named("i:Value")]); + [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"); @@ -673,7 +708,7 @@ public static class SqliteCSharpTests await LoadDocs(); var doc = await Find.FirstByFields(SqliteDb.TableName, FieldMatch.Any, - [Field.EQ("Value", "another")]); + [Field.Equal("Value", "another")]); Expect.isNotNull(doc, "There should have been a document returned"); Expect.equal(doc!.Id, "two", "The incorrect document was returned"); }), @@ -683,7 +718,7 @@ public static class SqliteCSharpTests await LoadDocs(); var doc = await Find.FirstByFields(SqliteDb.TableName, FieldMatch.Any, - [Field.EQ("Sub.Foo", "green")]); + [Field.Equal("Sub.Foo", "green")]); Expect.isNotNull(doc, "There should have been a document returned"); Expect.contains(["two", "four"], doc!.Id, "An incorrect document was returned"); }), @@ -693,7 +728,7 @@ public static class SqliteCSharpTests await LoadDocs(); var doc = await Find.FirstByFields(SqliteDb.TableName, FieldMatch.Any, - [Field.EQ("Value", "absent")]); + [Field.Equal("Value", "absent")]); Expect.isNull(doc, "There should not have been a document returned"); }) ]), @@ -705,7 +740,7 @@ public static class SqliteCSharpTests await LoadDocs(); var doc = await Find.FirstByFieldsOrdered(SqliteDb.TableName, FieldMatch.Any, - [Field.EQ("Sub.Foo", "green")], [Field.Named("Sub.Bar")]); + [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"); }), @@ -715,7 +750,7 @@ public static class SqliteCSharpTests await LoadDocs(); var doc = await Find.FirstByFieldsOrdered(SqliteDb.TableName, FieldMatch.Any, - [Field.EQ("Sub.Foo", "green")], [Field.Named("Sub.Bar DESC")]); + [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"); }) @@ -821,9 +856,9 @@ public static class SqliteCSharpTests await using var db = await SqliteDb.BuildDb(); await LoadDocs(); - await Patch.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.EQ("Value", "purple")], + await Patch.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("Value", "purple")], new { NumValue = 77 }); - var after = await Count.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.EQ("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"); }), TestCase("succeeds when no document is updated", async () => @@ -834,7 +869,7 @@ public static class SqliteCSharpTests Expect.isEmpty(before, "There should have been no documents returned"); // This not raising an exception is the test - await Patch.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.EQ("Value", "burgundy")], + await Patch.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("Value", "burgundy")], new { Foo = "green" }); }) ]) @@ -881,7 +916,7 @@ public static class SqliteCSharpTests await using var db = await SqliteDb.BuildDb(); await LoadDocs(); - await RemoveFields.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", 17)], ["Sub"]); + await RemoveFields.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", 17)], ["Sub"]); var updated = await Find.ById(SqliteDb.TableName, "four"); Expect.isNotNull(updated, "The updated document should have been retrieved"); Expect.isNull(updated.Sub, "The sub-document should have been removed"); @@ -892,7 +927,7 @@ public static class SqliteCSharpTests await LoadDocs(); // This not raising an exception is the test - await RemoveFields.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.EQ("NumValue", 17)], + await RemoveFields.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.Equal("NumValue", 17)], ["Nothing"]); }), TestCase("succeeds when no document is matched", async () => @@ -900,8 +935,8 @@ public static class SqliteCSharpTests await using var db = await SqliteDb.BuildDb(); // This not raising an exception is the test - await RemoveFields.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.NE("Abracadabra", "apple")], - ["Value"]); + await RemoveFields.ByFields(SqliteDb.TableName, FieldMatch.Any, + [Field.NotEqual("Abracadabra", "apple")], ["Value"]); }) ]) ]); @@ -939,7 +974,7 @@ public static class SqliteCSharpTests await using var db = await SqliteDb.BuildDb(); await LoadDocs(); - await Delete.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.NE("Value", "purple")]); + await Delete.ByFields(SqliteDb.TableName, FieldMatch.Any, [Field.NotEqual("Value", "purple")]); var remaining = await Count.All(SqliteDb.TableName); Expect.equal(remaining, 2L, "There should have been 2 documents remaining"); }), @@ -948,7 +983,7 @@ public static class SqliteCSharpTests await using var db = await SqliteDb.BuildDb(); await LoadDocs(); - await Delete.ByFields(SqliteDb.TableName, FieldMatch.All, [Field.EQ("Value", "crimson")]); + await Delete.ByFields(SqliteDb.TableName, FieldMatch.All, [Field.Equal("Value", "crimson")]); var remaining = await Count.All(SqliteDb.TableName); Expect.equal(remaining, 5L, "There should have been 5 documents remaining"); }) diff --git a/src/Tests.CSharp/Types.cs b/src/Tests.CSharp/Types.cs index ce63230..3acb0d0 100644 --- a/src/Tests.CSharp/Types.cs +++ b/src/Tests.CSharp/Types.cs @@ -18,4 +18,32 @@ public class JsonDocument public string Value { get; set; } = ""; public int NumValue { get; set; } = 0; public SubDocument? Sub { get; set; } = null; + + /// + /// A set of documents used for integration tests + /// + public static readonly List 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; } = []; + + /// + /// A set of documents used for integration tests + /// + public static readonly List TestDocuments = + [ + new() { Id = "first", Values = ["a", "b", "c"] }, + new() { Id = "second", Values = ["c", "d", "e"] }, + new() { Id = "third", Values = ["x", "y", "z"] } + ]; } diff --git a/src/Tests/CommonTests.fs b/src/Tests/CommonTests.fs index 5718789..95b4f2d 100644 --- a/src/Tests/CommonTests.fs +++ b/src/Tests/CommonTests.fs @@ -7,170 +7,183 @@ open Expecto let tbl = "test_table" /// Unit tests for the Op DU -let opTests = testList "Op" [ - test "EQ succeeds" { - Expect.equal (string EQ) "=" "The equals operator was not correct" +let comparisonTests = testList "Comparison.OpSql" [ + test "Equal succeeds" { + Expect.equal (Equal "").OpSql "=" "The Equals SQL was not correct" } - test "GT succeeds" { - Expect.equal (string GT) ">" "The greater than operator was not correct" + test "Greater succeeds" { + Expect.equal (Greater "").OpSql ">" "The Greater SQL was not correct" } - test "GE succeeds" { - Expect.equal (string GE) ">=" "The greater than or equal to operator was not correct" + test "GreaterOrEqual succeeds" { + Expect.equal (GreaterOrEqual "").OpSql ">=" "The GreaterOrEqual SQL was not correct" } - test "LT succeeds" { - Expect.equal (string LT) "<" "The less than operator was not correct" + test "Less succeeds" { + Expect.equal (Less "").OpSql "<" "The Less SQL was not correct" } - test "LE succeeds" { - Expect.equal (string LE) "<=" "The less than or equal to operator was not correct" + test "LessOrEqual succeeds" { + Expect.equal (LessOrEqual "").OpSql "<=" "The LessOrEqual SQL was not correct" } - test "NE succeeds" { - Expect.equal (string NE) "<>" "The not equal to operator was not correct" + test "NotEqual succeeds" { + Expect.equal (NotEqual "").OpSql "<>" "The NotEqual SQL was not correct" } - test "BT succeeds" { - Expect.equal (string BT) "BETWEEN" """The "between" operator was not correct""" + test "Between succeeds" { + Expect.equal (Between("", "")).OpSql "BETWEEN" "The Between SQL was not correct" } - test "EX succeeds" { - Expect.equal (string EX) "IS NOT NULL" """The "exists" operator was not correct""" + test "In succeeds" { + Expect.equal (In []).OpSql "IN" "The In SQL was not correct" } - test "NEX succeeds" { - Expect.equal (string NEX) "IS NULL" """The "not exists" operator 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" } ] /// Unit tests for the Field class let fieldTests = testList "Field" [ - test "EQ succeeds" { - let field = Field.EQ "Test" 14 + test "Equal succeeds" { + let field = Field.Equal "Test" 14 Expect.equal field.Name "Test" "Field name incorrect" - Expect.equal field.Op EQ "Operator incorrect" - Expect.equal field.Value 14 "Value 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 "GT succeeds" { - let field = Field.GT "Great" "night" + test "Greater succeeds" { + let field = Field.Greater "Great" "night" Expect.equal field.Name "Great" "Field name incorrect" - Expect.equal field.Op GT "Operator incorrect" - Expect.equal field.Value "night" "Value incorrect" + Expect.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" } - test "GE succeeds" { - let field = Field.GE "Nice" 88L + test "GreaterOrEqual succeeds" { + let field = Field.GreaterOrEqual "Nice" 88L Expect.equal field.Name "Nice" "Field name incorrect" - Expect.equal field.Op GE "Operator incorrect" - Expect.equal field.Value 88L "Value incorrect" + Expect.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 "LT succeeds" { - let field = Field.LT "Lesser" "seven" + test "Less succeeds" { + let field = Field.Less "Lesser" "seven" Expect.equal field.Name "Lesser" "Field name incorrect" - Expect.equal field.Op LT "Operator incorrect" - Expect.equal field.Value "seven" "Value incorrect" + Expect.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 "LE succeeds" { - let field = Field.LE "Nobody" "KNOWS"; + test "LessOrEqual succeeds" { + let field = Field.LessOrEqual "Nobody" "KNOWS"; Expect.equal field.Name "Nobody" "Field name incorrect" - Expect.equal field.Op LE "Operator incorrect" - Expect.equal field.Value "KNOWS" "Value incorrect" + Expect.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 "NE succeeds" { - let field = Field.NE "Park" "here" + test "NotEqual succeeds" { + let field = Field.NotEqual "Park" "here" Expect.equal field.Name "Park" "Field name incorrect" - Expect.equal field.Op NE "Operator incorrect" - Expect.equal field.Value "here" "Value incorrect" + Expect.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 "BT succeeds" { - let field = Field.BT "Age" 18 49 + test "Between succeeds" { + let field = Field.Between "Age" 18 49 Expect.equal field.Name "Age" "Field name incorrect" - Expect.equal field.Op BT "Operator incorrect" - Expect.sequenceEqual (field.Value :?> obj list) [ 18; 49 ] "Value incorrect" + Expect.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 "EX succeeds" { - let field = Field.EX "Groovy" + test "In succeeds" { + let field = Field.In "Here" [| 8; 16; 32 |] + Expect.equal field.Name "Here" "Field name incorrect" + Expect.equal field.Comparison (In [| 8; 16; 32 |]) "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 "InArray succeeds" { + let field = Field.InArray "ArrayField" "table" [| "z" |] + Expect.equal field.Name "ArrayField" "Field name incorrect" + Expect.equal field.Comparison (InArray("table", [| "z" |])) "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 "Exists succeeds" { + let field = Field.Exists "Groovy" Expect.equal field.Name "Groovy" "Field name incorrect" - Expect.equal field.Op EX "Operator 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 "NEX succeeds" { - let field = Field.NEX "Rad" + test "NotExists succeeds" { + let field = Field.NotExists "Rad" Expect.equal field.Name "Rad" "Field name incorrect" - Expect.equal field.Op NEX "Operator 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) "Path not constructed correctly" + 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) "Path not constructed correctly" + Expect.equal "data->>'Simple'" (Field.NameToPath "Simple" SQLite AsSql) "Path not constructed correctly" } test "succeeds for PostgreSQL and a nested name" { Expect.equal "data#>>'{A,Long,Path,to,the,Property}'" - (Field.NameToPath "A.Long.Path.to.the.Property" PostgreSQL) + (Field.NameToPath "A.Long.Path.to.the.Property" PostgreSQL AsSql) "Path not constructed correctly" } test "succeeds for SQLite and a nested name" { Expect.equal - "data->>'A'->>'Long'->>'Path'->>'to'->>'the'->>'Property'" - (Field.NameToPath "A.Long.Path.to.the.Property" SQLite) + "data->'A'->'Long'->'Path'->'to'->'the'->>'Property'" + (Field.NameToPath "A.Long.Path.to.the.Property" SQLite AsSql) "Path not constructed correctly" } ] test "WithParameterName succeeds" { - let field = (Field.EQ "Bob" "Tom").WithParameterName "@name" + 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.EQ "Bill" "Matt").WithQualifier "joe" + 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.GE "SomethingCool" 18 - Expect.equal "data->>'SomethingCool'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect" + 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.LT "SomethingElse" 9 with Qualifier = Some "this" } - Expect.equal "this.data->>'SomethingElse'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect" + 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.EQ "My.Nested.Field" "howdy" - Expect.equal "data#>>'{My,Nested,Field}'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect" + 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.EQ "Nest.Away" "doc" with Qualifier = Some "bird" } - Expect.equal "bird.data#>>'{Nest,Away}'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect" + 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.GE "SomethingCool" 18 - Expect.equal "data->>'SomethingCool'" (field.Path SQLite) "The SQLite path is incorrect" + 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.LT "SomethingElse" 9 with Qualifier = Some "this" } - Expect.equal "this.data->>'SomethingElse'" (field.Path SQLite) "The SQLite path is incorrect" + 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.EQ "My.Nested.Field" "howdy" - Expect.equal "data->>'My'->>'Nested'->>'Field'" (field.Path SQLite) "The SQLite path is incorrect" + 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.EQ "Nest.Away" "doc" with Qualifier = Some "bird" } - Expect.equal "bird.data->>'Nest'->>'Away'" (field.Path SQLite) "The SQLite path is incorrect" + 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" } ] ] @@ -373,7 +386,7 @@ let queryTests = testList "Query" [ 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 IF NOT EXISTS idx_{tbl}_nest ON {tbl} ((data->'a'->'b'->>'c'))" "CREATE INDEX for nested SQLite field incorrect" } ] @@ -435,7 +448,7 @@ let queryTests = testList "Query" [ (Query.orderBy [ Field.Named "Nested.Test.Field DESC"; Field.Named "AnotherField"; Field.Named "It DESC" ] SQLite) - " ORDER BY data->>'Nested'->>'Test'->>'Field' DESC, data->>'AnotherField', data->>'It' DESC" + " ORDER BY data->'Nested'->'Test'->>'Field' DESC, data->>'AnotherField', data->>'It' DESC" "Order By not constructed correctly" } test "succeeds for PostgreSQL numeric fields" { @@ -459,7 +472,7 @@ let queryTests = testList "Query" [ test "succeeds for SQLite case-insensitive ordering" { Expect.equal (Query.orderBy [ Field.Named "i:Test.Field ASC NULLS LAST" ] SQLite) - " ORDER BY data->>'Test'->>'Field' COLLATE NOCASE ASC NULLS LAST" + " ORDER BY data->'Test'->>'Field' COLLATE NOCASE ASC NULLS LAST" "Order By not constructed correctly for case-insensitive field" } ] @@ -467,7 +480,7 @@ let queryTests = testList "Query" [ /// Tests which do not hit the database let all = testList "Common" [ - opTests + comparisonTests fieldTests fieldMatchTests parameterNameTests diff --git a/src/Tests/PostgresExtensionTests.fs b/src/Tests/PostgresExtensionTests.fs index 533cf12..60fcf31 100644 --- a/src/Tests/PostgresExtensionTests.fs +++ b/src/Tests/PostgresExtensionTests.fs @@ -214,7 +214,7 @@ let integrationTests = use conn = mkConn db do! loadDocs conn - let! theCount = conn.countByFields PostgresDb.TableName Any [ 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" } testTask "countByContains succeeds" { @@ -257,7 +257,7 @@ let integrationTests = use conn = mkConn db do! loadDocs conn - let! exists = conn.existsByFields PostgresDb.TableName Any [ Field.EX "Sub" ] + let! exists = conn.existsByFields PostgresDb.TableName Any [ Field.Exists "Sub" ] Expect.isTrue exists "There should have been existing documents" } testTask "succeeds when documents do not exist" { @@ -265,7 +265,7 @@ let integrationTests = use conn = mkConn db do! loadDocs conn - let! exists = conn.existsByFields PostgresDb.TableName Any [ 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" } ] @@ -390,7 +390,7 @@ let integrationTests = use conn = mkConn db do! loadDocs conn - let! docs = conn.findByFields PostgresDb.TableName Any [ Field.EQ "Value" "another" ] + let! docs = conn.findByFields PostgresDb.TableName Any [ Field.Equal "Value" "another" ] Expect.equal (List.length docs) 1 "There should have been one document returned" } testTask "succeeds when documents are not found" { @@ -398,7 +398,7 @@ let integrationTests = use conn = mkConn db do! loadDocs conn - let! docs = conn.findByFields PostgresDb.TableName Any [ Field.EQ "Value" "mauve" ] + let! docs = conn.findByFields PostgresDb.TableName Any [ Field.Equal "Value" "mauve" ] Expect.isEmpty docs "There should have been no documents returned" } ] @@ -410,7 +410,7 @@ let integrationTests = let! docs = conn.findByFieldsOrdered - PostgresDb.TableName All [ Field.EQ "Value" "purple" ] [ Field.Named "Id" ] + 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" @@ -422,7 +422,7 @@ let integrationTests = let! docs = conn.findByFieldsOrdered - PostgresDb.TableName All [ Field.EQ "Value" "purple" ] [ Field.Named "Id DESC" ] + 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" @@ -524,7 +524,8 @@ let integrationTests = use conn = mkConn db do! loadDocs conn - let! doc = conn.findFirstByFields PostgresDb.TableName Any [ Field.EQ "Value" "another" ] + let! doc = + conn.findFirstByFields PostgresDb.TableName Any [ Field.Equal "Value" "another" ] Expect.isSome doc "There should have been a document returned" Expect.equal doc.Value.Id "two" "The incorrect document was returned" } @@ -533,7 +534,8 @@ let integrationTests = use conn = mkConn db do! loadDocs conn - let! doc = conn.findFirstByFields PostgresDb.TableName Any [ Field.EQ "Value" "purple" ] + let! doc = + conn.findFirstByFields PostgresDb.TableName Any [ Field.Equal "Value" "purple" ] Expect.isSome doc "There should have been a document returned" Expect.contains [ "five"; "four" ] doc.Value.Id "An incorrect document was returned" } @@ -542,7 +544,8 @@ let integrationTests = use conn = mkConn db do! loadDocs conn - let! doc = conn.findFirstByFields PostgresDb.TableName Any [ Field.EQ "Value" "absent" ] + let! doc = + conn.findFirstByFields PostgresDb.TableName Any [ Field.Equal "Value" "absent" ] Expect.isNone doc "There should not have been a document returned" } ] @@ -554,7 +557,7 @@ let integrationTests = let! doc = conn.findFirstByFieldsOrdered - PostgresDb.TableName Any [ Field.EQ "Value" "purple" ] [ Field.Named "Id" ] + 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" } @@ -565,7 +568,7 @@ let integrationTests = let! doc = conn.findFirstByFieldsOrdered - PostgresDb.TableName Any [ Field.EQ "Value" "purple" ] [ Field.Named "Id DESC" ] + 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" } @@ -750,8 +753,8 @@ let integrationTests = use conn = mkConn db do! loadDocs conn - do! conn.patchByFields PostgresDb.TableName Any [ Field.EQ "Value" "purple" ] {| NumValue = 77 |} - let! after = conn.countByFields PostgresDb.TableName Any [ Field.EQ "NumValue" "77" ] + do! conn.patchByFields PostgresDb.TableName Any [ Field.Equal "Value" "purple" ] {| NumValue = 77 |} + let! after = conn.countByFields PostgresDb.TableName Any [ Field.Equal "NumValue" "77" ] Expect.equal after 2 "There should have been 2 documents returned" } testTask "succeeds when no document is updated" { @@ -761,7 +764,7 @@ let integrationTests = Expect.equal before 0 "There should have been no documents returned" // This not raising an exception is the test - do! conn.patchByFields PostgresDb.TableName Any [ Field.EQ "Value" "burgundy" ] {| Foo = "green" |} + do! conn.patchByFields PostgresDb.TableName Any [ Field.Equal "Value" "burgundy" ] {| Foo = "green" |} } ] testList "patchByContains" [ @@ -811,9 +814,9 @@ let integrationTests = do! loadDocs conn do! conn.removeFieldsById PostgresDb.TableName "two" [ "Sub"; "Value" ] - let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NEX "Sub" ] + let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "Sub" ] Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" - let! noValue = conn.countByFields PostgresDb.TableName Any [ 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" } testTask "succeeds when a single field is removed" { @@ -822,9 +825,9 @@ let integrationTests = do! loadDocs conn do! conn.removeFieldsById PostgresDb.TableName "two" [ "Sub" ] - let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NEX "Sub" ] + let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "Sub" ] Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" - let! noValue = conn.countByFields PostgresDb.TableName Any [ 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" } testTask "succeeds when a field is not removed" { @@ -849,10 +852,11 @@ let integrationTests = use conn = mkConn db do! loadDocs conn - do! conn.removeFieldsByFields PostgresDb.TableName Any [ Field.EQ "NumValue" "17" ] [ "Sub"; "Value" ] - let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NEX "Sub" ] + do! conn.removeFieldsByFields + 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" - let! noValue = conn.countByFields PostgresDb.TableName Any [ 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" } testTask "succeeds when a single field is removed" { @@ -860,10 +864,10 @@ let integrationTests = use conn = mkConn db do! loadDocs conn - do! conn.removeFieldsByFields PostgresDb.TableName Any [ Field.EQ "NumValue" "17" ] [ "Sub" ] - let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NEX "Sub" ] + do! conn.removeFieldsByFields PostgresDb.TableName Any [ Field.Equal "NumValue" "17" ] [ "Sub" ] + let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "Sub" ] Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" - let! noValue = conn.countByFields PostgresDb.TableName Any [ 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" } testTask "succeeds when a field is not removed" { @@ -872,14 +876,15 @@ let integrationTests = do! loadDocs conn // This not raising an exception is the test - do! conn.removeFieldsByFields PostgresDb.TableName Any [ Field.EQ "NumValue" "17" ] [ "Nothing" ] + do! conn.removeFieldsByFields PostgresDb.TableName Any [ Field.Equal "NumValue" "17" ] [ "Nothing" ] } testTask "succeeds when no document is matched" { use db = PostgresDb.BuildDb() use conn = mkConn db // This not raising an exception is the test - do! conn.removeFieldsByFields PostgresDb.TableName Any [ Field.NE "Abracadabra" "apple" ] [ "Value" ] + do! conn.removeFieldsByFields + PostgresDb.TableName Any [ Field.NotEqual "Abracadabra" "apple" ] [ "Value" ] } ] testList "removeFieldsByContains" [ @@ -889,9 +894,9 @@ let integrationTests = do! loadDocs conn do! conn.removeFieldsByContains PostgresDb.TableName {| NumValue = 17 |} [ "Sub"; "Value" ] - let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NEX "Sub" ] + let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "Sub" ] Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" - let! noValue = conn.countByFields PostgresDb.TableName Any [ 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" } testTask "succeeds when a single field is removed" { @@ -900,9 +905,9 @@ let integrationTests = do! loadDocs conn do! conn.removeFieldsByContains PostgresDb.TableName {| NumValue = 17 |} [ "Sub" ] - let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NEX "Sub" ] + let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "Sub" ] Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" - let! noValue = conn.countByFields PostgresDb.TableName Any [ 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" } testTask "succeeds when a field is not removed" { @@ -928,9 +933,9 @@ let integrationTests = do! loadDocs conn do! conn.removeFieldsByJsonPath PostgresDb.TableName "$.NumValue ? (@ == 17)" [ "Sub"; "Value" ] - let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NEX "Sub" ] + let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "Sub" ] Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" - let! noValue = conn.countByFields PostgresDb.TableName Any [ 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" } testTask "succeeds when a single field is removed" { @@ -939,9 +944,9 @@ let integrationTests = do! loadDocs conn do! conn.removeFieldsByJsonPath PostgresDb.TableName "$.NumValue ? (@ == 17)" [ "Sub" ] - let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NEX "Sub" ] + let! noSubs = conn.countByFields PostgresDb.TableName Any [ Field.NotExists "Sub" ] Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" - let! noValue = conn.countByFields PostgresDb.TableName Any [ 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" } testTask "succeeds when a field is not removed" { @@ -986,7 +991,7 @@ let integrationTests = use conn = mkConn db do! loadDocs conn - do! conn.deleteByFields PostgresDb.TableName Any [ Field.EQ "Value" "purple" ] + do! conn.deleteByFields PostgresDb.TableName Any [ Field.Equal "Value" "purple" ] let! remaining = conn.countAll PostgresDb.TableName Expect.equal remaining 3 "There should have been 3 documents remaining" } @@ -995,7 +1000,7 @@ let integrationTests = use conn = mkConn db do! loadDocs conn - do! conn.deleteByFields PostgresDb.TableName Any [ Field.EQ "Value" "crimson" ] + do! conn.deleteByFields PostgresDb.TableName Any [ Field.Equal "Value" "crimson" ] let! remaining = conn.countAll PostgresDb.TableName Expect.equal remaining 5 "There should have been 5 documents remaining" } diff --git a/src/Tests/PostgresTests.fs b/src/Tests/PostgresTests.fs index 497e712..6043d9c 100644 --- a/src/Tests/PostgresTests.fs +++ b/src/Tests/PostgresTests.fs @@ -83,14 +83,14 @@ let parametersTests = testList "Parameters" [ } testList "addFieldParams" [ test "succeeds when a parameter is added" { - let paramList = addFieldParams [ Field.EQ "it" "242" ] [] + let paramList = addFieldParams [ Field.Where "it" (Equal "242") ] [] Expect.hasLength paramList 1 "There should have been a parameter added" let name, value = Seq.head paramList Expect.equal name "@field0" "Field parameter name not correct" Expect.equal value (Sql.string "242") "Parameter value not correct" } test "succeeds when multiple independent parameters are added" { - let paramList = addFieldParams [ Field.EQ "me" "you"; Field.GT "us" "them" ] [ idParam 14 ] + let paramList = addFieldParams [ Field.Equal "me" "you"; Field.Greater "us" "them" ] [ idParam 14 ] Expect.hasLength paramList 3 "There should have been 2 parameters added" let p = Array.ofSeq paramList Expect.equal (fst p[0]) "@id" "First field parameter name not correct" @@ -101,11 +101,11 @@ let parametersTests = testList "Parameters" [ Expect.equal (snd p[2]) (Sql.string "them") "Third parameter value not correct" } test "succeeds when a parameter is not added" { - let paramList = addFieldParams [ Field.EX "tacos" ] [] + let paramList = addFieldParams [ Field.Exists "tacos" ] [] Expect.isEmpty paramList "There should not have been any parameters added" } test "succeeds when two parameters are added for one field" { - let paramList = addFieldParams [ { Field.BT "that" "eh" "zed" with ParameterName = Some "@test" } ] [] + let paramList = addFieldParams [ { Field.Between "that" "eh" "zed" with ParameterName = Some "@test" } ] [] Expect.hasLength paramList 2 "There should have been 2 parameters added" let name, value = Seq.head paramList Expect.equal name "@testmin" "Minimum field name not correct" @@ -135,48 +135,66 @@ let parametersTests = testList "Parameters" [ /// Unit tests for the Query module of the PostgreSQL library let queryTests = testList "Query" [ testList "whereByFields" [ - test "succeeds for a single field when a logical operator is passed" { + test "succeeds for a single field when a logical comparison is passed" { Expect.equal - (Query.whereByFields Any [ { Field.GT "theField" "0" with ParameterName = Some "@test" } ]) + (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 operator is passed" { + test "succeeds for a single field when an existence comparison is passed" { Expect.equal - (Query.whereByFields Any [ Field.NEX "thatField" ]) + (Query.whereByFields Any [ Field.NotExists "thatField" ]) "data->>'thatField' IS NULL" "WHERE clause not correct" } - test "succeeds for a single field when a between operator is passed with numeric values" { + test "succeeds for a single field when a between comparison is passed with numeric values" { Expect.equal - (Query.whereByFields All [ { Field.BT "aField" 50 99 with ParameterName = Some "@range" } ]) + (Query.whereByFields All [ { Field.Between "aField" 50 99 with ParameterName = Some "@range" } ]) "(data->>'aField')::numeric BETWEEN @rangemin AND @rangemax" "WHERE clause not correct" } - test "succeeds for a single field when a between operator is passed with non-numeric values" { + test "succeeds for a single field when a between comparison is passed with non-numeric values" { Expect.equal - (Query.whereByFields Any [ { Field.BT "field0" "a" "b" with ParameterName = Some "@alpha" } ]) + (Query.whereByFields Any [ { Field.Between "field0" "a" "b" with ParameterName = Some "@alpha" } ]) "data->>'field0' BETWEEN @alphamin AND @alphamax" "WHERE clause not correct" } - test "succeeds for all multiple fields with logical operators" { + test "succeeds for all multiple fields with logical comparisons" { Expect.equal - (Query.whereByFields All [ Field.EQ "theFirst" "1"; Field.EQ "numberTwo" "2" ]) + (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 operator" { + test "succeeds for any multiple fields with an existence comparisons" { Expect.equal - (Query.whereByFields Any [ Field.NEX "thatField"; Field.GE "thisField" 18 ]) + (Query.whereByFields Any [ Field.NotExists "thatField"; Field.GreaterOrEqual "thisField" 18 ]) "data->>'thatField' IS NULL OR (data->>'thisField')::numeric >= @field0" "WHERE clause not correct" } - test "succeeds for all multiple fields with between operators" { + test "succeeds for all multiple fields with between comparisons" { Expect.equal - (Query.whereByFields All [ Field.BT "aField" 50 99; Field.BT "anotherField" "a" "b" ]) + (Query.whereByFields All [ Field.Between "aField" 50 99; Field.Between "anotherField" "a" "b" ]) "(data->>'aField')::numeric BETWEEN @field0min AND @field0max AND data->>'anotherField' BETWEEN @field1min AND @field1max" "WHERE clause not correct" } + test "succeeds for a field with an In comparison with alphanumeric values" { + Expect.equal + (Query.whereByFields All [ Field.In "this" [ "a"; "b"; "c" ] ]) + "data->>'this' IN (@field0_0, @field0_1, @field0_2)" + "WHERE clause not correct" + } + test "succeeds for a field with an In comparison with numeric values" { + Expect.equal + (Query.whereByFields All [ Field.In "this" [ 7; 14; 21 ] ]) + "(data->>'this')::numeric IN (@field0_0, @field0_1, @field0_2)" + "WHERE clause not correct" + } + test "succeeds for a field with an InArray comparison" { + Expect.equal + (Query.whereByFields All [ Field.InArray "theField" "the_table" [ "q", "r" ] ]) + "data->'theField' ?| @field0" + "WHERE clause not correct" + } ] testList "whereById" [ test "succeeds for numeric ID" { @@ -234,7 +252,7 @@ let queryTests = testList "Query" [ } test "byFields succeeds" { Expect.equal - (Query.byFields "unit" Any [ Field.GT "That" 14 ]) + (Query.byFields "unit" Any [ Field.Greater "That" 14 ]) "unit WHERE (data->>'That')::numeric > @field0" "By-Field query not correct" } @@ -532,14 +550,15 @@ let countTests = testList "Count" [ use db = PostgresDb.BuildDb() do! loadDocs () - let! theCount = Count.byFields PostgresDb.TableName Any [ Field.BT "NumValue" 15 20; Field.EQ "NumValue" 0 ] + let! theCount = + Count.byFields PostgresDb.TableName Any [ Field.Between "NumValue" 15 20; Field.Equal "NumValue" 0 ] Expect.equal theCount 3 "There should have been 3 matching documents" } testTask "succeeds when items are not found" { use db = PostgresDb.BuildDb() do! loadDocs () - let! theCount = Count.byFields PostgresDb.TableName All [ Field.EX "Sub"; Field.GT "NumValue" 100 ] + let! theCount = Count.byFields PostgresDb.TableName All [ Field.Exists "Sub"; Field.Greater "NumValue" 100 ] Expect.equal theCount 0 "There should have been no matching documents" } ] @@ -582,14 +601,14 @@ let existsTests = testList "Exists" [ use db = PostgresDb.BuildDb() do! loadDocs () - let! exists = Exists.byFields PostgresDb.TableName Any [ Field.EX "Sub"; Field.EX "Boo" ] + let! exists = Exists.byFields PostgresDb.TableName Any [ Field.Exists "Sub"; Field.Exists "Boo" ] Expect.isTrue exists "There should have been existing documents" } testTask "succeeds when documents do not exist" { use db = PostgresDb.BuildDb() do! loadDocs () - let! exists = Exists.byFields PostgresDb.TableName All [ Field.EQ "NumValue" "six"; Field.EX "Nope" ] + let! exists = Exists.byFields PostgresDb.TableName All [ Field.Equal "NumValue" "six"; Field.Exists "Nope" ] Expect.isFalse exists "There should not have been existing documents" } ] @@ -707,8 +726,16 @@ let findTests = testList "Find" [ do! loadDocs () let! docs = - Find.byFields PostgresDb.TableName All [ Field.EQ "Value" "purple"; Field.EX "Sub" ] - Expect.equal (List.length docs) 1 "There should have been one document returned" + Find.byFields + PostgresDb.TableName All [ Field.In "Value" [ "purple"; "blue" ]; Field.Exists "Sub" ] + Expect.hasLength docs 1 "There should have been one document returned" + } + testTask "succeeds when documents are found using IN with numeric field" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! docs = Find.byFields PostgresDb.TableName All [ Field.In "NumValue" [ 2; 4; 6; 8 ] ] + Expect.hasLength docs 1 "There should have been one document returned" } testTask "succeeds when documents are not found" { use db = PostgresDb.BuildDb() @@ -716,7 +743,27 @@ let findTests = testList "Find" [ let! docs = Find.byFields - PostgresDb.TableName All [ Field.EQ "Value" "mauve"; Field.NE "NumValue" 40 ] + PostgresDb.TableName All [ Field.Equal "Value" "mauve"; Field.NotEqual "NumValue" 40 ] + Expect.isEmpty docs "There should have been no documents returned" + } + testTask "succeeds for InArray when matching documents exist" { + use db = PostgresDb.BuildDb() + do! Definition.ensureTable PostgresDb.TableName + for doc in ArrayDocument.TestDocuments do do! insert PostgresDb.TableName doc + + let! docs = + Find.byFields + PostgresDb.TableName All [ Field.InArray "Values" PostgresDb.TableName [ "c" ] ] + Expect.hasLength docs 2 "There should have been two documents returned" + } + testTask "succeeds for InArray when no matching documents exist" { + use db = PostgresDb.BuildDb() + do! Definition.ensureTable PostgresDb.TableName + for doc in ArrayDocument.TestDocuments do do! insert PostgresDb.TableName doc + + let! docs = + Find.byFields + PostgresDb.TableName All [ Field.InArray "Values" PostgresDb.TableName [ "j" ] ] Expect.isEmpty docs "There should have been no documents returned" } ] @@ -727,7 +774,7 @@ let findTests = testList "Find" [ let! docs = Find.byFieldsOrdered - PostgresDb.TableName All [ Field.EQ "Value" "purple" ] [ Field.Named "Id" ] + 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" @@ -738,7 +785,7 @@ let findTests = testList "Find" [ let! docs = Find.byFieldsOrdered - PostgresDb.TableName All [ Field.EQ "Value" "purple" ] [ Field.Named "Id DESC" ] + 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" @@ -831,7 +878,7 @@ let findTests = testList "Find" [ use db = PostgresDb.BuildDb() do! loadDocs () - let! doc = Find.firstByFields PostgresDb.TableName Any [ Field.EQ "Value" "another" ] + let! doc = Find.firstByFields PostgresDb.TableName Any [ Field.Equal "Value" "another" ] Expect.isSome doc "There should have been a document returned" Expect.equal doc.Value.Id "two" "The incorrect document was returned" } @@ -839,7 +886,7 @@ let findTests = testList "Find" [ use db = PostgresDb.BuildDb() do! loadDocs () - let! doc = Find.firstByFields PostgresDb.TableName Any [ Field.EQ "Value" "purple" ] + let! doc = Find.firstByFields PostgresDb.TableName Any [ Field.Equal "Value" "purple" ] Expect.isSome doc "There should have been a document returned" Expect.contains [ "five"; "four" ] doc.Value.Id "An incorrect document was returned" } @@ -847,7 +894,7 @@ let findTests = testList "Find" [ use db = PostgresDb.BuildDb() do! loadDocs () - let! doc = Find.firstByFields PostgresDb.TableName Any [ Field.EQ "Value" "absent" ] + let! doc = Find.firstByFields PostgresDb.TableName Any [ Field.Equal "Value" "absent" ] Expect.isNone doc "There should not have been a document returned" } ] @@ -858,7 +905,7 @@ let findTests = testList "Find" [ let! doc = Find.firstByFieldsOrdered - PostgresDb.TableName Any [ Field.EQ "Value" "purple" ] [ Field.Named "Id" ] + 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" } @@ -868,7 +915,7 @@ let findTests = testList "Find" [ let! doc = Find.firstByFieldsOrdered - PostgresDb.TableName Any [ Field.EQ "Value" "purple" ] [ Field.Named "Id DESC" ] + 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" } @@ -1045,8 +1092,8 @@ let patchTests = testList "Patch" [ use db = PostgresDb.BuildDb() do! loadDocs () - do! Patch.byFields PostgresDb.TableName Any [ Field.EQ "Value" "purple" ] {| NumValue = 77 |} - let! after = Count.byFields PostgresDb.TableName Any [ Field.EQ "NumValue" 77 ] + do! Patch.byFields PostgresDb.TableName Any [ Field.Equal "Value" "purple" ] {| NumValue = 77 |} + let! after = Count.byFields PostgresDb.TableName Any [ Field.Equal "NumValue" 77 ] Expect.equal after 2 "There should have been 2 documents returned" } testTask "succeeds when no document is updated" { @@ -1056,7 +1103,7 @@ let patchTests = testList "Patch" [ Expect.equal before 0 "There should have been no documents returned" // This not raising an exception is the test - do! Patch.byFields PostgresDb.TableName Any [ Field.EQ "Value" "burgundy" ] {| Foo = "green" |} + do! Patch.byFields PostgresDb.TableName Any [ Field.Equal "Value" "burgundy" ] {| Foo = "green" |} } ] testList "byContains" [ @@ -1107,9 +1154,9 @@ let removeFieldsTests = testList "RemoveFields" [ do! loadDocs () do! RemoveFields.byId PostgresDb.TableName "two" [ "Sub"; "Value" ] - let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ] + let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Sub" ] Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" - let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ] + let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Value" ] Expect.equal noValue 1 "There should be 1 document without Value fields" } testTask "succeeds when a single field is removed" { @@ -1117,9 +1164,9 @@ let removeFieldsTests = testList "RemoveFields" [ do! loadDocs () do! RemoveFields.byId PostgresDb.TableName "two" [ "Sub" ] - let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ] + let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Sub" ] Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" - let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ] + let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Value" ] Expect.equal noValue 0 "There should be no documents without Value fields" } testTask "succeeds when a field is not removed" { @@ -1141,20 +1188,20 @@ let removeFieldsTests = testList "RemoveFields" [ use db = PostgresDb.BuildDb() do! loadDocs () - do! RemoveFields.byFields PostgresDb.TableName Any [ Field.EQ "NumValue" "17" ] [ "Sub"; "Value" ] - let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ] + do! RemoveFields.byFields PostgresDb.TableName Any [ Field.Equal "NumValue" 17 ] [ "Sub"; "Value" ] + let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Sub" ] Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" - let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ] + let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Value" ] Expect.equal noValue 1 "There should be 1 document without Value fields" } testTask "succeeds when a single field is removed" { use db = PostgresDb.BuildDb() do! loadDocs () - do! RemoveFields.byFields PostgresDb.TableName Any [ Field.EQ "NumValue" "17" ] [ "Sub" ] - let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ] + do! RemoveFields.byFields PostgresDb.TableName Any [ Field.Equal "NumValue" 17 ] [ "Sub" ] + let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Sub" ] Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" - let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ] + let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Value" ] Expect.equal noValue 0 "There should be no documents without Value fields" } testTask "succeeds when a field is not removed" { @@ -1162,13 +1209,13 @@ let removeFieldsTests = testList "RemoveFields" [ do! loadDocs () // This not raising an exception is the test - do! RemoveFields.byFields PostgresDb.TableName Any [ Field.EQ "NumValue" "17" ] [ "Nothing" ] + do! RemoveFields.byFields PostgresDb.TableName Any [ Field.Equal "NumValue" 17 ] [ "Nothing" ] } testTask "succeeds when no document is matched" { use db = PostgresDb.BuildDb() // This not raising an exception is the test - do! RemoveFields.byFields PostgresDb.TableName Any [ Field.NE "Abracadabra" "apple" ] [ "Value" ] + do! RemoveFields.byFields PostgresDb.TableName Any [ Field.NotEqual "Abracadabra" "apple" ] [ "Value" ] } ] testList "byContains" [ @@ -1177,9 +1224,9 @@ let removeFieldsTests = testList "RemoveFields" [ do! loadDocs () do! RemoveFields.byContains PostgresDb.TableName {| NumValue = 17 |} [ "Sub"; "Value" ] - let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ] + let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Sub" ] Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" - let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ] + let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Value" ] Expect.equal noValue 1 "There should be 1 document without Value fields" } testTask "succeeds when a single field is removed" { @@ -1187,9 +1234,9 @@ let removeFieldsTests = testList "RemoveFields" [ do! loadDocs () do! RemoveFields.byContains PostgresDb.TableName {| NumValue = 17 |} [ "Sub" ] - let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ] + let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Sub" ] Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" - let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ] + let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Value" ] Expect.equal noValue 0 "There should be no documents without Value fields" } testTask "succeeds when a field is not removed" { @@ -1212,9 +1259,9 @@ let removeFieldsTests = testList "RemoveFields" [ do! loadDocs () do! RemoveFields.byJsonPath PostgresDb.TableName "$.NumValue ? (@ == 17)" [ "Sub"; "Value" ] - let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ] + let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Sub" ] Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" - let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ] + let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Value" ] Expect.equal noValue 1 "There should be 1 document without Value fields" } testTask "succeeds when a single field is removed" { @@ -1222,9 +1269,9 @@ let removeFieldsTests = testList "RemoveFields" [ do! loadDocs () do! RemoveFields.byJsonPath PostgresDb.TableName "$.NumValue ? (@ == 17)" [ "Sub" ] - let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NEX "Sub" ] + let! noSubs = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Sub" ] Expect.equal noSubs 4 "There should now be 4 documents without Sub fields" - let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NEX "Value" ] + let! noValue = Count.byFields PostgresDb.TableName Any [ Field.NotExists "Value" ] Expect.equal noValue 0 "There should be no documents without Value fields" } testTask "succeeds when a field is not removed" { @@ -1268,7 +1315,7 @@ let deleteTests = testList "Delete" [ use db = PostgresDb.BuildDb() do! loadDocs () - do! Delete.byFields PostgresDb.TableName Any [ Field.EQ "Value" "purple" ] + do! Delete.byFields PostgresDb.TableName Any [ Field.Equal "Value" "purple" ] let! remaining = Count.all PostgresDb.TableName Expect.equal remaining 3 "There should have been 3 documents remaining" } @@ -1276,7 +1323,7 @@ let deleteTests = testList "Delete" [ use db = PostgresDb.BuildDb() do! loadDocs () - do! Delete.byFields PostgresDb.TableName Any [ Field.EQ "Value" "crimson" ] + do! Delete.byFields PostgresDb.TableName Any [ Field.Equal "Value" "crimson" ] let! remaining = Count.all PostgresDb.TableName Expect.equal remaining 5 "There should have been 5 documents remaining" } diff --git a/src/Tests/SqliteExtensionTests.fs b/src/Tests/SqliteExtensionTests.fs index 51d4f39..8f0380e 100644 --- a/src/Tests/SqliteExtensionTests.fs +++ b/src/Tests/SqliteExtensionTests.fs @@ -118,7 +118,7 @@ let integrationTests = use conn = Configuration.dbConn () do! loadDocs () - let! theCount = conn.countByFields SqliteDb.TableName Any [ 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" } testList "existsById" [ @@ -145,7 +145,7 @@ let integrationTests = use conn = Configuration.dbConn () do! loadDocs () - let! exists = conn.existsByFields SqliteDb.TableName Any [ Field.EQ "NumValue" 10 ] + let! exists = conn.existsByFields SqliteDb.TableName Any [ Field.Equal "NumValue" 10 ] Expect.isTrue exists "There should have been existing documents" } testTask "succeeds when no matching documents exist" { @@ -153,7 +153,7 @@ let integrationTests = use conn = Configuration.dbConn () do! loadDocs () - let! exists = conn.existsByFields SqliteDb.TableName Any [ 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" } ] @@ -244,7 +244,7 @@ let integrationTests = use conn = Configuration.dbConn () do! loadDocs () - let! docs = conn.findByFields SqliteDb.TableName Any [ Field.EQ "Sub.Foo" "green" ] + let! docs = conn.findByFields SqliteDb.TableName Any [ Field.Equal "Sub.Foo" "green" ] Expect.hasLength docs 2 "There should have been two documents returned" } testTask "succeeds when documents are not found" { @@ -252,7 +252,7 @@ let integrationTests = use conn = Configuration.dbConn () do! loadDocs () - let! docs = conn.findByFields SqliteDb.TableName Any [ Field.EQ "Value" "mauve" ] + let! docs = conn.findByFields SqliteDb.TableName Any [ Field.Equal "Value" "mauve" ] Expect.isEmpty docs "There should have been no documents returned" } ] @@ -264,7 +264,7 @@ let integrationTests = let! docs = conn.findByFieldsOrdered - SqliteDb.TableName Any [ Field.GT "NumValue" 15 ] [ Field.Named "Id" ] + 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" } @@ -275,7 +275,7 @@ let integrationTests = let! docs = conn.findByFieldsOrdered - SqliteDb.TableName Any [ Field.GT "NumValue" 15 ] [ Field.Named "Id DESC" ] + 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" } @@ -286,7 +286,7 @@ let integrationTests = use conn = Configuration.dbConn () do! loadDocs () - let! doc = conn.findFirstByFields SqliteDb.TableName Any [ Field.EQ "Value" "another" ] + let! doc = conn.findFirstByFields SqliteDb.TableName Any [ Field.Equal "Value" "another" ] Expect.isSome doc "There should have been a document returned" Expect.equal doc.Value.Id "two" "The incorrect document was returned" } @@ -295,7 +295,7 @@ let integrationTests = use conn = Configuration.dbConn () do! loadDocs () - let! doc = conn.findFirstByFields SqliteDb.TableName Any [ Field.EQ "Sub.Foo" "green" ] + let! doc = conn.findFirstByFields SqliteDb.TableName Any [ Field.Equal "Sub.Foo" "green" ] Expect.isSome doc "There should have been a document returned" Expect.contains [ "two"; "four" ] doc.Value.Id "An incorrect document was returned" } @@ -304,7 +304,7 @@ let integrationTests = use conn = Configuration.dbConn () do! loadDocs () - let! doc = conn.findFirstByFields SqliteDb.TableName Any [ Field.EQ "Value" "absent" ] + let! doc = conn.findFirstByFields SqliteDb.TableName Any [ Field.Equal "Value" "absent" ] Expect.isNone doc "There should not have been a document returned" } ] @@ -316,7 +316,7 @@ let integrationTests = let! doc = conn.findFirstByFieldsOrdered - SqliteDb.TableName Any [ Field.EQ "Sub.Foo" "green" ] [ Field.Named "Sub.Bar" ] + 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" } @@ -327,7 +327,7 @@ let integrationTests = let! doc = conn.findFirstByFieldsOrdered - SqliteDb.TableName Any [ Field.EQ "Sub.Foo" "green" ] [ Field.Named "Sub.Bar DESC" ] + 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" } @@ -416,8 +416,8 @@ let integrationTests = use conn = Configuration.dbConn () do! loadDocs () - do! conn.patchByFields SqliteDb.TableName Any [ Field.EQ "Value" "purple" ] {| NumValue = 77 |} - let! after = conn.countByFields SqliteDb.TableName Any [ Field.EQ "NumValue" 77 ] + do! conn.patchByFields SqliteDb.TableName Any [ Field.Equal "Value" "purple" ] {| NumValue = 77 |} + let! after = conn.countByFields SqliteDb.TableName Any [ Field.Equal "NumValue" 77 ] Expect.equal after 2L "There should have been 2 documents returned" } testTask "succeeds when no document is updated" { @@ -428,7 +428,7 @@ let integrationTests = Expect.isEmpty before "There should have been no documents returned" // This not raising an exception is the test - do! conn.patchByFields SqliteDb.TableName Any [ Field.EQ "Value" "burgundy" ] {| Foo = "green" |} + do! conn.patchByFields SqliteDb.TableName Any [ Field.Equal "Value" "burgundy" ] {| Foo = "green" |} } ] testList "removeFieldsById" [ @@ -467,7 +467,7 @@ let integrationTests = use conn = Configuration.dbConn () do! loadDocs () - do! conn.removeFieldsByFields SqliteDb.TableName Any [ Field.EQ "NumValue" 17 ] [ "Sub" ] + do! conn.removeFieldsByFields SqliteDb.TableName Any [ Field.Equal "NumValue" 17 ] [ "Sub" ] try let! _ = conn.findById SqliteDb.TableName "four" Expect.isTrue false "The updated document should have failed to parse" @@ -481,14 +481,15 @@ let integrationTests = do! loadDocs () // This not raising an exception is the test - do! conn.removeFieldsByFields SqliteDb.TableName Any [ Field.EQ "NumValue" 17 ] [ "Nothing" ] + do! conn.removeFieldsByFields SqliteDb.TableName Any [ Field.Equal "NumValue" 17 ] [ "Nothing" ] } testTask "succeeds when no document is matched" { use! db = SqliteDb.BuildDb() use conn = Configuration.dbConn () // This not raising an exception is the test - do! conn.removeFieldsByFields SqliteDb.TableName Any [ Field.NE "Abracadabra" "apple" ] [ "Value" ] + do! conn.removeFieldsByFields + SqliteDb.TableName Any [ Field.NotEqual "Abracadabra" "apple" ] [ "Value" ] } ] testList "deleteById" [ @@ -517,7 +518,7 @@ let integrationTests = use conn = Configuration.dbConn () do! loadDocs () - do! conn.deleteByFields SqliteDb.TableName Any [ Field.NE "Value" "purple" ] + do! conn.deleteByFields SqliteDb.TableName Any [ Field.NotEqual "Value" "purple" ] let! remaining = conn.countAll SqliteDb.TableName Expect.equal remaining 2L "There should have been 2 documents remaining" } @@ -526,7 +527,7 @@ let integrationTests = use conn = Configuration.dbConn () do! loadDocs () - do! conn.deleteByFields SqliteDb.TableName Any [ Field.EQ "Value" "crimson" ] + do! conn.deleteByFields SqliteDb.TableName Any [ Field.Equal "Value" "crimson" ] let! remaining = conn.countAll SqliteDb.TableName Expect.equal remaining 5L "There should have been 5 documents remaining" } diff --git a/src/Tests/SqliteTests.fs b/src/Tests/SqliteTests.fs index 1a2e71d..83e1c22 100644 --- a/src/Tests/SqliteTests.fs +++ b/src/Tests/SqliteTests.fs @@ -15,42 +15,54 @@ open Types /// 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 operator is passed" { + test "succeeds for a single field when a logical comparison is passed" { Expect.equal - (Query.whereByFields Any [ { Field.GT "theField" 0 with ParameterName = Some "@test" } ]) + (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 operator is passed" { + test "succeeds for a single field when an existence comparison is passed" { Expect.equal - (Query.whereByFields Any [ Field.NEX "thatField" ]) + (Query.whereByFields Any [ Field.NotExists "thatField" ]) "data->>'thatField' IS NULL" "WHERE clause not correct" } - test "succeeds for a single field when a between operator is passed" { + test "succeeds for a single field when a between comparison is passed" { Expect.equal - (Query.whereByFields All [ { Field.BT "aField" 50 99 with ParameterName = Some "@range" } ]) + (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 operators" { + test "succeeds for all multiple fields with logical comparisons" { Expect.equal - (Query.whereByFields All [ Field.EQ "theFirst" "1"; Field.EQ "numberTwo" "2" ]) + (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 operator" { + test "succeeds for any multiple fields with an existence comparison" { Expect.equal - (Query.whereByFields Any [ Field.NEX "thatField"; Field.GE "thisField" 18 ]) + (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 operators" { + test "succeeds for all multiple fields with between comparisons" { Expect.equal - (Query.whereByFields All [ Field.BT "aField" 50 99; Field.BT "anotherField" "a" "b" ]) + (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 "@id") "data->>'Id' = @id" "WHERE clause not correct" @@ -72,7 +84,7 @@ let queryTests = testList "Query" [ } test "byFields succeeds" { Expect.equal - (Query.byFields "unit" Any [ Field.GT "That" 14 ]) + (Query.byFields "unit" Any [ Field.Greater "That" 14 ]) "unit WHERE data->>'That' > @field0" "By-Field query not correct" } @@ -98,14 +110,14 @@ let parametersTests = testList "Parameters" [ } testList "addFieldParam" [ 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" let theParam = Seq.head paramList Expect.equal theParam.ParameterName "@field" "The parameter name is incorrect" Expect.equal theParam.Value 99 "The parameter value is incorrect" } 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" } ] @@ -380,14 +392,14 @@ let countTests = testList "Count" [ use! db = SqliteDb.BuildDb() do! loadDocs () - let! theCount = Count.byFields SqliteDb.TableName Any [ Field.BT "NumValue" 10 20 ] + let! theCount = Count.byFields SqliteDb.TableName Any [ Field.Between "NumValue" 10 20 ] Expect.equal theCount 3L "There should have been 3 matching documents" } testTask "succeeds for a non-numeric range" { use! db = SqliteDb.BuildDb() do! loadDocs () - let! theCount = Count.byFields SqliteDb.TableName Any [ Field.BT "Value" "aardvark" "apple" ] + let! theCount = Count.byFields SqliteDb.TableName Any [ Field.Between "Value" "aardvark" "apple" ] Expect.equal theCount 1L "There should have been 1 matching document" } ] @@ -416,14 +428,14 @@ let existsTests = testList "Exists" [ use! db = SqliteDb.BuildDb() do! loadDocs () - let! exists = Exists.byFields SqliteDb.TableName Any [ Field.EQ "NumValue" 10 ] + let! exists = Exists.byFields SqliteDb.TableName Any [ Field.Equal "NumValue" 10 ] Expect.isTrue exists "There should have been existing documents" } testTask "succeeds when no matching documents exist" { use! db = SqliteDb.BuildDb() do! loadDocs () - let! exists = Exists.byFields SqliteDb.TableName Any [ 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" } ] @@ -508,16 +520,43 @@ let findTests = testList "Find" [ use! db = SqliteDb.BuildDb() do! loadDocs () - let! docs = Find.byFields SqliteDb.TableName Any [ Field.GT "NumValue" 15 ] - Expect.equal (List.length docs) 2 "There should have been two documents returned" + let! docs = Find.byFields SqliteDb.TableName Any [ Field.Greater "NumValue" 15 ] + 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 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" { use! db = SqliteDb.BuildDb() do! loadDocs () - let! docs = Find.byFields SqliteDb.TableName Any [ Field.GT "NumValue" 100 ] + let! docs = Find.byFields SqliteDb.TableName Any [ Field.Greater "NumValue" 100 ] 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 + 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 + SqliteDb.TableName All [ Field.InArray "Values" SqliteDb.TableName [ "j" ] ] + Expect.isEmpty docs "There should have been no documents returned" + } ] testList "byFieldsOrdered" [ testTask "succeeds when sorting ascending" { @@ -526,7 +565,7 @@ let findTests = testList "Find" [ let! docs = Find.byFieldsOrdered - SqliteDb.TableName Any [ Field.GT "NumValue" 15 ] [ Field.Named "Id" ] + 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" @@ -537,7 +576,7 @@ let findTests = testList "Find" [ let! docs = Find.byFieldsOrdered - SqliteDb.TableName Any [ Field.GT "NumValue" 15 ] [ Field.Named "Id DESC" ] + 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" @@ -548,7 +587,7 @@ let findTests = testList "Find" [ let! docs = Find.byFieldsOrdered - SqliteDb.TableName All [ Field.LE "NumValue" 10 ] [ Field.Named "Value" ] + 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" @@ -559,7 +598,7 @@ let findTests = testList "Find" [ let! docs = Find.byFieldsOrdered - SqliteDb.TableName All [ Field.LE "NumValue" 10 ] [ Field.Named "i:Value" ] + 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" @@ -570,7 +609,7 @@ let findTests = testList "Find" [ use! db = SqliteDb.BuildDb() do! loadDocs () - let! doc = Find.firstByFields SqliteDb.TableName Any [ Field.EQ "Value" "another" ] + let! doc = Find.firstByFields SqliteDb.TableName Any [ Field.Equal "Value" "another" ] Expect.isSome doc "There should have been a document returned" Expect.equal doc.Value.Id "two" "The incorrect document was returned" } @@ -578,7 +617,7 @@ let findTests = testList "Find" [ use! db = SqliteDb.BuildDb() do! loadDocs () - let! doc = Find.firstByFields SqliteDb.TableName Any [ Field.EQ "Sub.Foo" "green" ] + let! doc = Find.firstByFields SqliteDb.TableName Any [ Field.Equal "Sub.Foo" "green" ] Expect.isSome doc "There should have been a document returned" Expect.contains [ "two"; "four" ] doc.Value.Id "An incorrect document was returned" } @@ -586,7 +625,7 @@ let findTests = testList "Find" [ use! db = SqliteDb.BuildDb() do! loadDocs () - let! doc = Find.firstByFields SqliteDb.TableName Any [ Field.EQ "Value" "absent" ] + let! doc = Find.firstByFields SqliteDb.TableName Any [ Field.Equal "Value" "absent" ] Expect.isNone doc "There should not have been a document returned" } ] @@ -597,7 +636,7 @@ let findTests = testList "Find" [ let! doc = Find.firstByFieldsOrdered - SqliteDb.TableName Any [ Field.EQ "Sub.Foo" "green" ] [ Field.Named "Sub.Bar" ] + 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" } @@ -607,7 +646,7 @@ let findTests = testList "Find" [ let! doc = Find.firstByFieldsOrdered - SqliteDb.TableName Any [ Field.EQ "Sub.Foo" "green" ] [ Field.Named "Sub.Bar DESC" ] + 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" } @@ -690,8 +729,8 @@ let patchTests = testList "Patch" [ use! db = SqliteDb.BuildDb() do! loadDocs () - do! Patch.byFields SqliteDb.TableName Any [ Field.EQ "Value" "purple" ] {| NumValue = 77 |} - let! after = Count.byFields SqliteDb.TableName Any [ Field.EQ "NumValue" 77 ] + do! Patch.byFields SqliteDb.TableName Any [ Field.Equal "Value" "purple" ] {| NumValue = 77 |} + let! after = Count.byFields SqliteDb.TableName Any [ Field.Equal "NumValue" 77 ] Expect.equal after 2L "There should have been 2 documents returned" } testTask "succeeds when no document is updated" { @@ -701,7 +740,7 @@ let patchTests = testList "Patch" [ Expect.isEmpty before "There should have been no documents returned" // This not raising an exception is the test - do! Patch.byFields SqliteDb.TableName Any [ Field.EQ "Value" "burgundy" ] {| Foo = "green" |} + do! Patch.byFields SqliteDb.TableName Any [ Field.Equal "Value" "burgundy" ] {| Foo = "green" |} } ] ] @@ -740,7 +779,7 @@ let removeFieldsTests = testList "RemoveFields" [ use! db = SqliteDb.BuildDb() do! loadDocs () - do! RemoveFields.byFields SqliteDb.TableName Any [ Field.EQ "NumValue" 17 ] [ "Sub" ] + do! RemoveFields.byFields SqliteDb.TableName Any [ Field.Equal "NumValue" 17 ] [ "Sub" ] try let! _ = Find.byId SqliteDb.TableName "four" Expect.isTrue false "The updated document should have failed to parse" @@ -753,13 +792,13 @@ let removeFieldsTests = testList "RemoveFields" [ do! loadDocs () // This not raising an exception is the test - do! RemoveFields.byFields SqliteDb.TableName Any [ Field.EQ "NumValue" 17 ] [ "Nothing" ] + do! RemoveFields.byFields SqliteDb.TableName Any [ Field.Equal "NumValue" 17 ] [ "Nothing" ] } testTask "succeeds when no document is matched" { use! db = SqliteDb.BuildDb() // This not raising an exception is the test - do! RemoveFields.byFields SqliteDb.TableName Any [ Field.NE "Abracadabra" "apple" ] [ "Value" ] + do! RemoveFields.byFields SqliteDb.TableName Any [ Field.NotEqual "Abracadabra" "apple" ] [ "Value" ] } ] ] @@ -789,7 +828,7 @@ let deleteTests = testList "Delete" [ use! db = SqliteDb.BuildDb() do! loadDocs () - do! Delete.byFields SqliteDb.TableName Any [ Field.NE "Value" "purple" ] + do! Delete.byFields SqliteDb.TableName Any [ Field.NotEqual "Value" "purple" ] let! remaining = Count.all SqliteDb.TableName Expect.equal remaining 2L "There should have been 2 documents remaining" } @@ -797,7 +836,7 @@ let deleteTests = testList "Delete" [ use! db = SqliteDb.BuildDb() do! loadDocs () - do! Delete.byFields SqliteDb.TableName Any [ Field.EQ "Value" "crimson" ] + do! Delete.byFields SqliteDb.TableName Any [ Field.Equal "Value" "crimson" ] let! remaining = Count.all SqliteDb.TableName Expect.equal remaining 5L "There should have been 5 documents remaining" } diff --git a/src/Tests/Types.fs b/src/Tests/Types.fs index 0deb585..bec9b16 100644 --- a/src/Tests/Types.fs +++ b/src/Tests/Types.fs @@ -8,20 +8,32 @@ type SubDocument = { Foo: string Bar: string } +type ArrayDocument = + { Id: string + Values: string list } +with + /// + /// A set of documents used for integration tests + /// + static member TestDocuments = + [ { Id = "first"; Values = [ "a"; "b"; "c" ] } + { Id = "second"; Values = [ "c"; "d"; "e" ] } + { Id = "third"; Values = [ "x"; "y"; "z" ] } ] + type JsonDocument = { Id: string Value: string NumValue: int Sub: SubDocument option } + /// An empty JsonDocument let emptyDoc = { Id = ""; Value = ""; NumValue = 0; Sub = None } /// Documents to use for testing -let testDocuments = [ - { Id = "one"; Value = "FIRST!"; NumValue = 0; Sub = None } - { Id = "two"; Value = "another"; NumValue = 10; Sub = Some { Foo = "green"; Bar = "blue" } } - { Id = "three"; Value = ""; NumValue = 4; Sub = None } - { Id = "four"; Value = "purple"; NumValue = 17; Sub = Some { Foo = "green"; Bar = "red" } } - { Id = "five"; Value = "purple"; NumValue = 18; Sub = None } -] +let testDocuments = + [ { Id = "one"; Value = "FIRST!"; NumValue = 0; Sub = None } + { Id = "two"; Value = "another"; NumValue = 10; Sub = Some { Foo = "green"; Bar = "blue" } } + { Id = "three"; Value = ""; NumValue = 4; Sub = None } + { Id = "four"; Value = "purple"; NumValue = 17; Sub = Some { Foo = "green"; Bar = "red" } } + { Id = "five"; Value = "purple"; NumValue = 18; Sub = None } ]