Skip to content

Commit cf988cd

Browse files
committed
fix: resolver fails to look up CTE columns
Closes #4228. The resolver fails to infer the type of a CTE column because it only searches normal tables. My solution is to have two passes, one where we search the CTE columns first (making them shadow regular tables) then if not found, we search through regular tables. We cannot simply add the CTE column in the existing `typeMap` because a CTE and a regular table will have the same column name which will trigger the `found > 1` check and the column will be considered ambiguous. So we use a different typeMap for CTEs so we can separate the search
1 parent b84b1d6 commit cf988cd

9 files changed

Lines changed: 184 additions & 7 deletions

File tree

internal/compiler/resolve.go

Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ func (comp *Compiler) resolveCatalogRefs(qc *QueryCatalog, rvs []*ast.RangeVar,
2828
// TODO: Deprecate defaultTable
2929
var defaultTable *ast.TableName
3030
var tables []*ast.TableName
31+
var ctes []*ast.TableName
3132

33+
typeMapCte := map[string]map[string]map[string]*catalog.Column{}
3234
typeMap := map[string]map[string]map[string]*catalog.Column{}
3335
indexTable := func(table catalog.Table) error {
3436
tables = append(tables, table.Rel)
@@ -67,9 +69,35 @@ func (comp *Compiler) resolveCatalogRefs(qc *QueryCatalog, rvs []*ast.RangeVar,
6769
continue
6870
}
6971
// If the table name doesn't exist, first check if it's a CTE
70-
if _, qcerr := qc.GetTable(fqn); qcerr != nil {
72+
cte, qcerr := qc.GetTable(fqn)
73+
if qcerr != nil {
7174
return nil, err
7275
}
76+
// duplicated logic from indexTable()
77+
schema := fqn.Schema
78+
if schema == "" {
79+
schema = c.DefaultSchema
80+
}
81+
if _, exists := typeMapCte[schema]; !exists {
82+
typeMapCte[schema] = map[string]map[string]*catalog.Column{}
83+
}
84+
typeMapCte[schema][fqn.Name] = map[string]*catalog.Column{}
85+
for _, col := range cte.Columns {
86+
cc := &catalog.Column{
87+
Name: col.Name,
88+
IsNotNull: col.NotNull,
89+
IsUnsigned: col.Unsigned,
90+
IsArray: col.IsArray,
91+
ArrayDims: col.ArrayDims,
92+
Comment: col.Comment,
93+
Length: col.Length,
94+
}
95+
if col.Type != nil {
96+
cc.Type = *col.Type
97+
}
98+
typeMapCte[schema][fqn.Name][col.Name] = cc
99+
}
100+
ctes = append(ctes, fqn)
73101
continue
74102
}
75103
err = indexTable(table)
@@ -195,7 +223,61 @@ func (comp *Compiler) resolveCatalogRefs(qc *QueryCatalog, rvs []*ast.RangeVar,
195223
panic("too many field items: " + strconv.Itoa(len(items)))
196224
}
197225

198-
search := tables
226+
search := ctes
227+
if alias != "" {
228+
if _, ok := aliasMap[alias]; ok {
229+
// alias maps to a real table, skip CTE search entirely
230+
search = []*ast.TableName{}
231+
}
232+
}
233+
234+
// resolve using ctes first
235+
var found int
236+
for _, table := range search {
237+
schema := table.Schema
238+
if schema == "" {
239+
schema = c.DefaultSchema
240+
}
241+
if c, ok := typeMapCte[schema][table.Name][key]; ok {
242+
found += 1
243+
if ref.name != "" {
244+
key = ref.name
245+
}
246+
247+
defaultP := named.NewInferredParam(key, c.IsNotNull)
248+
p, isNamed := params.FetchMerge(ref.ref.Number, defaultP)
249+
a = append(a, Parameter{
250+
Number: ref.ref.Number,
251+
Column: &Column{
252+
Name: p.Name(),
253+
OriginalName: c.Name,
254+
DataType: dataType(&c.Type),
255+
NotNull: p.NotNull(),
256+
Unsigned: c.IsUnsigned,
257+
IsArray: c.IsArray,
258+
ArrayDims: c.ArrayDims,
259+
Length: c.Length,
260+
Table: table,
261+
IsNamedParam: isNamed,
262+
IsSqlcSlice: p.IsSqlcSlice(),
263+
},
264+
})
265+
}
266+
}
267+
268+
if found == 1 {
269+
continue
270+
}
271+
if found > 1 {
272+
return nil, &sqlerr.Error{
273+
Code: "42703",
274+
Message: fmt.Sprintf("column reference %q is ambiguous", key),
275+
Location: node.Location,
276+
}
277+
}
278+
279+
280+
search = tables
199281
if alias != "" {
200282
if original, ok := aliasMap[alias]; ok {
201283
search = []*ast.TableName{original}
@@ -217,7 +299,9 @@ func (comp *Compiler) resolveCatalogRefs(qc *QueryCatalog, rvs []*ast.RangeVar,
217299
}
218300
}
219301

220-
var found int
302+
303+
// resolve using regular tables
304+
found = 0
221305
for _, table := range search {
222306
schema := table.Schema
223307
if schema == "" {
@@ -286,6 +370,7 @@ func (comp *Compiler) resolveCatalogRefs(qc *QueryCatalog, rvs []*ast.RangeVar,
286370
schema = c.DefaultSchema
287371
}
288372

373+
// should we use also look in CTEs?
289374
if c, ok := typeMap[schema][table.Name][key]; ok {
290375
defaultP := named.NewInferredParam(key, c.IsNotNull)
291376
p, isNamed := params.FetchMerge(ref.ref.Number, defaultP)
@@ -475,6 +560,7 @@ func (comp *Compiler) resolveCatalogRefs(qc *QueryCatalog, rvs []*ast.RangeVar,
475560
schema = c.DefaultSchema
476561
}
477562

563+
// should we use also look in CTEs?
478564
tableMap, ok := typeMap[schema][rel]
479565
if !ok {
480566
return nil, sqlerr.RelationNotFound(rel)
@@ -583,6 +669,7 @@ func (comp *Compiler) resolveCatalogRefs(qc *QueryCatalog, rvs []*ast.RangeVar,
583669
}
584670
}
585671

672+
// should we use also look in CTEs?
586673
for _, table := range search {
587674
schema := table.Schema
588675
if schema == "" {

internal/endtoend/testdata/cte_left_join/postgresql/pgx/go/query.sql.go

Lines changed: 2 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
https://github.com/sqlc-dev/sqlc/issues/4288

internal/endtoend/testdata/cte_renamed_column/postgresql/go/db.go

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/endtoend/testdata/cte_renamed_column/postgresql/go/models.go

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/endtoend/testdata/cte_renamed_column/postgresql/go/query.sql.go

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-- name: GetUser :one
2+
WITH found AS (
3+
SELECT id, id AS id2 FROM users WHERE id = $1
4+
)
5+
SELECT id2 + $2 AS result FROM found;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-- Schema
2+
CREATE TABLE users (
3+
id INT PRIMARY KEY,
4+
name TEXT NOT NULL
5+
);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
version: "2"
2+
sql:
3+
- engine: "postgresql"
4+
schema: "schema.sql"
5+
queries: "query.sql"
6+
gen:
7+
go:
8+
package: "querytest"
9+
out: "go"
10+
sql_package: "pgx/v5"

0 commit comments

Comments
 (0)