Skip to content

Commit 6828dff

Browse files
authored
Merge pull request #881 from Thorium/master
Fix for group by in sub-query
2 parents 6a6b518 + b7ec7d0 commit 6828dff

33 files changed

Lines changed: 623 additions & 143 deletions

‎.config/dotnet-tools.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"rollForward": false
4646
},
4747
"fsdocs-tool": {
48-
"version": "20.0.0",
48+
"version": "22.2.0",
4949
"commands": [
5050
"fsdocs"
5151
],

‎docs/RELEASE_NOTES.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
### 1.5.29 - 02.11.2026
22
- Fix: parameters of a sub-query nested in another sub-query are bound (IN, EXISTS, UNION and the like)
33
- Fix: groupBy keys with option or value-option columns, and with x.IsSome / x.IsNone in a tuple key
4+
- Fix: a grouped sub-query of IN / NOT IN that selects an aggregate selects it without the group keys, on both sides of a union too, #746
5+
- IN / NOT IN sub-queries with a projection computed in .NET (e.g. `g.Max(...) + 1L`, a narrowing conversion, a user function) fail with an error instead of comparing against the uncomputed column
6+
- Design time: a provider instance that goes away closes its connection instead of disposing it, as the cached types of other instances still use it ("Cannot access a disposed object")
47
- PostgreSQL: identity columns count as generated, so the typed ``Create(...)`` no longer takes them. Breaking for code that passed a value to a BY DEFAULT identity column: use Create() and set it, or Create(data). A saved ContextSchemaPath cache keeps the old signature until regenerated.
58

69
### 1.5.28 - 17.10.2026

‎docs/content/_template.html‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ <H1 style="font-size: 24px;">{{fsdocs-collection-name}}</H1>
110110
</div>
111111
</dialog>
112112
<script type="module" src="{{root}}content/fsdocs-tips.js"></script>
113+
<script type="module" src="{{root}}content/fsdocs-xmldoc-tips.js"></script>
113114
<script type="module" src="{{root}}content/fsdocs-theme-toggle.js"></script>
114115
<script type="module" src="{{root}}content/fsdocs-theme.js"></script>
115116
<script type="module" src="{{root}}content/fsdocs-search.js"></script>

‎docs/content/content/fsdocs-custom.css‎

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ blockquote {
345345

346346
#content pre.fssnip code {
347347
font: 0.86rem 'Fira Code Fixed', monospace;
348-
font-weight: 600;
348+
font-weight: 500;
349349
}
350350

351351
#content table.pre {
@@ -553,7 +553,7 @@ blockquote {
553553
}
554554
/* keywords */
555555
#content span.k {
556-
color: #1f4ee9;
556+
color: #4878f0;
557557
}
558558
/* comment */
559559
#content span.c {
@@ -597,7 +597,6 @@ div.fsdocs-tip {
597597
border-radius: 4px;
598598
font: 0.85rem 'Fira Code Fixed', monospace;
599599
padding: 6px 8px 6px 8px;
600-
display: none;
601600
color: #d1d1d1;
602601
pointer-events: none;
603602
}
@@ -607,6 +606,31 @@ div.fsdocs-tip {
607606
font: 0.85rem 'Fira Code Fixed', monospace;
608607
}
609608

