From 5d94812ea788cf662db932da2f0a8fb8f4b90bd6 Mon Sep 17 00:00:00 2001 From: Ryo Kusnadi Date: Fri, 6 Nov 2026 08:20:32 +0700 Subject: [PATCH] duckdb: resolve the rowid pseudo-column Signed-off-by: Ryo Kusnadi --- .../testdata/duckdb_rowid/duckdb/exec.json | 3 + .../testdata/duckdb_rowid/duckdb/go/db.go | 31 ++++ .../testdata/duckdb_rowid/duckdb/go/models.go | 22 +++ .../duckdb_rowid/duckdb/go/query.sql.go | 169 ++++++++++++++++++ .../testdata/duckdb_rowid/duckdb/query.sql | 17 ++ .../testdata/duckdb_rowid/duckdb/schema.sql | 10 ++ .../testdata/duckdb_rowid/duckdb/sqlc.yaml | 10 ++ internal/engine/duckdb/convert.go | 19 ++ 8 files changed, 281 insertions(+) create mode 100644 internal/endtoend/testdata/duckdb_rowid/duckdb/exec.json create mode 100644 internal/endtoend/testdata/duckdb_rowid/duckdb/go/db.go create mode 100644 internal/endtoend/testdata/duckdb_rowid/duckdb/go/models.go create mode 100644 internal/endtoend/testdata/duckdb_rowid/duckdb/go/query.sql.go create mode 100644 internal/endtoend/testdata/duckdb_rowid/duckdb/query.sql create mode 100644 internal/endtoend/testdata/duckdb_rowid/duckdb/schema.sql create mode 100644 internal/endtoend/testdata/duckdb_rowid/duckdb/sqlc.yaml diff --git a/internal/endtoend/testdata/duckdb_rowid/duckdb/exec.json b/internal/endtoend/testdata/duckdb_rowid/duckdb/exec.json new file mode 100644 index 0000000000..2e996ca79d --- /dev/null +++ b/internal/endtoend/testdata/duckdb_rowid/duckdb/exec.json @@ -0,0 +1,3 @@ +{ + "contexts": ["base"] +} diff --git a/internal/endtoend/testdata/duckdb_rowid/duckdb/go/db.go b/internal/endtoend/testdata/duckdb_rowid/duckdb/go/db.go new file mode 100644 index 0000000000..32faa9f2b8 --- /dev/null +++ b/internal/endtoend/testdata/duckdb_rowid/duckdb/go/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...any) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...any) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...any) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/internal/endtoend/testdata/duckdb_rowid/duckdb/go/models.go b/internal/endtoend/testdata/duckdb_rowid/duckdb/go/models.go new file mode 100644 index 0000000000..eb659bed85 --- /dev/null +++ b/internal/endtoend/testdata/duckdb_rowid/duckdb/go/models.go @@ -0,0 +1,22 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "database/sql" + + "github.com/google/uuid" +) + +type Plan struct { + ID uuid.UUID + ShipSize string + Price sql.NullFloat64 +} + +type Tag struct { + Rowid string + Name string +} diff --git a/internal/endtoend/testdata/duckdb_rowid/duckdb/go/query.sql.go b/internal/endtoend/testdata/duckdb_rowid/duckdb/go/query.sql.go new file mode 100644 index 0000000000..60c8097898 --- /dev/null +++ b/internal/endtoend/testdata/duckdb_rowid/duckdb/go/query.sql.go @@ -0,0 +1,169 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: query.sql + +package querytest + +import ( + "context" + "database/sql" + + "github.com/google/uuid" +) + +const byRowID = `-- name: ByRowID :one +SELECT id FROM plan WHERE rowid = $1; +` + +func (q *Queries) ByRowID(ctx context.Context, rowid sql.NullInt64) (uuid.UUID, error) { + row := q.db.QueryRowContext(ctx, byRowID, rowid) + var id uuid.UUID + err := row.Scan(&id) + return id, err +} + +const declaredRowID = `-- name: DeclaredRowID :many +SELECT rowid, name FROM tag; +` + +func (q *Queries) DeclaredRowID(ctx context.Context) ([]Tag, error) { + rows, err := q.db.QueryContext(ctx, declaredRowID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Tag + for rows.Next() { + var i Tag + if err := rows.Scan(&i.Rowid, &i.Name); 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 orderByRowID = `-- name: OrderByRowID :many +SELECT id FROM plan ORDER BY rowid; +` + +func (q *Queries) OrderByRowID(ctx context.Context) ([]uuid.UUID, error) { + rows, err := q.db.QueryContext(ctx, orderByRowID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []uuid.UUID + for rows.Next() { + var id uuid.UUID + if err := rows.Scan(&id); err != nil { + return nil, err + } + items = append(items, id) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const starSkipsRowID = `-- name: StarSkipsRowID :many +SELECT id, ship_size, price FROM plan; +` + +func (q *Queries) StarSkipsRowID(ctx context.Context) ([]Plan, error) { + rows, err := q.db.QueryContext(ctx, starSkipsRowID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Plan + for rows.Next() { + var i Plan + if err := rows.Scan(&i.ID, &i.ShipSize, &i.Price); 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 withQualifiedRowID = `-- name: WithQualifiedRowID :many +SELECT p.rowid, p.id FROM plan p; +` + +type WithQualifiedRowIDRow struct { + Rowid sql.NullInt64 + ID uuid.UUID +} + +func (q *Queries) WithQualifiedRowID(ctx context.Context) ([]WithQualifiedRowIDRow, error) { + rows, err := q.db.QueryContext(ctx, withQualifiedRowID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []WithQualifiedRowIDRow + for rows.Next() { + var i WithQualifiedRowIDRow + if err := rows.Scan(&i.Rowid, &i.ID); 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 withRowID = `-- name: WithRowID :many +SELECT rowid, id FROM plan; +` + +type WithRowIDRow struct { + Rowid sql.NullInt64 + ID uuid.UUID +} + +func (q *Queries) WithRowID(ctx context.Context) ([]WithRowIDRow, error) { + rows, err := q.db.QueryContext(ctx, withRowID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []WithRowIDRow + for rows.Next() { + var i WithRowIDRow + if err := rows.Scan(&i.Rowid, &i.ID); 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 +} diff --git a/internal/endtoend/testdata/duckdb_rowid/duckdb/query.sql b/internal/endtoend/testdata/duckdb_rowid/duckdb/query.sql new file mode 100644 index 0000000000..ecef5609c9 --- /dev/null +++ b/internal/endtoend/testdata/duckdb_rowid/duckdb/query.sql @@ -0,0 +1,17 @@ +-- name: WithRowID :many +SELECT rowid, id FROM plan; + +-- name: WithQualifiedRowID :many +SELECT p.rowid, p.id FROM plan p; + +-- name: ByRowID :one +SELECT id FROM plan WHERE rowid = $1; + +-- name: OrderByRowID :many +SELECT id FROM plan ORDER BY rowid; + +-- name: StarSkipsRowID :many +SELECT * FROM plan; + +-- name: DeclaredRowID :many +SELECT rowid, name FROM tag; diff --git a/internal/endtoend/testdata/duckdb_rowid/duckdb/schema.sql b/internal/endtoend/testdata/duckdb_rowid/duckdb/schema.sql new file mode 100644 index 0000000000..f5c58e5da5 --- /dev/null +++ b/internal/endtoend/testdata/duckdb_rowid/duckdb/schema.sql @@ -0,0 +1,10 @@ +CREATE TABLE plan ( + id UUID NOT NULL, + ship_size VARCHAR NOT NULL, + price DOUBLE +); + +CREATE TABLE tag ( + rowid VARCHAR NOT NULL, + name VARCHAR NOT NULL +); diff --git a/internal/endtoend/testdata/duckdb_rowid/duckdb/sqlc.yaml b/internal/endtoend/testdata/duckdb_rowid/duckdb/sqlc.yaml new file mode 100644 index 0000000000..07d6d9d3b8 --- /dev/null +++ b/internal/endtoend/testdata/duckdb_rowid/duckdb/sqlc.yaml @@ -0,0 +1,10 @@ +version: "2" +sql: + - engine: "duckdb" + schema: "schema.sql" + queries: "query.sql" + gen: + go: + package: "querytest" + out: "go" + sql_package: "database/sql" diff --git a/internal/engine/duckdb/convert.go b/internal/engine/duckdb/convert.go index 29721bb60d..03a01113d8 100644 --- a/internal/engine/duckdb/convert.go +++ b/internal/engine/duckdb/convert.go @@ -1061,6 +1061,25 @@ func (c *cc) convertCreateTableInfo(info *dw.CreateTableInfo) ast.Node { for i := range info.Columns { stmt.Cols = append(stmt.Cols, c.convertColumnDef(&info.Columns[i], primaryKey)) } + + // Every base table also has a rowid pseudo-column. It is a nullable + // BIGINT that queries can select, filter and sort on, but it is not part + // of the table's columns. A column the table declares as rowid takes + // precedence. + hasRowID := false + for _, col := range stmt.Cols { + if col.Colname == "rowid" { + hasRowID = true + break + } + } + if !hasRowID { + stmt.Cols = append(stmt.Cols, &ast.ColumnDef{ + Colname: "rowid", + IsHidden: true, + TypeName: &ast.TypeName{Name: "bigint"}, + }) + } return stmt }