snapshot of https://github.com/sqlc-dev/sqlc.git (main, 2026-09-16; blobs > 131072B omitted)
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

This commit is contained in:
upstream-mirror
2026-09-16 02:17:34 +05:00
commit 0f4df23290
7340 changed files with 238743 additions and 0 deletions

View File

@@ -0,0 +1,142 @@
package catalog
import (
"github.com/sqlc-dev/sqlc/internal/sql/ast"
)
// Catalog describes a database instance consisting of metadata in which database objects are defined
type Catalog struct {
Comment string
DefaultSchema string
Name string
Schemas []*Schema
SearchPath []string
LoadExtension func(string) *Schema
// TODO: un-export
Extensions map[string]struct{}
}
// New creates a new catalog
func New(defaultSchema string) *Catalog {
newCatalog := &Catalog{
DefaultSchema: defaultSchema,
Schemas: make([]*Schema, 0),
Extensions: make(map[string]struct{}),
}
if newCatalog.DefaultSchema != "" {
newCatalog.Schemas = append(newCatalog.Schemas, &Schema{Name: defaultSchema})
}
return newCatalog
}
func (c *Catalog) Build(stmts []ast.Statement) error {
for i := range stmts {
if err := c.Update(stmts[i], nil); err != nil {
return err
}
}
return nil
}
func (c *Catalog) Update(stmt ast.Statement, colGen columnGenerator) error {
if stmt.Raw == nil {
return nil
}
var err error
switch n := stmt.Raw.Stmt.(type) {
case *ast.AlterTableStmt:
err = c.alterTable(n)
case *ast.AlterTableSetSchemaStmt:
err = c.alterTableSetSchema(n)
case *ast.AlterTypeAddValueStmt:
err = c.alterTypeAddValue(n)
case *ast.AlterTypeRenameValueStmt:
err = c.alterTypeRenameValue(n)
case *ast.AlterTypeSetSchemaStmt:
err = c.alterTypeSetSchema(n)
case *ast.CommentOnColumnStmt:
err = c.commentOnColumn(n)
case *ast.CommentOnSchemaStmt:
err = c.commentOnSchema(n)
case *ast.CommentOnTableStmt:
err = c.commentOnTable(n)
case *ast.CommentOnTypeStmt:
err = c.commentOnType(n)
case *ast.CommentOnViewStmt:
err = c.commentOnView(n)
case *ast.CompositeTypeStmt:
err = c.createCompositeType(n)
case *ast.CreateEnumStmt:
err = c.createEnum(n)
case *ast.CreateExtensionStmt:
err = c.createExtension(n)
case *ast.CreateFunctionStmt:
err = c.createFunction(n)
case *ast.CreateSchemaStmt:
err = c.createSchema(n)
case *ast.CreateTableStmt:
err = c.createTable(n)
case *ast.CreateTableAsStmt:
err = c.createTableAs(n, colGen)
case *ast.ViewStmt:
err = c.createView(n, colGen)
case *ast.DropFunctionStmt:
err = c.dropFunction(n)
case *ast.DropSchemaStmt:
err = c.dropSchema(n)
case *ast.DropTableStmt:
err = c.dropTable(n)
case *ast.DropTypeStmt:
err = c.dropType(n)
case *ast.RenameColumnStmt:
err = c.renameColumn(n)
case *ast.RenameTableStmt:
err = c.renameTable(n)
case *ast.RenameTypeStmt:
err = c.renameType(n)
case *ast.List:
for _, nn := range n.Items {
if err = c.Update(ast.Statement{
Raw: &ast.RawStmt{
Stmt: nn,
StmtLocation: stmt.Raw.StmtLocation,
StmtLen: stmt.Raw.StmtLen,
},
}, colGen); err != nil {
return err
}
}
}
return err
}

View File

@@ -0,0 +1,76 @@
package catalog
import (
"github.com/sqlc-dev/sqlc/internal/sql/ast"
"github.com/sqlc-dev/sqlc/internal/sql/sqlerr"
)
func (c *Catalog) commentOnColumn(stmt *ast.CommentOnColumnStmt) error {
_, t, err := c.getTable(stmt.Table)
if err != nil {
return err
}
for i := range t.Columns {
if t.Columns[i].Name == stmt.Col.Name {
if stmt.Comment != nil {
t.Columns[i].Comment = *stmt.Comment
} else {
t.Columns[i].Comment = ""
}
return nil
}
}
return sqlerr.ColumnNotFound(stmt.Table.Name, stmt.Col.Name)
}
func (c *Catalog) commentOnSchema(stmt *ast.CommentOnSchemaStmt) error {
s, err := c.getSchema(stmt.Schema.Str)
if err != nil {
return err
}
if stmt.Comment != nil {
s.Comment = *stmt.Comment
} else {
s.Comment = ""
}
return nil
}
func (c *Catalog) commentOnTable(stmt *ast.CommentOnTableStmt) error {
_, t, err := c.getTable(stmt.Table)
if err != nil {
return err
}
if stmt.Comment != nil {
t.Comment = *stmt.Comment
} else {
t.Comment = ""
}
return nil
}
func (c *Catalog) commentOnType(stmt *ast.CommentOnTypeStmt) error {
t, _, err := c.getType(stmt.Type)
if err != nil {
return err
}
if stmt.Comment != nil {
t.SetComment(*stmt.Comment)
} else {
t.SetComment("")
}
return nil
}
func (c *Catalog) commentOnView(stmt *ast.CommentOnViewStmt) error {
_, t, err := c.getTable(stmt.View)
if err != nil {
return err
}
if stmt.Comment != nil {
t.Comment = *stmt.Comment
} else {
t.Comment = ""
}
return nil
}