609+
/* XML doc in a tool tip, rendered by fsdocs-xmldoc-tips.js */
610+
div.fsdocs-tip-doc {
611+
margin-top: 6px;
612+
padding-top: 6px;
613+
border-top: 1px solid #5f7478;
614+
white-space: normal;
615+
}
616+
617+
div.fsdocs-tip-doc-summary {
618+
color: #f0d98c;
619+
}
620+
621+
div.fsdocs-tip-doc-remarks,
622+
div.fsdocs-tip-doc-param {
623+
margin-top: 4px;
624+
}
625+
626+
span.fsdocs-tip-doc-name {
627+
color: #9cdcfe;
628+
}
629+
630+
div.fsdocs-tip-doc code {
631+
color: #ffffff;
632+
}
633+
610634
#content code {
611635
color: #8c938c
612636
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// fsdocs puts a member's XML doc into its tooltip as raw text, tags included.
2+
// This renders the tags: the summary in its own colour, one line per parameter, the return value, and
3+
// <see cref="..."/> / <paramref name="..."/> as the name they refer to.
4+
5+
const docTag = /<\/?(summary|param|returns|remarks|exception|typeparam|see|paramref|typeparamref|para|c|code)\b/;
6+
7+
function refName(el) {
8+
const name = el.getAttribute("cref") || el.getAttribute("name") || el.getAttribute("langword") || el.textContent;
9+
return name.replace(/^[A-Z]:/, "");
10+
}
11+
12+
function inline(node, target) {
13+
for (const child of node.childNodes) {
14+
if (child.nodeType === Node.TEXT_NODE) {
15+
target.append(child.textContent.replace(/\s+/g, " "));
16+
} else if (child.nodeType === Node.ELEMENT_NODE) {
17+
const tag = child.localName;
18+
if (tag === "see" || tag === "seealso" || tag === "paramref" || tag === "typeparamref" || tag === "c" || tag === "code") {
19+
const code = document.createElement("code");
20+
code.textContent = child.textContent.trim() || refName(child);
21+
target.append(code);
22+
} else {
23+
inline(child, target);
24+
}
25+
}
26+
}
27+
}
28+
29+
function line(cls, label, el) {
30+
const body = document.createDocumentFragment();
31+
inline(el, body);
32+
body.normalize();
33+
if (body.firstChild?.nodeType === Node.TEXT_NODE) body.firstChild.textContent = body.firstChild.textContent.trimStart();
34+
if (body.lastChild?.nodeType === Node.TEXT_NODE) body.lastChild.textContent = body.lastChild.textContent.trimEnd();
35+
const div = document.createElement("div");
36+
div.className = cls;
37+
if (label) {
38+
const name = document.createElement("span");
39+
name.className = "fsdocs-tip-doc-name";
40+
name.textContent = label;
41+
div.append(name, " ");
42+
}
43+
div.append(body);
44+
return div;
45+
}
46+
47+
function render(text) {
48+
const xml = new DOMParser().parseFromString(`<doc>${text}</doc>`, "application/xml");
49+
if (xml.getElementsByTagName("parsererror").length > 0) return null;
50+
const doc = document.createElement("div");
51+
doc.className = "fsdocs-tip-doc";
52+
for (const el of xml.documentElement.children) {
53+
switch (el.localName) {
54+
case "summary": doc.append(line("fsdocs-tip-doc-summary", null, el)); break;
55+
case "remarks": doc.append(line("fsdocs-tip-doc-remarks", null, el)); break;
56+
case "param":
57+
case "typeparam": doc.append(line("fsdocs-tip-doc-param", el.getAttribute("name") + ":", el)); break;
58+
case "returns": doc.append(line("fsdocs-tip-doc-param", "Returns:", el)); break;
59+
case "exception": doc.append(line("fsdocs-tip-doc-param", "Throws " + refName(el) + ":", el)); break;
60+
}
61+
}
62+
return doc.childNodes.length > 0 ? doc : null;
63+
}
64+
65+
for (const em of document.querySelectorAll("div.fsdocs-tip > em")) {
66+
if (!docTag.test(em.textContent)) continue;
67+
const doc = render(em.textContent);
68+
if (doc) em.replaceWith(doc);
69+
}

‎docs/content/core/async.fsx‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ open System.Threading.Tasks
5757
open FSharp.Data.Sql
5858

