492 lines
16 KiB
Go
492 lines
16 KiB
Go
// Copyright 2024 Dolthub, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package pgcatalog
|
|
|
|
import (
|
|
"io"
|
|
"math"
|
|
|
|
"github.com/dolthub/dolt/go/libraries/doltcore/sqlserver"
|
|
"github.com/dolthub/go-mysql-server/sql"
|
|
|
|
"github.com/dolthub/doltgresql/core/id"
|
|
pgparser "github.com/dolthub/doltgresql/postgres/parser/parser"
|
|
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
|
|
"github.com/dolthub/doltgresql/server/functions"
|
|
"github.com/dolthub/doltgresql/server/tables"
|
|
pgtypes "github.com/dolthub/doltgresql/server/types"
|
|
)
|
|
|
|
// PgAttributeName is a constant to the pg_attribute name.
|
|
const PgAttributeName = "pg_attribute"
|
|
|
|
// InitPgAttribute handles registration of the pg_attribute handler.
|
|
func InitPgAttribute() {
|
|
tables.AddHandler(PgCatalogName, PgAttributeName, PgAttributeHandler{})
|
|
}
|
|
|
|
// PgAttributeHandler is the handler for the pg_attribute table.
|
|
type PgAttributeHandler struct{}
|
|
|
|
var _ tables.Handler = PgAttributeHandler{}
|
|
var _ tables.IndexedTableHandler = PgAttributeHandler{}
|
|
|
|
// Name implements the interface tables.Handler.
|
|
func (p PgAttributeHandler) Name() string {
|
|
return PgAttributeName
|
|
}
|
|
|
|
// RowIter implements the interface tables.Handler.
|
|
func (p PgAttributeHandler) RowIter(ctx *sql.Context, partition sql.Partition) (sql.RowIter, error) {
|
|
// Use cached data from this session if it exists
|
|
pgCatalogCache, err := getPgCatalogCache(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if pgCatalogCache.pgAttributes == nil {
|
|
err = cachePgAttributes(ctx, pgCatalogCache)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if attrIdxPart, ok := partition.(inMemIndexPartition); ok {
|
|
return &inMemIndexScanIter[*pgAttribute]{
|
|
lookup: attrIdxPart.lookup,
|
|
rangeConverter: p,
|
|
btreeAccess: pgCatalogCache.pgAttributes,
|
|
rowConverter: pgAttributeToRow,
|
|
}, nil
|
|
}
|
|
|
|
return &pgAttributeTableScanIter{
|
|
attributeCache: pgCatalogCache.pgAttributes,
|
|
idx: 0,
|
|
}, nil
|
|
}
|
|
|
|
// cachePgAttributes caches the pg_attribute data for the current database in the session.
|
|
func cachePgAttributes(ctx *sql.Context, pgCatalogCache *pgCatalogCache) error {
|
|
var attributes []*pgAttribute
|
|
attrelidIdx := NewUniqueInMemIndexStorage[*pgAttribute](lessAttNum)
|
|
attrelidAttnameIdx := NewUniqueInMemIndexStorage[*pgAttribute](lessAttName)
|
|
|
|
// Get the engine for resolving view column types. May be nil in some test environments.
|
|
type queryAnalyzer interface {
|
|
AnalyzeQuery(*sql.Context, string) (sql.Node, error)
|
|
}
|
|
var engine queryAnalyzer
|
|
if runningServer := sqlserver.GetRunningServer(); runningServer != nil && runningServer.Engine != nil {
|
|
engine = runningServer.Engine
|
|
}
|
|
|
|
err := functions.IterateCurrentDatabase(ctx, functions.Callbacks{
|
|
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
|
|
if doltgresType, ok := col.Type.(*pgtypes.DoltgresType); ok {
|
|
typeOid = doltgresType.ID.AsId()
|
|
} else {
|
|
// TODO: Remove once all information_schema tables are converted to use DoltgresType
|
|
dt := pgtypes.FromGmsType(col.Type)
|
|
typeOid = dt.ID.AsId()
|
|
}
|
|
|
|
generated := ""
|
|
if col.Generated != nil {
|
|
generated = "s"
|
|
}
|
|
|
|
dimensions := int16(0)
|
|
if s, ok := col.Type.(sql.SetType); ok {
|
|
dimensions = int16(s.NumberOfElements())
|
|
}
|
|
|
|
hasDefault := col.Default != nil
|
|
|
|
attr := &pgAttribute{
|
|
attrelid: table.OID.AsId(),
|
|
attrelidNative: id.Cache().ToOID(table.OID.AsId()),
|
|
attname: col.Name,
|
|
atttypid: typeOid,
|
|
attnum: int16(i + 1),
|
|
attndims: dimensions,
|
|
attnotnull: !col.Nullable,
|
|
atthasdef: hasDefault,
|
|
attgenerated: generated,
|
|
}
|
|
attrelidIdx.Add(attr)
|
|
attrelidAttnameIdx.Add(attr)
|
|
attributes = append(attributes, attr)
|
|
}
|
|
return true, nil
|
|
},
|
|
View: func(ctx *sql.Context, _ functions.ItemSchema, view functions.ItemView) (cont bool, err error) {
|
|
if engine == nil {
|
|
return true, nil
|
|
}
|
|
|
|
// Get the SELECT body from the view definition.
|
|
selectBody := view.Item.TextDefinition
|
|
if selectBody == "" {
|
|
stmts, parseErr := pgparser.Parse(view.Item.CreateViewStatement)
|
|
if parseErr != nil || len(stmts) == 0 {
|
|
return true, nil
|
|
}
|
|
cv, ok := stmts[0].AST.(*tree.CreateView)
|
|
if !ok {
|
|
return true, nil
|
|
}
|
|
selectBody = cv.AsSource.String()
|
|
}
|
|
if selectBody == "" {
|
|
return true, nil
|
|
}
|
|
|
|
// Analyze the SELECT statement to get the view's output schema.
|
|
analyzed, analyzeErr := engine.AnalyzeQuery(ctx, selectBody)
|
|
if analyzeErr != nil {
|
|
return true, nil
|
|
}
|
|
|
|
for i, col := range analyzed.Schema(ctx) {
|
|
typeOid := id.Null
|
|
if doltgresType, ok := col.Type.(*pgtypes.DoltgresType); ok {
|
|
typeOid = doltgresType.ID.AsId()
|
|
} else {
|
|
dt := pgtypes.FromGmsType(col.Type)
|
|
typeOid = dt.ID.AsId()
|
|
}
|
|
|
|
attr := &pgAttribute{
|
|
attrelid: view.OID.AsId(),
|
|
attrelidNative: id.Cache().ToOID(view.OID.AsId()),
|
|
attname: col.Name,
|
|
atttypid: typeOid,
|
|
attnum: int16(i + 1),
|
|
}
|
|
attrelidIdx.Add(attr)
|
|
attrelidAttnameIdx.Add(attr)
|
|
attributes = append(attributes, attr)
|
|
}
|
|
return true, nil
|
|
},
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
pgCatalogCache.pgAttributes = &pgAttributeCache{
|
|
attributes: attributes,
|
|
attrelidIdx: attrelidIdx,
|
|
attrelidAttnameIdx: attrelidAttnameIdx,
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// getIndexScanRange implements the interface RangeConverter.
|
|
func (p PgAttributeHandler) getIndexScanRange(rng sql.Range, index sql.Index) (*pgAttribute, bool, *pgAttribute, bool) {
|
|
var gte, lt *pgAttribute
|
|
var hasLowerBound, hasUpperBound bool
|
|
|
|
switch index.(pgCatalogInMemIndex).name {
|
|
case "pg_attribute_relid_attnum_index":
|
|
msrng := rng.(sql.MySQLRange)
|
|
oidRng := msrng[0]
|
|
attNumRng := msrng[1]
|
|
|
|
var oidLower, oidUpper id.Id
|
|
attnumLower := int16(math.MinInt16)
|
|
attnumUpper := int16(math.MaxInt16)
|
|
attNumUpperSet := false
|
|
|
|
if oidRng.HasLowerBound() {
|
|
lb := sql.GetMySQLRangeCutKey(oidRng.LowerBound)
|
|
if lb != nil {
|
|
oidLower = lb.(id.Id)
|
|
hasLowerBound = true
|
|
}
|
|
}
|
|
|
|
if oidRng.HasUpperBound() {
|
|
ub := sql.GetMySQLRangeCutKey(oidRng.UpperBound)
|
|
if ub != nil {
|
|
oidUpper = ub.(id.Id)
|
|
hasUpperBound = true
|
|
}
|
|
}
|
|
|
|
if attNumRng.HasLowerBound() {
|
|
lb := sql.GetMySQLRangeCutKey(attNumRng.LowerBound)
|
|
if lb != nil {
|
|
attnumLower = lb.(int16)
|
|
}
|
|
}
|
|
|
|
if attNumRng.HasUpperBound() {
|
|
ub := sql.GetMySQLRangeCutKey(attNumRng.UpperBound)
|
|
if ub != nil {
|
|
attnumUpper = ub.(int16)
|
|
attNumUpperSet = true
|
|
}
|
|
}
|
|
|
|
if hasLowerBound {
|
|
gte = &pgAttribute{
|
|
attrelidNative: idToOid(oidLower),
|
|
attnum: attnumLower,
|
|
}
|
|
}
|
|
|
|
if hasUpperBound {
|
|
// our less-than upper bound depends on whether one or both fields in the range were set
|
|
oid := idToOid(oidUpper)
|
|
if !attNumUpperSet {
|
|
oid += 1
|
|
} else {
|
|
attnumUpper += 1
|
|
}
|
|
lt = &pgAttribute{
|
|
attrelidNative: oid,
|
|
attnum: attnumUpper,
|
|
}
|
|
}
|
|
|
|
case "pg_attribute_relid_attnam_index":
|
|
msrng := rng.(sql.MySQLRange)
|
|
attrelidRange := msrng[0]
|
|
attnameRange := msrng[1]
|
|
var attrelidLower, attrelidUpper uint32
|
|
var attnameLower, attnameUpper string
|
|
|
|
if attrelidRange.HasLowerBound() {
|
|
lb := sql.GetMySQLRangeCutKey(attrelidRange.LowerBound)
|
|
if lb != nil {
|
|
lowerRangeCutKey := lb.(id.Id)
|
|
attrelidLower = idToOid(lowerRangeCutKey)
|
|
hasLowerBound = true
|
|
}
|
|
}
|
|
if attrelidRange.HasUpperBound() {
|
|
ub := sql.GetMySQLRangeCutKey(attrelidRange.UpperBound)
|
|
if ub != nil {
|
|
upperRangeCutKey := ub.(id.Id)
|
|
attrelidUpper = idToOid(upperRangeCutKey)
|
|
hasUpperBound = true
|
|
}
|
|
}
|
|
|
|
if attnameRange.HasLowerBound() {
|
|
lb := sql.GetMySQLRangeCutKey(attnameRange.LowerBound)
|
|
if lb != nil {
|
|
attnameLower = lb.(string)
|
|
}
|
|
}
|
|
if attnameRange.HasUpperBound() {
|
|
ub := sql.GetMySQLRangeCutKey(attnameRange.UpperBound)
|
|
if ub != nil {
|
|
attnameUpper = ub.(string)
|
|
}
|
|
}
|
|
|
|
if attrelidRange.HasLowerBound() || attnameRange.HasLowerBound() {
|
|
gte = &pgAttribute{
|
|
attrelidNative: attrelidLower,
|
|
attname: attnameLower,
|
|
}
|
|
}
|
|
|
|
if attrelidRange.HasUpperBound() || attnameRange.HasUpperBound() {
|
|
// our less-than upper bound depends on whether one or both fields in the range were set
|
|
oidUpper := attrelidUpper
|
|
if attnameUpper == "" {
|
|
oidUpper += 1
|
|
} else {
|
|
attnameUpper += " "
|
|
}
|
|
lt = &pgAttribute{
|
|
attrelidNative: oidUpper,
|
|
attname: attnameUpper,
|
|
}
|
|
}
|
|
default:
|
|
panic("unknown index name: " + index.(pgCatalogInMemIndex).name)
|
|
}
|
|
|
|
return gte, hasLowerBound, lt, hasUpperBound
|
|
}
|
|
|
|
// PkSchema implements the interface tables.Handler.
|
|
func (p PgAttributeHandler) PkSchema() sql.PrimaryKeySchema {
|
|
return sql.PrimaryKeySchema{
|
|
Schema: pgAttributeSchema,
|
|
PkOrdinals: nil,
|
|
}
|
|
}
|
|
|
|
// Indexes implements tables.IndexedTableHandler.
|
|
func (p PgAttributeHandler) Indexes() ([]sql.Index, error) {
|
|
return []sql.Index{
|
|
pgCatalogInMemIndex{
|
|
name: "pg_attribute_relid_attnum_index",
|
|
tblName: "pg_attribute",
|
|
dbName: "pg_catalog",
|
|
uniq: true,
|
|
columnExprs: []sql.ColumnExpressionType{
|
|
{Expression: "pg_attribute.attrelid", Type: pgtypes.Oid},
|
|
{Expression: "pg_attribute.attnum", Type: pgtypes.Int16},
|
|
},
|
|
},
|
|
pgCatalogInMemIndex{
|
|
name: "pg_attribute_relid_attnam_index",
|
|
tblName: "pg_attribute",
|
|
dbName: "pg_catalog",
|
|
uniq: true,
|
|
columnExprs: []sql.ColumnExpressionType{
|
|
{Expression: "pg_attribute.attrelid", Type: pgtypes.Oid},
|
|
{Expression: "pg_attribute.attname", Type: pgtypes.Name},
|
|
},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// LookupPartitions implements tables.IndexedTableHandler.
|
|
func (p PgAttributeHandler) LookupPartitions(context *sql.Context, lookup sql.IndexLookup) (sql.PartitionIter, error) {
|
|
return &inMemIndexPartIter{
|
|
part: inMemIndexPartition{
|
|
idxName: lookup.Index.(pgCatalogInMemIndex).name,
|
|
lookup: lookup,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// pgAttributeSchema is the schema for pg_attribute.
|
|
var pgAttributeSchema = sql.Schema{
|
|
{Name: "attrelid", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: PgAttributeName},
|
|
{Name: "attname", Type: pgtypes.Name, Default: nil, Nullable: false, Source: PgAttributeName},
|
|
{Name: "atttypid", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: PgAttributeName},
|
|
{Name: "attlen", Type: pgtypes.Int16, Default: nil, Nullable: false, Source: PgAttributeName},
|
|
{Name: "attnum", Type: pgtypes.Int16, Default: nil, Nullable: false, Source: PgAttributeName},
|
|
{Name: "attcacheoff", Type: pgtypes.Int32, Default: nil, Nullable: false, Source: PgAttributeName},
|
|
{Name: "atttypmod", Type: pgtypes.Int32, Default: nil, Nullable: false, Source: PgAttributeName},
|
|
{Name: "attndims", Type: pgtypes.Int16, Default: nil, Nullable: false, Source: PgAttributeName},
|
|
{Name: "attbyval", Type: pgtypes.Bool, Default: nil, Nullable: false, Source: PgAttributeName},
|
|
{Name: "attalign", Type: pgtypes.InternalChar, Default: nil, Nullable: false, Source: PgAttributeName},
|
|
{Name: "attstorage", Type: pgtypes.InternalChar, Default: nil, Nullable: false, Source: PgAttributeName},
|
|
{Name: "attcompression", Type: pgtypes.InternalChar, Default: nil, Nullable: false, Source: PgAttributeName},
|
|
{Name: "attnotnull", Type: pgtypes.Bool, Default: nil, Nullable: false, Source: PgAttributeName},
|
|
{Name: "atthasdef", Type: pgtypes.Bool, Default: nil, Nullable: false, Source: PgAttributeName},
|
|
{Name: "atthasmissing", Type: pgtypes.Bool, Default: nil, Nullable: false, Source: PgAttributeName},
|
|
{Name: "attidentity", Type: pgtypes.InternalChar, Default: nil, Nullable: false, Source: PgAttributeName},
|
|
{Name: "attgenerated", Type: pgtypes.InternalChar, Default: nil, Nullable: false, Source: PgAttributeName},
|
|
{Name: "attisdropped", Type: pgtypes.Bool, Default: nil, Nullable: false, Source: PgAttributeName},
|
|
{Name: "attislocal", Type: pgtypes.Bool, Default: nil, Nullable: false, Source: PgAttributeName},
|
|
{Name: "attinhcount", Type: pgtypes.Int16, Default: nil, Nullable: false, Source: PgAttributeName},
|
|
{Name: "attstattarget", Type: pgtypes.Int16, Default: nil, Nullable: false, Source: PgAttributeName},
|
|
{Name: "attcollation", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: PgAttributeName},
|
|
{Name: "attacl", Type: pgtypes.TextArray, Default: nil, Nullable: true, Source: PgAttributeName}, // TODO: type aclitem[]
|
|
{Name: "attoptions", Type: pgtypes.TextArray, Default: nil, Nullable: true, Source: PgAttributeName}, // TODO: collation C
|
|
{Name: "attfdwoptions", Type: pgtypes.TextArray, Default: nil, Nullable: true, Source: PgAttributeName}, // TODO: collation C
|
|
{Name: "attmissingval", Type: pgtypes.AnyArray, Default: nil, Nullable: true, Source: PgAttributeName},
|
|
}
|
|
|
|
// pgAttribute represents a row in the pg_attribute table.
|
|
// We store oids in their native format as well so that we can do range scans on them.
|
|
type pgAttribute struct {
|
|
attrelid id.Id
|
|
attrelidNative uint32
|
|
attname string
|
|
atttypid id.Id
|
|
attnum int16
|
|
attndims int16
|
|
attnotnull bool
|
|
atthasdef bool
|
|
attgenerated string
|
|
}
|
|
|
|
// lessAttNum is a sort function for pgAttribute based on attrelid.
|
|
func lessAttNum(a, b *pgAttribute) bool {
|
|
if a.attrelidNative == b.attrelidNative {
|
|
return a.attnum < b.attnum
|
|
}
|
|
return a.attrelidNative < b.attrelidNative
|
|
}
|
|
|
|
// lessAttName is a sort function for pgAttribute based on attrelid, then attname.
|
|
func lessAttName(a, b *pgAttribute) bool {
|
|
if a.attrelidNative == b.attrelidNative {
|
|
return a.attname < b.attname
|
|
}
|
|
return a.attrelidNative < b.attrelidNative
|
|
}
|
|
|
|
// pgAttributeTableScanIter is the sql.RowIter for the pg_attribute table.
|
|
type pgAttributeTableScanIter struct {
|
|
attributeCache *pgAttributeCache
|
|
idx int
|
|
}
|
|
|
|
var _ sql.RowIter = (*pgAttributeTableScanIter)(nil)
|
|
|
|
// Next implements the interface sql.RowIter.
|
|
func (iter *pgAttributeTableScanIter) Next(ctx *sql.Context) (sql.Row, error) {
|
|
if iter.idx >= len(iter.attributeCache.attributes) {
|
|
return nil, io.EOF
|
|
}
|
|
iter.idx++
|
|
attr := iter.attributeCache.attributes[iter.idx-1]
|
|
|
|
return pgAttributeToRow(attr), nil
|
|
}
|
|
|
|
// Close implements the interface sql.RowIter.
|
|
func (iter *pgAttributeTableScanIter) Close(ctx *sql.Context) error {
|
|
return nil
|
|
}
|
|
|
|
func pgAttributeToRow(attr *pgAttribute) sql.Row {
|
|
// TODO: Fill in the rest of the pg_attribute columns
|
|
return sql.Row{
|
|
attr.attrelid, // attrelid
|
|
attr.attname, // attname
|
|
attr.atttypid, // atttypid
|
|
int16(0), // attlen
|
|
attr.attnum, // attnum
|
|
int32(-1), // attcacheoff
|
|
int32(-1), // atttypmod
|
|
attr.attndims, // attndims
|
|
false, // attbyval
|
|
"i", // attalign
|
|
"p", // attstorage
|
|
"", // attcompression
|
|
attr.attnotnull, // attnotnull
|
|
attr.atthasdef, // atthasdef
|
|
false, // atthasmissing
|
|
"", // attidentity
|
|
attr.attgenerated, // attgenerated
|
|
false, // attisdropped
|
|
true, // attislocal
|
|
int16(0), // attinhcount
|
|
int16(-1), // attstattarget
|
|
id.Null, // attcollation
|
|
nil, // attacl
|
|
nil, // attoptions
|
|
nil, // attfdwoptions
|
|
nil, // attmissingval
|
|
}
|
|
}
|