View File

@@ -0,0 +1,41 @@
package catalog
import (
"github.com/sqlc-dev/sqlc/internal/sql/ast"
)
func (c *Catalog) createExtension(stmt *ast.CreateExtensionStmt) error {
if stmt.Extname == nil {
return nil
}
// TODO: Implement IF NOT EXISTS
return c.loadExtension(*stmt.Extname)
}
// loadExtension adds what the engine knows of the named extension — or of
// the virtual table module that stands for one — to the default schema,
// once. An engine without extension data, or without data for this one,
// adds nothing.
func (c *Catalog) loadExtension(name string) error {
if _, exists := c.Extensions[name]; exists {
return nil
}
if c.LoadExtension == nil {
return nil
}
ext := c.LoadExtension(name)
if ext == nil {
return nil
}
if c.Extensions == nil {
c.Extensions = map[string]struct{}{}
}
c.Extensions[name] = struct{}{}
s, err := c.getSchema(c.DefaultSchema)
if err != nil {
return err
}
// TODO: Error on duplicate functions
s.Funcs = append(s.Funcs, ext.Funcs...)
return nil
}

View File

@@ -0,0 +1,122 @@
package catalog
import (
"errors"
"github.com/sqlc-dev/sqlc/internal/sql/ast"
"github.com/sqlc-dev/sqlc/internal/sql/sqlerr"
)
// Function describes a database function
//
// A database function is a method written to perform a specific operation on data within the database.
type Function struct {
Name string
Args []*Argument
ReturnType *ast.TypeName
Comment string
Desc string
ReturnTypeNullable bool
}
type Argument struct {
Name string
Type *ast.TypeName
HasDefault bool
Mode ast.FuncParamMode
}
func (f *Function) InArgs() []*Argument {
var args []*Argument
for _, a := range f.Args {
switch a.Mode {
case ast.FuncParamTable, ast.FuncParamOut:
continue
default:
args = append(args, a)
}
}
return args
}
func (f *Function) OutArgs() []*Argument {
var args []*Argument
for _, a := range f.Args {
switch a.Mode {
case ast.FuncParamOut:
args = append(args, a)
}
}
return args
}
func (c *Catalog) createFunction(stmt *ast.CreateFunctionStmt) error {
ns := stmt.Func.Schema
if ns == "" {
ns = c.DefaultSchema
}
s, err := c.getSchema(ns)
if err != nil {
return err
}
fn := &Function{
Name: stmt.Func.Name,
Args: make([]*Argument, len(stmt.Params.Items)),
ReturnType: stmt.ReturnType,
}
types := make([]*ast.TypeName, len(stmt.Params.Items))
for i, item := range stmt.Params.Items {
arg := item.(*ast.FuncParam)
var name string
if arg.Name != nil {
name = *arg.Name
}
fn.Args[i] = &Argument{
Name: name,
Type: arg.Type,
Mode: arg.Mode,
HasDefault: arg.DefExpr != nil,
}
types[i] = arg.Type
}
_, idx, err := s.getFunc(stmt.Func, types)
if err == nil && !stmt.Replace {
return sqlerr.RelationExists(stmt.Func.Name)
}
if idx >= 0 {
s.Funcs[idx] = fn
} else {
s.Funcs = append(s.Funcs, fn)
}
return nil
}
func (c *Catalog) dropFunction(stmt *ast.DropFunctionStmt) error {
for _, spec := range stmt.Funcs {
ns := spec.Name.Schema
if ns == "" {
ns = c.DefaultSchema
}
s, err := c.getSchema(ns)
if errors.Is(err, sqlerr.NotFound) && stmt.MissingOk {
continue
} else if err != nil {
return err
}
var idx int
if spec.HasArgs {
_, idx, err = s.getFunc(spec.Name, spec.Args)
} else {
_, idx, err = s.getFuncByName(spec.Name)
}
if errors.Is(err, sqlerr.NotFound) && stmt.MissingOk {
continue
} else if err != nil {
return err
}
s.Funcs = append(s.Funcs[:idx], s.Funcs[idx+1:]...)
}
return nil
}

View File

