Version 4 #6

Merged
danieljsummers merged 30 commits from version-four into main 2024-08-19 23:30:39 +00:00
3 changed files with 270 additions and 154 deletions
Showing only changes of commit d9d37f110d - Show all commits

View File

@ -46,6 +46,23 @@ module private Helpers =
() ()
} }
/// Create a numerically-typed parameter, or use the given parameter derivation function if non-(numeric or string)
let internal parameterFor<'T> (value: 'T) (catchAllFunc: 'T -> SqlValue) =
match box value with
| :? int8 as it -> Sql.int8 it
| :? uint8 as it -> Sql.int8 (int8 it)
| :? int16 as it -> Sql.int16 it
| :? uint16 as it -> Sql.int16 (int16 it)
| :? int as it -> Sql.int it
| :? uint32 as it -> Sql.int (int it)
| :? int64 as it -> Sql.int64 it
| :? uint64 as it -> Sql.int64 (int64 it)
| :? decimal as it -> Sql.decimal it
| :? single as it -> Sql.double (double it)
| :? double as it -> Sql.double it
| :? string as it -> Sql.string it
| _ -> catchAllFunc value
open BitBadger.Documents open BitBadger.Documents
@ -56,21 +73,7 @@ module Parameters =
/// Create an ID parameter (name "@id", key will be treated as a string) /// Create an ID parameter (name "@id", key will be treated as a string)
[<CompiledName "Id">] [<CompiledName "Id">]
let idParam (key: 'TKey) = let idParam (key: 'TKey) =
match box key with "@id", parameterFor key (fun it -> Sql.string (string it))
| :? int8 as it -> Sql.int8 it
| :? uint8 as it -> Sql.int8 (int8 it)
| :? int16 as it -> Sql.int16 it
| :? uint16 as it -> Sql.int16 (int16 it)
| :? int as it -> Sql.int it
| :? uint32 as it -> Sql.int (int it)
| :? int64 as it -> Sql.int64 it
| :? uint64 as it -> Sql.int64 (int64 it)
| :? decimal as it -> Sql.decimal it
| :? single as it -> Sql.double (double it)
| :? double as it -> Sql.double it
| :? string as it -> Sql.string it
| _ -> Sql.string (string key)
|> function it -> "@id", it
/// Create a parameter with a JSON value /// Create a parameter with a JSON value
[<CompiledName "Json">] [<CompiledName "Json">]
@ -89,11 +92,13 @@ module Parameters =
| BT -> | BT ->
let p = name.Derive it.ParameterName let p = name.Derive it.ParameterName
let values = it.Value :?> obj list let values = it.Value :?> obj list
yield ($"{p}min", Sql.parameter (NpgsqlParameter($"{p}min", List.head values))) yield ($"{p}min",
yield ($"{p}max", Sql.parameter (NpgsqlParameter($"{p}max", List.last values))) parameterFor (List.head values) (fun v -> Sql.parameter (NpgsqlParameter($"{p}min", v))))
yield ($"{p}max",
parameterFor (List.last values) (fun v -> Sql.parameter (NpgsqlParameter($"{p}max", v))))
| _ -> | _ ->
let p = name.Derive it.ParameterName let p = name.Derive it.ParameterName
yield (p, Sql.parameter (NpgsqlParameter(p, it.Value))) }) yield (p, parameterFor it.Value (fun v -> Sql.parameter (NpgsqlParameter(p, v)))) })
|> Seq.collect id |> Seq.collect id
|> Seq.append parameters |> Seq.append parameters
|> Seq.toList |> Seq.toList
@ -131,27 +136,30 @@ module Query =
[<CompiledName "WhereByFields">] [<CompiledName "WhereByFields">]
let whereByFields howMatched fields = let whereByFields howMatched fields =
let name = ParameterName() let name = ParameterName()
let isNumeric (it: obj) =
match it with
| :? int8 | :? uint8 | :? int16 | :? uint16 | :? int | :? uint32 | :? int64 | :? uint64
| :? decimal | :? single | :? double -> true
| _ -> false
fields fields
|> Seq.map (fun it -> |> Seq.map (fun it ->
match it.Op with match it.Op with
| EX | NEX -> $"{it.Path PostgreSQL} {it.Op}" | EX | NEX -> $"{it.Path PostgreSQL} {it.Op}"
| BT -> | _ ->
let p = name.Derive it.ParameterName let p = name.Derive it.ParameterName
let path, value = let param, value =
match it.Op with match it.Op with
| BT -> $"{p}min AND {p}max", (it.Value :?> obj list)[0] | BT -> $"{p}min AND {p}max", (it.Value :?> obj list)[0]
| _ -> p, it.Value | _ -> p, it.Value
match value with if isNumeric value then
| :? int8 | :? uint8 | :? int16 | :? uint16 | :? int | :? uint32 | :? int64 | :? uint64 $"({it.Path PostgreSQL})::numeric {it.Op} {param}"
| :? decimal | :? single | :? double -> $"({it.Path PostgreSQL})::numeric {it.Op} {path}" else $"{it.Path PostgreSQL} {it.Op} {param}")
| _ -> $"{it.Path PostgreSQL} {it.Op} {path}"
| _ -> $"{it.Path PostgreSQL} {it.Op} {name.Derive it.ParameterName}")
|> String.concat (match howMatched with Any -> " OR " | All -> " AND ") |> String.concat (match howMatched with Any -> " OR " | All -> " AND ")
/// Create a WHERE clause fragment to implement an ID-based query /// Create a WHERE clause fragment to implement an ID-based query
[<CompiledName "WhereById">] [<CompiledName "WhereById">]
let whereById paramName = let whereById<'TKey> (docId: 'TKey) =
whereByFields Any [ { Field.EQ (Configuration.idField ()) 0 with ParameterName = Some paramName } ] whereByFields Any [ { Field.EQ (Configuration.idField ()) docId with ParameterName = Some "@id" } ]
/// Table and index definition queries /// Table and index definition queries
module Definition = module Definition =
@ -191,9 +199,7 @@ module Query =
/// Create a query by a document's ID /// Create a query by a document's ID
[<CompiledName "ById">] [<CompiledName "ById">]
let byId<'TKey> statement (docId: 'TKey) = let byId<'TKey> statement (docId: 'TKey) =
Query.statementWhere Query.statementWhere statement (whereById docId)
statement
(whereByFields Any [ { Field.EQ (Configuration.idField ()) docId with ParameterName = Some "@id" } ])
/// Create a query on JSON fields /// Create a query on JSON fields
[<CompiledName "ByFields">] [<CompiledName "ByFields">]
@ -359,7 +365,7 @@ module WithProps =
/// Determine if a document exists for the given ID /// Determine if a document exists for the given ID
[<CompiledName "ById">] [<CompiledName "ById">]
let byId tableName (docId: 'TKey) sqlProps = let byId tableName (docId: 'TKey) sqlProps =
Custom.scalar (Query.exists tableName (Query.whereById "@id")) [ idParam docId ] toExists sqlProps Custom.scalar (Query.exists tableName (Query.whereById docId)) [ idParam docId ] toExists sqlProps
/// Determine if a document exists using JSON field comparisons (->> =) /// Determine if a document exists using JSON field comparisons (->> =)
[<CompiledName "ByFields">] [<CompiledName "ByFields">]
@ -537,9 +543,7 @@ module WithProps =
[<CompiledName "ById">] [<CompiledName "ById">]
let byId tableName (docId: 'TKey) (document: 'TDoc) sqlProps = let byId tableName (docId: 'TKey) (document: 'TDoc) sqlProps =
Custom.nonQuery Custom.nonQuery
(Query.statementWhere (Query.update tableName) (Query.whereById "@id")) (Query.byId (Query.update tableName) docId) [ idParam docId; jsonParam "@data" document ] sqlProps
[ idParam docId; jsonParam "@data" document ]
sqlProps
/// Update an entire document by its ID, using the provided function to obtain the ID from the document /// Update an entire document by its ID, using the provided function to obtain the ID from the document
[<CompiledName "FSharpByFunc">] [<CompiledName "FSharpByFunc">]

