Skip to content

Reuse prepared statements in DuckDBCommand.Prepare()#340

Open
skuirrels wants to merge 1 commit into
Giorgi:developfrom
skuirrels:feature/prepared-command-reuse
Open

Reuse prepared statements in DuckDBCommand.Prepare()#340
skuirrels wants to merge 1 commit into
Giorgi:developfrom
skuirrels:feature/prepared-command-reuse

Conversation

@skuirrels

Copy link
Copy Markdown
Contributor

DuckDBCommand.Prepare() is currently a no-op, so every execution still extracts, prepares and destroys the statement.

This change keeps a native prepared statement alive and reuses it. Bindings are cleared and applied again on each execution, while parameter metadata and named-parameter indexes are cached with the statement.

I only reuse commands that DuckDB extracts as a single native statement. Multi-statement commands stay on the existing path because some statements are dependent. Dynamic PIVOT, for example, builds its schema during execution, so caching its expansion can return stale columns. Prepare() also remains side-effect free and does not execute any part of the command.

Prepared statements are invalidated when the command text or connection changes, or when the connection closes. If a reader is still using the statement, disposal is deferred until the reader has finished.

Benchmarks

Same command and parameters, measured in the same BenchmarkDotNet run:

Existing execution path Reused prepared statement
Mean 74.56 μs 27.97 μs
Allocated 1.27 KB 1.04 KB

That makes repeated execution around 2.7x faster, with 18% less allocation.

There is a one-off cost when Prepare() is called:

Create command Create and prepare
Mean 92.41 ns 34.73 μs
Allocated 472 B 1,184 B

Benchmarks used .NET 10 on macOS Arm64 with BenchmarkDotNet ShortRun.

Testing

Added coverage for repeated parameter binding, cleared bindings, static and dynamic PIVOT, dependent multi-statement commands, active readers, command invalidation and connection close/reopen.

Full test suite: 6,905 passed.

Release build completed successfully.

@codecov

codecov Bot commented Jul 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 79.18552% with 46 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.02%. Comparing base (fe2abc5) to head (be9cf16).

Files with missing lines Patch % Lines
DuckDB.NET.Data/DuckDBCommand.cs 80.80% 14 Missing and 10 partials ⚠️
...DB.NET.Data/PreparedStatement/PreparedStatement.cs 76.81% 11 Missing and 5 partials ⚠️
DuckDB.NET.Data/DuckDBConnection.cs 77.77% 5 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop     #340      +/-   ##
===========================================
- Coverage    87.59%   87.02%   -0.57%     
===========================================
  Files           77       77              
  Lines         3143     3344     +201     
  Branches       466      526      +60     
===========================================
+ Hits          2753     2910     +157     
- Misses         276      305      +29     
- Partials       114      129      +15     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR makes DuckDBCommand.Prepare() actually prepare and cache a native DuckDB prepared statement for reuse across executions (when DuckDB extracts the command as a single statement), improving repeated-execution performance while keeping multi-statement and dynamic-expansion cases on the existing per-execution prepare path.

Changes:

  • Implement reusable native prepared statement caching in DuckDBCommand, including invalidation on command text/connection changes and connection close.
  • Add prepared-statement reuse support in PreparedStatement (including cached parameter metadata and cleared bindings on reuse).
  • Add extensive test coverage for statement reuse behaviors and add a benchmarks project to measure performance.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
DuckDB.NET.Test/Parameters/ParameterCollectionTests.cs Updates Prepare() expectations to throw on invalid statements.
DuckDB.NET.Test/DuckDBCommandTests.cs Adds tests covering reuse semantics, invalidation, pivots, and active-reader scenarios.
DuckDB.NET.slnx Includes the new benchmarks project in the solution.
DuckDB.NET.Data/PreparedStatement/PreparedStatement.cs Adds reusable prepared statement support, cached parameter metadata, and binding clearing.
DuckDB.NET.Data/DuckDBConnection.cs Tracks prepared commands and invalidates them on connection close.
DuckDB.NET.Data/DuckDBCommand.cs Implements Prepare(), statement reuse, invalidation, and deferred disposal plumbing.
DuckDB.NET.Bindings/NativeMethods/NativeMethods.PreparedStatements.cs Adds P/Invoke for duckdb_clear_bindings.
DuckDB.NET.Benchmarks/Program.cs Adds BenchmarkDotNet runner configuration for the new benchmarks.
DuckDB.NET.Benchmarks/PreparedCommandBenchmark.cs Adds benchmarks comparing prepared vs unprepared execution and setup cost.
DuckDB.NET.Benchmarks/NativeLibraryLoader.cs Adds benchmark-process native library loading.
DuckDB.NET.Benchmarks/Benchmarks.csproj Adds benchmarks project configuration and references.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread DuckDB.NET.Data/DuckDBCommand.cs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants