QUERY_COMPILATION.md¶
RowGuard Query Compilation¶
Purpose¶
Query compilation is the planning phase that transforms a user’s request into an immutable execution plan. Unlike execution, compilation performs no database I/O. Its responsibilities are to normalize inputs, integrate SQLRules, construct a SQLAlchemy statement, and prepare all metadata required by the runtime.
Compilation should be deterministic, cache-friendly, and independently testable.
Compilation vs Execution¶
Application Request
│
▼
Query Compilation
│
▼
ExecutionPlan
│
▼
Query Execution
│
▼
Database
Compilation decides what will be executed.
Execution decides when and how it is executed.
Inputs¶
A compilation request may include:
SQLAlchemy
TableSQLAlchemy
SelectORM model
SessionorAsyncSession(for dialect/context only; no execution)Target Pydantic model
Additional WHERE expressions
SQLRules configuration
Field mappings
Rejection policy
Diagnostics configuration
Compilation Stages¶
1. Normalize Inputs¶
Resolve the incoming request into a canonical internal representation.
Examples:
Convert a table into a
select(table)Validate supplied SQLAlchemy statements
Resolve target model
2. Build Statement¶
If the caller supplied a table:
stmt = select(users)
If the caller supplied a statement:
stmt = existing_statement
The compiler should preserve existing clauses whenever possible.
3. Compile SQLRules¶
When enabled:
compiled = sqlrules.compile(
model=UserRead,
table=users,
)
This stage never executes SQL.
4. Merge Filters¶
SQLRules expressions and user-provided expressions are merged into a single statement.
Conceptually:
stmt = stmt.where(
*sqlrules.where(compiled),
*user_filters,
)
The merge should preserve SQLAlchemy composability.
5. Resolve Adaptation¶
Precompute:
field mappings
alias mappings
duplicate-key strategy
adapter configuration
This avoids repeated work during execution.
6. Resolve Validation¶
Prepare:
target model
validation context
strictness configuration
No validation occurs yet.
7. Resolve Rejection Policy¶
Prepare the configured reject handler and immutable rejection configuration.
8. Build Execution Plan¶
The final output is an immutable execution plan.
Suggested structure:
@dataclass(frozen=True, slots=True)
class ExecutionPlan:
statement: Select
model: type[BaseModel]
compiled_rules: object | None
row_adapter: RowAdapter
validator: Validator
reject_handler: RejectHandler
statistics: StatisticsPlan
Execution plans should be reusable.
Caching¶
Compilation is a good candidate for caching.
Potential cache key:
model type
statement shape
SQLRules options
adapter configuration
Execution-specific state must not be cached.
Diagnostics¶
Compilation diagnostics may include:
SQLRules constraints applied
constraints skipped
alias resolution
field mappings
adapter selection
statement transformations
Diagnostics become part of the execution result.
Error Handling¶
Compilation errors occur before database access.
Examples:
invalid field mappings
unsupported SQLRules configuration
incompatible adapter
ambiguous result mappings
These errors should be reported immediately.
Performance¶
Goals:
O(fields + constraints)
No database I/O
Immutable outputs
Minimal allocations
Reusable execution plans
Testing¶
Compilation tests should verify:
statement generation
SQLRules integration
filter merging
adapter selection
execution plan contents
cache correctness
deterministic output
MVP Scope¶
Initial implementation:
table → select()
existing statement support
SQLRules integration
field-map planning
immutable execution plans
diagnostics
Deferred:
advanced plan caching
optimizer passes
cost estimation
distributed planning
Design Principles¶
Planning and execution are separate concerns.
Compilation performs no I/O.
Execution plans are immutable.
SQLRules is the only source of SQL constraint compilation.
Compilation should be fast enough to cache, but simple enough that caching is optional.