v3 RC1 #1

Merged
danieljsummers merged 25 commits from merge-projects into main 2024-01-06 20:51:49 +00:00
7 changed files with 2037 additions and 1257 deletions
Showing only changes of commit 18ec31d16b - Show all commits

View File

@ -18,11 +18,13 @@ module Configuration =
let mutable private dataSourceValue : NpgsqlDataSource option = None
/// Register a data source to use for query execution (disposes the current one if it exists)
[<CompiledName "UseDataSource">]
let useDataSource source =
if Option.isSome dataSourceValue then dataSourceValue.Value.Dispose()
dataSourceValue <- Some source
/// Retrieve the currently configured data source
[<CompiledName "DataSource">]
let dataSource () =
match dataSourceValue with
| Some source -> source
@ -85,8 +87,8 @@ module Query =
Query.Definition.ensureTableFor name "JSONB"
/// SQL statement to create an index on JSON documents in the specified table
[<CompiledName "EnsureJsonIndex">]
let ensureJsonIndex (name: string) idxType =
[<CompiledName "EnsureDocumentIndex">]
let ensureDocumentIndex (name: string) idxType =
let extraOps = match idxType with Full -> "" | Optimized -> " jsonb_path_ops"
let tableName = name.Split '.' |> Array.last
$"CREATE INDEX IF NOT EXISTS idx_{tableName}_document ON {name} USING GIN (data{extraOps})"
@ -266,9 +268,9 @@ module WithProps =
}
/// Create an index on documents in the specified table
[<CompiledName "EnsureJsonIndex">]
let ensureJsonIndex name idxType sqlProps =
Custom.nonQuery (Query.Definition.ensureJsonIndex name idxType) [] sqlProps
[<CompiledName "EnsureDocumentIndex">]
let ensureDocumentIndex name idxType sqlProps =
Custom.nonQuery (Query.Definition.ensureDocumentIndex name idxType) [] sqlProps
/// Create an index on field(s) within documents in the specified table
[<CompiledName "EnsureFieldIndex">]
@ -549,9 +551,9 @@ module Definition =
WithProps.Definition.ensureTable name (fromDataSource ())
/// Create an index on documents in the specified table
[<CompiledName "EnsureJsonIndex">]
let ensureJsonIndex name idxType =
WithProps.Definition.ensureJsonIndex name idxType (fromDataSource ())
[<CompiledName "EnsureDocumentIndex">]
let ensureDocumentIndex name idxType =
WithProps.Definition.ensureDocumentIndex name idxType (fromDataSource ())
/// Create an index on field(s) within documents in the specified table
[<CompiledName "EnsureFieldIndex">]

View File

