feat(dialect): report whether placeholders are positional - #4
Merged
Conversation
Closes #3. `Dialect` described placeholder syntax but not the consequence: whether a bind can be referenced more than once. That does not matter here -- `transpile` binds each literal once and never points at an earlier one -- but it decides correctness for a caller splicing a fragment that names one bind from two places. sqlx-aip's key-set cursor predicate pins each more-significant column in every clause after the first, so two ordering fields bind two values on Postgres and three on SQLite for the same predicate, and getting it wrong shifts the page rather than raising anything. Defaulted, so no existing impl breaks, and the default is the inference sqlx-aip was making from outside: render two adjacent indices and compare them. That handles the awkward middle case -- SQLite's numbered `?1` form is positional in syntax but addressable, renders the two differently, and is correctly reported as not positional. The three built-ins answer directly instead, and a test pins the default against them: without it nothing would notice the inference drifting away from the explicit answers, which is what a custom dialect gets.
iamralch
added a commit
to sqlx-contrib/sqlx-aip
that referenced
this pull request
Sep 5, 2026
Follow-up to sqlx-contrib/sqlx-cel#4, which added Dialect::is_positional. The local inference in cursor.rs goes away -- the trait answers it now. The comparison the local helper made is the trait's default, so nothing in the test suite changes, including the custom ?1 dialect, which does not override the method and so exercises that default and gets the same answer.
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.
Closes #3.
Dialectdescribed placeholder syntax but not the consequence that followsfrom it: whether a bind can be referenced more than once.
That does not matter to this crate —
transpilebinds each literal once andnever points at an earlier one — but it decides correctness for a caller
splicing a fragment that names one bind from two places.
sqlx-aip's key-set cursor predicate
pins each more-significant column in every clause after the first:
Two ordering fields bind two values on Postgres and three on SQLite for the same
predicate. Getting it wrong raises nothing — the binds shift by one and the page
silently resumes from the wrong row.
The change
Defaulted, so no existing
Dialectimpl breaks, and the default is exactly theinference sqlx-aip was making from outside. It handles the awkward middle case:
a dialect emitting SQLite's numbered
?1/?2form is positional in syntax butstill addressable, renders the two differently, and is correctly reported as
not positional.
The three built-ins answer directly rather than rendering anything, and the
blanket
impl Dialect for &Dforwards it — a missed forward there would looklike a silent fallback to the default rather than a compile error, so a test
calls through a reference.
One test pins the default against the three explicit answers. Without it nothing
would notice the inference drifting away from what the built-ins say, and the
inference is what every non-overriding custom dialect gets.
Testing
58 lib tests (+3), clippy and fmt clean across
sqlite,mysql.