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
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:
142
internal/sql/catalog/catalog.go
Normal file
142
internal/sql/catalog/catalog.go
Normal 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
|
||||
}
|
||||
76
internal/sql/catalog/comment_on.go
Normal file
76
internal/sql/catalog/comment_on.go
Normal 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
|
||||
}
|
||||
41
internal/sql/catalog/extension.go
Normal file
41
internal/sql/catalog/extension.go
Normal 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
|
||||
}
|
||||
122
internal/sql/catalog/func.go
Normal file
122
internal/sql/catalog/func.go
Normal 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
|
||||
}
|
||||
133
internal/sql/catalog/public.go
Normal file
133
internal/sql/catalog/public.go
Normal 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
|
||||
}
|
||||
}
|
||||
134
internal/sql/catalog/schema.go
Normal file
134
internal/sql/catalog/schema.go
Normal 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
|
||||
}
|
||||
505
internal/sql/catalog/table.go
Normal file
505
internal/sql/catalog/table.go
Normal 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
|
||||
}
|
||||
374
internal/sql/catalog/types.go
Normal file
374
internal/sql/catalog/types.go
Normal 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
|
||||
}
|
||||
52
internal/sql/catalog/view.go
Normal file
52
internal/sql/catalog/view.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user