Skip to content

Commit dcaae87

Browse files
committed
More LINQ query unit tests added
1 parent f207531 commit dcaae87

14 files changed

Lines changed: 3103 additions & 3 deletions

SQLProvider.Tests.sln

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,27 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{D383A6BC-F
2424
EndProject
2525
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "core", "core", "{C7E85487-91DA-43CE-B343-C54614949C13}"
2626
ProjectSection(SolutionItems) = preProject
27-
docs\content\general.fsx = docs\content\general.fsx
28-
docs\content\oracle.fsx = docs\content\oracle.fsx
27+
docs\content\core\composable.fsx = docs\content\core\composable.fsx
28+
docs\content\core\constraints-relationships.fsx = docs\content\core\constraints-relationships.fsx
29+
docs\content\core\contributing.fsx = docs\content\core\contributing.fsx
30+
docs\content\core\crud.fsx = docs\content\core\crud.fsx
31+
docs\content\core\general.fsx = docs\content\core\general.fsx
32+
docs\content\core\individuals.fsx = docs\content\core\individuals.fsx
33+
docs\content\core\mappers.fsx = docs\content\core\mappers.fsx
34+
docs\content\core\msaccess.fsx = docs\content\core\msaccess.fsx
35+
docs\content\core\mssql.fsx = docs\content\core\mssql.fsx
36+
docs\content\core\mssqlssdt.fsx = docs\content\core\mssqlssdt.fsx
37+
docs\content\core\mysql.fsx = docs\content\core\mysql.fsx
38+
docs\content\core\netstandard.fsx = docs\content\core\netstandard.fsx
39+
docs\content\core\odbc.fsx = docs\content\core\odbc.fsx
40+
docs\content\core\oracle.fsx = docs\content\core\oracle.fsx
41+
docs\content\core\parameters.fsx = docs\content\core\parameters.fsx
42+
docs\content\core\postgresql.fsx = docs\content\core\postgresql.fsx
43+
docs\content\core\programmability.fsx = docs\content\core\programmability.fsx
44+
docs\content\core\querying.fsx = docs\content\core\querying.fsx
45+
docs\content\core\sqlite.fsx = docs\content\core\sqlite.fsx
46+
docs\content\core\techdetails.fsx = docs\content\core\techdetails.fsx
47+
docs\content\core\unittest.fsx = docs\content\core\unittest.fsx
2948
EndProjectSection
3049
EndProject
3150
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "SqlProvider.Tests", "tests\SqlProvider.Tests\SqlProvider.Tests.fsproj", "{B5E56E66-5EE2-4C81-A242-9973AAAE9C99}"

docs/content/core/querying.fsx

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,50 @@ let itemAsync =
9696
9797
If you consider using asynchronous queries, read more from the [async documentation](async.html).
9898
99+
**)
100+
101+
// Example: Complex 4-table join pattern
102+
let complexJoinQuery =
103+
query {
104+
for customer in ctx.Main.Customers do
105+
join order in ctx.Main.Orders on (customer.CustomerId = order.CustomerId)
106+
join orderDetail in ctx.Main.OrderDetails on (order.OrderId = orderDetail.OrderId)
107+
join product in ctx.Main.Products on (orderDetail.ProductId = product.ProductId)
108+
where (customer.Country = "USA" && order.OrderDate > DateTime(2023, 1, 1))
109+
select (customer.ContactName, order.OrderDate, product.ProductName)
110+
}
111+
112+
(**
113+
114+
## Null-handling with queries
115+
116+
If you want to do explicit null-handling, you can use UseOptionTypes static parameter for constructor.
117+
You have options to generate nullable fields to Option or ValueOption types.
118+
119+
Here is an example:
120+
121+
*)
122+
type sql2 = SqlDataProvider<
123+
Common.DatabaseProviderTypes.SQLITE,
124+
connectionString,
125+
SQLiteLibrary=Common.SQLiteLibrary.SystemDataSQLite,
126+
ResolutionPath = resolutionPath,
127+
CaseSensitivityChange = Common.CaseSensitivityChange.ORIGINAL,
128+
UseOptionTypes = Common.NullableColumnType.VALUE_OPTION
129+
>
130+
let ctx2 = sql2.GetDataContext()
131+
let cutoffDate = DateTime.Today.AddDays(-7)
132+
133+
// Example: ValueOption patterns
134+
let findRecentOrders =
135+
query {
136+
for order in ctx2.Main.Orders do
137+
where (order.ShippedDate.IsSome && order.ShippedDate.Value > cutoffDate)
138+
select order
139+
}
140+
141+
(**
142+
99143
## SELECT -clause operations
100144
101145
You can control the execution context of the select-operations by the `GetDataContext` parameter `selectOperations`.

tests/SqlProvider.Tests/QueryTests.fs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ open System
1111
open FSharp.Data.Sql
1212
open System.Linq
1313
open NUnit.Framework
14-
open System.Linq
1514

1615
[<Literal>]
1716
let connectionString = @"Data Source=" + __SOURCE_DIRECTORY__ + @"/db/northwindEF.db;Version=3;Read Only=false;FailIfMissing=True;"
@@ -1747,6 +1746,18 @@ let ``simple async sum with operations 2``() =
17471746
} |> Seq.sumAsync |> Async.AwaitTask |> Async.RunSynchronously
17481747
Assert.That(qry, Is.EqualTo(31886.0M).Within(1.0M))
17491748

