Skip to content

Commit 45329a5

Browse files
authored
Minor code improvements and add Updates test (#10263)
1 parent d2c8757 commit 45329a5

File tree

4 files changed

+130
-23
lines changed

4 files changed

+130
-23
lines changed

frameworks/FSharp/giraffe/benchmark_config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"json_url": "/json",
88
"db_url": "/db",
99
"query_url": "/queries?queries=",
10+
"update_url": "/updates?queries=",
1011
"fortune_url": "/fortunes",
1112
"port": 8080,
1213
"approach": "Realistic",

frameworks/FSharp/giraffe/config.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
name = "giraffe"
33

44
[main]
5-
urls.plaintext = "/plaintext"
65
urls.json = "/json"
76
urls.db = "/db"
87
urls.query = "/queries?queries="
98
urls.fortune = "/fortunes"
9+
urls.update = "/updates?queries="
10+
urls.plaintext = "/plaintext"
1011
approach = "Realistic"
1112
classification = "fullstack"
1213
database = "Postgres"

frameworks/FSharp/giraffe/src/App/App.fsproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77

88
<ItemGroup>
99
<PackageReference Update="FSharp.Core" Version="9.0.303" />
10-
<PackageReference Include="Dapper" Version="2.1.66" />
1110
<PackageReference Include="Giraffe" Version="8.1.0-alpha-001" />
1211
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
1312
<PackageReference Include="Npgsql" Version="9.0.4" />
13+
<PackageReference Include="Dapper" Version="2.1.66" />
1414
</ItemGroup>
1515

1616
<ItemGroup>

frameworks/FSharp/giraffe/src/App/Program.fs

Lines changed: 126 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ module Common =
1818
Database=hello_world;
1919
User Id=benchmarkdbuser;
2020
Password=benchmarkdbpass;
21-
SSL Mode=Disable;
2221
Maximum Pool Size=1024;
2322
NoResetOnClose=true;
2423
Enlist=false;
25-
Max Auto Prepare=4;
26-
Multiplexing=true;
27-
Write Coalescing Buffer Threshold Bytes=1000
24+
Max Auto Prepare=4
2825
"""
2926

27+
[<Literal>]
28+
let MultiplexedConnectionString = ConnectionString + ";Multiplexing=true"
29+
3030
[<Struct>]
3131
type JsonMode =
3232
| System
@@ -46,8 +46,43 @@ module Common =
4646

4747
member _.Next () = rnd.Next (min, max)
4848

49-
[<Literal>]
50-
let MaxDegreeOfParallelism = 3
49+
// Cache SQL strings for bulk updates to avoid rebuilding on every request
50+
// This module generates and caches SQL UPDATE statements that use PostgreSQL's
51+
// VALUES clause to update multiple rows in a single statement.
52+
module BatchUpdateSql =
53+
let private cache = Array.zeroCreate<string> 501
54+
55+
let get (count: int) =
56+
match cache.[count] with
57+
| null ->
58+
let lastIndex = count - 1
59+
60+
// Build the VALUES clause: (@Id_0, @Rn_0), (@Id_1, @Rn_1), ...
61+
// Each pair represents (id, new_randomnumber) for one row
62+
let valueClauses =
63+
List.init lastIndex (fun i -> sprintf "(@Id_%d, @Rn_%d), " i i)
64+
|> String.concat ""
65+
66+
// The final SQL uses a CTE-like VALUES construct to update multiple rows:
67+
// UPDATE world SET randomnumber = temp.randomnumber
68+
// FROM (VALUES ...) AS temp(id, randomnumber)
69+
// WHERE temp.id = world.id
70+
let sql =
71+
sprintf
72+
"""
73+
UPDATE world
74+
SET randomnumber = temp.randomnumber
75+
FROM (VALUES %s(@Id_%d, @Rn_%d) ORDER BY 1)
76+
AS temp(id, randomnumber)
77+
WHERE temp.id = world.id
78+
"""
79+
valueClauses
80+
lastIndex
81+
lastIndex
82+
83+
cache.[count] <- sql
84+
sql
85+
| sql -> sql
5186

5287
[<RequireQualifiedAccess>]
5388
module HtmlViews =
@@ -87,10 +122,12 @@ module HttpHandlers =
87122
message = "Additional fortune added at request time."
88123
}
89124

90-
let private fortunes: HttpHandler =
125+
let fortunes: HttpHandler =
91126
fun _ ctx ->
92127
task {
93-
use conn = new NpgsqlConnection (ConnectionString)
128+
let dataSource = ctx.GetService<NpgsqlDataSource> ()
129+
use conn = dataSource.CreateConnection ()
130+
do! conn.OpenAsync ()
94131

95132
let! data = conn.QueryAsync<Fortune> ("SELECT id, message FROM fortune")
96133

@@ -106,11 +143,13 @@ module HttpHandlers =
106143
return! ctx.WriteBytesAsync bytes
107144
}
108145

109-
let private db: HttpHandler =
146+
let db: HttpHandler =
110147
fun _ ctx ->
111148
task {
112149
let rnd = ctx.GetService<RandomUtil> ()
113-
use conn = new NpgsqlConnection (ConnectionString)
150+
let dataSource = ctx.GetService<NpgsqlDataSource> ()
151+
use conn = dataSource.CreateConnection ()
152+
do! conn.OpenAsync ()
114153

115154
let! data =
116155
conn.QuerySingleAsync<World> (
@@ -121,7 +160,7 @@ module HttpHandlers =
121160
return! ctx.WriteJsonAsync data
122161
}
123162

124-
let private queries: HttpHandler =
163+
let queries: HttpHandler =
125164
fun _ ctx ->
126165
task {
127166
let queryParam =
@@ -137,30 +176,94 @@ module HttpHandlers =
137176
|> Option.defaultValue 1
138177

139178
let rnd = ctx.GetService<RandomUtil> ()
179+
let dataSource = ctx.GetService<NpgsqlDataSource> ()
180+
181+
use conn = dataSource.CreateConnection ()
182+
do! conn.OpenAsync ()
140183

141-
let! res =
142-
Array.init queryParam (fun _ -> rnd.Next ())
143-
|> Array.map (fun id ->
144-
use conn = new NpgsqlConnection (ConnectionString)
184+
// Read all rows sequentially
185+
let results = Array.zeroCreate<World> queryParam
145186

187+
for i in 0 .. queryParam - 1 do
188+
let! world =
146189
conn.QuerySingleAsync<World> (
147190
"SELECT id, randomnumber FROM world WHERE id = @Id",
148-
{| Id = id |}
191+
{| Id = rnd.Next () |}
149192
)
150-
|> Async.AwaitTask
193+
194+
results.[i] <- world
195+
196+
return! ctx.WriteJsonAsync results
197+
}
198+
199+
let updates: HttpHandler =
200+
fun _ ctx ->
201+
task {
202+
let queryParam =
203+
ctx.TryGetQueryStringValue "queries"
204+
|> Option.map (fun value ->
205+
match System.Int32.TryParse value with
206+
| true, intValue ->
207+
if intValue < 1 then 1
208+
elif intValue > 500 then 500
209+
else intValue
210+
| false, _ -> 1
151211
)
152-
|> fun computations ->
153-
Async.Parallel (computations, MaxDegreeOfParallelism)
212+
|> Option.defaultValue 1
213+
214+
let rnd = ctx.GetService<RandomUtil> ()
215+
216+
// Use multiplexed connection for updates (more efficient for sequential ops)
217+
use conn = new NpgsqlConnection (MultiplexedConnectionString)
218+
do! conn.OpenAsync ()
219+
220+
// Read all rows sequentially
221+
let readResults = Array.zeroCreate<World> queryParam
154222

155-
return! ctx.WriteJsonAsync res
223+
for i in 0 .. queryParam - 1 do
224+
let! world =
225+
conn.QuerySingleAsync<World> (
226+
"SELECT id, randomnumber FROM world WHERE id = @Id",
227+
{| Id = rnd.Next () |}
228+
)
229+
230+
readResults.[i] <- world
231+
232+
// Update random numbers functionally
233+
let updatedData =
234+
readResults
235+
|> Array.map (fun data -> { data with randomNumber = rnd.Next () })
236+
237+
// Build bulk update parameters functionally
238+
// We use a single UPDATE statement with a VALUES clause to update all rows at once.
239+
//
240+
// Example SQL for 2 rows:
241+
// UPDATE world SET randomnumber = temp.randomnumber
242+
// FROM (VALUES (@Id_0, @Rn_0), (@Id_1, @Rn_1) ORDER BY 1) AS temp(id, randomnumber)
243+
// WHERE temp.id = world.id
244+
let updateParams =
245+
updatedData
246+
|> Array.mapi (fun i data -> [
247+
sprintf "@Id_%d" i, box data.id // Parameter for the id
248+
sprintf "@Rn_%d" i, box data.randomNumber // Parameter for the new random number
249+
])
250+
|> Array.collect List.toArray // Flatten the list of parameter pairs
251+
|> dict // Convert to dictionary for Dapper
252+
253+
// Execute bulk update using Dapper
254+
let sql = BatchUpdateSql.get queryParam
255+
let! _ = conn.ExecuteAsync (sql, updateParams)
256+
257+
return! ctx.WriteJsonAsync updatedData
156258
}
157259

158260
let endpoints: Endpoint list = [
159-
route "/plaintext" (text "Hello, World!")
160261
route "/json" (json {| message = "Hello, World!" |})
161262
route "/db" db
162263
route "/queries" queries
163264
route "/fortunes" fortunes
265+
route "/updates" updates
266+
route "/plaintext" (text "Hello, World!")
164267
]
165268

166269

@@ -173,6 +276,7 @@ module Main =
173276
open Microsoft.Extensions.Logging
174277
open System.Text.Json
175278
open Newtonsoft.Json
279+
open Npgsql
176280

177281
[<EntryPoint>]
178282
let main args =
@@ -202,6 +306,7 @@ module Main =
202306
builder.Services
203307
.AddSingleton(jsonSerializer)
204308
.AddSingleton<RandomUtil>(rnd)
309+
.AddSingleton<NpgsqlDataSource>(NpgsqlDataSource.Create (ConnectionString))
205310
.AddGiraffe ()
206311
|> ignore
207312

0 commit comments

Comments
 (0)