[Pg-kit]: Bind positional parameters over the AWS Data API - #6148
[Pg-kit]: Bind positional parameters over the AWS Data API#6148the-simian wants to merge 1 commit into
Conversation
394abb0 to
d6cea52
Compare
The Data API binds named parameters only, so `$N` placeholders forwarded to `ExecuteStatement` are never bound and Postgres reports `bind message supplies 0 parameters, but prepared statement requires N`. SQL that drizzle-orm generates already arrives in named form via `AwsPgDialect.escapeParam()`, but SQL reaching `query` and `proxy` does not come from the dialect; studio sends raw `$N`. Rewrite `$N` to `:N` before `prepareQuery`, skipping comments, strings, dollar-quoted bodies, and identifiers so dollar signs that are not placeholders survive untouched. Fixes drizzle-team#6147
d6cea52 to
7306909
Compare
|
@AleksandrSherman / @AndriiSherman following up from #2982: you asked for a minimal reproducible example and suggested trying This PR is the one defect that survives on What I did to keep this cheap to review:
I have not run the Docker-backed parts of the suite, and CI has not run here since workflow approval is gated for first-time contributors, so that is the gap in my own verification. Anything you want changed, I will turn around quickly. With no attachment to my choices: the fix could live in |
|
Here's the before/after proof to make this easier to thumb up: Verified against real Aurora Serverless v2 over the RDS Data API, since that is the surface this fixes. Same cluster, same database, same query, same probe, on Before, published After, with this PR's rewrite applied at the two The control passes in both runs, so the connection and the query were never the variable; only the binding was. An empty result is weak evidence on its own, so two more probes through the same studio proxy confirm the parameters are genuinely bound rather than merely tolerated: Values arrive intact and in the right positions. And the case the literal-aware scan exists for: The Reproduction, including the probe used above: https://github.com/simiancraft/drizzle-kit-rds-data-api-repro ( More rigorous testing: behavior is now covered by a differential fuzz I ran locally against PGlite: 400 generated statements mixing real placeholders with decoy That 400-statement harness is not part of this PR, (I assumed it would be overkill) but I am happy to contribute it if you would want it in the suite. Point is, this is the fix, I'm sure of it. |
Fixes #6147.
The problem
The RDS Data API binds named parameters (
:1) only. It never binds Postgres positional placeholders ($1), and reports the mismatch as:SQL that drizzle-orm generates already arrives in named form, because
AwsPgDialect.escapeParam()emits:${index + 1}. Studio does not go through the dialect: it forwards raw SQL carrying$Ntoproxy, and nothing translates it beforesession.prepareQuery.The result is that
drizzle-kit studiocannot run any parameterized query against a Data API connection.The change
prepareAwsDataApiSql(sql: string, parameterCount: number): stringindrizzle-kit/src/utils/aws-data-api-placeholders.ts, applied at both Data API adapter entry points incli/connections.ts.proxyis the path studio uses and the one that is broken today;querygets the same treatment for consistency, since the two are the same adapter and introspection on this branch happens to be parameter-free.It takes the parameter count rather than the parameter array because the count is its entire dependency: nothing in the rewrite inspects a value. That keeps the signature free of
anyandunknown.The rewrite is literal aware, because a blind
$Nreplace corrupts valid SQL. These spans are copied through untouched:''escapes,E'...\'...', and continuations across a newline (line comments included), which keep the escape-string property of the first segment;$$ ... $$,$tag$ ... $tag$), including an unterminated body, which runs to end of input the way Postgres would read it;"a $1 b");$after the first character.foo$1andé$1are each a single identifier, and$é$is a valid dollar-quote tag.Two deliberately conservative choices:
$Nwhere1 <= N <= parameterCountis rewritten. A stray$9in a two-parameter query is left for the server to reject rather than silently renamed.The one documented limitation is that it assumes
standard_conforming_stringsis on, which is the default and what Aurora ships.Tests
drizzle-kit/tests/other/aws-data-api-placeholders.test.ts, 30 cases covering every span type above plus the boundaries:$0, a lone$, a two-digit index, an index beyond the parameter count, a repeated placeholder, a placeholder at end of input, and a quote following an identifier ending ine(which is not an E-string).The lexical edge cases are not guesses. I checked each one against real Postgres via PGlite, the same engine this package already tests against, and the tests encode the observed behavior:
SELECT E'a'\n'it\'s'ait's, so a continuation keeps escape-string semanticsSELECT E'a' -- c\n'it\'s'ait's, so a line comment is continuation whitespace, and a block comment there is a syntax errorSELECT 'a' 'b'on one lineSELECT 1 AS a --c<CR>, 2 AS bSELECT 1 AS é$1é$1SELECT $é$ hi $é$SELECT $1abctrailing junk after parameter, so it is not a placeholder to preserveThe function is pure, so these need no database and no AWS account; they run in
tests/otherwith the rest of the Docker-free suite.What I verified, and what I did not
The 30 tests pass inside this package, on Node 24.19.0 with pnpm 10.15.0:
The change adds no new failures to the Docker-free
tests/othersubset. Run onbetaat748058eand on this branch, the same 17 tests fail in the same files on both sides; the only difference is the passing tests this PR adds.dprint checkis clean on all three files.The module typechecks under
--strict --noUncheckedIndexedAccess, which is stricter thandrizzle-kit/tsconfig.json. Noany, no casts, no non-null assertions.Behavior was verified end to end against Aurora Serverless v2 (PostgreSQL 15.12) over the Data API, using a standalone reproduction: https://github.com/simiancraft/drizzle-kit-rds-data-api-repro. Its probe sends the same query twice, once without parameters and once with one, so the control isolates the failure to binding rather than to the query or the connection.
I did not run the full
drizzle-kitsuite, since the rest of it stands up Docker-backed databases I cannot run here. Happy to iterate if CI surfaces anything.I put this in drizzle-kit because
escapeParam()is already correct andAwsDataApiSessionis not the thing that is wrong. If you would rather the translation live in drizzle-orm's session so every consumer inherits it, say so and I will move it.