Version 4 #6
@ -35,6 +35,12 @@ type Op =
|
||||
| NEX -> "IS NULL"
|
||||
|
||||
|
||||
/// The dialect in which a command should be rendered
|
||||
[<Struct>]
|
||||
type Dialect =
|
||||
| PostgreSQL
|
||||
| SQLite
|
||||
|
||||
/// Criteria for a field WHERE clause
|
||||
type Field = {
|
||||
/// The name of the field
|
||||
@ -89,6 +95,16 @@ type Field = {
|
||||
static member NEX name =
|
||||
{ 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
|
||||
member this.WithParameterName name =
|
||||
{ this with ParameterName = Some name }
|
||||
@ -97,17 +113,9 @@ type Field = {
|
||||
member this.WithQualifier alias =
|
||||
{ this with Qualifier = Some alias }
|
||||
|
||||
/// Get the path for this field in PostgreSQL's format
|
||||
member this.PgSqlPath =
|
||||
(this.Qualifier |> Option.map (fun q -> $"{q}.data") |> Option.defaultValue "data")
|
||||
+ 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}'"
|
||||
/// 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
|
||||
|
||||
|
||||
/// 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
|
||||
[<CompiledName "EnsureIndexOn">]
|
||||
let ensureIndexOn tableName indexName (fields: string seq) =
|
||||
let ensureIndexOn tableName indexName (fields: string seq) dialect =
|
||||
let _, tbl = splitSchemaAndTable tableName
|
||||
let jsonFields =
|
||||
fields
|
||||
@ -229,14 +237,14 @@ 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]}"
|
||||
$"(data ->> '{fieldName}'){direction}")
|
||||
$"({Field.NameToPath fieldName dialect}){direction}")
|
||||
|> String.concat ", "
|
||||
$"CREATE INDEX IF NOT EXISTS idx_{tbl}_%s{indexName} ON {tableName} ({jsonFields})"
|
||||
|
||||
/// SQL statement to create a key index for a document table
|
||||
[<CompiledName "EnsureKey">]
|
||||
let ensureKey tableName =
|
||||
(ensureIndexOn tableName "key" [ Configuration.idField () ]).Replace("INDEX", "UNIQUE INDEX")
|
||||
let ensureKey tableName dialect =
|
||||
(ensureIndexOn tableName "key" [ Configuration.idField () ] dialect).Replace("INDEX", "UNIQUE INDEX")
|
||||
|
||||
/// Query to insert a document
|
||||
[<CompiledName "Insert">]
|
||||
@ -249,4 +257,16 @@ module Query =
|
||||
sprintf
|
||||
"INSERT INTO %s VALUES (@data) ON CONFLICT ((data->>'%s')) DO UPDATE SET data = EXCLUDED.data"
|
||||
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}"
|
||||
|
@ -120,18 +120,18 @@ module Query =
|
||||
fields
|
||||
|> Seq.map (fun it ->
|
||||
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 path, value =
|
||||
match it.Op with
|
||||
| BT -> $"{p}min AND {p}max", (it.Value :?> obj list)[0]
|
||||
| _ -> p, it.Value
|
||||
printfn $"%A{value}"
|
||||
match value with
|
||||
| :? int8 | :? uint8 | :? int16 | :? uint16 | :? int | :? uint32 | :? int64 | :? uint64
|
||||
| :? decimal | :? single | :? double -> $"({it.PgSqlPath})::numeric {it.Op} {path}"
|
||||
| _ -> $"{it.PgSqlPath} {it.Op} {path}")
|
||||
| :? decimal | :? single | :? double -> $"({it.Path PostgreSQL})::numeric {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 ")
|
||||
|
||||
/// 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
|
||||
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 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
|
||||
[<CompiledName "ByField">]
|
||||
@ -197,12 +192,12 @@ module Query =
|
||||
/// Query to count matching documents using a JSON containment query (@>)
|
||||
[<CompiledName "ByContains">]
|
||||
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 (@?)
|
||||
[<CompiledName "ByJsonPath">]
|
||||
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
|
||||
module Exists =
|
||||
@ -445,7 +440,7 @@ module WithProps =
|
||||
[<CompiledName "EnsureTable">]
|
||||
let ensureTable name sqlProps = backgroundTask {
|
||||
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
|
||||
@ -456,7 +451,7 @@ module WithProps =
|
||||
/// Create an index on field(s) within documents in the specified table
|
||||
[<CompiledName "EnsureFieldIndex">]
|
||||
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
|
||||
[<AutoOpen>]
|
||||
|
@ -38,11 +38,11 @@ module Query =
|
||||
fields
|
||||
|> Seq.map (fun it ->
|
||||
match it.Op with
|
||||
| EX | NEX -> $"{it.SqlitePath} {it.Op}"
|
||||
| EX | NEX -> $"{it.Path SQLite} {it.Op}"
|
||||
| BT ->
|
||||
let p = name.Derive it.ParameterName
|
||||
$"{it.SqlitePath} {it.Op} {p}min AND {p}max"
|
||||
| _ -> $"{it.SqlitePath} {it.Op} {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 ")
|
||||
|
||||
/// 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
|
||||
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 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
|
||||
[<CompiledName "ByField">]
|
||||
@ -361,13 +356,13 @@ module WithConn =
|
||||
[<CompiledName "EnsureTable">]
|
||||
let ensureTable name conn = backgroundTask {
|
||||
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
|
||||
[<CompiledName "EnsureFieldIndex">]
|
||||
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
|
||||
[<CompiledName "Insert">]
|
||||
|
@ -185,50 +185,54 @@ public static class CommonCSharpTests
|
||||
Expect.isSome(field.Qualifier, "The table qualifier should have been filled");
|
||||
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);
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
Expect.equal("bird.data#>>'{Nest,Away}'", field.PgSqlPath, "The PostgreSQL path is incorrect");
|
||||
})
|
||||
]),
|
||||
TestList("SqlitePath",
|
||||
[
|
||||
TestCase("succeeds for a single field with no qualifier", () =>
|
||||
Expect.equal("bird.data#>>'{Nest,Away}'", field.Path(Dialect.PostgreSQL),
|
||||
"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.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");
|
||||
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");
|
||||
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");
|
||||
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", () =>
|
||||
{
|
||||
Expect.equal(Query.Definition.EnsureKey("test.table"),
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data ->> 'Id'))",
|
||||
Expect.equal(Query.Definition.EnsureKey("test.table", Dialect.SQLite),
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data->>'Id'))",
|
||||
"CREATE INDEX for key statement with schema not constructed correctly");
|
||||
}),
|
||||
TestCase("succeeds when a schema is not present", () =>
|
||||
{
|
||||
Expect.equal(Query.Definition.EnsureKey("table"),
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON table ((data ->> 'Id'))",
|
||||
Expect.equal(Query.Definition.EnsureKey("table", Dialect.PostgreSQL),
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON table ((data->>'Id'))",
|
||||
"CREATE INDEX for key statement without schema not constructed correctly");
|
||||
})
|
||||
]),
|
||||
@ -266,9 +270,9 @@ public static class CommonCSharpTests
|
||||
{
|
||||
Expect.equal(
|
||||
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 "
|
||||
+ "((data ->> 'taco'), (data ->> 'guac') DESC, (data ->> 'salsa') ASC)",
|
||||
+ "((data->>'taco'), (data->>'guac') DESC, (data->>'salsa') ASC)",
|
||||
"CREATE INDEX for multiple field statement incorrect");
|
||||
})
|
||||
]),
|
||||
@ -281,7 +285,15 @@ public static class CommonCSharpTests
|
||||
Expect.equal(Query.Save("tbl"),
|
||||
"INSERT INTO tbl VALUES (@data) ON CONFLICT ((data->>'Id')) DO UPDATE SET data = EXCLUDED.data",
|
||||
"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");
|
||||
}),
|
||||
])
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
@ -287,11 +287,6 @@ public static class PostgresCSharpTests
|
||||
}),
|
||||
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", () =>
|
||||
{
|
||||
Expect.equal(
|
||||
|
@ -1,3 +1,4 @@
|
||||
using BitBadger.Documents.Postgres;
|
||||
using Npgsql;
|
||||
using Npgsql.FSharp;
|
||||
using ThrowawayDb.Postgres;
|
||||
@ -131,7 +132,7 @@ public static class PostgresDb
|
||||
var sqlProps = Sql.connect(database.ConnectionString);
|
||||
|
||||
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));
|
||||
|
||||
|
@ -99,11 +99,6 @@ public static class SqliteCSharpTests
|
||||
}),
|
||||
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
|
||||
TestCase("ByField succeeds", () =>
|
||||
{
|
||||
|
@ -119,40 +119,39 @@ let all =
|
||||
Expect.isSome field.Qualifier "The table qualifier should have been filled"
|
||||
Expect.equal "joe" field.Qualifier.Value "The table qualifier is incorrect"
|
||||
}
|
||||
testList "PgSqlPath" [
|
||||
test "succeeds for a single field with no qualifier" {
|
||||
testList "Path" [
|
||||
test "succeeds for a PostgreSQL single field with no qualifier" {
|
||||
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" }
|
||||
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"
|
||||
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" }
|
||||
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"
|
||||
}
|
||||
]
|
||||
testList "SqlitePath" [
|
||||
test "succeeds for a single field with no qualifier" {
|
||||
test "succeeds for a SQLite single field with no qualifier" {
|
||||
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" }
|
||||
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"
|
||||
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" }
|
||||
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" [
|
||||
test "succeeds when a schema is present" {
|
||||
Expect.equal
|
||||
(Query.Definition.ensureKey "test.table")
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data ->> 'Id'))"
|
||||
(Query.Definition.ensureKey "test.table" PostgreSQL)
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data->>'Id'))"
|
||||
"CREATE INDEX for key statement with schema not constructed correctly"
|
||||
}
|
||||
test "succeeds when a schema is not present" {
|
||||
Expect.equal
|
||||
(Query.Definition.ensureKey "table")
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON table ((data ->> 'Id'))"
|
||||
(Query.Definition.ensureKey "table" SQLite)
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON table ((data->>'Id'))"
|
||||
"CREATE INDEX for key statement without schema not constructed correctly"
|
||||
}
|
||||
]
|
||||
test "ensureIndexOn succeeds for multiple fields and directions" {
|
||||
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 "
|
||||
"((data ->> 'taco'), (data ->> 'guac') DESC, (data ->> 'salsa') ASC)" ]
|
||||
"((data->>'taco'), (data->>'guac') DESC, (data->>'salsa') ASC)" ]
|
||||
|> String.concat "")
|
||||
"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 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"
|
||||
}
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
|
@ -259,12 +259,6 @@ let unitTests =
|
||||
Expect.equal (Query.whereJsonPathMatches "@path") "data @? @path::jsonpath" "WHERE clause not correct"
|
||||
}
|
||||
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" {
|
||||
Expect.equal
|
||||
(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
|
||||
Expect.equal theCount 5 "There should have been 5 matching documents"
|
||||
}
|
||||
testList "byFields" [
|
||||
ptestList "byFields" [
|
||||
testTask "succeeds when items are found" {
|
||||
use db = PostgresDb.BuildDb()
|
||||
do! loadDocs ()
|
||||
@ -818,7 +812,7 @@ let integrationTests =
|
||||
Expect.equal results [] "There should have been no documents returned"
|
||||
}
|
||||
]
|
||||
ftestList "byId" [
|
||||
testList "byId" [
|
||||
testTask "succeeds when a document is found" {
|
||||
use db = PostgresDb.BuildDb()
|
||||
do! loadDocs ()
|
||||
@ -845,7 +839,7 @@ let integrationTests =
|
||||
PostgresDb.TableName All [ Field.EQ "Value" "purple"; Field.EX "Sub" ]
|
||||
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()
|
||||
do! loadDocs ()
|
||||
|
||||
|
@ -88,9 +88,6 @@ let unitTests =
|
||||
"UPDATE full statement not correct"
|
||||
}
|
||||
testList "Count" [
|
||||
test "all succeeds" {
|
||||
Expect.equal (Query.Count.all "tbl") $"SELECT COUNT(*) AS it FROM tbl" "Count query not correct"
|
||||
}
|
||||
test "byField succeeds" {
|
||||
Expect.equal
|
||||
(Query.Count.byField "tbl" (Field.EQ "thatField" 0))
|
||||
|
Loading…
Reference in New Issue
Block a user