1749+
[<Test>] // Note: Weird bug on SQLite decimal parameter arithmetic, at least on some drivers.
1750+
let ``simple math op``() =
1751+
let dc = sql.GetDataContext()
1752+
let qry =
1753+
query {
1754+
for od in dc.Main.OrderDetails do
1755+
where ((float od.UnitPrice) * (float od.Quantity) > 100.)
1756+
select (od.OrderId, (float od.UnitPrice) * (float od.Quantity) > 100.)
1757+
} |> Seq.toArray
1758+
1759+
CollectionAssert.IsNotEmpty qry
1760+
17501761
[<Test>]
17511762
let ``simple averageBy``() =
17521763
let dc = sql.GetDataContext()
@@ -2043,6 +2054,20 @@ let ``simple select join twice to same table``() =
20432054
Assert.AreEqual("Butte",c1)
20442055
Assert.AreEqual("Kirkland",c2)
20452056

2057+
[<Test>]
2058+
let ``simple select with multiple table joins with 4 tables``() =
2059+
let dc = sql.GetDataContext()
2060+
let results =
2061+
query {
2062+
for customer in dc.Main.Customers do
2063+
join order in dc.Main.Orders on (customer.CustomerId = order.CustomerId)
2064+
join orderDetail in dc.Main.OrderDetails on (order.OrderId = orderDetail.OrderId)
2065+
join product in dc.Main.Products on (orderDetail.ProductId = product.ProductId)
2066+
where (customer.Country = "USA")
2067+
select (customer.ContactName, product.ProductName)
2068+
} |> Seq.toArray
2069+
Assert.IsTrue(results.Length >= 0)
2070+
20462071
[<Test >]
20472072
let ``simple select query async``() =
20482073
let dc = sql.GetDataContext()
@@ -2159,6 +2184,18 @@ let ``simple select query async6``() =
21592184
} |> Async.RunSynchronously
21602185
()
21612186

2187+
[<Test>]
2188+
let ``simple select query lengthAsync``() =
2189+
task {
2190+
let dc = sql.GetDataContext()
2191+
let! count =
2192+
query {
2193+
for customer in dc.Main.Customers do
2194+
where (customer.Country = "USA")
2195+
select customer.CustomerId
2196+
} |> Seq.lengthAsync
2197+
Assert.IsTrue(count >= 0)
2198+
}
21622199

21632200
[<Test >] // Generates COUNT(DISTINCT CustomerId)
21642201
let ``simple select with distinct count async``() =

