Skip to main content
Enterprise Support Available
Spice.ai Enterprise provides full support for up to 3 years from the release date.
Version: v2.0

Parameterized Queries

Parameterized queries separate SQL logic from data values, providing protection against SQL injection attacks and improving query performance through prepared statement caching.

Overview​

Instead of embedding values directly in SQL strings, parameterized queries use placeholders (e.g., $1, $2) that are bound to values at execution time. This approach:

  • Prevents SQL injection: Values are never interpreted as SQL code
  • Improves performance: Query plans can be cached and reused
  • Enhances code clarity: Query structure is separate from data

Placeholder Syntax​

Spice uses positional placeholders following the PostgreSQL convention:

SELECT * FROM users WHERE id = $1 AND status = $2;

Parameters are bound in order: $1 receives the first parameter, $2 the second, and so on.

Using Parameterized Queries​

ADBC provides native support for parameterized queries through the FlightSQL driver:

from adbc_driver_flightsql.dbapi import connect

with connect("grpc://127.0.0.1:50051") as conn:
with conn.cursor() as cur:
# Single parameter
cur.execute("SELECT * FROM users WHERE id = $1", parameters=(42,))
table = cur.fetch_arrow_table()
print(table)

# Multiple parameters
cur.execute(
"SELECT * FROM orders WHERE customer_id = $1 AND total > $2",
parameters=(100, 50.0)
)
table = cur.fetch_arrow_table()
print(table)

Supported Parameter Types​

Spice supports a wide range of parameter types with automatic type inference:

Type CategoryExamplesArrow Type
Signed integersint, int8, int16, int32, int64Int8/16/32/64
Unsigned integersuint, uint8, uint16, uint32Uint8/16/32/64
Floating pointfloat, doubleFloat32/Float64
TextstringUtf8
Booleanbool, booleanBoolean
Binarybyte[], []byteBinary
Date/TimeLocalDate, LocalDateTime, time.TimeDate32/64, Timestamp
DecimalBigDecimal, decimalDecimal128/256
Nullnull, nilNull

Explicit Type Control​

For precise control over Arrow types, SDKs provide typed parameter constructors:

import "github.com/spiceai/gospice/v8"

reader, err := spice.SqlWithParams(
ctx,
"SELECT * FROM financial WHERE amount >= $1 AND timestamp > $2",
gospice.Decimal128Param(amountBytes, 19, 4), // Decimal with precision & scale
gospice.TimestampParam(ts, arrow.Microsecond, "UTC"), // Timestamp with unit & timezone
)

Available constructors: Int8Param, Int16Param, Int32Param, Int64Param, Uint8Param, Uint16Param, Uint32Param, Uint64Param, Float16Param, Float32Param, Float64Param, StringParam, LargeStringParam, BinaryParam, LargeBinaryParam, FixedSizeBinaryParam, BoolParam, Date32Param, Date64Param, Time32Param, Time64Param, TimestampParam, DurationParam, MonthIntervalParam, DayTimeIntervalParam, MonthDayNanoIntervalParam, Decimal128Param, Decimal256Param, NullParam

Security Benefits​

Parameterized queries protect against SQL injection by ensuring user input is never interpreted as SQL code.

// ❌ DANGEROUS: User input directly in SQL string
userId := getUserInput() // Could be: "1 OR 1=1"
sql := fmt.Sprintf("SELECT * FROM users WHERE id = %s", userId)
reader, err := spice.Sql(ctx, sql)

// ✅ SAFE: User input passed as parameter
userId := getUserInput()
reader, err := spice.SqlWithParams(ctx, "SELECT * FROM users WHERE id = $1", userId)

With parameterized queries, even if userId contains malicious SQL like 1 OR 1=1, it is treated as a literal string value, not as SQL code.

Performance Considerations​

Parameterized queries can improve performance in several ways:

  1. Query Plan Caching: The database can cache and reuse execution plans for parameterized queries
  2. Reduced Parsing: Parameter binding avoids repeated SQL parsing
  3. Batch Operations: Multiple executions with different parameters use the same prepared statement

SDK Support​

SDKVersionMethodStatus
gospice (Go)v8.0.0+SqlWithParams() with typed constructors✅ Full
spice-rs (Rust)v3.0.0+query_with_params() with RecordBatch✅ Full
spice-dotnet (Dotnet)v1.1.0+Query() with Dictionary<string, object>✅ Full
spice-java (Java)v0.5.0+queryWithParams() with Param class✅ Full
spice.js (JavaScript)v3.1.0+sql() with positional or named parameters✅ Full
spicepy (Python)v3.1.0+query_with_params() with Param class✅ Full
ADBC (Python)-cursor.execute() with parameters✅ Full
JDBC-PreparedStatement✅ Full
ODBC-Parameterized queries✅ Full

Examples​

Filtering with Multiple Conditions​

# Python with ADBC
cur.execute("""
SELECT order_id, customer_name, total
FROM orders
WHERE status = $1
AND order_date BETWEEN $2 AND $3
AND total > $4
ORDER BY order_date DESC
LIMIT $5
""", parameters=("completed", "2024-01-01", "2024-12-31", 100.0, 50))

Batch Processing Pattern​

// Go: Process multiple customers with same query
customerIDs := []int{100, 200, 300, 400, 500}

for _, id := range customerIDs {
reader, err := spice.SqlWithParams(
ctx,
"SELECT * FROM orders WHERE customer_id = $1",
id,
)
if err != nil {
log.Printf("Error querying customer %d: %v", id, err)
continue
}
processOrders(reader)
reader.Release()
}

Using with Aggregations​

# Calculate statistics for a specific time range
cur.execute("""
SELECT
DATE_TRUNC('day', order_date) as day,
COUNT(*) as order_count,
SUM(total) as daily_total
FROM orders
WHERE order_date >= $1 AND order_date < $2
GROUP BY DATE_TRUNC('day', order_date)
ORDER BY day
""", parameters=("2024-01-01", "2024-02-01"))

Troubleshooting​

Parameter Count Mismatch​

Ensure the number of parameters matches the number of placeholders:

# Error: 3 placeholders but only 2 parameters
cur.execute("SELECT * FROM t WHERE a = $1 AND b = $2 AND c = $3", parameters=(1, 2))

# Correct: 3 placeholders and 3 parameters
cur.execute("SELECT * FROM t WHERE a = $1 AND b = $2 AND c = $3", parameters=(1, 2, 3))

Type Mismatch​

If you encounter type errors, use explicit type constructors (Go) or ensure Python types match expected column types:

// If int is inferred as Int64 but column expects Int32
reader, err := spice.SqlWithParams(ctx, "SELECT $1", gospice.Int32Param(42))

Connection Issues​

For ADBC connections, verify the Spice runtime is running and accessible:

# Test connection
try:
conn = connect("grpc://localhost:50051")
cursor = conn.cursor()
cursor.execute("SELECT 1")
print("Connection successful")
except Exception as e:
print(f"Connection failed: {e}")