@ -23,8 +23,7 @@ public static class CommonCSharpTests
/// Unit tests
/// </summary>
[Tests]
public static Test Unit =
TestList("Common.C# Unit", new[]
public static readonly Test Unit = TestList("Common.C# Unit", new[]
{
TestSequenced(
TestList("Configuration", new[]
@ -177,8 +176,7 @@ public static class CommonCSharpTests
{
TestCase("All succeeds", () =>
{
Expect.equal(Query.Count.All("tbl"), "SELECT COUNT(*) AS it FROM tbl",
"Count query not correct");
Expect.equal(Query.Count.All("tbl"), "SELECT COUNT(*) AS it FROM tbl", "Count query not correct");
}),
TestCase("ByField succeeds", () =>
{

View File

@ -1,10 +1,11 @@
using Expecto.CSharp;
using Expecto;
using BitBadger.Documents.Postgres;
using Npgsql.FSharp;
using ThrowawayDb.Postgres;
namespace BitBadger.Documents.Tests.CSharp;
using static CommonExtensionsAndTypesForNpgsqlFSharp;
using static Runner;
/// <summary>
@ -12,24 +13,33 @@ using static Runner;
/// </summary>
public class PostgresCSharpTests
{
public static Test Unit =
TestList("Unit", new[]
/// <summary>
/// Tests which do not hit the database
/// </summary>
private static readonly Test Unit = TestList("Unit", new[]
{
TestList("Parameters", new[]
{
TestCase("Id succeeds", () => {
Expect.equal(Parameters.Id(88).Item1, "@id", "ID parameter not constructed correctly");
TestCase("Id succeeds", () =>
{
var it = Parameters.Id(88);
Expect.equal(it.Item1, "@id", "ID parameter not constructed correctly");
Expect.equal(it.Item2, Sql.@string("88"), "ID parameter value incorrect");
}),
TestCase("Json succeeds", () =>
{
Expect.equal(Parameters.Json("@test", new { Something = "good" }).Item1, "@test",
"JSON parameter not constructed correctly");
var it = Parameters.Json("@test", new { Something = "good" });
Expect.equal(it.Item1, "@test", "JSON parameter not constructed correctly");
Expect.equal(it.Item2, Sql.jsonb("{\"Something\":\"good\"}"), "JSON parameter value incorrect");
}),
TestCase("Field succeeds", () =>
{
Expect.equal(Parameters.Field(242).Item1, "@field", "Field parameter not constructed correctly");
var it = Parameters.Field(242);
Expect.equal(it.Item1, "@field", "Field parameter not constructed correctly");
Expect.isTrue(it.Item2.IsParameter, "Field parameter value incorrect");
}),
TestCase("None succeeds", () => {
TestCase("None succeeds", () =>
{
Expect.isEmpty(Parameters.None, "The no-params sequence should be empty");
})
}),
@ -43,16 +53,16 @@ public class PostgresCSharpTests
$"CREATE TABLE IF NOT EXISTS {PostgresDb.TableName} (data JSONB NOT NULL)",
"CREATE TABLE statement not constructed correctly");
}),
TestCase("EnsureJsonIndex succeeds for full index", () =>
TestCase("EnsureDocumentIndex succeeds for full index", () =>
{
Expect.equal(Postgres.Query.Definition.EnsureJsonIndex("schema.tbl", DocumentIndex.Full),
Expect.equal(Postgres.Query.Definition.EnsureDocumentIndex("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", () =>
TestCase("EnsureDocumentIndex succeeds for JSONB Path Ops index", () =>
{
Expect.equal(
Postgres.Query.Definition.EnsureJsonIndex(PostgresDb.TableName, DocumentIndex.Optimized),
Postgres.Query.Definition.EnsureDocumentIndex(PostgresDb.TableName, DocumentIndex.Optimized),
string.Format(
"CREATE INDEX IF NOT EXISTS idx_{0}_document ON {0} USING GIN (data jsonb_path_ops)",
PostgresDb.TableName),
@ -168,13 +178,794 @@ public class PostgresCSharpTests
new() { Id = "five", Value = "purple", NumValue = 18 }
};
/// <summary>
/// Add the test documents to the database
/// </summary>
internal static async Task LoadDocs()
{
foreach (var doc in TestDocuments) await Document.Insert(SqliteDb.TableName, doc);
}
/// <summary>
/// Integration tests for the PostgreSQL library
/// </summary>
private static readonly Test Integration = TestList("Integration", new[]
{
TestList("Configuration", new[]
{
TestCase("UseDataSource disposes existing source", () =>
{
using var db1 = ThrowawayDatabase.Create(PostgresDb.ConnStr.Value);
var source = PostgresDb.MkDataSource(db1.ConnectionString);
Postgres.Configuration.UseDataSource(source);
using var db2 = ThrowawayDatabase.Create(PostgresDb.ConnStr.Value);
Postgres.Configuration.UseDataSource(PostgresDb.MkDataSource(db2.ConnectionString));
try
{
_ = source.OpenConnection();
Expect.isTrue(false, "Data source should have been disposed");
}
catch (Exception)
{
// This is what should have happened
}
}),
TestCase("DataSource returns configured data source", () =>
{
using var db = ThrowawayDatabase.Create(PostgresDb.ConnStr.Value);
var source = PostgresDb.MkDataSource(db.ConnectionString);
Postgres.Configuration.UseDataSource(source);
Expect.isTrue(ReferenceEquals(source, Postgres.Configuration.DataSource()),
"Data source should have been the same");
})
}),
TestList("Custom", new[]
{
TestList("List", new[]
{
TestCase("succeeds when data is found", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var docs = await Custom.List(Query.SelectFromTable(PostgresDb.TableName), Parameters.None,
Results.FromData<JsonDocument>);
Expect.equal(docs.Count, 5, "There should have been 5 documents returned");
}),
TestCase("succeeds when data is not found", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var docs = await Custom.List(
$"SELECT data FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath",
new[] { Tuple.Create("@path", Sql.@string("$.NumValue ? (@ > 100)")) },
Results.FromData<JsonDocument>);
Expect.isEmpty(docs, "There should have been no documents returned");
})
}),
TestList("Single", new[]
{
TestCase("succeeds when a row is found", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var doc = await Custom.Single($"SELECT data FROM {PostgresDb.TableName} WHERE data ->> 'Id' = @id",
new[] { Tuple.Create("@id", Sql.@string("one")) }, Results.FromData<JsonDocument>);
Expect.isNotNull(doc, "There should have been a document returned");
Expect.equal(doc.Id, "one", "The incorrect document was returned");
}),
TestCase("succeeds when a row is not found", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var doc = await Custom.Single($"SELECT data FROM {PostgresDb.TableName} WHERE data ->> 'Id' = @id",
new[] { Tuple.Create("@id", Sql.@string("eighty")) }, Results.FromData<JsonDocument>);
Expect.isNull(doc, "There should not have been a document returned");
})
}),
TestList("NonQuery", new[]
{
TestCase("succeeds when operating on data", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
await Custom.NonQuery($"DELETE FROM {PostgresDb.TableName}", Parameters.None);
var remaining = await Count.All(PostgresDb.TableName);
Expect.equal(remaining, 0, "There should be no documents remaining in the table");
}),
TestCase("succeeds when no data matches where clause", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
await Custom.NonQuery($"DELETE FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath",
new[] { Tuple.Create("@path", Sql.@string("$.NumValue ? (@ > 100)")) });
var remaining = await Count.All(PostgresDb.TableName);
Expect.equal(remaining, 5, "There should be 5 documents remaining in the table");
})
}),
TestCase("Scalar succeeds", async () =>
{
await using var db = PostgresDb.BuildDb();
var nbr = await Custom.Scalar("SELECT 5 AS test_value", Parameters.None, row => row.@int("test_value"));
Expect.equal(nbr, 5, "The query should have returned the number 5");
})
}),
TestList("Definition", new[]
{
TestCase("EnsureTable succeeds", async () =>
{
await using var db = PostgresDb.BuildDb();
var tableExists = () => Custom.Scalar(
"SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'ensured') AS it", Parameters.None,
Results.ToExists);
var keyExists = () => Custom.Scalar(
"SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_key') AS it", Parameters.None,
Results.ToExists);
var exists = await tableExists();
var alsoExists = await keyExists();
Expect.isFalse(exists, "The table should not exist already");
Expect.isFalse(alsoExists, "The key index should not exist already");
await Definition.EnsureTable("ensured");
exists = await tableExists();
alsoExists = await keyExists();
Expect.isTrue(exists, "The table should now exist");
Expect.isTrue(alsoExists, "The key index should now exist");
}),
TestCase("EnsureDocumentIndex succeeds", async () =>
{
await using var db = PostgresDb.BuildDb();
var indexExists = () => Custom.Scalar(
"SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_document') AS it",
Parameters.None, Results.ToExists);
var exists = await indexExists();
Expect.isFalse(exists, "The index should not exist already");
await Definition.EnsureTable("ensured");
await Definition.EnsureDocumentIndex("ensured", DocumentIndex.Optimized);
exists = await indexExists();
Expect.isTrue(exists, "The index should now exist");
}),
TestCase("EnsureFieldIndex succeeds", async () =>
{
await using var db = PostgresDb.BuildDb();
var indexExists = () => Custom.Scalar(
"SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = 'idx_ensured_test') AS it", Parameters.None,
Results.ToExists);
var exists = await indexExists();
Expect.isFalse(exists, "The index should not exist already");
await Definition.EnsureTable("ensured");
await Definition.EnsureFieldIndex("ensured", "test", new[] { "Id", "Category" });
exists = await indexExists();
Expect.isTrue(exists, "The index should now exist");
})
}),
TestList("Document", new[]
{
TestList("Insert", new[]
{
TestCase("succeeds", async () =>
{
await using var db = PostgresDb.BuildDb();
var before = await Count.All(PostgresDb.TableName);
Expect.equal(before, 0, "There should be no documents in the table");
await Document.Insert(PostgresDb.TableName,
new JsonDocument { Id = "turkey", Sub = new() { Foo = "gobble", Bar = "gobble" } });
var after = await Count.All(PostgresDb.TableName);
Expect.equal(after, 1, "There should have been one document inserted");
}),
TestCase("fails for duplicate key", async () =>
{
await using var db = PostgresDb.BuildDb();
await Document.Insert(PostgresDb.TableName, new JsonDocument { Id = "test" });
try
{
await Document.Insert(PostgresDb.TableName, new JsonDocument { Id = "test" });
Expect.isTrue(false, "An exception should have been raised for duplicate document ID insert");
}
catch (Exception)
{
// This is what should have happened
}
})
}),
TestList("Save", new[]
{
TestCase("succeeds when a document is inserted", async () =>
{
await using var db = PostgresDb.BuildDb();
var before = await Count.All(PostgresDb.TableName);
Expect.equal(before, 0, "There should be no documents in the table");
await Document.Save(PostgresDb.TableName,
new JsonDocument { Id = "test", Sub = new() { Foo = "a", Bar = "b" } });
var after = await Count.All(PostgresDb.TableName);
Expect.equal(after, 1, "There should have been one document inserted");
}),
TestCase("succeeds when a document is updated", async () =>
{
await using var db = PostgresDb.BuildDb();
await Document.Insert(PostgresDb.TableName,
new JsonDocument { Id = "test", Sub = new() { Foo = "a", Bar = "b" } });
var before = await Find.ById<string, JsonDocument>(PostgresDb.TableName, "test");
Expect.isNotNull(before, "There should have been a document returned");
Expect.equal(before.Id, "test", "The document is not correct");
before.Sub = new() { Foo = "c", Bar = "d" };
await Document.Save(PostgresDb.TableName, before);
var after = await Find.ById<string, JsonDocument>(PostgresDb.TableName, "test");
Expect.isNotNull(after, "There should have been a document returned post-update");
Expect.equal(after.Id, "test", "The document is not correct");
Expect.equal(after.Sub!.Foo, "c", "The updated document is not correct");
})
})
}),
TestList("Count", new[]
{
TestCase("All succeeds", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var theCount = await Count.All(PostgresDb.TableName);
Expect.equal(theCount, 5, "There should have been 5 matching documents");
}),
TestCase("ByField succeeds", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var theCount = await Count.ByField(PostgresDb.TableName, "Value", Op.EQ, "purple");
Expect.equal(theCount, 2, "There should have been 2 matching documents");
}),
TestCase("ByContains succeeds", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var theCount = await Count.ByContains(PostgresDb.TableName, new { Value = "purple" });
Expect.equal(theCount, 2, "There should have been 2 matching documents");
}),
TestCase("ByJsonPath succeeds", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var theCount = await Count.ByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ > 5)");
Expect.equal(theCount, 3, "There should have been 3 matching documents");
})
}),
TestList("Exists", new[]
{
TestList("ById", new[]
{
TestCase("succeeds when a document exists", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var exists = await Exists.ById(PostgresDb.TableName, "three");
Expect.isTrue(exists, "There should have been an existing document");
}),
TestCase("succeeds when a document does not exist", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var exists = await Exists.ById(PostgresDb.TableName, "seven");
Expect.isFalse(exists, "There should not have been an existing document");
})
}),
TestList("ByField", new[]
{
TestCase("succeeds when documents exist", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var exists = await Exists.ByField(PostgresDb.TableName, "Sub", Op.NEX, "");
Expect.isTrue(exists, "There should have been existing documents");
}),
TestCase("succeeds when documents do not exist", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var exists = await Exists.ByField(PostgresDb.TableName, "NumValue", Op.EQ, "six");
Expect.isFalse(exists, "There should not have been existing documents");
})
}),
TestList("ByContains", new[]
{
TestCase("succeeds when documents exist", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var exists = await Exists.ByContains(PostgresDb.TableName, new { NumValue = 10 });
Expect.isTrue(exists, "There should have been existing documents");
}),
TestCase("succeeds when no matching documents exist", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var exists = await Exists.ByContains(PostgresDb.TableName, new { Nothing = "none" });
Expect.isFalse(exists, "There should not have been any existing documents");
})
}),
TestList("ByJsonPath", new[] {
TestCase("succeeds when documents exist", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var exists = await Exists.ByJsonPath(PostgresDb.TableName, "$.Sub.Foo ? (@ == \"green\")");
Expect.isTrue(exists, "There should have been existing documents");
}),
TestCase("succeeds when no matching documents exist", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var exists = await Exists.ByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ > 1000)");
Expect.isFalse(exists, "There should not have been any existing documents");
})
})
}),
TestList("Find", new[]
{
TestList("All", new[]
{
TestCase("succeeds when there is data", async () =>
{
await using var db = PostgresDb.BuildDb();
await Document.Insert(PostgresDb.TableName, new SubDocument { Foo = "one", Bar = "two" });
await Document.Insert(PostgresDb.TableName, new SubDocument { Foo = "three", Bar = "four" });
await Document.Insert(PostgresDb.TableName, new SubDocument { Foo = "five", Bar = "six" });
var results = await Find.All<SubDocument>(PostgresDb.TableName);
Expect.equal(results.Count, 3, "There should have been 3 documents returned");
}),
TestCase("succeeds when there is no data", async () =>
{
await using var db = PostgresDb.BuildDb();
var results = await Find.All<SubDocument>(PostgresDb.TableName);
Expect.isEmpty(results, "There should have been no documents returned");
})
}),
TestList("ById", new[]
{
TestCase("succeeds when a document is found", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var doc = await Find.ById<string, JsonDocument>(PostgresDb.TableName, "two");
Expect.isNotNull(doc, "There should have been a document returned");
Expect.equal(doc.Id, "two", "The incorrect document was returned");
}),
TestCase("succeeds when a document is not found", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var doc = await Find.ById<string, JsonDocument>(PostgresDb.TableName, "three hundred eighty-seven");
Expect.isNull(doc, "There should not have been a document returned");
})
}),
TestList("ByField", new[]
{
TestCase("succeeds when documents are found", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var docs = await Find.ByField<JsonDocument>(PostgresDb.TableName, "Value", Op.EQ, "another");
Expect.equal(docs.Count, 1, "There should have been one document returned");
}),
TestCase("succeeds when documents are not found", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var docs = await Find.ByField<JsonDocument>(PostgresDb.TableName, "Value", Op.EQ, "mauve");
Expect.isEmpty(docs, "There should have been no documents returned");
})
}),
TestList("ByContains", new[]
{
TestCase("succeeds when documents are found", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var docs = await Find.ByContains<JsonDocument>(PostgresDb.TableName,
new { Sub = new { Foo = "green" } });
Expect.equal(docs.Count, 2, "There should have been two documents returned");
}),
TestCase("succeeds when documents are not found", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var docs = await Find.ByContains<JsonDocument>(PostgresDb.TableName, new { Value = "mauve" });
Expect.isEmpty(docs, "There should have been no documents returned");
})
}),
TestList("ByJsonPath", new[]
{
TestCase("succeeds when documents are found", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var docs = await Find.ByJsonPath<JsonDocument>(PostgresDb.TableName, "$.NumValue ? (@ < 15)");
Expect.equal(docs.Count, 3, "There should have been 3 documents returned");
}),
TestCase("succeeds when documents are not found", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var docs = await Find.ByJsonPath<JsonDocument>(PostgresDb.TableName, "$.NumValue ? (@ < 0)");
Expect.isEmpty(docs, "There should have been no documents returned");
})
}),
TestList("FirstByField", new[]
{
TestCase("succeeds when a document is found", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var doc = await Find.FirstByField<JsonDocument>(PostgresDb.TableName, "Value", Op.EQ, "another");
Expect.isNotNull(doc, "There should have been a document returned");
Expect.equal(doc.Id, "two", "The incorrect document was returned");
}),
TestCase("succeeds when multiple documents are found", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var doc = await Find.FirstByField<JsonDocument>(PostgresDb.TableName, "Value", Op.EQ, "purple");
Expect.isNotNull(doc, "There should have been a document returned");
Expect.contains(new[] { "five", "four" }, doc.Id, "An incorrect document was returned");
}),
TestCase("succeeds when a document is not found", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var doc = await Find.FirstByField<JsonDocument>(PostgresDb.TableName, "Value", Op.EQ, "absent");
Expect.isNull(doc, "There should not have been a document returned");
})
}),
TestList("FirstByContains", new[]
{
TestCase("succeeds when a document is found", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var doc = await Find.FirstByContains<JsonDocument>(PostgresDb.TableName, new { Value = "another" });
Expect.isNotNull(doc, "There should have been a document returned");
Expect.equal(doc.Id, "two", "The incorrect document was returned");
}),
TestCase("succeeds when multiple documents are found", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var doc = await Find.FirstByContains<JsonDocument>(PostgresDb.TableName,
new { Sub = new { Foo = "green" } });
Expect.isNotNull(doc, "There should have been a document returned");
Expect.contains(new[] { "two", "four" }, doc.Id, "An incorrect document was returned");
}),
TestCase("succeeds when a document is not found", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var doc = await Find.FirstByContains<JsonDocument>(PostgresDb.TableName, new { Value = "absent" });
Expect.isNull(doc, "There should not have been a document returned");
})
}),
TestList("FirstByJsonPath", new[]
{
TestCase("succeeds when a document is found", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var doc = await Find.FirstByJsonPath<JsonDocument>(PostgresDb.TableName,
"$.Value ? (@ == \"FIRST!\")");
Expect.isNotNull(doc, "There should have been a document returned");
Expect.equal(doc.Id, "one", "The incorrect document was returned");
}),
TestCase("succeeds when multiple documents are found", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var doc = await Find.FirstByJsonPath<JsonDocument>(PostgresDb.TableName,
"$.Sub.Foo ? (@ == \"green\")");
Expect.isNotNull(doc, "There should have been a document returned");
Expect.contains(new[] { "two", "four" }, doc.Id, "An incorrect document was returned");
}),
TestCase("succeeds when a document is not found", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
var doc = await Find.FirstByJsonPath<JsonDocument>(PostgresDb.TableName, "$.Id ? (@ == \"nope\")");
Expect.isNull(doc, "There should not have been a document returned");
})
})
}),
TestList("Update", new[]
{
TestList("Full", new[]
{
TestCase("succeeds when a document is updated", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
await Update.Full(PostgresDb.TableName, "one",
new JsonDocument { Id = "one", Sub = new() { Foo = "blue", Bar = "red" } });
var after = await Find.ById<string, JsonDocument>(PostgresDb.TableName, "one");
Expect.isNotNull(after, "There should have been a document returned post-update");
Expect.equal(after.Id, "one", "The updated document is not correct (ID)");
Expect.equal(after.Value, "", "The updated document is not correct (Value)");
Expect.equal(after.NumValue, 0, "The updated document is not correct (NumValue)");
Expect.isNotNull(after.Sub, "The updated document should have had a sub-document");
Expect.equal(after.Sub!.Foo, "blue", "The updated document is not correct (Sub.Foo)");
Expect.equal(after.Sub.Bar, "red", "The updated document is not correct (Sub.Bar)");
}),
TestCase("succeeds when no document is updated", async () =>
{
await using var db = PostgresDb.BuildDb();
var before = await Count.All(PostgresDb.TableName);
Expect.equal(before, 0, "There should have been no documents returned");
// This not raising an exception is the test
await Update.Full(PostgresDb.TableName, "test",
new JsonDocument { Id = "x", Sub = new() { Foo = "blue", Bar = "red" } });
})
}),
TestList("FullFunc", new[]
{
TestCase("succeeds when a document is updated", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
await Update.FullFunc(PostgresDb.TableName, doc => doc.Id,
new JsonDocument { Id = "one", Value = "le un", NumValue = 1 });
var after = await Find.ById<string, JsonDocument>(PostgresDb.TableName, "one");
Expect.isNotNull(after, "There should have been a document returned post-update");
Expect.equal(after.Id, "one", "The updated document is not correct (ID)");
Expect.equal(after.Value, "le un", "The updated document is not correct (Value)");
Expect.equal(after.NumValue, 1, "The updated document is not correct (NumValue)");
Expect.isNull(after.Sub, "The updated document should not have had a sub-document");
}),
TestCase("succeeds when no document is updated", async () =>
{
await using var db = PostgresDb.BuildDb();
var before = await Count.All(PostgresDb.TableName);
Expect.equal(before, 0, "There should have been no documents returned");
// This not raising an exception is the test
await Update.FullFunc(PostgresDb.TableName, doc => doc.Id,
new JsonDocument { Id = "one", Value = "le un", NumValue = 1 });
})
}),
TestList("PartialById", new[]
{
TestCase("succeeds when a document is updated", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
await Update.PartialById(PostgresDb.TableName, "one", new { NumValue = 44 });
var after = await Find.ById<string, JsonDocument>(PostgresDb.TableName, "one");
Expect.isNotNull(after, "There should have been a document returned post-update");
Expect.equal(after.NumValue, 44, "The updated document is not correct");
}),
TestCase("succeeds when no document is updated", async () =>
{
await using var db = PostgresDb.BuildDb();
var before = await Count.All(PostgresDb.TableName);
Expect.equal(before, 0, "There should have been no documents returned");
// This not raising an exception is the test
await Update.PartialById(PostgresDb.TableName, "test", new { Foo = "green" });
})
}),
TestList("PartialByField", new[]
{
TestCase("succeeds when a document is updated", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
await Update.PartialByField(PostgresDb.TableName, "Value", Op.EQ, "purple", new { NumValue = 77 });
var after = await Count.ByField(PostgresDb.TableName, "NumValue", Op.EQ, "77");
Expect.equal(after, 2, "There should have been 2 documents returned");
}),
TestCase("succeeds when no document is updated", async () =>
{
await using var db = PostgresDb.BuildDb();
var before = await Count.All(PostgresDb.TableName);
Expect.equal(before, 0, "There should have been no documents returned");
// This not raising an exception is the test
await Update.PartialByField(PostgresDb.TableName, "Value", Op.EQ, "burgundy",
new { Foo = "green" });
})
}),
TestList("PartialByContains", new[]
{
TestCase("succeeds when a document is updated", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
await Update.PartialByContains(PostgresDb.TableName, new { Value = "purple" },
new { NumValue = 77 });
var after = await Count.ByContains(PostgresDb.TableName, new { NumValue = 77 });
Expect.equal(after, 2, "There should have been 2 documents returned");
}),
TestCase("succeeds when no document is updated", async () =>
{
await using var db = PostgresDb.BuildDb();
var before = await Count.All(PostgresDb.TableName);
Expect.equal(before, 0, "There should have been no documents returned");
// This not raising an exception is the test
await Update.PartialByContains(PostgresDb.TableName, new { Value = "burgundy" },
new { Foo = "green" });
})
}),
TestList("PartialByJsonPath", new[]
{
TestCase("succeeds when a document is updated", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
await Update.PartialByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ > 10)",
new { NumValue = 1000 });
var after = await Count.ByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ > 999)");
Expect.equal(after, 2, "There should have been 2 documents returned");
}),
TestCase("succeeds when no document is updated", async () =>
{
await using var db = PostgresDb.BuildDb();
var before = await Count.All(PostgresDb.TableName);
Expect.equal(before, 0, "There should have been no documents returned");
// This not raising an exception is the test
await Update.PartialByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ < 0)", new { Foo = "green" });
})
})
}),
TestList("Delete", new[]
{
TestList("ById", new[]
{
TestCase("succeeds when a document is deleted", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
await Delete.ById(PostgresDb.TableName, "four");
var remaining = await Count.All(PostgresDb.TableName);
Expect.equal(remaining, 4, "There should have been 4 documents remaining");
}),
TestCase("succeeds when a document is not deleted", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
await Delete.ById(PostgresDb.TableName, "thirty");
var remaining = await Count.All(PostgresDb.TableName);
Expect.equal(remaining, 5, "There should have been 5 documents remaining");
})
}),
TestList("ByField", new[]
{
TestCase("succeeds when documents are deleted", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
await Delete.ByField(PostgresDb.TableName, "Value", Op.EQ, "purple");
var remaining = await Count.All(PostgresDb.TableName);
Expect.equal(remaining, 3, "There should have been 3 documents remaining");
}),
TestCase("succeeds when documents are not deleted", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
await Delete.ByField(PostgresDb.TableName, "Value", Op.EQ, "crimson");
var remaining = await Count.All(PostgresDb.TableName);
Expect.equal(remaining, 5, "There should have been 5 documents remaining");
})
}),
TestList("ByContains", new[]
{
TestCase("succeeds when documents are deleted", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
await Delete.ByContains(PostgresDb.TableName, new { Value = "purple" });
var remaining = await Count.All(PostgresDb.TableName);
Expect.equal(remaining, 3, "There should have been 3 documents remaining");
}),
TestCase("succeeds when documents are not deleted", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
await Delete.ByContains(PostgresDb.TableName, new { Value = "crimson" });
var remaining = await Count.All(PostgresDb.TableName);
Expect.equal(remaining, 5, "There should have been 5 documents remaining");
})
}),
TestList("ByJsonPath", new[]
{
TestCase("succeeds when documents are deleted", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
await Delete.ByJsonPath(PostgresDb.TableName, "$.Sub.Foo ? (@ == \"green\")");
var remaining = await Count.All(PostgresDb.TableName);
Expect.equal(remaining, 3, "There should have been 3 documents remaining");
}),
TestCase("succeeds when documents are not deleted", async () =>
{
await using var db = PostgresDb.BuildDb();
await LoadDocs();
await Delete.ByJsonPath(PostgresDb.TableName, "$.NumValue ? (@ > 100)");
var remaining = await Count.All(PostgresDb.TableName);
Expect.equal(remaining, 5, "There should have been 5 documents remaining");
})
})
})
});
/// <summary>
/// All Postgres C# tests
/// </summary>
public static Test All = TestList("Postgres.C#", new[] { Unit });
[Tests]
public static readonly Test All = TestList("Postgres.C#", new[] { Unit, TestSequenced(Integration) });
}

