From f13ab61e70d5e1db8db393cbdb4c47a779a03b0b Mon Sep 17 00:00:00 2001 From: Jordan Marr Date: Fri, 10 Nov 2023 15:33:15 -0500 Subject: [PATCH] Query v2.2.1 - Allowed selected columns of a leftJoined table to be optional by wrapping them with `Some`. --- src/SqlHydra.Query/LinqExpressionVisitors.fs | 7 +++++-- src/SqlHydra.Query/SqlHydra.Query.fsproj | 2 +- src/Tests/SqlServer/QueryIntegrationTests.fs | 22 ++++++++++++++++++++ src/Tests/SqlServer/QueryUnitTests.fs | 14 +++++++++++++ 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/SqlHydra.Query/LinqExpressionVisitors.fs b/src/SqlHydra.Query/LinqExpressionVisitors.fs index e9b00386..3f21083f 100644 --- a/src/SqlHydra.Query/LinqExpressionVisitors.fs +++ b/src/SqlHydra.Query/LinqExpressionVisitors.fs @@ -322,12 +322,12 @@ let visitWhere<'T> (filter: Expression>) (qualifyColumn: string - | nameof like | nameof op_EqualsPercent -> query.WhereLike(fqCol, pattern, false) | _ -> query.WhereNotLike(fqCol, pattern, false) | _ -> notImpl() - | MethodCall m when m.Method.Name = nameof isNullValue || m.Method.Name = nameof isNotNullValue -> + | MethodCall m when List.contains m.Method.Name [ nameof isNullValue; "IsNull"; nameof isNotNullValue ] -> match m.Arguments.[0] with | Property p -> let alias = visitAlias p.Expression let fqCol = qualifyColumn alias p.Member - if m.Method.Name = nameof isNullValue + if m.Method.Name = nameof isNullValue || m.Method.Name = "IsNull" // CompiledName for `isNull` = `IsNull` then query.WhereNull(fqCol) else query.WhereNotNull(fqCol) | _ -> notImpl() @@ -668,6 +668,9 @@ let visitSelect<'T, 'Prop> (propertySelector: Expression>) = | MethodCall m when m.Method.Name = "Invoke" -> // Handle tuples visit m.Object + | MethodCall m when m.Method.Name = "Some" -> + // Columns selected from leftJoined tables may be wrapped in `Some` to make them optional. + visit m.Arguments.[0] | AggregateColumn (aggType, p) -> let alias = visitAlias p.Expression [ SelectedAggregateColumn (aggType, alias, p.Member.Name) ] diff --git a/src/SqlHydra.Query/SqlHydra.Query.fsproj b/src/SqlHydra.Query/SqlHydra.Query.fsproj index 028f0a17..e4b8e2d3 100644 --- a/src/SqlHydra.Query/SqlHydra.Query.fsproj +++ b/src/SqlHydra.Query/SqlHydra.Query.fsproj @@ -4,7 +4,7 @@ netstandard2.0;net6.0;net7.0 true 3390;$(WarnOn) - 2.2.0 + 2.2.1 SqlHydra.Query is an F# query builder powered by SqlKata.Query that supports SQL Server, PostgreSQL, Sqlite and Oracle. diff --git a/src/Tests/SqlServer/QueryIntegrationTests.fs b/src/Tests/SqlServer/QueryIntegrationTests.fs index 9e01e30c..9ebdebd9 100644 --- a/src/Tests/SqlServer/QueryIntegrationTests.fs +++ b/src/Tests/SqlServer/QueryIntegrationTests.fs @@ -786,3 +786,25 @@ let ``Guid getId Bug Repro Issue 38``() = task { guid <>! System.Guid.Empty } + +[] +let ``Individual column from a leftJoin table should be optional if Some``() = task { + let! results = + selectTask HydraReader.Read (Create openContext) { + for o in Sales.SalesOrderHeader do + leftJoin sr in Sales.SalesOrderHeaderSalesReason on (o.SalesOrderID = sr.Value.SalesOrderID) + leftJoin r in Sales.SalesReason on (sr.Value.SalesReasonID = r.Value.SalesReasonID) + where (isNullValue r.Value.Name) + select (o.SalesOrderID, Some r.Value.ReasonType, Some r.Value.Name) + take 10 + } + + let reasonsExist = + results + |> Seq.forall (fun (id, reasonType, name) -> + reasonType <> None && name <> None + ) + + gt0 results + reasonsExist =! false +} \ No newline at end of file diff --git a/src/Tests/SqlServer/QueryUnitTests.fs b/src/Tests/SqlServer/QueryUnitTests.fs index 0ba4fad6..4c388ec4 100644 --- a/src/Tests/SqlServer/QueryUnitTests.fs +++ b/src/Tests/SqlServer/QueryUnitTests.fs @@ -635,3 +635,17 @@ let ``Underscore Assignment Edge Case - insert - should fail with not supported` with | :? System.NotSupportedException -> Assert.Pass() | ex -> Assert.Fail("Should fail with NotSupportedException") + +[] +let ``Individual column from a leftJoin table should be optional if Some``() = + let query = + select { + for o in Sales.SalesOrderHeader do + leftJoin d in Sales.SalesOrderDetail on (o.SalesOrderID = d.Value.SalesOrderID) + select (Some d.Value.OrderQty) + } + + let sql = query |> toSql + sql =! """SELECT [d].[OrderQty] FROM [Sales].[SalesOrderHeader] AS [o] +LEFT JOIN [Sales].[SalesOrderDetail] AS [d] ON ([o].[SalesOrderID] = [d].[SalesOrderID])""" +