Version 4 #6

Merged
danieljsummers merged 30 commits from version-four into main 2024-08-19 23:30:39 +00:00
10 changed files with 255 additions and 734 deletions
Showing only changes of commit 74e5b77edb - Show all commits

View File

@ -214,11 +214,6 @@ module Query =
let statementWhere statement where = let statementWhere statement where =
$"%s{statement} WHERE %s{where}" $"%s{statement} WHERE %s{where}"
/// Create a SELECT clause to retrieve the document data from the given table
[<CompiledName "SelectFromTable">]
let selectFromTable tableName =
$"SELECT data FROM %s{tableName}"
/// Queries to define tables and indexes /// Queries to define tables and indexes
module Definition = module Definition =
@ -263,11 +258,6 @@ module Query =
"INSERT INTO %s VALUES (@data) ON CONFLICT ((data->>'%s')) DO UPDATE SET data = EXCLUDED.data" "INSERT INTO %s VALUES (@data) ON CONFLICT ((data->>'%s')) DO UPDATE SET data = EXCLUDED.data"
tableName (Configuration.idField ()) tableName (Configuration.idField ())
/// Query to update a document (no WHERE clause)
[<CompiledName "Update">]
let update tableName =
$"UPDATE %s{tableName} SET data = @data"
/// Query to count documents in a table (no WHERE clause) /// Query to count documents in a table (no WHERE clause)
[<CompiledName "Count">] [<CompiledName "Count">]
let count tableName = let count tableName =
@ -277,3 +267,24 @@ module Query =
[<CompiledName "Exists">] [<CompiledName "Exists">]
let exists tableName where = let exists tableName where =
$"SELECT EXISTS (SELECT 1 FROM %s{tableName} WHERE %s{where}) AS it" $"SELECT EXISTS (SELECT 1 FROM %s{tableName} WHERE %s{where}) AS it"
/// Query to select documents from a table (no WHERE clause)
[<CompiledName "Find">]
let find tableName =
$"SELECT data FROM %s{tableName}"
/// Query to update a document (no WHERE clause)
[<CompiledName "Update">]
let update tableName =
$"UPDATE %s{tableName} SET data = @data"
/// Query to delete documents from a table (no WHERE clause)
[<CompiledName "Delete">]
let delete tableName =
$"DELETE FROM %s{tableName}"
/// Create a SELECT clause to retrieve the document data from the given table
[<CompiledName "SelectFromTable">]
[<System.Obsolete "Use Find instead">]
let selectFromTable tableName =
find tableName

View File

@ -396,7 +396,7 @@ type NpgsqlConnectionCSharpExtensions =
/// Remove fields from a document by the document's ID /// Remove fields from a document by the document's ID
[<Extension>] [<Extension>]
static member inline RemoveFieldsById(conn, tableName, docId: 'TKey, fieldNames) = static member inline RemoveFieldsById(conn, tableName, docId: 'TKey, fieldNames) =
WithProps.RemoveFields.ById(tableName, docId, fieldNames, Sql.existingConnection conn) WithProps.RemoveFields.byId tableName docId fieldNames (Sql.existingConnection conn)
/// Remove fields from documents via a comparison on JSON fields in the document /// Remove fields from documents via a comparison on JSON fields in the document
[<Extension>] [<Extension>]
@ -412,12 +412,12 @@ type NpgsqlConnectionCSharpExtensions =
/// Remove fields from documents via a JSON containment query (@>) /// Remove fields from documents via a JSON containment query (@>)
[<Extension>] [<Extension>]
static member inline RemoveFieldsByContains(conn, tableName, criteria: 'TContains, fieldNames) = static member inline RemoveFieldsByContains(conn, tableName, criteria: 'TContains, fieldNames) =
WithProps.RemoveFields.ByContains(tableName, criteria, fieldNames, Sql.existingConnection conn) WithProps.RemoveFields.byContains tableName criteria fieldNames (Sql.existingConnection conn)
/// Remove fields from documents via a JSON Path match query (@?) /// Remove fields from documents via a JSON Path match query (@?)
[<Extension>] [<Extension>]
static member inline RemoveFieldsByJsonPath(conn, tableName, jsonPath, fieldNames) = static member inline RemoveFieldsByJsonPath(conn, tableName, jsonPath, fieldNames) =
WithProps.RemoveFields.ByJsonPath(tableName, jsonPath, fieldNames, Sql.existingConnection conn) WithProps.RemoveFields.byJsonPath tableName jsonPath fieldNames (Sql.existingConnection conn)
/// Delete a document by its ID /// Delete a document by its ID
[<Extension>] [<Extension>]

View File