View File

@ -20,12 +20,93 @@ public static class PostgresCSharpTests
[ [
TestList("Parameters", TestList("Parameters",
[ [
TestCase("Id succeeds", () => TestList("Id",
{ [
var it = Parameters.Id(88); // NOTE: these tests also exercise all branches of the internal parameterFor function
Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly"); TestCase("succeeds for byte ID", () =>
Expect.equal(it.Item2, Sql.@string("88"), "ID parameter value incorrect"); {
}), var it = Parameters.Id((sbyte)7);
Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly");
Expect.equal(it.Item2, Sql.int8(7), "Byte ID parameter not constructed correctly");
}),
TestCase("succeeds for unsigned byte ID", () =>
{
var it = Parameters.Id((byte)7);
Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly");
Expect.equal(it.Item2, Sql.int8(7), "Unsigned byte ID parameter not constructed correctly");
}),
TestCase("succeeds for short ID", () =>
{
var it = Parameters.Id((short)44);
Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly");
Expect.equal(it.Item2, Sql.int16(44), "Short ID parameter not constructed correctly");
}),
TestCase("succeeds for unsigned short ID", () =>
{
var it = Parameters.Id((ushort)64);
Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly");
Expect.equal(it.Item2, Sql.int16(64), "Unsigned short ID parameter not constructed correctly");
}),
TestCase("succeeds for integer ID", () =>
{
var it = Parameters.Id(88);
Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly");
Expect.equal(it.Item2, Sql.@int(88), "ID parameter value incorrect");
}),
TestCase("succeeds for unsigned integer ID", () =>
{
var it = Parameters.Id((uint)889);
Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly");
Expect.equal(it.Item2, Sql.@int(889), "Unsigned int ID parameter not constructed correctly");
}),
TestCase("succeeds for long ID", () =>
{
var it = Parameters.Id((long)123);
Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly");
Expect.equal(it.Item2, Sql.int64(123), "Long ID parameter not constructed correctly");
}),
TestCase("succeeds for unsigned long ID", () =>
{
var it = Parameters.Id((ulong)6464);
Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly");
Expect.equal(it.Item2, Sql.int64(6464),
"Unsigned long ID parameter not constructed correctly");
}),
TestCase("succeeds for decimal ID", () =>
{
var it = Parameters.Id((decimal)4.56);
Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly");
Expect.equal(it.Item2, Sql.@decimal((decimal)4.56),
"Decimal ID parameter not constructed correctly");
}),
TestCase("succeeds for single ID", () =>
{
var it = Parameters.Id((float)5.67);
Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly");
Expect.equal(it.Item2, Sql.@double((float)5.67),
"Single ID parameter not constructed correctly");
}),
TestCase("succeeds for double ID", () =>
{
var it = Parameters.Id(6.78);
Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly");
Expect.equal(it.Item2, Sql.@double(6.78),
"Double ID parameter not constructed correctly");
}),
TestCase("succeeds for string ID", () =>
{
var it = Parameters.Id("99");
Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly");
Expect.equal(it.Item2, Sql.@string("99"), "ID parameter value incorrect");
}),
TestCase("succeeds for non-numeric non-string ID", () =>
{
var it = Parameters.Id(new Uri("https://example.com"));
Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly");
Expect.equal(it.Item2, Sql.@string("https://example.com/"),
"Non-numeric, non-string parameter value incorrect");
})
]),
TestCase("Json succeeds", () => TestCase("Json succeeds", () =>
{ {
var it = Parameters.Json("@test", new { Something = "good" }); var it = Parameters.Json("@test", new { Something = "good" });
@ -40,10 +121,7 @@ public static class PostgresCSharpTests
Expect.hasLength(paramList, 1, "There should have been a parameter added"); Expect.hasLength(paramList, 1, "There should have been a parameter added");
var (name, value) = paramList[0]; var (name, value) = paramList[0];
Expect.equal(name, "@field0", "Field parameter name not correct"); Expect.equal(name, "@field0", "Field parameter name not correct");
if (!value.IsParameter) Expect.equal(value, Sql.@string("242"), "Field parameter value not correct");
{
Expect.isTrue(false, "The parameter was not a Parameter type");
}
}), }),
TestCase("succeeds when multiple independent parameters are added", () => TestCase("succeeds when multiple independent parameters are added", () =>
{ {
@ -52,22 +130,13 @@ public static class PostgresCSharpTests
Expect.hasLength(paramList, 3, "There should have been 2 parameters added"); Expect.hasLength(paramList, 3, "There should have been 2 parameters added");
var (name, value) = paramList[0]; var (name, value) = paramList[0];
Expect.equal(name, "@id", "First field parameter name not correct"); Expect.equal(name, "@id", "First field parameter name not correct");
if (!value.IsString) Expect.equal(value, Sql.@int(14), "First field parameter value not correct");
{
Expect.isTrue(false, "First parameter was not a String type");
}
(name, value) = paramList[1]; (name, value) = paramList[1];
Expect.equal(name, "@field0", "Second field parameter name not correct"); Expect.equal(name, "@field0", "Second field parameter name not correct");
if (!value.IsParameter) Expect.equal(value, Sql.@string("you"), "Second field parameter value not correct");
{
Expect.isTrue(false, "Second parameter was not a Parameter type");
}
(name, value) = paramList[2]; (name, value) = paramList[2];
Expect.equal(name, "@field1", "Third parameter name not correct"); Expect.equal(name, "@field1", "Third parameter name not correct");
if (!value.IsParameter) Expect.equal(value, Sql.@string("them"), "Third parameter value not correct");
{
Expect.isTrue(false, "Third parameter was not a Parameter type");
}
}), }),
TestCase("succeeds when a parameter is not added", () => TestCase("succeeds when a parameter is not added", () =>
{ {
@ -81,16 +150,10 @@ public static class PostgresCSharpTests
Expect.hasLength(paramList, 2, "There should have been 2 parameters added"); Expect.hasLength(paramList, 2, "There should have been 2 parameters added");
var (name, value) = paramList[0]; var (name, value) = paramList[0];
Expect.equal(name, "@testmin", "Minimum field name not correct"); Expect.equal(name, "@testmin", "Minimum field name not correct");
if (!value.IsParameter) Expect.equal(value, Sql.@string("eh"), "Minimum field value not correct");
{
Expect.isTrue(false, "Minimum parameter was not a Parameter type");
}
(name, value) = paramList[1]; (name, value) = paramList[1];
Expect.equal(name, "@testmax", "Maximum field name not correct"); Expect.equal(name, "@testmax", "Maximum field name not correct");
if (!value.IsParameter) Expect.equal(value, Sql.@string("zed"), "Maximum field value not correct");
{
Expect.isTrue(false, "Maximum parameter was not a Parameter type");
}
}) })
]), ]),
#pragma warning disable CS0618 #pragma warning disable CS0618
@ -101,7 +164,7 @@ public static class PostgresCSharpTests
var it = Parameters.AddField("@field", Field.EQ("it", "242"), []).ToList(); var it = Parameters.AddField("@field", Field.EQ("it", "242"), []).ToList();
Expect.hasLength(it, 1, "There should have been a parameter added"); Expect.hasLength(it, 1, "There should have been a parameter added");
Expect.equal(it[0].Item1, "@field", "Field parameter not constructed correctly"); Expect.equal(it[0].Item1, "@field", "Field parameter not constructed correctly");
Expect.isTrue(it[0].Item2.IsParameter, "Field parameter value incorrect"); Expect.isTrue(it[0].Item2.IsString, "Field parameter value incorrect");
}), }),
TestCase("succeeds when a parameter is not added", () => TestCase("succeeds when a parameter is not added", () =>
{ {
@ -113,9 +176,9 @@ public static class PostgresCSharpTests
var it = Parameters.AddField("@field", Field.BT("that", "eh", "zed"), []).ToList(); var it = Parameters.AddField("@field", Field.BT("that", "eh", "zed"), []).ToList();
Expect.hasLength(it, 2, "There should have been 2 parameters added"); Expect.hasLength(it, 2, "There should have been 2 parameters added");
Expect.equal(it[0].Item1, "@fieldmin", "Minimum field name not correct"); Expect.equal(it[0].Item1, "@fieldmin", "Minimum field name not correct");
Expect.isTrue(it[0].Item2.IsParameter, "Minimum field parameter value incorrect"); Expect.isTrue(it[0].Item2.IsString, "Minimum field parameter value incorrect");
Expect.equal(it[1].Item1, "@fieldmax", "Maximum field name not correct"); Expect.equal(it[1].Item1, "@fieldmax", "Maximum field name not correct");
Expect.isTrue(it[1].Item2.IsParameter, "Maximum field parameter value incorrect"); Expect.isTrue(it[1].Item2.IsString, "Maximum field parameter value incorrect");
}) })
]), ]),
#pragma warning restore CS0618 #pragma warning restore CS0618
@ -172,7 +235,7 @@ public static class PostgresCSharpTests
{ {
Expect.equal( Expect.equal(
Postgres.Query.WhereByFields(FieldMatch.Any, Postgres.Query.WhereByFields(FieldMatch.Any,
[Field.GT("theField", 0).WithParameterName("@test")]), [Field.GT("theField", "0").WithParameterName("@test")]),
"data->>'theField' > @test", "WHERE clause not correct"); "data->>'theField' > @test", "WHERE clause not correct");
}), }),
TestCase("succeeds for a single field when an existence operator is passed", () => TestCase("succeeds for a single field when an existence operator is passed", () =>
@ -206,7 +269,8 @@ public static class PostgresCSharpTests
Expect.equal( Expect.equal(
Postgres.Query.WhereByFields(FieldMatch.Any, Postgres.Query.WhereByFields(FieldMatch.Any,
[Field.NEX("thatField"), Field.GE("thisField", 18)]), [Field.NEX("thatField"), Field.GE("thisField", 18)]),
"data->>'thatField' IS NULL OR data->>'thisField' >= @field0", "WHERE clause not correct"); "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 operators", () =>
{ {
@ -217,10 +281,23 @@ public static class PostgresCSharpTests
"WHERE clause not correct"); "WHERE clause not correct");
}) })
]), ]),
TestCase("WhereById succeeds", () => TestList("WhereById",
{ [
Expect.equal(Postgres.Query.WhereById("@id"), "data->>'Id' = @id", "WHERE clause not correct"); TestCase("succeeds for numeric ID", () =>
}), {
Expect.equal(Postgres.Query.WhereById(18), "(data->>'Id')::numeric = @id",
"WHERE clause not correct");
}),
TestCase("succeeds for string ID", () =>
{
Expect.equal(Postgres.Query.WhereById("18"), "data->>'Id' = @id", "WHERE clause not correct");
}),
TestCase("succeeds for non-numeric non-string ID", () =>
{
Expect.equal(Postgres.Query.WhereById(new Uri("https://example.com")), "data->>'Id' = @id",
"WHERE clause not correct");
}),
]),
TestList("Definition", TestList("Definition",
[ [
TestCase("EnsureTable succeeds", () => TestCase("EnsureTable succeeds", () =>

View File

@ -11,9 +11,78 @@ open BitBadger.Documents.Tests
let unitTests = let unitTests =
testList "Unit" [ testList "Unit" [
testList "Parameters" [ testList "Parameters" [
test "idParam succeeds" { testList "idParam" [
Expect.equal (idParam 88) ("@id", Sql.string "88") "ID parameter not constructed correctly" // NOTE: these tests also exercise all branches of the internal parameterFor function
} test "succeeds for byte ID" {
Expect.equal
(idParam (sbyte 7)) ("@id", Sql.int8 (sbyte 7)) "Byte ID parameter not constructed correctly"
}
test "succeeds for unsigned byte ID" {
Expect.equal
(idParam (byte 7))
("@id", Sql.int8 (int8 (byte 7)))
"Unsigned byte ID parameter not constructed correctly"
}
test "succeeds for short ID" {
Expect.equal
(idParam (int16 44))
("@id", Sql.int16 (int16 44))
"Short ID parameter not constructed correctly"
}
test "succeeds for unsigned short ID" {
Expect.equal
(idParam (uint16 64))
("@id", Sql.int16 (int16 64))
"Unsigned short ID parameter not constructed correctly"
}
test "succeeds for integer ID" {
Expect.equal (idParam 88) ("@id", Sql.int 88) "Int ID parameter not constructed correctly"
}
test "succeeds for unsigned integer ID" {
Expect.equal
(idParam (uint 889)) ("@id", Sql.int 889) "Unsigned int ID parameter not constructed correctly"
}
test "succeeds for long ID" {
Expect.equal
(idParam (int64 123))
("@id", Sql.int64 (int64 123))
"Long ID parameter not constructed correctly"
}
test "succeeds for unsigned long ID" {
Expect.equal
(idParam (uint64 6464))
("@id", Sql.int64 (int64 6464))
"Unsigned long ID parameter not constructed correctly"
}
test "succeeds for decimal ID" {
Expect.equal
(idParam (decimal 4.56))
("@id", Sql.decimal (decimal 4.56))
"Decimal ID parameter not constructed correctly"
}
test "succeeds for single ID" {
Expect.equal
(idParam (single 5.67))
("@id", Sql.double (double (single 5.67)))
"Single ID parameter not constructed correctly"
}
test "succeeds for double ID" {
Expect.equal
(idParam (double 6.78))
("@id", Sql.double (double 6.78))
"Double ID parameter not constructed correctly"
}
test "succeeds for string ID" {
Expect.equal (idParam "99") ("@id", Sql.string "99") "String ID parameter not constructed correctly"
}
test "succeeds for non-numeric non-string ID" {
let target = { new obj() with override _.ToString() = "ToString was called" }
Expect.equal
(idParam target)
("@id", Sql.string "ToString was called")
"Non-numeric, non-string parameter not constructed correctly"
}
]
test "jsonParam succeeds" { test "jsonParam succeeds" {
Expect.equal Expect.equal
(jsonParam "@test" {| Something = "good" |}) (jsonParam "@test" {| Something = "good" |})
@ -24,35 +93,20 @@ let unitTests =
test "succeeds when a parameter is added" { test "succeeds when a parameter is added" {
let paramList = addFieldParams [ Field.EQ "it" "242" ] [] let paramList = addFieldParams [ Field.EQ "it" "242" ] []
Expect.hasLength paramList 1 "There should have been a parameter added" Expect.hasLength paramList 1 "There should have been a parameter added"
let it = Seq.head paramList let name, value = Seq.head paramList
Expect.equal (fst it) "@field0" "Field parameter name not correct" Expect.equal name "@field0" "Field parameter name not correct"
match snd it with Expect.equal value (Sql.string "242") "Parameter value not correct"
| SqlValue.Parameter value ->
Expect.equal value.ParameterName "@field0" "Parameter name not correct"
Expect.equal value.Value "242" "Parameter value not correct"
| _ -> Expect.isTrue false "The parameter was not a Parameter type"
} }
test "succeeds when multiple independent parameters are added" { 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.EQ "me" "you"; Field.GT "us" "them" ] [ idParam 14 ]
Expect.hasLength paramList 3 "There should have been 2 parameters added" Expect.hasLength paramList 3 "There should have been 2 parameters added"
let p = Array.ofSeq paramList let p = Array.ofSeq paramList
Expect.equal (fst p[0]) "@id" "First field parameter name not correct" Expect.equal (fst p[0]) "@id" "First field parameter name not correct"
match snd p[0] with Expect.equal (snd p[0]) (Sql.int 14) "First parameter value not correct"
| SqlValue.String value ->
Expect.equal value "14" "First parameter value not correct"
| _ -> Expect.isTrue false "First parameter was not a String type"
Expect.equal (fst p[1]) "@field0" "Second field parameter name not correct" Expect.equal (fst p[1]) "@field0" "Second field parameter name not correct"
match snd p[1] with Expect.equal (snd p[1]) (Sql.string "you") "Second parameter value not correct"
| SqlValue.Parameter value ->
Expect.equal value.ParameterName "@field0" "Second parameter name not correct"
Expect.equal value.Value "you" "Second parameter value not correct"
| _ -> Expect.isTrue false "Second parameter was not a Parameter type"
Expect.equal (fst p[2]) "@field1" "Third parameter name not correct" Expect.equal (fst p[2]) "@field1" "Third parameter name not correct"
match snd p[2] with Expect.equal (snd p[2]) (Sql.string "them") "Third parameter value not correct"
| SqlValue.Parameter value ->
Expect.equal value.ParameterName "@field1" "Third parameter name not correct"
Expect.equal value.Value "them" "Third parameter value not correct"
| _ -> Expect.isTrue false "Third parameter was not a Parameter type"
} }
test "succeeds when a parameter is not added" { test "succeeds when a parameter is not added" {
let paramList = addFieldParams [ Field.EX "tacos" ] [] let paramList = addFieldParams [ Field.EX "tacos" ] []
@ -62,33 +116,21 @@ let unitTests =
let paramList = let paramList =
addFieldParams [ { Field.BT "that" "eh" "zed" with ParameterName = Some "@test" } ] [] addFieldParams [ { Field.BT "that" "eh" "zed" with ParameterName = Some "@test" } ] []
Expect.hasLength paramList 2 "There should have been 2 parameters added" Expect.hasLength paramList 2 "There should have been 2 parameters added"
let min = Seq.head paramList let name, value = Seq.head paramList
Expect.equal (fst min) "@testmin" "Minimum field name not correct" Expect.equal name "@testmin" "Minimum field name not correct"
match snd min with Expect.equal value (Sql.string "eh") "Minimum parameter value not correct"
| SqlValue.Parameter value -> let name, value = paramList |> Seq.skip 1 |> Seq.head
Expect.equal value.ParameterName "@testmin" "Minimum parameter name not correct" Expect.equal name "@testmax" "Maximum field name not correct"
Expect.equal value.Value "eh" "Minimum parameter value not correct" Expect.equal value (Sql.string "zed") "Maximum parameter value not correct"
| _ -> Expect.isTrue false "Minimum parameter was not a Parameter type"
let max = paramList |> Seq.skip 1 |> Seq.head
Expect.equal (fst max) "@testmax" "Maximum field name not correct"
match snd max with
| SqlValue.Parameter value ->
Expect.equal value.ParameterName "@testmax" "Maximum parameter name not correct"
Expect.equal value.Value "zed" "Maximum parameter value not correct"
| _ -> Expect.isTrue false "Maximum parameter was not a Parameter type"
} }
] ]
testList "addFieldParam" [ testList "addFieldParam" [
test "succeeds when a parameter is added" { test "succeeds when a parameter is added" {
let paramList = addFieldParam "@field" (Field.EQ "it" "242") [] let paramList = addFieldParam "@field" (Field.EQ "it" "242") []
Expect.hasLength paramList 1 "There should have been a parameter added" Expect.hasLength paramList 1 "There should have been a parameter added"
let it = Seq.head paramList let name, value = Seq.head paramList
Expect.equal (fst it) "@field" "Field parameter name not correct" Expect.equal name "@field" "Field parameter name not correct"
match snd it with Expect.equal value (Sql.string "242") "Parameter value not correct"
| SqlValue.Parameter value ->
Expect.equal value.ParameterName "@field" "Parameter name not correct"
Expect.equal value.Value "242" "Parameter value not correct"
| _ -> Expect.isTrue false "The parameter was not a Parameter type"
} }
test "succeeds when a parameter is not added" { test "succeeds when a parameter is not added" {
let paramList = addFieldParam "@field" (Field.EX "tacos") [] let paramList = addFieldParam "@field" (Field.EX "tacos") []
@ -97,54 +139,36 @@ let unitTests =
test "succeeds when two parameters are added" { test "succeeds when two parameters are added" {
let paramList = addFieldParam "@field" (Field.BT "that" "eh" "zed") [] let paramList = addFieldParam "@field" (Field.BT "that" "eh" "zed") []
Expect.hasLength paramList 2 "There should have been 2 parameters added" Expect.hasLength paramList 2 "There should have been 2 parameters added"
let min = Seq.head paramList let name, value = Seq.head paramList
Expect.equal (fst min) "@fieldmin" "Minimum field name not correct" Expect.equal name "@fieldmin" "Minimum field name not correct"
match snd min with Expect.equal value (Sql.string "eh") "Minimum parameter value not correct"
| SqlValue.Parameter value -> let name, value = paramList |> Seq.skip 1 |> Seq.head
Expect.equal value.ParameterName "@fieldmin" "Minimum parameter name not correct" Expect.equal name "@fieldmax" "Maximum field name not correct"
Expect.equal value.Value "eh" "Minimum parameter value not correct" Expect.equal value (Sql.string "zed") "Maximum parameter value not correct"
| _ -> Expect.isTrue false "Minimum parameter was not a Parameter type"
let max = paramList |> Seq.skip 1 |> Seq.head
Expect.equal (fst max) "@fieldmax" "Maximum field name not correct"
match snd max with
| SqlValue.Parameter value ->
Expect.equal value.ParameterName "@fieldmax" "Maximum parameter name not correct"
Expect.equal value.Value "zed" "Maximum parameter value not correct"
| _ -> Expect.isTrue false "Maximum parameter was not a Parameter type"
} }
] ]
testList "fieldNameParams" [ testList "fieldNameParams" [
test "succeeds for one name" { test "succeeds for one name" {
let name, value = fieldNameParams [ "bob" ] let name, value = fieldNameParams [ "bob" ]
Expect.equal name "@name" "The parameter name was incorrect" Expect.equal name "@name" "The parameter name was incorrect"
match value with Expect.equal value (Sql.string "bob") "The parameter value was incorrect"
| SqlValue.String it -> Expect.equal it "bob" "The parameter value was incorrect"
| _ -> Expect.isTrue false "The parameter was not a String type"
} }
test "succeeds for multiple names" { test "succeeds for multiple names" {
let name, value = fieldNameParams [ "bob"; "tom"; "mike" ] let name, value = fieldNameParams [ "bob"; "tom"; "mike" ]
Expect.equal name "@name" "The parameter name was incorrect" Expect.equal name "@name" "The parameter name was incorrect"
match value with Expect.equal value (Sql.stringArray [| "bob"; "tom"; "mike" |]) "The parameter value was incorrect"
| SqlValue.StringArray it ->
Expect.equal it [| "bob"; "tom"; "mike" |] "The parameter value was incorrect"
| _ -> Expect.isTrue false "The parameter was not a StringArray type"
} }
] ]
testList "fieldNameParam" [ testList "fieldNameParam" [
test "succeeds for one name" { test "succeeds for one name" {
let name, value = fieldNameParam [ "bob" ] let name, value = fieldNameParam [ "bob" ]
Expect.equal name "@name" "The parameter name was incorrect" Expect.equal name "@name" "The parameter name was incorrect"
match value with Expect.equal value (Sql.string "bob") "The parameter value was incorrect"
| SqlValue.String it -> Expect.equal it "bob" "The parameter value was incorrect"
| _ -> Expect.isTrue false "The parameter was not a String type"
} }
test "succeeds for multiple names" { test "succeeds for multiple names" {
let name, value = fieldNameParam [ "bob"; "tom"; "mike" ] let name, value = fieldNameParam [ "bob"; "tom"; "mike" ]
Expect.equal name "@name" "The parameter name was incorrect" Expect.equal name "@name" "The parameter name was incorrect"
match value with Expect.equal value (Sql.stringArray [| "bob"; "tom"; "mike" |]) "The parameter value was incorrect"
| SqlValue.StringArray it ->
Expect.equal it [| "bob"; "tom"; "mike" |] "The parameter value was incorrect"
| _ -> Expect.isTrue false "The parameter was not a StringArray type"
} }
] ]
test "noParams succeeds" { test "noParams succeeds" {
@ -155,7 +179,7 @@ let unitTests =
testList "whereByFields" [ testList "whereByFields" [
test "succeeds for a single field when a logical operator is passed" { test "succeeds for a single field when a logical operator is passed" {
Expect.equal Expect.equal
(Query.whereByFields Any [ { Field.GT "theField" 0 with ParameterName = Some "@test" } ]) (Query.whereByFields Any [ { Field.GT "theField" "0" with ParameterName = Some "@test" } ])
"data->>'theField' > @test" "data->>'theField' > @test"
"WHERE clause not correct" "WHERE clause not correct"
} }
@ -186,7 +210,7 @@ let unitTests =
test "succeeds for any multiple fields with an existence operator" { test "succeeds for any multiple fields with an existence operator" {
Expect.equal Expect.equal
(Query.whereByFields Any [ Field.NEX "thatField"; Field.GE "thisField" 18 ]) (Query.whereByFields Any [ Field.NEX "thatField"; Field.GE "thisField" 18 ])
"data->>'thatField' IS NULL OR data->>'thisField' >= @field0" "data->>'thatField' IS NULL OR (data->>'thisField')::numeric >= @field0"
"WHERE clause not correct" "WHERE clause not correct"
} }
test "succeeds for all multiple fields with between operators" { test "succeeds for all multiple fields with between operators" {
@ -196,9 +220,20 @@ let unitTests =
"WHERE clause not correct" "WHERE clause not correct"
} }
] ]
test "whereById succeeds" { testList "whereById" [
Expect.equal (Query.whereById "@id") "data->>'Id' = @id" "WHERE clause not correct" test "succeeds for numeric ID" {
} Expect.equal (Query.whereById 18) "(data->>'Id')::numeric = @id" "WHERE clause not correct"
}
test "succeeds for string ID" {
Expect.equal (Query.whereById "18") "data->>'Id' = @id" "WHERE clause not correct"
}
test "succeeds for non-numeric non-string ID" {
Expect.equal
(Query.whereById (System.Uri "https://example.com"))
"data->>'Id' = @id"
"WHERE clause not correct"
}
]
testList "Definition" [ testList "Definition" [
test "ensureTable succeeds" { test "ensureTable succeeds" {
Expect.equal Expect.equal
@ -505,7 +540,7 @@ let integrationTests =
Expect.isFalse exists "There should not have been an existing document" Expect.isFalse exists "There should not have been an existing document"
} }
] ]
ftestList "byFields" [ testList "byFields" [
testTask "succeeds when documents exist" { testTask "succeeds when documents exist" {
use db = PostgresDb.BuildDb() use db = PostgresDb.BuildDb()
do! loadDocs () do! loadDocs ()
@ -621,7 +656,7 @@ let integrationTests =
PostgresDb.TableName All [ Field.EQ "Value" "purple"; Field.EX "Sub" ] PostgresDb.TableName All [ Field.EQ "Value" "purple"; Field.EX "Sub" ]
Expect.equal (List.length docs) 1 "There should have been one document returned" Expect.equal (List.length docs) 1 "There should have been one document returned"
} }
ftestTask "succeeds when documents are not found" { testTask "succeeds when documents are not found" {
use db = PostgresDb.BuildDb() use db = PostgresDb.BuildDb()
do! loadDocs () do! loadDocs ()