Skip to content

SQL queries are truncated when Chinese comments are present #4235

Description

@aitsuki

Version

1.30.0

What happened?

When SQL files contain Chinese (or other multi-byte UTF-8) comments, sqlc incorrectly truncates the generated SQL query constants in the Go code. The truncation occurs at or near the position of the Chinese characters, resulting in incomplete SQL statements.

Reproduce

app_item.sql

-- name: CreateMissingAppItemsForAllApp :exec
-- 当新建MasterItem时,需要为所有App创建相应的AppItem
INSERT INTO app_items (app_id, master_item_id)
SELECT a.id, ? FROM apps a
WHERE NOT EXISTS (
    SELECT 1 FROM app_items ai 
    WHERE ai.app_id = a.id AND ai.master_item_id = ?
);

-- name: ResetAppItemStatusByMasterItem :exec
-- 当修改MasterItem后需要同步给所有AppItem
UPDATE app_items
SET status = 'pending',
    remark = '',
    last_synced_at = CURRENT_TIMESTAMP
WHERE master_item_id = ?;


-- name: CreateAppItemsForApp :exec
-- 当新建App时,需要为该APP创建所有AppItem
INSERT INTO app_items (app_id, master_item_id)
SELECT ?, id FROM master_items;

-- name: UpdateAppItem :exec
UPDATE app_items
SET status = ?,
    remark = ?,
    updated_at = CURRENT_TIMESTAMP
WHERE id = ?;

-- name: ListAppItemsByAppID :many
SELECT 
    ai.id,
    ai.app_id,
    ai.master_item_id,
    m.title,
    m.description,
    ai.status,
    ai.remark,
    ai.last_synced_at,
    ai.updated_at
FROM app_items ai
JOIN master_items m ON ai.master_item_id = m.id
WHERE ai.app_id = ?
ORDER BY ai.id;

-- name: GetAppItemByID :one
SELECT * FROM app_items
WHERE id = ?;

-- name: CountPendingByAppID :one
SELECT COUNT(*) as count
FROM app_items
WHERE app_id = ?
AND status = 'pending';

app_item.sql.go

// Code generated by sqlc. DO NOT EDIT.
// versions:
//   sqlc v1.30.0
// source: app_item.sql

package database

import (
	"context"
	"time"
)

const createAppItemsForApp = `-- name: CreateAppItemsForApp :exec
ced_at = CURRENT_TIMESTAMP
WHERE master_item_id = ?;


`

// 当新建App时,需要为该APP创建所有AppItem
func (q *Queries) CreateAppItemsForApp(ctx context.Context, appID int64) error {
	_, err := q.db.ExecContext(ctx, createAppItemsForApp, appID)
	return err
}

const createMissingAppItemsForAllApp = `-- name: CreateMissingAppItemsForAllApp :exec
INSERT INTO app_items (app_id, master_item_id)
SELECT a.id, ? FROM apps a
WHERE NOT EXISTS (
    SELECT 1 FROM app_items ai 
    WHERE ai.app_id = a.
`

// 当新建MasterItem时,需要为所有App创建相应的AppItem
func (q *Queries) CreateMissingAppItemsForAllApp(ctx context.Context, masterItemID int64) error {
	_, err := q.db.ExecContext(ctx, createMissingAppItemsForAllApp, masterItemID)
	return err
}

const getAppItemByID = `-- name: GetAppItemByID :one
BY ai.id;

SELECT * FROM app_items
WHERE id = ?;

S
`

func (q *Queries) GetAppItemByID(ctx context.Context, appID int64) (int64, error) {
	row := q.db.QueryRowContext(ctx, getAppItemByID, appID)
	var count int64
	err := row.Scan(&count)
	return count, err
}

const listAppItemsByAppID = `-- name: ListAppItemsByAppID :many
T status = ?,
    remark = ?,
    updated_at = CURRENT_TIMESTAMP
WHERE id = ?;

SELECT 
    ai.id,
    ai.app_id,
    ai.master_item_id,
    m.title,
    m.description,
    ai.status,
    ai.remark,
    ai.last_synced_at,
    ai.updated_at
FROM app_items ai
JOIN
`

type ListAppItemsByAppIDRow struct {
	ID           int64
	AppID        int64
	MasterItemID int64
	Title        string
	Description  string
	Status       string
	Remark       string
	LastSyncedAt time.Time
	UpdatedAt    time.Time
}