@ -56,6 +56,7 @@ module Parameters =
/// Create an ID parameter (name "@id", key will be treated as a string) /// Create an ID parameter (name "@id", key will be treated as a string)
[<CompiledName "Id">] [<CompiledName "Id">]
let idParam (key: 'TKey) = let idParam (key: 'TKey) =
// TODO: bind key by numeric types
"@id", Sql.string (string key) "@id", Sql.string (string key)
/// Create a parameter with a JSON value /// Create a parameter with a JSON value
@ -170,142 +171,38 @@ module Query =
let whereJsonPathMatches paramName = let whereJsonPathMatches paramName =
$"data @? %s{paramName}::jsonpath" $"data @? %s{paramName}::jsonpath"
/// Create an UPDATE statement to patch documents
[<CompiledName "Patch">]
let patch tableName =
$"UPDATE %s{tableName} SET data = data || @data"
/// Create an UPDATE statement to remove fields from documents
[<CompiledName "RemoveFields">]
let removeFields tableName =
$"UPDATE %s{tableName} SET data = data - @name"
/// Create a query by a document's ID
[<CompiledName "ById">]
let byId<'TKey> statement (docId: 'TKey) =
Query.statementWhere
statement
(whereByFields Any [ { Field.EQ (Configuration.idField ()) docId with ParameterName = Some "@id" } ])
/// Create a query on JSON fields /// Create a query on JSON fields
let fieldQuery statement howMatched fields = [<CompiledName "ByFields">]
let byFields statement howMatched fields =
Query.statementWhere statement (whereByFields howMatched fields) Query.statementWhere statement (whereByFields howMatched fields)
/// Create a JSON containment query /// Create a JSON containment query
let containQuery statement = [<CompiledName "ByContains">]
let byContains statement =
Query.statementWhere statement (whereDataContains "@criteria") Query.statementWhere statement (whereDataContains "@criteria")
/// Create a JSON Path match query /// Create a JSON Path match query
let pathMatchQuery statement = [<CompiledName "ByPathMatch">]
let byPathMatch statement =
Query.statementWhere statement (whereJsonPathMatches "@path") Query.statementWhere statement (whereJsonPathMatches "@path")
/// Queries for retrieving documents
module Find =
/// Query to retrieve a document by its ID
[<CompiledName "ById">]
let byId tableName =
$"""{Query.selectFromTable tableName} WHERE {whereById "@id"}"""
/// Query to retrieve documents using a comparison on JSON fields
[<CompiledName "ByFields">]
let byFields tableName howMatched fields =
$"{Query.selectFromTable tableName} WHERE {whereByFields howMatched fields}"
/// Query to retrieve documents using a comparison on a JSON field
[<CompiledName "ByField">]
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
let byField tableName field =
byFields tableName Any [ field ]
/// Query to retrieve documents using a JSON containment query (@>)
[<CompiledName "ByContains">]
let byContains tableName =
$"""{Query.selectFromTable tableName} WHERE {whereDataContains "@criteria"}"""
/// Query to retrieve documents using a JSON Path match (@?)
[<CompiledName "ByJsonPath">]
let byJsonPath tableName =
$"""{Query.selectFromTable tableName} WHERE {whereJsonPathMatches "@path"}"""
/// Queries to patch (partially update) documents
module Patch =
/// Create an UPDATE statement to patch documents
let private update tableName whereClause =
$"UPDATE %s{tableName} SET data = data || @data WHERE {whereClause}"
/// Query to patch a document by its ID
[<CompiledName "ById">]
let byId tableName =
whereById "@id" |> update tableName
/// Query to patch documents match JSON field comparisons (->> =)
[<CompiledName "ByFields">]
let byFields tableName howMatched fields =
whereByFields howMatched fields |> update tableName
/// Query to patch documents match a JSON field comparison (->> =)
[<CompiledName "ByField">]
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
let byField tableName field =
byFields tableName Any [ field ]
/// Query to patch documents matching a JSON containment query (@>)
[<CompiledName "ByContains">]
let byContains tableName =
whereDataContains "@criteria" |> update tableName
/// Query to patch documents matching a JSON containment query (@>)
[<CompiledName "ByJsonPath">]
let byJsonPath tableName =
whereJsonPathMatches "@path" |> update tableName
/// Queries to remove fields from documents
module RemoveFields =
/// Create an UPDATE statement to remove parameters
let private update tableName whereClause =
$"UPDATE %s{tableName} SET data = data - @name WHERE {whereClause}"
/// Query to remove fields from a document by the document's ID
[<CompiledName "ById">]
let byId tableName =
whereById "@id" |> update tableName
/// Query to remove fields from documents via a comparison on JSON fields within the document
[<CompiledName "ByFields">]
let byFields tableName howMatched fields =
whereByFields howMatched fields |> update tableName
/// Query to remove fields from documents via a comparison on a JSON field within the document
[<CompiledName "ByField">]
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
let byField tableName field =
byFields tableName Any [ field ]
/// Query to patch documents matching a JSON containment query (@>)
[<CompiledName "ByContains">]
let byContains tableName =
whereDataContains "@criteria" |> update tableName
/// Query to patch documents matching a JSON containment query (@>)
[<CompiledName "ByJsonPath">]
let byJsonPath tableName =
whereJsonPathMatches "@path" |> update tableName
/// Queries to delete documents
module Delete =
/// Query to delete a document by its ID
[<CompiledName "ById">]
let byId tableName =
$"""DELETE FROM %s{tableName} WHERE {whereById "@id"}"""
/// Query to delete documents using a comparison on JSON fields
[<CompiledName "ByFields">]
let byFields tableName howMatched fields =
$"DELETE FROM %s{tableName} WHERE {whereByFields howMatched fields}"
/// Query to delete documents using a comparison on a JSON field
[<CompiledName "ByField">]
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
let byField tableName field =
byFields tableName Any [ field ]
/// Query to delete documents using a JSON containment query (@>)
[<CompiledName "ByContains">]
let byContains tableName =
$"""DELETE FROM %s{tableName} WHERE {whereDataContains "@criteria"}"""
/// Query to delete documents using a JSON Path match (@?)
[<CompiledName "ByJsonPath">]
let byJsonPath tableName =
$"""DELETE FROM %s{tableName} WHERE {whereJsonPathMatches "@path"}"""
/// Functions for dealing with results /// Functions for dealing with results
[<AutoOpen>] [<AutoOpen>]
@ -434,19 +331,19 @@ module WithProps =
[<CompiledName "ByFields">] [<CompiledName "ByFields">]
let byFields tableName howMatched fields sqlProps = let byFields tableName howMatched fields sqlProps =
Custom.scalar Custom.scalar
(Query.fieldQuery (Query.count tableName) howMatched fields) (addFieldParams fields []) toCount sqlProps (Query.byFields (Query.count tableName) howMatched fields) (addFieldParams fields []) toCount sqlProps
/// Count matching documents using a JSON containment query (@>) /// Count matching documents using a JSON containment query (@>)
[<CompiledName "ByContains">] [<CompiledName "ByContains">]
let byContains tableName (criteria: 'TContains) sqlProps = let byContains tableName (criteria: 'TContains) sqlProps =
Custom.scalar Custom.scalar
(Query.containQuery (Query.count tableName)) [ jsonParam "@criteria" criteria ] toCount sqlProps (Query.byContains (Query.count tableName)) [ jsonParam "@criteria" criteria ] toCount sqlProps
/// Count matching documents using a JSON Path match query (@?) /// Count matching documents using a JSON Path match query (@?)
[<CompiledName "ByJsonPath">] [<CompiledName "ByJsonPath">]
let byJsonPath tableName jsonPath sqlProps = let byJsonPath tableName jsonPath sqlProps =
Custom.scalar Custom.scalar
(Query.pathMatchQuery (Query.count tableName)) [ "@path", Sql.string jsonPath ] toCount sqlProps (Query.byPathMatch (Query.count tableName)) [ "@path", Sql.string jsonPath ] toCount sqlProps
/// Commands to determine if documents exist /// Commands to determine if documents exist
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
@ -491,26 +388,30 @@ module WithProps =
/// Retrieve all documents in the given table /// Retrieve all documents in the given table
[<CompiledName "FSharpAll">] [<CompiledName "FSharpAll">]
let all<'TDoc> tableName sqlProps = let all<'TDoc> tableName sqlProps =
Custom.list<'TDoc> (Query.selectFromTable tableName) [] fromData<'TDoc> sqlProps Custom.list<'TDoc> (Query.find tableName) [] fromData<'TDoc> sqlProps
/// Retrieve all documents in the given table /// Retrieve all documents in the given table
let All<'TDoc>(tableName, sqlProps) = let All<'TDoc>(tableName, sqlProps) =
Custom.List<'TDoc>(Query.selectFromTable tableName, [], fromData<'TDoc>, sqlProps) Custom.List<'TDoc>(Query.find tableName, [], fromData<'TDoc>, sqlProps)
/// Retrieve a document by its ID (returns None if not found) /// Retrieve a document by its ID (returns None if not found)
[<CompiledName "FSharpById">] [<CompiledName "FSharpById">]
let byId<'TKey, 'TDoc> tableName (docId: 'TKey) sqlProps = let byId<'TKey, 'TDoc> tableName (docId: 'TKey) sqlProps =
Custom.single (Query.Find.byId tableName) [ idParam docId ] fromData<'TDoc> sqlProps Custom.single (Query.byId (Query.find tableName) docId) [ idParam docId ] fromData<'TDoc> sqlProps
/// Retrieve a document by its ID (returns null if not found) /// Retrieve a document by its ID (returns null if not found)
let ById<'TKey, 'TDoc when 'TDoc: null>(tableName, docId: 'TKey, sqlProps) = let ById<'TKey, 'TDoc when 'TDoc: null>(tableName, docId: 'TKey, sqlProps) =
Custom.Single<'TDoc>(Query.Find.byId tableName, [ idParam docId ], fromData<'TDoc>, sqlProps) Custom.Single<'TDoc>(
Query.byId (Query.find tableName) docId, [ idParam docId ], fromData<'TDoc>, sqlProps)
/// Retrieve documents matching JSON field comparisons (->> =) /// Retrieve documents matching JSON field comparisons (->> =)
[<CompiledName "FSharpByFields">] [<CompiledName "FSharpByFields">]
let byFields<'TDoc> tableName howMatched fields sqlProps = let byFields<'TDoc> tableName howMatched fields sqlProps =
Custom.list<'TDoc> Custom.list<'TDoc>
(Query.Find.byFields tableName howMatched fields) (addFieldParams fields []) fromData<'TDoc> sqlProps (Query.byFields (Query.find tableName) howMatched fields)
(addFieldParams fields [])
fromData<'TDoc>
sqlProps
/// Retrieve documents matching a JSON field comparison (->> =) /// Retrieve documents matching a JSON field comparison (->> =)
[<CompiledName "FSharpByField">] [<CompiledName "FSharpByField">]
@ -521,7 +422,10 @@ module WithProps =
/// Retrieve documents matching JSON field comparisons (->> =) /// Retrieve documents matching JSON field comparisons (->> =)
let ByFields<'TDoc>(tableName, howMatched, fields, sqlProps) = let ByFields<'TDoc>(tableName, howMatched, fields, sqlProps) =
Custom.List<'TDoc>( Custom.List<'TDoc>(
Query.Find.byFields tableName howMatched fields, addFieldParams fields [], fromData<'TDoc>, sqlProps) Query.byFields (Query.find tableName) howMatched fields,
addFieldParams fields [],
fromData<'TDoc>,
sqlProps)
/// Retrieve documents matching a JSON field comparison (->> =) /// Retrieve documents matching a JSON field comparison (->> =)
[<System.Obsolete "Use ByFields instead; will be removed in v4">] [<System.Obsolete "Use ByFields instead; will be removed in v4">]
@ -532,29 +436,35 @@ module WithProps =
[<CompiledName "FSharpByContains">] [<CompiledName "FSharpByContains">]
let byContains<'TDoc> tableName (criteria: obj) sqlProps = let byContains<'TDoc> tableName (criteria: obj) sqlProps =
Custom.list<'TDoc> Custom.list<'TDoc>
(Query.Find.byContains tableName) [ jsonParam "@criteria" criteria ] fromData<'TDoc> sqlProps (Query.byContains (Query.find tableName)) [ jsonParam "@criteria" criteria ] fromData<'TDoc> sqlProps
/// Retrieve documents matching a JSON containment query (@>) /// Retrieve documents matching a JSON containment query (@>)
let ByContains<'TDoc>(tableName, criteria: obj, sqlProps) = let ByContains<'TDoc>(tableName, criteria: obj, sqlProps) =
Custom.List<'TDoc>( Custom.List<'TDoc>(
Query.Find.byContains tableName, [ jsonParam "@criteria" criteria ], fromData<'TDoc>, sqlProps) Query.byContains (Query.find tableName),
[ jsonParam "@criteria" criteria ],
fromData<'TDoc>,
sqlProps)
/// Retrieve documents matching a JSON Path match query (@?) /// Retrieve documents matching a JSON Path match query (@?)
[<CompiledName "FSharpByJsonPath">] [<CompiledName "FSharpByJsonPath">]
let byJsonPath<'TDoc> tableName jsonPath sqlProps = let byJsonPath<'TDoc> tableName jsonPath sqlProps =
Custom.list<'TDoc> Custom.list<'TDoc>
(Query.Find.byJsonPath tableName) [ "@path", Sql.string jsonPath ] fromData<'TDoc> sqlProps (Query.byPathMatch (Query.find tableName)) [ "@path", Sql.string jsonPath ] fromData<'TDoc> sqlProps
/// Retrieve documents matching a JSON Path match query (@?) /// Retrieve documents matching a JSON Path match query (@?)
let ByJsonPath<'TDoc>(tableName, jsonPath, sqlProps) = let ByJsonPath<'TDoc>(tableName, jsonPath, sqlProps) =
Custom.List<'TDoc>( Custom.List<'TDoc>(
Query.Find.byJsonPath tableName, [ "@path", Sql.string jsonPath ], fromData<'TDoc>, sqlProps) Query.byPathMatch (Query.find tableName),
[ "@path", Sql.string jsonPath ],
fromData<'TDoc>,
sqlProps)
/// Retrieve the first document matching JSON field comparisons (->> =); returns None if not found /// Retrieve the first document matching JSON field comparisons (->> =); returns None if not found
[<CompiledName "FSharpFirstByFields">] [<CompiledName "FSharpFirstByFields">]
let firstByFields<'TDoc> tableName howMatched fields sqlProps = let firstByFields<'TDoc> tableName howMatched fields sqlProps =
Custom.single<'TDoc> Custom.single<'TDoc>
$"{Query.Find.byFields tableName howMatched fields} LIMIT 1" $"{Query.byFields (Query.find tableName) howMatched fields} LIMIT 1"
(addFieldParams fields []) (addFieldParams fields [])
fromData<'TDoc> fromData<'TDoc>
sqlProps sqlProps
@ -568,7 +478,7 @@ module WithProps =
/// Retrieve the first document matching JSON field comparisons (->> =); returns null if not found /// Retrieve the first document matching JSON field comparisons (->> =); returns null if not found
let FirstByFields<'TDoc when 'TDoc: null>(tableName, howMatched, fields, sqlProps) = let FirstByFields<'TDoc when 'TDoc: null>(tableName, howMatched, fields, sqlProps) =
Custom.Single<'TDoc>( Custom.Single<'TDoc>(
$"{Query.Find.byFields tableName howMatched fields} LIMIT 1", $"{Query.byFields (Query.find tableName) howMatched fields} LIMIT 1",
addFieldParams fields [], addFieldParams fields [],
fromData<'TDoc>, fromData<'TDoc>,
sqlProps) sqlProps)
@ -582,12 +492,15 @@ module WithProps =
[<CompiledName "FSharpFirstByContains">] [<CompiledName "FSharpFirstByContains">]
let firstByContains<'TDoc> tableName (criteria: obj) sqlProps = let firstByContains<'TDoc> tableName (criteria: obj) sqlProps =
Custom.single<'TDoc> Custom.single<'TDoc>
$"{Query.Find.byContains tableName} LIMIT 1" [ jsonParam "@criteria" criteria ] fromData<'TDoc> sqlProps $"{Query.byContains (Query.find tableName)} LIMIT 1"
[ jsonParam "@criteria" criteria ]
fromData<'TDoc>
sqlProps
/// Retrieve the first document matching a JSON containment query (@>); returns null if not found /// Retrieve the first document matching a JSON containment query (@>); returns null if not found
let FirstByContains<'TDoc when 'TDoc: null>(tableName, criteria: obj, sqlProps) = let FirstByContains<'TDoc when 'TDoc: null>(tableName, criteria: obj, sqlProps) =
Custom.Single<'TDoc>( Custom.Single<'TDoc>(
$"{Query.Find.byContains tableName} LIMIT 1", $"{Query.byContains (Query.find tableName)} LIMIT 1",
[ jsonParam "@criteria" criteria ], [ jsonParam "@criteria" criteria ],
fromData<'TDoc>, fromData<'TDoc>,
sqlProps) sqlProps)
@ -596,12 +509,15 @@ module WithProps =
[<CompiledName "FSharpFirstByJsonPath">] [<CompiledName "FSharpFirstByJsonPath">]
let firstByJsonPath<'TDoc> tableName jsonPath sqlProps = let firstByJsonPath<'TDoc> tableName jsonPath sqlProps =
Custom.single<'TDoc> Custom.single<'TDoc>
$"{Query.Find.byJsonPath tableName} LIMIT 1" [ "@path", Sql.string jsonPath ] fromData<'TDoc> sqlProps $"{Query.byPathMatch (Query.find tableName)} LIMIT 1"
[ "@path", Sql.string jsonPath ]
fromData<'TDoc>
sqlProps
/// Retrieve the first document matching a JSON Path match query (@?); returns null if not found /// Retrieve the first document matching a JSON Path match query (@?); returns null if not found
let FirstByJsonPath<'TDoc when 'TDoc: null>(tableName, jsonPath, sqlProps) = let FirstByJsonPath<'TDoc when 'TDoc: null>(tableName, jsonPath, sqlProps) =
Custom.Single<'TDoc>( Custom.Single<'TDoc>(
$"{Query.Find.byJsonPath tableName} LIMIT 1", $"{Query.byPathMatch (Query.find tableName)} LIMIT 1",
[ "@path", Sql.string jsonPath ], [ "@path", Sql.string jsonPath ],
fromData<'TDoc>, fromData<'TDoc>,
sqlProps) sqlProps)
@ -634,13 +550,14 @@ module WithProps =
/// Patch a document by its ID /// Patch a document by its ID
[<CompiledName "ById">] [<CompiledName "ById">]
let byId tableName (docId: 'TKey) (patch: 'TPatch) sqlProps = let byId tableName (docId: 'TKey) (patch: 'TPatch) sqlProps =
Custom.nonQuery (Query.Patch.byId tableName) [ idParam docId; jsonParam "@data" patch ] sqlProps Custom.nonQuery
(Query.byId (Query.patch tableName) docId) [ idParam docId; jsonParam "@data" patch ] sqlProps
/// Patch documents using a JSON field comparison query in the WHERE clause (->> =) /// Patch documents using a JSON field comparison query in the WHERE clause (->> =)
[<CompiledName "ByFields">] [<CompiledName "ByFields">]
let byFields tableName howMatched fields (patch: 'TPatch) sqlProps = let byFields tableName howMatched fields (patch: 'TPatch) sqlProps =
Custom.nonQuery Custom.nonQuery
(Query.Patch.byFields tableName howMatched fields) (Query.byFields (Query.patch tableName) howMatched fields)
(addFieldParams fields [ jsonParam "@data" patch ]) (addFieldParams fields [ jsonParam "@data" patch ])
sqlProps sqlProps
@ -654,32 +571,33 @@ module WithProps =
[<CompiledName "ByContains">] [<CompiledName "ByContains">]
let byContains tableName (criteria: 'TContains) (patch: 'TPatch) sqlProps = let byContains tableName (criteria: 'TContains) (patch: 'TPatch) sqlProps =
Custom.nonQuery Custom.nonQuery
(Query.Patch.byContains tableName) [ jsonParam "@data" patch; jsonParam "@criteria" criteria ] sqlProps (Query.byContains (Query.patch tableName))
[ jsonParam "@data" patch; jsonParam "@criteria" criteria ]
sqlProps
/// Patch documents using a JSON Path match query in the WHERE clause (@?) /// Patch documents using a JSON Path match query in the WHERE clause (@?)
[<CompiledName "ByJsonPath">] [<CompiledName "ByJsonPath">]
let byJsonPath tableName jsonPath (patch: 'TPatch) sqlProps = let byJsonPath tableName jsonPath (patch: 'TPatch) sqlProps =
Custom.nonQuery Custom.nonQuery
(Query.Patch.byJsonPath tableName) [ jsonParam "@data" patch; "@path", Sql.string jsonPath ] sqlProps (Query.byPathMatch (Query.patch tableName))
[ jsonParam "@data" patch; "@path", Sql.string jsonPath ]
sqlProps
/// Commands to remove fields from documents /// Commands to remove fields from documents
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module RemoveFields = module RemoveFields =
/// Remove fields from a document by the document's ID /// Remove fields from a document by the document's ID
[<CompiledName "FSharpById">] [<CompiledName "ById">]
let byId tableName (docId: 'TKey) fieldNames sqlProps = let byId tableName (docId: 'TKey) fieldNames sqlProps =
Custom.nonQuery (Query.RemoveFields.byId tableName) [ idParam docId; fieldNameParams fieldNames ] sqlProps Custom.nonQuery
(Query.byId (Query.removeFields tableName) docId) [ idParam docId; fieldNameParams fieldNames ] sqlProps
/// Remove fields from a document by the document's ID
let ById(tableName, docId: 'TKey, fieldNames, sqlProps) =
byId tableName docId (List.ofSeq fieldNames) sqlProps
/// Remove fields from documents via a comparison on JSON fields in the document /// Remove fields from documents via a comparison on JSON fields in the document
[<CompiledName "ByFields">] [<CompiledName "ByFields">]
let byFields tableName howMatched fields fieldNames sqlProps = let byFields tableName howMatched fields fieldNames sqlProps =
Custom.nonQuery Custom.nonQuery
(Query.RemoveFields.byFields tableName howMatched fields) (Query.byFields (Query.removeFields tableName) howMatched fields)
(addFieldParams fields [ fieldNameParams fieldNames ]) (addFieldParams fields [ fieldNameParams fieldNames ])
sqlProps sqlProps
@ -690,29 +608,21 @@ module WithProps =
byFields tableName Any [ field ] fieldNames sqlProps byFields tableName Any [ field ] fieldNames sqlProps
/// Remove fields from documents via a JSON containment query (@>) /// Remove fields from documents via a JSON containment query (@>)
[<CompiledName "FSharpByContains">] [<CompiledName "ByContains">]
let byContains tableName (criteria: 'TContains) fieldNames sqlProps = let byContains tableName (criteria: 'TContains) fieldNames sqlProps =
Custom.nonQuery Custom.nonQuery
(Query.RemoveFields.byContains tableName) (Query.byContains (Query.removeFields tableName))
[ jsonParam "@criteria" criteria; fieldNameParams fieldNames ] [ jsonParam "@criteria" criteria; fieldNameParams fieldNames ]
sqlProps sqlProps
/// Remove fields from documents via a JSON containment query (@>)
let ByContains(tableName, criteria: 'TContains, fieldNames, sqlProps) =
byContains tableName criteria (List.ofSeq fieldNames) sqlProps
/// Remove fields from documents via a JSON Path match query (@?) /// Remove fields from documents via a JSON Path match query (@?)
[<CompiledName "FSharpByJsonPath">] [<CompiledName "FSharpByJsonPath">]
let byJsonPath tableName jsonPath fieldNames sqlProps = let byJsonPath tableName jsonPath fieldNames sqlProps =
Custom.nonQuery Custom.nonQuery
(Query.RemoveFields.byJsonPath tableName) (Query.byPathMatch (Query.removeFields tableName))
[ "@path", Sql.string jsonPath; fieldNameParams fieldNames ] [ "@path", Sql.string jsonPath; fieldNameParams fieldNames ]
sqlProps sqlProps
/// Remove fields from documents via a JSON Path match query (@?)
let ByJsonPath(tableName, jsonPath, fieldNames, sqlProps) =
byJsonPath tableName jsonPath (List.ofSeq fieldNames) sqlProps
/// Commands to delete documents /// Commands to delete documents
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module Delete = module Delete =
@ -720,12 +630,13 @@ module WithProps =
/// Delete a document by its ID /// Delete a document by its ID
[<CompiledName "ById">] [<CompiledName "ById">]
let byId tableName (docId: 'TKey) sqlProps = let byId tableName (docId: 'TKey) sqlProps =
Custom.nonQuery (Query.Delete.byId tableName) [ idParam docId ] sqlProps Custom.nonQuery (Query.byId (Query.delete tableName) docId) [ idParam docId ] sqlProps
/// Delete documents by matching a JSON field comparison query (->> =) /// Delete documents by matching a JSON field comparison query (->> =)
[<CompiledName "ByFields">] [<CompiledName "ByFields">]
let byFields tableName howMatched fields sqlProps = let byFields tableName howMatched fields sqlProps =
Custom.nonQuery (Query.Delete.byFields tableName howMatched fields) (addFieldParams fields []) sqlProps Custom.nonQuery
(Query.byFields (Query.delete tableName) howMatched fields) (addFieldParams fields []) sqlProps
/// Delete documents by matching a JSON field comparison query (->> =) /// Delete documents by matching a JSON field comparison query (->> =)
[<CompiledName "ByField">] [<CompiledName "ByField">]
@ -736,12 +647,12 @@ module WithProps =
/// Delete documents by matching a JSON contains query (@>) /// Delete documents by matching a JSON contains query (@>)
[<CompiledName "ByContains">] [<CompiledName "ByContains">]
let byContains tableName (criteria: 'TCriteria) sqlProps = let byContains tableName (criteria: 'TCriteria) sqlProps =
Custom.nonQuery (Query.Delete.byContains tableName) [ jsonParam "@criteria" criteria ] sqlProps Custom.nonQuery (Query.byContains (Query.delete tableName)) [ jsonParam "@criteria" criteria ] sqlProps
/// Delete documents by matching a JSON Path match query (@?) /// Delete documents by matching a JSON Path match query (@?)
[<CompiledName "ByJsonPath">] [<CompiledName "ByJsonPath">]
let byJsonPath tableName path sqlProps = let byJsonPath tableName path sqlProps =
Custom.nonQuery (Query.Delete.byJsonPath tableName) [ "@path", Sql.string path ] sqlProps Custom.nonQuery (Query.byPathMatch (Query.delete tableName)) [ "@path", Sql.string path ] sqlProps
/// Commands to execute custom SQL queries /// Commands to execute custom SQL queries
@ -1032,14 +943,10 @@ module Patch =
module RemoveFields = module RemoveFields =
/// Remove fields from a document by the document's ID /// Remove fields from a document by the document's ID
[<CompiledName "FSharpById">] [<CompiledName "ById">]
let byId tableName (docId: 'TKey) fieldNames = let byId tableName (docId: 'TKey) fieldNames =
WithProps.RemoveFields.byId tableName docId fieldNames (fromDataSource ()) WithProps.RemoveFields.byId tableName docId fieldNames (fromDataSource ())
/// Remove fields from a document by the document's ID
let ById(tableName, docId: 'TKey, fieldNames) =
WithProps.RemoveFields.ById(tableName, docId, fieldNames, fromDataSource ())
/// Remove fields from documents via a comparison on JSON fields in the document /// Remove fields from documents via a comparison on JSON fields in the document
[<CompiledName "ByFields">] [<CompiledName "ByFields">]
let byFields tableName howMatched fields fieldNames = let byFields tableName howMatched fields fieldNames =
@ -1052,23 +959,15 @@ module RemoveFields =
byFields tableName Any [ field ] fieldNames byFields tableName Any [ field ] fieldNames
/// Remove fields from documents via a JSON containment query (@>) /// Remove fields from documents via a JSON containment query (@>)
[<CompiledName "FSharpByContains">] [<CompiledName "ByContains">]
let byContains tableName (criteria: 'TContains) fieldNames = let byContains tableName (criteria: 'TContains) fieldNames =
WithProps.RemoveFields.byContains tableName criteria fieldNames (fromDataSource ()) WithProps.RemoveFields.byContains tableName criteria fieldNames (fromDataSource ())
/// Remove fields from documents via a JSON containment query (@>)
let ByContains(tableName, criteria: 'TContains, fieldNames) =
WithProps.RemoveFields.ByContains(tableName, criteria, fieldNames, fromDataSource ())
/// Remove fields from documents via a JSON Path match query (@?) /// Remove fields from documents via a JSON Path match query (@?)
[<CompiledName "FSharpByJsonPath">] [<CompiledName "ByJsonPath">]
let byJsonPath tableName jsonPath fieldNames = let byJsonPath tableName jsonPath fieldNames =
WithProps.RemoveFields.byJsonPath tableName jsonPath fieldNames (fromDataSource ()) WithProps.RemoveFields.byJsonPath tableName jsonPath fieldNames (fromDataSource ())
/// Remove fields from documents via a JSON Path match query (@?)
let ByJsonPath(tableName, jsonPath, fieldNames) =
WithProps.RemoveFields.ByJsonPath(tableName, jsonPath, fieldNames, fromDataSource ())
/// Commands to delete documents /// Commands to delete documents
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]

View File

@ -56,6 +56,29 @@ module Query =
let whereById paramName = let whereById paramName =
whereByFields Any [ { Field.EQ (Configuration.idField ()) 0 with ParameterName = Some paramName } ] whereByFields Any [ { Field.EQ (Configuration.idField ()) 0 with ParameterName = Some paramName } ]
/// Create an UPDATE statement to patch documents
[<CompiledName "Patch">]
let patch tableName =
$"UPDATE %s{tableName} SET data = json_patch(data, json(@data))"
/// Create an UPDATE statement to remove fields from documents
[<CompiledName "RemoveFields">]
let removeFields tableName (parameters: SqliteParameter seq) =
let paramNames = parameters |> Seq.map _.ParameterName |> String.concat ", "
$"UPDATE %s{tableName} SET data = json_remove(data, {paramNames})"
/// Create a query by a document's ID
[<CompiledName "ById">]
let byId<'TKey> statement (docId: 'TKey) =
Query.statementWhere
statement
(whereByFields Any [ { Field.EQ (Configuration.idField ()) docId with ParameterName = Some "@id" } ])
/// Create a query on JSON fields
[<CompiledName "ByFields">]
let byFields statement howMatched fields =
Query.statementWhere statement (whereByFields howMatched fields)
/// Data definition /// Data definition
module Definition = module Definition =
@ -64,91 +87,6 @@ module Query =
let ensureTable name = let ensureTable name =
Query.Definition.ensureTableFor name "TEXT" Query.Definition.ensureTableFor name "TEXT"
/// Queries for retrieving documents
module Find =
/// Query to retrieve a document by its ID
[<CompiledName "ById">]
let byId tableName =
$"""{Query.selectFromTable tableName} WHERE {whereById "@id"}"""
/// Query to retrieve documents using a comparison on JSON fields
[<CompiledName "ByField">]
let byFields tableName howMatched fields =
$"{Query.selectFromTable tableName} WHERE {whereByFields howMatched fields}"
/// Query to retrieve documents using a comparison on a JSON field
[<CompiledName "ByField">]
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
let byField tableName field =
byFields tableName Any [ field ]
/// 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 =
whereById "@id" |> update tableName
/// Query to patch (partially update) a document via a comparison on JSON fields
[<CompiledName "ByFields">]
let byFields tableName howMatched fields =
whereByFields howMatched fields |> update tableName
/// Query to patch (partially update) a document via a comparison on a JSON field
[<CompiledName "ByField">]
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
let byField tableName field =
byFields tableName Any [ field ]
/// Queries to remove fields from documents
module RemoveFields =
/// Create an UPDATE statement to remove parameters
let internal update tableName (parameters: SqliteParameter seq) whereClause =
let paramNames = parameters |> Seq.map _.ParameterName |> String.concat ", "
$"UPDATE %s{tableName} SET data = json_remove(data, {paramNames}) WHERE %s{whereClause}"
/// Query to remove fields from a document by the document's ID
[<CompiledName "ById">]
let byId tableName parameters =
whereById "@id" |> update tableName parameters
/// Query to remove fields from documents via a comparison on JSON fields within the document
[<CompiledName "ByFields">]
let byFields tableName howMatched fields parameters =
whereByFields howMatched fields |> update tableName parameters
/// Query to remove fields from documents via a comparison on a JSON field within the document
[<CompiledName "ByField">]
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
let byField tableName field parameters =
byFields tableName Any [ field ] parameters
/// Queries to delete documents
module Delete =
/// Query to delete a document by its ID
[<CompiledName "ById">]
let byId tableName =
$"""DELETE FROM %s{tableName} WHERE {whereById "@id"}"""
/// Query to delete documents using a comparison on JSON fields
[<CompiledName "ByFields">]
let byFields tableName howMatched fields =
$"DELETE FROM %s{tableName} WHERE {whereByFields howMatched fields}"
/// Query to delete documents using a comparison on a JSON field
[<CompiledName "ByField">]
[<System.Obsolete "Use ByFields instead; will be removed in v4">]
let byField tableName field =
byFields tableName Any [ field ]
/// Parameter handling helpers /// Parameter handling helpers
[<AutoOpen>] [<AutoOpen>]
@ -349,10 +287,7 @@ module WithConn =
[<CompiledName "ByFields">] [<CompiledName "ByFields">]
let byFields tableName howMatched fields conn = let byFields tableName howMatched fields conn =
Custom.scalar Custom.scalar
(Query.statementWhere (Query.count tableName) (Query.whereByFields howMatched fields)) (Query.byFields (Query.count tableName) howMatched fields) (addFieldParams fields []) toCount conn
(addFieldParams fields [])
toCount
conn
/// Commands to determine if documents exist /// Commands to determine if documents exist
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
@ -379,26 +314,29 @@ module WithConn =
/// Retrieve all documents in the given table /// Retrieve all documents in the given table
[<CompiledName "FSharpAll">] [<CompiledName "FSharpAll">]
let all<'TDoc> tableName conn = let all<'TDoc> tableName conn =
Custom.list<'TDoc> (Query.selectFromTable tableName) [] fromData<'TDoc> conn Custom.list<'TDoc> (Query.find tableName) [] fromData<'TDoc> conn
/// Retrieve all documents in the given table /// Retrieve all documents in the given table
let All<'TDoc>(tableName, conn) = let All<'TDoc>(tableName, conn) =
Custom.List(Query.selectFromTable tableName, [], fromData<'TDoc>, conn) Custom.List(Query.find tableName, [], fromData<'TDoc>, conn)
/// Retrieve a document by its ID (returns None if not found) /// Retrieve a document by its ID (returns None if not found)
[<CompiledName "FSharpById">] [<CompiledName "FSharpById">]
let byId<'TKey, 'TDoc> tableName (docId: 'TKey) conn = let byId<'TKey, 'TDoc> tableName (docId: 'TKey) conn =
Custom.single<'TDoc> (Query.Find.byId tableName) [ idParam docId ] fromData<'TDoc> conn Custom.single<'TDoc> (Query.byId (Query.find tableName) docId) [ idParam docId ] fromData<'TDoc> conn
/// Retrieve a document by its ID (returns null if not found) /// Retrieve a document by its ID (returns null if not found)
let ById<'TKey, 'TDoc when 'TDoc: null>(tableName, docId: 'TKey, conn) = let ById<'TKey, 'TDoc when 'TDoc: null>(tableName, docId: 'TKey, conn) =
Custom.Single<'TDoc>(Query.Find.byId tableName, [ idParam docId ], fromData<'TDoc>, conn) Custom.Single<'TDoc>(Query.byId (Query.find tableName) docId, [ idParam docId ], fromData<'TDoc>, conn)
/// Retrieve documents via a comparison on JSON fields /// Retrieve documents via a comparison on JSON fields
[<CompiledName "FSharpByFields">] [<CompiledName "FSharpByFields">]
let byFields<'TDoc> tableName howMatched fields conn = let byFields<'TDoc> tableName howMatched fields conn =
Custom.list<'TDoc> Custom.list<'TDoc>
(Query.Find.byFields tableName howMatched fields) (addFieldParams fields []) fromData<'TDoc> conn (Query.byFields (Query.find tableName) howMatched fields)
(addFieldParams fields [])
fromData<'TDoc>
conn
/// Retrieve documents via a comparison on a JSON field /// Retrieve documents via a comparison on a JSON field
[<CompiledName "FSharpByField">] [<CompiledName "FSharpByField">]
@ -409,7 +347,10 @@ module WithConn =
/// Retrieve documents via a comparison on JSON fields /// Retrieve documents via a comparison on JSON fields
let ByFields<'TDoc>(tableName, howMatched, fields, conn) = let ByFields<'TDoc>(tableName, howMatched, fields, conn) =
Custom.List<'TDoc>( Custom.List<'TDoc>(
Query.Find.byFields tableName howMatched fields, addFieldParams fields [], fromData<'TDoc>, conn) Query.byFields (Query.find tableName) howMatched fields,
addFieldParams fields [],
fromData<'TDoc>,
conn)
/// Retrieve documents via a comparison on a JSON field /// Retrieve documents via a comparison on a JSON field
[<System.Obsolete "Use ByFields instead; will be removed in v4">] [<System.Obsolete "Use ByFields instead; will be removed in v4">]
@ -420,7 +361,7 @@ module WithConn =
[<CompiledName "FSharpFirstByFields">] [<CompiledName "FSharpFirstByFields">]
let firstByFields<'TDoc> tableName howMatched fields conn = let firstByFields<'TDoc> tableName howMatched fields conn =
Custom.single Custom.single
$"{Query.Find.byFields tableName howMatched fields} LIMIT 1" $"{Query.byFields (Query.find tableName) howMatched fields} LIMIT 1"
(addFieldParams fields []) (addFieldParams fields [])
fromData<'TDoc> fromData<'TDoc>
conn conn
@ -434,7 +375,7 @@ module WithConn =
/// Retrieve documents via a comparison on JSON fields, returning only the first result /// Retrieve documents via a comparison on JSON fields, returning only the first result
let FirstByFields<'TDoc when 'TDoc: null>(tableName, howMatched, fields, conn) = let FirstByFields<'TDoc when 'TDoc: null>(tableName, howMatched, fields, conn) =
Custom.Single( Custom.Single(
$"{Query.Find.byFields tableName howMatched fields} LIMIT 1", $"{Query.byFields (Query.find tableName) howMatched fields} LIMIT 1",
addFieldParams fields [], addFieldParams fields [],
fromData<'TDoc>, fromData<'TDoc>,
conn) conn)
@ -472,13 +413,14 @@ module WithConn =
/// Patch a document by its ID /// Patch a document by its ID
[<CompiledName "ById">] [<CompiledName "ById">]
let byId tableName (docId: 'TKey) (patch: 'TPatch) conn = let byId tableName (docId: 'TKey) (patch: 'TPatch) conn =
Custom.nonQuery (Query.Patch.byId tableName) [ idParam docId; jsonParam "@data" patch ] conn Custom.nonQuery
(Query.byId (Query.patch tableName) docId) [ idParam docId; jsonParam "@data" patch ] conn
/// Patch documents using a comparison on JSON fields /// Patch documents using a comparison on JSON fields
[<CompiledName "ByFields">] [<CompiledName "ByFields">]
let byFields tableName howMatched fields (patch: 'TPatch) conn = let byFields tableName howMatched fields (patch: 'TPatch) conn =
Custom.nonQuery Custom.nonQuery
(Query.Patch.byFields tableName howMatched fields) (Query.byFields (Query.patch tableName) howMatched fields)
(addFieldParams fields [ jsonParam "@data" patch ]) (addFieldParams fields [ jsonParam "@data" patch ])
conn conn
@ -497,7 +439,7 @@ module WithConn =
let byId tableName (docId: 'TKey) fieldNames conn = let byId tableName (docId: 'TKey) fieldNames conn =
let nameParams = fieldNameParams "@name" fieldNames let nameParams = fieldNameParams "@name" fieldNames
Custom.nonQuery Custom.nonQuery
(Query.RemoveFields.byId tableName nameParams) (Query.byId (Query.removeFields tableName nameParams) docId)
(idParam docId |> Seq.singleton |> Seq.append nameParams) (idParam docId |> Seq.singleton |> Seq.append nameParams)
conn conn
@ -506,7 +448,7 @@ module WithConn =
let byFields tableName howMatched fields fieldNames conn = let byFields tableName howMatched fields fieldNames conn =
let nameParams = fieldNameParams "@name" fieldNames let nameParams = fieldNameParams "@name" fieldNames
Custom.nonQuery Custom.nonQuery
(Query.RemoveFields.byFields tableName howMatched fields nameParams) (Query.byFields (Query.removeFields tableName nameParams) howMatched fields)
(addFieldParams fields nameParams) (addFieldParams fields nameParams)
conn conn
@ -523,12 +465,12 @@ module WithConn =
/// Delete a document by its ID /// Delete a document by its ID
[<CompiledName "ById">] [<CompiledName "ById">]
let byId tableName (docId: 'TKey) conn = let byId tableName (docId: 'TKey) conn =
Custom.nonQuery (Query.Delete.byId tableName) [ idParam docId ] conn Custom.nonQuery (Query.byId (Query.delete tableName) docId) [ idParam docId ] conn
/// Delete documents by matching a comparison on JSON fields /// Delete documents by matching a comparison on JSON fields
[<CompiledName "ByFields">] [<CompiledName "ByFields">]
let byFields tableName howMatched fields conn = let byFields tableName howMatched fields conn =
Custom.nonQuery (Query.Delete.byFields tableName howMatched fields) (addFieldParams fields []) conn Custom.nonQuery (Query.byFields (Query.delete tableName) howMatched fields) (addFieldParams fields []) conn
/// Delete documents by matching a comparison on a JSON field /// Delete documents by matching a comparison on a JSON field
[<CompiledName "ByField">] [<CompiledName "ByField">]

