Version 4 #6
@ -125,6 +125,10 @@ type FieldMatch =
|
||||
| Any
|
||||
/// All fields match (AND)
|
||||
| All
|
||||
|
||||
/// The SQL value implementing each matching strategy
|
||||
override this.ToString() =
|
||||
match this with Any -> "OR" | All -> "AND"
|
||||
|
||||
|
||||
/// Derive parameter names (each instance wraps a counter to uniquely name anonymous fields)
|
||||
|
@ -46,7 +46,7 @@ module private Helpers =
|
||||
()
|
||||
}
|
||||
|
||||
/// Create a numerically-typed parameter, or use the given parameter derivation function if non-(numeric or string)
|
||||
/// Create a number or string parameter, or use the given parameter derivation function if non-(numeric or string)
|
||||
let internal parameterFor<'T> (value: 'T) (catchAllFunc: 'T -> SqlValue) =
|
||||
match box value with
|
||||
| :? int8 as it -> Sql.int8 it
|
||||
@ -70,7 +70,7 @@ open BitBadger.Documents
|
||||
[<AutoOpen>]
|
||||
module Parameters =
|
||||
|
||||
/// Create an ID parameter (name "@id", key will be treated as a string)
|
||||
/// Create an ID parameter (name "@id")
|
||||
[<CompiledName "Id">]
|
||||
let idParam (key: 'TKey) =
|
||||
"@id", parameterFor key (fun it -> Sql.string (string it))
|
||||
@ -134,7 +134,7 @@ module Query =
|
||||
|
||||
/// Create a WHERE clause fragment to implement a comparison on fields in a JSON document
|
||||
[<CompiledName "WhereByFields">]
|
||||
let whereByFields howMatched fields =
|
||||
let whereByFields (howMatched: FieldMatch) fields =
|
||||
let name = ParameterName()
|
||||
let isNumeric (it: obj) =
|
||||
match it with
|
||||
@ -154,7 +154,7 @@ module Query =
|
||||
if isNumeric value then
|
||||
$"({it.Path PostgreSQL})::numeric {it.Op} {param}"
|
||||
else $"{it.Path PostgreSQL} {it.Op} {param}")
|
||||
|> String.concat (match howMatched with Any -> " OR " | All -> " AND ")
|
||||
|> String.concat $" {howMatched} "
|
||||
|
||||
/// Create a WHERE clause fragment to implement an ID-based query
|
||||
[<CompiledName "WhereById">]
|
||||
|
@ -33,7 +33,7 @@ module Query =
|
||||
|
||||
/// Create a WHERE clause fragment to implement a comparison on fields in a JSON document
|
||||
[<CompiledName "WhereByFields">]
|
||||
let whereByFields howMatched fields =
|
||||
let whereByFields (howMatched: FieldMatch) fields =
|
||||
let name = ParameterName()
|
||||
fields
|
||||
|> Seq.map (fun it ->
|
||||
@ -43,7 +43,7 @@ module Query =
|
||||
let p = name.Derive it.ParameterName
|
||||
$"{it.Path SQLite} {it.Op} {p}min AND {p}max"
|
||||
| _ -> $"{it.Path SQLite} {it.Op} {name.Derive it.ParameterName}")
|
||||
|> String.concat (match howMatched with Any -> " OR " | All -> " AND ")
|
||||
|> String.concat $" {howMatched} "
|
||||
|
||||
/// Create a WHERE clause fragment to implement an ID-based query
|
||||
[<CompiledName "WhereById">]
|
||||
|
@ -1,6 +1,7 @@
|
||||
using Expecto.CSharp;
|
||||
using Expecto;
|
||||
using Microsoft.FSharp.Collections;
|
||||
using Microsoft.FSharp.Core;
|
||||
|
||||
namespace BitBadger.Documents.Tests.CSharp;
|
||||
|
||||
@ -236,6 +237,39 @@ public static class CommonCSharpTests
|
||||
})
|
||||
])
|
||||
]),
|
||||
TestList("FieldMatch.ToString",
|
||||
[
|
||||
TestCase("succeeds for Any", () =>
|
||||
{
|
||||
Expect.equal(FieldMatch.Any.ToString(), "OR", "SQL for Any is incorrect");
|
||||
}),
|
||||
TestCase("succeeds for All", () =>
|
||||
{
|
||||
Expect.equal(FieldMatch.All.ToString(), "AND", "SQL for All is incorrect");
|
||||
})
|
||||
]),
|
||||
TestList("ParameterName.Derive",
|
||||
[
|
||||
TestCase("succeeds with existing name", () =>
|
||||
{
|
||||
ParameterName name = new();
|
||||
Expect.equal(name.Derive(FSharpOption<string>.Some("@taco")), "@taco", "Name should have been @taco");
|
||||
Expect.equal(name.Derive(FSharpOption<string>.None), "@field0",
|
||||
"Counter should not have advanced for named field");
|
||||
}),
|
||||
TestCase("Derive succeeds with non-existent name", () =>
|
||||
{
|
||||
ParameterName name = new();
|
||||
Expect.equal(name.Derive(FSharpOption<string>.None), "@field0",
|
||||
"Anonymous field name should have been returned");
|
||||
Expect.equal(name.Derive(FSharpOption<string>.None), "@field1",
|
||||
"Counter should have advanced from previous call");
|
||||
Expect.equal(name.Derive(FSharpOption<string>.None), "@field2",
|
||||
"Counter should have advanced from previous call");
|
||||
Expect.equal(name.Derive(FSharpOption<string>.None), "@field3",
|
||||
"Counter should have advanced from previous call");
|
||||
})
|
||||
]),
|
||||
TestList("Query",
|
||||
[
|
||||
TestCase("StatementWhere succeeds", () =>
|
||||
|
@ -156,32 +156,6 @@ public static class PostgresCSharpTests
|
||||
Expect.equal(value, Sql.@string("zed"), "Maximum field value not correct");
|
||||
})
|
||||
]),
|
||||
#pragma warning disable CS0618
|
||||
TestList("AddField",
|
||||
[
|
||||
TestCase("succeeds when a parameter is added", () =>
|
||||
{
|
||||
var it = Parameters.AddField("@field", Field.EQ("it", "242"), []).ToList();
|
||||
Expect.hasLength(it, 1, "There should have been a parameter added");
|
||||
Expect.equal(it[0].Item1, "@field", "Field parameter not constructed correctly");
|
||||
Expect.isTrue(it[0].Item2.IsString, "Field parameter value incorrect");
|
||||
}),
|
||||
TestCase("succeeds when a parameter is not added", () =>
|
||||
{
|
||||
var it = Parameters.AddField("@it", Field.EX("It"), []);
|
||||
Expect.isEmpty(it, "There should not have been any parameters added");
|
||||
}),
|
||||
TestCase("succeeds when two parameters are added", () =>
|
||||
{
|
||||
var it = Parameters.AddField("@field", Field.BT("that", "eh", "zed"), []).ToList();
|
||||
Expect.hasLength(it, 2, "There should have been 2 parameters added");
|
||||
Expect.equal(it[0].Item1, "@fieldmin", "Minimum field name not correct");
|
||||
Expect.isTrue(it[0].Item2.IsString, "Minimum field parameter value incorrect");
|
||||
Expect.equal(it[1].Item1, "@fieldmax", "Maximum field name not correct");
|
||||
Expect.isTrue(it[1].Item2.IsString, "Maximum field parameter value incorrect");
|
||||
})
|
||||
]),
|
||||
#pragma warning restore CS0618
|
||||
TestList("FieldNames",
|
||||
[
|
||||
TestCase("succeeds for one name", () =>
|
||||
@ -331,6 +305,36 @@ public static class PostgresCSharpTests
|
||||
{
|
||||
Expect.equal(Postgres.Query.WhereJsonPathMatches("@path"), "data @? @path::jsonpath",
|
||||
"WHERE clause not correct");
|
||||
}),
|
||||
TestCase("Patch succeeds", () =>
|
||||
{
|
||||
Expect.equal(Postgres.Query.Patch(PostgresDb.TableName),
|
||||
$"UPDATE {PostgresDb.TableName} SET data = data || @data", "Patch query not correct");
|
||||
}),
|
||||
TestCase("RemoveFields succeeds", () =>
|
||||
{
|
||||
Expect.equal(Postgres.Query.RemoveFields(PostgresDb.TableName),
|
||||
$"UPDATE {PostgresDb.TableName} SET data = data - @name", "Field removal query not correct");
|
||||
}),
|
||||
TestCase("ById succeeds", () =>
|
||||
{
|
||||
Expect.equal(Postgres.Query.ById("test", "14"), "test WHERE data->>'Id' = @id",
|
||||
"By-ID query not correct");
|
||||
}),
|
||||
TestCase("ByFields succeeds", () =>
|
||||
{
|
||||
Expect.equal(Postgres.Query.ByFields("unit", FieldMatch.Any, [Field.GT("That", 14)]),
|
||||
"unit WHERE (data->>'That')::numeric > @field0", "By-Field query not correct");
|
||||
}),
|
||||
TestCase ("ByContains succeeds", () =>
|
||||
{
|
||||
Expect.equal(Postgres.Query.ByContains("exam"), "exam WHERE data @> @criteria",
|
||||
"By-Contains query not correct");
|
||||
}),
|
||||
TestCase("ByPathMach succeeds", () =>
|
||||
{
|
||||
Expect.equal(Postgres.Query.ByPathMatch("verify"), "verify WHERE data @? @path::jsonpath",
|
||||
"By-JSON Path query not correct");
|
||||
})
|
||||
])
|
||||
]);
|
||||
|
@ -67,6 +67,27 @@ public static class SqliteCSharpTests
|
||||
{
|
||||
Expect.equal(Sqlite.Query.WhereById("@id"), "data->>'Id' = @id", "WHERE clause not correct");
|
||||
}),
|
||||
TestCase("Patch succeeds", () =>
|
||||
{
|
||||
Expect.equal(Sqlite.Query.Patch(SqliteDb.TableName),
|
||||
$"UPDATE {SqliteDb.TableName} SET data = json_patch(data, json(@data))", "Patch query not correct");
|
||||
}),
|
||||
TestCase("RemoveFields succeeds", () =>
|
||||
{
|
||||
Expect.equal(Sqlite.Query.RemoveFields(SqliteDb.TableName, [new("@a", "a"), new("@b", "b")]),
|
||||
$"UPDATE {SqliteDb.TableName} SET data = json_remove(data, @a, @b)",
|
||||
"Field removal query not correct");
|
||||
}),
|
||||
TestCase("ById succeeds", () =>
|
||||
{
|
||||
Expect.equal(Sqlite.Query.ById("test", "14"), "test WHERE data->>'Id' = @id",
|
||||
"By-ID query not correct");
|
||||
}),
|
||||
TestCase("ByFields succeeds", () =>
|
||||
{
|
||||
Expect.equal(Sqlite.Query.ByFields("unit", FieldMatch.Any, [Field.GT("That", 14)]),
|
||||
"unit WHERE data->>'That' > @field0", "By-Field query not correct");
|
||||
}),
|
||||
TestCase("Definition.EnsureTable succeeds", () =>
|
||||
{
|
||||
Expect.equal(Sqlite.Query.Definition.EnsureTable("tbl"),
|
||||
|
@ -155,13 +155,21 @@ let all =
|
||||
}
|
||||
]
|
||||
]
|
||||
testList "ParameterName" [
|
||||
test "Derive succeeds with existing name" {
|
||||
testList "FieldMatch.ToString" [
|
||||
test "succeeds for Any" {
|
||||
Expect.equal (string Any) "OR" "SQL for Any is incorrect"
|
||||
}
|
||||
test "succeeds for All" {
|
||||
Expect.equal (string All) "AND" "SQL for All is incorrect"
|
||||
}
|
||||
]
|
||||
testList "ParameterName.Derive" [
|
||||
test "succeeds with existing name" {
|
||||
let name = ParameterName()
|
||||
Expect.equal (name.Derive(Some "@taco")) "@taco" "Name should have been @taco"
|
||||
Expect.equal (name.Derive None) "@field0" "Counter should not have advanced for named field"
|
||||
}
|
||||
test "Derive succeeds with non-existent name" {
|
||||
test "succeeds with non-existent name" {
|
||||
let name = ParameterName()
|
||||
Expect.equal (name.Derive None) "@field0" "Anonymous field name should have been returned"
|
||||
Expect.equal (name.Derive None) "@field1" "Counter should have advanced from previous call"
|
||||
|
@ -124,29 +124,6 @@ let unitTests =
|
||||
Expect.equal value (Sql.string "zed") "Maximum parameter value not correct"
|
||||
}
|
||||
]
|
||||
testList "addFieldParam" [
|
||||
test "succeeds when a parameter is added" {
|
||||
let paramList = addFieldParam "@field" (Field.EQ "it" "242") []
|
||||
Expect.hasLength paramList 1 "There should have been a parameter added"
|
||||
let name, value = Seq.head paramList
|
||||
Expect.equal name "@field" "Field parameter name not correct"
|
||||
Expect.equal value (Sql.string "242") "Parameter value not correct"
|
||||
}
|
||||
test "succeeds when a parameter is not added" {
|
||||
let paramList = addFieldParam "@field" (Field.EX "tacos") []
|
||||
Expect.isEmpty paramList "There should not have been any parameters added"
|
||||
}
|
||||
test "succeeds when two parameters are added" {
|
||||
let paramList = addFieldParam "@field" (Field.BT "that" "eh" "zed") []
|
||||
Expect.hasLength paramList 2 "There should have been 2 parameters added"
|
||||
let name, value = Seq.head paramList
|
||||
Expect.equal name "@fieldmin" "Minimum field name not correct"
|
||||
Expect.equal value (Sql.string "eh") "Minimum parameter value not correct"
|
||||
let name, value = paramList |> Seq.skip 1 |> Seq.head
|
||||
Expect.equal name "@fieldmax" "Maximum field name not correct"
|
||||
Expect.equal value (Sql.string "zed") "Maximum parameter value not correct"
|
||||
}
|
||||
]
|
||||
testList "fieldNameParams" [
|
||||
test "succeeds for one name" {
|
||||
let name, value = fieldNameParams [ "bob" ]
|
||||
@ -261,6 +238,34 @@ let unitTests =
|
||||
test "whereJsonPathMatches succeeds" {
|
||||
Expect.equal (Query.whereJsonPathMatches "@path") "data @? @path::jsonpath" "WHERE clause not correct"
|
||||
}
|
||||
test "patch succeeds" {
|
||||
Expect.equal
|
||||
(Query.patch PostgresDb.TableName)
|
||||
$"UPDATE {PostgresDb.TableName} SET data = data || @data"
|
||||
"Patch query not correct"
|
||||
}
|
||||
test "removeFields succeeds" {
|
||||
Expect.equal
|
||||
(Query.removeFields PostgresDb.TableName)
|
||||
$"UPDATE {PostgresDb.TableName} SET data = data - @name"
|
||||
"Field removal query not correct"
|
||||
}
|
||||
test "byId succeeds" {
|
||||
Expect.equal (Query.byId "test" "14") "test WHERE data->>'Id' = @id" "By-ID query not correct"
|
||||
}
|
||||
test "byFields succeeds" {
|
||||
Expect.equal
|
||||
(Query.byFields "unit" Any [ Field.GT "That" 14 ])
|
||||
"unit WHERE (data->>'That')::numeric > @field0"
|
||||
"By-Field query not correct"
|
||||
}
|
||||
test "byContains succeeds" {
|
||||
Expect.equal (Query.byContains "exam") "exam WHERE data @> @criteria" "By-Contains query not correct"
|
||||
}
|
||||
test "byPathMach succeeds" {
|
||||
Expect.equal
|
||||
(Query.byPathMatch "verify") "verify WHERE data @? @path::jsonpath" "By-JSON Path query not correct"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
|
@ -55,6 +55,27 @@ let unitTests =
|
||||
test "whereById succeeds" {
|
||||
Expect.equal (Query.whereById "@id") "data->>'Id' = @id" "WHERE clause not correct"
|
||||
}
|
||||
test "patch succeeds" {
|
||||
Expect.equal
|
||||
(Query.patch SqliteDb.TableName)
|
||||
$"UPDATE {SqliteDb.TableName} SET data = json_patch(data, json(@data))"
|
||||
"Patch query not correct"
|
||||
}
|
||||
test "removeFields succeeds" {
|
||||
Expect.equal
|
||||
(Query.removeFields SqliteDb.TableName [ SqliteParameter("@a", "a"); SqliteParameter("@b", "b") ])
|
||||
$"UPDATE {SqliteDb.TableName} SET data = json_remove(data, @a, @b)"
|
||||
"Field removal query not correct"
|
||||
}
|
||||
test "byId succeeds" {
|
||||
Expect.equal (Query.byId "test" "14") "test WHERE data->>'Id' = @id" "By-ID query not correct"
|
||||
}
|
||||
test "byFields succeeds" {
|
||||
Expect.equal
|
||||
(Query.byFields "unit" Any [ Field.GT "That" 14 ])
|
||||
"unit WHERE data->>'That' > @field0"
|
||||
"By-Field query not correct"
|
||||
}
|
||||
test "Definition.ensureTable succeeds" {
|
||||
Expect.equal
|
||||
(Query.Definition.ensureTable "tbl")
|
||||
|
Loading…
Reference in New Issue
Block a user