diff --git a/src/Common/Library.fs b/src/Common/Library.fs index a1cc7a2..4ee5c4f 100644 --- a/src/Common/Library.fs +++ b/src/Common/Library.fs @@ -214,11 +214,6 @@ module Query = let statementWhere statement where = $"%s{statement} WHERE %s{where}" - /// Create a SELECT clause to retrieve the document data from the given table - [] - let selectFromTable tableName = - $"SELECT data FROM %s{tableName}" - /// Queries to define tables and indexes module Definition = @@ -263,11 +258,6 @@ module Query = "INSERT INTO %s VALUES (@data) ON CONFLICT ((data->>'%s')) DO UPDATE SET data = EXCLUDED.data" tableName (Configuration.idField ()) - /// Query to update a document (no WHERE clause) - [] - let update tableName = - $"UPDATE %s{tableName} SET data = @data" - /// Query to count documents in a table (no WHERE clause) [] let count tableName = @@ -277,3 +267,24 @@ module Query = [] let exists tableName where = $"SELECT EXISTS (SELECT 1 FROM %s{tableName} WHERE %s{where}) AS it" + + /// Query to select documents from a table (no WHERE clause) + [] + let find tableName = + $"SELECT data FROM %s{tableName}" + + /// Query to update a document (no WHERE clause) + [] + let update tableName = + $"UPDATE %s{tableName} SET data = @data" + + /// Query to delete documents from a table (no WHERE clause) + [] + let delete tableName = + $"DELETE FROM %s{tableName}" + + /// Create a SELECT clause to retrieve the document data from the given table + [] + [] + let selectFromTable tableName = + find tableName diff --git a/src/Postgres/Extensions.fs b/src/Postgres/Extensions.fs index b297eb9..818d11b 100644 --- a/src/Postgres/Extensions.fs +++ b/src/Postgres/Extensions.fs @@ -396,7 +396,7 @@ type NpgsqlConnectionCSharpExtensions = /// Remove fields from a document by the document's ID [] 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 [] @@ -412,12 +412,12 @@ type NpgsqlConnectionCSharpExtensions = /// Remove fields from documents via a JSON containment query (@>) [] 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 (@?) [] 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 [] diff --git a/src/Postgres/Library.fs b/src/Postgres/Library.fs index 69f86e0..b96d322 100644 --- a/src/Postgres/Library.fs +++ b/src/Postgres/Library.fs @@ -56,6 +56,7 @@ module Parameters = /// Create an ID parameter (name "@id", key will be treated as a string) [] let idParam (key: 'TKey) = + // TODO: bind key by numeric types "@id", Sql.string (string key) /// Create a parameter with a JSON value @@ -170,141 +171,37 @@ module Query = let whereJsonPathMatches paramName = $"data @? %s{paramName}::jsonpath" + /// Create an UPDATE statement to patch documents + [] + let patch tableName = + $"UPDATE %s{tableName} SET data = data || @data" + + /// Create an UPDATE statement to remove fields from documents + [] + let removeFields tableName = + $"UPDATE %s{tableName} SET data = data - @name" + + /// Create a query by a document's ID + [] + 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 - let fieldQuery statement howMatched fields = + [] + let byFields statement howMatched fields = Query.statementWhere statement (whereByFields howMatched fields) /// Create a JSON containment query - let containQuery statement = + [] + let byContains statement = Query.statementWhere statement (whereDataContains "@criteria") /// Create a JSON Path match query - let pathMatchQuery statement = + [] + let byPathMatch statement = Query.statementWhere statement (whereJsonPathMatches "@path") - - /// Queries for retrieving documents - module Find = - - /// Query to retrieve a document by its ID - [] - let byId tableName = - $"""{Query.selectFromTable tableName} WHERE {whereById "@id"}""" - - /// Query to retrieve documents using a comparison on JSON fields - [] - let byFields tableName howMatched fields = - $"{Query.selectFromTable tableName} WHERE {whereByFields howMatched fields}" - - /// Query to retrieve documents using a comparison on a JSON field - [] - [] - let byField tableName field = - byFields tableName Any [ field ] - - /// Query to retrieve documents using a JSON containment query (@>) - [] - let byContains tableName = - $"""{Query.selectFromTable tableName} WHERE {whereDataContains "@criteria"}""" - - /// Query to retrieve documents using a JSON Path match (@?) - [] - 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 - [] - let byId tableName = - whereById "@id" |> update tableName - - /// Query to patch documents match JSON field comparisons (->> =) - [] - let byFields tableName howMatched fields = - whereByFields howMatched fields |> update tableName - - /// Query to patch documents match a JSON field comparison (->> =) - [] - [] - let byField tableName field = - byFields tableName Any [ field ] - - /// Query to patch documents matching a JSON containment query (@>) - [] - let byContains tableName = - whereDataContains "@criteria" |> update tableName - - /// Query to patch documents matching a JSON containment query (@>) - [] - 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 - [] - let byId tableName = - whereById "@id" |> update tableName - - /// Query to remove fields from documents via a comparison on JSON fields within the document - [] - 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 - [] - [] - let byField tableName field = - byFields tableName Any [ field ] - - /// Query to patch documents matching a JSON containment query (@>) - [] - let byContains tableName = - whereDataContains "@criteria" |> update tableName - - /// Query to patch documents matching a JSON containment query (@>) - [] - let byJsonPath tableName = - whereJsonPathMatches "@path" |> update tableName - - /// Queries to delete documents - module Delete = - - /// Query to delete a document by its ID - [] - let byId tableName = - $"""DELETE FROM %s{tableName} WHERE {whereById "@id"}""" - - /// Query to delete documents using a comparison on JSON fields - [] - let byFields tableName howMatched fields = - $"DELETE FROM %s{tableName} WHERE {whereByFields howMatched fields}" - - /// Query to delete documents using a comparison on a JSON field - [] - [] - let byField tableName field = - byFields tableName Any [ field ] - - /// Query to delete documents using a JSON containment query (@>) - [] - let byContains tableName = - $"""DELETE FROM %s{tableName} WHERE {whereDataContains "@criteria"}""" - - /// Query to delete documents using a JSON Path match (@?) - [] - let byJsonPath tableName = - $"""DELETE FROM %s{tableName} WHERE {whereJsonPathMatches "@path"}""" /// Functions for dealing with results @@ -434,19 +331,19 @@ module WithProps = [] let byFields tableName howMatched fields sqlProps = 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 (@>) [] let byContains tableName (criteria: 'TContains) sqlProps = 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 (@?) [] let byJsonPath tableName jsonPath sqlProps = 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 [] @@ -491,27 +388,31 @@ module WithProps = /// Retrieve all documents in the given table [] 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 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) [] 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) 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 (->> =) [] let byFields<'TDoc> tableName howMatched fields sqlProps = 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 (->> =) [] [] @@ -521,7 +422,10 @@ module WithProps = /// Retrieve documents matching JSON field comparisons (->> =) let ByFields<'TDoc>(tableName, howMatched, fields, sqlProps) = 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 (->> =) [] @@ -532,29 +436,35 @@ module WithProps = [] let byContains<'TDoc> tableName (criteria: obj) sqlProps = 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 (@>) let ByContains<'TDoc>(tableName, criteria: obj, sqlProps) = 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 (@?) [] let byJsonPath<'TDoc> tableName jsonPath sqlProps = 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 (@?) let ByJsonPath<'TDoc>(tableName, jsonPath, sqlProps) = 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 [] let firstByFields<'TDoc> tableName howMatched fields sqlProps = Custom.single<'TDoc> - $"{Query.Find.byFields tableName howMatched fields} LIMIT 1" + $"{Query.byFields (Query.find tableName) howMatched fields} LIMIT 1" (addFieldParams fields []) fromData<'TDoc> sqlProps @@ -568,7 +478,7 @@ module WithProps = /// Retrieve the first document matching JSON field comparisons (->> =); returns null if not found let FirstByFields<'TDoc when 'TDoc: null>(tableName, howMatched, fields, sqlProps) = Custom.Single<'TDoc>( - $"{Query.Find.byFields tableName howMatched fields} LIMIT 1", + $"{Query.byFields (Query.find tableName) howMatched fields} LIMIT 1", addFieldParams fields [], fromData<'TDoc>, sqlProps) @@ -582,12 +492,15 @@ module WithProps = [] let firstByContains<'TDoc> tableName (criteria: obj) sqlProps = 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 let FirstByContains<'TDoc when 'TDoc: null>(tableName, criteria: obj, sqlProps) = Custom.Single<'TDoc>( - $"{Query.Find.byContains tableName} LIMIT 1", + $"{Query.byContains (Query.find tableName)} LIMIT 1", [ jsonParam "@criteria" criteria ], fromData<'TDoc>, sqlProps) @@ -596,12 +509,15 @@ module WithProps = [] let firstByJsonPath<'TDoc> tableName jsonPath sqlProps = 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 let FirstByJsonPath<'TDoc when 'TDoc: null>(tableName, jsonPath, sqlProps) = Custom.Single<'TDoc>( - $"{Query.Find.byJsonPath tableName} LIMIT 1", + $"{Query.byPathMatch (Query.find tableName)} LIMIT 1", [ "@path", Sql.string jsonPath ], fromData<'TDoc>, sqlProps) @@ -634,13 +550,14 @@ module WithProps = /// Patch a document by its ID [] 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 (->> =) [] let byFields tableName howMatched fields (patch: 'TPatch) sqlProps = Custom.nonQuery - (Query.Patch.byFields tableName howMatched fields) + (Query.byFields (Query.patch tableName) howMatched fields) (addFieldParams fields [ jsonParam "@data" patch ]) sqlProps @@ -654,32 +571,33 @@ module WithProps = [] let byContains tableName (criteria: 'TContains) (patch: 'TPatch) sqlProps = 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 (@?) [] let byJsonPath tableName jsonPath (patch: 'TPatch) sqlProps = 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 [] module RemoveFields = /// Remove fields from a document by the document's ID - [] + [] let byId tableName (docId: 'TKey) fieldNames sqlProps = - Custom.nonQuery (Query.RemoveFields.byId tableName) [ 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 + Custom.nonQuery + (Query.byId (Query.removeFields tableName) docId) [ idParam docId; fieldNameParams fieldNames ] sqlProps /// Remove fields from documents via a comparison on JSON fields in the document [] let byFields tableName howMatched fields fieldNames sqlProps = Custom.nonQuery - (Query.RemoveFields.byFields tableName howMatched fields) + (Query.byFields (Query.removeFields tableName) howMatched fields) (addFieldParams fields [ fieldNameParams fieldNames ]) sqlProps @@ -690,29 +608,21 @@ module WithProps = byFields tableName Any [ field ] fieldNames sqlProps /// Remove fields from documents via a JSON containment query (@>) - [] + [] let byContains tableName (criteria: 'TContains) fieldNames sqlProps = Custom.nonQuery - (Query.RemoveFields.byContains tableName) + (Query.byContains (Query.removeFields tableName)) [ jsonParam "@criteria" criteria; fieldNameParams fieldNames ] 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 (@?) [] let byJsonPath tableName jsonPath fieldNames sqlProps = Custom.nonQuery - (Query.RemoveFields.byJsonPath tableName) + (Query.byPathMatch (Query.removeFields tableName)) [ "@path", Sql.string jsonPath; fieldNameParams fieldNames ] 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 [] module Delete = @@ -720,12 +630,13 @@ module WithProps = /// Delete a document by its ID [] 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 (->> =) [] 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 (->> =) [] @@ -736,14 +647,14 @@ module WithProps = /// Delete documents by matching a JSON contains query (@>) [] 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 (@?) [] 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 [] module Custom = @@ -1032,14 +943,10 @@ module Patch = module RemoveFields = /// 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 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 [] let byFields tableName howMatched fields fieldNames = @@ -1052,24 +959,16 @@ module RemoveFields = byFields tableName Any [ field ] fieldNames /// 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 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 (@?) - [] + [] let byJsonPath tableName jsonPath fieldNames = 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 [] module Delete = diff --git a/src/Sqlite/Library.fs b/src/Sqlite/Library.fs index a0d861a..6929be9 100644 --- a/src/Sqlite/Library.fs +++ b/src/Sqlite/Library.fs @@ -56,6 +56,29 @@ module Query = let whereById paramName = whereByFields Any [ { Field.EQ (Configuration.idField ()) 0 with ParameterName = Some paramName } ] + /// Create an UPDATE statement to patch documents + [] + let patch tableName = + $"UPDATE %s{tableName} SET data = json_patch(data, json(@data))" + + /// Create an UPDATE statement to remove fields from documents + [] + 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 + [] + 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 + [] + let byFields statement howMatched fields = + Query.statementWhere statement (whereByFields howMatched fields) + /// Data definition module Definition = @@ -63,92 +86,7 @@ module Query = [] let ensureTable name = Query.Definition.ensureTableFor name "TEXT" - - /// Queries for retrieving documents - module Find = - /// Query to retrieve a document by its ID - [] - let byId tableName = - $"""{Query.selectFromTable tableName} WHERE {whereById "@id"}""" - - /// Query to retrieve documents using a comparison on JSON fields - [] - let byFields tableName howMatched fields = - $"{Query.selectFromTable tableName} WHERE {whereByFields howMatched fields}" - - /// Query to retrieve documents using a comparison on a JSON field - [] - [] - 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 - [] - let byId tableName = - whereById "@id" |> update tableName - - /// Query to patch (partially update) a document via a comparison on JSON fields - [] - let byFields tableName howMatched fields = - whereByFields howMatched fields |> update tableName - - /// Query to patch (partially update) a document via a comparison on a JSON field - [] - [] - 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 - [] - let byId tableName parameters = - whereById "@id" |> update tableName parameters - - /// Query to remove fields from documents via a comparison on JSON fields within the document - [] - 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 - [] - [] - let byField tableName field parameters = - byFields tableName Any [ field ] parameters - - /// Queries to delete documents - module Delete = - - /// Query to delete a document by its ID - [] - let byId tableName = - $"""DELETE FROM %s{tableName} WHERE {whereById "@id"}""" - - /// Query to delete documents using a comparison on JSON fields - [] - let byFields tableName howMatched fields = - $"DELETE FROM %s{tableName} WHERE {whereByFields howMatched fields}" - - /// Query to delete documents using a comparison on a JSON field - [] - [] - let byField tableName field = - byFields tableName Any [ field ] - /// Parameter handling helpers [] @@ -349,10 +287,7 @@ module WithConn = [] let byFields tableName howMatched fields conn = Custom.scalar - (Query.statementWhere (Query.count tableName) (Query.whereByFields howMatched fields)) - (addFieldParams fields []) - toCount - conn + (Query.byFields (Query.count tableName) howMatched fields) (addFieldParams fields []) toCount conn /// Commands to determine if documents exist [] @@ -379,27 +314,30 @@ module WithConn = /// Retrieve all documents in the given table [] 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 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) [] 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) 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 [] let byFields<'TDoc> tableName howMatched fields conn = 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 [] [] @@ -409,8 +347,11 @@ module WithConn = /// Retrieve documents via a comparison on JSON fields let ByFields<'TDoc>(tableName, howMatched, fields, conn) = 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 [] let ByField<'TDoc>(tableName, field, conn) = @@ -420,7 +361,7 @@ module WithConn = [] let firstByFields<'TDoc> tableName howMatched fields conn = Custom.single - $"{Query.Find.byFields tableName howMatched fields} LIMIT 1" + $"{Query.byFields (Query.find tableName) howMatched fields} LIMIT 1" (addFieldParams fields []) fromData<'TDoc> conn @@ -434,7 +375,7 @@ module WithConn = /// Retrieve documents via a comparison on JSON fields, returning only the first result let FirstByFields<'TDoc when 'TDoc: null>(tableName, howMatched, fields, conn) = Custom.Single( - $"{Query.Find.byFields tableName howMatched fields} LIMIT 1", + $"{Query.byFields (Query.find tableName) howMatched fields} LIMIT 1", addFieldParams fields [], fromData<'TDoc>, conn) @@ -472,13 +413,14 @@ module WithConn = /// Patch a document by its ID [] 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 [] let byFields tableName howMatched fields (patch: 'TPatch) conn = Custom.nonQuery - (Query.Patch.byFields tableName howMatched fields) + (Query.byFields (Query.patch tableName) howMatched fields) (addFieldParams fields [ jsonParam "@data" patch ]) conn @@ -497,7 +439,7 @@ module WithConn = let byId tableName (docId: 'TKey) fieldNames conn = let nameParams = fieldNameParams "@name" fieldNames Custom.nonQuery - (Query.RemoveFields.byId tableName nameParams) + (Query.byId (Query.removeFields tableName nameParams) docId) (idParam docId |> Seq.singleton |> Seq.append nameParams) conn @@ -506,10 +448,10 @@ module WithConn = let byFields tableName howMatched fields fieldNames conn = let nameParams = fieldNameParams "@name" fieldNames Custom.nonQuery - (Query.RemoveFields.byFields tableName howMatched fields nameParams) + (Query.byFields (Query.removeFields tableName nameParams) howMatched fields) (addFieldParams fields nameParams) conn - + /// Remove fields from documents via a comparison on a JSON field in the document [] [] @@ -523,13 +465,13 @@ module WithConn = /// Delete a document by its ID [] 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 [] 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 [] [] diff --git a/src/Tests.CSharp/CommonCSharpTests.cs b/src/Tests.CSharp/CommonCSharpTests.cs index d1ed8cd..fa60bb8 100644 --- a/src/Tests.CSharp/CommonCSharpTests.cs +++ b/src/Tests.CSharp/CommonCSharpTests.cs @@ -238,10 +238,9 @@ public static class CommonCSharpTests ]), TestList("Query", [ - TestCase("SelectFromTable succeeds", () => + TestCase("StatementWhere succeeds", () => { - Expect.equal(Query.SelectFromTable("test.table"), "SELECT data FROM test.table", - "SELECT statement not correct"); + Expect.equal(Query.StatementWhere("q", "r"), "q WHERE r", "Statements not combined correctly"); }), TestList("Definition", [ @@ -266,15 +265,32 @@ public static class CommonCSharpTests "CREATE INDEX for key statement without schema not constructed correctly"); }) ]), - TestCase("EnsureIndexOn succeeds for multiple fields and directions", () => - { - Expect.equal( - Query.Definition.EnsureIndexOn("test.table", "gibberish", - new[] { "taco", "guac DESC", "salsa ASC" }, Dialect.SQLite), - "CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table " - + "((data->>'taco'), (data->>'guac') DESC, (data->>'salsa') ASC)", - "CREATE INDEX for multiple field statement incorrect"); - }) + TestList("EnsureIndexOn", + [ + TestCase("succeeds for multiple fields and directions", () => + { + Expect.equal( + Query.Definition.EnsureIndexOn("test.table", "gibberish", + new[] { "taco", "guac DESC", "salsa ASC" }, Dialect.SQLite), + "CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table " + + "((data->>'taco'), (data->>'guac') DESC, (data->>'salsa') ASC)", + "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", () => { @@ -285,6 +301,27 @@ public static class CommonCSharpTests Expect.equal(Query.Save("tbl"), "INSERT INTO tbl VALUES (@data) ON CONFLICT ((data->>'Id')) DO UPDATE SET data = EXCLUDED.data", "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"); }) ]) ]); diff --git a/src/Tests.CSharp/PostgresCSharpTests.cs b/src/Tests.CSharp/PostgresCSharpTests.cs index cc115fd..71ff804 100644 --- a/src/Tests.CSharp/PostgresCSharpTests.cs +++ b/src/Tests.CSharp/PostgresCSharpTests.cs @@ -279,153 +279,7 @@ public static class PostgresCSharpTests { Expect.equal(Postgres.Query.WhereJsonPathMatches("@path"), "data @? @path::jsonpath", "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"); - }) - ]) + }) ]) ]); diff --git a/src/Tests.CSharp/SqliteCSharpTests.cs b/src/Tests.CSharp/SqliteCSharpTests.cs index be07d87..3aa1f86 100644 --- a/src/Tests.CSharp/SqliteCSharpTests.cs +++ b/src/Tests.CSharp/SqliteCSharpTests.cs @@ -91,73 +91,7 @@ public static class SqliteCSharpTests { Expect.equal(Sqlite.Query.Definition.EnsureTable("tbl"), "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", [ diff --git a/src/Tests/CommonTests.fs b/src/Tests/CommonTests.fs index 4e2eab0..edc5cb1 100644 --- a/src/Tests/CommonTests.fs +++ b/src/Tests/CommonTests.fs @@ -170,8 +170,8 @@ let all = } ] testList "Query" [ - test "selectFromTable succeeds" { - Expect.equal (Query.selectFromTable tbl) $"SELECT data FROM {tbl}" "SELECT statement not correct" + test "statementWhere succeeds" { + Expect.equal (Query.statementWhere "x" "y") "x WHERE y" "Statements not combined correctly" } testList "Definition" [ test "ensureTableFor succeeds" { @@ -194,15 +194,29 @@ let all = "CREATE INDEX for key statement without schema not constructed correctly" } ] - test "ensureIndexOn succeeds for multiple fields and directions" { - Expect.equal - (Query.Definition.ensureIndexOn - "test.table" "gibberish" [ "taco"; "guac DESC"; "salsa ASC" ] PostgreSQL) - ([ "CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table " - "((data->>'taco'), (data->>'guac') DESC, (data->>'salsa') ASC)" ] - |> String.concat "") - "CREATE INDEX for multiple field statement incorrect" - } + testList "ensureIndexOn" [ + test "succeeds for multiple fields and directions" { + Expect.equal + (Query.Definition.ensureIndexOn + "test.table" "gibberish" [ "taco"; "guac DESC"; "salsa ASC" ] PostgreSQL) + ([ "CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table " + "((data->>'taco'), (data->>'guac') DESC, (data->>'salsa') ASC)" ] + |> String.concat "") + "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" { 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" } 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" } ] ] diff --git a/src/Tests/PostgresTests.fs b/src/Tests/PostgresTests.fs index d217a2b..e39bbb9 100644 --- a/src/Tests/PostgresTests.fs +++ b/src/Tests/PostgresTests.fs @@ -252,132 +252,6 @@ let unitTests = test "whereJsonPathMatches succeeds" { 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" - } - ] ] ] diff --git a/src/Tests/SqliteTests.fs b/src/Tests/SqliteTests.fs index a916473..a19d7d3 100644 --- a/src/Tests/SqliteTests.fs +++ b/src/Tests/SqliteTests.fs @@ -81,65 +81,6 @@ let unitTests = "CREATE TABLE IF NOT EXISTS tbl (data TEXT NOT NULL)" "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" [ test "idParam succeeds" {