View File

@ -238,10 +238,9 @@ public static class CommonCSharpTests
]), ]),
TestList("Query", TestList("Query",
[ [
TestCase("SelectFromTable succeeds", () => TestCase("StatementWhere succeeds", () =>
{ {
Expect.equal(Query.SelectFromTable("test.table"), "SELECT data FROM test.table", Expect.equal(Query.StatementWhere("q", "r"), "q WHERE r", "Statements not combined correctly");
"SELECT statement not correct");
}), }),
TestList("Definition", TestList("Definition",
[ [
@ -266,7 +265,9 @@ public static class CommonCSharpTests
"CREATE INDEX for key statement without schema not constructed correctly"); "CREATE INDEX for key statement without schema not constructed correctly");
}) })
]), ]),
TestCase("EnsureIndexOn succeeds for multiple fields and directions", () => TestList("EnsureIndexOn",
[
TestCase("succeeds for multiple fields and directions", () =>
{ {
Expect.equal( Expect.equal(
Query.Definition.EnsureIndexOn("test.table", "gibberish", Query.Definition.EnsureIndexOn("test.table", "gibberish",
@ -274,7 +275,22 @@ public static class CommonCSharpTests
"CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table " "CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table "
+ "((data->>'taco'), (data->>'guac') DESC, (data->>'salsa') ASC)", + "((data->>'taco'), (data->>'guac') DESC, (data->>'salsa') ASC)",
"CREATE INDEX for multiple field statement incorrect"); "CREATE INDEX for multiple field statement incorrect");
}),
TestCase("succeeds for nested PostgreSQL field", () =>
{
Expect.equal(
Query.Definition.EnsureIndexOn("tbl", "nest", ["a.b.c"], Dialect.PostgreSQL),
"CREATE INDEX IF NOT EXISTS idx_tbl_nest ON tbl ((data#>>'{a,b,c}'))",
"CREATE INDEX for nested PostgreSQL field incorrect");
}),
TestCase("succeeds for nested SQLite field", () =>
{
Expect.equal(
Query.Definition.EnsureIndexOn("tbl", "nest", ["a.b.c"], Dialect.SQLite),
"CREATE INDEX IF NOT EXISTS idx_tbl_nest ON tbl ((data->>'a'->>'b'->>'c'))",
"CREATE INDEX for nested SQLite field incorrect");
}) })
])
]), ]),
TestCase("Insert succeeds", () => TestCase("Insert succeeds", () =>
{ {
@ -285,6 +301,27 @@ public static class CommonCSharpTests
Expect.equal(Query.Save("tbl"), Expect.equal(Query.Save("tbl"),
"INSERT INTO tbl VALUES (@data) ON CONFLICT ((data->>'Id')) DO UPDATE SET data = EXCLUDED.data", "INSERT INTO tbl VALUES (@data) ON CONFLICT ((data->>'Id')) DO UPDATE SET data = EXCLUDED.data",
"INSERT ON CONFLICT UPDATE statement not correct"); "INSERT ON CONFLICT UPDATE statement not correct");
}),
TestCase("Count succeeds", () =>
{
Expect.equal(Query.Count("tbl"), "SELECT COUNT(*) AS it FROM tbl", "Count query not correct");
}),
TestCase("Exists succeeds", () =>
{
Expect.equal(Query.Exists("tbl", "chicken"), "SELECT EXISTS (SELECT 1 FROM tbl WHERE chicken) AS it",
"Exists query not correct");
}),
TestCase("Find succeeds", () =>
{
Expect.equal(Query.Find("test.table"), "SELECT data FROM test.table", "Find query not correct");
}),
TestCase("Update succeeds", () =>
{
Expect.equal(Query.Update("tbl"), "UPDATE tbl SET data = @data", "Update query not correct");
}),
TestCase("Delete succeeds", () =>
{
Expect.equal(Query.Delete("tbl"), "DELETE FROM tbl", "Delete query not correct");
}) })
]) ])
]); ]);

