diff --git a/src/Postgres/Library.fs b/src/Postgres/Library.fs index 2d755e9..32aa352 100644 --- a/src/Postgres/Library.fs +++ b/src/Postgres/Library.fs @@ -62,31 +62,47 @@ module Parameters = [] let jsonParam (name: string) (it: 'TJson) = name, Sql.jsonb (Configuration.serializer().Serialize it) + + /// Convert the fields to their parameters + let private convertFieldsToParameters fields = + let name = ParameterName() + fields + |> Seq.map (fun it -> + seq { + match it.Op with + | EX | NEX -> () + | BT -> + let p = name.Derive it.ParameterName + let values = it.Value :?> obj list + yield ($"{p}min", Sql.parameter (NpgsqlParameter($"{p}min", List.head values))) + yield ($"{p}max", Sql.parameter (NpgsqlParameter($"{p}max", List.last values))) + | _ -> + let p = name.Derive it.ParameterName + yield (p, Sql.parameter (NpgsqlParameter(p, it.Value))) }) + |> Seq.collect id + + /// Create JSON field parameters + [] + let addFieldParams (fields: Field list) parameters = + convertFieldsToParameters fields + |> Seq.toList + |> List.append parameters + + /// Create JSON field parameters + let AddFields fields parameters = + convertFieldsToParameters fields + |> Seq.append parameters /// Create a JSON field parameter [] + [] let addFieldParam name field parameters = - match field.Op with - | EX | NEX -> parameters - | BT -> - let values = field.Value :?> obj list - List.concat - [ parameters - [ ($"{name}min", Sql.parameter (NpgsqlParameter($"{name}min", List.head values))) - ($"{name}max", Sql.parameter (NpgsqlParameter($"{name}max", List.last values))) ] ] - | _ -> (name, Sql.parameter (NpgsqlParameter(name, field.Value))) :: parameters + addFieldParams [ { field with ParameterName = Some name } ] parameters /// Create a JSON field parameter + [] let AddField name field parameters = - match field.Op with - | EX | NEX -> parameters - | BT -> - let values = field.Value :?> obj list - ResizeArray - [ ($"{name}min", Sql.parameter (NpgsqlParameter($"{name}min", List.head values))) - ($"{name}max", Sql.parameter (NpgsqlParameter($"{name}max", List.last values))) ] - |> Seq.append parameters - | _ -> (name, Sql.parameter (NpgsqlParameter(name, field.Value))) |> Seq.singleton |> Seq.append parameters + AddFields [ { field with ParameterName = Some name } ] parameters /// Append JSON field name parameters for the given field names to the given parameters [] @@ -134,7 +150,7 @@ module Query = /// Create a WHERE clause fragment to implement a comparison on a field in a JSON document [] - //[] + [] let whereByField field paramName = whereByFields [ { field with ParameterName = Some paramName } ] Any @@ -184,7 +200,8 @@ module Query = /// Query to count matching documents using a text comparison on a JSON field [] let byField tableName field = - $"""SELECT COUNT(*) AS it FROM %s{tableName} WHERE {whereByField field "@field"}""" + whereByFields [ { field with ParameterName = Some "@field" } ] Any + |> sprintf "SELECT COUNT(*) AS it FROM %s WHERE %s" tableName /// Query to count matching documents using a JSON containment query (@>) [] @@ -207,7 +224,8 @@ module Query = /// Query to determine if documents exist using a comparison on a JSON field [] let byField tableName field = - $"""SELECT EXISTS (SELECT 1 FROM %s{tableName} WHERE {whereByField field "@field"}) AS it""" + whereByFields [ { field with ParameterName = Some "@field" } ] Any + |> sprintf "SELECT EXISTS (SELECT 1 FROM %s WHERE %s) AS it" tableName /// Query to determine if documents exist using a JSON containment query (@>) [] @@ -230,7 +248,8 @@ module Query = /// Query to retrieve documents using a comparison on a JSON field [] let byField tableName field = - $"""{Query.selectFromTable tableName} WHERE {whereByField field "@field"}""" + whereByFields [ { field with ParameterName = Some "@field" } ] Any + |> sprintf "%s WHERE %s" (Query.selectFromTable tableName) /// Query to retrieve documents using a JSON containment query (@>) [] @@ -257,7 +276,7 @@ module Query = /// Query to patch documents match a JSON field comparison (->> =) [] let byField tableName field = - whereByField field "@field" |> update tableName + whereByFields [ { field with ParameterName = Some "@field" } ] Any |> update tableName /// Query to patch documents matching a JSON containment query (@>) [] @@ -284,7 +303,7 @@ module Query = /// Query to remove fields from documents via a comparison on a JSON field within the document [] let byField tableName field = - whereByField field "@field" |> update tableName + whereByFields [ { field with ParameterName = Some "@field" } ] Any |> update tableName /// Query to patch documents matching a JSON containment query (@>) [] @@ -307,7 +326,8 @@ module Query = /// Query to delete documents using a comparison on a JSON field [] let byField tableName field = - $"""DELETE FROM %s{tableName} WHERE {whereByField field "@field"}""" + whereByFields [ { field with ParameterName = Some "@field" } ] Any + |> sprintf "DELETE FROM %s WHERE %s" tableName /// Query to delete documents using a JSON containment query (@>) [] @@ -444,7 +464,11 @@ module WithProps = /// Count matching documents using a JSON field comparison (->> =) [] let byField tableName field sqlProps = - Custom.scalar (Query.Count.byField tableName field) (addFieldParam "@field" field []) toCount sqlProps + Custom.scalar + (Query.Count.byField tableName field) + (addFieldParams [ { field with ParameterName = Some "@field" } ] []) + toCount + sqlProps /// Count matching documents using a JSON containment query (@>) [] @@ -468,7 +492,11 @@ module WithProps = /// Determine if a document exists using a JSON field comparison (->> =) [] let byField tableName field sqlProps = - Custom.scalar (Query.Exists.byField tableName field) (addFieldParam "@field" field []) toExists sqlProps + Custom.scalar + (Query.Exists.byField tableName field) + (addFieldParams [ { field with ParameterName = Some "@field" } ] []) + toExists + sqlProps /// Determine if a document exists using a JSON containment query (@>) [] @@ -506,12 +534,18 @@ module WithProps = [] let byField<'TDoc> tableName field sqlProps = Custom.list<'TDoc> - (Query.Find.byField tableName field) (addFieldParam "@field" field []) fromData<'TDoc> sqlProps + (Query.Find.byField tableName field) + (addFieldParams [ { field with ParameterName = Some "@field" } ] []) + fromData<'TDoc> + sqlProps /// Retrieve documents matching a JSON field comparison (->> =) let ByField<'TDoc>(tableName, field, sqlProps) = Custom.List<'TDoc>( - Query.Find.byField tableName field, addFieldParam "@field" field [], fromData<'TDoc>, sqlProps) + Query.Find.byField tableName field, + addFieldParams [ { field with ParameterName = Some "@field" } ] [], + fromData<'TDoc>, + sqlProps) /// Retrieve documents matching a JSON containment query (@>) [] @@ -540,7 +574,7 @@ module WithProps = let firstByField<'TDoc> tableName field sqlProps = Custom.single<'TDoc> $"{Query.Find.byField tableName field} LIMIT 1" - (addFieldParam "@field" field []) + (addFieldParams [ { field with ParameterName = Some "@field" } ] []) fromData<'TDoc> sqlProps @@ -548,7 +582,7 @@ module WithProps = let FirstByField<'TDoc when 'TDoc: null>(tableName, field, sqlProps) = Custom.Single<'TDoc>( $"{Query.Find.byField tableName field} LIMIT 1", - addFieldParam "@field" field [], + addFieldParams [ { field with ParameterName = Some "@field" } ] [], fromData<'TDoc>, sqlProps) @@ -612,7 +646,7 @@ module WithProps = let byField tableName field (patch: 'TPatch) sqlProps = Custom.nonQuery (Query.Patch.byField tableName field) - (addFieldParam "@field" field [ jsonParam "@data" patch ]) + (addFieldParams [ { field with ParameterName = Some "@field" } ] [ jsonParam "@data" patch ]) sqlProps /// Patch documents using a JSON containment query in the WHERE clause (@>) @@ -645,7 +679,7 @@ module WithProps = let byField tableName field fieldNames sqlProps = Custom.nonQuery (Query.RemoveFields.byField tableName field) - (addFieldParam "@field" field [ fieldNameParam fieldNames ]) + (addFieldParams [ { field with ParameterName = Some "@field" } ] [ fieldNameParam fieldNames ]) sqlProps /// Remove fields from documents via a comparison on a JSON field in the document @@ -688,7 +722,10 @@ module WithProps = /// Delete documents by matching a JSON field comparison query (->> =) [] let byField tableName field sqlProps = - Custom.nonQuery (Query.Delete.byField tableName field) (addFieldParam "@field" field []) sqlProps + Custom.nonQuery + (Query.Delete.byField tableName field) + (addFieldParams [ { field with ParameterName = Some "@field" } ] []) + sqlProps /// Delete documents by matching a JSON contains query (@>) [] diff --git a/src/Sqlite/Library.fs b/src/Sqlite/Library.fs index cec835d..90a8520 100644 --- a/src/Sqlite/Library.fs +++ b/src/Sqlite/Library.fs @@ -51,6 +51,7 @@ module Query = /// Create a WHERE clause fragment to implement a comparison on a field in a JSON document [] + [] let whereByField field paramName = whereByFields [ { field with ParameterName = Some paramName } ] Any @@ -83,7 +84,8 @@ module Query = /// Query to count matching documents using a text comparison on a JSON field [] let byField tableName field = - $"""SELECT COUNT(*) AS it FROM %s{tableName} WHERE {whereByField field "@field"}""" + whereByFields [ { field with ParameterName = Some "@field" } ] Any + |> sprintf "SELECT COUNT(*) AS it FROM %s WHERE %s" tableName /// Queries for determining document existence module Exists = @@ -96,7 +98,8 @@ module Query = /// Query to determine if documents exist using a comparison on a JSON field [] let byField tableName field = - $"""SELECT EXISTS (SELECT 1 FROM %s{tableName} WHERE {whereByField field "@field"}) AS it""" + whereByFields [ { field with ParameterName = Some "@field" } ] Any + |> sprintf "SELECT EXISTS (SELECT 1 FROM %s WHERE %s) AS it" tableName /// Queries for retrieving documents module Find = @@ -109,7 +112,8 @@ module Query = /// Query to retrieve documents using a comparison on a JSON field [] let byField tableName field = - $"""{Query.selectFromTable tableName} WHERE {whereByField field "@field"}""" + whereByFields [ { field with ParameterName = Some "@field" } ] Any + |> sprintf "%s WHERE %s" (Query.selectFromTable tableName) /// Document patching (partial update) queries module Patch = @@ -126,7 +130,7 @@ module Query = /// Query to patch (partially update) a document via a comparison on a JSON field [] let byField tableName field = - whereByField field "@field" |> update tableName + whereByFields [ { field with ParameterName = Some "@field" } ] Any |> update tableName /// Queries to remove fields from documents module RemoveFields = @@ -148,7 +152,7 @@ module Query = /// Query to remove fields from documents via a comparison on a JSON field within the document [] let byField tableName field parameters = - whereByField field "@field" |> update tableName parameters + whereByFields [ { field with ParameterName = Some "@field" } ] Any |> update tableName parameters /// Query to remove fields from documents via a comparison on a JSON field within the document let ByField(tableName, field, parameters) = @@ -165,7 +169,8 @@ module Query = /// Query to delete documents using a comparison on a JSON field [] let byField tableName field = - $"""DELETE FROM %s{tableName} WHERE {whereByField field "@field"}""" + whereByFields [ { field with ParameterName = Some "@field" } ] Any + |> sprintf "DELETE FROM %s WHERE %s" tableName /// Parameter handling helpers @@ -182,30 +187,44 @@ module Parameters = let jsonParam name (it: 'TJson) = SqliteParameter(name, Configuration.serializer().Serialize it) + /// Convert the fields to their parameters + let private convertFieldsToParameters fields = + let name = ParameterName() + fields + |> Seq.map (fun it -> + seq { + match it.Op with + | EX | NEX -> () + | BT -> + let p = name.Derive it.ParameterName + let values = it.Value :?> obj list + yield SqliteParameter($"{p}min", List.head values) + yield SqliteParameter($"{p}max", List.last values) + | _ -> yield SqliteParameter(name.Derive it.ParameterName, it.Value) }) + |> Seq.collect id + + /// Create JSON field parameters + [] + let addFieldParams (fields: Field list) parameters = + convertFieldsToParameters fields + |> Seq.toList + |> List.append parameters + + /// Create JSON field parameters + let AddFields fields parameters = + convertFieldsToParameters fields + |> Seq.append parameters + /// Create a JSON field parameter (name "@field") [] + [] let addFieldParam name field parameters = - match field.Op with - | EX | NEX -> parameters - | BT -> - let values = field.Value :?> obj list - SqliteParameter($"{name}min", values[0]) :: SqliteParameter($"{name}max", values[1]) :: parameters - | _ -> SqliteParameter(name, field.Value) :: parameters + addFieldParams [ { field with ParameterName = Some name } ] parameters /// Create a JSON field parameter (name "@field") + [] let AddField(name, field, parameters) = - match field.Op with - | EX | NEX -> parameters - | BT -> - let values = field.Value :?> obj list - // let min = SqliteParameter($"{name}min", SqliteType.Integer) - // min.Value <- values[0] - // let max = SqliteParameter($"{name}max", SqliteType.Integer) - // max.Value <- values[1] - [ SqliteParameter($"{name}min", values[0]); SqliteParameter($"{name}max", values[1]) ] - // [min; max] - |> Seq.append parameters - | _ -> SqliteParameter(name, field.Value) |> Seq.singleton |> Seq.append parameters + AddFields [ { field with ParameterName = Some name } ] parameters /// Append JSON field name parameters for the given field names to the given parameters [] @@ -366,7 +385,11 @@ module WithConn = /// Count matching documents using a comparison on a JSON field [] let byField tableName field conn = - Custom.scalar (Query.Count.byField tableName field) (addFieldParam "@field" field []) toCount conn + Custom.scalar + (Query.Count.byField tableName field) + (addFieldParams [ { field with ParameterName = Some "@field" } ] []) + toCount + conn /// Commands to determine if documents exist [] @@ -380,7 +403,11 @@ module WithConn = /// Determine if a document exists using a comparison on a JSON field [] let byField tableName field conn = - Custom.scalar (Query.Exists.byField tableName field) (addFieldParam "@field" field []) toExists conn + Custom.scalar + (Query.Exists.byField tableName field) + (addFieldParams [ { field with ParameterName = Some "@field" } ] []) + toExists + conn /// Commands to retrieve documents [] @@ -408,23 +435,35 @@ module WithConn = [] let byField<'TDoc> tableName field conn = Custom.list<'TDoc> - (Query.Find.byField tableName field) (addFieldParam "@field" field []) fromData<'TDoc> conn + (Query.Find.byField tableName field) + (addFieldParams [ { field with ParameterName = Some "@field" } ] []) + fromData<'TDoc> + conn /// Retrieve documents via a comparison on a JSON field let ByField<'TDoc>(tableName, field, conn) = Custom.List<'TDoc>( - Query.Find.byField tableName field, addFieldParam "@field" field [], fromData<'TDoc>, conn) + Query.Find.byField tableName field, + addFieldParams [ { field with ParameterName = Some "@field" } ] [], + fromData<'TDoc>, + conn) /// Retrieve documents via a comparison on a JSON field, returning only the first result [] let firstByField<'TDoc> tableName field conn = Custom.single - $"{Query.Find.byField tableName field} LIMIT 1" (addFieldParam "@field" field []) fromData<'TDoc> conn + $"{Query.Find.byField tableName field} LIMIT 1" + (addFieldParams [ { field with ParameterName = Some "@field" } ] []) + fromData<'TDoc> + conn /// Retrieve documents via a comparison on a JSON field, returning only the first result let FirstByField<'TDoc when 'TDoc: null>(tableName, field, conn) = Custom.Single( - $"{Query.Find.byField tableName field} LIMIT 1", addFieldParam "@field" field [], fromData<'TDoc>, conn) + $"{Query.Find.byField tableName field} LIMIT 1", + addFieldParams [ { field with ParameterName = Some "@field" } ] [], + fromData<'TDoc>, + conn) /// Commands to update documents [] @@ -457,7 +496,9 @@ module WithConn = [] let byField tableName field (patch: 'TPatch) (conn: SqliteConnection) = Custom.nonQuery - (Query.Patch.byField tableName field) (addFieldParam "@field" field [ jsonParam "@data" patch ]) conn + (Query.Patch.byField tableName field) + (addFieldParams [ { field with ParameterName = Some "@field" } ] [ jsonParam "@data" patch ]) + conn /// Commands to remove fields from documents [] @@ -478,7 +519,9 @@ module WithConn = let byField tableName field fieldNames conn = let nameParams = fieldNameParams "@name" fieldNames Custom.nonQuery - (Query.RemoveFields.byField tableName field nameParams) (addFieldParam "@field" field nameParams) conn + (Query.RemoveFields.byField tableName field nameParams) + (addFieldParams [ { field with ParameterName = Some "@field" } ] nameParams) + conn /// Remove fields from documents via a comparison on a JSON field in the document let ByField(tableName, field, fieldNames, conn) = @@ -496,7 +539,10 @@ module WithConn = /// Delete documents by matching a comparison on a JSON field [] let byField tableName field conn = - Custom.nonQuery (Query.Delete.byField tableName field) (addFieldParam "@field" field []) conn + Custom.nonQuery + (Query.Delete.byField tableName field) + (addFieldParams [ { field with ParameterName = Some "@field" } ] []) + conn /// Commands to execute custom SQL queries