Skip to content
Merged
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
76 changes: 74 additions & 2 deletions server/functions/format_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@
package functions

import (
"bytes"
"fmt"

"github.com/dolthub/go-mysql-server/sql"
"github.com/lib/pq/oid"

"github.com/dolthub/doltgresql/core"
"github.com/dolthub/doltgresql/core/id"

"github.com/dolthub/doltgresql/postgres/parser/lex"
"github.com/dolthub/doltgresql/postgres/parser/types"
"github.com/dolthub/doltgresql/server/functions/framework"
pgtypes "github.com/dolthub/doltgresql/server/types"
Expand Down Expand Up @@ -47,6 +51,74 @@ var format_type = framework.Function2{
return t.SQLStandardNameWithTypmod(true, int(val2.(int32))), nil
}
}
return "???", nil
typ, err := getDoltgresTypeFromId(ctx, val1.(id.Id))
if err != nil {
if pgtypes.ErrTypeDoesNotExist.Is(err) {
return "???", nil
}
return nil, err
}
return formatUserDefinedType(ctx, typ, val2)
},
}

// formatUserDefinedType renders a catalog type using PostgreSQL's generic type-name and typmod rules.
func formatUserDefinedType(ctx *sql.Context, typ *pgtypes.DoltgresType, typmodValue any) (string, error) {
isArray := typ.IsArrayType()
if isArray {
typ = typ.ArrayBaseType()
}

name, err := visibleTypeName(ctx, typ.ID)
if err != nil {
return "", err
}
if typmodValue != nil && typmodValue.(int32) >= 0 {
typmod := typmodValue.(int32)
if typ.ModOutFunc == 0 {
name = fmt.Sprintf("%s(%d)", name, typmod)
} else {
modifier, err := typ.TypModOut(ctx, typmod)
if err != nil {
return "", err
}
name += modifier
}
}
if isArray {
name += "[]"
}
return name, nil
}

// visibleTypeName returns a quoted type name, schema-qualifying it when an unqualified reference would resolve elsewhere.
func visibleTypeName(ctx *sql.Context, typID id.Type) (string, error) {
typCol, err := core.GetTypesCollectionFromContext(ctx, "")
if err != nil {
return "", err
}
searchPath, err := core.SearchPath(ctx)
if err != nil {
return "", err
}
for _, schema := range searchPath {
candidate, err := typCol.GetType(ctx, id.NewType(schema, typID.TypeName()))
if err != nil {
return "", err
}
if candidate != nil {
if candidate.ID == typID {
return quoteTypeIdentifier(typID.TypeName()), nil
}
break
}
}
return quoteTypeIdentifier(typID.SchemaName()) + "." + quoteTypeIdentifier(typID.TypeName()), nil
}

