diff --git a/src/Sqlite/Extensions.fs b/src/Sqlite/Extensions.fs index 91304b5..89fab23 100644 --- a/src/Sqlite/Extensions.fs +++ b/src/Sqlite/Extensions.fs @@ -88,6 +88,14 @@ module Extensions = member conn.patchByField tableName fieldName op (value: obj) (patch: 'TPatch) = WithConn.Patch.byField tableName fieldName op value patch conn + /// Remove a field from a document by the document's ID + member conn.removeFieldById tableName (docId: 'TKey) fieldName = + WithConn.RemoveField.byId tableName docId fieldName conn + + /// Remove a field from a document via a comparison on a JSON field in the document + member conn.removeFieldByField tableName whereFieldName op (value: obj) removeFieldName = + WithConn.RemoveField.byField tableName whereFieldName op value removeFieldName conn + /// Delete a document by its ID member conn.deleteById tableName (docId: 'TKey) = WithConn.Delete.byId tableName docId conn @@ -204,6 +212,16 @@ type SqliteConnectionCSharpExtensions = static member inline PatchByField<'TPatch>(conn, tableName, fieldName, op, value: obj, patch: 'TPatch) = WithConn.Patch.byField tableName fieldName op value patch conn + /// Remove a field from a document by the document's ID + [] + static member inline RemoveFieldById<'TKey>(conn, tableName, docId: 'TKey, fieldName) = + WithConn.RemoveField.byId tableName docId fieldName conn + + /// Remove a field from a document via a comparison on a JSON field in the document + [] + static member inline RemoveFieldByField(conn, tableName, whereFieldName, op, value: obj, removeFieldName) = + WithConn.RemoveField.byField tableName whereFieldName op value removeFieldName conn + /// Delete a document by its ID [] static member inline DeleteById<'TKey>(conn, tableName, docId: 'TKey) = diff --git a/src/Tests.CSharp/SqliteCSharpExtensionTests.cs b/src/Tests.CSharp/SqliteCSharpExtensionTests.cs index cc5fb5f..89e2178 100644 --- a/src/Tests.CSharp/SqliteCSharpExtensionTests.cs +++ b/src/Tests.CSharp/SqliteCSharpExtensionTests.cs @@ -467,6 +467,68 @@ public static class SqliteCSharpExtensionTests await conn.PatchByField(SqliteDb.TableName, "Value", Op.EQ, "burgundy", new { Foo = "green" }); }) }), + TestList("RemoveFieldById", new[] + { + TestCase("succeeds when a field is removed", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + await conn.RemoveFieldById(SqliteDb.TableName, "two", "Sub"); + var updated = await Find.ById(SqliteDb.TableName, "two"); + Expect.isNotNull(updated, "The updated document should have been retrieved"); + Expect.isNull(updated.Sub, "The sub-document should have been removed"); + }), + TestCase("succeeds when a field is not removed", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + // This not raising an exception is the test + await conn.RemoveFieldById(SqliteDb.TableName, "two", "AFieldThatIsNotThere"); + }), + TestCase("succeeds when no document is matched", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + + // This not raising an exception is the test + await conn.RemoveFieldById(SqliteDb.TableName, "two", "Value"); + }) + }), + TestList("RemoveFieldByField", new[] + { + TestCase("succeeds when a field is removed", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + await conn.RemoveFieldByField(SqliteDb.TableName, "NumValue", Op.EQ, 17, "Sub"); + var updated = await Find.ById(SqliteDb.TableName, "four"); + Expect.isNotNull(updated, "The updated document should have been retrieved"); + Expect.isNull(updated.Sub, "The sub-document should have been removed"); + }), + TestCase("succeeds when a field is not removed", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + await LoadDocs(); + + // This not raising an exception is the test + await conn.RemoveFieldByField(SqliteDb.TableName, "NumValue", Op.EQ, 17, "Nothing"); + }), + TestCase("succeeds when no document is matched", async () => + { + await using var db = await SqliteDb.BuildDb(); + await using var conn = Sqlite.Configuration.DbConn(); + + // This not raising an exception is the test + await conn.RemoveFieldByField(SqliteDb.TableName, "Abracadabra", Op.NE, "apple", "Value"); + }) + }), TestList("DeleteById", new[] { TestCase("succeeds when a document is deleted", async () => diff --git a/src/Tests.CSharp/SqliteCSharpTests.cs b/src/Tests.CSharp/SqliteCSharpTests.cs index 91fb9ce..cd7440d 100644 --- a/src/Tests.CSharp/SqliteCSharpTests.cs +++ b/src/Tests.CSharp/SqliteCSharpTests.cs @@ -585,6 +585,34 @@ public static class SqliteCSharpTests // This not raising an exception is the test await RemoveField.ById(SqliteDb.TableName, "two", "Value"); }) + }), + TestList("ByField", new[] + { + TestCase("succeeds when a field is removed", async () => + { + await using var db = await SqliteDb.BuildDb(); + await LoadDocs(); + + await RemoveField.ByField(SqliteDb.TableName, "NumValue", Op.EQ, 17, "Sub"); + var updated = await Find.ById(SqliteDb.TableName, "four"); + Expect.isNotNull(updated, "The updated document should have been retrieved"); + Expect.isNull(updated.Sub, "The sub-document should have been removed"); + }), + TestCase("succeeds when a field is not removed", async () => + { + await using var db = await SqliteDb.BuildDb(); + await LoadDocs(); + + // This not raising an exception is the test + await RemoveField.ByField(SqliteDb.TableName, "NumValue", Op.EQ, 17, "Nothing"); + }), + TestCase("succeeds when no document is matched", async () => + { + await using var db = await SqliteDb.BuildDb(); + + // This not raising an exception is the test + await RemoveField.ByField(SqliteDb.TableName, "Abracadabra", Op.NE, "apple", "Value"); + }) }) }), TestList("Delete", new[] diff --git a/src/Tests/SqliteExtensionTests.fs b/src/Tests/SqliteExtensionTests.fs index 4c6e2b5..b7586e3 100644 --- a/src/Tests/SqliteExtensionTests.fs +++ b/src/Tests/SqliteExtensionTests.fs @@ -1,5 +1,6 @@ module SqliteExtensionTests +open System.Text.Json open BitBadger.Documents open BitBadger.Documents.Sqlite open BitBadger.Documents.Tests @@ -344,6 +345,66 @@ let integrationTests = do! conn.patchByField SqliteDb.TableName "Value" EQ "burgundy" {| Foo = "green" |} } ] + testList "removeFieldById" [ + testTask "succeeds when a field is removed" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + do! conn.removeFieldById SqliteDb.TableName "two" "Sub" + try + let! _ = conn.findById SqliteDb.TableName "two" + Expect.isTrue false "The updated document should have failed to parse" + with + | :? JsonException -> () + | exn as ex -> Expect.isTrue false $"Threw {ex.GetType().Name} ({ex.Message})" + } + testTask "succeeds when a field is not removed" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + // This not raising an exception is the test + do! conn.removeFieldById SqliteDb.TableName "two" "AFieldThatIsNotThere" + } + testTask "succeeds when no document is matched" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + + // This not raising an exception is the test + do! conn.removeFieldById SqliteDb.TableName "two" "Value" + } + ] + testList "removeFieldByField" [ + testTask "succeeds when a field is removed" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + do! conn.removeFieldByField SqliteDb.TableName "NumValue" EQ 17 "Sub" + try + let! _ = conn.findById SqliteDb.TableName "four" + Expect.isTrue false "The updated document should have failed to parse" + with + | :? JsonException -> () + | exn as ex -> Expect.isTrue false $"Threw {ex.GetType().Name} ({ex.Message})" + } + testTask "succeeds when a field is not removed" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + do! loadDocs () + + // This not raising an exception is the test + do! conn.removeFieldByField SqliteDb.TableName "NumValue" EQ 17 "Nothing" + } + testTask "succeeds when no document is matched" { + use! db = SqliteDb.BuildDb() + use conn = Configuration.dbConn () + + // This not raising an exception is the test + do! conn.removeFieldByField SqliteDb.TableName "Abracadabra" NE "apple" "Value" + } + ] testList "deleteById" [ testTask "succeeds when a document is deleted" { use! db = SqliteDb.BuildDb() diff --git a/src/Tests/SqliteTests.fs b/src/Tests/SqliteTests.fs index 582b2b1..613d0ac 100644 --- a/src/Tests/SqliteTests.fs +++ b/src/Tests/SqliteTests.fs @@ -532,6 +532,33 @@ let integrationTests = do! RemoveField.byId SqliteDb.TableName "two" "Value" } ] + testList "byField" [ + testTask "succeeds when a field is removed" { + use! db = SqliteDb.BuildDb() + do! loadDocs () + + do! RemoveField.byField SqliteDb.TableName "NumValue" EQ 17 "Sub" + try + let! _ = Find.byId SqliteDb.TableName "four" + Expect.isTrue false "The updated document should have failed to parse" + with + | :? JsonException -> () + | exn as ex -> Expect.isTrue false $"Threw {ex.GetType().Name} ({ex.Message})" + } + testTask "succeeds when a field is not removed" { + use! db = SqliteDb.BuildDb() + do! loadDocs () + + // This not raising an exception is the test + do! RemoveField.byField SqliteDb.TableName "NumValue" EQ 17 "Nothing" + } + testTask "succeeds when no document is matched" { + use! db = SqliteDb.BuildDb() + + // This not raising an exception is the test + do! RemoveField.byField SqliteDb.TableName "Abracadabra" NE "apple" "Value" + } + ] ] testList "Delete" [ testList "byId" [