View File

@ -279,153 +279,7 @@ public static class PostgresCSharpTests
{ {
Expect.equal(Postgres.Query.WhereJsonPathMatches("@path"), "data @? @path::jsonpath", Expect.equal(Postgres.Query.WhereJsonPathMatches("@path"), "data @? @path::jsonpath",
"WHERE clause not correct"); "WHERE clause not correct");
}),
TestList("Find",
[
TestCase("ById succeeds", () =>
{
Expect.equal(Postgres.Query.Find.ById(PostgresDb.TableName),
$"SELECT data FROM {PostgresDb.TableName} WHERE data->>'Id' = @id",
"SELECT by ID query not correct");
}),
TestCase("ByFields succeeds", () =>
{
Expect.equal(
Postgres.Query.Find.ByFields("x", FieldMatch.Any, [Field.GE("Golf", 0), Field.LE("Flog", 1)]),
$"SELECT data FROM x WHERE data->>'Golf' >= @field0 OR data->>'Flog' <= @field1",
"SELECT by JSON comparison query not correct");
}),
#pragma warning disable CS0618
TestCase("ByField succeeds", () =>
{
Expect.equal(Postgres.Query.Find.ByField(PostgresDb.TableName, Field.GE("Golf", 0)),
$"SELECT data FROM {PostgresDb.TableName} WHERE data->>'Golf' >= @field0",
"SELECT by JSON comparison query not correct");
}),
#pragma warning restore CS0618
TestCase("byContains succeeds", () =>
{
Expect.equal(Postgres.Query.Find.ByContains(PostgresDb.TableName),
$"SELECT data FROM {PostgresDb.TableName} WHERE data @> @criteria",
"SELECT by JSON containment query not correct");
}),
TestCase("byJsonPath succeeds", () =>
{
Expect.equal(Postgres.Query.Find.ByJsonPath(PostgresDb.TableName),
$"SELECT data FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath",
"SELECT by JSON Path match query not correct");
}) })
]),
TestList("Patch",
[
TestCase("ById succeeds", () =>
{
Expect.equal(Postgres.Query.Patch.ById(PostgresDb.TableName),
$"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data->>'Id' = @id",
"UPDATE partial by ID statement not correct");
}),
TestCase("ByFields succeeds", () =>
{
Expect.equal(
Postgres.Query.Patch.ByFields("x", FieldMatch.All,
[Field.LT("Snail", 0), Field.BT("Slug", 8, 14)]),
$"UPDATE x SET data = data || @data WHERE data->>'Snail' < @field0 AND (data->>'Slug')::numeric BETWEEN @field1min AND @field1max",
"UPDATE partial by ID statement not correct");
}),
#pragma warning disable CS0618
TestCase("ByField succeeds", () =>
{
Expect.equal(Postgres.Query.Patch.ByField(PostgresDb.TableName, Field.LT("Snail", 0)),
$"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data->>'Snail' < @field0",
"UPDATE partial by ID statement not correct");
}),
#pragma warning restore CS0618
TestCase("ByContains succeeds", () =>
{
Expect.equal(Postgres.Query.Patch.ByContains(PostgresDb.TableName),
$"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data @> @criteria",
"UPDATE partial by JSON containment statement not correct");
}),
TestCase("ByJsonPath succeeds", () =>
{
Expect.equal(Postgres.Query.Patch.ByJsonPath(PostgresDb.TableName),
$"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data @? @path::jsonpath",
"UPDATE partial by JSON Path statement not correct");
})
]),
TestList("RemoveFields",
[
TestCase("ById succeeds", () =>
{
Expect.equal(Postgres.Query.RemoveFields.ById(PostgresDb.TableName),
$"UPDATE {PostgresDb.TableName} SET data = data - @name WHERE data->>'Id' = @id",
"Remove field by ID query not correct");
}),
TestCase("ByFields succeeds", () =>
{
Expect.equal(
Postgres.Query.RemoveFields.ByFields("x", FieldMatch.Any,
[Field.LT("Fly", 0), Field.LT("Ant", 2)]),
$"UPDATE x SET data = data - @name WHERE data->>'Fly' < @field0 OR data->>'Ant' < @field1",
"Remove field by field query not correct");
}),
#pragma warning disable CS0618
TestCase("ByField succeeds", () =>
{
Expect.equal(Postgres.Query.RemoveFields.ByField(PostgresDb.TableName, Field.LT("Fly", 0)),
$"UPDATE {PostgresDb.TableName} SET data = data - @name WHERE data->>'Fly' < @field0",
"Remove field by field query not correct");
}),
#pragma warning restore CS0618
TestCase("ByContains succeeds", () =>
{
Expect.equal(Postgres.Query.RemoveFields.ByContains(PostgresDb.TableName),
$"UPDATE {PostgresDb.TableName} SET data = data - @name WHERE data @> @criteria",
"Remove field by contains query not correct");
}),
TestCase("ByJsonPath succeeds", () =>
{
Expect.equal(Postgres.Query.RemoveFields.ByJsonPath(PostgresDb.TableName),
$"UPDATE {PostgresDb.TableName} SET data = data - @name WHERE data @? @path::jsonpath",
"Remove field by JSON path query not correct");
})
]),
TestList("Delete",
[
TestCase("ById succeeds", () =>
{
Expect.equal(Postgres.Query.Delete.ById(PostgresDb.TableName),
$"DELETE FROM {PostgresDb.TableName} WHERE data->>'Id' = @id",
"DELETE by ID query not correct");
}),
TestCase("ByFields succeeds", () =>
{
Expect.equal(
Postgres.Query.Delete.ByFields("tbl", FieldMatch.All, [Field.NEX("gone"), Field.EX("here")]),
$"DELETE FROM tbl WHERE data->>'gone' IS NULL AND data->>'here' IS NOT NULL",
"DELETE by JSON comparison query not correct");
}),
#pragma warning disable CS0618
TestCase("ByField succeeds", () =>
{
Expect.equal(Postgres.Query.Delete.ByField(PostgresDb.TableName, Field.NEX("gone")),
$"DELETE FROM {PostgresDb.TableName} WHERE data->>'gone' IS NULL",
"DELETE by JSON comparison query not correct");
}),
#pragma warning restore CS0618
TestCase("byContains succeeds", () =>
{
Expect.equal(Postgres.Query.Delete.ByContains(PostgresDb.TableName),
$"DELETE FROM {PostgresDb.TableName} WHERE data @> @criteria",
"DELETE by JSON containment query not correct");
}),
TestCase("byJsonPath succeeds", () =>
{
Expect.equal(Postgres.Query.Delete.ByJsonPath(PostgresDb.TableName),
$"DELETE FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath",
"DELETE by JSON Path match query not correct");
})
])
]) ])
]); ]);

