feat: SQL-file parameter metadata annotations (-- param:)#497
Merged
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #497 +/- ##
==========================================
- Coverage 74.46% 74.30% -0.16%
==========================================
Files 438 439 +1
Lines 53134 53273 +139
Branches 8424 8451 +27
==========================================
+ Hits 39566 39585 +19
- Misses 11032 11061 +29
- Partials 2536 2627 +91
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
fb847b8 to
9a865cf
Compare
Add a _declared_parameters slot to SQL and thread it through all seven construction/reset paths (__init__, _init_from_sql_object, copy full + fast path via _create_empty_copy, reset, _create_cached_direct, plus the declared_parameters property). Proven mypyc-safe and pool-leak-free against the compiled module. Spike smgc.7 for sqlspec-smgc (gh-491).
Introduce core/parameters/_declared.py with the mypyc-safe ParameterDeclaration value object and an extensible type registry (register_param_type / resolve_param_type) that resolves declared type strings to Python types via a fixed allowlist plus user registration, never evaluating the string. Exported through sqlspec.core and the top-level package. Ch1 sqlspec-smgc.1 (gh-491).
Scan each named statement's leading comment block for -- param: <name> <type>[?] [description] directives (alongside -- dialect:), storing them on NamedStatement.parameters. Malformed directives warn and skip by default, or raise when strict_parameter_annotations is set. Surface declarations via SQLFileLoader.get_query_parameters / SQLSpec.get_query_parameters and accept them in add_named_sql. Declarations ride the SQLFileCacheEntry, surviving the file-cache roundtrip. Ch2 sqlspec-smgc.2 (gh-491).
…iver Tighten the SQL._declared_parameters slot to tuple[ParameterDeclaration, ...], add a declared_parameters constructor keyword, and populate it from get_sql(). Thread the slot through every self-derivation (as_script, add_named_parameter, _create_modified_copy_with_expression) and through the driver's _prepare_from_sql rebuild so declarations survive prepare_statement + filter application across all adapters. Fold the spike proof into a permanent carriage test. Ch3 sqlspec-smgc.3 (gh-491).
When a query declares params, cross-check them against the SQL's actual placeholders at load time: declared names must be a subset of the named placeholders (drift), or for positional binding the declared count must equal the placeholder count. Raises SQLFileParseError early; declaration-driven, so queries without -- param: directives are unaffected. Ch4 sqlspec-smgc.4 (gh-491).
Auto-generated parameter names (builder where/in/between helpers) now use the
param_ prefix instead of parameter_, aligning with the param_{ordinal} fallback
already used in the converter/processor and with the new -- param: file
annotations.
gh-491.
Declared parameters are now strictly binary: a declared param is always validated (presence + type); undeclared params are untouched. Removes the ?-suffix grammar and ParameterDeclaration.required, which had an empty domain for loaded SQL files (every declared placeholder is static and always bound, so required=False could never legitimately fire).
Single shared hook in prepare_statement validates declared params on the original user params before driver style conversion, covering all adapters and execution methods. Declared params must be present (named binding); present non-None values whose declared type resolves via the registry must satisfy isinstance. None is allowed (SQL NULL); unresolved types are documentation-only; extra params (filter-injected limit/offset) are never rejected. execute_many checks the first row only; positional binding is skipped (arity validated at load). No-op unless declarations are present.
Adds a Declared Parameters section to the SQL file loader usage guide (grammar, type vocabulary, register_param_type, load/execute-time validation timing, strict_parameter_annotations, introspection), a runnable declared_params.py example, and ParameterDeclaration/register_param_type/resolve_param_type autodoc in the loader reference. Documents the binary declaration model (no optional marker). Docs build clean under -W.
2b6ddac to
65294aa
Compare
refactor(error-handling): improve error messages for missing and type mismatch parameters feat(loader): add strict parameter annotations for SQL file loading chore(dependencies): update package versions in lock file
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements #491 by adding
-- param:metadata support for SQL files. SQLSpec now parses declared parameters from loaded SQL, exposes those declarations for introspection, carries them onSQLobjects, and validates declared parameter contracts at load time and execute time.What changed
ParameterDeclarationmetadata for SQL-file parameters: name, declared type string, description, and required/optional status.-- param:parsing in SQL file headers alongside-- name:and-- dialect:.SQLFileLoader.get_query_parameters(),SQLSpec.get_query_parameters(), andSQL.declared_parameters.SQLobjects are rebuilt, copied, deep-copied, pickled, or prepared with filters.execute_many()dispatch.type?or trailing(optional)in the description.None, so SQL receivesNULL. Queries still need to express nullable behavior explicitly, for example(:status_cd is null or status_cd = :status_cd).Nonevalid for SQLNULLand skipped type checks forNonevalues.Declared type support
Declared types resolve through the existing core declared-parameter registry. Built-in declared types now include:
str,int,float,bool,bytesdate,datetime,time,Decimaluuid,uuid.UUIDdict,dict[str, Any]json,jsonblist,list[int],list[str],list[float],list[bool],tuplejsonandjsonbvalidate through SQLSpec's existing JSON serializer instead of a separate recursive JSON implementation. Unknown type strings remain documentation-only and skip runtime type validation. Custom matchers can still be registered withregister_param_type(...).Batch behavior
For
execute_many()with named mappings:Noneon each row.Positional placeholders remain arity-based and cannot be omitted by name.
Docs and examples
Updated SQL file loader docs and examples to cover:
NULLsemantics for omitted optional named parameters.Out of scope
Typed query artifact generation is not part of this PR. The follow-up brainstorming issue is #509 and covers possible generated runtime accessors,
.pyistubs, query-shape/result models,TypedDict,msgspec, Pydantic, import generation, and data-dictionary-backed result inference.Validation
Local verification performed on this branch:
uv run pytest tests/unit -qmake lintmake type-checkmake docsuv run pytest tests/unit/utils/test_mypyc_inventory.py tests/unit/utils/test_mypyc_smoke.py -qenv HATCH_BUILD_HOOKS_ENABLE=1 uv buildThe mypyc-enabled build produced a compiled wheel containing compiled extensions for the touched compiled modules:
sqlspec/loader,sqlspec/core/statement,sqlspec/core/parameters/_declared, andsqlspec/driver/_common.make testwas also run. It failed only in Oracle Docker fixture setup with a RootlessKit port bind conflict (address already in use); the run reported9374 passed, 367 skipped, 2 xfailed, 308 Oracle setup errors.Closes #491