@@ -0,0 +1,133 @@
package catalog
import (
"fmt"
"strings"
"github.com/sqlc-dev/sqlc/internal/sql/ast"
"github.com/sqlc-dev/sqlc/internal/sql/sqlerr"
)
func (c *Catalog) schemasToSearch(ns string) []string {
if ns == "" {
ns = c.DefaultSchema
}
return append(c.SearchPath, ns)
}
func (c *Catalog) ListFuncsByName(rel *ast.FuncName) ([]Function, error) {
var funcs []Function
lowered := strings.ToLower(rel.Name)
for _, ns := range c.schemasToSearch(rel.Schema) {
s, err := c.getSchema(ns)
if err != nil {
return nil, err
}
for i := range s.Funcs {
if strings.ToLower(s.Funcs[i].Name) == lowered {
funcs = append(funcs, *s.Funcs[i])
}
}
}
return funcs, nil
}
func (c *Catalog) ResolveFuncCall(call *ast.FuncCall) (*Function, error) {
// Do not validate unknown functions
funs, err := c.ListFuncsByName(call.Func)
if err != nil || len(funs) == 0 {
return nil, sqlerr.FunctionNotFound(call.Func.Name)
}
// https://www.postgresql.org/docs/current/sql-syntax-calling-funcs.html
var positional []ast.Node
var named []*ast.NamedArgExpr
if call.Args != nil {
for _, arg := range call.Args.Items {
if narg, ok := arg.(*ast.NamedArgExpr); ok {
named = append(named, narg)
} else {
// The mixed notation combines positional and named notation.
// However, as already mentioned, named arguments cannot precede
// positional arguments.
if len(named) > 0 {
return nil, &sqlerr.Error{
Code: "",
Message: "positional argument cannot follow named argument",
Location: call.Pos(),
}
}
positional = append(positional, arg)
}
}
}
for _, fun := range funs {
args := fun.InArgs()
var defaults int
var variadic bool
known := map[string]struct{}{}
for _, arg := range args {
if arg.HasDefault {
defaults += 1
}
if arg.Mode == ast.FuncParamVariadic {
variadic = true
defaults += 1
}
if arg.Name != "" {
known[arg.Name] = struct{}{}
}
}
if variadic {
if (len(named) + len(positional)) < (len(args) - defaults) {
continue
}
} else {
if (len(named) + len(positional)) > len(args) {
continue
}
if (len(named) + len(positional)) < (len(args) - defaults) {
continue
}
}
// Validate that the provided named arguments exist in the function
var unknownArgName bool
for _, expr := range named {
if expr.Name != nil {
if _, found := known[*expr.Name]; !found {
unknownArgName = true
}
}
}
if unknownArgName {
continue
}
return &fun, nil
}
var sig []string
for range call.Args.Items {
sig = append(sig, "unknown")
}
return nil, &sqlerr.Error{
Code: "42883",
Message: fmt.Sprintf("function %s(%s) does not exist", call.Func.Name, strings.Join(sig, ", ")),
Location: call.Pos(),
// Hint: "No function matches the given name and argument types. You might need to add explicit type casts.",
}
}
func (c *Catalog) GetTable(rel *ast.TableName) (Table, error) {
_, table, err := c.getTable(rel)
if table == nil {
return Table{}, err
} else {
return *table, err
}
}

View File

@@ -0,0 +1,134 @@
package catalog
import (
"fmt"
"strings"
"github.com/sqlc-dev/sqlc/internal/sql/ast"
"github.com/sqlc-dev/sqlc/internal/sql/sqlerr"
)
// Schema describes how the data in a relational database may relate to other tables or other data models
type Schema struct {
Name string
Tables []*Table
Types []Type
Funcs []*Function
Comment string
}
func (s *Schema) getFunc(rel *ast.FuncName, tns []*ast.TypeName) (*Function, int, error) {
for i := range s.Funcs {
if !strings.EqualFold(s.Funcs[i].Name, rel.Name) {
continue
}
args := s.Funcs[i].InArgs()
if len(args) != len(tns) {
continue
}
found := true
for j := range args {
if !sameType(s.Funcs[i].Args[j].Type, tns[j]) {
found = false
break
}
}
if !found {
continue
}
return s.Funcs[i], i, nil
}
return nil, -1, sqlerr.RelationNotFound(rel.Name)
}
func (s *Schema) getFuncByName(rel *ast.FuncName) (*Function, int, error) {
idx := -1
name := strings.ToLower(rel.Name)
for i := range s.Funcs {
lowered := strings.ToLower(s.Funcs[i].Name)
if lowered == name && idx >= 0 {
return nil, -1, sqlerr.FunctionNotUnique(rel.Name)
}
if lowered == name {
idx = i
}
}
if idx < 0 {
return nil, -1, sqlerr.RelationNotFound(rel.Name)
}
return s.Funcs[idx], idx, nil
}
func (s *Schema) getTable(rel *ast.TableName) (*Table, int, error) {
for i := range s.Tables {
if s.Tables[i].Rel.Name == rel.Name {
return s.Tables[i], i, nil
}
}
return nil, -1, sqlerr.RelationNotFound(rel.Name)
}
func (s *Schema) getType(rel *ast.TypeName) (Type, int, error) {
for i := range s.Types {
switch typ := s.Types[i].(type) {
case *Enum:
if typ.Name == rel.Name {
return s.Types[i], i, nil
}
case *CompositeType:
if typ.Name == rel.Name {
return s.Types[i], i, nil
}
}
}
return nil, -1, sqlerr.TypeNotFound(rel.Name)
}
func (c *Catalog) getSchema(name string) (*Schema, error) {
for i := range c.Schemas {
if c.Schemas[i].Name == name {
return c.Schemas[i], nil
}
}
return nil, sqlerr.SchemaNotFound(name)
}
func (c *Catalog) createSchema(stmt *ast.CreateSchemaStmt) error {
if stmt.Name == nil {
return fmt.Errorf("create schema: empty name")
}
if _, err := c.getSchema(*stmt.Name); err == nil {
// If the default schema already exists, treat additional CREATE SCHEMA
// statements as no-ops.
if *stmt.Name == c.DefaultSchema {
return nil
}
if !stmt.IfNotExists {
return sqlerr.SchemaExists(*stmt.Name)
}
}
c.Schemas = append(c.Schemas, &Schema{Name: *stmt.Name})
return nil
}
func (c *Catalog) dropSchema(stmt *ast.DropSchemaStmt) error {
// TODO: n^2 in the worst-case
for _, name := range stmt.Schemas {
idx := -1
for i := range c.Schemas {
if c.Schemas[i].Name == name.Str {
idx = i
}
}
if idx == -1 {
if stmt.MissingOk {
continue
}
return sqlerr.SchemaNotFound(name.Str)
}
c.Schemas = append(c.Schemas[:idx], c.Schemas[idx+1:]...)
}
return nil
}

