GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — fix(duckdb): rewrite sqlc.arg and @name to native placeholders by xen0l · Pull Request #4664 · sqlc-dev/sqlc

Conversation

@xen0l

@xen0l xen0l commented Oct 24, 2026

Copy link
Copy Markdown

Fixes #4658

Problem

The preprocess dialect table (internal/sql/preprocess/dialect.go) had no entry for DuckDB, and an engine with no entry gets its source back unchanged. sqlc.arg(), sqlc.narg() and @name then reached the analyzer as function calls and column names (unknown column "want_id"), or failed in the parser (@).

Change

  • Add a config.EngineDuckDB entry: $1 placeholders, @name as sqlc syntax, $$ strings, $1 as a bind parameter. A bare ? is left to the engine, as before.
  • Add a NoSlice dialect flag and set it for DuckDB. sqlc.slice is rejected with sqlc.slice is not supported by this engine; bind a list parameter instead.
  • Add a DuckDB row to the dialect table in internal/sql/preprocess/CLAUDE.md.

Why sqlc.slice is rejected: with $1 placeholders the generated Go still calls strings.Replace on a ? marker that the SQL does not contain. The call changes nothing, so the query binds only the first element of the slice and returns wrong rows without an error. I ran id IN (sqlc.slice(ids)) with []int32{1, 3} on DuckDB and got one row. A DuckDB user binds a list with x = ANY($1::INTEGER[]) or list_contains($1::INTEGER[], x); I ran both with []int32{1, 3} and each returned the right two rows.
The PostgreSQL engine generates the same IN ($1) and the same no-op Replace today. I did not change that here.

Tests

New end-to-end cases:

  • duckdb_named_params/duckdb (sqlc generate, output committed): sqlc.arg, sqlc.narg, @name, and two sqlc.arg in one query. It fails without the dialect entry (Parser Error: syntax error at or near "@") and passes with it.
  • analyze_slice_rejected/duckdb (sqlc analyze, stderr.txt committed): sqlc.slice must fail. It fails without the NoSlice flag and passes with it.

go test ./internal/endtoend -run TestReplay/base: 971 pass, 58 skip, 0 fail before; 973 pass, 58 skip, 0 fail after. The only differences are the two new cases.
go test ./internal/engine/... ./internal/core/... ./internal/compiler/... ./internal/codegen/... ./internal/cmd/... ./internal/metadata/... ./internal/sql/...: all ok.

I ran the generated Go of the first case on DuckDB v1.5.6 (duckdb-go/v2 v2.10506.0): ByID(2), ByAt(3), ByNullableID(4) and Range(1,4) each returned the expected rows.

Not run: the managed-db context, --tags=examples, and the internal/goldeneye checks.

Notes

  • A query that mixes sqlc.arg(a) with an explicit $2 fails with could not determine data type of parameter $1. The PostgreSQL and SQLite engines give the same error for the same mix, so I left it.
  • I set the lexical flags from DuckDB's documented syntax. I did not set NestedBlockComment, because I could not confirm whether the DuckDB parser nests block comments. Please check it.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@xen0l
fix(duckdb): rewrite sqlc.arg and @name to native placeholders
c42104e
The preprocess dialect table had no entry for DuckDB, and an engine with
no entry gets its source back unchanged. sqlc.arg(), sqlc.narg() and
@name then reached the analyzer as function calls and column names.

Add a DuckDB entry that uses $1 placeholders.

Reject sqlc.slice for this dialect. With $1 placeholders the generated Go
still calls strings.Replace on a "?" marker that the SQL does not
contain, so it binds only the first element of the list and returns wrong
rows without an error. A list parameter (list_contains or ANY) is the
DuckDB way to do this.

Adds the duckdb_named_params and analyze_slice_rejected cases.
@xen0l

xen0l commented Nov 10, 2026

Copy link
Copy Markdown
Author

@kyleconroy Could you review this fix for #4658 when you have time, please? Thanks!

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

duckdb: sqlc.arg(), sqlc.narg() and @name are not rewritten

Uh oh!

There was an error while loading. Please reload this page.

1 participant

@xen0l
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.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.