Skip to content

Commit 6d17b2c

Browse files
committed
docs: add Spanner API reference and custom dialect pattern guide
- Add Spanner adapter section to API reference (adapters.rst) - Create custom SQLglot dialect guide (custom-sqlglot-dialects.md) - Update AGENTS.md with custom dialect pattern and spanner in adapter list
1 parent 993c23b commit 6d17b2c

File tree

3 files changed

+521
-11
lines changed

3 files changed

+521
-11
lines changed

AGENTS.md

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ Each adapter in `sqlspec/adapters/` follows this structure:
7676
- `_types.py` - Adapter-specific type definitions
7777
- Optional: `_*_handlers.py` - Type handlers for optional features (numpy, pgvector)
7878

79-
Supported adapters: adbc, aiosqlite, asyncmy, asyncpg, bigquery, duckdb, oracledb, psqlpy, psycopg, sqlite
79+
Supported adapters: adbc, aiosqlite, asyncmy, asyncpg, bigquery, duckdb, oracledb, psqlpy, psycopg, spanner, sqlite
8080

8181
### Key Design Patterns
8282

@@ -295,6 +295,77 @@ _register_with_sqlglot()
295295
- MySQL: `DISTANCE(embedding, STRING_TO_VECTOR('[0.1,0.2]'), 'EUCLIDEAN')` (function)
296296
- Oracle: `VECTOR_DISTANCE(embedding, TO_VECTOR('[0.1,0.2]'), EUCLIDEAN)` (function)
297297

298+
### Custom SQLglot Dialect Pattern
299+
300+
For databases with unique SQL syntax not supported by existing sqlglot dialects:
301+
302+
```python
303+
# In sqlspec/adapters/{adapter}/dialect/_dialect.py
304+
from sqlglot import exp
305+
from sqlglot.dialects.bigquery import BigQuery
306+
from sqlglot.tokens import TokenType
307+
308+
class CustomDialect(BigQuery):
309+
"""Inherit from closest matching dialect."""
310+
311+
class Tokenizer(BigQuery.Tokenizer):
312+
"""Add custom keywords."""
313+
KEYWORDS = {
314+
**BigQuery.Tokenizer.KEYWORDS,
315+
"INTERLEAVE": TokenType.INTERLEAVE,
316+
}
317+
318+
class Parser(BigQuery.Parser):
319+
"""Override parser for custom syntax."""
320+
def _parse_table_parts(self, schema=False, is_db_reference=False, wildcard=False):
321+
table = super()._parse_table_parts(schema=schema, is_db_reference=is_db_reference, wildcard=wildcard)
322+
323+
# Parse custom clause
324+
if self._match_text_seq("INTERLEAVE", "IN", "PARENT"):
325+
parent = self._parse_table(schema=True, is_db_reference=True)
326+
table.set("interleave_parent", parent)
327+
328+
return table
329+
330+
class Generator(BigQuery.Generator):
331+
"""Override generator for custom SQL output."""
332+
def table_sql(self, expression, sep=" "):
333+
sql = super().table_sql(expression, sep=sep)
334+
335+
# Generate custom clause
336+
parent = expression.args.get("interleave_parent")
337+
if parent:
338+
sql = f"{sql}\nINTERLEAVE IN PARENT {self.sql(parent)}"
339+
340+
return sql
341+
342+
# Register dialect in adapter __init__.py
343+
from sqlglot.dialects.dialect import Dialect
344+
Dialect.classes["custom"] = CustomDialect
345+
```
346+
347+
**Create custom dialect when**:
348+
- Database has unique DDL/DML syntax not in existing dialects
349+
- Need to parse and validate database-specific keywords
350+
- Need to generate database-specific SQL from AST
351+
- An existing dialect provides 80%+ compatibility to inherit from
352+
353+
**Do NOT create custom dialect if**:
354+
- Only parameter style differences (use parameter profiles)
355+
- Only type conversion differences (use type converters)
356+
- Only connection management differences (use config/driver)
357+
358+
**Key principles**:
359+
- **Inherit from closest dialect**: Spanner inherits BigQuery (both GoogleSQL)
360+
- **Minimal overrides**: Only override methods that need customization
361+
- **Store metadata in AST**: Use `expression.set(key, value)` for custom data
362+
- **Handle missing tokens**: Check `getattr(TokenType, "KEYWORD", None)` before using
363+
- **Test thoroughly**: Unit tests for parsing/generation, integration tests with real DB
364+
365+
**Reference implementation**: `sqlspec/adapters/spanner/dialect/` (GoogleSQL and PostgreSQL modes)
366+
367+
**Documentation**: See `/docs/guides/architecture/custom-sqlglot-dialects.md` for full guide
368+
298369
### Error Handling
299370

300371
- Custom exceptions inherit from `SQLSpecError` in `sqlspec/exceptions.py`

0 commit comments

Comments
 (0)