View File

@@ -0,0 +1,505 @@
package catalog
import (
"errors"
"fmt"
"github.com/sqlc-dev/sqlc/internal/sql/ast"
"github.com/sqlc-dev/sqlc/internal/sql/sqlerr"
)
// Table describes a relational database table
//
// A database table is a collection of related data held in a table format within a database.
// It consists of columns and rows.
type Table struct {
Rel *ast.TableName
Columns []*Column
Comment string
}
func checkMissing(err error, missingOK bool) error {
var serr *sqlerr.Error
if errors.As(err, &serr) {
if serr.Err == sqlerr.NotFound && missingOK {
return nil
}
}
return err
}
func (table *Table) isExistColumn(cmd *ast.AlterTableCmd) (int, error) {
for i, c := range table.Columns {
if c.Name == *cmd.Name {
return i, nil
}
}
if !cmd.MissingOk {
return -1, sqlerr.ColumnNotFound(table.Rel.Name, *cmd.Name)
}
// Missing column is allowed
return -1, nil
}
func (c *Catalog) addColumn(table *Table, cmd *ast.AlterTableCmd) error {
for _, c := range table.Columns {
if c.Name == cmd.Def.Colname {
if !cmd.MissingOk {
return sqlerr.ColumnExists(table.Rel.Name, cmd.Def.Colname)
}
return nil
}
}
tc, err := c.defineColumn(table.Rel, cmd.Def)
if err != nil {
return err
}
table.Columns = append(table.Columns, tc)
return nil
}
func (table *Table) alterColumnType(cmd *ast.AlterTableCmd) error {
index, err := table.isExistColumn(cmd)
if err != nil {
return err
}
if index >= 0 {
table.Columns[index].Type = *cmd.Def.TypeName
table.Columns[index].IsArray = cmd.Def.IsArray
table.Columns[index].ArrayDims = cmd.Def.ArrayDims
}
return nil
}
func (c *Catalog) dropColumn(table *Table, cmd *ast.AlterTableCmd) error {
index, err := table.isExistColumn(cmd)
if err != nil {
return err
}
if index < 0 {
return nil
}
col := table.Columns[index]
if col.linkedType {
drop := &ast.DropTypeStmt{
Types: []*ast.TypeName{
&col.Type,
},
}
if err := c.dropType(drop); err != nil {
return err
}
}
table.Columns = append(table.Columns[:index], table.Columns[index+1:]...)
return nil
}
func (table *Table) dropNotNull(cmd *ast.AlterTableCmd) error {
index, err := table.isExistColumn(cmd)
if err != nil {
return err
}
if index >= 0 {
table.Columns[index].IsNotNull = false
}
return nil
}
func (table *Table) setNotNull(cmd *ast.AlterTableCmd) error {
index, err := table.isExistColumn(cmd)
if err != nil {
return err
}
if index >= 0 {
table.Columns[index].IsNotNull = true
}
return nil
}
// Column describes a set of data values of a particular type in a relational database table
//
// TODO: Should this just be ast Nodes?
type Column struct {
Name string
Type ast.TypeName
IsNotNull bool
IsUnsigned bool
IsArray bool
ArrayDims int
Comment string
Length *int
linkedType bool
}
// An interface is used to resolve a circular import between the catalog and compiler packages.
// The createView function requires access to functions in the compiler package to parse the SELECT
// statement that defines the view.
type columnGenerator interface {
OutputColumns(node ast.Node) ([]*Column, error)
}
func (c *Catalog) getTable(tableName *ast.TableName) (*Schema, *Table, error) {
schemaName := tableName.Schema
if schemaName == "" {
schemaName = c.DefaultSchema
}
var schema *Schema
for i := range c.Schemas {
if c.Schemas[i].Name == schemaName {
schema = c.Schemas[i]
break
}
}
if schema == nil {
return nil, nil, sqlerr.SchemaNotFound(schemaName)
}
table, _, err := schema.getTable(tableName)
if err != nil {
return nil, nil, err
}
return schema, table, nil
}
func isStmtImplemented(stmt *ast.AlterTableStmt) bool {
var implemented bool
for _, item := range stmt.Cmds.Items {
switch cmd := item.(type) {
case *ast.AlterTableCmd:
switch cmd.Subtype {
case ast.AT_AddColumn:
implemented = true
case ast.AT_AlterColumnType:
implemented = true
case ast.AT_DropColumn:
implemented = true
case ast.AT_DropNotNull:
implemented = true
case ast.AT_SetNotNull:
implemented = true
}
}
}
return implemented
}
func (c *Catalog) alterTable(stmt *ast.AlterTableStmt) error {
if !isStmtImplemented(stmt) {
return nil
}
_, table, err := c.getTable(stmt.Table)
if err != nil {
return checkMissing(err, stmt.MissingOk)
}
for _, item := range stmt.Cmds.Items {
switch cmd := item.(type) {
case *ast.AlterTableCmd:
switch cmd.Subtype {
case ast.AT_AddColumn:
if err := c.addColumn(table, cmd); err != nil {
return err
}
case ast.AT_AlterColumnType:
if err := table.alterColumnType(cmd); err != nil {
return err
}
case ast.AT_DropColumn:
if err := c.dropColumn(table, cmd); err != nil {
return err
}
case ast.AT_DropNotNull:
if err := table.dropNotNull(cmd); err != nil {
return err
}
case ast.AT_SetNotNull:
if err := table.setNotNull(cmd); err != nil {
return err
}
}
}
}
return nil
}
func (c *Catalog) alterTableSetSchema(stmt *ast.AlterTableSetSchemaStmt) error {
ns := stmt.Table.Schema
if ns == "" {
ns = c.DefaultSchema
}
oldSchema, err := c.getSchema(ns)
if err != nil {
return checkMissing(err, stmt.MissingOk)
}
tbl, idx, err := oldSchema.getTable(stmt.Table)
if err != nil {
return checkMissing(err, stmt.MissingOk)
}
tbl.Rel.Schema = *stmt.NewSchema
newSchema, err := c.getSchema(*stmt.NewSchema)
if err != nil {
return err
}
if _, _, err := newSchema.getTable(stmt.Table); err == nil {
return sqlerr.RelationExists(stmt.Table.Name)
}
oldSchema.Tables = append(oldSchema.Tables[:idx], oldSchema.Tables[idx+1:]...)
newSchema.Tables = append(newSchema.Tables, tbl)
return nil
}
func (c *Catalog) createTable(stmt *ast.CreateTableStmt) error {
// A virtual table's module is the engine's word for the extension it
// needs, the way CREATE EXTENSION is PostgreSQL's.
if stmt.Using != "" {
if err := c.loadExtension(stmt.Using); err != nil {
return err
}
}
ns := stmt.Name.Schema
if ns == "" {
ns = c.DefaultSchema
}
schema, err := c.getSchema(ns)
if err != nil {
return err
}
_, _, err = schema.getTable(stmt.Name)
if err == nil && stmt.IfNotExists {
return nil
} else if err == nil {
return sqlerr.RelationExists(stmt.Name.Name)
}
tbl := Table{Rel: stmt.Name, Comment: stmt.Comment}
coltype := make(map[string]ast.TypeName) // used to check for duplicate column names
seen := make(map[string]bool) // used to check for duplicate column names
for _, inheritTable := range stmt.Inherits {
t, _, err := schema.getTable(inheritTable)
if err != nil {
return err
}
// check and ignore duplicate columns
for _, col := range t.Columns {
if notNull, ok := seen[col.Name]; ok {
seen[col.Name] = notNull || col.IsNotNull
if a, ok := coltype[col.Name]; ok {
if !sameType(&a, &col.Type) {
return fmt.Errorf("column %q has a type conflict", col.Name)
}
}
continue
}
seen[col.Name] = col.IsNotNull
coltype[col.Name] = col.Type
tbl.Columns = append(tbl.Columns, col)
}
}
if stmt.ReferTable != nil {
_, original, err := c.getTable(stmt.ReferTable)
if err != nil {
return err
}
for _, col := range original.Columns {
newCol := *col // make a copy, so changes to the ReferTable don't propagate
tbl.Columns = append(tbl.Columns, &newCol)
}
}
for _, col := range stmt.Cols {
// The legacy catalog has no notion of a hidden column, so it drops
// them rather than list them on the table.
if col.IsHidden {
continue
}
if notNull, ok := seen[col.Colname]; ok {
seen[col.Colname] = notNull || col.IsNotNull
if a, ok := coltype[col.Colname]; ok {
if !sameType(&a, col.TypeName) {
return fmt.Errorf("column %q has a type conflict", col.Colname)
}
}
continue
}
tc, err := c.defineColumn(stmt.Name, col)
if err != nil {
return err
}
tbl.Columns = append(tbl.Columns, tc)
}
// If one of the merged columns was not null, mark the column as not null
for i := range tbl.Columns {
if notNull, ok := seen[tbl.Columns[i].Name]; ok {
tbl.Columns[i].IsNotNull = notNull
}
}
schema.Tables = append(schema.Tables, &tbl)
return nil
}
func (c *Catalog) defineColumn(table *ast.TableName, col *ast.ColumnDef) (*Column, error) {
tc := &Column{
Name: col.Colname,
Type: *col.TypeName,
IsNotNull: col.IsNotNull,
IsUnsigned: col.IsUnsigned,
IsArray: col.IsArray,
ArrayDims: col.ArrayDims,
Comment: col.Comment,
Length: col.Length,
}
if col.Vals != nil {
typeName := ast.TypeName{
Name: fmt.Sprintf("%s_%s", table.Name, col.Colname),
}
s := &ast.CreateEnumStmt{TypeName: &typeName, Vals: col.Vals}
if err := c.createEnum(s); err != nil {
return nil, err
}
tc.Type = typeName
tc.linkedType = true
}
return tc, nil
}
func (c *Catalog) dropTable(stmt *ast.DropTableStmt) error {
for _, name := range stmt.Tables {
ns := name.Schema
if ns == "" {
ns = c.DefaultSchema
}
schema, err := c.getSchema(ns)
if errors.Is(err, sqlerr.NotFound) && stmt.IfExists {
continue
} else if err != nil {
return err
}
tbl, idx, err := schema.getTable(name)
if errors.Is(err, sqlerr.NotFound) && stmt.IfExists {
continue
} else if err != nil {
return err
}
drop := &ast.DropTypeStmt{}
for _, col := range tbl.Columns {
if !col.linkedType {
continue
}
drop.Types = append(drop.Types, &col.Type)
}
if err := c.dropType(drop); err != nil {
return err
}
schema.Tables = append(schema.Tables[:idx], schema.Tables[idx+1:]...)
}
return nil
}
func (c *Catalog) renameColumn(stmt *ast.RenameColumnStmt) error {
_, tbl, err := c.getTable(stmt.Table)
if err != nil {
return checkMissing(err, stmt.MissingOk)
}
idx := -1
for i := range tbl.Columns {
if tbl.Columns[i].Name == stmt.Col.Name {
idx = i
}
if tbl.Columns[i].Name == *stmt.NewName {
return sqlerr.ColumnExists(tbl.Rel.Name, *stmt.NewName)
}
}
if idx == -1 {
return sqlerr.ColumnNotFound(tbl.Rel.Name, stmt.Col.Name)
}
tbl.Columns[idx].Name = *stmt.NewName
if tbl.Columns[idx].linkedType {
name := fmt.Sprintf("%s_%s", tbl.Rel.Name, *stmt.NewName)
rename := &ast.RenameTypeStmt{
Type: &tbl.Columns[idx].Type,
NewName: &name,
}
if err := c.renameType(rename); err != nil {
return err
}
}
return nil
}
func (c *Catalog) renameTable(stmt *ast.RenameTableStmt) error {
sch, tbl, err := c.getTable(stmt.Table)
if err != nil {
return checkMissing(err, stmt.MissingOk)
}
if _, _, err := sch.getTable(&ast.TableName{Name: *stmt.NewName}); err == nil {
return sqlerr.RelationExists(*stmt.NewName)
}
if stmt.NewName != nil {
tbl.Rel.Name = *stmt.NewName
}
for idx := range tbl.Columns {
if tbl.Columns[idx].linkedType {
name := fmt.Sprintf("%s_%s", *stmt.NewName, tbl.Columns[idx].Name)
rename := &ast.RenameTypeStmt{
Type: &tbl.Columns[idx].Type,
NewName: &name,
}
if err := c.renameType(rename); err != nil {
return err
}
}
}
return nil
}
func (c *Catalog) createTableAs(stmt *ast.CreateTableAsStmt, colGen columnGenerator) error {
cols, err := colGen.OutputColumns(stmt.Query)
if err != nil {
return err
}
catName := ""
if stmt.Into.Rel.Catalogname != nil {
catName = *stmt.Into.Rel.Catalogname
}
schemaName := ""
if stmt.Into.Rel.Schemaname != nil {
schemaName = *stmt.Into.Rel.Schemaname
}
tbl := Table{
Rel: &ast.TableName{
Catalog: catName,
Schema: schemaName,
Name: *stmt.Into.Rel.Relname,
},
Columns: cols,
}
ns := tbl.Rel.Schema
if ns == "" {
ns = c.DefaultSchema
}
schema, err := c.getSchema(ns)
if err != nil {
return err
}
_, _, err = schema.getTable(tbl.Rel)
if err == nil {
return sqlerr.RelationExists(tbl.Rel.Name)
}
schema.Tables = append(schema.Tables, &tbl)
return nil
}