View File

@ -91,73 +91,7 @@ public static class SqliteCSharpTests
{ {
Expect.equal(Sqlite.Query.Definition.EnsureTable("tbl"), Expect.equal(Sqlite.Query.Definition.EnsureTable("tbl"),
"CREATE TABLE IF NOT EXISTS tbl (data TEXT NOT NULL)", "CREATE TABLE statement not correct"); "CREATE TABLE IF NOT EXISTS tbl (data TEXT NOT NULL)", "CREATE TABLE statement not correct");
}),
TestList("Find",
[
TestCase("ById succeeds", () =>
{
Expect.equal(Sqlite.Query.Find.ById("tbl"), "SELECT data FROM tbl WHERE data->>'Id' = @id",
"SELECT by ID query not correct");
}),
#pragma warning disable CS0618
TestCase("ByField succeeds", () =>
{
Expect.equal(Sqlite.Query.Find.ByField("tbl", Field.GE("Golf", 0)),
"SELECT data FROM tbl WHERE data->>'Golf' >= @field0",
"SELECT by JSON comparison query not correct");
}) })
#pragma warning restore CS0618
]),
TestList("Patch",
[
TestCase("ById succeeds", () =>
{
Expect.equal(Sqlite.Query.Patch.ById("tbl"),
"UPDATE tbl SET data = json_patch(data, json(@data)) WHERE data->>'Id' = @id",
"UPDATE partial by ID statement not correct");
}),
#pragma warning disable CS0618
TestCase("ByField succeeds", () =>
{
Expect.equal(Sqlite.Query.Patch.ByField("tbl", Field.NE("Part", 0)),
"UPDATE tbl SET data = json_patch(data, json(@data)) WHERE data->>'Part' <> @field0",
"UPDATE partial by JSON comparison query not correct");
})
#pragma warning restore CS0618
]),
TestList("RemoveFields",
[
TestCase("ById succeeds", () =>
{
Expect.equal(Sqlite.Query.RemoveFields.ById("tbl", new[] { new SqliteParameter("@name", "one") }),
"UPDATE tbl SET data = json_remove(data, @name) WHERE data->>'Id' = @id",
"Remove field by ID query not correct");
}),
#pragma warning disable CS0618
TestCase("ByField succeeds", () =>
{
Expect.equal(Sqlite.Query.RemoveFields.ByField("tbl", Field.LT("Fly", 0),
new[] { new SqliteParameter("@name0", "one"), new SqliteParameter("@name1", "two") }),
"UPDATE tbl SET data = json_remove(data, @name0, @name1) WHERE data->>'Fly' < @field0",
"Remove field by field query not correct");
})
#pragma warning restore CS0618
]),
TestList("Delete",
[
TestCase("ById succeeds", () =>
{
Expect.equal(Sqlite.Query.Delete.ById("tbl"), "DELETE FROM tbl WHERE data->>'Id' = @id",
"DELETE by ID query not correct");
}),
#pragma warning disable CS0618
TestCase("ByField succeeds", () =>
{
Expect.equal(Sqlite.Query.Delete.ByField("tbl", Field.NEX("gone")),
"DELETE FROM tbl WHERE data->>'gone' IS NULL", "DELETE by JSON comparison query not correct");
})
#pragma warning restore CS0618
])
]), ]),
TestList("Parameters", TestList("Parameters",
[ [

View File

@ -170,8 +170,8 @@ let all =
} }
] ]
testList "Query" [ testList "Query" [
test "selectFromTable succeeds" { test "statementWhere succeeds" {
Expect.equal (Query.selectFromTable tbl) $"SELECT data FROM {tbl}" "SELECT statement not correct" Expect.equal (Query.statementWhere "x" "y") "x WHERE y" "Statements not combined correctly"
} }
testList "Definition" [ testList "Definition" [
test "ensureTableFor succeeds" { test "ensureTableFor succeeds" {
@ -194,7 +194,8 @@ let all =
"CREATE INDEX for key statement without schema not constructed correctly" "CREATE INDEX for key statement without schema not constructed correctly"
} }
] ]
test "ensureIndexOn succeeds for multiple fields and directions" { testList "ensureIndexOn" [
test "succeeds for multiple fields and directions" {
Expect.equal Expect.equal
(Query.Definition.ensureIndexOn (Query.Definition.ensureIndexOn
"test.table" "gibberish" [ "taco"; "guac DESC"; "salsa ASC" ] PostgreSQL) "test.table" "gibberish" [ "taco"; "guac DESC"; "salsa ASC" ] PostgreSQL)
@ -203,6 +204,19 @@ let all =
|> String.concat "") |> String.concat "")
"CREATE INDEX for multiple field statement incorrect" "CREATE INDEX for multiple field statement incorrect"
} }
test "succeeds for nested PostgreSQL field" {
Expect.equal
(Query.Definition.ensureIndexOn tbl "nest" [ "a.b.c" ] PostgreSQL)
$"CREATE INDEX IF NOT EXISTS idx_{tbl}_nest ON {tbl} ((data#>>'{{a,b,c}}'))"
"CREATE INDEX for nested PostgreSQL field incorrect"
}
test "succeeds for nested SQLite field" {
Expect.equal
(Query.Definition.ensureIndexOn tbl "nest" [ "a.b.c" ] SQLite)
$"CREATE INDEX IF NOT EXISTS idx_{tbl}_nest ON {tbl} ((data->>'a'->>'b'->>'c'))"
"CREATE INDEX for nested SQLite field incorrect"
}
]
] ]
test "insert succeeds" { test "insert succeeds" {
Expect.equal (Query.insert tbl) $"INSERT INTO {tbl} VALUES (@data)" "INSERT statement not correct" Expect.equal (Query.insert tbl) $"INSERT INTO {tbl} VALUES (@data)" "INSERT statement not correct"
@ -214,7 +228,22 @@ let all =
"INSERT ON CONFLICT UPDATE statement not correct" "INSERT ON CONFLICT UPDATE statement not correct"
} }
test "count succeeds" { test "count succeeds" {
Expect.equal (Query.count "a_table") "SELECT COUNT(*) AS it FROM a_table" "Count query not correct" Expect.equal (Query.count tbl) $"SELECT COUNT(*) AS it FROM {tbl}" "Count query not correct"
}
test "exists succeeds" {
Expect.equal
(Query.exists tbl "turkey")
$"SELECT EXISTS (SELECT 1 FROM {tbl} WHERE turkey) AS it"
"Exists query not correct"
}
test "find succeeds" {
Expect.equal (Query.find tbl) $"SELECT data FROM {tbl}" "Find query not correct"
}
test "update succeeds" {
Expect.equal (Query.update tbl) $"UPDATE {tbl} SET data = @data" "Update query not correct"
}
test "delete succeeds" {
Expect.equal (Query.delete tbl) $"DELETE FROM {tbl}" "Delete query not correct"
} }
] ]
] ]

