From 88f11a854f8dffb915f04819c010082b7b0b2235 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Sat, 17 Aug 2024 14:08:46 -0400 Subject: [PATCH] Add compat namespace functions - Update package release notes --- src/Directory.Build.props | 2 +- .../BitBadger.Documents.Postgres.fsproj | 1 + src/Postgres/Compat.fs | 270 ++++++++++++++++++ src/Postgres/Library.fs | 12 - src/Sqlite/BitBadger.Documents.Sqlite.fsproj | 1 + src/Sqlite/Compat.fs | 269 +++++++++++++++++ src/Tests.CSharp/PostgresCSharpTests.cs | 23 -- src/Tests/PostgresTests.fs | 14 - 8 files changed, 542 insertions(+), 50 deletions(-) create mode 100644 src/Postgres/Compat.fs create mode 100644 src/Sqlite/Compat.fs diff --git a/src/Directory.Build.props b/src/Directory.Build.props index e73f3da..4796c0a 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -6,7 +6,7 @@ 4.0.0.0 4.0.0.0 4.0.0 - Change ByField to ByFields; support dot-access to document fields; add Find*Ordered functions/methods + Change ByField to ByFields; support dot-access to nested document fields; add Find*Ordered functions/methods; see project site for breaking changes and compatibility danieljsummers Bit Badger Solutions README.md diff --git a/src/Postgres/BitBadger.Documents.Postgres.fsproj b/src/Postgres/BitBadger.Documents.Postgres.fsproj index 1504c4a..6773b53 100644 --- a/src/Postgres/BitBadger.Documents.Postgres.fsproj +++ b/src/Postgres/BitBadger.Documents.Postgres.fsproj @@ -8,6 +8,7 @@ + diff --git a/src/Postgres/Compat.fs b/src/Postgres/Compat.fs new file mode 100644 index 0000000..dae2bfa --- /dev/null +++ b/src/Postgres/Compat.fs @@ -0,0 +1,270 @@ +namespace BitBadger.Documents.Postgres.Compat + +open BitBadger.Documents +open BitBadger.Documents.Postgres + +[] +module Parameters = + + /// Create a JSON field parameter + [] + [] + let addFieldParam name field parameters = + addFieldParams [ { field with ParameterName = Some name } ] parameters + + /// Append JSON field name parameters for the given field names to the given parameters + [] + [] + let fieldNameParam fieldNames = + fieldNameParams fieldNames + + +[] +module Query = + + /// Create a WHERE clause fragment to implement a comparison on a field in a JSON document + [] + [] + let whereByField field paramName = + Query.whereByFields Any [ { field with ParameterName = Some paramName } ] + + +module WithProps = + + [] + module Count = + + /// Count matching documents using a JSON field comparison (->> =) + [] + [] + let byField tableName field sqlProps = + WithProps.Count.byFields tableName Any [ field ] sqlProps + + [] + module Exists = + + /// Determine if a document exists using a JSON field comparison (->> =) + [] + [] + let byField tableName field sqlProps = + WithProps.Exists.byFields tableName Any [ field ] sqlProps + + [] + module Find = + + /// Retrieve documents matching a JSON field comparison (->> =) + [] + [] + let byField<'TDoc> tableName field sqlProps = + WithProps.Find.byFields<'TDoc> tableName Any [ field ] sqlProps + + /// Retrieve documents matching a JSON field comparison (->> =) + [] + let ByField<'TDoc>(tableName, field, sqlProps) = + WithProps.Find.ByFields<'TDoc>(tableName, Any, Seq.singleton field, sqlProps) + + /// Retrieve the first document matching a JSON field comparison (->> =); returns None if not found + [] + [] + let firstByField<'TDoc> tableName field sqlProps = + WithProps.Find.firstByFields<'TDoc> tableName Any [ field ] sqlProps + + /// Retrieve the first document matching a JSON field comparison (->> =); returns null if not found + [] + let FirstByField<'TDoc when 'TDoc: null>(tableName, field, sqlProps) = + WithProps.Find.FirstByFields<'TDoc>(tableName, Any, Seq.singleton field, sqlProps) + + [] + module Patch = + + /// Patch documents using a JSON field comparison query in the WHERE clause (->> =) + [] + [] + let byField tableName field (patch: 'TPatch) sqlProps = + WithProps.Patch.byFields tableName Any [ field ] patch sqlProps + + [] + module RemoveFields = + + /// Remove fields from documents via a comparison on a JSON field in the document + [] + [] + let byField tableName field fieldNames sqlProps = + WithProps.RemoveFields.byFields tableName Any [ field ] fieldNames sqlProps + + [] + module Delete = + + /// Delete documents by matching a JSON field comparison query (->> =) + [] + [] + let byField tableName field sqlProps = + WithProps.Delete.byFields tableName Any [ field ] sqlProps + + +[] +module Count = + + /// Count matching documents using a JSON field comparison (->> =) + [] + [] + let byField tableName field = + Count.byFields tableName Any [ field ] + + +[] +module Exists = + + /// Determine if a document exists using a JSON field comparison (->> =) + [] + [] + let byField tableName field = + Exists.byFields tableName Any [ field ] + + +[] +module Find = + + /// Retrieve documents matching a JSON field comparison (->> =) + [] + [] + let byField<'TDoc> tableName field = + Find.byFields<'TDoc> tableName Any [ field ] + + /// Retrieve documents matching a JSON field comparison (->> =) + [] + let ByField<'TDoc>(tableName, field) = + Find.ByFields<'TDoc>(tableName, Any, Seq.singleton field) + + /// Retrieve the first document matching a JSON field comparison (->> =); returns None if not found + [] + [] + let firstByField<'TDoc> tableName field = + Find.firstByFields<'TDoc> tableName Any [ field ] + + /// Retrieve the first document matching a JSON field comparison (->> =); returns null if not found + [] + let FirstByField<'TDoc when 'TDoc: null>(tableName, field) = + Find.FirstByFields<'TDoc>(tableName, Any, Seq.singleton field) + + +[] +module Patch = + + /// Patch documents using a JSON field comparison query in the WHERE clause (->> =) + [] + [] + let byField tableName field (patch: 'TPatch) = + Patch.byFields tableName Any [ field ] patch + + +[] +module RemoveFields = + + /// Remove fields from documents via a comparison on a JSON field in the document + [] + [] + let byField tableName field fieldNames = + RemoveFields.byFields tableName Any [ field ] fieldNames + + +[] +module Delete = + + /// Delete documents by matching a JSON field comparison query (->> =) + [] + [] + let byField tableName field = + Delete.byFields tableName Any [ field ] + + +open Npgsql + +/// F# Extensions for the NpgsqlConnection type +[] +module Extensions = + + type NpgsqlConnection with + + /// Count matching documents using a JSON field comparison query (->> =) + [] + member conn.countByField tableName field = + conn.countByFields tableName Any [ field ] + + /// Determine if documents exist using a JSON field comparison query (->> =) + [] + member conn.existsByField tableName field = + conn.existsByFields tableName Any [ field ] + + /// Retrieve documents matching a JSON field comparison query (->> =) + [] + member conn.findByField<'TDoc> tableName field = + conn.findByFields<'TDoc> tableName Any [ field ] + + /// Retrieve the first document matching a JSON field comparison query (->> =); returns None if not found + [] + member conn.findFirstByField<'TDoc> tableName field = + conn.findFirstByFields<'TDoc> tableName Any [ field ] + + /// Patch documents using a JSON field comparison query in the WHERE clause (->> =) + [] + member conn.patchByField tableName field (patch: 'TPatch) = + conn.patchByFields tableName Any [ field ] patch + + /// Remove fields from documents via a comparison on a JSON field in the document + [] + member conn.removeFieldsByField tableName field fieldNames = + conn.removeFieldsByFields tableName Any [ field ] fieldNames + + /// Delete documents by matching a JSON field comparison query (->> =) + [] + member conn.deleteByField tableName field = + conn.deleteByFields tableName Any [ field ] + + +open System.Runtime.CompilerServices +open Npgsql.FSharp + +type NpgsqlConnectionCSharpCompatExtensions = + + /// Count matching documents using a JSON field comparison query (->> =) + [] + [] + static member inline CountByField(conn, tableName, field) = + WithProps.Count.byFields tableName Any [ field ] (Sql.existingConnection conn) + + /// Determine if documents exist using a JSON field comparison query (->> =) + [] + [] + static member inline ExistsByField(conn, tableName, field) = + WithProps.Exists.byFields tableName Any [ field ] (Sql.existingConnection conn) + + /// Retrieve documents matching a JSON field comparison query (->> =) + [] + [] + static member inline FindByField<'TDoc>(conn, tableName, field) = + WithProps.Find.ByFields<'TDoc>(tableName, Any, [ field ], Sql.existingConnection conn) + + /// Retrieve the first document matching a JSON field comparison query (->> =); returns null if not found + [] + [] + static member inline FindFirstByField<'TDoc when 'TDoc: null>(conn, tableName, field) = + WithProps.Find.FirstByFields<'TDoc>(tableName, Any, [ field ], Sql.existingConnection conn) + + /// Patch documents using a JSON field comparison query in the WHERE clause (->> =) + [] + [] + static member inline PatchByField(conn, tableName, field, patch: 'TPatch) = + WithProps.Patch.byFields tableName Any [ field ] patch (Sql.existingConnection conn) + + /// Remove fields from documents via a comparison on a JSON field in the document + [] + [] + static member inline RemoveFieldsByField(conn, tableName, field, fieldNames) = + WithProps.RemoveFields.byFields tableName Any [ field ] fieldNames (Sql.existingConnection conn) + + /// Delete documents by matching a JSON field comparison query (->> =) + [] + [] + static member inline DeleteByField(conn, tableName, field) = + WithProps.Delete.byFields tableName Any [ field ] (Sql.existingConnection conn) diff --git a/src/Postgres/Library.fs b/src/Postgres/Library.fs index b5abc7d..54c4d77 100644 --- a/src/Postgres/Library.fs +++ b/src/Postgres/Library.fs @@ -104,24 +104,12 @@ module Parameters = |> Seq.toList |> Seq.ofList - /// Create a JSON field parameter - [] - [] - let addFieldParam name field parameters = - addFieldParams [ { field with ParameterName = Some name } ] parameters - /// Append JSON field name parameters for the given field names to the given parameters [] let fieldNameParams (fieldNames: string seq) = if Seq.length fieldNames = 1 then "@name", Sql.string (Seq.head fieldNames) else "@name", Sql.stringArray (Array.ofSeq fieldNames) - /// Append JSON field name parameters for the given field names to the given parameters - [] - [] - let fieldNameParam fieldNames = - fieldNameParams fieldNames - /// An empty parameter sequence [] let noParams = diff --git a/src/Sqlite/BitBadger.Documents.Sqlite.fsproj b/src/Sqlite/BitBadger.Documents.Sqlite.fsproj index 7d46603..f51a328 100644 --- a/src/Sqlite/BitBadger.Documents.Sqlite.fsproj +++ b/src/Sqlite/BitBadger.Documents.Sqlite.fsproj @@ -8,6 +8,7 @@ + diff --git a/src/Sqlite/Compat.fs b/src/Sqlite/Compat.fs new file mode 100644 index 0000000..7288470 --- /dev/null +++ b/src/Sqlite/Compat.fs @@ -0,0 +1,269 @@ +namespace BitBadger.Documents.Sqlite.Compat + +open BitBadger.Documents +open BitBadger.Documents.Sqlite + +[] +module Parameters = + + /// Create a JSON field parameter + [] + [] + let addFieldParam name field parameters = + addFieldParams [ { field with ParameterName = Some name } ] parameters + + /// Append JSON field name parameters for the given field names to the given parameters + [] + [] + let fieldNameParam fieldNames = + fieldNameParams fieldNames + + +[] +module Query = + + /// Create a WHERE clause fragment to implement a comparison on a field in a JSON document + [] + [] + let whereByField field paramName = + Query.whereByFields Any [ { field with ParameterName = Some paramName } ] + + +module WithConn = + + [] + module Count = + + /// Count matching documents using a JSON field comparison (->> =) + [] + [] + let byField tableName field conn = + WithConn.Count.byFields tableName Any [ field ] conn + + [] + module Exists = + + /// Determine if a document exists using a JSON field comparison (->> =) + [] + [] + let byField tableName field conn = + WithConn.Exists.byFields tableName Any [ field ] conn + + [] + module Find = + + /// Retrieve documents matching a JSON field comparison (->> =) + [] + [] + let byField<'TDoc> tableName field conn = + WithConn.Find.byFields<'TDoc> tableName Any [ field ] conn + + /// Retrieve documents matching a JSON field comparison (->> =) + [] + let ByField<'TDoc>(tableName, field, conn) = + WithConn.Find.ByFields<'TDoc>(tableName, Any, Seq.singleton field, conn) + + /// Retrieve the first document matching a JSON field comparison (->> =); returns None if not found + [] + [] + let firstByField<'TDoc> tableName field conn = + WithConn.Find.firstByFields<'TDoc> tableName Any [ field ] conn + + /// Retrieve the first document matching a JSON field comparison (->> =); returns null if not found + [] + let FirstByField<'TDoc when 'TDoc: null>(tableName, field, conn) = + WithConn.Find.FirstByFields<'TDoc>(tableName, Any, Seq.singleton field, conn) + + [] + module Patch = + + /// Patch documents using a JSON field comparison query in the WHERE clause (->> =) + [] + [] + let byField tableName field (patch: 'TPatch) conn = + WithConn.Patch.byFields tableName Any [ field ] patch conn + + [] + module RemoveFields = + + /// Remove fields from documents via a comparison on a JSON field in the document + [] + [] + let byField tableName field fieldNames conn = + WithConn.RemoveFields.byFields tableName Any [ field ] fieldNames conn + + [] + module Delete = + + /// Delete documents by matching a JSON field comparison query (->> =) + [] + [] + let byField tableName field conn = + WithConn.Delete.byFields tableName Any [ field ] conn + + +[] +module Count = + + /// Count matching documents using a JSON field comparison (->> =) + [] + [] + let byField tableName field = + Count.byFields tableName Any [ field ] + + +[] +module Exists = + + /// Determine if a document exists using a JSON field comparison (->> =) + [] + [] + let byField tableName field = + Exists.byFields tableName Any [ field ] + + +[] +module Find = + + /// Retrieve documents matching a JSON field comparison (->> =) + [] + [] + let byField<'TDoc> tableName field = + Find.byFields<'TDoc> tableName Any [ field ] + + /// Retrieve documents matching a JSON field comparison (->> =) + [] + let ByField<'TDoc>(tableName, field) = + Find.ByFields<'TDoc>(tableName, Any, Seq.singleton field) + + /// Retrieve the first document matching a JSON field comparison (->> =); returns None if not found + [] + [] + let firstByField<'TDoc> tableName field = + Find.firstByFields<'TDoc> tableName Any [ field ] + + /// Retrieve the first document matching a JSON field comparison (->> =); returns null if not found + [] + let FirstByField<'TDoc when 'TDoc: null>(tableName, field) = + Find.FirstByFields<'TDoc>(tableName, Any, Seq.singleton field) + + +[] +module Patch = + + /// Patch documents using a JSON field comparison query in the WHERE clause (->> =) + [] + [] + let byField tableName field (patch: 'TPatch) = + Patch.byFields tableName Any [ field ] patch + + +[] +module RemoveFields = + + /// Remove fields from documents via a comparison on a JSON field in the document + [] + [] + let byField tableName field fieldNames = + RemoveFields.byFields tableName Any [ field ] fieldNames + + +[] +module Delete = + + /// Delete documents by matching a JSON field comparison query (->> =) + [] + [] + let byField tableName field = + Delete.byFields tableName Any [ field ] + + +open Microsoft.Data.Sqlite + +/// F# Extensions for the NpgsqlConnection type +[] +module Extensions = + + type SqliteConnection with + + /// Count matching documents using a JSON field comparison query (->> =) + [] + member conn.countByField tableName field = + conn.countByFields tableName Any [ field ] + + /// Determine if documents exist using a JSON field comparison query (->> =) + [] + member conn.existsByField tableName field = + conn.existsByFields tableName Any [ field ] + + /// Retrieve documents matching a JSON field comparison query (->> =) + [] + member conn.findByField<'TDoc> tableName field = + conn.findByFields<'TDoc> tableName Any [ field ] + + /// Retrieve the first document matching a JSON field comparison query (->> =); returns None if not found + [] + member conn.findFirstByField<'TDoc> tableName field = + conn.findFirstByFields<'TDoc> tableName Any [ field ] + + /// Patch documents using a JSON field comparison query in the WHERE clause (->> =) + [] + member conn.patchByField tableName field (patch: 'TPatch) = + conn.patchByFields tableName Any [ field ] patch + + /// Remove fields from documents via a comparison on a JSON field in the document + [] + member conn.removeFieldsByField tableName field fieldNames = + conn.removeFieldsByFields tableName Any [ field ] fieldNames + + /// Delete documents by matching a JSON field comparison query (->> =) + [] + member conn.deleteByField tableName field = + conn.deleteByFields tableName Any [ field ] + + +open System.Runtime.CompilerServices + +type SqliteConnectionCSharpCompatExtensions = + + /// Count matching documents using a JSON field comparison query (->> =) + [] + [] + static member inline CountByField(conn, tableName, field) = + WithConn.Count.byFields tableName Any [ field ] conn + + /// Determine if documents exist using a JSON field comparison query (->> =) + [] + [] + static member inline ExistsByField(conn, tableName, field) = + WithConn.Exists.byFields tableName Any [ field ] conn + + /// Retrieve documents matching a JSON field comparison query (->> =) + [] + [] + static member inline FindByField<'TDoc>(conn, tableName, field) = + WithConn.Find.ByFields<'TDoc>(tableName, Any, [ field ], conn) + + /// Retrieve the first document matching a JSON field comparison query (->> =); returns null if not found + [] + [] + static member inline FindFirstByField<'TDoc when 'TDoc: null>(conn, tableName, field) = + WithConn.Find.FirstByFields<'TDoc>(tableName, Any, [ field ], conn) + + /// Patch documents using a JSON field comparison query in the WHERE clause (->> =) + [] + [] + static member inline PatchByField(conn, tableName, field, patch: 'TPatch) = + WithConn.Patch.byFields tableName Any [ field ] patch conn + + /// Remove fields from documents via a comparison on a JSON field in the document + [] + [] + static member inline RemoveFieldsByField(conn, tableName, field, fieldNames) = + WithConn.RemoveFields.byFields tableName Any [ field ] fieldNames conn + + /// Delete documents by matching a JSON field comparison query (->> =) + [] + [] + static member inline DeleteByField(conn, tableName, field) = + WithConn.Delete.byFields tableName Any [ field ] conn diff --git a/src/Tests.CSharp/PostgresCSharpTests.cs b/src/Tests.CSharp/PostgresCSharpTests.cs index 93c3b4d..38324af 100644 --- a/src/Tests.CSharp/PostgresCSharpTests.cs +++ b/src/Tests.CSharp/PostgresCSharpTests.cs @@ -170,30 +170,7 @@ public static class PostgresCSharpTests Expect.isTrue(false, "The parameter was not a StringArray type"); } }) - ]), -#pragma warning disable CS0618 - TestList("FieldName", - [ - TestCase("succeeds for one name", () => - { - var (name, value) = Parameters.FieldName(["bob"]); - Expect.equal(name, "@name", "The parameter name was incorrect"); - if (!value.IsString) - { - Expect.isTrue(false, "The parameter was not a String type"); - } - }), - TestCase("succeeds for multiple names", () => - { - var (name, value) = Parameters.FieldName(["bob", "tom", "mike"]); - Expect.equal(name, "@name", "The parameter name was incorrect"); - if (!value.IsStringArray) - { - Expect.isTrue(false, "The parameter was not a StringArray type"); - } - }) ]) -#pragma warning restore CS0618 ]); /// diff --git a/src/Tests/PostgresTests.fs b/src/Tests/PostgresTests.fs index 676c8ef..ee31a27 100644 --- a/src/Tests/PostgresTests.fs +++ b/src/Tests/PostgresTests.fs @@ -5,8 +5,6 @@ open BitBadger.Documents open BitBadger.Documents.Postgres open BitBadger.Documents.Tests -#nowarn "0044" - (** UNIT TESTS **) /// Unit tests for the Parameters module of the PostgreSQL library @@ -129,18 +127,6 @@ let parametersTests = testList "Parameters" [ Expect.equal value (Sql.stringArray [| "bob"; "tom"; "mike" |]) "The parameter value was incorrect" } ] - testList "fieldNameParam" [ - test "succeeds for one name" { - let name, value = fieldNameParam [ "bob" ] - Expect.equal name "@name" "The parameter name was incorrect" - Expect.equal value (Sql.string "bob") "The parameter value was incorrect" - } - test "succeeds for multiple names" { - let name, value = fieldNameParam [ "bob"; "tom"; "mike" ] - Expect.equal name "@name" "The parameter name was incorrect" - Expect.equal value (Sql.stringArray [| "bob"; "tom"; "mike" |]) "The parameter value was incorrect" - } - ] test "noParams succeeds" { Expect.isEmpty noParams "The no-params sequence should be empty" }