Data Flow#
SQLSpec processes database interactions through a four-stage pipeline: statement authoring, compilation and normalization, driver execution, and result transformation. This architecture decouples how queries are written from how databases execute them and how applications consume results.
Pipeline Overview#
flowchart TD
A[Stage 1: Statement Input<br/>Raw SQL, SQL Object, Builder, or SQL Files] --> B[Stage 2: Compilation & Normalization<br/>StatementConfig, Placeholders, AST Cache, Declared Params]
B --> C[Stage 3: Session & Driver Execution<br/>Sync / Async Driver Adapter, Connection Pool, Transactions]
C --> D[Stage 4: Result Transformation<br/>SQLResult, Schema Mapping, Chunked Streams, Arrow Zero-Copy]
Stage 1: Statement Input#
SQLSpec accepts queries in multiple forms to match the needs of your application:
Raw SQL strings: Plain SQL with named (
:name) or positional (?) placeholders.SQL objects: Instances of
SQLwith support for immutable.where()condition chaining,.select_only(), and.paginate().QueryBuilder: The fluent AST builder (
sql.select(),sql.insert(),sql.update(),sql.delete()) with compile-time dialect targeting.SQL Files: External
.sqlfiles loaded into the registry viaspec.load_sql_files()and retrieved by name withspec.get_sql().
Stage 2: Compilation & Normalization#
Before reaching the database, queries pass through the core pipeline:
Parameter style conversion: Placeholders (
:param,?,$n) are automatically mapped to the dialect-native style required by the target driver.Declared parameter validation: Queries loaded from SQL files with
-- param:directives have parameter presence and Python types verified before driver dispatch.Compiled statement caching: Parameterized queries and AST structures are cached by fingerprint to bypass repeated SQL parsing overhead on hot paths.
Dialect translation: SQLGlot-powered AST transformers can convert expressions between SQL dialects when cross-database portability is needed.
Stage 3: Session & Driver Execution#
Database operations occur within a managed session:
Session provisioning: Calling
spec.provide_session(config)yields a synchronous (SyncDriverAdapterBase) or asynchronous (AsyncDriverAdapterBase) driver adapter.Connection management: The session acquires a physical connection from the config's pool (
provide_pool()/create_pool()) and releases it upon exit.Transaction control: Sessions manage transaction boundaries via
session.begin(),session.commit(),session.rollback(), savepoints, or thesession.transaction()context manager.Exception mapping: Low-level database errors (DBAPI exceptions, driver errors) are translated into unified SQLSpec exceptions (
SQLSpecError,IntegrityError,OperationalError).
Stage 4: Result Transformation#
Drivers provide flexible consumption patterns for query results:
Structured SQLResult: Low-level
session.execute()returns aSQLResultwith.all(),.one(),.one_or_none(),.scalar(),.scalar_or_none(), and DML stats (.rows_affected,.last_inserted_id).Direct query helpers:
session.select(),session.select_one(),session.select_value(), andsession.select_with_total()return processed rows directly.Schema mapping: Passing
schema_type=Modelautomatically maps result rows into dataclasses, msgspec Structs, Pydantic models, attrs classes, or TypedDict instances.Memory-bounded streaming:
session.select_stream(chunk_size=N)streams large result sets in bounded chunks using the driver's native cursor streaming primitive.Arrow integration:
session.select_to_arrow()yields Apache Arrow Tables, RecordBatches, or RecordBatchReaders for zero-copy analytical processing.Storage export:
session.select_to_storage()streams and writes query results directly to local files or cloud object storage (S3, GCS, Azure) in Parquet or CSV format.
Minimal Execution Example#
execute select#from sqlspec import SQLSpec
from sqlspec.adapters.sqlite import SqliteConfig
db_path = tmp_path / "selects.db"
spec = SQLSpec()
config = spec.add_config(SqliteConfig(connection_config={"database": str(db_path)}))
with spec.provide_session(config) as session:
session.execute("create table if not exists teams (id integer primary key, name text)")
session.execute("insert into teams (name) values ('Litestar'), ('SQLSpec')")
result = session.execute("select id, name from teams order by id")
print(result.all())