View File

@ -252,132 +252,6 @@ let unitTests =
test "whereJsonPathMatches succeeds" { test "whereJsonPathMatches succeeds" {
Expect.equal (Query.whereJsonPathMatches "@path") "data @? @path::jsonpath" "WHERE clause not correct" Expect.equal (Query.whereJsonPathMatches "@path") "data @? @path::jsonpath" "WHERE clause not correct"
} }
testList "Find" [
test "byId succeeds" {
Expect.equal
(Query.Find.byId PostgresDb.TableName)
$"SELECT data FROM {PostgresDb.TableName} WHERE data->>'Id' = @id"
"SELECT by ID query not correct"
}
test "byFields succeeds" {
Expect.equal
(Query.Find.byFields "tbl" Any [ Field.GE "Golf" 0; Field.LE "Flog" 1 ])
$"SELECT data FROM tbl WHERE data->>'Golf' >= @field0 OR data->>'Flog' <= @field1"
"SELECT by JSON comparison query not correct"
}
test "byField succeeds" {
Expect.equal
(Query.Find.byField PostgresDb.TableName (Field.GE "Golf" 0))
$"SELECT data FROM {PostgresDb.TableName} WHERE data->>'Golf' >= @field0"
"SELECT by JSON comparison query not correct"
}
test "byContains succeeds" {
Expect.equal
(Query.Find.byContains PostgresDb.TableName)
$"SELECT data FROM {PostgresDb.TableName} WHERE data @> @criteria"
"SELECT by JSON containment query not correct"
}
test "byJsonPath succeeds" {
Expect.equal
(Query.Find.byJsonPath PostgresDb.TableName)
$"SELECT data FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath"
"SELECT by JSON Path match query not correct"
}
]
testList "Patch" [
test "byId succeeds" {
Expect.equal
(Query.Patch.byId PostgresDb.TableName)
$"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data->>'Id' = @id"
"UPDATE partial by ID statement not correct"
}
test "byFields succeeds" {
Expect.equal
(Query.Patch.byFields "x" All [ Field.LT "Snail" 0; Field.BT "Slug" 8 12 ])
$"UPDATE x SET data = data || @data WHERE data->>'Snail' < @field0 AND (data->>'Slug')::numeric BETWEEN @field1min AND @field1max"
"UPDATE partial by ID statement not correct"
}
test "byField succeeds" {
Expect.equal
(Query.Patch.byField PostgresDb.TableName (Field.LT "Snail" 0))
$"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data->>'Snail' < @field0"
"UPDATE partial by ID statement not correct"
}
test "byContains succeeds" {
Expect.equal
(Query.Patch.byContains PostgresDb.TableName)
$"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data @> @criteria"
"UPDATE partial by JSON containment statement not correct"
}
test "byJsonPath succeeds" {
Expect.equal
(Query.Patch.byJsonPath PostgresDb.TableName)
$"UPDATE {PostgresDb.TableName} SET data = data || @data WHERE data @? @path::jsonpath"
"UPDATE partial by JSON Path statement not correct"
}
]
testList "RemoveFields" [
test "byId succeeds" {
Expect.equal
(Query.RemoveFields.byId PostgresDb.TableName)
$"UPDATE {PostgresDb.TableName} SET data = data - @name WHERE data->>'Id' = @id"
"Remove field by ID query not correct"
}
test "byFields succeeds" {
Expect.equal
(Query.RemoveFields.byFields "tbl" Any [ Field.LT "Fly" 0; Field.LT "Ant" 2 ])
"UPDATE tbl SET data = data - @name WHERE data->>'Fly' < @field0 OR data->>'Ant' < @field1"
"Remove field by field query not correct"
}
test "byField succeeds" {
Expect.equal
(Query.RemoveFields.byField PostgresDb.TableName (Field.LT "Fly" 0))
$"UPDATE {PostgresDb.TableName} SET data = data - @name WHERE data->>'Fly' < @field0"
"Remove field by field query not correct"
}
test "byContains succeeds" {
Expect.equal
(Query.RemoveFields.byContains PostgresDb.TableName)
$"UPDATE {PostgresDb.TableName} SET data = data - @name WHERE data @> @criteria"
"Remove field by contains query not correct"
}
test "byJsonPath succeeds" {
Expect.equal
(Query.RemoveFields.byJsonPath PostgresDb.TableName)
$"UPDATE {PostgresDb.TableName} SET data = data - @name WHERE data @? @path::jsonpath"
"Remove field by JSON path query not correct"
}
]
testList "Delete" [
test "byId succeeds" {
Expect.equal
(Query.Delete.byId PostgresDb.TableName)
$"DELETE FROM {PostgresDb.TableName} WHERE data->>'Id' = @id"
"DELETE by ID query not correct"
}
test "byFields succeeds" {
Expect.equal
(Query.Delete.byFields PostgresDb.TableName All [ Field.NEX "gone"; Field.EX "here" ])
$"DELETE FROM {PostgresDb.TableName} WHERE data->>'gone' IS NULL AND data->>'here' IS NOT NULL"
"DELETE by JSON comparison query not correct"
}
test "byField succeeds" {
Expect.equal
(Query.Delete.byField PostgresDb.TableName (Field.NEX "gone"))
$"DELETE FROM {PostgresDb.TableName} WHERE data->>'gone' IS NULL"
"DELETE by JSON comparison query not correct"
}
test "byContains succeeds" {
Expect.equal (Query.Delete.byContains PostgresDb.TableName)
$"DELETE FROM {PostgresDb.TableName} WHERE data @> @criteria"
"DELETE by JSON containment query not correct"
}
test "byJsonPath succeeds" {
Expect.equal (Query.Delete.byJsonPath PostgresDb.TableName)
$"DELETE FROM {PostgresDb.TableName} WHERE data @? @path::jsonpath"
"DELETE by JSON Path match query not correct"
}
]
] ]
] ]