View File

@ -137,7 +137,7 @@ public static class PostgresDb
Sql.executeNonQuery(Sql.query(Postgres.Query.Definition.EnsureTable(TableName), sqlProps));
Sql.executeNonQuery(Sql.query(Query.Definition.EnsureKey(TableName), sqlProps));
Postgres.Configuration.useDataSource(MkDataSource(database.ConnectionString));
Postgres.Configuration.UseDataSource(MkDataSource(database.ConnectionString));
return new ThrowawayPostgresDb(database);
}

View File

@ -14,9 +14,11 @@ public static class SqliteCSharpExtensionTests
{
private static Task LoadDocs() => SqliteCSharpTests.LoadDocs();
/// <summary>
/// Integration tests for the SQLite extension methods
/// </summary>
[Tests]
public static Test Integration =
TestList("Extensions", new[]
public static readonly Test Integration = TestList("Extensions", new[]
{
TestList("CustomSingle", new[]
{
@ -26,8 +28,7 @@ public static class SqliteCSharpExtensionTests
await using var conn = Sqlite.Configuration.DbConn();
await LoadDocs();
var doc = await conn.CustomSingle(
$"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id",
var doc = await conn.CustomSingle($"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id",
new[] { Parameters.Id("one") }, Results.FromData<JsonDocument>);
Expect.isNotNull(doc, "There should have been a document returned");
Expect.equal(doc!.Id, "one", "The incorrect document was returned");
@ -38,8 +39,7 @@ public static class SqliteCSharpExtensionTests
await using var conn = Sqlite.Configuration.DbConn();
await LoadDocs();
var doc = await conn.CustomSingle(
$"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id",
var doc = await conn.CustomSingle($"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id",
new[] { Parameters.Id("eighty") }, Results.FromData<JsonDocument>);
Expect.isNull(doc, "There should not have been a document returned");
})
@ -87,8 +87,7 @@ public static class SqliteCSharpExtensionTests
await using var conn = Sqlite.Configuration.DbConn();
await LoadDocs();
await conn.CustomNonQuery(
$"DELETE FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value",
await conn.CustomNonQuery($"DELETE FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value",
new[] { new SqliteParameter("@value", 100) });
var remaining = await conn.CountAll(SqliteDb.TableName);
@ -325,8 +324,7 @@ public static class SqliteCSharpExtensionTests
await using var conn = Sqlite.Configuration.DbConn();
await LoadDocs();
var doc = await conn.FindFirstByField<JsonDocument>(SqliteDb.TableName, "Value", Op.EQ,
"another");
var doc = await conn.FindFirstByField<JsonDocument>(SqliteDb.TableName, "Value", Op.EQ, "another");
Expect.isNotNull(doc, "There should have been a document returned");
Expect.equal(doc!.Id, "two", "The incorrect document was returned");
}),
@ -336,8 +334,7 @@ public static class SqliteCSharpExtensionTests
await using var conn = Sqlite.Configuration.DbConn();
await LoadDocs();
var doc = await conn.FindFirstByField<JsonDocument>(SqliteDb.TableName, "Sub.Foo", Op.EQ,
"green");
var doc = await conn.FindFirstByField<JsonDocument>(SqliteDb.TableName, "Sub.Foo", Op.EQ, "green");
Expect.isNotNull(doc, "There should have been a document returned");
Expect.contains(new[] { "two", "four" }, doc!.Id, "An incorrect document was returned");
}),
@ -347,8 +344,7 @@ public static class SqliteCSharpExtensionTests
await using var conn = Sqlite.Configuration.DbConn();
await LoadDocs();
var doc = await conn.FindFirstByField<JsonDocument>(SqliteDb.TableName, "Value", Op.EQ,
"absent");
var doc = await conn.FindFirstByField<JsonDocument>(SqliteDb.TableName, "Value", Op.EQ, "absent");
Expect.isNull(doc, "There should not have been a document returned");
})
}),
@ -443,8 +439,7 @@ public static class SqliteCSharpExtensionTests
await using var conn = Sqlite.Configuration.DbConn();
await LoadDocs();
await conn.UpdatePartialByField(SqliteDb.TableName, "Value", Op.EQ, "purple",
new { NumValue = 77 });
await conn.UpdatePartialByField(SqliteDb.TableName, "Value", Op.EQ, "purple", new { NumValue = 77 });
var after = await conn.CountByField(SqliteDb.TableName, "NumValue", Op.EQ, 77);
Expect.equal(after, 2L, "There should have been 2 documents returned");
}),
@ -456,8 +451,7 @@ public static class SqliteCSharpExtensionTests
Expect.isEmpty(before, "There should have been no documents returned");
// This not raising an exception is the test
await conn.UpdatePartialByField(SqliteDb.TableName, "Value", Op.EQ, "burgundy",
new { Foo = "green" });
await conn.UpdatePartialByField(SqliteDb.TableName, "Value", Op.EQ, "burgundy", new { Foo = "green" });
})
}),
TestList("DeleteById", new[]

View File

@ -16,9 +16,7 @@ public static class SqliteCSharpTests
/// <summary>
/// Unit tests for the SQLite library
/// </summary>
[Tests]
public static Test Unit =
TestList("Unit", new[]
private static readonly Test Unit = TestList("Unit", new[]
{
TestList("Query", new[]
{
@ -80,14 +78,15 @@ public static class SqliteCSharpTests
new() { Id = "five", Value = "purple", NumValue = 18 }
};
/// <summary>
/// Add the test documents to the database
/// </summary>
internal static async Task LoadDocs()
{
foreach (var doc in TestDocuments) await Document.Insert(SqliteDb.TableName, doc);
}
[Tests]
public static Test Integration =
TestList("Integration", new[]
private static readonly Test Integration = TestList("Integration", new[]
{
TestCase("Configuration.UseConnectionString succeeds", () =>
{
@ -95,8 +94,7 @@ public static class SqliteCSharpTests
{
Sqlite.Configuration.UseConnectionString("Data Source=test.db");
Expect.equal(Sqlite.Configuration.connectionString,
new FSharpOption<string>("Data Source=test.db;Foreign Keys=True"),
"Connection string incorrect");
new FSharpOption<string>("Data Source=test.db;Foreign Keys=True"), "Connection string incorrect");
}
finally
{
@ -105,15 +103,14 @@ public static class SqliteCSharpTests
}),
TestList("Custom", new[]
{
TestList("Single", new []
TestList("Single", new[]
{
TestCase("succeeds when a row is found", async () =>
{
await using var db = await SqliteDb.BuildDb();
await LoadDocs();
var doc = await Custom.Single(
$"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id",
var doc = await Custom.Single($"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id",
new[] { Parameters.Id("one") }, Results.FromData<JsonDocument>);
Expect.isNotNull(doc, "There should have been a document returned");
Expect.equal(doc!.Id, "one", "The incorrect document was returned");
@ -123,8 +120,7 @@ public static class SqliteCSharpTests
await using var db = await SqliteDb.BuildDb();
await LoadDocs();
var doc = await Custom.Single(
$"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id",
var doc = await Custom.Single($"SELECT data FROM {SqliteDb.TableName} WHERE data ->> 'Id' = @id",
new[] { Parameters.Id("eighty") }, Results.FromData<JsonDocument>);
Expect.isNull(doc, "There should not have been a document returned");
})
@ -168,8 +164,7 @@ public static class SqliteCSharpTests
await using var db = await SqliteDb.BuildDb();
await LoadDocs();
await Custom.NonQuery(
$"DELETE FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value",
await Custom.NonQuery($"DELETE FROM {SqliteDb.TableName} WHERE data ->> 'NumValue' > @value",
new[] { new SqliteParameter("@value", 100) });
var remaining = await Count.All(SqliteDb.TableName);
@ -207,8 +202,7 @@ public static class SqliteCSharpTests
{
var result = await Custom.Scalar(
$"SELECT EXISTS (SELECT 1 FROM {SqliteDb.Catalog} WHERE name = @name) AS it",
new SqliteParameter[] { new("@name", name) },
rdr => rdr.GetInt64(0));
new SqliteParameter[] { new("@name", name) }, rdr => rdr.GetInt64(0));
return result > 0L;
}
})
@ -582,5 +576,6 @@ public static class SqliteCSharpTests
/// <summary>
/// All tests for SQLite C# functions and methods
/// </summary>
[Tests]
public static readonly Test All = TestList("Sqlite.C#", new[] { Unit, TestSequenced(Integration) });
}

View File

@ -39,15 +39,15 @@ let unitTests =
$"CREATE TABLE IF NOT EXISTS {PostgresDb.TableName} (data JSONB NOT NULL)"
"CREATE TABLE statement not constructed correctly"
}
test "ensureJsonIndex succeeds for full index" {
test "ensureDocumentIndex succeeds for full index" {
Expect.equal
(Query.Definition.ensureJsonIndex "schema.tbl" Full)
(Query.Definition.ensureDocumentIndex "schema.tbl" Full)
"CREATE INDEX IF NOT EXISTS idx_tbl_document ON schema.tbl USING GIN (data)"
"CREATE INDEX statement not constructed correctly"
}
test "ensureJsonIndex succeeds for JSONB Path Ops index" {
test "ensureDocumentIndex succeeds for JSONB Path Ops index" {
Expect.equal
(Query.Definition.ensureJsonIndex PostgresDb.TableName Optimized)
(Query.Definition.ensureDocumentIndex PostgresDb.TableName Optimized)
(sprintf "CREATE INDEX IF NOT EXISTS idx_%s_document ON %s USING GIN (data jsonb_path_ops)"
PostgresDb.TableName PostgresDb.TableName)
"CREATE INDEX statement not constructed correctly"
@ -266,7 +266,7 @@ let integrationTests =
Expect.isTrue exists' "The table should now exist"
Expect.isTrue alsoExists' "The key index should now exist"
}
testTask "ensureJsonIndex succeeds" {
testTask "ensureDocumentIndex succeeds" {
use db = PostgresDb.BuildDb()
let indexExists () =
Custom.scalar
@ -278,7 +278,7 @@ let integrationTests =
Expect.isFalse exists "The index should not exist already"
do! Definition.ensureTable "ensured"
do! Definition.ensureJsonIndex "ensured" Optimized
do! Definition.ensureDocumentIndex "ensured" Optimized
let! exists' = indexExists ()
Expect.isTrue exists' "The index should now exist"
}
@ -730,7 +730,7 @@ let integrationTests =
Expect.equal before 0 "There should have been no documents returned"
// This not raising an exception is the test
do! Update.partialByContains PostgresDb.TableName {| Value = "burgundy" |} {| Foo = "green" |}
do! Update.partialByJsonPath PostgresDb.TableName "$.NumValue ? (@ < 0)" {| Foo = "green" |}
}
]
]