Prepared statements allow you to execute parameterized SQL queries efficiently and safely. They prevent SQL injection and improve performance when executing the same query multiple times with different parameters.
Creating Prepared Statements
Prepare a SQL statement with parameter placeholders:
duckdb_state duckdb_prepare (
duckdb_connection connection ,
const char * query ,
duckdb_prepared_statement * out_prepared_statement
);
connection
duckdb_connection
required
Active database connection.
SQL query with $1, $2, … as parameter placeholders.
out_prepared_statement
duckdb_prepared_statement*
required
Output parameter for the prepared statement handle.
Example :
duckdb_prepared_statement stmt;
if ( duckdb_prepare (con, "SELECT * FROM users WHERE age > $1 AND city = $2" , & stmt ) == DuckDBError) {
fprintf (stderr, "Failed to prepare statement \n " );
return 1 ;
}
Binding Parameters
Bind values to parameter placeholders before execution:
Binding Functions
// Bind boolean
duckdb_state duckdb_bind_boolean (duckdb_prepared_statement prepared_statement , idx_t param_idx , bool val );
// Bind integers
duckdb_state duckdb_bind_int8 (duckdb_prepared_statement prepared_statement , idx_t param_idx , int8_t val );
duckdb_state duckdb_bind_int16 (duckdb_prepared_statement prepared_statement , idx_t param_idx , int16_t val );
duckdb_state duckdb_bind_int32 (duckdb_prepared_statement prepared_statement , idx_t param_idx , int32_t val );
duckdb_state duckdb_bind_int64 (duckdb_prepared_statement prepared_statement , idx_t param_idx , int64_t val );
// Bind floating point
duckdb_state duckdb_bind_float (duckdb_prepared_statement prepared_statement , idx_t param_idx , float val );
duckdb_state duckdb_bind_double (duckdb_prepared_statement prepared_statement , idx_t param_idx , double val );
// Bind strings
duckdb_state duckdb_bind_varchar (duckdb_prepared_statement prepared_statement , idx_t param_idx , const char * val );
duckdb_state duckdb_bind_varchar_length (duckdb_prepared_statement prepared_statement , idx_t param_idx , const char * val , idx_t length );
// Bind NULL
duckdb_state duckdb_bind_null (duckdb_prepared_statement prepared_statement , idx_t param_idx );
// Bind date/time types
duckdb_state duckdb_bind_date (duckdb_prepared_statement prepared_statement , idx_t param_idx , duckdb_date val );
duckdb_state duckdb_bind_time (duckdb_prepared_statement prepared_statement , idx_t param_idx , duckdb_time val );
duckdb_state duckdb_bind_timestamp (duckdb_prepared_statement prepared_statement , idx_t param_idx , duckdb_timestamp val );
Parameter indices are 1-based (first parameter is $1, which has index 1).
Binding Example
duckdb_prepared_statement stmt;
duckdb_prepare (con, "INSERT INTO users(name, age, salary) VALUES ($1, $2, $3)" , & stmt );
// Bind parameters
duckdb_bind_varchar (stmt, 1 , "Alice" );
duckdb_bind_int32 (stmt, 2 , 30 );
duckdb_bind_double (stmt, 3 , 75000.50 );
Executing Prepared Statements
Execute a prepared statement after binding parameters:
duckdb_state duckdb_execute_prepared (
duckdb_prepared_statement prepared_statement ,
duckdb_result * out_result
);
prepared_statement
duckdb_prepared_statement
required
The prepared statement to execute.
Output parameter for results. Pass NULL if no results are needed.
Example :
duckdb_result result;
if ( duckdb_execute_prepared (stmt, & result ) == DuckDBSuccess) {
// Process result...
duckdb_destroy_result ( & result);
}
Complete Example
Insert multiple rows using a prepared statement:
#include "duckdb.h"
#include <stdio.h>
int main () {
duckdb_database db;
duckdb_connection con;
duckdb_prepared_statement stmt;
// Setup
duckdb_open ( NULL , & db);
duckdb_connect (db, & con);
// Create table
duckdb_query (con, "CREATE TABLE products(id INTEGER, name VARCHAR, price DOUBLE)" , NULL );
// Prepare INSERT statement
if ( duckdb_prepare (con, "INSERT INTO products VALUES ($1, $2, $3)" , & stmt) == DuckDBError) {
fprintf (stderr, "Failed to prepare statement \n " );
goto cleanup;
}
// Insert multiple rows
struct {
int id;
const char * name;
double price;
} products [] = {
{ 1 , "Laptop" , 999.99 },
{ 2 , "Mouse" , 29.99 },
{ 3 , "Keyboard" , 79.99 }
};
for ( int i = 0 ; i < 3 ; i ++ ) {
// Bind parameters
duckdb_bind_int32 (stmt, 1 , products [i]. id );
duckdb_bind_varchar (stmt, 2 , products [i]. name );
duckdb_bind_double (stmt, 3 , products [i]. price );
// Execute
if ( duckdb_execute_prepared (stmt, NULL ) == DuckDBError) {
fprintf (stderr, "Failed to insert row %d \n " , i);
}
// Clear bindings for next iteration
duckdb_clear_bindings (stmt);
}
// Query results
duckdb_result result;
if ( duckdb_query (con, "SELECT * FROM products" , & result) == DuckDBSuccess) {
idx_t row_count = duckdb_row_count ( & result);
printf ( "Inserted %llu rows \n " , row_count);
duckdb_destroy_result ( & result);
}
duckdb_destroy_prepare ( & stmt);
cleanup:
duckdb_disconnect ( & con);
duckdb_close ( & db);
return 0 ;
}
Query with Parameters
Select data using prepared statements:
duckdb_prepared_statement stmt;
duckdb_result result;
// Prepare SELECT with parameters
if ( duckdb_prepare (con, "SELECT name, price FROM products WHERE price > $1 AND name LIKE $2" , & stmt ) == DuckDBSuccess) {
// Bind parameters
duckdb_bind_double (stmt, 1 , 50.0 );
duckdb_bind_varchar (stmt, 2 , " %to p % " );
// Execute
if ( duckdb_execute_prepared (stmt, & result) == DuckDBSuccess) {
idx_t row_count = duckdb_row_count ( & result);
for ( idx_t i = 0 ; i < row_count; i ++ ) {
char * name = duckdb_value_varchar ( & result, 0 , i);
double price = duckdb_value_double ( & result, 1 , i);
printf ( " %s : $ %.2f \n " , name, price);
duckdb_free (name);
}
duckdb_destroy_result ( & result);
}
duckdb_destroy_prepare ( & stmt);
}
Prepared Statement Management
Get Parameter Count
idx_t duckdb_nparams (duckdb_prepared_statement prepared_statement );
Returns the number of parameters in the prepared statement.
Get Parameter Type
duckdb_type duckdb_param_type (duckdb_prepared_statement prepared_statement , idx_t param_idx );
Returns the expected type for a parameter.
Clear Bindings
duckdb_state duckdb_clear_bindings (duckdb_prepared_statement prepared_statement );
Reset all bound parameters. Useful when re-executing with different values.
Get Error Message
const char * duckdb_prepare_error (duckdb_prepared_statement prepared_statement );
Returns error message if preparation failed.
Destroy Prepared Statement
void duckdb_destroy_prepare (duckdb_prepared_statement * prepared_statement );
Always call duckdb_destroy_prepare() to free resources when done with a prepared statement.
Benefits of Prepared Statements
SQL Injection Prevention Parameters are properly escaped, preventing SQL injection attacks.
Performance Query is parsed and optimized once, then executed multiple times.
Type Safety Parameter types are validated at bind time.
Cleaner Code No need for manual string concatenation or escaping.
Comparison: Direct Query vs Prepared
Direct Query (Don't do this!)
Prepared Statement (Safe)
// Unsafe: vulnerable to SQL injection
char query [ 256 ];
sprintf (query, "SELECT * FROM users WHERE name = ' %s '" , user_input);
duckdb_query (con, query, & result );
Next Steps
Appender Bulk load data even faster
Data Types Learn about all supported data types