View File

@@ -0,0 +1,374 @@
package catalog
import (
"errors"
"fmt"
"github.com/sqlc-dev/sqlc/internal/sql/ast"
"github.com/sqlc-dev/sqlc/internal/sql/sqlerr"
)
type Type interface {
isType()
SetComment(string)
}
type Enum struct {
Name string
Vals []string
Comment string
}
func (e *Enum) SetComment(c string) {
e.Comment = c
}
func (e *Enum) isType() {
}
type CompositeType struct {
Name string
Comment string
}
func (ct *CompositeType) isType() {
}
func (ct *CompositeType) SetComment(c string) {
ct.Comment = c
}
func sameType(a, b *ast.TypeName) bool {
if a.Catalog != b.Catalog {
return false
}
// The pg_catalog schema is searched by default, so take that into
// account when comparing schemas
aSchema := a.Schema
bSchema := b.Schema
if aSchema == "pg_catalog" {
aSchema = ""
}
if bSchema == "pg_catalog" {
bSchema = ""
}
if aSchema != bSchema {
return false
}
if a.Name != b.Name {
return false
}
return true
}
func (c *Catalog) createEnum(stmt *ast.CreateEnumStmt) error {
ns := stmt.TypeName.Schema
if ns == "" {
ns = c.DefaultSchema
}
schema, err := c.getSchema(ns)
if err != nil {
return err
}
// Because tables have associated data types, the type name must also
// be distinct from the name of any existing table in the same
// schema.
// https://www.postgresql.org/docs/current/sql-createtype.html
tbl := &ast.TableName{
Name: stmt.TypeName.Name,
}
if _, _, err := schema.getTable(tbl); err == nil {
return sqlerr.RelationExists(tbl.Name)
}
if _, _, err := schema.getType(stmt.TypeName); err == nil {
return sqlerr.TypeExists(tbl.Name)
}
schema.Types = append(schema.Types, &Enum{
Name: stmt.TypeName.Name,
Vals: stringSlice(stmt.Vals),
})
return nil
}
func stringSlice(list *ast.List) []string {
items := []string{}
for _, item := range list.Items {
if n, ok := item.(*ast.String); ok {
items = append(items, n.Str)
}
}
return items
}
func (c *Catalog) getType(rel *ast.TypeName) (Type, int, error) {
ns := rel.Schema
if ns == "" {
ns = c.DefaultSchema
}
s, err := c.getSchema(ns)
if err != nil {
return nil, -1, err
}
return s.getType(rel)
}
func (c *Catalog) createCompositeType(stmt *ast.CompositeTypeStmt) error {
ns := stmt.TypeName.Schema
if ns == "" {
ns = c.DefaultSchema
}
schema, err := c.getSchema(ns)
if err != nil {
return err
}
// Because tables have associated data types, the type name must also
// be distinct from the name of any existing table in the same
// schema.
// https://www.postgresql.org/docs/current/sql-createtype.html
tbl := &ast.TableName{
Name: stmt.TypeName.Name,
}
if _, _, err := schema.getTable(tbl); err == nil {
return sqlerr.RelationExists(tbl.Name)
}
if _, _, err := schema.getType(stmt.TypeName); err == nil {
return sqlerr.TypeExists(tbl.Name)
}
schema.Types = append(schema.Types, &CompositeType{
Name: stmt.TypeName.Name,
})
return nil
}
func (c *Catalog) alterTypeRenameValue(stmt *ast.AlterTypeRenameValueStmt) error {
ns := stmt.Type.Schema
if ns == "" {
ns = c.DefaultSchema
}
schema, err := c.getSchema(ns)
if err != nil {
return err
}
typ, _, err := schema.getType(stmt.Type)
if err != nil {
return err
}
enum, ok := typ.(*Enum)
if !ok {
return fmt.Errorf("type is not an enum: %T", stmt.Type)
}
oldIndex := -1
newIndex := -1
for i, val := range enum.Vals {
if val == *stmt.OldValue {
oldIndex = i
}
if val == *stmt.NewValue {
newIndex = i
}
}
if oldIndex < 0 {
return fmt.Errorf("type %T does not have value %s", stmt.Type, *stmt.OldValue)
}
if newIndex >= 0 {
return fmt.Errorf("type %T already has value %s", stmt.Type, *stmt.NewValue)
}
enum.Vals[oldIndex] = *stmt.NewValue
return nil
}
func (c *Catalog) alterTypeAddValue(stmt *ast.AlterTypeAddValueStmt) error {
ns := stmt.Type.Schema
if ns == "" {
ns = c.DefaultSchema
}
schema, err := c.getSchema(ns)
if err != nil {
return err
}
typ, _, err := schema.getType(stmt.Type)
if err != nil {
return err
}
enum, ok := typ.(*Enum)
if !ok {
return fmt.Errorf("type is not an enum: %T", stmt.Type)
}
existingIndex := -1
for i, val := range enum.Vals {
if val == *stmt.NewValue {
existingIndex = i
}
}
if existingIndex >= 0 {
if !stmt.SkipIfNewValExists {
return fmt.Errorf("enum %s already has value %s", enum.Name, *stmt.NewValue)
} else {
return nil
}
}
insertIndex := len(enum.Vals)
if stmt.NewValHasNeighbor {
foundNeighbor := false
for i, val := range enum.Vals {
if val == *stmt.NewValNeighbor {
if stmt.NewValIsAfter {
insertIndex = i + 1
} else {
insertIndex = i
}
foundNeighbor = true
break
}
}
if !foundNeighbor {
return fmt.Errorf("enum %s unable to find existing neighbor value %s for new value %s", enum.Name, *stmt.NewValNeighbor, *stmt.NewValue)
}
}
if insertIndex == len(enum.Vals) {
enum.Vals = append(enum.Vals, *stmt.NewValue)
} else {
enum.Vals = append(enum.Vals[:insertIndex+1], enum.Vals[insertIndex:]...)
enum.Vals[insertIndex] = *stmt.NewValue
}
return nil
}
func (c *Catalog) alterTypeSetSchema(stmt *ast.AlterTypeSetSchemaStmt) error {
ns := stmt.Type.Schema
if ns == "" {
ns = c.DefaultSchema
}
oldSchema, err := c.getSchema(ns)
if err != nil {
return err
}
typ, idx, err := oldSchema.getType(stmt.Type)
if err != nil {
return err
}
oldType := *stmt.Type
stmt.Type.Schema = *stmt.NewSchema
newSchema, err := c.getSchema(*stmt.NewSchema)
if err != nil {
return err
}
// Because tables have associated data types, the type name must also
// be distinct from the name of any existing table in the same
// schema.
// https://www.postgresql.org/docs/current/sql-createtype.html
tbl := &ast.TableName{
Name: stmt.Type.Name,
}
if _, _, err := newSchema.getTable(tbl); err == nil {
return sqlerr.RelationExists(tbl.Name)
}
if _, _, err := newSchema.getType(stmt.Type); err == nil {
return sqlerr.TypeExists(stmt.Type.Name)
}
oldSchema.Types = append(oldSchema.Types[:idx], oldSchema.Types[idx+1:]...)
newSchema.Types = append(newSchema.Types, typ)
// Update all the table columns with the new type
for _, schema := range c.Schemas {
for _, table := range schema.Tables {
for _, column := range table.Columns {
if column.Type == oldType {
column.Type.Schema = *stmt.NewSchema
}
}
}
}
return nil
}
func (c *Catalog) dropType(stmt *ast.DropTypeStmt) error {
for _, name := range stmt.Types {
ns := name.Schema
if ns == "" {
ns = c.DefaultSchema
}
schema, err := c.getSchema(ns)
if errors.Is(err, sqlerr.NotFound) && stmt.IfExists {
continue
} else if err != nil {
return err
}
_, idx, err := schema.getType(name)
if errors.Is(err, sqlerr.NotFound) && stmt.IfExists {
continue
} else if err != nil {
return err
}
schema.Types = append(schema.Types[:idx], schema.Types[idx+1:]...)
}
return nil
}
func (c *Catalog) renameType(stmt *ast.RenameTypeStmt) error {
if stmt.NewName == nil {
return fmt.Errorf("rename type: empty name")
}
newName := *stmt.NewName
ns := stmt.Type.Schema
if ns == "" {
ns = c.DefaultSchema
}
schema, err := c.getSchema(ns)
if err != nil {
return err
}
ityp, idx, err := schema.getType(stmt.Type)
if err != nil {
return err
}
if _, _, err := schema.getTable(&ast.TableName{Name: newName}); err == nil {
return sqlerr.RelationExists(newName)
}
if _, _, err := schema.getType(&ast.TypeName{Name: newName}); err == nil {
return sqlerr.TypeExists(newName)
}
switch typ := ityp.(type) {
case *CompositeType:
schema.Types[idx] = &CompositeType{
Name: newName,
Comment: typ.Comment,
}
case *Enum:
schema.Types[idx] = &Enum{
Name: newName,
Vals: typ.Vals,
Comment: typ.Comment,
}
default:
return fmt.Errorf("unsupported type: %T", typ)
}
// Update all the table columns with the new type
for _, schema := range c.Schemas {
for _, table := range schema.Tables {
for _, column := range table.Columns {
if column.Type == *stmt.Type {
column.Type.Name = newName
}
}
}
}
return nil
}

