Skip to content

fix(postgres): type the two placeholders Postgres would mistype - #45

Merged
Makisuo merged 2 commits into
fix/ch-array-literal-castfrom
fix/pg-typed-params
Oct 23, 2026
Merged

Makisuo merged 2 commits into
fix/ch-array-literal-castfrom
fix/pg-typed-params

Conversation

@Makisuo

@Makisuo Makisuo commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Postgres types an untyped $n from its context, which went wrong twice:

  • param.of(type) in a select list bound as text: a boolean came back as 'false', an array as '{}', and decoding failed. It now binds as $n::<type> ($1::boolean, $2::text[]), exact for the column it belongs to.
  • A fractional value compared with an integer column was typed int8: "Amount" > $1 with 9.75 failed invalid input syntax for type bigint. Fractional bound values cast to numeric; whole numbers stay uncast so integer comparisons keep their index.

param.of kinds were keyed by codec alone, and types share codecs (PG.int8/PG.float8, PG.text/CH.string), so a kind could carry another type's SQL; they are keyed by codec and SQL type. The dialect's placeholder hook now receives the encoded value. One SQL snapshot changes: param.of(PG.text) → $1::text.

Also in the fuzz model: ClickHouse float % is not IEEE fmod; the upstream ClickHouse and() bug matcher covers nested ORs. That ClickHouse 25.8 bug (WHERE a AND ((b AND a) OR a) → TOO_FEW_ARGUMENTS_FOR_FUNCTION, avoided by optimize_extract_common_expressions = 0) is upstream and stays in known-issues.ts.

Verification: PGlite tests in bound-literals.test.ts; full suite with ClickHouse live; fuzz at 30× with only the upstream issue left in the backlog — 58/58 green.


Stack (merge bottom-up):

  1. test(fuzz): property-based fuzzer for the query builder #39 test(fuzz): property-based fuzzer for the query builder
  2. fix(clickhouse): write number literals that read back exactly #40 fix(clickhouse): write number literals that read back exactly
  3. fix: parenthesise an arithmetic expression passed as an argument #41 fix: parenthesise an arithmetic expression passed as an argument
  4. fix: fail with QueryBuilderError instead of throwing on unusable values #42 fix: fail with QueryBuilderError instead of throwing on unusable values
  5. fix: bare select() over a subquery or union selects every alias #43 fix: bare select() over a subquery or union selects every alias
  6. fix(clickhouse): cast array and map comparison literals to the column type #44 fix(clickhouse): cast array and map comparison literals to the column type
  7. fix(postgres): type the two placeholders Postgres would mistype #45 fix(postgres): type the two placeholders Postgres would mistype

🤖 Generated with Claude Code

Postgres types an untyped `$n` from its context, which went wrong twice:

- a `param.of(type)` selected on its own has no context, so it bound as
  text: a boolean came back as 'false', an array as '{}', and decoding
  failed. It is now `CAST($n AS <type>)` in the select list.
- a fractional value compared with an integer column was typed int8 and
  failed with `invalid input syntax for type bigint`. It now binds as
  `$n::numeric`.

Nothing else is cast, so whole numbers and params compared with columns
keep using indexes, `real` columns still match, and an insert of a fraction
into an integer column is still refused rather than rounded.

`param.of` kinds were keyed by codec alone, and types share codecs
(`PG.int8`/`PG.float8`), so a kind could carry another type's SQL; they are
keyed by codec and SQL type now.

Fuzz model: ClickHouse float `%` is not IEEE fmod, and the matcher for the
upstream ClickHouse and()-absorption bug covers nested ORs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Makisuo
Makisuo added this pull request to stack #46 October 23, 2026 00:18
@coderabbitai

coderabbitai Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 3 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: d3faf328-2c04-4b9f-9e87-f18934150077
📥 Commits

Reviewing files that changed from the base of the PR and between 4384826 and 2ba116c.

📒 Files selected for processing: 7
  • .changeset/pg-typed-placeholders.md
  • src/ch/compile.ts
  • src/ch/expr.ts
  • src/ch/param.ts
  • src/pg/bound-literals.test.ts
  • tests/fuzz/known-issues.ts
  • tests/fuzz/model.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Makisuo Makisuo changed the title fix(postgres): type the two placeholders Postgres would mistype fix(clickhouse): cast array and map comparison literals to the column type Oct 23, 2026
@Makisuo Makisuo changed the title fix(clickhouse): cast array and map comparison literals to the column type fix(postgres): type the two placeholders Postgres would mistype Oct 23, 2026
@Makisuo
Makisuo merged commit 4f690ed into main Oct 23, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant