Skip to content

feat: type query placeholders by their SQL cast - #57

Merged
omer-cengel merged 3 commits into
masterfrom
feature/cast-typed-placeholders
Oct 1, 2026
Merged

omer-cengel merged 3 commits into
masterfrom
feature/cast-typed-placeholders

Conversation

@omer-cengel

Copy link
Copy Markdown
Member

Summary

Lets a query state a placeholder's type with a cast, so common idioms such as an optional filter (:name::text IS NULL OR name = :name), a computed pattern '%' || :term::text || '%', and SET bio = COALESCE(:bio::text, bio) compile as written. The cast type is resolved by the same declared-type mapping the schema parser uses, now extracted into a shared ColumnTypeMapping.

Changes

  • A $N or :name placeholder that is the direct operand of ::type or CAST(... AS type) anywhere inside WHERE, an INSERT value, or an UPDATE assignment takes the cast's type, including declared enums, char(n), and one-dimensional arrays such as tags && $1::varchar[].
  • A named cast placeholder keeps its own name. An indexed one keeps the column name an uncast placeholder would take in that position (compared, IN, BETWEEN, plain LIKE/ILIKE pattern, inserted, or assigned column), and is otherwise named param<N>. A cast pattern under NOT LIKE, SIMILAR TO, ESCAPE, or BINARY is accepted and named param<N>.
  • Occurrences of one placeholder may mix cast and uncast locations; conflicting types are rejected.
  • Rejected with diagnostics: an unmapped cast type such as $1::interval or $1::int[][], a cast placeholder in an unanalyzed location such as ORDER BY $1::int, a placeholder that is not the cast's direct operand, and quoted, qualified, or &name cast operands, which previously reached the executable SQL unreplaced.
  • The declared SQL type mapping moves from DefaultSchemaParser into ColumnTypeMapping without changing schema parsing.
  • Query documentation describes the accepted casts, naming, and rejections, with analyzer, compiler, generated-code, and PostgreSQL execution tests.

Scope and non-goals

  • Casts in projections, ORDER BY, HAVING, join conditions, pagination values, and subqueries stay rejected; computed or cast projections are not added.
  • The uncast LIKE pattern restrictions and their messages are unchanged, as are queries without casts.
  • Code generation and the runtime are unchanged.

A $N or :name placeholder that is the direct operand of ::type or
CAST(... AS type) inside WHERE, INSERT values, or UPDATE assignments takes
the cast's type, including declared enums and one-dimensional arrays. An
indexed cast placeholder keeps the column name an uncast one would take
and is otherwise named param<N>; a named one keeps its own name. Unmapped
cast types, casts in unanalyzed locations, and quoted, qualified, or &name
cast operands are rejected.
@omer-cengel
omer-cengel merged commit e110322 into master Oct 1, 2026
6 checks passed
@omer-cengel
omer-cengel deleted the feature/cast-typed-placeholders branch October 1, 2026 17:36
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