Skip to content

Add a Postgres dialect - #2

Merged
Makisuo merged 3 commits into
feat/dialect-seamfrom
feat/dialect-postgres
Oct 3, 2026
Merged

Makisuo merged 3 commits into
feat/dialect-seamfrom
feat/dialect-postgres

Conversation

@Makisuo

@Makisuo Makisuo commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #1. Finishes the dialect work: identifier quoting, clause differences, and a Postgres dialect that runs on real Postgres. The design notes, including where the plan changed, are in design/dialects.md; the user-facing guide is docs/postgres.md.

Identifiers through the dialect

Column refs, qualifiers, tables, aliases, CTE names and group/order keys render as Ident fragments that Dialect.quoteIdent quotes. A column ref carries its qualifier separately (ident("Name", "e")), so a ClickHouse Nested column like Events.Name is never split, while a db.table qualifier is quoted per segment. Untyped boolean and DateTime literals go through the dialect as well. ClickHouse writes names bare, so its output is byte-identical.

Clause differences as capability flags

Dialect.clauses holds what differs between the two databases:

  • format: .format() fails to compile where FORMAT does not exist, rather than being dropped silently.
  • derivedTableAlias: a wrapped union gets AS "__union".
  • groupByAlias: Postgres resolves a bare name in GROUP BY to an input column before a select alias, so select({ Service: lower($.Service) }).groupBy("Service") would group by the raw column. Postgres keys are written by position (GROUP BY 1), which keeps ClickHouse's meaning. There is a test that fails without this.

The plan was to move compile.ts into a ClickHouse dialect class. Three flags read in three places did the job without relocating ~1100 lines.

The ./postgres entry point

  • postgresDialect: double-quoted identifiers, standard string literals (an E'...' string only when a value spells the param marker, so the marker can be hex-escaped), $n binding. Dialect.paramCodecs makes param.bool bind a boolean and param.dateTime an ISO-8601 instant, which no session time zone can reinterpret.
  • Column types on the shared descriptor, with codecs that accept what common drivers send: int8 and numeric as number, string or bigint; timestamptz as a Date or text; jsonb parsed.
  • Functions Postgres spells differently: count(*), countIf/sumIf as FILTER (WHERE ...), percentile_cont, date_trunc(..., 'UTC'), date_bin, array_agg, ->>, coalesce.
  • compile / compileUnsafe / compileUnion / compileUnionUnsafe that default to Postgres.

Testing

src/pg/postgres.test.ts runs every query on PGlite (Postgres 17 in WASM, new dev dependency), so a test passes only if Postgres accepts the SQL, binds the params, and the rows decode through the declared types. It covers aggregates with FILTER, dateBin/dateTrunc buckets with Date and DateTime.Utc params, a join to a schema-qualified table over a subquery, a CTE, an ordered union, a value containing a quote, a backslash and the param marker (as a bound param, a literal, and a LIKE pattern), boolean binding, jsonb comparison, and the GROUP BY alias case.

  • tsc for the library and the tests
  • vitest run src/ch src/sql src/pg src/docs-examples.test.ts: 324 passed
  • bun run build, the doc-citation and export-catalog checks, and the doc-example check, which now runs the docs/postgres.md example on PGlite and asserts its rows
  • Not run: the benchmark tests and test:clickhouse (live ClickHouse via Docker)

Not in this PR

  • The root entry's ClickHouse functions (quantile, toStartOfInterval, map subscripts) still write ClickHouse SQL under any dialect; they could refuse to compile under another one instead.
  • The package name and root exports are unchanged.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Column refs, qualifiers, tables, aliases, CTE names and group/order keys
render as Ident fragments the dialect quotes. ClickHouse writes them bare,
so its output is unchanged. Untyped boolean and DateTime literals go
through the dialect too. Dialect.clauses records which clauses exist:
FORMAT fails to compile where it has no meaning, and a wrapped union gets
the derived-table alias some databases require. Dialect.paramCodecs lets
a dialect re-encode a portable param kind.
New ./postgres entry point: postgresDialect (double-quoted identifiers,
standard string literals, $n binding), Postgres column types whose codecs
accept what common drivers send, a function catalog for what Postgres
spells differently (count(*), FILTER (WHERE ...), percentile_cont,
date_trunc in UTC, date_bin, array_agg, ->>), and a compile that defaults
to Postgres.

Core changes it needed: Dialect.paramCodecs now re-encodes portable param
kinds (param.bool binds a boolean, param.dateTime an ISO instant), and
Dialect.clauses.groupByAlias writes GROUP BY keys by position where a bare
name would resolve to an input column first.

Tests run every query on PGlite, so they pass only if Postgres accepts
the SQL and the rows decode. ClickHouse output is unchanged.
@coderabbitai

coderabbitai Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 54428098-a414-44a2-aa7d-1c73a5b3a977
📥 Commits

Reviewing files that changed from the base of the PR and between ffa089d and 2abbdd4.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (24)
  • CHANGELOG.md
  • README.md
  • design/dialects.md
  • docs/README.md
  • docs/params-and-compilation.md
  • docs/postgres.md
  • docs/reference.md
  • package.json
  • scripts/check-doc-examples.mjs
  • src/ch/compile.ts
  • src/ch/dialect.test.ts
  • src/ch/dialect.ts
  • src/ch/expr.ts
  • src/ch/index.ts
  • src/ch/literal.ts
  • src/pg/dialect.ts
  • src/pg/functions.ts
  • src/pg/postgres.test.ts
  • src/pg/types.ts
  • src/postgres.ts
  • src/sql/literal-syntax.ts
  • src/sql/sql-fragment.ts
  • src/sql/sql-syntax.ts
  • tsdown.config.ts
 ________________________________________________________________________
< Brb...inventing the time machine to fix your code before you wrote it. >
 ------------------------------------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · 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 added this pull request to stack #3 October 3, 2026 13:25
@Makisuo
Makisuo merged commit e0cb976 into main Oct 3, 2026
3 of 4 checks passed
@Makisuo
Makisuo deleted the feat/dialect-postgres branch October 3, 2026 15:34
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