From bac3bd2ef09737d66cc0850ed11547c64adc0431 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Wed, 14 Aug 2024 22:13:46 -0400 Subject: [PATCH] Add *Ordered functions - WIP on PostgreSQL numeric ordering --- src/Common/Library.fs | 11 +- src/Postgres/Extensions.fs | 76 +++++++ src/Postgres/Library.fs | 187 ++++++++++++++++++ src/Sqlite/Extensions.fs | 30 +++ src/Sqlite/Library.fs | 80 ++++++++ src/Tests.CSharp/CommonCSharpTests.cs | 70 ++++++- .../PostgresCSharpExtensionTests.cs | 2 +- src/Tests.CSharp/PostgresCSharpTests.cs | 2 +- .../SqliteCSharpExtensionTests.cs | 2 +- src/Tests.CSharp/SqliteCSharpTests.cs | 2 +- src/Tests/BitBadger.Documents.Tests.fsproj | 1 + src/Tests/CommonTests.fs | 56 ++++++ src/Tests/PostgresTests.fs | 24 +++ 13 files changed, 536 insertions(+), 7 deletions(-) diff --git a/src/Common/Library.fs b/src/Common/Library.fs index 00d9bb2..8311375 100644 --- a/src/Common/Library.fs +++ b/src/Common/Library.fs @@ -105,6 +105,10 @@ type Field = { else $"->>'{name}'" $"data{path}" + /// Create a field with a given name, but no other properties filled (op will be EQ, value will be "") + static member Named name = + { Name = name; Op = EQ; Value = ""; ParameterName = None; Qualifier = None } + /// Specify the name of the parameter for this field member this.WithParameterName name = { this with ParameterName = Some name } @@ -300,7 +304,10 @@ module Query = else fields |> Seq.map (fun it -> - let direction = if it.Name.Contains ' ' then $" {(it.Name.Split ' ')[1]}" else "" - it.Path dialect + direction) + if it.Name.Contains ' ' then + let parts = it.Name.Split ' ' + { it with Name = parts[0] }, Some $" {parts[1]}" + else it, None) + |> Seq.map (fun (field, direction) -> field.Path dialect + defaultArg direction "") |> String.concat ", " |> function it -> $" ORDER BY {it}" diff --git a/src/Postgres/Extensions.fs b/src/Postgres/Extensions.fs index 818d11b..6492c1a 100644 --- a/src/Postgres/Extensions.fs +++ b/src/Postgres/Extensions.fs @@ -92,6 +92,10 @@ module Extensions = member conn.findAll<'TDoc> tableName = WithProps.Find.all<'TDoc> tableName (Sql.existingConnection conn) + /// Retrieve all documents in the given table ordered by the given fields in the document + member conn.findAllOrdered<'TDoc> tableName orderFields = + WithProps.Find.allOrdered<'TDoc> tableName orderFields (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) @@ -100,6 +104,12 @@ module Extensions = member conn.findByFields<'TDoc> tableName howMatched fields = WithProps.Find.byFields<'TDoc> tableName howMatched fields (Sql.existingConnection conn) + /// Retrieve documents matching a JSON field comparison query (->> =) ordered by the given fields in the + /// document + member conn.findByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields = + WithProps.Find.byFieldsOrdered<'TDoc> + tableName howMatched queryFields orderFields (Sql.existingConnection conn) + /// Retrieve documents matching a JSON field comparison query (->> =) [] member conn.findByField<'TDoc> tableName field = @@ -109,14 +119,28 @@ module Extensions = member conn.findByContains<'TDoc> tableName (criteria: obj) = WithProps.Find.byContains<'TDoc> tableName criteria (Sql.existingConnection conn) + /// Retrieve documents matching a JSON containment query (@>) ordered by the given fields in the document + member conn.findByContainsOrdered<'TDoc> tableName (criteria: obj) orderFields = + WithProps.Find.byContainsOrdered<'TDoc> tableName criteria orderFields (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 documents matching a JSON Path match query (@?) ordered by the given fields in the document + member conn.findByJsonPathOrdered<'TDoc> tableName jsonPath orderFields = + WithProps.Find.byJsonPathOrdered<'TDoc> tableName jsonPath orderFields (Sql.existingConnection conn) + /// Retrieve the first document matching a JSON field comparison query (->> =); returns None if not found member conn.findFirstByFields<'TDoc> tableName howMatched fields = WithProps.Find.firstByFields<'TDoc> tableName howMatched fields (Sql.existingConnection conn) + /// Retrieve the first document matching a JSON field comparison query (->> =) ordered by the given fields in + /// the document; returns None if not found + member conn.findFirstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields = + WithProps.Find.firstByFieldsOrdered<'TDoc> + tableName howMatched queryFields orderFields (Sql.existingConnection conn) + /// Retrieve the first document matching a JSON field comparison query (->> =); returns None if not found [] member conn.findFirstByField<'TDoc> tableName field = @@ -126,10 +150,20 @@ module Extensions = member conn.findFirstByContains<'TDoc> tableName (criteria: obj) = WithProps.Find.firstByContains<'TDoc> tableName criteria (Sql.existingConnection conn) + /// Retrieve the first document matching a JSON containment query (@>) ordered by the given fields in the + /// document; returns None if not found + member conn.findFirstByContainsOrdered<'TDoc> tableName (criteria: obj) orderFields = + WithProps.Find.firstByContainsOrdered<'TDoc> tableName criteria orderFields (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) + /// Retrieve the first document matching a JSON Path match query (@?) ordered by the given fields in the + /// document; returns None if not found + member conn.findFirstByJsonPathOrdered<'TDoc> tableName jsonPath orderFields = + WithProps.Find.firstByJsonPathOrdered<'TDoc> tableName jsonPath orderFields (Sql.existingConnection conn) + /// Update an entire document by its ID member conn.updateById tableName (docId: 'TKey) (document: 'TDoc) = WithProps.Update.byId tableName docId document (Sql.existingConnection conn) @@ -310,6 +344,11 @@ type NpgsqlConnectionCSharpExtensions = static member inline FindAll<'TDoc>(conn, tableName) = WithProps.Find.All<'TDoc>(tableName, Sql.existingConnection conn) + /// Retrieve all documents in the given table ordered by the given fields in the document + [] + static member inline FindAllOrdered<'TDoc>(conn, tableName, orderFields) = + WithProps.Find.AllOrdered<'TDoc>(tableName, orderFields, 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) = @@ -320,6 +359,12 @@ type NpgsqlConnectionCSharpExtensions = static member inline FindByFields<'TDoc>(conn, tableName, howMatched, fields) = WithProps.Find.ByFields<'TDoc>(tableName, howMatched, fields, Sql.existingConnection conn) + /// Retrieve documents matching a JSON field comparison query (->> =) ordered by the given fields in the document + [] + static member inline FindByFieldsOrdered<'TDoc>(conn, tableName, howMatched, queryFields, orderFields) = + WithProps.Find.ByFieldsOrdered<'TDoc>( + tableName, howMatched, queryFields, orderFields, Sql.existingConnection conn) + /// Retrieve documents matching a JSON field comparison query (->> =) [] [] @@ -331,16 +376,34 @@ type NpgsqlConnectionCSharpExtensions = static member inline FindByContains<'TDoc>(conn, tableName, criteria: obj) = WithProps.Find.ByContains<'TDoc>(tableName, criteria, Sql.existingConnection conn) + /// Retrieve documents matching a JSON containment query (@>) ordered by the given fields in the document + [] + static member inline FindByContainsOrdered<'TDoc>(conn, tableName, criteria: obj, orderFields) = + WithProps.Find.ByContainsOrdered<'TDoc>(tableName, criteria, orderFields, 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 documents matching a JSON Path match query (@?) ordered by the given fields in the document + [] + static member inline FindByJsonPathOrdered<'TDoc>(conn, tableName, jsonPath, orderFields) = + WithProps.Find.ByJsonPathOrdered<'TDoc>(tableName, jsonPath, orderFields, Sql.existingConnection conn) + /// Retrieve the first document matching a JSON field comparison query (->> =); returns null if not found [] static member inline FindFirstByFields<'TDoc when 'TDoc: null>(conn, tableName, howMatched, fields) = WithProps.Find.FirstByFields<'TDoc>(tableName, howMatched, fields, Sql.existingConnection conn) + /// Retrieve the first document matching a JSON field comparison query (->> =) ordered by the given fields in the + /// document; returns null if not found + [] + static member inline FindFirstByFieldsOrdered<'TDoc when 'TDoc: null>( + conn, tableName, howMatched, queryFields, orderFields) = + WithProps.Find.FirstByFieldsOrdered<'TDoc>( + tableName, howMatched, queryFields, orderFields, Sql.existingConnection conn) + /// Retrieve the first document matching a JSON field comparison query (->> =); returns null if not found [] [] @@ -352,11 +415,24 @@ type NpgsqlConnectionCSharpExtensions = 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 containment query (@>) ordered by the given fields in the document; + /// returns None if not found + [] + static member inline FindFirstByContainsOrdered<'TDoc when 'TDoc: null>( + conn, tableName, criteria: obj, orderFields) = + WithProps.Find.FirstByContainsOrdered<'TDoc>(tableName, criteria, orderFields, 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) + /// Retrieve the first document matching a JSON Path match query (@?) ordered by the given fields in the document; + /// returns None if not found + [] + static member inline FindFirstByJsonPathOrdered<'TDoc when 'TDoc: null>(conn, tableName, jsonPath, orderFields) = + WithProps.Find.FirstByJsonPathOrdered<'TDoc>(tableName, jsonPath, orderFields, Sql.existingConnection conn) + /// Update an entire document by its ID [] static member inline UpdateById(conn, tableName, docId: 'TKey, document: 'TDoc) = diff --git a/src/Postgres/Library.fs b/src/Postgres/Library.fs index d3c5d07..b5abc7d 100644 --- a/src/Postgres/Library.fs +++ b/src/Postgres/Library.fs @@ -407,6 +407,16 @@ module WithProps = let All<'TDoc>(tableName, sqlProps) = Custom.List<'TDoc>(Query.find tableName, [], fromData<'TDoc>, sqlProps) + /// Retrieve all documents in the given table ordered by the given fields in the document + [] + let allOrdered<'TDoc> tableName orderFields sqlProps = + Custom.list<'TDoc> (Query.find tableName + Query.orderBy orderFields PostgreSQL) [] fromData<'TDoc> sqlProps + + /// Retrieve all documents in the given table ordered by the given fields in the document + let AllOrdered<'TDoc>(tableName, orderFields, sqlProps) = + Custom.List<'TDoc>( + Query.find tableName + Query.orderBy orderFields PostgreSQL, [], fromData<'TDoc>, sqlProps) + /// Retrieve a document by its ID (returns None if not found) [] let byId<'TKey, 'TDoc> tableName (docId: 'TKey) sqlProps = @@ -434,6 +444,23 @@ module WithProps = fromData<'TDoc>, sqlProps) + /// Retrieve documents matching JSON field comparisons (->> =) ordered by the given fields in the document + [] + let byFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields sqlProps = + Custom.list<'TDoc> + (Query.byFields (Query.find tableName) howMatched queryFields + Query.orderBy orderFields PostgreSQL) + (addFieldParams queryFields []) + fromData<'TDoc> + sqlProps + + /// Retrieve documents matching JSON field comparisons (->> =) ordered by the given fields in the document + let ByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, sqlProps) = + Custom.List<'TDoc>( + Query.byFields (Query.find tableName) howMatched queryFields + Query.orderBy orderFields PostgreSQL, + addFieldParams queryFields [], + fromData<'TDoc>, + sqlProps) + /// Retrieve documents matching a JSON containment query (@>) [] let byContains<'TDoc> tableName (criteria: obj) sqlProps = @@ -448,6 +475,23 @@ module WithProps = fromData<'TDoc>, sqlProps) + /// Retrieve documents matching a JSON containment query (@>) ordered by the given fields in the document + [] + let byContainsOrdered<'TDoc> tableName (criteria: obj) orderFields sqlProps = + Custom.list<'TDoc> + (Query.byContains (Query.find tableName) + Query.orderBy orderFields PostgreSQL) + [ jsonParam "@criteria" criteria ] + fromData<'TDoc> + sqlProps + + /// Retrieve documents matching a JSON containment query (@>) ordered by the given fields in the document + let ByContainsOrdered<'TDoc>(tableName, criteria: obj, orderFields, sqlProps) = + Custom.List<'TDoc>( + Query.byContains (Query.find tableName) + Query.orderBy orderFields PostgreSQL, + [ jsonParam "@criteria" criteria ], + fromData<'TDoc>, + sqlProps) + /// Retrieve documents matching a JSON Path match query (@?) [] let byJsonPath<'TDoc> tableName jsonPath sqlProps = @@ -462,6 +506,23 @@ module WithProps = fromData<'TDoc>, sqlProps) + /// Retrieve documents matching a JSON Path match query (@?) ordered by the given fields in the document + [] + let byJsonPathOrdered<'TDoc> tableName jsonPath orderFields sqlProps = + Custom.list<'TDoc> + (Query.byPathMatch (Query.find tableName) + Query.orderBy orderFields PostgreSQL) + [ "@path", Sql.string jsonPath ] + fromData<'TDoc> + sqlProps + + /// Retrieve documents matching a JSON Path match query (@?) ordered by the given fields in the document + let ByJsonPathOrdered<'TDoc>(tableName, jsonPath, orderFields, sqlProps) = + Custom.List<'TDoc>( + Query.byPathMatch (Query.find tableName) + Query.orderBy orderFields PostgreSQL, + [ "@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 = @@ -479,6 +540,25 @@ module WithProps = fromData<'TDoc>, sqlProps) + /// Retrieve the first document matching JSON field comparisons (->> =) ordered by the given fields in the + /// document; returns None if not found + [] + let firstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields sqlProps = + Custom.single<'TDoc> + $"{Query.byFields (Query.find tableName) howMatched queryFields}{Query.orderBy orderFields PostgreSQL} LIMIT 1" + (addFieldParams queryFields []) + fromData<'TDoc> + sqlProps + + /// Retrieve the first document matching JSON field comparisons (->> =) ordered by the given fields in the + /// document; returns null if not found + let FirstByFieldsOrdered<'TDoc when 'TDoc: null>(tableName, howMatched, queryFields, orderFields, sqlProps) = + Custom.Single<'TDoc>( + $"{Query.byFields (Query.find tableName) howMatched queryFields}{Query.orderBy orderFields PostgreSQL} LIMIT 1", + addFieldParams queryFields [], + fromData<'TDoc>, + sqlProps) + /// Retrieve the first document matching a JSON containment query (@>); returns None if not found [] let firstByContains<'TDoc> tableName (criteria: obj) sqlProps = @@ -496,6 +576,25 @@ module WithProps = fromData<'TDoc>, sqlProps) + /// Retrieve the first document matching a JSON containment query (@>) ordered by the given fields in the + /// document; returns None if not found + [] + let firstByContainsOrdered<'TDoc> tableName (criteria: obj) orderFields sqlProps = + Custom.single<'TDoc> + $"{Query.byContains (Query.find tableName)}{Query.orderBy orderFields PostgreSQL} LIMIT 1" + [ jsonParam "@criteria" criteria ] + fromData<'TDoc> + sqlProps + + /// Retrieve the first document matching a JSON containment query (@>) ordered by the given fields in the + /// document; returns null if not found + let FirstByContainsOrdered<'TDoc when 'TDoc: null>(tableName, criteria: obj, orderFields, sqlProps) = + Custom.Single<'TDoc>( + $"{Query.byContains (Query.find tableName)}{Query.orderBy orderFields PostgreSQL} LIMIT 1", + [ jsonParam "@criteria" criteria ], + fromData<'TDoc>, + sqlProps) + /// Retrieve the first document matching a JSON Path match query (@?); returns None if not found [] let firstByJsonPath<'TDoc> tableName jsonPath sqlProps = @@ -513,6 +612,25 @@ module WithProps = fromData<'TDoc>, sqlProps) + /// Retrieve the first document matching a JSON Path match query (@?) ordered by the given fields in the + /// document; returns None if not found + [] + let firstByJsonPathOrdered<'TDoc> tableName jsonPath orderFields sqlProps = + Custom.single<'TDoc> + $"{Query.byPathMatch (Query.find tableName)}{Query.orderBy orderFields PostgreSQL} LIMIT 1" + [ "@path", Sql.string jsonPath ] + fromData<'TDoc> + sqlProps + + /// Retrieve the first document matching a JSON Path match query (@?) ordered by the given fields in the + /// document; returns null if not found + let FirstByJsonPathOrdered<'TDoc when 'TDoc: null>(tableName, jsonPath, orderFields, sqlProps) = + Custom.Single<'TDoc>( + $"{Query.byPathMatch (Query.find tableName)}{Query.orderBy orderFields PostgreSQL} LIMIT 1", + [ "@path", Sql.string jsonPath ], + fromData<'TDoc>, + sqlProps) + /// Commands to update documents [] module Update = @@ -761,6 +879,15 @@ module Find = let All<'TDoc> tableName = WithProps.Find.All<'TDoc>(tableName, fromDataSource ()) + /// Retrieve all documents in the given table ordered by the given fields in the document + [] + let allOrdered<'TDoc> tableName orderFields = + WithProps.Find.allOrdered<'TDoc> tableName orderFields (fromDataSource ()) + + /// Retrieve all documents in the given table ordered by the given fields in the document + let AllOrdered<'TDoc> tableName orderFields = + WithProps.Find.AllOrdered<'TDoc>(tableName, orderFields, fromDataSource ()) + /// Retrieve a document by its ID; returns None if not found [] let byId<'TKey, 'TDoc> tableName docId = @@ -779,6 +906,15 @@ module Find = let ByFields<'TDoc>(tableName, howMatched, fields) = WithProps.Find.ByFields<'TDoc>(tableName, howMatched, fields, fromDataSource ()) + /// Retrieve documents matching a JSON field comparison query (->> =) ordered by the given fields in the document + [] + let byFieldsOrdered<'TDoc> tableName howMatched queryField orderFields = + WithProps.Find.byFieldsOrdered<'TDoc> tableName howMatched queryField orderFields (fromDataSource ()) + + /// Retrieve documents matching a JSON field comparison query (->> =) ordered by the given fields in the document + let ByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields) = + WithProps.Find.ByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, fromDataSource ()) + /// Retrieve documents matching a JSON containment query (@>) [] let byContains<'TDoc> tableName (criteria: obj) = @@ -788,6 +924,15 @@ module Find = let ByContains<'TDoc>(tableName, criteria: obj) = WithProps.Find.ByContains<'TDoc>(tableName, criteria, fromDataSource ()) + /// Retrieve documents matching a JSON containment query (@>) ordered by the given fields in the document + [] + let byContainsOrdered<'TDoc> tableName (criteria: obj) orderFields = + WithProps.Find.byContainsOrdered<'TDoc> tableName criteria orderFields (fromDataSource ()) + + /// Retrieve documents matching a JSON containment query (@>) ordered by the given fields in the document + let ByContainsOrdered<'TDoc>(tableName, criteria: obj, orderFields) = + WithProps.Find.ByContainsOrdered<'TDoc>(tableName, criteria, orderFields, fromDataSource ()) + /// Retrieve documents matching a JSON Path match query (@?) [] let byJsonPath<'TDoc> tableName jsonPath = @@ -797,6 +942,15 @@ module Find = let ByJsonPath<'TDoc>(tableName, jsonPath) = WithProps.Find.ByJsonPath<'TDoc>(tableName, jsonPath, fromDataSource ()) + /// Retrieve documents matching a JSON Path match query (@?) ordered by the given fields in the document + [] + let byJsonPathOrdered<'TDoc> tableName jsonPath orderFields = + WithProps.Find.byJsonPathOrdered<'TDoc> tableName jsonPath orderFields (fromDataSource ()) + + /// Retrieve documents matching a JSON Path match query (@?) ordered by the given fields in the document + let ByJsonPathOrdered<'TDoc>(tableName, jsonPath, orderFields) = + WithProps.Find.ByJsonPathOrdered<'TDoc>(tableName, jsonPath, orderFields, fromDataSource ()) + /// Retrieve the first document matching a JSON field comparison query (->> =); returns None if not found [] let firstByFields<'TDoc> tableName howMatched fields = @@ -806,6 +960,17 @@ module Find = let FirstByFields<'TDoc when 'TDoc: null>(tableName, howMatched, fields) = WithProps.Find.FirstByFields<'TDoc>(tableName, howMatched, fields, fromDataSource ()) + /// Retrieve the first document matching a JSON field comparison query (->> =) ordered by the given fields in the + /// document; returns None if not found + [] + let firstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields = + WithProps.Find.firstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields (fromDataSource ()) + + /// Retrieve the first document matching a JSON field comparison query (->> =) ordered by the given fields in the + /// document; returns null if not found + let FirstByFieldsOrdered<'TDoc when 'TDoc: null>(tableName, howMatched, queryFields, orderFields) = + WithProps.Find.FirstByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, fromDataSource ()) + /// Retrieve the first document matching a JSON containment query (@>); returns None if not found [] let firstByContains<'TDoc> tableName (criteria: obj) = @@ -815,6 +980,17 @@ module Find = let FirstByContains<'TDoc when 'TDoc: null>(tableName, criteria: obj) = WithProps.Find.FirstByContains<'TDoc>(tableName, criteria, fromDataSource ()) + /// Retrieve the first document matching a JSON containment query (@>) ordered by the given fields in the document; + /// returns None if not found + [] + let firstByContainsOrdered<'TDoc> tableName (criteria: obj) orderFields = + WithProps.Find.firstByContainsOrdered<'TDoc> tableName criteria orderFields (fromDataSource ()) + + /// Retrieve the first document matching a JSON containment query (@>) ordered by the given fields in the document; + /// returns null if not found + let FirstByContainsOrdered<'TDoc when 'TDoc: null>(tableName, criteria: obj, orderFields) = + WithProps.Find.FirstByContainsOrdered<'TDoc>(tableName, criteria, orderFields, fromDataSource ()) + /// Retrieve the first document matching a JSON Path match query (@?); returns None if not found [] let firstByJsonPath<'TDoc> tableName jsonPath = @@ -824,6 +1000,17 @@ module Find = let FirstByJsonPath<'TDoc when 'TDoc: null>(tableName, jsonPath) = WithProps.Find.FirstByJsonPath<'TDoc>(tableName, jsonPath, fromDataSource ()) + /// Retrieve the first document matching a JSON Path match query (@?) ordered by the given fields in the document; + /// returns None if not found + [] + let firstByJsonPathOrdered<'TDoc> tableName jsonPath orderFields = + WithProps.Find.firstByJsonPathOrdered<'TDoc> tableName jsonPath orderFields (fromDataSource ()) + + /// Retrieve the first document matching a JSON Path match query (@?) ordered by the given fields in the document; + /// returns null if not found + let FirstByJsonPathOrdered<'TDoc when 'TDoc: null>(tableName, jsonPath, orderFields) = + WithProps.Find.FirstByJsonPathOrdered<'TDoc>(tableName, jsonPath, orderFields, fromDataSource ()) + /// Commands to update documents [] diff --git a/src/Sqlite/Extensions.fs b/src/Sqlite/Extensions.fs index 576b771..c5e83e5 100644 --- a/src/Sqlite/Extensions.fs +++ b/src/Sqlite/Extensions.fs @@ -71,6 +71,10 @@ module Extensions = member conn.findAll<'TDoc> tableName = WithConn.Find.all<'TDoc> tableName conn + /// Retrieve all documents in the given table ordered by the given fields in the document + member conn.findAllOrdered<'TDoc> tableName orderFields = + WithConn.Find.allOrdered<'TDoc> tableName orderFields conn + /// Retrieve a document by its ID member conn.findById<'TKey, 'TDoc> tableName (docId: 'TKey) = WithConn.Find.byId<'TKey, 'TDoc> tableName docId conn @@ -79,6 +83,10 @@ module Extensions = member conn.findByFields<'TDoc> tableName howMatched fields = WithConn.Find.byFields<'TDoc> tableName howMatched fields conn + /// Retrieve documents via a comparison on JSON fields ordered by the given fields in the document + member conn.findByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields = + WithConn.Find.byFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields conn + /// Retrieve documents via a comparison on a JSON field [] member conn.findByField<'TDoc> tableName field = @@ -88,6 +96,11 @@ module Extensions = member conn.findFirstByFields<'TDoc> tableName howMatched fields = WithConn.Find.firstByFields<'TDoc> tableName howMatched fields conn + /// Retrieve documents via a comparison on JSON fields ordered by the given fields in the document, returning + /// only the first result + member conn.findFirstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields = + WithConn.Find.firstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields conn + /// Retrieve documents via a comparison on a JSON field, returning only the first result [] member conn.findFirstByField<'TDoc> tableName field = @@ -225,6 +238,11 @@ type SqliteConnectionCSharpExtensions = static member inline FindAll<'TDoc>(conn, tableName) = WithConn.Find.All<'TDoc>(tableName, conn) + /// Retrieve all documents in the given table ordered by the given fields in the document + [] + static member inline FindAllOrdered<'TDoc>(conn, tableName, orderFields) = + WithConn.Find.AllOrdered<'TDoc>(tableName, conn, orderFields) + /// Retrieve a document by its ID [] static member inline FindById<'TKey, 'TDoc when 'TDoc: null>(conn, tableName, docId: 'TKey) = @@ -235,6 +253,11 @@ type SqliteConnectionCSharpExtensions = static member inline FindByFields<'TDoc>(conn, tableName, howMatched, fields) = WithConn.Find.ByFields<'TDoc>(tableName, howMatched, fields, conn) + /// Retrieve documents via a comparison on JSON fields ordered by the given fields in the document + [] + static member inline FindByFieldsOrdered<'TDoc>(conn, tableName, howMatched, queryFields, orderFields) = + WithConn.Find.ByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, conn) + /// Retrieve documents via a comparison on a JSON field [] [] @@ -246,6 +269,13 @@ type SqliteConnectionCSharpExtensions = static member inline FindFirstByFields<'TDoc when 'TDoc: null>(conn, tableName, howMatched, fields) = WithConn.Find.FirstByFields<'TDoc>(tableName, howMatched, fields, conn) + /// Retrieve documents via a comparison on JSON fields ordered by the given fields in the document, returning only + /// the first result + [] + static member inline FindFirstByFieldsOrdered<'TDoc when 'TDoc: null>( + conn, tableName, howMatched, queryFields, orderFields) = + WithConn.Find.FirstByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, conn) + /// Retrieve documents via a comparison on a JSON field, returning only the first result [] [] diff --git a/src/Sqlite/Library.fs b/src/Sqlite/Library.fs index 3ac23de..7fb3afd 100644 --- a/src/Sqlite/Library.fs +++ b/src/Sqlite/Library.fs @@ -314,6 +314,15 @@ module WithConn = let All<'TDoc>(tableName, conn) = Custom.List(Query.find tableName, [], fromData<'TDoc>, conn) + /// Retrieve all documents in the given table ordered by the given fields in the document + [] + let allOrdered<'TDoc> tableName orderFields conn = + Custom.list<'TDoc> (Query.find tableName + Query.orderBy orderFields SQLite) [] fromData<'TDoc> conn + + /// Retrieve all documents in the given table ordered by the given fields in the document + let AllOrdered<'TDoc>(tableName, orderFields, conn) = + Custom.List(Query.find tableName + Query.orderBy orderFields PostgreSQL, [], fromData<'TDoc>, conn) + /// Retrieve a document by its ID (returns None if not found) [] let byId<'TKey, 'TDoc> tableName (docId: 'TKey) conn = @@ -340,6 +349,23 @@ module WithConn = fromData<'TDoc>, conn) + /// Retrieve documents via a comparison on JSON fields ordered by the given fields in the document + [] + let byFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields conn = + Custom.list<'TDoc> + (Query.byFields (Query.find tableName) howMatched queryFields + Query.orderBy orderFields SQLite) + (addFieldParams queryFields []) + fromData<'TDoc> + conn + + /// Retrieve documents via a comparison on JSON fields ordered by the given fields in the document + let ByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, conn) = + Custom.List<'TDoc>( + Query.byFields (Query.find tableName) howMatched queryFields + Query.orderBy orderFields SQLite, + addFieldParams queryFields [], + fromData<'TDoc>, + conn) + /// Retrieve documents via a comparison on JSON fields, returning only the first result [] let firstByFields<'TDoc> tableName howMatched fields conn = @@ -357,6 +383,25 @@ module WithConn = fromData<'TDoc>, conn) + /// Retrieve documents via a comparison on JSON fields ordered by the given fields in the document, returning + /// only the first result + [] + let firstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields conn = + Custom.single + $"{Query.byFields (Query.find tableName) howMatched queryFields}{Query.orderBy orderFields SQLite} LIMIT 1" + (addFieldParams queryFields []) + fromData<'TDoc> + conn + + /// Retrieve documents via a comparison on JSON fields ordered by the given fields in the document, returning + /// only the first result + let FirstByFieldsOrdered<'TDoc when 'TDoc: null>(tableName, howMatched, queryFields, orderFields, conn) = + Custom.Single( + $"{Query.byFields (Query.find tableName) howMatched queryFields}{Query.orderBy orderFields SQLite} LIMIT 1", + addFieldParams queryFields [], + fromData<'TDoc>, + conn) + /// Commands to update documents [] module Update = @@ -560,6 +605,17 @@ module Find = use conn = Configuration.dbConn () WithConn.Find.All<'TDoc>(tableName, conn) + /// Retrieve all documents in the given table ordered by the given fields in the document + [] + let allOrdered<'TDoc> tableName orderFields = + use conn = Configuration.dbConn () + WithConn.Find.allOrdered<'TDoc> tableName orderFields conn + + /// Retrieve all documents in the given table ordered by the given fields in the document + let AllOrdered<'TDoc> tableName orderFields = + use conn = Configuration.dbConn () + WithConn.Find.AllOrdered<'TDoc>(tableName, orderFields, conn) + /// Retrieve a document by its ID (returns None if not found) [] let byId<'TKey, 'TDoc> tableName docId = @@ -582,6 +638,17 @@ module Find = use conn = Configuration.dbConn () WithConn.Find.ByFields<'TDoc>(tableName, howMatched, fields, conn) + /// Retrieve documents via a comparison on JSON fields ordered by the given fields in the document + [] + let byFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields = + use conn = Configuration.dbConn () + WithConn.Find.byFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields conn + + /// Retrieve documents via a comparison on JSON fields ordered by the given fields in the document + let ByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields) = + use conn = Configuration.dbConn () + WithConn.Find.ByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, conn) + /// Retrieve documents via a comparison on JSON fields, returning only the first result [] let firstByFields<'TDoc> tableName howMatched fields = @@ -593,6 +660,19 @@ module Find = use conn = Configuration.dbConn () WithConn.Find.FirstByFields<'TDoc>(tableName, howMatched, fields, conn) + /// Retrieve documents via a comparison on JSON fields ordered by the given fields in the document, returning only + /// the first result + [] + let firstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields = + use conn = Configuration.dbConn () + WithConn.Find.firstByFieldsOrdered<'TDoc> tableName howMatched queryFields orderFields conn + + /// Retrieve documents via a comparison on JSON fields ordered by the given fields in the document, returning only + /// the first result + let FirstByFieldsOrdered<'TDoc when 'TDoc: null>(tableName, howMatched, queryFields, orderFields) = + use conn = Configuration.dbConn () + WithConn.Find.FirstByFieldsOrdered<'TDoc>(tableName, howMatched, queryFields, orderFields, conn) + /// Commands to update documents [] diff --git a/src/Tests.CSharp/CommonCSharpTests.cs b/src/Tests.CSharp/CommonCSharpTests.cs index aafa8d6..547e300 100644 --- a/src/Tests.CSharp/CommonCSharpTests.cs +++ b/src/Tests.CSharp/CommonCSharpTests.cs @@ -174,6 +174,31 @@ public static class CommonCSharpTests Expect.equal(field.Name, "Rad", "Field name incorrect"); Expect.equal(field.Op, Op.NEX, "Operator incorrect"); }), + TestList("NameToPath", + [ + TestCase("succeeds for PostgreSQL and a simple name", () => + { + Expect.equal("data->>'Simple'", Field.NameToPath("Simple", Dialect.PostgreSQL), + "Path not constructed correctly"); + }), + TestCase("succeeds for SQLite and a simple name", () => + { + Expect.equal("data->>'Simple'", Field.NameToPath("Simple", Dialect.SQLite), + "Path not constructed correctly"); + }), + TestCase("succeeds for PostgreSQL and a nested name", () => + { + Expect.equal("data#>>'{A,Long,Path,to,the,Property}'", + Field.NameToPath("A.Long.Path.to.the.Property", Dialect.PostgreSQL), + "Path not constructed correctly"); + }), + TestCase("succeeds for SQLite and a nested name", () => + { + Expect.equal("data->>'A'->>'Long'->>'Path'->>'to'->>'the'->>'Property'", + Field.NameToPath("A.Long.Path.to.the.Property", Dialect.SQLite), + "Path not constructed correctly"); + }) + ]), TestCase("WithParameterName succeeds", () => { var field = Field.EQ("Bob", "Tom").WithParameterName("@name"); @@ -356,7 +381,50 @@ public static class CommonCSharpTests TestCase("Delete succeeds", () => { Expect.equal(Query.Delete("tbl"), "DELETE FROM tbl", "Delete query not correct"); - }) + }), + TestList("OrderBy", + [ + TestCase("succeeds for no fields", () => + { + Expect.equal("", Query.OrderBy([], Dialect.PostgreSQL), + "Order By should have been blank (PostgreSQL)"); + Expect.equal("", Query.OrderBy([], Dialect.SQLite), "Order By should have been blank (SQLite)"); + }), + TestCase("succeeds for PostgreSQL with one field and no direction", () => + { + Expect.equal(" ORDER BY data->>'TestField'", + Query.OrderBy([Field.Named("TestField")], Dialect.PostgreSQL), + "Order By not constructed correctly"); + }), + TestCase("succeeds for SQLite with one field and no direction", () => + { + Expect.equal(" ORDER BY data->>'TestField'", + Query.OrderBy([Field.Named("TestField")], Dialect.SQLite), + "Order By not constructed correctly"); + }), + TestCase("succeeds for PostgreSQL with multiple fields and direction", () => + { + Expect.equal(" ORDER BY data#>>'{Nested,Test,Field}' DESC, data->>'AnotherField', data->>'It' DESC", + Query.OrderBy( + [ + Field.Named("Nested.Test.Field DESC"), Field.Named("AnotherField"), + Field.Named("It DESC") + ], + Dialect.PostgreSQL), "Order By not constructed correctly"); + }), + TestCase("succeeds for SQLite with multiple fields and direction", () => + { + Expect.equal( + " ORDER BY data->>'Nested'->>'Test'->>'Field' DESC, data->>'AnotherField', data->>'It' DESC", + Query.OrderBy( + [ + Field.Named("Nested.Test.Field DESC"), Field.Named("AnotherField"), + Field.Named("It DESC") + ], + Dialect.SQLite), + "Order By not constructed correctly"); + }) + ]) ]) ]); } diff --git a/src/Tests.CSharp/PostgresCSharpExtensionTests.cs b/src/Tests.CSharp/PostgresCSharpExtensionTests.cs index df85d27..d23f6d3 100644 --- a/src/Tests.CSharp/PostgresCSharpExtensionTests.cs +++ b/src/Tests.CSharp/PostgresCSharpExtensionTests.cs @@ -41,7 +41,7 @@ public class PostgresCSharpExtensionTests await using var conn = MkConn(db); await LoadDocs(); - var docs = await conn.CustomList(Query.SelectFromTable(PostgresDb.TableName), Parameters.None, + var docs = await conn.CustomList(Query.Find(PostgresDb.TableName), Parameters.None, Results.FromData); Expect.equal(docs.Count, 5, "There should have been 5 documents returned"); }), diff --git a/src/Tests.CSharp/PostgresCSharpTests.cs b/src/Tests.CSharp/PostgresCSharpTests.cs index 3bfa365..1f53ba7 100644 --- a/src/Tests.CSharp/PostgresCSharpTests.cs +++ b/src/Tests.CSharp/PostgresCSharpTests.cs @@ -400,7 +400,7 @@ public static class PostgresCSharpTests await using var db = PostgresDb.BuildDb(); await LoadDocs(); - var docs = await Custom.List(Query.SelectFromTable(PostgresDb.TableName), Parameters.None, + var docs = await Custom.List(Query.Find(PostgresDb.TableName), Parameters.None, Results.FromData); Expect.equal(docs.Count, 5, "There should have been 5 documents returned"); }), diff --git a/src/Tests.CSharp/SqliteCSharpExtensionTests.cs b/src/Tests.CSharp/SqliteCSharpExtensionTests.cs index 87ceeea..70098ce 100644 --- a/src/Tests.CSharp/SqliteCSharpExtensionTests.cs +++ b/src/Tests.CSharp/SqliteCSharpExtensionTests.cs @@ -52,7 +52,7 @@ public static class SqliteCSharpExtensionTests await using var conn = Sqlite.Configuration.DbConn(); await LoadDocs(); - var docs = await conn.CustomList(Query.SelectFromTable(SqliteDb.TableName), Parameters.None, + var docs = await conn.CustomList(Query.Find(SqliteDb.TableName), Parameters.None, Results.FromData); Expect.equal(docs.Count, 5, "There should have been 5 documents returned"); }), diff --git a/src/Tests.CSharp/SqliteCSharpTests.cs b/src/Tests.CSharp/SqliteCSharpTests.cs index e0af7bc..e0c534e 100644 --- a/src/Tests.CSharp/SqliteCSharpTests.cs +++ b/src/Tests.CSharp/SqliteCSharpTests.cs @@ -194,7 +194,7 @@ public static class SqliteCSharpTests await using var db = await SqliteDb.BuildDb(); await LoadDocs(); - var docs = await Custom.List(Query.SelectFromTable(SqliteDb.TableName), Parameters.None, + var docs = await Custom.List(Query.Find(SqliteDb.TableName), Parameters.None, Results.FromData); Expect.equal(docs.Count, 5, "There should have been 5 documents returned"); }), diff --git a/src/Tests/BitBadger.Documents.Tests.fsproj b/src/Tests/BitBadger.Documents.Tests.fsproj index 90c8b65..d976fb3 100644 --- a/src/Tests/BitBadger.Documents.Tests.fsproj +++ b/src/Tests/BitBadger.Documents.Tests.fsproj @@ -2,6 +2,7 @@ Exe + 1182 diff --git a/src/Tests/CommonTests.fs b/src/Tests/CommonTests.fs index 815600a..4b3b718 100644 --- a/src/Tests/CommonTests.fs +++ b/src/Tests/CommonTests.fs @@ -109,6 +109,28 @@ let all = Expect.isNone field.ParameterName "The default parameter name should be None" Expect.isNone field.Qualifier "The default table qualifier should be None" } + testList "NameToPath" [ + test "succeeds for PostgreSQL and a simple name" { + Expect.equal + "data->>'Simple'" (Field.NameToPath "Simple" PostgreSQL) "Path not constructed correctly" + } + test "succeeds for SQLite and a simple name" { + Expect.equal + "data->>'Simple'" (Field.NameToPath "Simple" SQLite) "Path not constructed correctly" + } + test "succeeds for PostgreSQL and a nested name" { + Expect.equal + "data#>>'{A,Long,Path,to,the,Property}'" + (Field.NameToPath "A.Long.Path.to.the.Property" PostgreSQL) + "Path not constructed correctly" + } + test "succeeds for SQLite and a nested name" { + Expect.equal + "data->>'A'->>'Long'->>'Path'->>'to'->>'the'->>'Property'" + (Field.NameToPath "A.Long.Path.to.the.Property" SQLite) + "Path not constructed correctly" + } + ] test "WithParameterName succeeds" { let field = (Field.EQ "Bob" "Tom").WithParameterName "@name" Expect.isSome field.ParameterName "The parameter name should have been filled" @@ -253,5 +275,39 @@ let all = test "delete succeeds" { Expect.equal (Query.delete tbl) $"DELETE FROM {tbl}" "Delete query not correct" } + testList "orderBy" [ + test "succeeds for no fields" { + Expect.equal "" (Query.orderBy [] PostgreSQL) "Order By should have been blank (PostgreSQL)" + Expect.equal "" (Query.orderBy [] SQLite) "Order By should have been blank (SQLite)" + } + test "succeeds for PostgreSQL with one field and no direction" { + Expect.equal + " ORDER BY data->>'TestField'" + (Query.orderBy [ Field.Named "TestField" ] PostgreSQL) + "Order By not constructed correctly" + } + test "succeeds for SQLite with one field and no direction" { + Expect.equal + " ORDER BY data->>'TestField'" + (Query.orderBy [ Field.Named "TestField" ] SQLite) + "Order By not constructed correctly" + } + test "succeeds for PostgreSQL with multiple fields and direction" { + Expect.equal + " ORDER BY data#>>'{Nested,Test,Field}' DESC, data->>'AnotherField', data->>'It' DESC" + (Query.orderBy + [ Field.Named "Nested.Test.Field DESC"; Field.Named "AnotherField"; Field.Named "It DESC" ] + PostgreSQL) + "Order By not constructed correctly" + } + test "succeeds for SQLite with multiple fields and direction" { + Expect.equal + " ORDER BY data->>'Nested'->>'Test'->>'Field' DESC, data->>'AnotherField', data->>'It' DESC" + (Query.orderBy + [ Field.Named "Nested.Test.Field DESC"; Field.Named "AnotherField"; Field.Named "It DESC" ] + SQLite) + "Order By not constructed correctly" + } + ] ] ] diff --git a/src/Tests/PostgresTests.fs b/src/Tests/PostgresTests.fs index 4c4a635..b930aeb 100644 --- a/src/Tests/PostgresTests.fs +++ b/src/Tests/PostgresTests.fs @@ -602,6 +602,30 @@ let integrationTests = Expect.equal results [] "There should have been no documents returned" } ] + testList "allOrdered" [ + ptestTask "succeeds when ordering numerically" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! results = Find.allOrdered PostgresDb.TableName [ Field.EQ "NumValue" 0 ] + Expect.hasLength results 5 "There should have been 5 documents returned" + Expect.equal + (results |> List.map _.Id |> String.concat "|") + "one|three|two|four|five" + "The documents were not ordered correctly" + } + testTask "succeeds when ordering alphabetically" { + use db = PostgresDb.BuildDb() + do! loadDocs () + + let! results = Find.allOrdered PostgresDb.TableName [ Field.Named "Id DESC" ] + Expect.hasLength results 5 "There should have been 5 documents returned" + Expect.equal + (results |> List.map _.Id |> String.concat "|") + "two|three|one|four|five" + "The documents were not ordered correctly" + } + ] testList "byId" [ testTask "succeeds when a document is found" { use db = PostgresDb.BuildDb()