Repository navigation
duckdb: col.field of a STRUCT column does not resolve, and alias.col.field is typed as the whole struct #4655
Description
Version
main at commit 3ad8b20 (2026-10-23), built with CGO_ENABLED=0 go install github.com/sqlc-dev/sqlc/cmd/sqlc@3ad8b20fb02b5bea81591b673da0758b8685d951.
sqlc version prints v1.31.1 because internal/info/facts.go hard-codes it.
The DuckDB engine was added in #4621 and is not in a tagged release.
What happened?
Reading a field of a STRUCT column does not work like it does in DuckDB.
- The bare form
info.labelfails withunknown column "info"."label". DuckDB accepts it. - The form
i.info.labelparses, but the result has the type of the struct column. The field name is ignored. - A parameter that is compared with
i.info.scoregets the nameinfoand the typeany. The expected type is a float. - The generated code compiles. It fails when it scans a row (see the output).
sqlc generategives no warning.
A workaround exists: CAST(i.info.label AS VARCHAR) gives sql.NullString.
What DuckDB v1.5.6 reports for the same queries (DESCRIBE): VARCHAR for info.label, i.info.label,
struct_extract(info, 'label') and info['label'].
Expected: the field resolves, and the type is the type of the field (nullable varchar for label).
Cause
typeColumnRef in internal/core/analyzer/expr.go (line 152 at this commit) reads only parts[0] and parts[1]
of the column reference. For i.info.label it resolves relation i and column info, and drops label.
For info.label it looks for a relation named info, finds none, and reports an unknown column.
sqlc analyze already shows the wrong type, so the fault is in the analyzer and not in code generation.
The struct's field types are available: the column's TypeExpr is struct(label: varchar, score: double).
Reproduction
Without Go code (only the sqlc binary):
repro1a.sh
#!/usr/bin/env bash
# No Go code needed: only the sqlc binary.
rm -rf repro1a && mkdir repro1a && cd repro1a
cat > schema.sql <<'EOF'
CREATE TABLE items (
id INTEGER NOT NULL,
info STRUCT(label VARCHAR, score DOUBLE)
);
EOF
while IFS= read -r q; do
printf -- '-- name: Q :many\n%s\n' "$q" > query.sql
echo "== $q"
sqlc analyze --dialect duckdb --schema schema.sql query.sql 2>&1 | grep -E '"(name|nullable)"|Error' | grep -v '"name": "Q"'
done <<'EOF'
SELECT info.label AS label FROM items;
SELECT i.info.label AS label FROM items AS i;
SELECT CAST(i.info.label AS VARCHAR) AS label FROM items AS i;
EOFOutput:
== SELECT info.label AS label FROM items;
Error: error parsing queries: query.sql:1:1: unknown column "info"."label"
== SELECT i.info.label AS label FROM items AS i;
"name": "label",
"name": "struct",
"nullable": true,
"name": "varchar"
"name": "double"
== SELECT CAST(i.info.label AS VARCHAR) AS label FROM items AS i;
"name": "label",
"name": "varchar",
"nullable": true
With the generated code, run on DuckDB:
repro1b.sh (needs Go 1.27+ and a C compiler)
#!/usr/bin/env bash
# Needs Go 1.27+ and a C compiler (duckdb-go v2.10506.0).
rm -rf repro1b && mkdir repro1b && cd repro1b
cat > sqlc.yaml <<'EOF'
version: "2"
sql:
- engine: "duckdb"
schema: "schema.sql"
queries: "query.sql"
gen:
go:
package: "repro"
out: "out"
EOF
cat > schema.sql <<'EOF'
CREATE TABLE items (
id INTEGER NOT NULL,
info STRUCT(label VARCHAR, score DOUBLE)
);
EOF
while IFS= read -r q; do
printf -- '-- name: Q :many\n%s\n' "$q" > query.sql; rm -rf out
echo "== $q"; sqlc generate 2>&1 | head -3; grep -hE '^func \(q \*Queries\) Q' out/query.sql.go 2>/dev/null
done <<'EOF'
SELECT i.info.label AS label FROM items AS i;
SELECT id FROM items AS i WHERE i.info.score > $1;
SELECT CAST(i.info.label AS VARCHAR) AS label FROM items AS i;
EOF
echo; echo "== run the code generated for the first query"
printf -- '-- name: Q :many\nSELECT i.info.label AS label FROM items AS i;\n' > query.sql
rm -rf out && sqlc generate
cat > main.go <<'EOF'
package main
import (
"context"
"database/sql"
"fmt"
_ "github.com/duckdb/duckdb-go/v2"
"example.com/repro/out"
)
func main() {
db, err := sql.Open("duckdb", "")
if err != nil {
panic(err)
}
defer db.Close()
db.SetMaxOpenConns(1)
if _, err := db.Exec(`CREATE TABLE items (id INTEGER NOT NULL, info STRUCT(label VARCHAR, score DOUBLE));
INSERT INTO items VALUES (1, {'label': 'a', 'score': 1.5});`); err != nil {
panic(err)
}
var s string
_ = db.QueryRow("SELECT i.info.label FROM items AS i").Scan(&s)
fmt.Printf("DuckDB returns a VARCHAR: %q\n", s)
rows, err := repro.New(db).Q(context.Background())
fmt.Println("generated Q():", rows, err)
}
EOF
printf 'module example.com/repro\n\ngo 1.27\n' > go.mod
go get github.com/duckdb/duckdb-go/v2@v2.10506.0 >/dev/null 2>&1
go mod tidy >/dev/null 2>&1
go run .Fix
I have a patch with an end-to-end test case, and I will open a pull request.
Not covered
struct_extract(info, 'label') is still typed as struct, and info['label'] is still untyped.
They go through function and subscript typing, which is a different cause. I did not change them.
Relevant log output
== SELECT i.info.label AS label FROM items AS i;
func (q *Queries) Q(ctx context.Context) ([]*map[string]any, error) {
== SELECT id FROM items AS i WHERE i.info.score > $1;
func (q *Queries) Q(ctx context.Context, info any) ([]int32, error) {
== SELECT CAST(i.info.label AS VARCHAR) AS label FROM items AS i;
func (q *Queries) Q(ctx context.Context) ([]sql.NullString, error) {
== run the code generated for the first query
DuckDB returns a VARCHAR: "a"
generated Q(): [] sql: Scan error on column index 0, name "label": unsupported Scan, storing driver.Value type string into type *map[string]interface {}Database schema
CREATE TABLE items (
id INTEGER NOT NULL,
info STRUCT(label VARCHAR, score DOUBLE)
);SQL queries
SELECT info.label AS label FROM items;
SELECT i.info.label AS label FROM items AS i;
SELECT id FROM items AS i WHERE i.info.score > $1;
SELECT CAST(i.info.label AS VARCHAR) AS label FROM items AS i;Configuration
version: "2"
sql:
- engine: "duckdb"
schema: "schema.sql"
queries: "query.sql"
gen:
go:
package: "repro"
out: "out"Playground URL
Not available. play.sqlc.dev runs only v1.18.0–v1.25.0, and the DuckDB engine is not in a release (v1.25.0 answers unknown engine: duckdb; main, a commit and every later release return HTTP 500). The report has a script that needs only go install github.com/sqlc-dev/sqlc/cmd/sqlc@3ad8b20fb02b5bea81591b673da0758b8685d951.
What operating system are you using?
Linux
What database engines are you using?
(none of the listed options; the engine is duckdb)
What type of code are you generating?
Go
Activity