Files
2026-07-13 12:32:25 +08:00

255 lines
8.4 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 functions
import (
"fmt"
"strconv"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/doltgresql/core"
"github.com/dolthub/doltgresql/core/id"
"github.com/dolthub/doltgresql/server/functions/framework"
"github.com/dolthub/doltgresql/server/settings"
pgtypes "github.com/dolthub/doltgresql/server/types"
"github.com/dolthub/doltgresql/utils"
)
// initRegclass registers the functions to the catalog.
func initRegclass() {
framework.RegisterFunction(regclassin)
framework.RegisterFunction(regclassout)
framework.RegisterFunction(regclassrecv)
framework.RegisterFunction(regclasssend)
}
// regclassin represents the PostgreSQL function of regclass type IO input.
var regclassin = framework.Function1{
Name: "regclassin",
Return: pgtypes.Regclass,
Parameters: [1]*pgtypes.DoltgresType{pgtypes.Cstring},
Strict: true,
Callable: func(ctx *sql.Context, _ [2]*pgtypes.DoltgresType, val any) (any, error) {
// If the string just represents a number, then we return it.
input := val.(string)
if parsedOid, err := strconv.ParseUint(input, 10, 32); err == nil {
if internalID := id.Cache().ToInternal(uint32(parsedOid)); internalID.IsValid() {
return internalID, nil
}
return id.NewOID(uint32(parsedOid)).AsId(), nil
}
sections, err := ioInputSections(input)
if err != nil {
return id.Null, err
}
if err = regclass_IoInputValidation(ctx, input, sections); err != nil {
return id.Null, err
}
var database string
var searchSchemas []string
var relationName string
switch len(sections) {
case 1:
database = ctx.GetCurrentDatabase()
searchSchemas, err = core.SearchPath(ctx)
if err != nil {
return id.Null, err
}
relationName = sections[0]
case 3:
database = ctx.GetCurrentDatabase()
searchSchemas = []string{sections[0]}
relationName = sections[2]
case 5:
database = sections[0]
searchSchemas = []string{sections[2]}
relationName = sections[4]
default:
return id.Null, errors.Errorf("unexpected input for regclass: %s", input)
}
// Iterate over all of the items to find which relation matches.
// Postgres does not need to worry about name conflicts since everything is created in the same naming space, but
// GMS and Dolt use different naming spaces, so for now we just ignore potential name conflicts and return the first
// match found.
var resultOid id.Id
err = IterateDatabase(ctx, database, Callbacks{
Index: func(ctx *sql.Context, schema ItemSchema, table ItemTable, index ItemIndex) (cont bool, err error) {
idxName := index.Item.ID()
if idxName == "PRIMARY" {
idxName = fmt.Sprintf("%s_pkey", index.Item.Table())
}
if relationName == idxName {
resultOid = index.OID.AsId()
return false, nil
}
return true, nil
},
Sequence: func(ctx *sql.Context, schema ItemSchema, sequence ItemSequence) (cont bool, err error) {
if sequence.Item.Id.SequenceName() == relationName {
resultOid = sequence.OID.AsId()
return false, nil
}
return true, nil
},
Table: func(ctx *sql.Context, schema ItemSchema, table ItemTable) (cont bool, err error) {
if table.Item.Name() == relationName {
resultOid = table.OID.AsId()
return false, nil
}
return true, nil
},
View: func(ctx *sql.Context, schema ItemSchema, view ItemView) (cont bool, err error) {
if view.Item.Name == relationName {
resultOid = view.OID.AsId()
return false, nil
}
return true, nil
},
SearchSchemas: searchSchemas,
})
if err != nil || resultOid.IsValid() {
return resultOid, err
}
return id.Null, errors.Errorf(`relation "%s" does not exist`, input)
},
}
// regclassout represents the PostgreSQL function of regclass type IO output.
var regclassout = framework.Function1{
Name: "regclassout",
Return: pgtypes.Cstring,
Parameters: [1]*pgtypes.DoltgresType{pgtypes.Regclass},
Strict: true,
Callable: func(ctx *sql.Context, _ [2]*pgtypes.DoltgresType, val any) (any, error) {
// Find all the schemas on the search path. If a schema is on the search path, then it is not included in the
// output of relation name. If the relation's schema is not on the search path, then it is explicitly included.
schemasMap, err := settings.GetCurrentSchemasAsMap(ctx)
if err != nil {
return "", err
}
// The pg_catalog schema is always implicitly part of the search path
// https://www.postgresql.org/docs/current/ddl-schemas.html#DDL-SCHEMAS-CATALOG
schemasMap["pg_catalog"] = struct{}{}
input := val.(id.Id)
if input.Section() == id.Section_OID {
return input.Segment(0), nil
}
var output string
err = RunCallback(ctx, input, Callbacks{
Index: func(ctx *sql.Context, schema ItemSchema, table ItemTable, index ItemIndex) (cont bool, err error) {
output = index.Item.ID()
if output == "PRIMARY" {
schemaName := schema.Item.SchemaName()
if _, ok := schemasMap[schemaName]; ok {
output = fmt.Sprintf("%s_pkey", index.Item.Table())
} else {
output = fmt.Sprintf("%s.%s_pkey", schemaName, index.Item.Table())
}
}
return false, nil
},
Sequence: func(ctx *sql.Context, schema ItemSchema, sequence ItemSequence) (cont bool, err error) {
schemaName := schema.Item.SchemaName()
if _, ok := schemasMap[schemaName]; ok {
output = sequence.Item.Id.SequenceName()
} else {
output = fmt.Sprintf("%s.%s", schemaName, sequence.Item.Id.SequenceName())
}
return false, nil
},
Table: func(ctx *sql.Context, schema ItemSchema, table ItemTable) (cont bool, err error) {
schemaName := schema.Item.SchemaName()
if _, ok := schemasMap[schemaName]; ok {
output = table.Item.Name()
} else {
output = fmt.Sprintf("%s.%s", schemaName, table.Item.Name())
}
return false, nil
},
View: func(ctx *sql.Context, schema ItemSchema, view ItemView) (cont bool, err error) {
schemaName := schema.Item.SchemaName()
if _, ok := schemasMap[schemaName]; ok {
output = view.Item.Name
} else {
output = fmt.Sprintf("%s.%s", schemaName, view.Item.Name)
}
return false, nil
},
})
return output, err
},
}
// regclassrecv represents the PostgreSQL function of regclass type IO receive.
var regclassrecv = framework.Function1{
Name: "regclassrecv",
Return: pgtypes.Regclass,
Parameters: [1]*pgtypes.DoltgresType{pgtypes.Internal},
Strict: true,
Callable: func(ctx *sql.Context, _ [2]*pgtypes.DoltgresType, val any) (any, error) {
data := val.([]byte)
if data == nil {
return nil, nil
}
reader := utils.NewWireReader(data)
cachedID := id.Cache().ToInternal(reader.ReadUint32())
return cachedID, nil
},
}
// regclasssend represents the PostgreSQL function of regclass type IO send.
var regclasssend = framework.Function1{
Name: "regclasssend",
Return: pgtypes.Bytea,
Parameters: [1]*pgtypes.DoltgresType{pgtypes.Regclass},
Strict: true,
Callable: func(ctx *sql.Context, _ [2]*pgtypes.DoltgresType, val any) (any, error) {
writer := utils.NewWireWriter()
writer.WriteUint32(id.Cache().ToOID(val.(id.Id)))
return writer.BufferData(), nil
},
}
// regclass_IoInputValidation handles the validation for the parsed sections in regclass_IoInput.
func regclass_IoInputValidation(ctx *sql.Context, input string, sections []string) error {
switch len(sections) {
case 1:
return nil
case 3:
if sections[1] != "." {
return errors.Errorf("invalid name syntax")
}
return nil
case 5:
if sections[1] != "." || sections[3] != "." {
return errors.Errorf("invalid name syntax")
}
return nil
case 7:
if sections[1] != "." || sections[3] != "." || sections[5] != "." {
return errors.Errorf("invalid name syntax")
}
return errors.Errorf("improper qualified name (too many dotted names): %s", input)
default:
return errors.Errorf("invalid name syntax")
}
}