Changelog¶
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
0.6.0 — 2026-07-11¶
Upgrade notes¶
New rejection policies:
callback,quarantine, andlog.Optional
max_rejections/max_rejection_rateon all entry points.quarantine_transactionmust be"separate"; other values raise at config/plan time.See Rejection policies and Upgrading.
Added¶
on_reject="callback"withreject_callback=andCallbackDecisionStructured
CallbackContext(no live session by default)on_reject="quarantine"withQuarantineRecord/QuarantineReceiptReference providers:
InMemoryQuarantineProvider,JSONLQuarantineProvideron_reject="log"(WARNING log, continue, no retain)Redaction (
callback_values/quarantine_values/redact_fields)Quarantine retention (
receipt/rejection/both/none)Rejection thresholds (
max_rejections,max_rejection_rate)Async reject handlers for
aselect/aexecute/astreamQueryResult.quarantine_receipts/ stream receipt accessorsTyped errors:
CallbackError,QuarantineError,RejectionThresholdErrorExamples:
callback_basic.py,quarantine_jsonl.py
Notes¶
Provider failures never invent accepted models; original rejections are preserved
Reference providers do not write through the source Session
on_callback_error="reject_handler"aliases"raise"for MVPPlugin registration of quarantine providers remains deferred to 0.7.0
Fixed¶
Policy errors (
raise/ callback / quarantine) take precedence over rejection thresholdsBuffered engines no longer let rejection-policy
close()mask primary errorsRedacted handoffs strip
validation_errorinputs and redactsource_identityAsyncStreamResultfalls back to sync iteration likeAsyncExecutionEngineAsync callable objects (
async def __call__) are detected as async callbacksInvalid / policy-irrelevant rejection knobs raise
ConfigurationErrorPlan cache keys include rejection thresholds, redaction, and handler identity
0.5.0 — 2026-07-10¶
Upgrade notes¶
Require
sqlrules>=1.0.0,<2(uninstall older majors).New ORM / SQLModel read paths; see Upgrading.
Added¶
SQLAlchemy ORM mapped classes as
table=/source=with SQLRules pushdownProjected ORM selects via
execute(statement=select(...), source=MappedClass)Single-entity adaptation (
ORMEntityAdapter) forselect(MappedClass)Opt-in
orm_validation="from_attributes"with unloaded-attribute guardsunloaded_attributes="error"(only supported policy in 0.5)Optional
attribute_mapfor entity attribute → model field remappingRejectedRow.source_identityprimary-key dict (no live entity retention)SQLModel table-source support via optional
rowguard[sqlmodel]Examples:
orm_projected.py,orm_entity.py,sqlmodel_basic.pyUser guide: ORM and SQLModel
Changed¶
Require
sqlrules>=1.0.0,<2(SQLRules 1.0 stable Application API)
Fixed¶
attribute_mapcannot be combined withfrom_attributes(plan-time error)Entity-shaped selects require
Session/AsyncSession(not bareConnection)Model-only default/optional fields no longer force entity
getattrfailuresSynonyms are not treated as permanently unloaded when the target column is loaded
Joined-inheritance
column_mapmembership uses full mapper columnsSelect passed as
source=is treated as the statement (no raw SA crash)_table_column_namesusesselected_columnsfor Selects (no SelectBase.c warning)field_maprejected on entity shapes; relationships rejected at plan timeAdaptation failures preserve best-effort
source_identityPlan cache distinguishes
diagnostics.enabled
Notes¶
Prefer column projections for strict read-contract validation
Multi-entity and entity+scalar shapes are rejected at plan time
Relationship traversal, write-back, and lazy-load-enabled validation remain out of scope
0.4.0 — 2026-07-10¶
Added¶
First-class async APIs:
aselect(),aexecute(), andastream()AsyncSession/AsyncConnectionexecution viaAsyncExecutionContextAsyncStreamResult[T]with lifecycle parity to syncStreamResult(async with,async for, idempotentclose(), raise-policy stats)AsyncExecutionEngine/AsyncStreamEnginereusing planner +process_rowAsync tests under
tests/async/(aiosqlite parity, lifecycle, cancellation)examples/async_basic.pyand pytest-asyncioasyncio_mode = autoOptional install:
pip install rowguard[async](aiosqlite,greenlet)
Notes¶
Await only DB I/O; Pydantic validation remains synchronous on the event loop (document blocking risk for heavy models)
Async callback / quarantine reject handlers remain deferred to 0.6.0
Driver matrix for this release: sqlite+aiosqlite (asyncpg not required)
0.3.1 — 2026-07-10¶
Fixed¶
Stream iteration without
withnow closes the SQLAlchemy result on earlybreak/ consumer exit (for model in stream)Raise-policy rejections are recorded in stream statistics before raising, so post-mortem
is_clean/rows_rejectedare accurateon_stream_completeobservers receive a non-zeroexecution_time_nsafter rows were processedClose failures after a successful complete no longer emit a spurious
on_stream_failedwhen using a context managerRe-entering a closed
StreamResultraisesQueryExecutionErrorinstead of silently empty-iteratingInvalid
yield_peris rejected atstream()/StreamingConfigconstructionPlan-time
field_mapno longer treats values as source table columns (they are result keys); labeledexecute+source=+field_mapworksPlan-cache hits rewrite diagnostic
execution_ids to match the rebound plancompile_planrejectstable=+statement=togethercompiled_rules/column_mapwithout a pushdown source raisePlanningErrorinstead of being silently dropped
0.3.0 — 2026-07-10¶
Added¶
Public
stream()returning context-managedStreamResult[T]Incremental validation that yields accepted models without retaining them
SQLAlchemy streaming options:
stream_results=Trueand optionalyield_per=Rejection policy parity during streaming (
raise,collect,skip)Live
statistics, retainedrejected,diagnostics, andstatementonStreamResultFirst-party
StreamObserver/BaseStreamObserverprogress hooksStreamingConfigfor execution-time streaming optionsShared
MutableStatistics/ExecutionStateused by buffered and streaming enginesStreaming tests (parity, cleanup, observers, memory regression) and
examples/streaming.py
Changed¶
stream()accepts eithertable=orstatement=with the same planning knobs asselect/execute(session/connection, pushdown, maps,strict, …)Package version and docs updated for the 0.3.0 surface
Deferred¶
Async APIs including
astream()(0.4.0)ORM / SQLModel (0.5.0)
Callback and quarantine rejection policies (0.6.0)
0.2.0 — 2026-07-10¶
Added¶
Staged immutable
ExecutionPlanwith source, pushdown, adapter, validation, and rejection sub-plansPublic
compile_plan()for plan inspection without database I/OConfig dataclasses:
PushdownConfig,ValidationConfig,RejectionConfig,DiagnosticsConfig,AdapterConfigPrecompiled SQLRules via
compiled_rules=(skips livesqlrules.compile)Plan-time validation of
field_map/column_mapkeysPlanningErrorfor stage failures with optionalstage/execution_idStable planning diagnostic codes (
planning.source_resolved,planning.pushdown_disabled,planning.precompiled_rules, …)Optional structural plan cache on
QueryPlanner(off by default)SyncExecutionContextholds session/connection (no longer on the plan)Optional
strict=for Pydantic validation planning
Changed¶
Session/connection moved off
ExecutionPlanonto execution contextInternal plan shape is not frozen until 1.0; 0.1.0 call sites remain supported
pushdown_sourceno longer overrides the SELECTFROMwhentable=is setSkipped pushdown (no source) sets
use_sqlrules=False/PushdownPlan.enabled=False
Fixed¶
Plan cache key collisions on
parameters,column_mapvalues,compiled_rules, andpushdown.source; cache hits rebind parameters and mint a freshexecution_idcolumn_mapmembership is hard-failed when pushdown source columns are knownresult.close()failures no longer mask validation/execution errorsvalidate_rowsrejects unknownfield_mapkeys like the plannerUnexpected adapter exceptions are wrapped as
RowAdaptationErrorfor rejection policiesStreamResultraisesNotImplementedErrorinstead of silently empty-iteratingcompile_planrejects passing bothtable=andsource=LRUCacherejects non-positivemax_entries
Deferred¶
ORM / SQLModel (planned for 0.5.0)
Async APIs (0.4.0)
Callback and quarantine rejection policies
0.1.0 — 2026-07-10¶
Added¶
Public sync API:
select(),execute(),validate_rows()SQLAlchemy Core support for
Tableand existingSelectSync
SessionandConnectionexecutionSQLRules pushdown integration (
use_sqlrules, optionalcolumn_map)Pydantic v2 row validation via mapping adaptation
Rejection policies:
raise,collect,skipQueryResult[T],RejectedRow,QueryStatistics, and diagnosticsOptional
field_mapfor result key remappingSQLite integration tests, unit tests, CI, and example
Fixed¶
field_mapno longer silently binds wrong keys or accepts defaults when mapped source columns are missing (raisesRowAdaptationError)SQLAlchemy results are closed after buffered execution
Rows are processed incrementally so
raisecan abort without full prefetchAdaptation failures record timing and enrich
RowAdaptationErrorwithrow_index/modelunder the raise policyexecution_timeis end-to-end for SQL andvalidate_rowspathsrows_validatedcounts only rows that reached Pydantic validationExplicit
statement+sourcepushdown emitssqlrules.pushdown_source_explicitdiagnosticRequire
sqlrules>=0.4.0
Deferred¶
stream()(planned for 0.3.0)Async APIs (planned for 0.4.0)
ORM / SQLModel integrations
Callback and quarantine rejection policies