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(sqlite): keep parameter in LIKE ... ESCAPE ? by manuelschoene · Pull Request #4675 · sqlc-dev/sqlc

Conversation

@manuelschoene

@manuelschoene manuelschoene commented Nov 4, 2026

Copy link
Copy Markdown

Fixes #4668

Problem

With the SQLite engine, a parameter in the ESCAPE clause of LIKE is dropped from the generated code. The query text keeps every placeholder, but the generated method passes one argument too few:

-- name: SearchName :many
SELECT id FROM t WHERE name LIKE ? ESCAPE ?;
// before
func (q *Queries) SearchName(ctx context.Context, name string) ([]int64, error) {
	rows, err := q.db.QueryContext(ctx, searchName, name)

What happens at runtime depends on how the parameters are written:

  • ? placeholders: the query fails with sql: expected 2 arguments, got 1.
  • Named parameters (@pattern ESCAPE @esc, sqlc.arg(...)): these are rewritten to ?1/?2, and the driver does not count arguments for those. ?2 is bound to NULL, LIKE … ESCAPE NULL evaluates to NULL, and the query silently returns no rows.
  • Parameters after the escape (… ESCAPE ? AND score > ?): the arguments shift, so score is bound in the ESCAPE position.

Both the default analyzer and the core analyzer (SQLCEXPERIMENT=coreanalyzer) are affected. An escape given as a literal (ESCAPE '!') already worked.

Cause

The SQLite parser reads the escape into LikeExpr.Escape, but convertLikeExpr (internal/engine/sqlite/convert.go) only converted the two operands. ast.A_Expr had no field for an escape, so the parameter was gone before parameter discovery ran.

Changes

  • internal/sql/ast/a_expr.go: add A_Expr.Escape, and print ESCAPE <expr> for the pattern-match operators in Format. The PostgreSQL engine never sets the field, because pg_query folds the escape into the pattern as like_escape(p, e).
  • internal/engine/sqlite/convert.go: convert n.Escape into the new field.
  • internal/sql/astutils/walk.go, rewrite.go: visit Escape, so parameter discovery and named-parameter rewriting reach it.
  • internal/compiler/find_params.go, resolve.go (default analyzer): record an escape parameter with its own parent type. Otherwise it would be typed and named after the matched column. It is now always text, named escape unless the user names it.
  • internal/core/analyzer/expr.go (core analyzer): type the escape operand the same way (text, named escape by default), and count it towards the nullability of the result.

After the fix:

type SearchNameParams struct {
	Name   string
	Escape string
}

type SearchThenWhereParams struct {
	Name   string
	Escape string
	Score  int64
}

Tests

Two new end-to-end cases share the same schema and queries:

  • internal/endtoend/testdata/like_escape/sqlite/ covers the default analyzer.
  • internal/endtoend/testdata/like_escape_core/sqlite/ covers the core analyzer; its exec.json sets SQLCEXPERIMENT=coreanalyzer.

Both paths generate identical code. The cases cover:

  • positional ? parameters;
  • named parameters (@pattern ESCAPE @esc);
  • NOT LIKE;
  • a parameter only in the ESCAPE position;
  • a literal escape with a parameter pattern;
  • a parameter after the escape (… ESCAPE ? AND score > ?).

I ran the test suite without databases (go test ./...), and it passes. I did not run the tests that need a live PostgreSQL or MySQL server (the managed-db context and the examples). The change only affects the SQLite converter and the shared AST code, and the PostgreSQL and MySQL cases still generate unchanged under base.

Sorry, something went wrong.

Uh oh!

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

All reactions
manuelschoene added 2 commits November 3, 2026 15:28
@manuelschoene
fix: Parse ESCAPE clause parameters
2f9b49c
Added a escape field to the AST expression struct. This field now stores the node for the ESCAPE clause and gets walked. The SQLite converter fills in the field. Both the default and core analyzer got a new case for recording the ESCAPE parameters.
@manuelschoene
test: LIKE ESCAPE end-to-end tests with multiple paramters
b4f74ee
Added end-to-end tests with different combinations of parameters inside the ESCAPE clause. like_escape uses the default analyzer while like_escape_core uses the core analyzer. Both tests contain the same test cases but use the different analyzers.
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.

SQLite: parameter in LIKE ... ESCAPE ? is dropped from generated code

Uh oh!

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

1 participant

@manuelschoene
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.