WIP on pulling code up to Common

This commit is contained in:
Daniel J. Summers 2024-08-09 20:15:08 -04:00
parent b1c3991e11
commit 35755df99a
10 changed files with 135 additions and 119 deletions

View File

@ -35,6 +35,12 @@ type Op =
| NEX -> "IS NULL" | NEX -> "IS NULL"
/// The dialect in which a command should be rendered
[<Struct>]
type Dialect =
| PostgreSQL
| SQLite
/// Criteria for a field WHERE clause /// Criteria for a field WHERE clause
type Field = { type Field = {
/// The name of the field /// The name of the field
@ -89,6 +95,16 @@ type Field = {
static member NEX name = static member NEX name =
{ Name = name; Op = NEX; Value = obj (); ParameterName = None; Qualifier = None } { Name = name; Op = NEX; Value = obj (); ParameterName = None; Qualifier = None }
/// Transform a field name (a.b.c) to a path for the given SQL dialect
static member NameToPath (name: string) dialect =
let path =
if name.Contains '.' then
match dialect with
| PostgreSQL -> "#>>'{" + String.concat "," (name.Split '.') + "}'"
| SQLite -> "->>'" + String.concat "'->>'" (name.Split '.') + "'"
else $"->>'{name}'"
$"data{path}"
/// Specify the name of the parameter for this field /// Specify the name of the parameter for this field
member this.WithParameterName name = member this.WithParameterName name =
{ this with ParameterName = Some name } { this with ParameterName = Some name }
@ -97,17 +113,9 @@ type Field = {
member this.WithQualifier alias = member this.WithQualifier alias =
{ this with Qualifier = Some alias } { this with Qualifier = Some alias }
/// Get the path for this field in PostgreSQL's format /// Get the qualified path to the field
member this.PgSqlPath = member this.Path dialect =
(this.Qualifier |> Option.map (fun q -> $"{q}.data") |> Option.defaultValue "data") (this.Qualifier |> Option.map (fun q -> $"{q}.") |> Option.defaultValue "") + Field.NameToPath this.Name dialect
+ if this.Name.Contains '.' then "#>>'{" + String.concat "," (this.Name.Split '.') + "}'"
else $"->>'{this.Name}'"
/// Get the path for this field in SQLite's format
member this.SqlitePath =
(this.Qualifier |> Option.map (fun q -> $"{q}.data") |> Option.defaultValue "data")
+ if this.Name.Contains '.' then "->>'" + String.concat "'->>'" (this.Name.Split '.') + "'"
else $"->>'{this.Name}'"
/// How fields should be matched /// How fields should be matched
@ -221,7 +229,7 @@ module Query =
/// SQL statement to create an index on one or more fields in a JSON document /// SQL statement to create an index on one or more fields in a JSON document
[<CompiledName "EnsureIndexOn">] [<CompiledName "EnsureIndexOn">]
let ensureIndexOn tableName indexName (fields: string seq) = let ensureIndexOn tableName indexName (fields: string seq) dialect =
let _, tbl = splitSchemaAndTable tableName let _, tbl = splitSchemaAndTable tableName
let jsonFields = let jsonFields =
fields fields
@ -229,14 +237,14 @@ module Query =
let parts = it.Split ' ' let parts = it.Split ' '
let fieldName = if Array.length parts = 1 then it else parts[0] let fieldName = if Array.length parts = 1 then it else parts[0]
let direction = if Array.length parts < 2 then "" else $" {parts[1]}" let direction = if Array.length parts < 2 then "" else $" {parts[1]}"
$"(data ->> '{fieldName}'){direction}") $"({Field.NameToPath fieldName dialect}){direction}")
|> String.concat ", " |> String.concat ", "
$"CREATE INDEX IF NOT EXISTS idx_{tbl}_%s{indexName} ON {tableName} ({jsonFields})" $"CREATE INDEX IF NOT EXISTS idx_{tbl}_%s{indexName} ON {tableName} ({jsonFields})"
/// SQL statement to create a key index for a document table /// SQL statement to create a key index for a document table
[<CompiledName "EnsureKey">] [<CompiledName "EnsureKey">]
let ensureKey tableName = let ensureKey tableName dialect =
(ensureIndexOn tableName "key" [ Configuration.idField () ]).Replace("INDEX", "UNIQUE INDEX") (ensureIndexOn tableName "key" [ Configuration.idField () ] dialect).Replace("INDEX", "UNIQUE INDEX")
/// Query to insert a document /// Query to insert a document
[<CompiledName "Insert">] [<CompiledName "Insert">]
@ -249,4 +257,16 @@ module Query =
sprintf sprintf
"INSERT INTO %s VALUES (@data) ON CONFLICT ((data->>'%s')) DO UPDATE SET data = EXCLUDED.data" "INSERT INTO %s VALUES (@data) ON CONFLICT ((data->>'%s')) DO UPDATE SET data = EXCLUDED.data"
tableName (Configuration.idField ()) tableName (Configuration.idField ())
/// Queries for counting documents
module Count =
/// Query to count all documents in a table
[<CompiledName "All">]
let all tableName =
$"SELECT COUNT(*) AS it FROM %s{tableName}"
/// Query to count matching documents using a text comparison on JSON fields
[<CompiledName "ByFields">]
let byFields (whereByFields: FieldMatch -> Field seq -> string) tableName howMatched fields =
$"SELECT COUNT(*) AS it FROM %s{tableName} WHERE {whereByFields howMatched fields}"

View File

@ -120,18 +120,18 @@ module Query =
fields fields
|> Seq.map (fun it -> |> Seq.map (fun it ->
match it.Op with match it.Op with
| EX | NEX -> $"{it.PgSqlPath} {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 path, 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
printfn $"%A{value}"
match value with match value with
| :? int8 | :? uint8 | :? int16 | :? uint16 | :? int | :? uint32 | :? int64 | :? uint64 | :? int8 | :? uint8 | :? int16 | :? uint16 | :? int | :? uint32 | :? int64 | :? uint64
| :? decimal | :? single | :? double -> $"({it.PgSqlPath})::numeric {it.Op} {path}" | :? decimal | :? single | :? double -> $"({it.Path PostgreSQL})::numeric {it.Op} {path}"
| _ -> $"{it.PgSqlPath} {it.Op} {path}") | _ -> $"{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 a comparison on a field in a JSON document /// Create a WHERE clause fragment to implement a comparison on a field in a JSON document
@ -178,15 +178,10 @@ module Query =
/// Queries for counting documents /// Queries for counting documents
module Count = module Count =
/// Query to count all documents in a table
[<CompiledName "All">]
let all tableName =
$"SELECT COUNT(*) AS it FROM %s{tableName}"
/// Query to count matching documents using a text comparison on JSON fields /// Query to count matching documents using a text comparison on JSON fields
[<CompiledName "ByFields">] [<CompiledName "ByFields">]
let byFields tableName howMatched fields = let byFields tableName howMatched fields =
$"SELECT COUNT(*) AS it FROM %s{tableName} WHERE {whereByFields howMatched fields}" Query.Count.byFields whereByFields tableName howMatched fields
/// Query to count matching documents using a text comparison on a JSON field /// Query to count matching documents using a text comparison on a JSON field
[<CompiledName "ByField">] [<CompiledName "ByField">]
@ -197,12 +192,12 @@ module Query =
/// Query to count matching documents using a JSON containment query (@>) /// Query to count matching documents using a JSON containment query (@>)
[<CompiledName "ByContains">] [<CompiledName "ByContains">]
let byContains tableName = let byContains tableName =
$"""SELECT COUNT(*) AS it FROM %s{tableName} WHERE {whereDataContains "@criteria"}""" $"""{Query.Count.all tableName} WHERE {whereDataContains "@criteria"}"""
/// Query to count matching documents using a JSON Path match (@?) /// Query to count matching documents using a JSON Path match (@?)
[<CompiledName "ByJsonPath">] [<CompiledName "ByJsonPath">]
let byJsonPath tableName = let byJsonPath tableName =
$"""SELECT COUNT(*) AS it FROM %s{tableName} WHERE {whereJsonPathMatches "@path"}""" $"""{Query.Count.all tableName} WHERE {whereJsonPathMatches "@path"}"""
/// Queries for determining document existence /// Queries for determining document existence
module Exists = module Exists =
@ -445,7 +440,7 @@ module WithProps =
[<CompiledName "EnsureTable">] [<CompiledName "EnsureTable">]
let ensureTable name sqlProps = backgroundTask { let ensureTable name sqlProps = backgroundTask {
do! Custom.nonQuery (Query.Definition.ensureTable name) [] sqlProps do! Custom.nonQuery (Query.Definition.ensureTable name) [] sqlProps
do! Custom.nonQuery (Query.Definition.ensureKey name) [] sqlProps do! Custom.nonQuery (Query.Definition.ensureKey name PostgreSQL) [] sqlProps
} }
/// Create an index on documents in the specified table /// Create an index on documents in the specified table
@ -456,7 +451,7 @@ module WithProps =
/// Create an index on field(s) within documents in the specified table /// Create an index on field(s) within documents in the specified table
[<CompiledName "EnsureFieldIndex">] [<CompiledName "EnsureFieldIndex">]
let ensureFieldIndex tableName indexName fields sqlProps = let ensureFieldIndex tableName indexName fields sqlProps =
Custom.nonQuery (Query.Definition.ensureIndexOn tableName indexName fields) [] sqlProps Custom.nonQuery (Query.Definition.ensureIndexOn tableName indexName fields PostgreSQL) [] sqlProps
/// Commands to add documents /// Commands to add documents
[<AutoOpen>] [<AutoOpen>]

View File

@ -38,11 +38,11 @@ module Query =
fields fields
|> Seq.map (fun it -> |> Seq.map (fun it ->
match it.Op with match it.Op with
| EX | NEX -> $"{it.SqlitePath} {it.Op}" | EX | NEX -> $"{it.Path SQLite} {it.Op}"
| BT -> | BT ->
let p = name.Derive it.ParameterName let p = name.Derive it.ParameterName
$"{it.SqlitePath} {it.Op} {p}min AND {p}max" $"{it.Path SQLite} {it.Op} {p}min AND {p}max"
| _ -> $"{it.SqlitePath} {it.Op} {name.Derive it.ParameterName}") | _ -> $"{it.Path SQLite} {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 a comparison on a field in a JSON document /// Create a WHERE clause fragment to implement a comparison on a field in a JSON document
@ -72,15 +72,10 @@ module Query =
/// Queries for counting documents /// Queries for counting documents
module Count = module Count =
/// Query to count all documents in a table
[<CompiledName "All">]
let all tableName =
$"SELECT COUNT(*) AS it FROM %s{tableName}"
/// Query to count matching documents using a text comparison on JSON fields /// Query to count matching documents using a text comparison on JSON fields
[<CompiledName "ByFields">] [<CompiledName "ByFields">]
let byFields tableName howMatched fields = let byFields tableName howMatched fields =
$"SELECT COUNT(*) AS it FROM %s{tableName} WHERE {whereByFields howMatched fields}" Query.Count.byFields whereByFields tableName howMatched fields
/// Query to count matching documents using a text comparison on a JSON field /// Query to count matching documents using a text comparison on a JSON field
[<CompiledName "ByField">] [<CompiledName "ByField">]
@ -361,13 +356,13 @@ module WithConn =
[<CompiledName "EnsureTable">] [<CompiledName "EnsureTable">]
let ensureTable name conn = backgroundTask { let ensureTable name conn = backgroundTask {
do! Custom.nonQuery (Query.Definition.ensureTable name) [] conn do! Custom.nonQuery (Query.Definition.ensureTable name) [] conn
do! Custom.nonQuery (Query.Definition.ensureKey name) [] conn do! Custom.nonQuery (Query.Definition.ensureKey name SQLite) [] conn
} }
/// Create an index on a document table /// Create an index on a document table
[<CompiledName "EnsureFieldIndex">] [<CompiledName "EnsureFieldIndex">]
let ensureFieldIndex tableName indexName fields conn = let ensureFieldIndex tableName indexName fields conn =
Custom.nonQuery (Query.Definition.ensureIndexOn tableName indexName fields) [] conn Custom.nonQuery (Query.Definition.ensureIndexOn tableName indexName fields SQLite) [] conn
/// Insert a new document /// Insert a new document
[<CompiledName "Insert">] [<CompiledName "Insert">]

View File

@ -185,50 +185,54 @@ public static class CommonCSharpTests
Expect.isSome(field.Qualifier, "The table qualifier should have been filled"); Expect.isSome(field.Qualifier, "The table qualifier should have been filled");
Expect.equal("joe", field.Qualifier.Value, "The table qualifier is incorrect"); Expect.equal("joe", field.Qualifier.Value, "The table qualifier is incorrect");
}), }),
TestList("PgSqlPath", TestList("Path",
[ [
TestCase("succeeds for a single field with no qualifier", () => TestCase("succeeds for a PostgreSQL single field with no qualifier", () =>
{ {
var field = Field.GE("SomethingCool", 18); var field = Field.GE("SomethingCool", 18);
Expect.equal("data->>'SomethingCool'", field.PgSqlPath, "The PostgreSQL path is incorrect"); Expect.equal("data->>'SomethingCool'", field.Path(Dialect.PostgreSQL),
"The PostgreSQL path is incorrect");
}), }),
TestCase("succeeds for a single field with a qualifier", () => TestCase("succeeds for a PostgreSQL single field with a qualifier", () =>
{ {
var field = Field.LT("SomethingElse", 9).WithQualifier("this"); var field = Field.LT("SomethingElse", 9).WithQualifier("this");
Expect.equal("this.data->>'SomethingElse'", field.PgSqlPath, "The PostgreSQL path is incorrect"); Expect.equal("this.data->>'SomethingElse'", field.Path(Dialect.PostgreSQL),
"The PostgreSQL path is incorrect");
}), }),
TestCase("succeeds for a nested field with no qualifier", () => TestCase("succeeds for a PostgreSQL nested field with no qualifier", () =>
{ {
var field = Field.EQ("My.Nested.Field", "howdy"); var field = Field.EQ("My.Nested.Field", "howdy");
Expect.equal("data#>>'{My,Nested,Field}'", field.PgSqlPath, "The PostgreSQL path is incorrect"); Expect.equal("data#>>'{My,Nested,Field}'", field.Path(Dialect.PostgreSQL),
"The PostgreSQL path is incorrect");
}), }),
TestCase("succeeds for a nested field with a qualifier", () => TestCase("succeeds for a PostgreSQL nested field with a qualifier", () =>
{ {
var field = Field.EQ("Nest.Away", "doc").WithQualifier("bird"); var field = Field.EQ("Nest.Away", "doc").WithQualifier("bird");
Expect.equal("bird.data#>>'{Nest,Away}'", field.PgSqlPath, "The PostgreSQL path is incorrect"); Expect.equal("bird.data#>>'{Nest,Away}'", field.Path(Dialect.PostgreSQL),
}) "The PostgreSQL path is incorrect");
]), }),
TestList("SqlitePath", TestCase("succeeds for a SQLite single field with no qualifier", () =>
[
TestCase("succeeds for a single field with no qualifier", () =>
{ {
var field = Field.GE("SomethingCool", 18); var field = Field.GE("SomethingCool", 18);
Expect.equal("data->>'SomethingCool'", field.SqlitePath, "The SQLite path is incorrect"); Expect.equal("data->>'SomethingCool'", field.Path(Dialect.SQLite), "The SQLite path is incorrect");
}), }),
TestCase("succeeds for a single field with a qualifier", () => TestCase("succeeds for a SQLite single field with a qualifier", () =>
{ {
var field = Field.LT("SomethingElse", 9).WithQualifier("this"); var field = Field.LT("SomethingElse", 9).WithQualifier("this");
Expect.equal("this.data->>'SomethingElse'", field.SqlitePath, "The SQLite path is incorrect"); Expect.equal("this.data->>'SomethingElse'", field.Path(Dialect.SQLite),
"The SQLite path is incorrect");
}), }),
TestCase("succeeds for a nested field with no qualifier", () => TestCase("succeeds for a SQLite nested field with no qualifier", () =>
{ {
var field = Field.EQ("My.Nested.Field", "howdy"); var field = Field.EQ("My.Nested.Field", "howdy");
Expect.equal("data->>'My'->>'Nested'->>'Field'", field.SqlitePath, "The SQLite path is incorrect"); Expect.equal("data->>'My'->>'Nested'->>'Field'", field.Path(Dialect.SQLite),
"The SQLite path is incorrect");
}), }),
TestCase("succeeds for a nested field with a qualifier", () => TestCase("succeeds for a SQLite nested field with a qualifier", () =>
{ {
var field = Field.EQ("Nest.Away", "doc").WithQualifier("bird"); var field = Field.EQ("Nest.Away", "doc").WithQualifier("bird");
Expect.equal("bird.data->>'Nest'->>'Away'", field.SqlitePath, "The SQLite path is incorrect"); Expect.equal("bird.data->>'Nest'->>'Away'", field.Path(Dialect.SQLite),
"The SQLite path is incorrect");
}) })
]) ])
]), ]),
@ -251,14 +255,14 @@ public static class CommonCSharpTests
[ [
TestCase("succeeds when a schema is present", () => TestCase("succeeds when a schema is present", () =>
{ {
Expect.equal(Query.Definition.EnsureKey("test.table"), Expect.equal(Query.Definition.EnsureKey("test.table", Dialect.SQLite),
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data ->> 'Id'))", "CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data->>'Id'))",
"CREATE INDEX for key statement with schema not constructed correctly"); "CREATE INDEX for key statement with schema not constructed correctly");
}), }),
TestCase("succeeds when a schema is not present", () => TestCase("succeeds when a schema is not present", () =>
{ {
Expect.equal(Query.Definition.EnsureKey("table"), Expect.equal(Query.Definition.EnsureKey("table", Dialect.PostgreSQL),
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON table ((data ->> 'Id'))", "CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON table ((data->>'Id'))",
"CREATE INDEX for key statement without schema not constructed correctly"); "CREATE INDEX for key statement without schema not constructed correctly");
}) })
]), ]),
@ -266,9 +270,9 @@ public static class CommonCSharpTests
{ {
Expect.equal( Expect.equal(
Query.Definition.EnsureIndexOn("test.table", "gibberish", Query.Definition.EnsureIndexOn("test.table", "gibberish",
new[] { "taco", "guac DESC", "salsa ASC" }), new[] { "taco", "guac DESC", "salsa ASC" }, Dialect.SQLite),
"CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table " "CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table "
+ "((data ->> 'taco'), (data ->> 'guac') DESC, (data ->> 'salsa') ASC)", + "((data->>'taco'), (data->>'guac') DESC, (data->>'salsa') ASC)",
"CREATE INDEX for multiple field statement incorrect"); "CREATE INDEX for multiple field statement incorrect");
}) })
]), ]),
@ -281,7 +285,15 @@ public static class CommonCSharpTests
Expect.equal(Query.Save("tbl"), Expect.equal(Query.Save("tbl"),
"INSERT INTO tbl VALUES (@data) ON CONFLICT ((data->>'Id')) DO UPDATE SET data = EXCLUDED.data", "INSERT INTO tbl VALUES (@data) ON CONFLICT ((data->>'Id')) DO UPDATE SET data = EXCLUDED.data",
"INSERT ON CONFLICT UPDATE statement not correct"); "INSERT ON CONFLICT UPDATE statement not correct");
}) }),
TestList("Count",
[
TestCase("All succeeds", () =>
{
Expect.equal(Query.Count.All("a_table"), $"SELECT COUNT(*) AS it FROM a_table",
"Count query not correct");
}),
])
]) ])
]); ]);
} }