func (q *Queries) ListAppItemsByAppID(ctx context.Context, appID int64) ([]ListAppItemsByAppIDRow, error) {
	rows, err := q.db.QueryContext(ctx, listAppItemsByAppID, appID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var items []ListAppItemsByAppIDRow
	for rows.Next() {
		var i ListAppItemsByAppIDRow
		if err := rows.Scan(
			&i.ID,
			&i.AppID,
			&i.MasterItemID,
			&i.Title,
			&i.Description,
			&i.Status,
			&i.Remark,
			&i.LastSyncedAt,
			&i.UpdatedAt,
		); err != nil {
			return nil, err
		}
		items = append(items, i)
	}
	if err := rows.Close(); err != nil {
		return nil, err
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return items, nil
}

const resetAppItemStatusByMasterItem = `-- name: ResetAppItemStatusByMasterItem :exec
d AND ai.master_item_id = ?
);

UPDATE app_items
SET status = 'pending',
    remark = '',
    last_sy
`

// 当修改MasterItem后需要同步给所有AppItem
func (q *Queries) ResetAppItemStatusByMasterItem(ctx context.Context, masterItemID int64) error {
	_, err := q.db.ExecContext(ctx, resetAppItemStatusByMasterItem, masterItemID)
	return err
}

const updateAppItem = `-- name: UpdateAppItem :exec
INSERT INTO app_items (app_id, master_item_id)
SELECT ?, id FROM master_items;

UPDATE app_items
S
`

type UpdateAppItemParams struct {
	Status string
	Remark string
	ID     int64
}

func (q *Queries) UpdateAppItem(ctx context.Context, arg UpdateAppItemParams) error {
	_, err := q.db.ExecContext(ctx, updateAppItem, arg.Status, arg.Remark, arg.ID)
	return err
}

Note:Note: The content appears corrupted/mixed with other queries and incomplete.
Removing Chinese comments or using English comments will generate correct code.

Strangely, I did not reproduce this issue on another computer (using the same environment)

Relevant log output

Database schema

CREATE TABLE users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL UNIQUE,
    pass BLOB NOT NULL,
    admin BOOLEAN NOT NULL DEFAULT 0,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE master_items (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    title TEXT NOT NULL,
    description TEXT NOT NULL DEFAULT '',
    propose_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE apps (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL UNIQUE,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE app_items (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    app_id INTEGER NOT NULL,
    master_item_id INTEGER NOT NULL,
    status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'checked', 'ignored', 'publish')),
    publish_version TEXT NOT NULL DEFAULT '1.0.0',
    remark TEXT NOT NULL DEFAULT '',
    last_synced_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (app_id) REFERENCES apps(id) ON DELETE CASCADE,
    FOREIGN KEY (master_item_id) REFERENCES master_items(id) ON DELETE CASCADE,
    UNIQUE(app_id, master_item_id)
);
CREATE INDEX idx_app_items_app_id ON app_items(app_id);
CREATE INDEX idx_app_items_master_item_id ON app_items(master_item_id);
CREATE INDEX idx_app_items_status ON app_items(status);
CREATE INDEX idx_app_items_app_status ON app_items(app_id, status);

SQL queries

-- name: CreateMissingAppItemsForAllApp :exec
-- 当新建MasterItem时,需要为所有App创建相应的AppItem
INSERT INTO app_items (app_id, master_item_id)
SELECT a.id, ? FROM apps a
WHERE NOT EXISTS (
    SELECT 1 FROM app_items ai 
    WHERE ai.app_id = a.id AND ai.master_item_id = ?
);

-- name: ResetAppItemStatusByMasterItem :exec
-- 当修改MasterItem后需要同步给所有AppItem
UPDATE app_items
SET status = 'pending',
    remark = '',
    last_synced_at = CURRENT_TIMESTAMP
WHERE master_item_id = ?;


-- name: CreateAppItemsForApp :exec
-- 当新建App时,需要为该APP创建所有AppItem
INSERT INTO app_items (app_id, master_item_id)
SELECT ?, id FROM master_items;

-- name: UpdateAppItem :exec
UPDATE app_items
SET status = ?,
    remark = ?,
    updated_at = CURRENT_TIMESTAMP
WHERE id = ?;

-- name: ListAppItemsByAppID :many
SELECT 
    ai.id,
    ai.app_id,
    ai.master_item_id,
    m.title,
    m.description,
    ai.status,
    ai.remark,
    ai.last_synced_at,
    ai.updated_at
FROM app_items ai
JOIN master_items m ON ai.master_item_id = m.id
WHERE ai.app_id = ?
ORDER BY ai.id;

-- name: GetAppItemByID :one
SELECT * FROM app_items
WHERE id = ?;

-- name: CountPendingByAppID :one
SELECT COUNT(*) as count
FROM app_items
WHERE app_id = ?
AND status = 'pending';

Configuration

version: "2"
sql:
  - engine: "sqlite"
    queries: "queries"
    schema: "migrations"
    gen:
      go:
        package: "database"
        out: "database"

Playground URL

No response

What operating system are you using?

Windows

What database engines are you using?

SQLite

What type of code are you generating?

Go

Activity

  1. darthShadow commented on Feb 23, 2026

    @darthShadow

    I encountered a similar issue with Em dash i.e. U+2014 characters too.

  2. skupri commented on Mar 29, 2026

    @skupri

    I have similar problem under Linux.
    Non ASCII symbols in comment before query cuts generated SQL query body.
    More symbols - more cuted.
    Looks like only SQLite generator affected and PostgreSQL works fine.

    query.sql:

    -- name: KVTest :many
    -- comment
    select key
    from kv;

    query.sql.go:

    const kVTest = `-- name: KVTest :many
    select key
    from kv
    `

    query.sql:

    -- name: KVTest :many
    -- comment ФЫЙЦ
    select key
    from kv;

    query.sql.go

    const kVTest = `-- name: KVTest :many
    select key
    fro
    `

    query.sql:

    -- name: KVTest :many
    -- comment ФЫЙЦФЫЙЦ
    select key
    from kv;

    query.sql.go

    const kVTest = `-- name: KVTest :many
    select key
    `
  3. exploded commented on May 7, 2026

    @exploded

    I hit the same bug with em-dashes in SQL comments and traced it to the root cause: ANTLR returns character indices (1 unit per character) but sqlc consumes them as byte offsets when slicing the source. Any multi-byte UTF-8 character in a comment before a query shifts the cut position by (byte_length - 1) per character — which is why your Cyrillic example truncates by exactly 4 bytes for ФЫЙЦ (4 chars × 2 bytes = 8 bytes, minus 4 chars treated as 4 bytes = 4 extra bytes lost from the query body).
    I opened #4373 with a fix that converts the ANTLR character indices to byte offsets, plus tests covering em-dash, Cyrillic, and other multi-byte cases. It also resolves my own report in #4372. Would appreciate a review from a maintainer when there's bandwidth.

  4. added a commit that references this issue on Aug 18, 2026
    49950e4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions