Skip to content

Commit 82add78

Browse files
committed
Bug fix: include view columns in pg_catalog.pg_attribute
1 parent de170ef commit 82add78

2 files changed

Lines changed: 91 additions & 0 deletions

File tree

server/tables/pgcatalog/pg_attribute.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@ import (
1818
"io"
1919
"math"
2020

21+
"github.com/dolthub/dolt/go/libraries/doltcore/sqlserver"
2122
"github.com/dolthub/go-mysql-server/sql"
2223

2324
"github.com/dolthub/doltgresql/core/id"
25+
pgparser "github.com/dolthub/doltgresql/postgres/parser/parser"
26+
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
2427
"github.com/dolthub/doltgresql/server/functions"
2528
"github.com/dolthub/doltgresql/server/tables"
2629
pgtypes "github.com/dolthub/doltgresql/server/types"
@@ -81,6 +84,15 @@ func cachePgAttributes(ctx *sql.Context, pgCatalogCache *pgCatalogCache) error {
8184
attrelidIdx := NewUniqueInMemIndexStorage[*pgAttribute](lessAttNum)
8285
attrelidAttnameIdx := NewUniqueInMemIndexStorage[*pgAttribute](lessAttName)
8386

87+
// Get the engine for resolving view column types. May be nil in some test environments.
88+
type queryAnalyzer interface {
89+
AnalyzeQuery(*sql.Context, string) (sql.Node, error)
90+
}
91+
var engine queryAnalyzer
92+
if runningServer := sqlserver.GetRunningServer(); runningServer != nil && runningServer.Engine != nil {
93+
engine = runningServer.Engine
94+
}
95+
8496
err := functions.IterateCurrentDatabase(ctx, functions.Callbacks{
8597
Table: func(ctx *sql.Context, _ functions.ItemSchema, table functions.ItemTable) (cont bool, err error) {
8698
for i, col := range table.Item.Schema(ctx) {
@@ -122,6 +134,56 @@ func cachePgAttributes(ctx *sql.Context, pgCatalogCache *pgCatalogCache) error {
122134
}
123135
return true, nil
124136
},
137+
View: func(ctx *sql.Context, _ functions.ItemSchema, view functions.ItemView) (cont bool, err error) {
138+
if engine == nil {
139+
return true, nil
140+
}
141+
142+
// Get the SELECT body from the view definition.
143+
selectBody := view.Item.TextDefinition
144+
if selectBody == "" {
145+
stmts, parseErr := pgparser.Parse(view.Item.CreateViewStatement)
146+
if parseErr != nil || len(stmts) == 0 {
147+
return true, nil
148+
}
149+
cv, ok := stmts[0].AST.(*tree.CreateView)
150+
if !ok {
151+
return true, nil
152+
}
153+
selectBody = cv.AsSource.String()
154+
}
155+
if selectBody == "" {
156+
return true, nil
157+
}
158+
159+
// Analyze the SELECT statement to get the view's output schema.
160+
analyzed, analyzeErr := engine.AnalyzeQuery(ctx, selectBody)
161+
if analyzeErr != nil {
162+
return true, nil
163+
}
164+
165+
for i, col := range analyzed.Schema(ctx) {
166+
typeOid := id.Null
167+
if doltgresType, ok := col.Type.(*pgtypes.DoltgresType); ok {
168+
typeOid = doltgresType.ID.AsId()
169+
} else {
170+
dt := pgtypes.FromGmsType(col.Type)
171+
typeOid = dt.ID.AsId()
172+
}
173+
174+
attr := &pgAttribute{
175+
attrelid: view.OID.AsId(),
176+
attrelidNative: id.Cache().ToOID(view.OID.AsId()),
177+
attname: col.Name,
178+
atttypid: typeOid,
179+
attnum: int16(i + 1),
180+
}
181+
attrelidIdx.Add(attr)
182+
attrelidAttnameIdx.Add(attr)
183+
attributes = append(attributes, attr)
184+
}
185+
return true, nil
186+
},
125187
})
126188
if err != nil {
127189
return err

testing/go/pgcatalog_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,35 @@ order by 1,2;`,
279279
})
280280
}
281281

282+
func TestPgAttributeViewColumns(t *testing.T) {
283+
RunScripts(t, []ScriptTest{
284+
{
285+
Name: "pg_attribute includes view columns",
286+
SetUpScript: []string{
287+
`CREATE TABLE t (k int);`,
288+
`CREATE VIEW v AS SELECT * FROM t;`,
289+
},
290+
Assertions: []ScriptTestAssertion{
291+
{
292+
// View columns should appear in pg_attribute, just like table columns.
293+
Query: `SELECT c.relname, a.attname FROM pg_catalog.pg_class c JOIN pg_catalog.pg_attribute a ON a.attrelid=c.oid WHERE c.relname IN ('t','v') ORDER BY c.relname, a.attnum;`,
294+
Expected: []sql.Row{
295+
{"t", "k"},
296+
{"v", "k"},
297+
},
298+
},
299+
{
300+
// The view column's atttypid should match the underlying table column's type (int4 = 23).
301+
Query: `SELECT a.attname, a.atttypid FROM pg_catalog.pg_class c JOIN pg_catalog.pg_attribute a ON a.attrelid=c.oid WHERE c.relname = 'v' ORDER BY a.attnum;`,
302+
Expected: []sql.Row{
303+
{"k", uint32(23)},
304+
},
305+
},
306+
},
307+
},
308+
})
309+
}
310+
282311
func TestPgAttrdef(t *testing.T) {
283312
RunScripts(t, []ScriptTest{
284313
{

0 commit comments

Comments
 (0)