View File

@ -287,11 +287,6 @@ public static class PostgresCSharpTests
}), }),
TestList("Count", TestList("Count",
[ [
TestCase("All succeeds", () =>
{
Expect.equal(Postgres.Query.Count.All(PostgresDb.TableName),
$"SELECT COUNT(*) AS it FROM {PostgresDb.TableName}", "Count query not correct");
}),
TestCase("ByFields succeeds", () => TestCase("ByFields succeeds", () =>
{ {
Expect.equal( Expect.equal(

View File

@ -1,3 +1,4 @@
using BitBadger.Documents.Postgres;
using Npgsql; using Npgsql;
using Npgsql.FSharp; using Npgsql.FSharp;
using ThrowawayDb.Postgres; using ThrowawayDb.Postgres;
@ -131,7 +132,7 @@ public static class PostgresDb
var sqlProps = Sql.connect(database.ConnectionString); var sqlProps = Sql.connect(database.ConnectionString);
Sql.executeNonQuery(Sql.query(Postgres.Query.Definition.EnsureTable(TableName), sqlProps)); Sql.executeNonQuery(Sql.query(Postgres.Query.Definition.EnsureTable(TableName), sqlProps));
Sql.executeNonQuery(Sql.query(Query.Definition.EnsureKey(TableName), sqlProps)); Sql.executeNonQuery(Sql.query(Query.Definition.EnsureKey(TableName, Dialect.PostgreSQL), sqlProps));
Postgres.Configuration.UseDataSource(MkDataSource(database.ConnectionString)); Postgres.Configuration.UseDataSource(MkDataSource(database.ConnectionString));

View File

@ -99,11 +99,6 @@ public static class SqliteCSharpTests
}), }),
TestList("Count", TestList("Count",
[ [
TestCase("All succeeds", () =>
{
Expect.equal(Sqlite.Query.Count.All("tbl"), "SELECT COUNT(*) AS it FROM tbl",
"Count query not correct");
}),
#pragma warning disable CS0618 #pragma warning disable CS0618
TestCase("ByField succeeds", () => TestCase("ByField succeeds", () =>
{ {

View File

@ -119,40 +119,39 @@ let all =
Expect.isSome field.Qualifier "The table qualifier should have been filled" Expect.isSome field.Qualifier "The table qualifier should have been filled"
Expect.equal "joe" field.Qualifier.Value "The table qualifier is incorrect" Expect.equal "joe" field.Qualifier.Value "The table qualifier is incorrect"
} }
testList "PgSqlPath" [ testList "Path" [
test "succeeds for a single field with no qualifier" { test "succeeds for a PostgreSQL single field with no qualifier" {
let field = Field.GE "SomethingCool" 18 let field = Field.GE "SomethingCool" 18
Expect.equal "data->>'SomethingCool'" field.PgSqlPath "The PostgreSQL path is incorrect" Expect.equal "data->>'SomethingCool'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect"
} }
test "succeeds for a single field with a qualifier" { test "succeeds for a PostgreSQL single field with a qualifier" {
let field = { Field.LT "SomethingElse" 9 with Qualifier = Some "this" } let field = { Field.LT "SomethingElse" 9 with Qualifier = Some "this" }
Expect.equal "this.data->>'SomethingElse'" field.PgSqlPath "The PostgreSQL path is incorrect" Expect.equal
"this.data->>'SomethingElse'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect"
} }
test "succeeds for a nested field with no qualifier" { test "succeeds for a PostgreSQL nested field with no qualifier" {
let field = Field.EQ "My.Nested.Field" "howdy" let field = Field.EQ "My.Nested.Field" "howdy"
Expect.equal "data#>>'{My,Nested,Field}'" field.PgSqlPath "The PostgreSQL path is incorrect" Expect.equal "data#>>'{My,Nested,Field}'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect"
} }
test "succeeds for a nested field with a qualifier" { test "succeeds for a PostgreSQL nested field with a qualifier" {
let field = { Field.EQ "Nest.Away" "doc" with Qualifier = Some "bird" } let field = { Field.EQ "Nest.Away" "doc" with Qualifier = Some "bird" }
Expect.equal "bird.data#>>'{Nest,Away}'" field.PgSqlPath "The PostgreSQL path is incorrect" Expect.equal "bird.data#>>'{Nest,Away}'" (field.Path PostgreSQL) "The PostgreSQL path is incorrect"
} }
] test "succeeds for a SQLite single field with no qualifier" {
testList "SqlitePath" [
test "succeeds for a single field with no qualifier" {
let field = Field.GE "SomethingCool" 18 let field = Field.GE "SomethingCool" 18
Expect.equal "data->>'SomethingCool'" field.SqlitePath "The SQLite path is incorrect" Expect.equal "data->>'SomethingCool'" (field.Path SQLite) "The SQLite path is incorrect"
} }
test "succeeds for a single field with a qualifier" { test "succeeds for a SQLite single field with a qualifier" {
let field = { Field.LT "SomethingElse" 9 with Qualifier = Some "this" } let field = { Field.LT "SomethingElse" 9 with Qualifier = Some "this" }
Expect.equal "this.data->>'SomethingElse'" field.SqlitePath "The SQLite path is incorrect" Expect.equal "this.data->>'SomethingElse'" (field.Path SQLite) "The SQLite path is incorrect"
} }
test "succeeds for a nested field with no qualifier" { test "succeeds for a SQLite nested field with no qualifier" {
let field = Field.EQ "My.Nested.Field" "howdy" let field = Field.EQ "My.Nested.Field" "howdy"
Expect.equal "data->>'My'->>'Nested'->>'Field'" field.SqlitePath "The SQLite path is incorrect" Expect.equal "data->>'My'->>'Nested'->>'Field'" (field.Path SQLite) "The SQLite path is incorrect"
} }
test "succeeds for a nested field with a qualifier" { test "succeeds for a SQLite nested field with a qualifier" {
let field = { Field.EQ "Nest.Away" "doc" with Qualifier = Some "bird" } let field = { Field.EQ "Nest.Away" "doc" with Qualifier = Some "bird" }
Expect.equal "bird.data->>'Nest'->>'Away'" field.SqlitePath "The SQLite path is incorrect" Expect.equal "bird.data->>'Nest'->>'Away'" (field.Path SQLite) "The SQLite path is incorrect"
} }
] ]
] ]
@ -184,22 +183,23 @@ let all =
testList "ensureKey" [ testList "ensureKey" [
test "succeeds when a schema is present" { test "succeeds when a schema is present" {
Expect.equal Expect.equal
(Query.Definition.ensureKey "test.table") (Query.Definition.ensureKey "test.table" PostgreSQL)
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data ->> 'Id'))" "CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data->>'Id'))"
"CREATE INDEX for key statement with schema not constructed correctly" "CREATE INDEX for key statement with schema not constructed correctly"
} }
test "succeeds when a schema is not present" { test "succeeds when a schema is not present" {
Expect.equal Expect.equal
(Query.Definition.ensureKey "table") (Query.Definition.ensureKey "table" SQLite)
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON table ((data ->> 'Id'))" "CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON table ((data->>'Id'))"
"CREATE INDEX for key statement without schema not constructed correctly" "CREATE INDEX for key statement without schema not constructed correctly"
} }
] ]
test "ensureIndexOn succeeds for multiple fields and directions" { test "ensureIndexOn succeeds for multiple fields and directions" {
Expect.equal Expect.equal
(Query.Definition.ensureIndexOn "test.table" "gibberish" [ "taco"; "guac DESC"; "salsa ASC" ]) (Query.Definition.ensureIndexOn
"test.table" "gibberish" [ "taco"; "guac DESC"; "salsa ASC" ] PostgreSQL)
([ "CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table " ([ "CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table "
"((data ->> 'taco'), (data ->> 'guac') DESC, (data ->> 'salsa') ASC)" ] "((data->>'taco'), (data->>'guac') DESC, (data->>'salsa') ASC)" ]
|> String.concat "") |> String.concat "")
"CREATE INDEX for multiple field statement incorrect" "CREATE INDEX for multiple field statement incorrect"
} }
@ -213,6 +213,18 @@ let all =
$"INSERT INTO {tbl} VALUES (@data) ON CONFLICT ((data->>'Id')) DO UPDATE SET data = EXCLUDED.data" $"INSERT INTO {tbl} VALUES (@data) ON CONFLICT ((data->>'Id')) DO UPDATE SET data = EXCLUDED.data"
"INSERT ON CONFLICT UPDATE statement not correct" "INSERT ON CONFLICT UPDATE statement not correct"
} }
testList "Count" [
test "all succeeds" {
Expect.equal (Query.Count.all "a_table") "SELECT COUNT(*) AS it FROM a_table"
"Count query not correct"
}
test "byFields succeeds" {
let test = fun _ _ -> "howdy"
Expect.equal
(Query.Count.byFields test "over_here" Any [])
"SELECT COUNT(*) AS it FROM over_here WHERE howdy"
"Count by fields query not correct"
}
]
] ]
] ]

