From a3e75bf42f952fddd4e8d435a0b7398ab656aaa8 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Wed, 27 Dec 2023 12:57:06 -0500 Subject: [PATCH] WIP on Postgres extensions --- .../BitBadger.Documents.Postgres.fsproj | 1 + src/Postgres/Extensions.fs | 333 ++++++++++++++++++ src/Sqlite/Extensions.fs | 1 - 3 files changed, 334 insertions(+), 1 deletion(-) create mode 100644 src/Postgres/Extensions.fs diff --git a/src/Postgres/BitBadger.Documents.Postgres.fsproj b/src/Postgres/BitBadger.Documents.Postgres.fsproj index 246a808..5cd5b86 100644 --- a/src/Postgres/BitBadger.Documents.Postgres.fsproj +++ b/src/Postgres/BitBadger.Documents.Postgres.fsproj @@ -2,6 +2,7 @@ + diff --git a/src/Postgres/Extensions.fs b/src/Postgres/Extensions.fs new file mode 100644 index 0000000..5a53a8f --- /dev/null +++ b/src/Postgres/Extensions.fs @@ -0,0 +1,333 @@ +namespace BitBadger.Documents.Postgres + +open Npgsql +open Npgsql.FSharp + +/// F# Extensions for the NpgsqlConnection type +[] +module Extensions = + + type NpgsqlConnection with + + /// Execute a query that returns a list of results + member conn.customList<'TDoc> query parameters (mapFunc: RowReader -> 'TDoc) = + WithProps.Custom.list<'TDoc> query parameters mapFunc (Sql.existingConnection conn) + + /// Execute a query that returns one or no results; returns None if not found + member conn.customSingle<'TDoc> query parameters (mapFunc: RowReader -> 'TDoc) = + WithProps.Custom.single<'TDoc> query parameters mapFunc (Sql.existingConnection conn) + + /// Execute a query that returns no results + member conn.customNonQuery query parameters = + WithProps.Custom.nonQuery query parameters (Sql.existingConnection conn) + + /// Execute a query that returns a scalar value + member conn.customScalar<'T when 'T: struct> query parameters (mapFunc: RowReader -> 'T) = + WithProps.Custom.scalar query parameters mapFunc (Sql.existingConnection conn) + + /// Create a document table + member conn.ensureTable name = + WithProps.Definition.ensureTable name (Sql.existingConnection conn) + + /// Create an index on documents in the specified table + member conn.ensureDocumentIndex name idxType = + WithProps.Definition.ensureDocumentIndex name idxType (Sql.existingConnection conn) + + /// Create an index on field(s) within documents in the specified table + member conn.ensureFieldIndex tableName indexName fields = + WithProps.Definition.ensureFieldIndex tableName indexName fields (Sql.existingConnection conn) + + /// Insert a new document + member conn.insert<'TDoc> tableName (document: 'TDoc) = + WithProps.Document.insert<'TDoc> tableName document (Sql.existingConnection conn) + + /// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert") + member conn.save<'TDoc> tableName (document: 'TDoc) = + WithProps.Document.save<'TDoc> tableName document (Sql.existingConnection conn) + + /// Count all documents in a table + member conn.countAll tableName = + WithProps.Count.all tableName (Sql.existingConnection conn) + + /// Count matching documents using a JSON field comparison query (->> =) + member conn.countByField tableName fieldName op (value: obj) = + WithProps.Count.byField tableName fieldName op value (Sql.existingConnection conn) + + /// Count matching documents using a JSON containment query (@>) + member conn.countByContains tableName criteria = + WithProps.Count.byContains tableName criteria (Sql.existingConnection conn) + + /// Count matching documents using a JSON Path match query (@?) + member conn.countByJsonPath tableName jsonPath = + WithProps.Count.byJsonPath tableName jsonPath (Sql.existingConnection conn) + + /// Determine if a document exists for the given ID + member conn.existsById tableName docId = + WithProps.Exists.byId tableName docId (Sql.existingConnection conn) + + /// Determine if documents exist using a JSON field comparison query (->> =) + member conn.existsByField tableName fieldName op (value: obj) = + WithProps.Exists.byField tableName fieldName op value (Sql.existingConnection conn) + + /// Determine if documents exist using a JSON containment query (@>) + member conn.existsByContains tableName criteria = + WithProps.Exists.byContains tableName criteria (Sql.existingConnection conn) + + /// Determine if documents exist using a JSON Path match query (@?) + member conn.existsByJsonPath tableName jsonPath = + WithProps.Exists.byJsonPath tableName jsonPath (Sql.existingConnection conn) + + /// Retrieve all documents in the given table + member conn.findAll<'TDoc> tableName = + WithProps.Find.all<'TDoc> tableName (Sql.existingConnection conn) + + /// Retrieve a document by its ID; returns None if not found + member conn.findById<'TKey, 'TDoc> tableName docId = + WithProps.Find.byId<'TKey, 'TDoc> tableName docId (Sql.existingConnection conn) + + /// Retrieve documents matching a JSON field comparison query (->> =) + member conn.findByField<'TDoc> tableName fieldName op (value: obj) = + WithProps.Find.byField<'TDoc> tableName fieldName op value (Sql.existingConnection conn) + + /// Retrieve documents matching a JSON containment query (@>) + member conn.findByContains<'TDoc> tableName (criteria: obj) = + WithProps.Find.byContains<'TDoc> tableName criteria (Sql.existingConnection conn) + + /// Retrieve documents matching a JSON Path match query (@?) + member conn.findByJsonPath<'TDoc> tableName jsonPath = + WithProps.Find.byJsonPath<'TDoc> tableName jsonPath (Sql.existingConnection conn) + + /// Retrieve the first document matching a JSON field comparison query (->> =); returns None if not found + member conn.findFirstByField<'TDoc> tableName fieldName op (value: obj) = + WithProps.Find.firstByField<'TDoc> tableName fieldName op value (Sql.existingConnection conn) + + /// Retrieve the first document matching a JSON containment query (@>); returns None if not found + member conn.findFirstByContains<'TDoc> tableName (criteria: obj) = + WithProps.Find.firstByContains<'TDoc> tableName criteria (Sql.existingConnection conn) + + /// Retrieve the first document matching a JSON Path match query (@?); returns None if not found + member conn.findFirstByJsonPath<'TDoc> tableName jsonPath = + WithProps.Find.firstByJsonPath<'TDoc> tableName jsonPath (Sql.existingConnection conn) + + /// Update a full document + member conn.updateFull tableName (docId: 'TKey) (document: 'TDoc) = + WithProps.Update.full tableName docId document (Sql.existingConnection conn) + + /// Update a full document + member conn.updateFullFunc tableName (idFunc: 'TDoc -> 'TKey) (document: 'TDoc) = + WithProps.Update.fullFunc tableName idFunc document (Sql.existingConnection conn) + + /// Update a partial document + member conn.updatePartialById tableName (docId: 'TKey) (partial: 'TPartial) = + WithProps.Update.partialById tableName docId partial (Sql.existingConnection conn) + + /// Update partial documents using a JSON field comparison query in the WHERE clause (->> =) + member conn.updatePartialByField tableName fieldName op (value: obj) (partial: 'TPartial) = + WithProps.Update.partialByField tableName fieldName op value partial (Sql.existingConnection conn) + + /// Update partial documents using a JSON containment query in the WHERE clause (@>) + member conn.updatePartialByContains tableName (criteria: 'TCriteria) (partial: 'TPartial) = + WithProps.Update.partialByContains tableName criteria partial (Sql.existingConnection conn) + + /// Update partial documents using a JSON Path match query in the WHERE clause (@?) + member conn.updatePartialByJsonPath tableName jsonPath (partial: 'TPartial) = + WithProps.Update.partialByJsonPath tableName jsonPath partial (Sql.existingConnection conn) + + /// Delete a document by its ID + member conn.deleteById tableName (docId: 'TKey) = + WithProps.Delete.byId tableName docId (Sql.existingConnection conn) + + /// Delete documents by matching a JSON field comparison query (->> =) + member conn.deleteByField tableName fieldName op (value: obj) = + WithProps.Delete.byField tableName fieldName op value (Sql.existingConnection conn) + + /// Delete documents by matching a JSON containment query (@>) + member conn.deleteByContains tableName (criteria: 'TContains) = + WithProps.Delete.byContains tableName criteria (Sql.existingConnection conn) + + /// Delete documents by matching a JSON Path match query (@?) + member conn.deleteByJsonPath tableName path = + WithProps.Delete.byJsonPath tableName path (Sql.existingConnection conn) + + +open System.Runtime.CompilerServices + +/// C# extensions on the NpgsqlConnection type +type NpgsqlConnectionCSharpExtensions = + + /// Execute a query that returns a list of results + [] + static member inline CustomList<'TDoc>(conn, query, parameters, mapFunc: System.Func) = + WithProps.Custom.List<'TDoc>(query, parameters, mapFunc, Sql.existingConnection conn) + + /// Execute a query that returns one or no results; returns None if not found + [] + static member inline CustomSingle<'TDoc when 'TDoc: null>( + conn, query, parameters, mapFunc: System.Func) = + WithProps.Custom.Single<'TDoc>(query, parameters, mapFunc, Sql.existingConnection conn) + + /// Execute a query that returns no results + [] + static member inline CustomNonQuery(conn, query, parameters) = + WithProps.Custom.nonQuery query parameters (Sql.existingConnection conn) + + /// Execute a query that returns a scalar value + [] + static member inline CustomScalar<'T when 'T: struct>( + conn, query, parameters, mapFunc: System.Func) = + WithProps.Custom.Scalar(query, parameters, mapFunc, Sql.existingConnection conn) + + /// Create a document table + [] + static member inline EnsureTable(conn, name) = + WithProps.Definition.ensureTable name (Sql.existingConnection conn) + + /// Create an index on documents in the specified table + [] + static member inline EnsureDocumentIndex(conn, name, idxType) = + WithProps.Definition.ensureDocumentIndex name idxType (Sql.existingConnection conn) + + /// Create an index on field(s) within documents in the specified table + [] + static member inline EnsureFieldIndex(conn, tableName, indexName, fields) = + WithProps.Definition.ensureFieldIndex tableName indexName fields (Sql.existingConnection conn) + + /// Insert a new document + [] + static member inline Insert<'TDoc>(conn, tableName, document: 'TDoc) = + WithProps.Document.insert<'TDoc> tableName document (Sql.existingConnection conn) + + /// Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert") + [] + static member inline Save<'TDoc>(conn, tableName, document: 'TDoc) = + WithProps.Document.save<'TDoc> tableName document (Sql.existingConnection conn) + + /// Count all documents in a table + [] + static member inline CountAll(conn, tableName) = + WithProps.Count.all tableName (Sql.existingConnection conn) + + /// Count matching documents using a JSON field comparison query (->> =) + [] + static member inline CountByField(conn, tableName, fieldName, op, value: obj) = + WithProps.Count.byField tableName fieldName op value (Sql.existingConnection conn) + + /// Count matching documents using a JSON containment query (@>) + [] + static member inline CountByContains(conn, tableName, criteria: 'TCriteria) = + WithProps.Count.byContains tableName criteria (Sql.existingConnection conn) + + /// Count matching documents using a JSON Path match query (@?) + [] + static member inline CountByJsonPath(conn, tableName, jsonPath) = + WithProps.Count.byJsonPath tableName jsonPath (Sql.existingConnection conn) + + /// Determine if a document exists for the given ID + [] + static member inline ExistsById(conn, tableName, docId) = + WithProps.Exists.byId tableName docId (Sql.existingConnection conn) + + /// Determine if documents exist using a JSON field comparison query (->> =) + [] + static member inline ExistsByField(conn, tableName, fieldName, op, value: obj) = + WithProps.Exists.byField tableName fieldName op value (Sql.existingConnection conn) + + /// Determine if documents exist using a JSON containment query (@>) + [] + static member inline ExistsByContains(conn, tableName, criteria: 'TCriteria) = + WithProps.Exists.byContains tableName criteria (Sql.existingConnection conn) + + /// Determine if documents exist using a JSON Path match query (@?) + [] + static member inline ExistsByJsonPath(conn, tableName, jsonPath) = + WithProps.Exists.byJsonPath tableName jsonPath (Sql.existingConnection conn) + + /// Retrieve all documents in the given table + [] + static member inline FindAll<'TDoc>(conn, tableName) = + WithProps.Find.All<'TDoc>(tableName, Sql.existingConnection conn) + + /// Retrieve a document by its ID; returns None if not found + [] + static member inline FindById<'TKey, 'TDoc when 'TDoc: null>(conn, tableName, docId: 'TKey) = + WithProps.Find.ById<'TKey, 'TDoc>(tableName, docId, Sql.existingConnection conn) + + /// Retrieve documents matching a JSON field comparison query (->> =) + [] + static member inline FindByField<'TDoc>(conn, tableName, fieldName, op, value: obj) = + WithProps.Find.ByField<'TDoc>(tableName, fieldName, op, value, Sql.existingConnection conn) + + /// Retrieve documents matching a JSON containment query (@>) + [] + static member inline FindByContains<'TDoc>(conn, tableName, criteria: obj) = + WithProps.Find.ByContains<'TDoc>(tableName, criteria, Sql.existingConnection conn) + + /// Retrieve documents matching a JSON Path match query (@?) + [] + static member inline FindByJsonPath<'TDoc>(conn, tableName, jsonPath) = + WithProps.Find.ByJsonPath<'TDoc>(tableName, jsonPath, Sql.existingConnection conn) + + /// Retrieve the first document matching a JSON field comparison query (->> =); returns None if not found + [] + static member inline FindFirstByField<'TDoc when 'TDoc: null>(conn, tableName, fieldName, op, value: obj) = + WithProps.Find.FirstByField<'TDoc>(tableName, fieldName, op, value, Sql.existingConnection conn) + + /// Retrieve the first document matching a JSON containment query (@>); returns None if not found + [] + static member inline FindFirstByContains<'TDoc when 'TDoc: null>(conn, tableName, criteria: obj) = + WithProps.Find.FirstByContains<'TDoc>(tableName, criteria, Sql.existingConnection conn) + + /// Retrieve the first document matching a JSON Path match query (@?); returns None if not found + [] + static member inline FindFirstByJsonPath<'TDoc when 'TDoc: null>(conn, tableName, jsonPath) = + WithProps.Find.FirstByJsonPath<'TDoc>(tableName, jsonPath, Sql.existingConnection conn) + + /// Update a full document + [] + static member inline UpdateFull(conn, tableName, docId: 'TKey, document: 'TDoc) = + WithProps.Update.full tableName docId document (Sql.existingConnection conn) + + /// Update a full document + [] + static member inline UpdateFullFunc(conn, tableName, idFunc: System.Func<'TDoc, 'TKey>, document: 'TDoc) = + WithProps.Update.FullFunc(tableName, idFunc, document, Sql.existingConnection conn) + + /// Update a partial document + [] + static member inline UpdatePartialById(conn, tableName, docId: 'TKey, partial: 'TPartial) = + WithProps.Update.partialById tableName docId partial (Sql.existingConnection conn) + + /// Update partial documents using a JSON field comparison query in the WHERE clause (->> =) + [] + static member inline UpdatePartialByField(conn, tableName, fieldName, op, value: obj, partial: 'TPartial) = + WithProps.Update.partialByField tableName fieldName op value partial (Sql.existingConnection conn) + + /// Update partial documents using a JSON containment query in the WHERE clause (@>) + [] + static member inline UpdatePartialByContains(conn, tableName, criteria: 'TCriteria, partial: 'TPartial) = + WithProps.Update.partialByContains tableName criteria partial (Sql.existingConnection conn) + + /// Update partial documents using a JSON Path match query in the WHERE clause (@?) + [] + static member inline UpdatePartialByJsonPath(conn, tableName, jsonPath, partial: 'TPartial) = + WithProps.Update.partialByJsonPath tableName jsonPath partial (Sql.existingConnection conn) + + /// Delete a document by its ID + [] + static member inline DeleteById(conn, tableName, docId: 'TKey) = + WithProps.Delete.byId tableName docId (Sql.existingConnection conn) + + /// Delete documents by matching a JSON field comparison query (->> =) + [] + static member inline DeleteByField(conn, tableName, fieldName, op, value: obj) = + WithProps.Delete.byField tableName fieldName op value (Sql.existingConnection conn) + + /// Delete documents by matching a JSON containment query (@>) + [] + static member inline DeleteByContains(conn, tableName, criteria: 'TContains) = + WithProps.Delete.byContains tableName criteria (Sql.existingConnection conn) + + /// Delete documents by matching a JSON Path match query (@?) + [] + static member inline DeleteByJsonPath(conn, tableName, path) = + WithProps.Delete.byJsonPath tableName path (Sql.existingConnection conn) diff --git a/src/Sqlite/Extensions.fs b/src/Sqlite/Extensions.fs index bacd13b..57ed75f 100644 --- a/src/Sqlite/Extensions.fs +++ b/src/Sqlite/Extensions.fs @@ -100,7 +100,6 @@ module Extensions = open System.Runtime.CompilerServices /// C# extensions on the SqliteConnection type -[] type SqliteConnectionCSharpExtensions = /// Execute a query that returns a list of results