Files
upstream-mirror 0f4df23290
Some checks failed
kotlin / test (push) Has been skipped
python / test (push) Has been skipped
typescript / test (push) Has been skipped
go / build darwin/amd64 (push) Has been cancelled
go / build linux/amd64 (push) Has been cancelled
go / build windows/amd64 (push) Has been cancelled
go / build darwin/arm64 (push) Has been cancelled
go / build linux/arm64 (push) Has been cancelled
go / build windows/arm64 (push) Has been cancelled
go / test (push) Has been cancelled
go / vuln_check (push) Has been cancelled
snapshot of https://github.com/sqlc-dev/sqlc.git (main, 2026-09-16; blobs > 131072B omitted)
2026-09-16 02:17:34 +05:00

571 lines
14 KiB
Go

package schema
import (
"fmt"
"strings"
"github.com/sqlc-dev/sqlc/internal/core"
"github.com/sqlc-dev/sqlc/internal/core/analyzer"
"github.com/sqlc-dev/sqlc/internal/sql/ast"
)
func Apply(cat *core.Catalog, n ast.Node) error {
switch v := n.(type) {
case nil:
return nil
case *ast.RawStmt:
return Apply(cat, v.Stmt)
case *ast.List:
for _, it := range v.Items {
if err := Apply(cat, it); err != nil {
return err
}
}
return nil
case *ast.CreateTableStmt:
return applyCreateTable(cat, v)
case *ast.DropTableStmt:
return applyDropTable(cat, v)
case *ast.CreateEnumStmt:
return applyCreateEnum(cat, v)
case *ast.CreateDomainStmt:
return applyCreateDomain(cat, v)
case *ast.CompositeTypeStmt:
return applyCompositeType(cat, v)
case *ast.CreateRangeStmt:
return applyCreateRange(cat, v)
case *ast.CreateExtensionStmt:
if v.Extname == nil {
return nil
}
return cat.LoadExtension(*v.Extname)
case *ast.CreateFunctionStmt:
return applyCreateFunction(cat, v)
case *ast.AlterTableStmt:
return applyAlterTable(cat, v)
case *ast.RenameColumnStmt:
return applyRenameColumn(cat, v)
case *ast.RenameTableStmt:
return applyRenameTable(cat, v)
case *ast.ViewStmt:
return applyView(cat, v.View, v.Aliases, v.Query, v.Replace)
case *ast.CreateTableAsStmt:
if v.Into == nil {
return nil
}
return applyView(cat, v.Into.Rel, v.Into.ColNames, v.Query, false)
}
return nil
}
// applyView records a view, or a table created from a query, as a relation
// whose columns are the ones its query selects.
func applyView(cat *core.Catalog, rel *ast.RangeVar, aliases *ast.List, query ast.Node, replace bool) error {
if rel == nil || rel.Relname == nil {
return fmt.Errorf("create view with nil name")
}
name := *rel.Relname
schema := ""
if rel.Schemaname != nil {
schema = *rel.Schemaname
}
nsOID, err := resolveOrCreateNamespace(cat, schema)
if err != nil {
return err
}
if existing, err := cat.ClassOID(nsOID, name); err == nil {
if !replace {
return fmt.Errorf("relation %q already exists", name)
}
if err := cat.DropClass(existing); err != nil {
return err
}
}
sel, ok := query.(*ast.SelectStmt)
if !ok {
return fmt.Errorf("view %q: unsupported query %T", name, query)
}
res, err := analyzer.Prepare(cat, sel)
if err != nil {
return fmt.Errorf("view %q: %w", name, err)
}
classOID, err := cat.CreateClass(nsOID, name, "v")
if err != nil {
return err
}
names := listStrings(aliases)
for i, col := range res.Columns {
colName := col.Name
if i < len(names) {
colName = names[i]
}
typeOID := col.TypeOID
if typeOID == 0 {
typeOID, err = cat.ResolveTypeName("any")
if err != nil {
return err
}
}
if err := cat.CreateAttributeSpec(core.AttributeSpec{
ClassOID: classOID,
Name: colName,
TypeOID: typeOID,
Num: i + 1,
NotNull: col.NotNull,
DeclType: col.DataType,
}); err != nil {
return fmt.Errorf("view %s.%s: %w", name, colName, err)
}
}
return nil
}
func listStrings(l *ast.List) []string {
if l == nil {
return nil
}
out := make([]string, 0, len(l.Items))
for _, item := range l.Items {
if s, ok := item.(*ast.String); ok {
out = append(out, s.Str)
}
}
return out
}
func applyCreateTable(cat *core.Catalog, stmt *ast.CreateTableStmt) error {
if stmt.Name == nil {
return fmt.Errorf("create table with nil name")
}
// A virtual table's module is the dialect's word for the extension it
// needs, the way CREATE EXTENSION is PostgreSQL's.
if stmt.Using != "" {
if err := cat.LoadExtension(stmt.Using); err != nil {
return err
}
}
nsOID, err := resolveOrCreateNamespace(cat, stmt.Name.Schema)
if err != nil {
return err
}
if _, err := cat.ClassOID(nsOID, stmt.Name.Name); err == nil {
if stmt.IfNotExists {
return nil
}
return fmt.Errorf("relation %q already exists", stmt.Name.Name)
}
classOID, err := cat.CreateClass(nsOID, stmt.Name.Name, "r")
if err != nil {
return err
}
for i, col := range stmt.Cols {
if col == nil || col.TypeName == nil {
return fmt.Errorf("column %d on %q: missing type", i+1, stmt.Name.Name)
}
typeOID, err := columnTypeOID(cat, col)
if err != nil {
return fmt.Errorf("column %s.%s: %w", stmt.Name.Name, col.Colname, err)
}
if err := cat.CreateAttributeSpec(core.AttributeSpec{
ClassOID: classOID,
Name: col.Colname,
TypeOID: typeOID,
Num: i + 1,
NotNull: col.IsNotNull || col.PrimaryKey || typeNotNull(cat, typeOID),
IsPrimaryKey: col.PrimaryKey,
DeclType: declType(col.TypeName),
Hidden: col.IsHidden,
}); err != nil {
return fmt.Errorf("attr %s.%s: %w", stmt.Name.Name, col.Colname, err)
}
}
return nil
}
func applyDropTable(cat *core.Catalog, stmt *ast.DropTableStmt) error {
for _, tn := range stmt.Tables {
if tn == nil {
continue
}
nsOID, err := cat.NamespaceOID(nsName(tn.Schema))
if err != nil {
if stmt.IfExists {
continue
}
return err
}
classOID, err := cat.ClassOID(nsOID, tn.Name)
if err != nil {
if stmt.IfExists {
continue
}
return fmt.Errorf("drop table %q: %w", tn.Name, err)
}
if err := cat.DropClass(classOID); err != nil {
return fmt.Errorf("drop table %q: %w", tn.Name, err)
}
}
return nil
}
func applyAlterTable(cat *core.Catalog, stmt *ast.AlterTableStmt) error {
table := stmt.Table
if table == nil && stmt.Relation != nil {
table = rangeVarTableName(stmt.Relation)
}
classOID, err := lookupClass(cat, table)
if err != nil {
if stmt.MissingOk {
return nil
}
return err
}
for _, item := range listItems(stmt.Cmds) {
cmd, ok := item.(*ast.AlterTableCmd)
if !ok {
continue
}
switch cmd.Subtype {
case ast.AT_AddColumn:
if cmd.Def == nil {
continue
}
typeOID, err := columnTypeOID(cat, cmd.Def)
if err != nil {
return err
}
num, err := cat.NextAttributeNum(classOID)
if err != nil {
return err
}
if err := cat.CreateAttributeSpec(core.AttributeSpec{
ClassOID: classOID,
Name: cmd.Def.Colname,
TypeOID: typeOID,
Num: num,
NotNull: cmd.Def.IsNotNull || cmd.Def.PrimaryKey || typeNotNull(cat, typeOID),
IsPrimaryKey: cmd.Def.PrimaryKey,
DeclType: declType(cmd.Def.TypeName),
}); err != nil {
return err
}
case ast.AT_DropColumn:
if cmd.Name == nil {
continue
}
if err := cat.DropAttribute(classOID, *cmd.Name); err != nil {
return err
}
case ast.AT_AlterColumnType:
if cmd.Def == nil {
continue
}
name := cmd.Def.Colname
if cmd.Name != nil && *cmd.Name != "" {
name = *cmd.Name
}
typeOID, err := columnTypeOID(cat, cmd.Def)
if err != nil {
return err
}
if err := cat.SetAttributeType(classOID, name, typeOID, declType(cmd.Def.TypeName)); err != nil {
return err
}
// An engine that reports a column's whole new definition also
// reports whether it still accepts NULL.
if cmd.Def.IsNotNull {
if err := cat.SetAttributeNotNull(classOID, name, true); err != nil {
return err
}
}
case ast.AT_SetNotNull, ast.AT_DropNotNull:
if cmd.Name == nil {
continue
}
if err := cat.SetAttributeNotNull(classOID, *cmd.Name, cmd.Subtype == ast.AT_SetNotNull); err != nil {
return err
}
}
}
return nil
}
func applyRenameColumn(cat *core.Catalog, stmt *ast.RenameColumnStmt) error {
if stmt.Col == nil || stmt.NewName == nil {
return nil
}
classOID, err := lookupClass(cat, stmt.Table)
if err != nil {
if stmt.MissingOk {
return nil
}
return err
}
// Engines report the column either as a field list or as a bare name.
name := stmt.Col.Name
if names := listStrings(stmt.Col.Fields); len(names) > 0 {
name = names[len(names)-1]
}
if name == "" {
return nil
}
return cat.RenameAttribute(classOID, name, *stmt.NewName)
}
func applyRenameTable(cat *core.Catalog, stmt *ast.RenameTableStmt) error {
if stmt.NewName == nil {
return nil
}
classOID, err := lookupClass(cat, stmt.Table)
if err != nil {
if stmt.MissingOk {
return nil
}
return err
}
return cat.RenameClass(classOID, *stmt.NewName)
}
func lookupClass(cat *core.Catalog, table *ast.TableName) (int64, error) {
if table == nil {
return 0, fmt.Errorf("missing table name")
}
nsOID, err := cat.NamespaceOID(nsName(table.Schema))
if err != nil {
return 0, err
}
return cat.ClassOID(nsOID, table.Name)
}
func rangeVarTableName(rv *ast.RangeVar) *ast.TableName {
tn := &ast.TableName{}
if rv.Schemaname != nil {
tn.Schema = *rv.Schemaname
}
if rv.Relname != nil {
tn.Name = *rv.Relname
}
return tn
}
func listItems(l *ast.List) []ast.Node {
if l == nil {
return nil
}
return l.Items
}
// applyCreateEnum records an enum as a type whose arguments are its labels,
// in order, the way pg_enum keeps them.
func applyCreateEnum(cat *core.Catalog, stmt *ast.CreateEnumStmt) error {
if stmt.TypeName == nil {
return fmt.Errorf("create type with nil name")
}
name := declaredTypeName(stmt.TypeName)
if name == "" {
return fmt.Errorf("create type with empty name")
}
if cat.TypeDeclared(name) {
return nil
}
var labels []core.TypeArg
for _, label := range listStrings(stmt.Vals) {
l := label
labels = append(labels, core.TypeArg{String: &l})
}
_, err := cat.CreateTypeWithArgs(core.TypeSpec{Name: name, Typtype: "e", Category: "E"}, labels)
return err
}
// applyCreateDomain records a domain: a type of its own that stands on its
// base, which is what it resolves through, and that may forbid NULL.
func applyCreateDomain(cat *core.Catalog, stmt *ast.CreateDomainStmt) error {
name := strings.ToLower(strings.Join(listStrings(stmt.Domainname), "."))
if name == "" || stmt.TypeName == nil {
return fmt.Errorf("create domain: missing name or type")
}
if cat.TypeDeclared(name) {
return nil
}
baseOID, err := cat.ResolveType(stmt.TypeName)
if err != nil {
return fmt.Errorf("domain %q: %w", name, err)
}
base, err := cat.LookupType(baseOID)
if err != nil {
return err
}
notNull := false
for _, item := range listItems(stmt.Constraints) {
if con, ok := item.(*ast.Constraint); ok && con.Contype == ast.ConstrTypeNotNull {
notNull = true
}
}
_, err = cat.CreateTypeWithArgs(core.TypeSpec{
Name: name,
Typtype: "d",
Category: base.Category,
BaseOID: baseOID,
NotNull: notNull,
}, nil)
return err
}
// applyCompositeType records a composite type as a type whose arguments are
// its fields, labelled by name.
func applyCompositeType(cat *core.Catalog, stmt *ast.CompositeTypeStmt) error {
if stmt.TypeName == nil {
return fmt.Errorf("create type with nil name")
}
name := declaredTypeName(stmt.TypeName)
if name == "" {
return fmt.Errorf("create type with empty name")
}
if cat.TypeDeclared(name) {
return nil
}
var fields []core.TypeArg
for _, item := range listItems(stmt.Coldeflist) {
col, ok := item.(*ast.ColumnDef)
if !ok || col.TypeName == nil {
continue
}
t := core.ColumnTypeExpr(col)
if t == nil {
continue
}
fields = append(fields, core.TypeArg{Label: col.Colname, Type: t})
}
_, err := cat.CreateTypeWithArgs(core.TypeSpec{Name: name, Typtype: "c", Category: "C"}, fields)
return err
}
// applyCreateRange records a range type over its subtype, which is what a
// bound of it has.
func applyCreateRange(cat *core.Catalog, stmt *ast.CreateRangeStmt) error {
name := strings.ToLower(strings.Join(listStrings(stmt.TypeName), "."))
if name == "" {
return fmt.Errorf("create type with empty name")
}
if cat.TypeDeclared(name) {
return nil
}
spec := core.TypeSpec{Name: name, Typtype: "r", Category: "R"}
for _, item := range listItems(stmt.Params) {
def, ok := item.(*ast.DefElem)
if !ok || def.Defname == nil || *def.Defname != "subtype" {
continue
}
tn, ok := def.Arg.(*ast.TypeName)
if !ok {
continue
}
oid, err := cat.ResolveType(tn)
if err != nil {
return fmt.Errorf("range %q: %w", name, err)
}
spec.ElementOID = oid
}
_, err := cat.CreateTypeWithArgs(spec, nil)
return err
}
// declaredTypeName is the name a CREATE TYPE gives, qualified by its schema
// when it names one.
func declaredTypeName(tn *ast.TypeName) string {
t := core.TypeExprOfTypeName(tn)
if t == nil {
return ""
}
if tn.Schema != "" && !strings.Contains(t.Name, ".") {
return strings.ToLower(tn.Schema) + "." + t.Name
}
return t.Name
}
func applyCreateFunction(cat *core.Catalog, stmt *ast.CreateFunctionStmt) error {
// A procedure returns nothing, so there is no result for a query to
// select and nothing worth recording.
if stmt.Func == nil || stmt.Func.Name == "" || stmt.ReturnType == nil {
return nil
}
returnOID, err := cat.ResolveType(stmt.ReturnType)
if err != nil {
return fmt.Errorf("function %q: %w", stmt.Func.Name, err)
}
var args []core.ProcArg
if stmt.Params != nil {
for _, item := range stmt.Params.Items {
p, ok := item.(*ast.FuncParam)
if !ok {
continue
}
switch p.Mode {
case ast.FuncParamOut, ast.FuncParamTable:
continue
}
argOID, err := cat.ResolveType(p.Type)
if err != nil {
return fmt.Errorf("function %q: %w", stmt.Func.Name, err)
}
arg := core.ProcArg{TypeOID: argOID, HasDefault: p.DefExpr != nil}
if p.Name != nil {
arg.Name = *p.Name
}
args = append(args, arg)
}
}
_, err = cat.CreateProc(core.ProcSpec{
Name: stmt.Func.Name,
ReturnTypeOID: returnOID,
ReturnSet: stmt.ReturnType != nil && stmt.ReturnType.Setof,
Args: args,
})
return err
}
func nsName(schema string) string {
if schema == "" {
return "public"
}
return schema
}
func resolveOrCreateNamespace(cat *core.Catalog, schema string) (int64, error) {
name := nsName(schema)
if oid, err := cat.NamespaceOID(name); err == nil {
return oid, nil
}
return cat.CreateNamespace(name)
}
// typeNotNull reports whether a column of the type can never be NULL
// because the type itself says so, as a domain declared NOT NULL does.
func typeNotNull(cat *core.Catalog, typeOID int64) bool {
info, err := cat.LookupType(typeOID)
return err == nil && info.NotNull
}
// columnTypeOID interns a column's type and returns its row.
func columnTypeOID(cat *core.Catalog, col *ast.ColumnDef) (int64, error) {
t := core.ColumnTypeExpr(col)
if t == nil {
return 0, fmt.Errorf("missing type name")
}
return cat.ResolveTypeExpr(t)
}
// declType is the type as the schema spelled it: an engine that folds or
// reduces the name for the catalog keeps the full spelling alongside.
func declType(tn *ast.TypeName) string {
if tn == nil {
return ""
}
if tn.Spelling != "" {
return tn.Spelling
}
return tn.Name
}