Skip to content

Commit 1c6a2ad

Browse files
authored
feat(driver): Google Spanner Driver (#272)
Implements full support for Google Cloud Spanner.
1 parent a4d650a commit 1c6a2ad

Some content is hidden

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

57 files changed

+5823
-439
lines changed

.claude/skills/README.md

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -67,16 +67,17 @@ Detailed guidance for individual database adapters:
6767

6868
| Adapter | File | Status |
6969
|---------|------|--------|
70-
| AsyncPG (PostgreSQL async) | [asyncpg.md](sqlspec-adapters/asyncpg.md) | ✅ Complete |
71-
| Psycopg (PostgreSQL sync/async) | psycopg.md | 📝 Template |
72-
| Psqlpy (PostgreSQL Rust-based) | psqlpy.md | 📝 Template |
73-
| SQLite (sync) | sqlite.md | 📝 Template |
74-
| AioSQLite (async) | aiosqlite.md | 📝 Template |
75-
| DuckDB (analytics) | duckdb.md | 📝 Template |
76-
| Oracle | oracledb.md | 📝 Template |
77-
| Asyncmy (MySQL async) | asyncmy.md | 📝 Template |
78-
| BigQuery | bigquery.md | 📝 Template |
79-
| ADBC (Arrow-native) | adbc.md | 📝 Template |
70+
| AsyncPG (PostgreSQL async) | [asyncpg.md](sqlspec_adapters/asyncpg.md) | ✅ Complete |
71+
| Psycopg (PostgreSQL sync/async) | [psycopg.md](sqlspec_adapters/psycopg.md) | ✅ Complete |
72+
| Psqlpy (PostgreSQL Rust-based) | [psqlpy.md](sqlspec_adapters/psqlpy.md) | ✅ Complete |
73+
| SQLite (sync) | [sqlite.md](sqlspec_adapters/sqlite.md) | ✅ Complete |
74+
| AioSQLite (async) | [aiosqlite.md](sqlspec_adapters/aiosqlite.md) | ✅ Complete |
75+
| DuckDB (analytics) | [duckdb.md](sqlspec_adapters/duckdb.md) | ✅ Complete |
76+
| Oracle | [oracledb.md](sqlspec_adapters/oracledb.md) | ✅ Complete |
77+
| Asyncmy (MySQL async) | [asyncmy.md](sqlspec_adapters/asyncmy.md) | ✅ Complete |
78+
| BigQuery | [bigquery.md](sqlspec_adapters/bigquery.md) | ✅ Complete |
79+
| Spanner | [spanner.md](sqlspec_adapters/spanner.md) | ✅ Complete |
80+
| ADBC (Arrow-native) | [adbc.md](sqlspec_adapters/adbc.md) | ✅ Complete |
8081

8182
**Note:** Template adapters follow the AsyncPG structure and can be quickly expanded when needed.
8283

.claude/skills/sqlspec_adapters/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Individual skills for each database adapter with adapter-specific guidance.
3030
### Cloud & Multi-Database
3131

3232
- **[bigquery.md](bigquery.md)** - Google BigQuery (data warehouse) ✅
33+
- **[spanner.md](spanner.md)** - Google Cloud Spanner (globally distributed) ✅
3334
- **[adbc.md](adbc.md)** - ADBC (Arrow-native, multi-database) ✅
3435

3536
## Adapter Selection Guide
@@ -44,6 +45,7 @@ Individual skills for each database adapter with adapter-specific guidance.
4445
| Oracle enterprise | OracleDB | oracledb.md |
4546
| MySQL/MariaDB | Asyncmy | asyncmy.md |
4647
| Cloud data warehouse | BigQuery | bigquery.md |
48+
| Global scale | Spanner | spanner.md |
4749
| Multi-database | ADBC | adbc.md |
4850
| Arrow ecosystem | ADBC or DuckDB | adbc.md, duckdb.md |
4951

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
# Spanner Adapter Skill
2+
3+
**Adapter:** Google Cloud Spanner (Sync)
4+
**Category:** Database Adapter
5+
**Status:** Active
6+
7+
## Description
8+
9+
Expert guidance for using SQLSpec's Spanner adapter for Google Cloud Spanner. Spanner is a globally distributed, horizontally scalable database with strong consistency guarantees.
10+
11+
## When to Use Spanner
12+
13+
- **Global distribution** - Multi-region deployments with strong consistency
14+
- **Horizontal scalability** - Automatic sharding and scaling
15+
- **Financial/critical workloads** - ACID transactions at scale
16+
- **Agent deployments** - ADK integration for AI agent session/event storage
17+
- **Interleaved data** - Parent-child relationships with physical co-location
18+
19+
## Configuration
20+
21+
```python
22+
from sqlspec.adapters.spanner import SpannerSyncConfig
23+
24+
config = SpannerSyncConfig(
25+
pool_config={
26+
"project": "my-gcp-project",
27+
"instance_id": "my-instance",
28+
"database_id": "my-database",
29+
# Optional pool settings:
30+
"pool_type": FixedSizePool, # or PingingPool
31+
"min_sessions": 5,
32+
"max_sessions": 20,
33+
"ping_interval": 300, # For PingingPool
34+
},
35+
# Optional: Custom credentials
36+
# credentials=credentials_object,
37+
# client_options={"api_endpoint": "localhost:9010"}, # Emulator
38+
)
39+
```
40+
41+
### With Emulator (Local Development)
42+
43+
```python
44+
from google.auth.credentials import AnonymousCredentials
45+
46+
config = SpannerSyncConfig(
47+
pool_config={
48+
"project": "test-project",
49+
"instance_id": "test-instance",
50+
"database_id": "test-database",
51+
"credentials": AnonymousCredentials(),
52+
"client_options": {"api_endpoint": "localhost:9010"},
53+
}
54+
)
55+
```
56+
57+
## Parameter Style
58+
59+
**Named with @**: `@param_name`
60+
61+
```python
62+
# Single parameter
63+
result = session.execute(
64+
"SELECT * FROM users WHERE id = @id",
65+
{"id": "user-123"}
66+
)
67+
68+
# Multiple parameters
69+
result = session.execute(
70+
"SELECT * FROM users WHERE status = @status AND age > @min_age",
71+
{"status": "active", "min_age": 18}
72+
)
73+
```
74+
75+
## Custom SQLGlot Dialects
76+
77+
Spanner adapter includes two custom dialects:
78+
79+
### GoogleSQL (spanner)
80+
81+
Default dialect for standard Spanner SQL:
82+
83+
```python
84+
# Supports INTERLEAVE, ROW DELETION POLICY
85+
ddl = """
86+
CREATE TABLE orders (
87+
customer_id STRING(36) NOT NULL,
88+
order_id STRING(36) NOT NULL,
89+
total NUMERIC
90+
) PRIMARY KEY (customer_id, order_id),
91+
INTERLEAVE IN PARENT customers ON DELETE CASCADE
92+
"""
93+
```
94+
95+
### PostgreSQL Mode (spangres)
96+
97+
For Spanner PostgreSQL interface:
98+
99+
```python
100+
from sqlspec.adapters.spanner.dialect import SpangresDialect
101+
# Uses PostgreSQL-compatible syntax with Spanner-specific features
102+
```
103+
104+
## Interleaved Tables
105+
106+
Physical co-location of parent-child rows for performance:
107+
108+
```python
109+
# Parent table
110+
ddl_parent = """
111+
CREATE TABLE customers (
112+
customer_id STRING(36) NOT NULL,
113+
name STRING(100)
114+
) PRIMARY KEY (customer_id)
115+
"""
116+
117+
# Child table interleaved with parent
118+
ddl_child = """
119+
CREATE TABLE orders (
120+
customer_id STRING(36) NOT NULL,
121+
order_id STRING(36) NOT NULL,
122+
total NUMERIC
123+
) PRIMARY KEY (customer_id, order_id),
124+
INTERLEAVE IN PARENT customers ON DELETE CASCADE
125+
"""
126+
```
127+
128+
Benefits:
129+
- Automatic co-location of related data
130+
- Efficient joins between parent and child
131+
- Cascading deletes for data integrity
132+
133+
## TTL Policies (Row Deletion)
134+
135+
Automatic row expiration:
136+
137+
```python
138+
ddl = """
139+
CREATE TABLE events (
140+
id STRING(36) NOT NULL,
141+
data JSON,
142+
created_at TIMESTAMP NOT NULL
143+
) PRIMARY KEY (id),
144+
ROW DELETION POLICY (OLDER_THAN(created_at, INTERVAL 30 DAY))
145+
"""
146+
```
147+
148+
## Litestar Integration
149+
150+
Session store for Litestar applications:
151+
152+
```python
153+
from litestar import Litestar
154+
from litestar.middleware.session import SessionMiddleware
155+
from sqlspec.adapters.spanner import SpannerSyncConfig
156+
from sqlspec.adapters.spanner.litestar import SpannerSyncStore
157+
158+
config = SpannerSyncConfig(
159+
pool_config={
160+
"project": "my-project",
161+
"instance_id": "my-instance",
162+
"database_id": "my-database",
163+
},
164+
extension_config={
165+
"litestar": {
166+
"table_name": "sessions",
167+
"shard_count": 10, # For high throughput
168+
}
169+
},
170+
)
171+
172+
store = SpannerSyncStore(config)
173+
174+
app = Litestar(
175+
middleware=[SessionMiddleware(backend=store)],
176+
)
177+
```
178+
179+
### Session Store Features
180+
181+
- **Sharding** - Distribute sessions across shards for write throughput
182+
- **TTL Support** - Automatic session expiration via Spanner TTL
183+
- **Commit Timestamps** - Automatic created_at/updated_at tracking
184+
185+
## ADK Integration
186+
187+
Session and event storage for AI agents:
188+
189+
```python
190+
from sqlspec.adapters.spanner import SpannerSyncConfig
191+
from sqlspec.adapters.spanner.adk import SpannerADKStore
192+
193+
config = SpannerSyncConfig(
194+
pool_config={
195+
"project": "my-project",
196+
"instance_id": "my-instance",
197+
"database_id": "my-database",
198+
},
199+
extension_config={
200+
"adk": {
201+
"sessions_table": "adk_sessions",
202+
"events_table": "adk_events",
203+
}
204+
},
205+
)
206+
207+
store = SpannerADKStore(config)
208+
209+
# Create session
210+
session = store.create_session(app_name="my-agent", user_id="user-123")
211+
212+
# Add event (stored in interleaved table)
213+
store.add_event(session.id, {"type": "tool_call", "tool": "search"})
214+
215+
# List events (efficient due to interleaving)
216+
events = store.list_events(session.id)
217+
```
218+
219+
### ADK Store Features
220+
221+
- **Interleaved Events** - Events table interleaved with sessions for efficiency
222+
- **JSON State** - Session state stored as native JSON
223+
- **Timestamp Tracking** - Automatic created_at/updated_at
224+
225+
## Storage Bridge
226+
227+
Export and import data via Arrow:
228+
229+
```python
230+
# Export to storage
231+
job = session.select_to_storage(
232+
"SELECT * FROM users WHERE active = @active",
233+
"gs://my-bucket/exports/users.parquet",
234+
{"active": True},
235+
format_hint="parquet",
236+
)
237+
238+
# Load from Arrow table
239+
import pyarrow as pa
240+
241+
table = pa.table({
242+
"id": [1, 2, 3],
243+
"name": ["Alice", "Bob", "Charlie"],
244+
})
245+
job = session.load_from_arrow("scores", table, overwrite=True)
246+
```
247+
248+
## Best Practices
249+
250+
1. **Use interleaved tables** - For parent-child relationships
251+
2. **Avoid hotspots** - Use UUIDs or distributed keys
252+
3. **Batch writes** - Stay under 20k mutation limit per transaction
253+
4. **Configure TTL** - For temporary data (sessions, events, logs)
254+
5. **Use session pooling** - Configure based on concurrency needs
255+
6. **Prefer snapshots** - Use read-only snapshots for queries
256+
7. **Transaction for writes** - Always use transactions for mutations
257+
258+
## Common Issues
259+
260+
### DDL Operations Fail
261+
262+
DDL cannot be executed through `execute()`. Use database admin API:
263+
264+
```python
265+
database.update_ddl([ddl_statement])
266+
```
267+
268+
### Mutation Limit Exceeded
269+
270+
Spanner has 20,000 mutation limit per transaction. Batch operations:
271+
272+
```python
273+
# Split large inserts into batches
274+
for batch in chunks(records, 1000):
275+
with session.transaction():
276+
for record in batch:
277+
session.execute(insert_sql, record)
278+
```
279+
280+
### Read-Only Session Error
281+
282+
Default sessions are read-only snapshots. For writes:
283+
284+
```python
285+
# Use transaction context
286+
with config.provide_session(transaction=True) as session:
287+
session.execute("UPDATE ...")
288+
```
289+
290+
### Emulator Limitations
291+
292+
Emulator doesn't support all features:
293+
- Some complex queries
294+
- Backups
295+
- Instance/database admin operations
296+
297+
Test critical functionality against real Spanner instance.
298+
299+
## Performance Characteristics
300+
301+
- **Latency**: 5-10ms for simple queries (within region)
302+
- **Throughput**: Scales horizontally with nodes
303+
- **Consistency**: Linearizable (strongest)
304+
- **Availability**: 99.999% SLA (multi-region)
305+
306+
Compared to other cloud databases:
307+
- **vs BigQuery**: Better for OLTP, worse for analytics
308+
- **vs Cloud SQL**: Better for global scale, higher cost
309+
- **vs Firestore**: Better for complex queries, relational data

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ repos:
1717
- id: mixed-line-ending
1818
- id: trailing-whitespace
1919
- repo: https://github.com/charliermarsh/ruff-pre-commit
20-
rev: "v0.14.6"
20+
rev: "v0.14.7"
2121
hooks:
2222
- id: ruff
2323
args: ["--fix"]

0 commit comments

Comments
 (0)