@@ -107,7 +107,10 @@ impl SqlBenchmarkRunner {
107107 /// Run a synchronous query benchmark.
108108 ///
109109 /// Executes the query function `iterations` times, collecting timing information.
110- /// The function should return `(row_count, result)` where `row_count` is used for validation.
110+ /// The function should return `(row_count, optional_timing, result)` where:
111+ /// - `row_count` is used for validation
112+ /// - `optional_timing` can be `Some(Duration)` if the callback wants to report its own timing
113+ /// (e.g., DuckDB's internal timing), or `None` to use external wall-clock measurement
111114 ///
112115 /// This handles:
113116 /// - Memory tracking (start/end)
@@ -123,7 +126,7 @@ impl SqlBenchmarkRunner {
123126 mut f : F ,
124127 ) -> T
125128 where
126- F : FnMut ( ) -> ( usize , T ) ,
129+ F : FnMut ( ) -> ( usize , Option < Duration > , T ) ,
127130 {
128131 self . start_query ( ) ;
129132
@@ -132,8 +135,9 @@ impl SqlBenchmarkRunner {
132135
133136 for _ in 0 ..iterations {
134137 let start = Instant :: now ( ) ;
135- let ( row_count, iter_result) = f ( ) ;
136- runs. push ( start. elapsed ( ) ) ;
138+ let ( row_count, timing, iter_result) = f ( ) ;
139+ let elapsed = timing. unwrap_or_else ( || start. elapsed ( ) ) ;
140+ runs. push ( elapsed) ;
137141
138142 if result. is_none ( ) {
139143 result = Some ( ( row_count, iter_result) ) ;
@@ -149,7 +153,10 @@ impl SqlBenchmarkRunner {
149153 /// Run an asynchronous query benchmark.
150154 ///
151155 /// Executes the async query function `iterations` times, collecting timing information.
152- /// The function should return `(row_count, result)` where `row_count` is used for validation.
156+ /// The function should return `(row_count, optional_timing, result)` where:
157+ /// - `row_count` is used for validation
158+ /// - `optional_timing` can be `Some(Duration)` if the callback wants to report its own timing
159+ /// (e.g., DuckDB's internal timing), or `None` to use external wall-clock measurement
153160 ///
154161 /// This handles:
155162 /// - Memory tracking (start/end)
@@ -166,7 +173,7 @@ impl SqlBenchmarkRunner {
166173 ) -> T
167174 where
168175 F : FnMut ( ) -> Fut ,
169- Fut : Future < Output = ( usize , T ) > ,
176+ Fut : Future < Output = ( usize , Option < Duration > , T ) > ,
170177 {
171178 self . start_query ( ) ;
172179
@@ -175,8 +182,9 @@ impl SqlBenchmarkRunner {
175182
176183 for _ in 0 ..iterations {
177184 let start = Instant :: now ( ) ;
178- let ( row_count, iter_result) = f ( ) . await ;
179- runs. push ( start. elapsed ( ) ) ;
185+ let ( row_count, timing, iter_result) = f ( ) . await ;
186+ let elapsed = timing. unwrap_or_else ( || start. elapsed ( ) ) ;
187+ runs. push ( elapsed) ;
180188
181189 if result. is_none ( ) {
182190 result = Some ( ( row_count, iter_result) ) ;
@@ -306,7 +314,8 @@ impl SqlBenchmarkRunner {
306314 /// 2. Iterates over all queries, calling `execute` for each
307315 ///
308316 /// The `execute` callback receives the context, query index, and query string,
309- /// and should return the row count.
317+ /// and should return `(row_count, optional_timing)` where `optional_timing` can be
318+ /// `Some(Duration)` if the callback wants to report its own timing.
310319 pub fn run_all < Ctx , S , E > (
311320 & mut self ,
312321 queries : & [ ( usize , String ) ] ,
@@ -316,19 +325,19 @@ impl SqlBenchmarkRunner {
316325 ) -> anyhow:: Result < ( ) >
317326 where
318327 S : FnMut ( Format ) -> anyhow:: Result < Ctx > ,
319- E : FnMut ( & mut Ctx , usize , & str ) -> anyhow:: Result < usize > ,
328+ E : FnMut ( & mut Ctx , usize , & str ) -> anyhow:: Result < ( usize , Option < Duration > ) > ,
320329 {
321330 for format in self . formats . clone ( ) {
322331 let mut ctx = setup ( format) ?;
323332
324333 for ( query_idx, query) in queries. iter ( ) {
325334 let query_idx = * query_idx;
326335 self . run_query ( query_idx, format, iterations, || {
327- let row_count =
336+ let ( row_count, timing ) =
328337 execute ( & mut ctx, query_idx, query. as_str ( ) ) . unwrap_or_else ( |err| {
329338 vortex_panic ! ( "query {query_idx} failed: {err}" ) ;
330339 } ) ;
331- ( row_count, ( ) )
340+ ( row_count, timing , ( ) )
332341 } ) ;
333342 }
334343 }
@@ -343,7 +352,9 @@ impl SqlBenchmarkRunner {
343352 /// 2. Iterates over all queries, calling `execute` for each
344353 ///
345354 /// The `execute` callback receives the context, query index, and query string,
346- /// and should return `(row_count, result)`. Use `Box::pin(async move { ... })` in the closure.
355+ /// and should return `(row_count, optional_timing, result)` where `optional_timing` can be
356+ /// `Some(Duration)` if the callback wants to report its own timing.
357+ /// Use `Box::pin(async move { ... })` in the closure.
347358 pub async fn run_all_async < Ctx , S , SFut , E , T > (
348359 & mut self ,
349360 queries : & [ ( usize , String ) ] ,
@@ -358,7 +369,7 @@ impl SqlBenchmarkRunner {
358369 & ' c Ctx ,
359370 usize ,
360371 & ' c str ,
361- ) -> Pin < Box < dyn Future < Output = anyhow:: Result < ( usize , T ) > > + ' c > > ,
372+ ) -> Pin < Box < dyn Future < Output = anyhow:: Result < ( usize , Option < Duration > , T ) > > + ' c > > ,
362373 {
363374 for format in self . formats . clone ( ) {
364375 let ctx = setup ( format) . await ?;
@@ -373,12 +384,13 @@ impl SqlBenchmarkRunner {
373384
374385 for _ in 0 ..iterations {
375386 let start = Instant :: now ( ) ;
376- let ( row_count, iter_result) = execute ( & ctx, query_idx, query. as_str ( ) )
387+ let ( row_count, timing , iter_result) = execute ( & ctx, query_idx, query. as_str ( ) )
377388 . await
378389 . unwrap_or_else ( |err| {
379390 vortex_panic ! ( "query {query_idx} failed: {err}" ) ;
380391 } ) ;
381- runs. push ( start. elapsed ( ) ) ;
392+ let elapsed = timing. unwrap_or_else ( || start. elapsed ( ) ) ;
393+ runs. push ( elapsed) ;
382394
383395 if result. is_none ( ) {
384396 result = Some ( ( row_count, iter_result) ) ;
0 commit comments