// quoteTypeIdentifier quotes a type or schema name when PostgreSQL would not accept it as a bare identifier.
func quoteTypeIdentifier(name string) string {
var buf bytes.Buffer
lex.EncodeRestrictedSQLIdent(&buf, name, 0)
return buf.String()
}
9 changes: 8 additions & 1 deletion server/tables/pgcatalog/pg_attribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,10 @@ func cachePgAttributes(ctx *sql.Context, pgCatalogCache *pgCatalogCache) error {
Table: func(ctx *sql.Context, _ functions.ItemSchema, table functions.ItemTable) (cont bool, err error) {
for i, col := range table.Item.Schema(ctx) {
typeOid := id.Null
typeMod := int32(-1)
if doltgresType, ok := col.Type.(*pgtypes.DoltgresType); ok {
typeOid = doltgresType.ID.AsId()
typeMod = doltgresType.GetAttTypMod()
} else {
// TODO: Remove once all information_schema tables are converted to use DoltgresType
dt := pgtypes.FromGmsType(col.Type)
Expand All @@ -122,6 +124,7 @@ func cachePgAttributes(ctx *sql.Context, pgCatalogCache *pgCatalogCache) error {
attrelidNative: id.Cache().ToOID(table.OID.AsId()),
attname: col.Name,
atttypid: typeOid,
atttypmod: typeMod,
attnum: int16(i + 1),
attndims: dimensions,
attnotnull: !col.Nullable,
Expand Down Expand Up @@ -164,8 +167,10 @@ func cachePgAttributes(ctx *sql.Context, pgCatalogCache *pgCatalogCache) error {

for i, col := range analyzed.Schema(ctx) {
typeOid := id.Null
typeMod := int32(-1)
if doltgresType, ok := col.Type.(*pgtypes.DoltgresType); ok {
typeOid = doltgresType.ID.AsId()
typeMod = doltgresType.GetAttTypMod()
} else {
dt := pgtypes.FromGmsType(col.Type)
typeOid = dt.ID.AsId()
Expand All @@ -176,6 +181,7 @@ func cachePgAttributes(ctx *sql.Context, pgCatalogCache *pgCatalogCache) error {
attrelidNative: id.Cache().ToOID(view.OID.AsId()),
attname: col.Name,
atttypid: typeOid,
atttypmod: typeMod,
attnum: int16(i + 1),
}
attrelidIdx.Add(attr)
Expand Down Expand Up @@ -411,6 +417,7 @@ type pgAttribute struct {
attrelidNative uint32
attname string
atttypid id.Id
atttypmod int32
attnum int16
attndims int16
attnotnull bool
Expand Down Expand Up @@ -467,7 +474,7 @@ func pgAttributeToRow(attr *pgAttribute) sql.Row {
int16(0), // attlen
attr.attnum, // attnum
int32(-1), // attcacheoff
int32(-1), // atttypmod
attr.atttypmod, // atttypmod
attr.attndims, // attndims
false, // attbyval
"i", // attalign
Expand Down
54 changes: 54 additions & 0 deletions testing/go/extensions/pgvector_catalog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,60 @@ import (

func TestPgvectorCatalog(t *testing.T) {
framework.RunScripts(t, []framework.ScriptTest{
{
Name: "dimensioned types expose their modifiers through pg_attribute",
SetUpScript: []string{
"CREATE EXTENSION vector;",
"CREATE TABLE vector_typmod_probe (v vector(1024), h halfvec(7), s sparsevec(99), unbounded vector, vectors vector(3)[]);",
"CREATE VIEW vector_typmod_view AS SELECT v FROM vector_typmod_probe;",
},
Assertions: []framework.ScriptTestAssertion{
{
Query: `SELECT attribute.attname, attribute.atttypmod, format_type(attribute.atttypid, attribute.atttypmod)
FROM pg_attribute AS attribute
JOIN pg_class AS relation ON relation.oid = attribute.attrelid
WHERE relation.relname = 'vector_typmod_probe' AND attribute.attnum > 0 AND NOT attribute.attisdropped
ORDER BY attribute.attnum;`,
Expected: []sql.Row{
{"v", int32(1024), "vector(1024)"},
{"h", int32(7), "halfvec(7)"},
{"s", int32(99), "sparsevec(99)"},
{"unbounded", int32(-1), "vector"},
{"vectors", int32(3), "vector(3)[]"},
},
},
{
Query: `SELECT attribute.atttypmod, format_type(attribute.atttypid, attribute.atttypmod)
FROM pg_attribute AS attribute
JOIN pg_class AS relation ON relation.oid = attribute.attrelid
WHERE relation.relname = 'vector_typmod_view' AND attribute.attname = 'v';`,
Expected: []sql.Row{{int32(1024), "vector(1024)"}},
},
},
},
{
Name: "user-defined type names are quoted and qualified according to visibility",
SetUpScript: []string{
"CREATE SCHEMA first_schema;",
"CREATE SCHEMA second_schema;",
`CREATE TYPE first_schema."Mixed Type" AS ENUM ('first');`,
`CREATE TYPE second_schema."Mixed Type" AS ENUM ('second');`,
"SET search_path TO first_schema, second_schema, public;",
},
Assertions: []framework.ScriptTestAssertion{
{
Query: `SELECT namespace.nspname, format_type(type.oid, NULL)
FROM pg_type AS type
JOIN pg_namespace AS namespace ON namespace.oid = type.typnamespace
WHERE type.typname = 'Mixed Type'
ORDER BY namespace.nspname;`,
Expected: []sql.Row{
{"first_schema", `"Mixed Type"`},
{"second_schema", `second_schema."Mixed Type"`},
},
},
},
},
{
Name: "pg_am lists the extension access methods once installed",
Assertions: []framework.ScriptTestAssertion{
Expand Down
2 changes: 1 addition & 1 deletion testing/go/information_schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ func TestInfoSchemaColumns(t *testing.T) {
) WHERE ("table_schema" = 'public' AND "table_name" = 'test_table');`,
Expected: []sql.Row{
{"id", nil, "integer", "integer"},
{"col1", nil, "character varying", "character varying"},
{"col1", nil, "character varying", "character varying(255)"},
},
},
{
Expand Down
Loading