View File

@@ -0,0 +1,52 @@
package catalog
import (
"github.com/sqlc-dev/sqlc/internal/sql/ast"
"github.com/sqlc-dev/sqlc/internal/sql/sqlerr"
)
func (c *Catalog) createView(stmt *ast.ViewStmt, colGen columnGenerator) error {
cols, err := colGen.OutputColumns(stmt.Query)
if err != nil {
return err
}
catName := ""
if stmt.View.Catalogname != nil {
catName = *stmt.View.Catalogname
}
schemaName := ""
if stmt.View.Schemaname != nil {
schemaName = *stmt.View.Schemaname
}
tbl := Table{
Rel: &ast.TableName{
Catalog: catName,
Schema: schemaName,
Name: *stmt.View.Relname,
},
Columns: cols,
}
ns := tbl.Rel.Schema
if ns == "" {
ns = c.DefaultSchema
}
schema, err := c.getSchema(ns)
if err != nil {
return err
}
_, existingIdx, err := schema.getTable(tbl.Rel)
if err == nil && !stmt.Replace {
return sqlerr.RelationExists(tbl.Rel.Name)
}
if stmt.Replace && err == nil {
schema.Tables[existingIdx] = &tbl
} else {
schema.Tables = append(schema.Tables, &tbl)
}
return nil
}