tests/SqlProvider.Tests/SqlProvider.Tests.fsproj

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@
1919
<IsLinux Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true'">true</IsLinux>
2020
</PropertyGroup>
2121
<ItemGroup>
22+
<Compile Include="more\AdvancedQueryTests.fs" />
23+
<Compile Include="more\AdvancedQueryTestsWithOpts.fs" />
24+
<Compile Include="more\ComplexJoinTests.fs" />
25+
<Compile Include="more\NavigationPropertiesTests.fs" />
26+
<Compile Include="more\OptionTypesTests.fs" />
27+
<Compile Include="more\PerformancePatternTests.fs" />
28+
<Compile Include="more\PerformanceTests.fs" />
29+
<Compile Include="more\SubqueryCompositionTests.fs" />
30+
<Compile Include="more\SupplementaryTests.fs" />
31+
<Compile Include="more\ValueOptionTests.fs" />
2232
<Compile Include="QueryTests.fs" />
2333
<Compile Include="CrudTests.fs" />
2434
<!--<Compile Include="PostgreSQLTests.fs" />-->
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
#if INTERACTIVE
2+
#I @"../../bin/lib/net48/"
3+
#r "FSharp.Data.SqlProvider.Common.dll"
4+
#r "FSharp.Data.SqlProvider.dll"
5+
#r @"../../packages/tests/NUnit/lib/netstandard2.0/nunit.framework.dll"
6+
#else
7+
module AdvancedQueryTests
8+
#endif
9+
10+
open System
11+
open FSharp.Data.Sql
12+
open System.Linq
13+
open NUnit.Framework
14+
15+
[<Literal>]
16+
let connectionString = @"Data Source=" + __SOURCE_DIRECTORY__ + @"/../db/northwindEF.db;Version=3;Read Only=false;FailIfMissing=True;"
17+
18+
[<Literal>]
19+
let resolutionPath = __SOURCE_DIRECTORY__ + "/../libs"
20+
21+
// If you want to run these in Visual Studio Test Explorer, please install:
22+
// Tools -> Extensions and Updates... -> Online -> NUnit Test Adapter for Visual Studio
23+
// http://nunit.org/index.php?p=vsTestAdapter&r=2.6.4
24+
25+
type sql = SqlDataProvider<Common.DatabaseProviderTypes.SQLITE, connectionString, CaseSensitivityChange=Common.CaseSensitivityChange.ORIGINAL, ResolutionPath = resolutionPath, SQLiteLibrary=Common.SQLiteLibrary.SystemDataSQLite>
26+
FSharp.Data.Sql.Common.QueryEvents.SqlQueryEvent |> Event.add (printfn "Executing SQL: %O")
27+
type sqlOpt = SqlDataProvider<Common.DatabaseProviderTypes.SQLITE, connectionString, CaseSensitivityChange=Common.CaseSensitivityChange.ORIGINAL, ResolutionPath = resolutionPath, SQLiteLibrary=Common.SQLiteLibrary.SystemDataSQLite, UseOptionTypes = Common.NullableColumnType.VALUE_OPTION>
28+
29+
//[<SetUp>]
30+
//let setUp() =
31+
// let ctx = sql.GetDataContext()
32+
// // Setup test data
33+
// ()
34+
35+
[<Test>]
36+
let ``complex join with navigation properties`` () =
37+
let ctx = sql.GetDataContext()
38+
let query =
39+
query {
40+
for customer in ctx.Main.Customers do
41+
for order in customer.``main.Orders by CustomerID`` do
42+
for detail in order.``main.OrderDetails by OrderID`` do
43+
where (detail.Quantity > (int16 10))
44+
select (customer.CompanyName, order.OrderDate, detail.Quantity)
45+
}
46+
47+
let results = query |> Seq.toList
48+
Assert.IsNotEmpty(results)
49+
50+
[<Test>]
51+
let ``subquery with exists pattern`` () =
52+
let ctx = sql.GetDataContext()
53+
54+
// Find customers who have orders with expensive items
55+
let expensiveOrderCustomers =
56+
query {
57+
for customer in ctx.Main.Customers do
58+
where (query {
59+
for order in ctx.Main.Orders do
60+
join detail in ctx.Main.OrderDetails on (order.OrderId = detail.OrderId)
61+
exists (order.CustomerId = customer.CustomerId && detail.UnitPrice > 50m)
62+
})
63+
select customer.CustomerId
64+
}
65+
66+
let results = expensiveOrderCustomers |> Seq.toList
67+
Assert.IsNotEmpty(results)
68+
69+
[<Test>]
70+
let ``complex aggregation with grouping`` () =
71+
let ctx = sql.GetDataContext()
72+
73+
let orderStats =
74+
query {
75+
for order in ctx.Main.Orders do
76+
join detail in ctx.Main.OrderDetails on (order.OrderId = detail.OrderId)
77+
groupBy order.CustomerId into g
78+
select (g.Key,
79+
g.Count(),
80+
g.Sum(fun (_,x) -> x.UnitPrice * decimal x.Quantity),
81+
g.Average(fun (_,x) -> x.UnitPrice))
82+
}
83+
84+
let results = orderStats |> Seq.toList
85+
Assert.IsNotEmpty(results)
86+
87+
[<Test>]
88+
let ``option type handling with ValueSome pattern`` () =
89+
let ctx = sqlOpt.GetDataContext()
90+
91+
let customersWithRegion =
92+
query {
93+
for customer in ctx.Main.Customers do
94+
where (customer.Region.IsSome)
95+
select (customer.CompanyName, customer.Region.Value)
96+
}
97+
98+
let results = customersWithRegion |> Seq.toList
99+
Assert.IsNotEmpty(results)
100+
101+
[<Test>]
102+
let ``pagination with complex sorting`` () =
103+
let ctx = sql.GetDataContext()
104+
let pageSize = 10
105+
let pageNumber = 1
106+
107+
let pagedResults =
108+
query {
109+
for customer in ctx.Main.Customers do
110+
sortBy customer.Country
111+
thenBy customer.City
112+
thenByDescending customer.CompanyName
113+
skip ((pageNumber - 1) * pageSize)
114+
take pageSize
115+
select customer
116+
}
117+
118+
let results = pagedResults |> Seq.toList
119+
Assert.LessOrEqual(results.Length, pageSize)
120+
121+
122+
[<Test>]
123+
let ``dynamic filtering with multiple optional parameters`` () =
124+
let ctx = sql.GetDataContext()
125+
126+
let searchCustomers (country: string option) (city: string option) (hasOrders: bool) =
127+
let noCountry, country = match country with | None -> true, "" | Some x -> false, x
128+
let noCity, city = match city with | None -> true, "" | Some x -> false, x
129+
130+
query {
131+
for customer in ctx.Main.Customers do
132+
where (
133+
(noCountry || customer.Country = country) &&
134+
(noCity || customer.City = city) &&
135+
(hasOrders ||
136+
(query {
137+
for order in ctx.Main.Orders do
138+
exists (order.CustomerId = customer.CustomerId)
139+
}))
140+
)
141+
select customer
142+
}
143+
144+
let results1 = searchCustomers (Some "USA") None false |> Seq.toList
145+
let results2 = searchCustomers None (Some "London") true |> Seq.toList
146+
147+
Assert.IsNotEmpty(results1)
148+
Assert.IsNotEmpty(results2)
149+
150+
[<Test>]
151+
let ``complex case when expression`` () =
152+
let ctx = sql.GetDataContext()
153+
154+
let customerCategories =
155+
query {
156+
for customer in ctx.Main.Customers do
157+
select (
158+
customer.CompanyName,
159+
(query {
160+
for order in ctx.Main.Orders do
161+
where (order.CustomerId = customer.CustomerId)
162+
count
163+
} |> fun count ->
164+
if count > 10 then "High Volume"
165+
elif count > 5 then "Medium Volume"
166+
else "Low Volume")
167+
)
168+
}
169+
170+
let results = customerCategories |> Seq.toList
171+
Assert.IsNotEmpty(results)
172+

0 commit comments

Comments
 (0)