- Implements `Field` type for by-field actions (**BREAKING**)
- Adds `RemoveFields*` functions/methods for removing fields from documents
This commit was merged in pull request #2.
This commit is contained in:
2024-01-23 21:23:24 -05:00
committed by GitHub
parent 68ad874256
commit 06daa4ea5c
19 changed files with 1701 additions and 291 deletions

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageReleaseNotes>Initial release; SQLite document implementation similar to BitBadger.Npgsql.Documents (RC 1)</PackageReleaseNotes>
<PackageReleaseNotes>Adds Field type for by-field operations (BREAKING from rc-1); adds RemoveFields* functions</PackageReleaseNotes>
<PackageTags>JSON Document SQLite</PackageTags>
</PropertyGroup>
@@ -13,7 +13,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.0" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -45,16 +45,16 @@ module Extensions =
WithConn.Count.all tableName conn
/// Count matching documents using a comparison on a JSON field
member conn.countByField tableName fieldName op (value: obj) =
WithConn.Count.byField tableName fieldName op value conn
member conn.countByField tableName field =
WithConn.Count.byField tableName field conn
/// Determine if a document exists for the given ID
member conn.existsById tableName (docId: 'TKey) =
WithConn.Exists.byId tableName docId conn
/// Determine if a document exists using a comparison on a JSON field
member conn.existsByField tableName fieldName op (value: obj) =
WithConn.Exists.byField tableName fieldName op value conn
member conn.existsByField tableName field =
WithConn.Exists.byField tableName field conn
/// Retrieve all documents in the given table
member conn.findAll<'TDoc> tableName =
@@ -65,12 +65,12 @@ module Extensions =
WithConn.Find.byId<'TKey, 'TDoc> tableName docId conn
/// Retrieve documents via a comparison on a JSON field
member conn.findByField<'TDoc> tableName fieldName op (value: obj) =
WithConn.Find.byField<'TDoc> tableName fieldName op value conn
member conn.findByField<'TDoc> tableName field =
WithConn.Find.byField<'TDoc> tableName field conn
/// Retrieve documents via a comparison on a JSON field, returning only the first result
member conn.findFirstByField<'TDoc> tableName fieldName op (value: obj) =
WithConn.Find.firstByField<'TDoc> tableName fieldName op value conn
member conn.findFirstByField<'TDoc> tableName field =
WithConn.Find.firstByField<'TDoc> tableName field conn
/// Update an entire document by its ID
member conn.updateById tableName (docId: 'TKey) (document: 'TDoc) =
@@ -85,16 +85,24 @@ module Extensions =
WithConn.Patch.byId tableName docId patch conn
/// Patch documents using a comparison on a JSON field
member conn.patchByField tableName fieldName op (value: obj) (patch: 'TPatch) =
WithConn.Patch.byField tableName fieldName op value patch conn
member conn.patchByField tableName field (patch: 'TPatch) =
WithConn.Patch.byField tableName field patch conn
/// Remove fields from a document by the document's ID
member conn.removeFieldsById tableName (docId: 'TKey) fieldNames =
WithConn.RemoveFields.byId tableName docId fieldNames conn
/// Remove a field from a document via a comparison on a JSON field in the document
member conn.removeFieldsByField tableName field fieldNames =
WithConn.RemoveFields.byField tableName field fieldNames conn
/// Delete a document by its ID
member conn.deleteById tableName (docId: 'TKey) =
WithConn.Delete.byId tableName docId conn
/// Delete documents by matching a comparison on a JSON field
member conn.deleteByField tableName fieldName op (value: obj) =
WithConn.Delete.byField tableName fieldName op value conn
member conn.deleteByField tableName field =
WithConn.Delete.byField tableName field conn
open System.Runtime.CompilerServices
@@ -151,8 +159,8 @@ type SqliteConnectionCSharpExtensions =
/// Count matching documents using a comparison on a JSON field
[<Extension>]
static member inline CountByField(conn, tableName, fieldName, op, value: obj) =
WithConn.Count.byField tableName fieldName op value conn
static member inline CountByField(conn, tableName, field) =
WithConn.Count.byField tableName field conn
/// Determine if a document exists for the given ID
[<Extension>]
@@ -161,8 +169,8 @@ type SqliteConnectionCSharpExtensions =
/// Determine if a document exists using a comparison on a JSON field
[<Extension>]
static member inline ExistsByField(conn, tableName, fieldName, op, value: obj) =
WithConn.Exists.byField tableName fieldName op value conn
static member inline ExistsByField(conn, tableName, field) =
WithConn.Exists.byField tableName field conn
/// Retrieve all documents in the given table
[<Extension>]
@@ -176,13 +184,13 @@ type SqliteConnectionCSharpExtensions =
/// Retrieve documents via a comparison on a JSON field
[<Extension>]
static member inline FindByField<'TDoc>(conn, tableName, fieldName, op, value) =
WithConn.Find.ByField<'TDoc>(tableName, fieldName, op, value, conn)
static member inline FindByField<'TDoc>(conn, tableName, field) =
WithConn.Find.ByField<'TDoc>(tableName, field, conn)
/// Retrieve documents via a comparison on a JSON field, returning only the first result
[<Extension>]
static member inline FindFirstByField<'TDoc when 'TDoc: null>(conn, tableName, fieldName, op, value: obj) =
WithConn.Find.FirstByField<'TDoc>(tableName, fieldName, op, value, conn)
static member inline FindFirstByField<'TDoc when 'TDoc: null>(conn, tableName, field) =
WithConn.Find.FirstByField<'TDoc>(tableName, field, conn)
/// Update an entire document by its ID
[<Extension>]
@@ -201,9 +209,19 @@ type SqliteConnectionCSharpExtensions =
/// Patch documents using a comparison on a JSON field
[<Extension>]
static member inline PatchByField<'TPatch>(conn, tableName, fieldName, op, value: obj, patch: 'TPatch) =
WithConn.Patch.byField tableName fieldName op value patch conn
static member inline PatchByField<'TPatch>(conn, tableName, field, patch: 'TPatch) =
WithConn.Patch.byField tableName field patch conn
/// Remove fields from a document by the document's ID
[<Extension>]
static member inline RemoveFieldsById<'TKey>(conn, tableName, docId: 'TKey, fieldNames) =
WithConn.RemoveFields.ById(tableName, docId, fieldNames, conn)
/// Remove fields from documents via a comparison on a JSON field in the document
[<Extension>]
static member inline RemoveFieldsByField(conn, tableName, field, fieldNames) =
WithConn.RemoveFields.ByField(tableName, field, fieldNames, conn)
/// Delete a document by its ID
[<Extension>]
static member inline DeleteById<'TKey>(conn, tableName, docId: 'TKey) =
@@ -211,5 +229,5 @@ type SqliteConnectionCSharpExtensions =
/// Delete documents by matching a comparison on a JSON field
[<Extension>]
static member inline DeleteByField(conn, tableName, fieldName, op, value: obj) =
WithConn.Delete.byField tableName fieldName op value conn
static member inline DeleteByField(conn, tableName, field) =
WithConn.Delete.byField tableName field conn

View File

@@ -12,7 +12,7 @@ module Configuration =
/// Register a connection string to use for query execution (enables foreign keys)
[<CompiledName "UseConnectionString">]
let useConnectionString connStr =
let builder = SqliteConnectionStringBuilder(connStr)
let builder = SqliteConnectionStringBuilder connStr
builder.ForeignKeys <- Option.toNullable (Some true)
connectionString <- Some (string builder)
@@ -42,17 +42,45 @@ module Query =
/// Document patching (partial update) queries
module Patch =
/// Create an UPDATE statement to patch documents
let internal update tableName whereClause =
$"UPDATE %s{tableName} SET data = json_patch(data, json(@data)) WHERE %s{whereClause}"
/// Query to patch (partially update) a document by its ID
[<CompiledName "ById">]
let byId tableName =
$"""UPDATE %s{tableName} SET data = json_patch(data, json(@data)) WHERE {Query.whereById "@id"}"""
Query.whereById "@id" |> update tableName
/// Query to patch (partially update) a document via a comparison on a JSON field
[<CompiledName "ByField">]
let byField tableName fieldName op =
sprintf
"UPDATE %s SET data = json_patch(data, json(@data)) WHERE %s"
tableName (Query.whereByField fieldName op "@field")
let byField tableName field =
Query.whereByField field "@field" |> update tableName
/// Queries to remove fields from documents
module RemoveFields =
/// Create an UPDATE statement to remove parameters
let internal update tableName (parameters: SqliteParameter list) whereClause =
let paramNames = parameters |> List.map _.ParameterName |> String.concat ", "
$"UPDATE %s{tableName} SET data = json_remove(data, {paramNames}) WHERE {whereClause}"
/// Query to remove fields from a document by the document's ID
[<CompiledName "FSharpById">]
let byId tableName parameters =
Query.whereById "@id" |> update tableName parameters
/// Query to remove fields from a document by the document's ID
let ById(tableName, parameters) =
byId tableName (List.ofSeq parameters)
/// Query to remove fields from documents via a comparison on a JSON field within the document
[<CompiledName "FSharpByField">]
let byField tableName field parameters =
Query.whereByField field "@field" |> update tableName parameters
/// Query to remove fields from documents via a comparison on a JSON field within the document
let ByField(tableName, field, parameters) =
byField tableName field (List.ofSeq parameters)
/// Parameter handling helpers
@@ -70,9 +98,24 @@ module Parameters =
SqliteParameter(name, Configuration.serializer().Serialize it)
/// Create a JSON field parameter (name "@field")
[<CompiledName "Field">]
let fieldParam (value: obj) =
SqliteParameter("@field", value)
[<CompiledName "FSharpAddField">]
let addFieldParam name field parameters =
match field.Op with EX | NEX -> parameters | _ -> SqliteParameter(name, field.Value) :: parameters
/// Create a JSON field parameter (name "@field")
let AddField(name, field, parameters) =
match field.Op with
| EX | NEX -> parameters
| _ -> SqliteParameter(name, field.Value) |> Seq.singleton |> Seq.append parameters
/// Append JSON field name parameters for the given field names to the given parameters
[<CompiledName "FSharpFieldNames">]
let fieldNameParams paramName (fieldNames: string list) =
fieldNames |> List.mapi (fun idx name -> SqliteParameter($"%s{paramName}{idx}", $"$.{name}"))
/// Append JSON field name parameters for the given field names to the given parameters
let FieldNames(paramName, fieldNames: string seq) =
fieldNames |> Seq.mapi (fun idx name -> SqliteParameter($"%s{paramName}{idx}", $"$.{name}"))
/// An empty parameter sequence
[<CompiledName "None">]
@@ -223,8 +266,8 @@ module WithConn =
/// Count matching documents using a comparison on a JSON field
[<CompiledName "ByField">]
let byField tableName fieldName op (value: obj) conn =
Custom.scalar (Query.Count.byField tableName fieldName op) [ fieldParam value ] toCount conn
let byField tableName field conn =
Custom.scalar (Query.Count.byField tableName field) (addFieldParam "@field" field []) toCount conn
/// Commands to determine if documents exist
[<RequireQualifiedAccess>]
@@ -237,8 +280,8 @@ module WithConn =
/// Determine if a document exists using a comparison on a JSON field
[<CompiledName "ByField">]
let byField tableName fieldName op (value: obj) conn =
Custom.scalar (Query.Exists.byField tableName fieldName op) [ fieldParam value ] toExists conn
let byField tableName field conn =
Custom.scalar (Query.Exists.byField tableName field) (addFieldParam "@field" field []) toExists conn
/// Commands to retrieve documents
[<RequireQualifiedAccess>]
@@ -264,23 +307,25 @@ module WithConn =
/// Retrieve documents via a comparison on a JSON field
[<CompiledName "FSharpByField">]
let byField<'TDoc> tableName fieldName op (value: obj) conn =
Custom.list<'TDoc> (Query.Find.byField tableName fieldName op) [ fieldParam value ] fromData<'TDoc> conn
let byField<'TDoc> tableName field conn =
Custom.list<'TDoc>
(Query.Find.byField tableName field) (addFieldParam "@field" field []) fromData<'TDoc> conn
/// Retrieve documents via a comparison on a JSON field
let ByField<'TDoc>(tableName, fieldName, op, value: obj, conn) =
Custom.List<'TDoc>(Query.Find.byField tableName fieldName op, [ fieldParam value ], fromData<'TDoc>, conn)
let ByField<'TDoc>(tableName, field, conn) =
Custom.List<'TDoc>(
Query.Find.byField tableName field, addFieldParam "@field" field [], fromData<'TDoc>, conn)
/// Retrieve documents via a comparison on a JSON field, returning only the first result
[<CompiledName "FSharpFirstByField">]
let firstByField<'TDoc> tableName fieldName op (value: obj) conn =
let firstByField<'TDoc> tableName field conn =
Custom.single
$"{Query.Find.byField tableName fieldName op} LIMIT 1" [ fieldParam value ] fromData<'TDoc> conn
$"{Query.Find.byField tableName field} LIMIT 1" (addFieldParam "@field" field []) fromData<'TDoc> conn
/// Retrieve documents via a comparison on a JSON field, returning only the first result
let FirstByField<'TDoc when 'TDoc: null>(tableName, fieldName, op, value: obj, conn) =
let FirstByField<'TDoc when 'TDoc: null>(tableName, field, conn) =
Custom.Single(
$"{Query.Find.byField tableName fieldName op} LIMIT 1", [ fieldParam value ], fromData<'TDoc>, conn)
$"{Query.Find.byField tableName field} LIMIT 1", addFieldParam "@field" field [], fromData<'TDoc>, conn)
/// Commands to update documents
[<RequireQualifiedAccess>]
@@ -311,10 +356,35 @@ module WithConn =
/// Patch documents using a comparison on a JSON field
[<CompiledName "ByField">]
let byField tableName fieldName op (value: obj) (patch: 'TPatch) (conn: SqliteConnection) =
let byField tableName field (patch: 'TPatch) (conn: SqliteConnection) =
Custom.nonQuery
(Query.Patch.byField tableName fieldName op) [ fieldParam value; jsonParam "@data" patch ] conn
(Query.Patch.byField tableName field) (addFieldParam "@field" field [ jsonParam "@data" patch ]) conn
/// Commands to remove fields from documents
[<RequireQualifiedAccess>]
module RemoveFields =
/// Remove fields from a document by the document's ID
[<CompiledName "FSharpById">]
let byId tableName (docId: 'TKey) fieldNames conn =
let nameParams = fieldNameParams "@name" fieldNames
Custom.nonQuery (Query.RemoveFields.byId tableName nameParams) (idParam docId :: nameParams) conn
/// Remove fields from a document by the document's ID
let ById(tableName, docId: 'TKey, fieldNames, conn) =
byId tableName docId (List.ofSeq fieldNames) conn
/// Remove fields from documents via a comparison on a JSON field in the document
[<CompiledName "FSharpByField">]
let byField tableName field fieldNames conn =
let nameParams = fieldNameParams "@name" fieldNames
Custom.nonQuery
(Query.RemoveFields.byField tableName field nameParams) (addFieldParam "@field" field nameParams) conn
/// Remove fields from documents via a comparison on a JSON field in the document
let ByField(tableName, field, fieldNames, conn) =
byField tableName field (List.ofSeq fieldNames) conn
/// Commands to delete documents
[<RequireQualifiedAccess>]
module Delete =
@@ -326,8 +396,8 @@ module WithConn =
/// Delete documents by matching a comparison on a JSON field
[<CompiledName "ByField">]
let byField tableName fieldName op (value: obj) conn =
Custom.nonQuery (Query.Delete.byField tableName fieldName op) [ fieldParam value ] conn
let byField tableName field conn =
Custom.nonQuery (Query.Delete.byField tableName field) (addFieldParam "@field" field []) conn
/// Commands to execute custom SQL queries
@@ -417,9 +487,9 @@ module Count =
/// Count matching documents using a comparison on a JSON field
[<CompiledName "ByField">]
let byField tableName fieldName op (value: obj) =
let byField tableName field =
use conn = Configuration.dbConn ()
WithConn.Count.byField tableName fieldName op value conn
WithConn.Count.byField tableName field conn
/// Commands to determine if documents exist
[<RequireQualifiedAccess>]
@@ -433,9 +503,9 @@ module Exists =
/// Determine if a document exists using a comparison on a JSON field
[<CompiledName "ByField">]
let byField tableName fieldName op (value: obj) =
let byField tableName field =
use conn = Configuration.dbConn ()
WithConn.Exists.byField tableName fieldName op value conn
WithConn.Exists.byField tableName field conn
/// Commands to determine if documents exist
[<RequireQualifiedAccess>]
@@ -465,25 +535,25 @@ module Find =
/// Retrieve documents via a comparison on a JSON field
[<CompiledName "FSharpByField">]
let byField<'TDoc> tableName fieldName op value =
let byField<'TDoc> tableName field =
use conn = Configuration.dbConn ()
WithConn.Find.byField<'TDoc> tableName fieldName op value conn
WithConn.Find.byField<'TDoc> tableName field conn
/// Retrieve documents via a comparison on a JSON field
let ByField<'TDoc>(tableName, fieldName, op, value) =
let ByField<'TDoc>(tableName, field) =
use conn = Configuration.dbConn ()
WithConn.Find.ByField<'TDoc>(tableName, fieldName, op, value, conn)
WithConn.Find.ByField<'TDoc>(tableName, field, conn)
/// Retrieve documents via a comparison on a JSON field, returning only the first result
[<CompiledName "FSharpFirstByField">]
let firstByField<'TDoc> tableName fieldName op value =
let firstByField<'TDoc> tableName field =
use conn = Configuration.dbConn ()
WithConn.Find.firstByField<'TDoc> tableName fieldName op value conn
WithConn.Find.firstByField<'TDoc> tableName field conn
/// Retrieve documents via a comparison on a JSON field, returning only the first result
let FirstByField<'TDoc when 'TDoc: null>(tableName, fieldName, op, value) =
let FirstByField<'TDoc when 'TDoc: null>(tableName, field) =
use conn = Configuration.dbConn ()
WithConn.Find.FirstByField<'TDoc>(tableName, fieldName, op, value, conn)
WithConn.Find.FirstByField<'TDoc>(tableName, field, conn)
/// Commands to update documents
[<RequireQualifiedAccess>]
@@ -518,9 +588,35 @@ module Patch =
/// Patch documents using a comparison on a JSON field in the WHERE clause
[<CompiledName "ByField">]
let byField tableName fieldName op (value: obj) (patch: 'TPatch) =
let byField tableName field (patch: 'TPatch) =
use conn = Configuration.dbConn ()
WithConn.Patch.byField tableName fieldName op value patch conn
WithConn.Patch.byField tableName field patch conn
/// Commands to remove fields from documents
[<RequireQualifiedAccess>]
module RemoveFields =
/// Remove fields from a document by the document's ID
[<CompiledName "FSharpById">]
let byId tableName (docId: 'TKey) fieldNames =
use conn = Configuration.dbConn ()
WithConn.RemoveFields.byId tableName docId fieldNames conn
/// Remove fields from a document by the document's ID
let ById(tableName, docId: 'TKey, fieldNames) =
use conn = Configuration.dbConn ()
WithConn.RemoveFields.ById(tableName, docId, fieldNames, conn)
/// Remove field from documents via a comparison on a JSON field in the document
[<CompiledName "FSharpByField">]
let byField tableName field fieldNames =
use conn = Configuration.dbConn ()
WithConn.RemoveFields.byField tableName field fieldNames conn
/// Remove field from documents via a comparison on a JSON field in the document
let ByField(tableName, field, fieldNames) =
use conn = Configuration.dbConn ()
WithConn.RemoveFields.ByField(tableName, field, fieldNames, conn)
/// Commands to delete documents
[<RequireQualifiedAccess>]
@@ -534,6 +630,6 @@ module Delete =
/// Delete documents by matching a comparison on a JSON field
[<CompiledName "ByField">]
let byField tableName fieldName op (value: obj) =
let byField tableName field =
use conn = Configuration.dbConn ()
WithConn.Delete.byField tableName fieldName op value conn
WithConn.Delete.byField tableName field conn