v3 RC1 #1
|
@ -92,10 +92,12 @@ module Query =
|
|||
$"CREATE INDEX IF NOT EXISTS idx_{tableName}_document ON {name} USING GIN (data{extraOps})"
|
||||
|
||||
/// Create a WHERE clause fragment to implement a @> (JSON contains) condition
|
||||
[<CompiledName "WhereDataContains">]
|
||||
let whereDataContains paramName =
|
||||
$"data @> %s{paramName}"
|
||||
|
||||
/// Create a WHERE clause fragment to implement a @? (JSON Path match) condition
|
||||
[<CompiledName "WhereJsonPathMatches">]
|
||||
let whereJsonPathMatches paramName =
|
||||
$"data @? %s{paramName}::jsonpath"
|
||||
|
||||
|
@ -103,10 +105,12 @@ module Query =
|
|||
module Count =
|
||||
|
||||
/// 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 to count matching documents using a JSON Path match (@?)
|
||||
[<CompiledName "ByJsonPath">]
|
||||
let byJsonPath tableName =
|
||||
$"""SELECT COUNT(*) AS it FROM %s{tableName} WHERE {whereJsonPathMatches "@path"}"""
|
||||
|
||||
|
@ -114,10 +118,12 @@ module Query =
|
|||
module Exists =
|
||||
|
||||
/// Query to determine if documents exist using a JSON containment query (@>)
|
||||
[<CompiledName "ByContains">]
|
||||
let byContains tableName =
|
||||
$"""SELECT EXISTS (SELECT 1 FROM %s{tableName} WHERE {whereDataContains "@criteria"}) AS it"""
|
||||
|
||||
/// Query to determine if documents exist using a JSON Path match (@?)
|
||||
[<CompiledName "ByJsonPath">]
|
||||
let byJsonPath tableName =
|
||||
$"""SELECT EXISTS (SELECT 1 FROM %s{tableName} WHERE {whereJsonPathMatches "@path"}) AS it"""
|
||||
|
||||
|
@ -125,10 +131,12 @@ module Query =
|
|||
module Find =
|
||||
|
||||
/// Query to retrieve documents using a JSON containment query (@>)
|
||||
[<CompiledName "ByContains">]
|
||||
let byContains tableName =
|
||||
$"""{Query.selectFromTable tableName} WHERE {whereDataContains "@criteria"}"""
|
||||
|
||||
/// Query to retrieve documents using a JSON Path match (@?)
|
||||
[<CompiledName "ByJsonPath">]
|
||||
let byJsonPath tableName =
|
||||
$"""{Query.selectFromTable tableName} WHERE {whereJsonPathMatches "@path"}"""
|
||||
|
||||
|
@ -136,18 +144,22 @@ module Query =
|
|||
module Update =
|
||||
|
||||
/// Query to update a document
|
||||
[<CompiledName "PartialById">]
|
||||
let partialById tableName =
|
||||
$"""UPDATE %s{tableName} SET data = data || @data WHERE {Query.whereById "@id"}"""
|
||||
|
||||
/// Query to update a document
|
||||
[<CompiledName "PartialByField">]
|
||||
let partialByField tableName fieldName op =
|
||||
$"""UPDATE %s{tableName} SET data = data || @data WHERE {Query.whereByField fieldName op "@field"}"""
|
||||
|
||||
/// Query to update partial documents matching a JSON containment query (@>)
|
||||
[<CompiledName "PartialByContains">]
|
||||
let partialByContains tableName =
|
||||
$"""UPDATE %s{tableName} SET data = data || @data WHERE {whereDataContains "@criteria"}"""
|
||||
|
||||
/// Query to update partial documents matching a JSON containment query (@>)
|
||||
[<CompiledName "PartialByJsonPath">]
|
||||
let partialByJsonPath tableName =
|
||||
$"""UPDATE %s{tableName} SET data = data || @data WHERE {whereJsonPathMatches "@path"}"""
|
||||
|
||||
|
@ -155,10 +167,12 @@ module Query =
|
|||
module Delete =
|
||||
|
||||
/// Query to delete documents using a JSON containment query (@>)
|
||||
[<CompiledName "ByContains">]
|
||||
let byContains tableName =
|
||||
$"""DELETE FROM %s{tableName} WHERE {whereDataContains "@criteria"}"""
|
||||
|
||||
/// Query to delete documents using a JSON Path match (@?)
|
||||
[<CompiledName "ByJsonPath">]
|
||||
let byJsonPath tableName =
|
||||
$"""DELETE FROM %s{tableName} WHERE {whereJsonPathMatches "@path"}"""
|
||||
|
||||
|
@ -168,18 +182,22 @@ module Query =
|
|||
module Results =
|
||||
|
||||
/// Create a domain item from a document, specifying the field in which the document is found
|
||||
[<CompiledName "FromDocument">]
|
||||
let fromDocument<'T> field (row: RowReader) : 'T =
|
||||
Configuration.serializer().Deserialize<'T>(row.string field)
|
||||
|
||||
/// Create a domain item from a document
|
||||
[<CompiledName "FromData">]
|
||||
let fromData<'T> row : 'T =
|
||||
fromDocument "data" row
|
||||
|
||||
/// Extract a count from the column "it"
|
||||
[<CompiledName "ToCount">]
|
||||
let toCount (row: RowReader) =
|
||||
row.int "it"
|
||||
|
||||
/// Extract a true/false value from the column "it"
|
||||
[<CompiledName "ToExists">]
|
||||
let toExists (row: RowReader) =
|
||||
row.bool "it"
|
||||
|
||||
|
|
180
src/Tests.CSharp/PostgresCSharpTests.cs
Normal file
180
src/Tests.CSharp/PostgresCSharpTests.cs
Normal file
|
@ -0,0 +1,180 @@
|
|||
using Expecto.CSharp;
|
||||
using Expecto;
|
||||
using BitBadger.Documents.Postgres;
|
||||
using Npgsql.FSharp;
|
||||
|
||||
namespace BitBadger.Documents.Tests.CSharp;
|
||||
|
||||
using static Runner;
|
||||
|
||||
/// <summary>
|
||||
/// C# tests for the PostgreSQL implementation of <tt>BitBadger.Documents</tt>
|
||||
/// </summary>
|
||||
public class PostgresCSharpTests
|
||||
{
|
||||
public static Test Unit =
|
||||
TestList("Unit", new[]
|
||||
{
|
||||
TestList("Parameters", new[]
|
||||
{
|
||||
TestCase("Id succeeds", () => {
|
||||
Expect.equal(Parameters.Id(88).Item1, "@id", "ID parameter not constructed correctly");
|
||||
}),
|
||||
TestCase("Json succeeds", () =>
|
||||
{
|
||||
Expect.equal(Parameters.Json("@test", new { Something = "good" }).Item1, "@test",
|
||||
"JSON parameter not constructed correctly");
|
||||
}),
|
||||
TestCase("Field succeeds", () =>
|
||||
{
|
||||
Expect.equal(Parameters.Field(242).Item1, "@field", "Field parameter not constructed correctly");
|
||||
}),
|
||||
TestCase("None succeeds", () => {
|
||||
Expect.isEmpty(Parameters.None, "The no-params sequence should be empty");
|
||||
})
|
||||
}),
|
||||
TestList("Query", new[]
|
||||
{
|
||||
TestList("Definition", new[]
|
||||
{
|
||||
TestCase("EnsureTable succeeds", () =>
|
||||
{
|
||||
Expect.equal(Postgres.Query.Definition.EnsureTable(PostgresDb.TableName),
|
||||
$"CREATE TABLE IF NOT EXISTS {PostgresDb.TableName} (data JSONB NOT NULL)",
|
||||
"CREATE TABLE statement not constructed correctly");
|
||||
}),
|
||||
TestCase("EnsureJsonIndex succeeds for full index", () =>
|
||||
{
|
||||
Expect.equal(Postgres.Query.Definition.EnsureJsonIndex("schema.tbl", DocumentIndex.Full),
|
||||
"CREATE INDEX IF NOT EXISTS idx_tbl_document ON schema.tbl USING GIN (data)",
|
||||
"CREATE INDEX statement not constructed correctly");
|
||||
}),
|
||||
TestCase("EnsureJsonIndex succeeds for JSONB Path Ops index", () =>
|
||||
{
|
||||
Expect.equal(
|
||||
Postgres.Query.Definition.EnsureJsonIndex(PostgresDb.TableName, DocumentIndex.Optimized),
|
||||
string.Format(
|
||||
"CREATE INDEX IF NOT EXISTS idx_{0}_document ON {0} USING GIN (data jsonb_path_ops)",
|
||||
PostgresDb.TableName),
|
||||
"CREATE INDEX statement not constructed correctly");
|
||||
})
|
||||
}),
|
||||
TestCase("WhereDataContains succeeds", () =>
|
||||
{
|
||||
Expect.equal(Postgres.Query.WhereDataContains("@test"), "data @> @test",
|
||||
"WHERE clause not correct");
|
||||
}),
|
||||
TestCase("WhereJsonPathMatches succeeds", () =>
|
||||
{
|
||||
Expect.equal(Postgres.Query.WhereJsonPathMatches("@path"), "data @? @path::jsonpath",
|
||||
"WHERE clause not correct");
|
||||
}),
|
||||
TestList("Count", new[]
|
||||
{
|
||||
TestCase("ByContains succeeds", () =>
|
||||
{
|
||||
Expect.equal(Postgres.Query.Count.ByContains(PostgresDb.TableName),
|
||||
$"SELECT COUNT(*) AS it FROM {PostgresDb.TableName} WHERE data @> @criteria",
|
||||
"JSON containment count query not correct");
|
||||
}),
|
||||
TestCase("ByJsonPath succeeds", () =>
|
||||
{
|
||||
Expect.equal(Postgres.Query.Count.ByJsonPath(PostgresDb.TableName),
|
||||
$"SELECT COUNT(*) AS it FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath",
|
||||
"JSON Path match count query not correct");
|
||||
})
|
||||
}),
|
||||
TestList("Exists", new[]
|
||||
{
|
||||
TestCase("ByContains succeeds", () =>
|
||||
{
|
||||
Expect.equal(Postgres.Query.Exists.ByContains(PostgresDb.TableName),
|
||||
$"SELECT EXISTS (SELECT 1 FROM {PostgresDb.TableName} WHERE data @> @criteria) AS it",
|
||||
"JSON containment exists query not correct");
|
||||
}),
|
||||
TestCase("byJsonPath succeeds", () =>
|
||||
{
|
||||
Expect.equal(Postgres.Query.Exists.ByJsonPath(PostgresDb.TableName),
|
||||
$"SELECT EXISTS (SELECT 1 FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath) AS it",
|
||||
"JSON Path match existence query not correct");
|
||||
})
|
||||
}),
|
||||
TestList("Find", new[]
|
||||
{
|
||||
TestCase("byContains succeeds", () =>
|
||||
{
|
||||
Expect.equal(Postgres.Query.Find.ByContains(PostgresDb.TableName),
|
||||
$"SELECT data FROM {PostgresDb.TableName} WHERE data @> @criteria",
|
||||
"SELECT by JSON containment query not correct");
|
||||
}),
|
||||
TestCase("byJsonPath succeeds", () =>
|
||||
{
|
||||
Expect.equal(Postgres.Query.Find.ByJsonPath(PostgresDb.TableName),
|
||||
$"SELECT data FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath",
|
||||
"SELECT by JSON Path match query not correct");
|
||||
})
|
||||
}),
|
||||
TestList("Update", new[]
|
||||
{
|
||||
TestCase("partialById succeeds", () =>
|
||||
{
|
||||
Expect.equal(Postgres.Query.Update.PartialById(PostgresDb.TableName),
|
||||
$"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data ->> 'Id' = @id",
|
||||
"UPDATE partial by ID statement not correct");
|
||||
}),
|
||||
TestCase("partialByField succeeds", () =>
|
||||
{
|
||||
Expect.equal(Postgres.Query.Update.PartialByField(PostgresDb.TableName, "Snail", Op.LT),
|
||||
$"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data ->> 'Snail' < @field",
|
||||
"UPDATE partial by ID statement not correct");
|
||||
}),
|
||||
TestCase("partialByContains succeeds", () =>
|
||||
{
|
||||
Expect.equal(Postgres.Query.Update.PartialByContains(PostgresDb.TableName),
|
||||
$"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data @> @criteria",
|
||||
"UPDATE partial by JSON containment statement not correct");
|
||||
}),
|
||||
TestCase("partialByJsonPath succeeds", () =>
|
||||
{
|
||||
Expect.equal(Postgres.Query.Update.PartialByJsonPath(PostgresDb.TableName),
|
||||
$"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data @? @path::jsonpath",
|
||||
"UPDATE partial by JSON Path statement not correct");
|
||||
})
|
||||
}),
|
||||
TestList("Delete", new[]
|
||||
{
|
||||
TestCase("byContains succeeds", () =>
|
||||
{
|
||||
Expect.equal(Postgres.Query.Delete.ByContains(PostgresDb.TableName),
|
||||
$"DELETE FROM {PostgresDb.TableName} WHERE data @> @criteria",
|
||||
"DELETE by JSON containment query not correct");
|
||||
}),
|
||||
TestCase("byJsonPath succeeds", () =>
|
||||
{
|
||||
Expect.equal(Postgres.Query.Delete.ByJsonPath(PostgresDb.TableName),
|
||||
$"DELETE FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath",
|
||||
"DELETE by JSON Path match query not correct");
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
private static readonly List<JsonDocument> TestDocuments = new()
|
||||
{
|
||||
new() { Id = "one", Value = "FIRST!", NumValue = 0 },
|
||||
new() { Id = "two", Value = "another", NumValue = 10, Sub = new() { Foo = "green", Bar = "blue" } },
|
||||
new() { Id = "three", Value = "", NumValue = 4 },
|
||||
new() { Id = "four", Value = "purple", NumValue = 17, Sub = new() { Foo = "green", Bar = "red" } },
|
||||
new() { Id = "five", Value = "purple", NumValue = 18 }
|
||||
};
|
||||
|
||||
internal static async Task LoadDocs()
|
||||
{
|
||||
foreach (var doc in TestDocuments) await Document.Insert(SqliteDb.TableName, doc);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// All Postgres C# tests
|
||||
/// </summary>
|
||||
public static Test All = TestList("Postgres.C#", new[] { Unit });
|
||||
}
|
|
@ -16,6 +16,7 @@ public static class SqliteCSharpTests
|
|||
/// <summary>
|
||||
/// Unit tests for the SQLite library
|
||||
/// </summary>
|
||||
[Tests]
|
||||
public static Test Unit =
|
||||
TestList("Unit", new[]
|
||||
{
|
||||
|
|
|
@ -7,6 +7,7 @@ let allTests =
|
|||
[ CommonTests.all
|
||||
CommonCSharpTests.Unit
|
||||
PostgresTests.all
|
||||
PostgresCSharpTests.All
|
||||
SqliteTests.all
|
||||
testSequenced SqliteExtensionTests.integrationTests
|
||||
SqliteCSharpTests.All
|
||||
|
|
Loading…
Reference in New Issue
Block a user