View File

@ -81,65 +81,6 @@ let unitTests =
"CREATE TABLE IF NOT EXISTS tbl (data TEXT NOT NULL)" "CREATE TABLE IF NOT EXISTS tbl (data TEXT NOT NULL)"
"CREATE TABLE statement not correct" "CREATE TABLE statement not correct"
} }
testList "Find" [
test "byId succeeds" {
Expect.equal
(Query.Find.byId "tbl")
"SELECT data FROM tbl WHERE data->>'Id' = @id"
"SELECT by ID query not correct"
}
test "byField succeeds" {
Expect.equal
(Query.Find.byField "tbl" (Field.GE "Golf" 0))
"SELECT data FROM tbl WHERE data->>'Golf' >= @field0"
"SELECT by JSON comparison query not correct"
}
]
testList "Patch" [
test "byId succeeds" {
Expect.equal
(Query.Patch.byId "tbl")
"UPDATE tbl SET data = json_patch(data, json(@data)) WHERE data->>'Id' = @id"
"UPDATE partial by ID statement not correct"
}
test "byField succeeds" {
Expect.equal
(Query.Patch.byField "tbl" (Field.NE "Part" 0))
"UPDATE tbl SET data = json_patch(data, json(@data)) WHERE data->>'Part' <> @field0"
"UPDATE partial by JSON comparison query not correct"
}
]
testList "RemoveFields" [
test "byId succeeds" {
Expect.equal
(Query.RemoveFields.byId "tbl" [ SqliteParameter("@name", "one") ])
"UPDATE tbl SET data = json_remove(data, @name) WHERE data->>'Id' = @id"
"Remove field by ID query not correct"
}
test "byField succeeds" {
Expect.equal
(Query.RemoveFields.byField
"tbl"
(Field.GT "Fly" 0)
[ SqliteParameter("@name0", "one"); SqliteParameter("@name1", "two") ])
"UPDATE tbl SET data = json_remove(data, @name0, @name1) WHERE data->>'Fly' > @field0"
"Remove field by field query not correct"
}
]
testList "Delete" [
test "byId succeeds" {
Expect.equal
(Query.Delete.byId "tbl")
"DELETE FROM tbl WHERE data->>'Id' = @id"
"DELETE by ID query not correct"
}
test "byField succeeds" {
Expect.equal
(Query.Delete.byField "tbl" (Field.NEX "gone"))
"DELETE FROM tbl WHERE data->>'gone' IS NULL"
"DELETE by JSON comparison query not correct"
}
]
] ]
testList "Parameters" [ testList "Parameters" [
test "idParam succeeds" { test "idParam succeeds" {