View File

@ -259,12 +259,6 @@ let unitTests =
Expect.equal (Query.whereJsonPathMatches "@path") "data @? @path::jsonpath" "WHERE clause not correct" Expect.equal (Query.whereJsonPathMatches "@path") "data @? @path::jsonpath" "WHERE clause not correct"
} }
testList "Count" [ testList "Count" [
test "all succeeds" {
Expect.equal
(Query.Count.all PostgresDb.TableName)
$"SELECT COUNT(*) AS it FROM {PostgresDb.TableName}"
"Count query not correct"
}
test "byFields succeeds" { test "byFields succeeds" {
Expect.equal Expect.equal
(Query.Count.byFields "tbl" All [ Field.EQ "thatField" 0; Field.EQ "anotherField" 8]) (Query.Count.byFields "tbl" All [ Field.EQ "thatField" 0; Field.EQ "anotherField" 8])
@ -664,7 +658,7 @@ let integrationTests =
let! theCount = Count.all PostgresDb.TableName let! theCount = Count.all PostgresDb.TableName
Expect.equal theCount 5 "There should have been 5 matching documents" Expect.equal theCount 5 "There should have been 5 matching documents"
} }
testList "byFields" [ ptestList "byFields" [
testTask "succeeds when items are found" { testTask "succeeds when items are found" {
use db = PostgresDb.BuildDb() use db = PostgresDb.BuildDb()
do! loadDocs () do! loadDocs ()
@ -818,7 +812,7 @@ let integrationTests =
Expect.equal results [] "There should have been no documents returned" Expect.equal results [] "There should have been no documents returned"
} }
] ]
ftestList "byId" [ testList "byId" [
testTask "succeeds when a document is found" { testTask "succeeds when a document is found" {
use db = PostgresDb.BuildDb() use db = PostgresDb.BuildDb()
do! loadDocs () do! loadDocs ()
@ -845,7 +839,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"
} }
testTask "succeeds when documents are not found" { ptestTask "succeeds when documents are not found" {
use db = PostgresDb.BuildDb() use db = PostgresDb.BuildDb()
do! loadDocs () do! loadDocs ()

View File

@ -88,9 +88,6 @@ let unitTests =
"UPDATE full statement not correct" "UPDATE full statement not correct"
} }
testList "Count" [ testList "Count" [
test "all succeeds" {
Expect.equal (Query.Count.all "tbl") $"SELECT COUNT(*) AS it FROM tbl" "Count query not correct"
}
test "byField succeeds" { test "byField succeeds" {
Expect.equal Expect.equal
(Query.Count.byField "tbl" (Field.EQ "thatField" 0)) (Query.Count.byField "tbl" (Field.EQ "thatField" 0))