From abaeec6ed8ced1c488c05a4ccf05dc22d316fa22 Mon Sep 17 00:00:00 2001 From: alexrjones Date: Tue, 20 Jan 2026 11:34:48 +0800 Subject: [PATCH 1/3] Index CTEs when resolving queries --- internal/compiler/resolve.go | 38 ++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/internal/compiler/resolve.go b/internal/compiler/resolve.go index b1fbb1990e..1b05bc9bac 100644 --- a/internal/compiler/resolve.go +++ b/internal/compiler/resolve.go @@ -21,6 +21,7 @@ func dataType(n *ast.TypeName) string { } } +// resolveCatalogRefs func (comp *Compiler) resolveCatalogRefs(qc *QueryCatalog, rvs []*ast.RangeVar, args []paramRef, params *named.ParamSet, embeds rewrite.EmbedSet) ([]Parameter, error) { c := comp.catalog @@ -67,10 +68,14 @@ func (comp *Compiler) resolveCatalogRefs(qc *QueryCatalog, rvs []*ast.RangeVar, continue } // If the table name doesn't exist, first check if it's a CTE - if _, qcerr := qc.GetTable(fqn); qcerr != nil { + qctab, qcerr := qc.GetTable(fqn) + if qcerr != nil { return nil, err } - continue + table = catalog.Table{ + Rel: qctab.Rel, + Columns: convertCompilerColumnsToCatalogColumns(qctab.Columns), + } } err = indexTable(table) if err != nil { @@ -635,3 +640,32 @@ func (comp *Compiler) resolveCatalogRefs(qc *QueryCatalog, rvs []*ast.RangeVar, } return a, nil } + +func convertCompilerColumnsToCatalogColumns(qccols []*Column) []*catalog.Column { + ret := make([]*catalog.Column, len(qccols)) + for i, v := range qccols { + ret[i] = convertCompilerColumnToCatalogColumn(v) + } + return ret +} + +func convertCompilerColumnToCatalogColumn(qccol *Column) *catalog.Column { + + var tn ast.TypeName + if qccol.Type != nil { + tn = *qccol.Type + } else { + tn = ast.TypeName{Name: qccol.DataType} + } + + return &catalog.Column{ + Name: qccol.Name, + Type: tn, + IsNotNull: qccol.NotNull, + IsUnsigned: qccol.Unsigned, + IsArray: qccol.IsArray, + ArrayDims: qccol.ArrayDims, + Comment: qccol.Comment, + Length: qccol.Length, + } +} From 25d7f2d17b01c7e5c95c326ef5168160b08909d6 Mon Sep 17 00:00:00 2001 From: alexrjones Date: Tue, 20 Jan 2026 11:49:04 +0800 Subject: [PATCH 2/3] Update tests --- .../postgresql/pgx/go/query.sql.go | 6 +- .../testdata/cte_recursive_join/issue.md | 1 + .../postgresql/pgx/exec.json | 3 + .../postgresql/pgx/go/db.go | 32 ++++++++++ .../postgresql/pgx/go/models.go | 17 +++++ .../postgresql/pgx/go/query.sql.go | 62 +++++++++++++++++++ .../postgresql/pgx/query.sql | 20 ++++++ .../postgresql/pgx/schema.sql | 10 +++ .../postgresql/pgx/sqlc.yaml | 10 +++ .../cte_update/postgresql/pgx/go/query.sql.go | 6 +- 10 files changed, 160 insertions(+), 7 deletions(-) create mode 100644 internal/endtoend/testdata/cte_recursive_join/issue.md create mode 100644 internal/endtoend/testdata/cte_recursive_join/postgresql/pgx/exec.json create mode 100644 internal/endtoend/testdata/cte_recursive_join/postgresql/pgx/go/db.go create mode 100644 internal/endtoend/testdata/cte_recursive_join/postgresql/pgx/go/models.go create mode 100644 internal/endtoend/testdata/cte_recursive_join/postgresql/pgx/go/query.sql.go create mode 100644 internal/endtoend/testdata/cte_recursive_join/postgresql/pgx/query.sql create mode 100644 internal/endtoend/testdata/cte_recursive_join/postgresql/pgx/schema.sql create mode 100644 internal/endtoend/testdata/cte_recursive_join/postgresql/pgx/sqlc.yaml diff --git a/internal/endtoend/testdata/cte_left_join/postgresql/pgx/go/query.sql.go b/internal/endtoend/testdata/cte_left_join/postgresql/pgx/go/query.sql.go index 564b33b190..4c087d16df 100644 --- a/internal/endtoend/testdata/cte_left_join/postgresql/pgx/go/query.sql.go +++ b/internal/endtoend/testdata/cte_left_join/postgresql/pgx/go/query.sql.go @@ -7,8 +7,6 @@ package querytest import ( "context" - - "github.com/jackc/pgx/v5/pgtype" ) const badQuery = `-- name: BadQuery :exec @@ -28,7 +26,7 @@ FROM WHERE c1.name = $1 ` -func (q *Queries) BadQuery(ctx context.Context, dollar_1 pgtype.Text) error { - _, err := q.db.Exec(ctx, badQuery, dollar_1) +func (q *Queries) BadQuery(ctx context.Context, name string) error { + _, err := q.db.Exec(ctx, badQuery, name) return err } diff --git a/internal/endtoend/testdata/cte_recursive_join/issue.md b/internal/endtoend/testdata/cte_recursive_join/issue.md new file mode 100644 index 0000000000..3bc77bd241 --- /dev/null +++ b/internal/endtoend/testdata/cte_recursive_join/issue.md @@ -0,0 +1 @@ +https://github.com/sqlc-dev/sqlc/issues/3924 \ No newline at end of file diff --git a/internal/endtoend/testdata/cte_recursive_join/postgresql/pgx/exec.json b/internal/endtoend/testdata/cte_recursive_join/postgresql/pgx/exec.json new file mode 100644 index 0000000000..ee1b7ecd9e --- /dev/null +++ b/internal/endtoend/testdata/cte_recursive_join/postgresql/pgx/exec.json @@ -0,0 +1,3 @@ +{ + "contexts": ["managed-db"] +} diff --git a/internal/endtoend/testdata/cte_recursive_join/postgresql/pgx/go/db.go b/internal/endtoend/testdata/cte_recursive_join/postgresql/pgx/go/db.go new file mode 100644 index 0000000000..1e00549714 --- /dev/null +++ b/internal/endtoend/testdata/cte_recursive_join/postgresql/pgx/go/db.go @@ -0,0 +1,32 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package querytest + +import ( + "context" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" +) + +type DBTX interface { + Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) + Query(context.Context, string, ...interface{}) (pgx.Rows, error) + QueryRow(context.Context, string, ...interface{}) pgx.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx pgx.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/internal/endtoend/testdata/cte_recursive_join/postgresql/pgx/go/models.go b/internal/endtoend/testdata/cte_recursive_join/postgresql/pgx/go/models.go new file mode 100644 index 0000000000..972ee49b8f --- /dev/null +++ b/internal/endtoend/testdata/cte_recursive_join/postgresql/pgx/go/models.go @@ -0,0 +1,17 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package querytest + +import ( + "github.com/jackc/pgx/v5/pgtype" +) + +type UserReferral struct { + UserID int64 + ReferrerID int64 + Balance pgtype.Numeric + CreatedAt pgtype.Timestamptz + UpdatedAt pgtype.Timestamptz +} diff --git a/internal/endtoend/testdata/cte_recursive_join/postgresql/pgx/go/query.sql.go b/internal/endtoend/testdata/cte_recursive_join/postgresql/pgx/go/query.sql.go new file mode 100644 index 0000000000..9385a2ef2f --- /dev/null +++ b/internal/endtoend/testdata/cte_recursive_join/postgresql/pgx/go/query.sql.go @@ -0,0 +1,62 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: query.sql + +package querytest + +import ( + "context" +) + +const getReferralChain = `-- name: GetReferralChain :many +WITH RECURSIVE referral_chain AS ( + SELECT user_id, + referrer_id, + 1 AS level + FROM user_referrals r + WHERE r.user_id = $1 + + UNION ALL + + SELECT r.user_id, + r.referrer_id, + rc.level + 1 + FROM user_referrals r + JOIN referral_chain rc ON r.user_id = rc.referrer_id + WHERE rc.level < $2 +) +SELECT user_id, referrer_id, level +FROM referral_chain +` + +type GetReferralChainParams struct { + UserID int64 + Level int32 +} + +type GetReferralChainRow struct { + UserID int64 + ReferrerID int64 + Level int32 +} + +func (q *Queries) GetReferralChain(ctx context.Context, arg GetReferralChainParams) ([]GetReferralChainRow, error) { + rows, err := q.db.Query(ctx, getReferralChain, arg.UserID, arg.Level) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetReferralChainRow + for rows.Next() { + var i GetReferralChainRow + if err := rows.Scan(&i.UserID, &i.ReferrerID, &i.Level); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/internal/endtoend/testdata/cte_recursive_join/postgresql/pgx/query.sql b/internal/endtoend/testdata/cte_recursive_join/postgresql/pgx/query.sql new file mode 100644 index 0000000000..1c04ec9589 --- /dev/null +++ b/internal/endtoend/testdata/cte_recursive_join/postgresql/pgx/query.sql @@ -0,0 +1,20 @@ +-- name: GetReferralChain :many +WITH RECURSIVE referral_chain AS ( + SELECT user_id, + referrer_id, + 1 AS level + FROM user_referrals r + WHERE r.user_id = $1 + + UNION ALL + + SELECT r.user_id, + r.referrer_id, + rc.level + 1 + FROM user_referrals r + JOIN referral_chain rc ON r.user_id = rc.referrer_id + WHERE rc.level < @level +) +SELECT * +FROM referral_chain +; \ No newline at end of file diff --git a/internal/endtoend/testdata/cte_recursive_join/postgresql/pgx/schema.sql b/internal/endtoend/testdata/cte_recursive_join/postgresql/pgx/schema.sql new file mode 100644 index 0000000000..e044c78de0 --- /dev/null +++ b/internal/endtoend/testdata/cte_recursive_join/postgresql/pgx/schema.sql @@ -0,0 +1,10 @@ +CREATE TABLE user_referrals +( + user_id bigint NOT NULL + CONSTRAINT user_referrals_pk + PRIMARY KEY, + referrer_id bigint NOT NULL, + balance numeric(38, 9) DEFAULT 0.00 NOT NULL, + created_at timestamp with time zone DEFAULT NOW() NOT NULL, + updated_at timestamp with time zone DEFAULT NOW() NOT NULL +); \ No newline at end of file diff --git a/internal/endtoend/testdata/cte_recursive_join/postgresql/pgx/sqlc.yaml b/internal/endtoend/testdata/cte_recursive_join/postgresql/pgx/sqlc.yaml new file mode 100644 index 0000000000..01489e0ffc --- /dev/null +++ b/internal/endtoend/testdata/cte_recursive_join/postgresql/pgx/sqlc.yaml @@ -0,0 +1,10 @@ +version: "2" +sql: + - engine: "postgresql" + schema: "schema.sql" + queries: "query.sql" + gen: + go: + package: "querytest" + out: "go" + sql_package: "pgx/v5" \ No newline at end of file diff --git a/internal/endtoend/testdata/cte_update/postgresql/pgx/go/query.sql.go b/internal/endtoend/testdata/cte_update/postgresql/pgx/go/query.sql.go index 61ba601b90..8e8ff6238b 100644 --- a/internal/endtoend/testdata/cte_update/postgresql/pgx/go/query.sql.go +++ b/internal/endtoend/testdata/cte_update/postgresql/pgx/go/query.sql.go @@ -23,9 +23,9 @@ from updated_attribute ` type UpdateAttributeParams struct { - FilterValue pgtype.Bool - Value pgtype.Text - ID pgtype.Int8 + FilterValue bool + Value string + ID int64 } type UpdateAttributeRow struct { From 8155f97e783c9fc29d5d7035bfd69960be13749e Mon Sep 17 00:00:00 2001 From: alexrjones Date: Tue, 20 Jan 2026 11:55:49 +0800 Subject: [PATCH 3/3] Remove comment --- internal/compiler/resolve.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/compiler/resolve.go b/internal/compiler/resolve.go index 1b05bc9bac..a2c80b9ac8 100644 --- a/internal/compiler/resolve.go +++ b/internal/compiler/resolve.go @@ -21,7 +21,6 @@ func dataType(n *ast.TypeName) string { } } -// resolveCatalogRefs func (comp *Compiler) resolveCatalogRefs(qc *QueryCatalog, rvs []*ast.RangeVar, args []paramRef, params *named.ParamSet, embeds rewrite.EmbedSet) ([]Parameter, error) { c := comp.catalog