Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion internal/core/analyzer/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,11 @@ func (a *analyzer) typeColumnRef(c *ast.ColumnRef) (exprType, error) {
if err != nil {
return exprType{}, err
}
// Parts after relation.column name fields of a struct column.
var fields []string
if ok && len(parts) > 2 {
fields = parts[2:]
}
// A dotted name may be a column's own, as ClickHouse names the columns
// a Nested column stores as n.a.
if !ok && relation != "" {
Expand All @@ -172,6 +177,17 @@ func (a *analyzer) typeColumnRef(c *ast.ColumnRef) (exprType, error) {
return exprType{}, err
}
}
// Otherwise the first part may be a struct column and the rest its
// fields: info.label, when info is no relation in scope.
if !ok && relation != "" {
rel, col, ok, err = a.resolveColumn("", relation)
if err != nil {
return exprType{}, err
}
if ok {
fields = parts[1:]
}
}
if !ok {
if relation != "" {
return exprType{}, fmt.Errorf("unknown column %q.%q", relation, column)
Expand All @@ -183,7 +199,33 @@ func (a *analyzer) typeColumnRef(c *ast.ColumnRef) (exprType, error) {
}
return exprType{}, fmt.Errorf("unknown column %q", column)
}
return columnType(rel, col), nil
t := columnType(rel, col)
for _, field := range fields {
f, ok := a.structField(t, field)
if !ok {
return exprType{}, fmt.Errorf("unknown field %q of column %q", field, col.Name)
}
t = f
}
return t, nil
}

// structField is the type of a field of a struct-typed expression. A field
// is never a column of a relation, so the result carries no source column,
// and it is nullable because a struct field can hold NULL.
func (a *analyzer) structField(t exprType, field string) (exprType, bool) {
e := a.exprOf(t)
if e == nil || e.Name != "struct" {
return exprType{}, false
}
for _, arg := range e.Args {
if arg.Type != nil && strings.EqualFold(arg.Label, field) {
out := a.lookupType(arg.Type)
out.nullable = true
return out, true
}
}
return exprType{}, false
}

func flattenFields(fields *ast.List) []string {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"contexts": ["base"]}
31 changes: 31 additions & 0 deletions internal/endtoend/testdata/duckdb_struct_field/duckdb/go/db.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions internal/endtoend/testdata/duckdb_struct_field/duckdb/go/models.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

124 changes: 124 additions & 0 deletions internal/endtoend/testdata/duckdb_struct_field/duckdb/go/query.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions internal/endtoend/testdata/duckdb_struct_field/duckdb/query.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-- name: Bare :many
SELECT info.label FROM items;

-- name: Qualified :many
SELECT i.info.label AS label, i.info.score AS score FROM items AS i;

-- name: Filtered :many
SELECT id FROM items AS i WHERE i.info.score > $1;

-- name: CaseInsensitive :many
SELECT info.LABEL AS label FROM items;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CREATE TABLE items (
id INTEGER NOT NULL,
info STRUCT(label VARCHAR, score DOUBLE, tags VARCHAR[])
);
10 changes: 10 additions & 0 deletions internal/endtoend/testdata/duckdb_struct_field/duckdb/sqlc.yaml
Original file line number Diff line number Diff line change
@@ -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"
Loading