5959
type MyWebServer() =
60-
member __.``Execute Business Logics`` (id : Guid) : Task<_> =
60+
member __.``Execute Business Logics`` (orderId : int64) : Task<_> =
6161
task {
6262
use transaction =
6363
new System.Transactions.TransactionScope(
@@ -67,14 +67,14 @@ type MyWebServer() =
6767
let context = TypeProviderConnection.GetDataContext cstr
6868
let! fetched =
6969
query {
70-
for t2 in context.MyDataBase.MyTable2 do
71-
join t1 in context.MyDataBase.MyTable1 on (t2.ForeignId = t1.Id)
72-
where (t2.Id = id)
73-
select t1
70+
for order in context.Main.Orders do
71+
join customer in context.Main.Customers on (order.CustomerId.Value = customer.CustomerId)
72+
where (order.OrderId = orderId)
73+
select order
7474
} |> Seq.executeQueryAsync
7575

76-
fetched |> Seq.iter (fun entity ->
77-
entity.SetColumn("Updated", DateTime.UtcNow |> box)
76+
fetched |> Seq.iter (fun order ->
77+
order.ShippedDate <- Some DateTime.UtcNow
7878
)
7979
do! context.SubmitUpdatesAsync()
8080

‎docs/content/core/composable.fsx‎

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ type sql = SqlDataProvider<
1919
(*** hide ***)
2020
let ctx = sql.GetDataContext()
2121
(*** hide ***)
22-
let xs = [].AsQueryable()
22+
let xs = ([] : int list).AsQueryable()
2323

2424
(**
2525
# Composable Queries
@@ -82,10 +82,10 @@ In other words, SQLProvider queries typically follow this pattern:
8282
8383
*)
8484

85-
let (qry:IQueryable<'T>) =
85+
let pattern (xs:IQueryable<'T>) : IQueryable<'T> =
8686
query {
8787
//for is like C# foreach
88-
for x in (xs:IQueryable<'T>) do
88+
for x in xs do
8989
select x
9090
}
9191

@@ -125,7 +125,7 @@ let companyNameFilter inUse =
125125
126126
Then, you can create the main query
127127
*)
128-
let query1 =
128+
let mainQuery =
129129
query {
130130
for customers in ctx.Main.Customers do
131131
where (customers.ContactTitle = "USA")
@@ -136,7 +136,7 @@ let query1 =
136136
and now call you are able to call the second query like this
137137
*)
138138

139-
let res = companyNameFilter true query1 |> Seq.toArray
139+
let res = companyNameFilter true mainQuery |> Seq.toArray
140140

141141
(**
142142
### Generate composable queries by using FSharp.Linq.ComposableQuery
@@ -150,18 +150,18 @@ Example for using FSharpComposableQuery
150150
*)
151151

152152
let qry1 =
153-
query { for u in dbContext.Users do
154-
select (u.Id, u.Name, u.Email)
153+
query { for c in ctx.Main.Customers do
154+
select (c.CustomerId, c.CompanyName, c.City)
155155
}
156156

157157
let qry2 =
158-
query { for c in dbContext.Cars do
159-
select (c.UserId, c.Brand, c.Year)
158+
query { for o in ctx.Main.Orders do
159+
select (o.CustomerId, o.OrderId, o.Freight)
160160
}
161161

162162
query { for (i,n,e) in qry1 do
163163
join (u,b,y) in qry2 on (i = u)
164-
where (y > 2015)
164+
where (y > 100m)
165165
select (i,n,e,u,b,y)
166166
} |> Seq.toArray
167167

@@ -180,29 +180,31 @@ open System.Linq
180180

181181
let nestedQueryTest =
182182
let qry1 = query {
183-
for emp in ctx.Hr.Employees do
183+
for emp in ctx.Main.Employees do
184184
where (emp.FirstName.StartsWith("S"))
185185
select emp.FirstName
186186
}
187187
query {
188-
for emp in ctx.Hr.Employees do
188+
for emp in ctx.Main.Employees do
189189
where (qry1.Contains(emp.FirstName))
190190
select (emp.FirstName, emp.LastName)
191191
} |> Seq.toArray
192192

193193
(**
194194
195-
### Using non-strongly-typed __.GetColumn "name"
195+
### Using non-strongly-typed GetColumn "name"
196196
197-
All the entities inherit from SqlEntity which has GetColumn-method.
198-
So you can use non-strongly-typed columns like this:
197+
All the entities are SqlEntity objects, which implement `IColumnHolder` with a GetColumn method.
198+
So you can use non-strongly-typed columns, by the database's column name, like this:
199199
200200
*)
201+
open FSharp.Data.Sql.Common
202+
201203
let qry =
202204
query {
203205
for x in query1 do
204-
where ((x.GetColumn<string> "CustomerId") = "ALFKI")
205-
select (x.GetColumn<string> "CustomerId")
206+
where (((x :> IColumnHolder).GetColumn<string> "CustomerID") = "ALFKI")
207+
select ((x :> IColumnHolder).GetColumn<string> "CustomerID")
206208
} |> Seq.head
207209

208210
(**

‎docs/content/core/crud.fsx‎

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -143,15 +143,15 @@ ctx.SubmitUpdates()
143143
*)
144144

145145
type Employee2 = {
146-
Id:int
146+
Id:int64
147147
FirstName:string
148148
LastName:string
149149
}
150150

151151
let updateEmployee (employee: Employee2) =
152152
let foundEmployeeMaybe = query {
153-
for p in ctx.Public.Employee2 do
154-
where (p.Id = employee.Id)
153+
for p in ctx.Main.Employees do
154+
where (p.EmployeeId = employee.Id)
155155
select (Some p)
156156
exactlyOneOrDefault
157157
}
@@ -164,8 +164,8 @@ let updateEmployee (employee: Employee2) =
164164

165165
let updateEmployee' (employee: Employee2) =
166166
query {
167-
for p in ctx.Public.Employee2 do
168-
where (p.Id = employee.Id)
167+
for p in ctx.Main.Employees do
168+
where (p.EmployeeId = employee.Id)
169169
}
170170
|> Seq.iter( fun e ->
171171
e.FirstName <- employee.FirstName
@@ -174,7 +174,7 @@ let updateEmployee' (employee: Employee2) =
174174
ctx.SubmitUpdates()
175175

176176
let john = {
177-
Id = 1
177+
Id = 1L
178178
FirstName = "John"
179179
LastName = "Doe" }
180180

@@ -188,7 +188,7 @@ output of .ColumnValues:
188188

189189
employees
190190
|> Seq.map (fun x ->
191-
employee.Create(x.ColumnValues)) // create twins
191+
ctx.Main.Employees.Create(x.ColumnValues)) // create twins
192192
|> Seq.toList
193193

194194
/// Retrieve the FSharp.Data.Sql.Common.SqlEntity objects
@@ -284,14 +284,12 @@ You can leverage this feature by setting the `OnConflict` property on a row obje
284284
Sql Server has a similar feature in the form of the MERGE statement. This is not yet supported.
285285
*)
286286

287-
let ctx = sql.GetDataContext()
288-
289-
let emp = ctx.Main.Employees.Create()
290-
emp.Id <- 1
291-
emp.FirstName <- "Jane"
292-
emp.LastName <- "Doe"
287+
let jane = ctx.Main.Employees.Create()
288+
jane.EmployeeId <- 1L
289+
jane.FirstName <- "Jane"
290+
jane.LastName <- "Doe"
293291

294-
emp.OnConflict <- FSharp.Data.Sql.Common.OnConflict.Update
292+
jane.OnConflict <- FSharp.Data.Sql.Common.OnConflict.Update
295293

296294
ctx.SubmitUpdates()
297295

@@ -321,7 +319,7 @@ There are 3 overrides of create.
321319
322320
*)
323321

324-
let emp = ctx.Main.Employees.``Create(FirstName, LastName)``("Don", "Syme")
322+
let don = ctx.Main.Employees.``Create(FirstName, LastName)``("Don", "Syme")
325323

326324
(**
327325

‎docs/content/core/general.fsx‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ let [<Literal>] resolutionPath = __SOURCE_DIRECTORY__ + @"/../../files/sqlite"
66
(*** hide ***)
77
let [<Literal>] connectionString = "Data Source=" + __SOURCE_DIRECTORY__ + @"\..\northwindEF.db;Version=3;Read Only=false;FailIfMissing=True;"
88

9-
(*** hide ***)
109
(**
1110
1211
# SQL Provider Basics

‎docs/content/core/mappers.fsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ If you want to use SQLProvider as code-generator and copy and paste the tables a
102102
103103
*)
104104

105-
sql.dataContext.DesignTimeCommands.TemplateAsRecord.``main.OrdersTemplate``
105+
// The template is a nested type: type its path and a dot in the editor
106+
// sql.dataContext.DesignTimeCommands.TemplateAsRecord.``main.OrdersTemplate``.
106107

107108
// intellisense will generate you code that you can copy and paste as template to create your own type:
108109
// ``type MainOrders = { CustomerId : String voption; EmployeeId : Int64 voption; Freight : Decimal voption; OrderDate : DateTime voption; OrderId : Int64; RequiredDate : DateTime voption; ShipAddress : String voption; ShipCity : String voption; ShipCountry : String voption; ShipName : String voption; ShipPostalCode : String voption; ShipRegion : String voption; ShippedDate : DateTime voption }``

0 commit comments

Comments
 (0)