@@ -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>]
5388module 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