Skip to content

Commit 49cf181

Browse files
authored
fix: named parameter handling, thread local pooling & typing config (#52)
fix: bind parametr handling fix: typing updates for litestar feat: `sqlite` Thread local pool cleanup feat: corrected `aiosqlite` pooler feat: corrected thread safe `duckdb` pooler
1 parent 9f6a8ce commit 49cf181

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+4288
-826
lines changed

docs/examples/adbc_example.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
"""Example demonstrating ADBC driver usage with query mixins.
2+
3+
This example shows how to use the ADBC (Arrow Database Connectivity) driver
4+
with the development PostgreSQL container started by `make infra-up`.
5+
"""
6+
7+
from sqlspec import SQLSpec
8+
from sqlspec.adapters.adbc import AdbcConfig
9+
from sqlspec.builder import Select
10+
11+
__all__ = ("adbc_example", "main")
12+
13+
14+
def adbc_example() -> None:
15+
"""Demonstrate ADBC database driver usage with query mixins."""
16+
# Create SQLSpec instance with ADBC (connects to dev PostgreSQL container)
17+
spec = SQLSpec()
18+
config = AdbcConfig(connection_config={"uri": "postgresql://postgres:postgres@localhost:5433/postgres"})
19+
spec.add_config(config)
20+
21+
# Get a driver directly (drivers now have built-in query methods)
22+
with spec.provide_session(config) as driver:
23+
# Create a table
24+
driver.execute("""
25+
CREATE TABLE IF NOT EXISTS analytics_data (
26+
id SERIAL PRIMARY KEY,
27+
metric_name TEXT NOT NULL,
28+
metric_value DOUBLE PRECISION NOT NULL,
29+
dimensions JSONB,
30+
recorded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
31+
)
32+
""")
33+
34+
# Clean up any existing data
35+
driver.execute("TRUNCATE TABLE analytics_data RESTART IDENTITY")
36+
37+
# Insert data
38+
driver.execute(
39+
"INSERT INTO analytics_data (metric_name, metric_value, dimensions) VALUES ($1, $2, $3::jsonb)",
40+
"page_views",
41+
1250.0,
42+
'{"source": "organic", "device": "desktop"}',
43+
)
44+
45+
# Insert multiple rows
46+
driver.execute_many(
47+
"INSERT INTO analytics_data (metric_name, metric_value, dimensions) VALUES ($1, $2, $3::jsonb)",
48+
[
49+
("conversion_rate", 0.045, '{"funnel": "signup", "campaign": "summer"}'),
50+
("revenue", 15420.50, '{"product": "pro", "region": "us"}'),
51+
("bounce_rate", 0.32, '{"page": "landing", "source": "paid"}'),
52+
("session_duration", 180.5, '{"device": "mobile", "browser": "chrome"}'),
53+
],
54+
)
55+
56+
# Select all metrics using query mixin
57+
metrics = driver.select("SELECT * FROM analytics_data ORDER BY recorded_at")
58+
print(f"All metrics: {metrics}")
59+
60+
# Select one metric using query mixin
61+
revenue = driver.select_one("SELECT * FROM analytics_data WHERE metric_name = $1", "revenue")
62+
print(f"Revenue metric: {revenue}")
63+
64+
# Select one or none (no match) using query mixin
65+
nothing = driver.select_one_or_none("SELECT * FROM analytics_data WHERE metric_name = $1", "nothing")
66+
print(f"Nothing: {nothing}")
67+
68+
# Select scalar value using query mixin
69+
avg_value = driver.select_value("SELECT AVG(metric_value) FROM analytics_data WHERE metric_value > $1", 1.0)
70+
print(f"Average metric value: {avg_value:.2f}")
71+
72+
# Update
73+
result = driver.execute(
74+
"UPDATE analytics_data SET dimensions = $1::jsonb WHERE metric_name = $2",
75+
'{"updated": true}',
76+
"bounce_rate",
77+
)
78+
print(f"Updated {result.rows_affected} bounce rate records")
79+
80+
# Delete
81+
result = driver.execute("DELETE FROM analytics_data WHERE metric_value < $1", 1.0)
82+
print(f"Removed {result.rows_affected} low-value metrics")
83+
84+
# Use query builder with driver - this demonstrates the QueryBuilder parameter fix
85+
query = Select("*").from_("analytics_data").where("metric_name = $1")
86+
page_view_metrics = driver.select(query, "page_views")
87+
print(f"Page view metrics: {page_view_metrics}")
88+
89+
# JSON operations (PostgreSQL-specific) - using raw SQL due to SQLGlot JSON operator conversion
90+
mobile_metrics = driver.select(
91+
"SELECT metric_name, metric_value, dimensions->>'device' as device FROM analytics_data WHERE dimensions->>'device' = $1",
92+
"mobile",
93+
)
94+
print(f"Mobile metrics: {mobile_metrics}")
95+
96+
# Demonstrate pagination
97+
page_metrics = driver.select("SELECT * FROM analytics_data ORDER BY metric_value DESC LIMIT $1 OFFSET $2", 2, 0)
98+
total_count = driver.select_value("SELECT COUNT(*) FROM analytics_data")
99+
print(f"Page 1: {page_metrics}, Total: {total_count}")
100+
101+
102+
def main() -> None:
103+
"""Run ADBC example."""
104+
print("=== ADBC (Arrow Database Connectivity) Driver Example ===")
105+
try:
106+
adbc_example()
107+
print("✅ ADBC example completed successfully!")
108+
except Exception as e:
109+
print(f"❌ ADBC example failed: {e}")
110+
print("Make sure PostgreSQL is running with: make infra-up")
111+
112+
113+
if __name__ == "__main__":
114+
main()

docs/examples/aiosqlite_example.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# type: ignore
2+
"""Example demonstrating AIOSQLite driver usage with query mixins.
3+
4+
This example shows how to use the AIOSQLite driver directly with its built-in query
5+
mixin functionality for common database operations.
6+
"""
7+
8+
import asyncio
9+
10+
from sqlspec import SQLSpec
11+
from sqlspec.adapters.aiosqlite import AiosqliteConfig
12+
from sqlspec.builder import Select
13+
14+
__all__ = ("aiosqlite_example", "main")
15+
16+
17+
async def aiosqlite_example() -> None:
18+
"""Demonstrate asynchronous database driver usage with query mixins."""
19+
# Create SQLSpec instance with AIOSQLite
20+
spec = SQLSpec()
21+
config = AiosqliteConfig(pool_config={"database": ":memory:"})
22+
conf = spec.add_config(config)
23+
24+
# Get an async driver directly (drivers now have built-in query methods)
25+
async with spec.provide_session(conf) as driver:
26+
# Create a table
27+
await driver.execute("""
28+
CREATE TABLE products (
29+
id INTEGER PRIMARY KEY,
30+
name TEXT NOT NULL,
31+
price REAL NOT NULL
32+
)
33+
""")
34+
35+
# Insert data
36+
await driver.execute("INSERT INTO products (name, price) VALUES (?, ?)", "Laptop", 999.99)
37+
38+
# Insert multiple rows
39+
await driver.execute_many(
40+
"INSERT INTO products (name, price) VALUES (?, ?)",
41+
[("Mouse", 29.99), ("Keyboard", 79.99), ("Monitor", 299.99)],
42+
)
43+
44+
# Select all products using query mixin
45+
products = await driver.select("SELECT * FROM products ORDER BY price")
46+
print(f"All products: {products}")
47+
48+
# Select one product using query mixin
49+
laptop = await driver.select_one("SELECT * FROM products WHERE name = ?", "Laptop")
50+
print(f"Laptop: {laptop}")
51+
52+
# Select scalar value using query mixin
53+
avg_price = await driver.select_value("SELECT AVG(price) FROM products")
54+
print(f"Average price: ${avg_price:.2f}")
55+
56+
# Update
57+
result = await driver.execute("UPDATE products SET price = price * 0.9 WHERE price > ?", 100.0)
58+
print(f"Applied 10% discount to {result.rows_affected} expensive products")
59+
60+
# Use query builder with async driver
61+
query = Select("name", "price").from_("products").where("price < ?").order_by("price")
62+
cheap_products = await driver.select(query, 100.0)
63+
print(f"Cheap products: {cheap_products}")
64+
65+
# Demonstrate pagination
66+
page_products = await driver.select("SELECT * FROM products ORDER BY price LIMIT ? OFFSET ?", 2, 1)
67+
total_count = await driver.select_value("SELECT COUNT(*) FROM products")
68+
print(f"Products page 2: {len(page_products)} items, Total: {total_count}")
69+
70+
71+
async def main_async() -> None:
72+
"""Run AIOSQLite example with proper cleanup."""
73+
print("=== AIOSQLite Driver Example ===")
74+
await aiosqlite_example()
75+
print("✅ AIOSQLite example completed successfully!")
76+
77+
78+
def main() -> None:
79+
"""Run AIOSQLite example."""
80+
asyncio.run(main_async())
81+
82+
83+
if __name__ == "__main__":
84+
main()

docs/examples/asyncmy_example.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
"""Example demonstrating asyncmy driver usage with query mixins.
2+
3+
This example shows how to use the asyncmy driver with the development MySQL
4+
container started by `make infra-up`.
5+
"""
6+
7+
import asyncio
8+
9+
from sqlspec import SQLSpec
10+
from sqlspec.adapters.asyncmy import AsyncmyConfig
11+
from sqlspec.builder import Select
12+
13+
__all__ = ("asyncmy_example", "main")
14+
15+
16+
async def asyncmy_example() -> None:
17+
"""Demonstrate asyncmy database driver usage with query mixins."""
18+
# Create SQLSpec instance with MySQL (connects to dev container)
19+
spec = SQLSpec()
20+
config = AsyncmyConfig(
21+
pool_config={"host": "localhost", "port": 3307, "user": "root", "password": "mysql", "database": "test"}
22+
)
23+
spec.add_config(config)
24+
25+
# Get a driver directly (drivers now have built-in query methods)
26+
async with spec.provide_session(config) as driver:
27+
# Create a table
28+
await driver.execute("""
29+
CREATE TABLE IF NOT EXISTS inventory (
30+
id INT AUTO_INCREMENT PRIMARY KEY,
31+
item_name VARCHAR(255) NOT NULL,
32+
quantity INT NOT NULL,
33+
price DECIMAL(10,2) NOT NULL,
34+
supplier VARCHAR(255)
35+
)
36+
""")
37+
38+
# Clean up any existing data
39+
await driver.execute("TRUNCATE TABLE inventory")
40+
41+
# Insert data
42+
await driver.execute(
43+
"INSERT INTO inventory (item_name, quantity, price, supplier) VALUES (%s, %s, %s, %s)",
44+
"Laptop",
45+
50,
46+
1299.99,
47+
"TechCorp",
48+
)
49+
50+
# Insert multiple rows
51+
await driver.execute_many(
52+
"INSERT INTO inventory (item_name, quantity, price, supplier) VALUES (%s, %s, %s, %s)",
53+
[
54+
("Mouse", 200, 25.99, "TechCorp"),
55+
("Keyboard", 150, 89.99, "TechCorp"),
56+
("Monitor", 75, 399.99, "DisplayCo"),
57+
("Headphones", 100, 159.99, "AudioPlus"),
58+
],
59+
)
60+
61+
# Select all items using query mixin
62+
items = await driver.select("SELECT * FROM inventory ORDER BY price")
63+
print(f"All inventory: {items}")
64+
65+
# Select one item using query mixin
66+
laptop = await driver.select_one("SELECT * FROM inventory WHERE item_name = %s", "Laptop")
67+
print(f"Laptop: {laptop}")
68+
69+
# Select one or none (no match) using query mixin
70+
nothing = await driver.select_one_or_none("SELECT * FROM inventory WHERE item_name = %s", "Nothing")
71+
print(f"Nothing: {nothing}")
72+
73+
# Select scalar value using query mixin
74+
total_value = await driver.select_value("SELECT SUM(quantity * price) FROM inventory")
75+
print(f"Total inventory value: ${total_value:.2f}")
76+
77+
# Update
78+
result = await driver.execute(
79+
"UPDATE inventory SET quantity = quantity + %s WHERE supplier = %s", 10, "TechCorp"
80+
)
81+
print(f"Added stock for {result.rows_affected} TechCorp items")
82+
83+
# Delete
84+
result = await driver.execute("DELETE FROM inventory WHERE quantity < %s", 80)
85+
print(f"Removed {result.rows_affected} low-stock items")
86+
87+
# Use query builder with driver - this demonstrates the QueryBuilder parameter fix
88+
query = Select("*").from_("inventory").where("supplier = %s")
89+
techcorp_items = await driver.select(query, "TechCorp")
90+
print(f"TechCorp items: {techcorp_items}")
91+
92+
# Query builder with comparison
93+
query = Select("item_name", "price").from_("inventory").where("price > %s").order_by("price")
94+
expensive_items = await driver.select(query, 200.0)
95+
print(f"Expensive items: {expensive_items}")
96+
97+
# Demonstrate pagination
98+
page_items = await driver.select("SELECT * FROM inventory ORDER BY item_name LIMIT %s OFFSET %s", 2, 0)
99+
total_count = await driver.select_value("SELECT COUNT(*) FROM inventory")
100+
print(f"Page 1: {page_items}, Total: {total_count}")
101+
102+
103+
def main() -> None:
104+
"""Run asyncmy example."""
105+
print("=== asyncmy Driver Example ===")
106+
try:
107+
asyncio.run(asyncmy_example())
108+
print("✅ asyncmy example completed successfully!")
109+
except Exception as e:
110+
print(f"❌ asyncmy example failed: {e}")
111+
print("Make sure MySQL is running with: make infra-up")
112+
113+
114+
if __name__ == "__main__":
115+
main()

0 commit comments

Comments
 (0)