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
799 lines
21 KiB
Go
799 lines
21 KiB
Go
package postgresql
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
|
|
nodes "github.com/sqlc-dev/oliphant"
|
|
"github.com/sqlc-dev/oliphant/parser"
|
|
|
|
"github.com/sqlc-dev/sqlc/internal/source"
|
|
"github.com/sqlc-dev/sqlc/internal/sql/ast"
|
|
"github.com/sqlc-dev/sqlc/internal/sql/sqlerr"
|
|
)
|
|
|
|
var Parse = nodes.Parse
|
|
var Fingerprint = nodes.Fingerprint
|
|
|
|
func stringSlice(list *nodes.List) []string {
|
|
items := []string{}
|
|
for _, item := range list.Items {
|
|
if n, ok := item.Node.(*nodes.Node_String_); ok {
|
|
items = append(items, n.String_.Sval)
|
|
}
|
|
}
|
|
return items
|
|
}
|
|
|
|
func stringSliceFromNodes(s []*nodes.Node) []string {
|
|
var items []string
|
|
for _, item := range s {
|
|
if n, ok := item.Node.(*nodes.Node_String_); ok {
|
|
items = append(items, n.String_.Sval)
|
|
}
|
|
}
|
|
return items
|
|
}
|
|
|
|
type relation struct {
|
|
Catalog string
|
|
Schema string
|
|
Name string
|
|
}
|
|
|
|
func (r relation) TableName() *ast.TableName {
|
|
return &ast.TableName{
|
|
Catalog: r.Catalog,
|
|
Schema: r.Schema,
|
|
Name: r.Name,
|
|
}
|
|
}
|
|
|
|
func (r relation) TypeName() *ast.TypeName {
|
|
return &ast.TypeName{
|
|
Catalog: r.Catalog,
|
|
Schema: r.Schema,
|
|
Name: r.Name,
|
|
}
|
|
}
|
|
|
|
func (r relation) FuncName() *ast.FuncName {
|
|
return &ast.FuncName{
|
|
Catalog: r.Catalog,
|
|
Schema: r.Schema,
|
|
Name: r.Name,
|
|
}
|
|
}
|
|
|
|
func parseRelationFromNodes(list []*nodes.Node) (*relation, error) {
|
|
parts := stringSliceFromNodes(list)
|
|
switch len(parts) {
|
|
case 1:
|
|
return &relation{
|
|
Name: parts[0],
|
|
}, nil
|
|
case 2:
|
|
return &relation{
|
|
Schema: parts[0],
|
|
Name: parts[1],
|
|
}, nil
|
|
case 3:
|
|
return &relation{
|
|
Catalog: parts[0],
|
|
Schema: parts[1],
|
|
Name: parts[2],
|
|
}, nil
|
|
default:
|
|
return nil, fmt.Errorf("invalid name: %s", joinNodes(list, "."))
|
|
}
|
|
}
|
|
|
|
func parseRelationFromRangeVar(rv *nodes.RangeVar) *relation {
|
|
return &relation{
|
|
Catalog: rv.Catalogname,
|
|
Schema: rv.Schemaname,
|
|
Name: rv.Relname,
|
|
}
|
|
}
|
|
|
|
func parseRelation(in *nodes.Node) (*relation, error) {
|
|
switch n := in.Node.(type) {
|
|
case *nodes.Node_List:
|
|
return parseRelationFromNodes(n.List.Items)
|
|
case *nodes.Node_RangeVar:
|
|
return parseRelationFromRangeVar(n.RangeVar), nil
|
|
case *nodes.Node_TypeName:
|
|
return parseRelationFromNodes(n.TypeName.Names)
|
|
default:
|
|
return nil, fmt.Errorf("unexpected node type: %T", n)
|
|
}
|
|
}
|
|
|
|
func parseColName(node *nodes.Node) (*ast.ColumnRef, *ast.TableName, error) {
|
|
switch n := node.Node.(type) {
|
|
case *nodes.Node_List:
|
|
parts := stringSlice(n.List)
|
|
var tbl *ast.TableName
|
|
var ref *ast.ColumnRef
|
|
switch len(parts) {
|
|
case 2:
|
|
tbl = &ast.TableName{Name: parts[0]}
|
|
ref = &ast.ColumnRef{Name: parts[1]}
|
|
case 3:
|
|
tbl = &ast.TableName{Schema: parts[0], Name: parts[1]}
|
|
ref = &ast.ColumnRef{Name: parts[2]}
|
|
case 4:
|
|
tbl = &ast.TableName{Catalog: parts[0], Schema: parts[1], Name: parts[2]}
|
|
ref = &ast.ColumnRef{Name: parts[3]}
|
|
default:
|
|
return nil, nil, fmt.Errorf("column specifier %q is not the proper format, expected '[catalog.][schema.]colname.tablename'", strings.Join(parts, "."))
|
|
}
|
|
return ref, tbl, nil
|
|
default:
|
|
return nil, nil, fmt.Errorf("parseColName: unexpected node type: %T", n)
|
|
}
|
|
}
|
|
|
|
func joinNodes(list []*nodes.Node, sep string) string {
|
|
return strings.Join(stringSliceFromNodes(list), sep)
|
|
}
|
|
|
|
func NewParser() *Parser {
|
|
return &Parser{}
|
|
}
|
|
|
|
type Parser struct {
|
|
}
|
|
|
|
var errSkip = errors.New("skip stmt")
|
|
|
|
func (p *Parser) Parse(r io.Reader) ([]ast.Statement, error) {
|
|
f, err := p.ParseFile(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return f.Stmts, nil
|
|
}
|
|
|
|
// ParseFile parses like Parse and also carries the file's comments, which
|
|
// oliphant's parser collects from the same pass its scanner already makes
|
|
// over the input.
|
|
func (p *Parser) ParseFile(r io.Reader) (*ast.File, error) {
|
|
blob, err := io.ReadAll(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
contents := string(blob)
|
|
parsed, err := parser.ParseFile(contents)
|
|
if err != nil {
|
|
pErr := normalizeErr(err)
|
|
return nil, pErr
|
|
}
|
|
tree := parsed.ParseResult
|
|
|
|
var stmts []ast.Statement
|
|
// PostgreSQL 18 changed stmt_location to point at the statement's first
|
|
// token. The rest of the compiler expects the earlier convention, where a
|
|
// statement begins where the previous one ended and so carries its leading
|
|
// comments (that's where `-- name:` lives).
|
|
loc := 0
|
|
for _, raw := range tree.Stmts {
|
|
start := loc
|
|
end := int(raw.StmtLocation) + int(raw.StmtLen)
|
|
next := end
|
|
for next < len(contents) && contents[next] != ';' {
|
|
next++
|
|
}
|
|
terminated := next < len(contents)
|
|
if terminated {
|
|
next++
|
|
}
|
|
loc = next
|
|
length := end - start
|
|
if !terminated {
|
|
// A zero length marked an unterminated final statement before
|
|
// PostgreSQL 18, and the compiler still reports it that way.
|
|
length = 0
|
|
}
|
|
|
|
n, err := translate(raw.Stmt)
|
|
if err == errSkip {
|
|
continue
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if n == nil {
|
|
return nil, fmt.Errorf("unexpected nil node")
|
|
}
|
|
stmts = append(stmts, ast.Statement{
|
|
Raw: &ast.RawStmt{
|
|
Stmt: n,
|
|
StmtLocation: start,
|
|
StmtLen: length,
|
|
},
|
|
})
|
|
}
|
|
|
|
var comments []ast.Comment
|
|
for _, tok := range parsed.Comments {
|
|
comments = append(comments, ast.Comment{
|
|
Text: strings.TrimRight(contents[tok.Start:tok.End], " \t\r\n"),
|
|
Start: int(tok.Start),
|
|
End: int(tok.End),
|
|
OwnLine: ownLine(contents, int(tok.Start)),
|
|
})
|
|
}
|
|
return &ast.File{Stmts: stmts, Comments: comments}, nil
|
|
}
|
|
|
|
// ownLine reports that only blank space sits between the preceding line
|
|
// break and pos.
|
|
func ownLine(src string, pos int) bool {
|
|
for j := pos - 1; j >= 0; j-- {
|
|
switch src[j] {
|
|
case '\n':
|
|
return true
|
|
case ' ', '\t', '\r':
|
|
continue
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func normalizeErr(err error) error {
|
|
//TODO: errors.As complains that *parser.Error does not implement error
|
|
if pErr, ok := err.(*parser.Error); ok {
|
|
sErr := &sqlerr.Error{
|
|
Message: pErr.Message,
|
|
//Err: pErr,
|
|
Line: pErr.Lineno,
|
|
Location: pErr.Cursorpos,
|
|
}
|
|
return sErr
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-COMMENTS
|
|
func (p *Parser) CommentSyntax() source.CommentSyntax {
|
|
return source.CommentSyntax{
|
|
Dash: true,
|
|
SlashStar: true,
|
|
}
|
|
}
|
|
|
|
func translate(node *nodes.Node) (ast.Node, error) {
|
|
switch inner := node.Node.(type) {
|
|
|
|
case *nodes.Node_AlterEnumStmt:
|
|
n := inner.AlterEnumStmt
|
|
rel, err := parseRelationFromNodes(n.TypeName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if n.OldVal != "" {
|
|
return &ast.AlterTypeRenameValueStmt{
|
|
Type: rel.TypeName(),
|
|
OldValue: makeString(n.OldVal),
|
|
NewValue: makeString(n.NewVal),
|
|
}, nil
|
|
} else {
|
|
return &ast.AlterTypeAddValueStmt{
|
|
Type: rel.TypeName(),
|
|
NewValue: makeString(n.NewVal),
|
|
NewValHasNeighbor: len(n.NewValNeighbor) > 0,
|
|
NewValNeighbor: makeString(n.NewValNeighbor),
|
|
NewValIsAfter: n.NewValIsAfter,
|
|
SkipIfNewValExists: n.SkipIfNewValExists,
|
|
}, nil
|
|
}
|
|
|
|
case *nodes.Node_AlterObjectSchemaStmt:
|
|
n := inner.AlterObjectSchemaStmt
|
|
switch n.ObjectType {
|
|
|
|
case nodes.ObjectType_OBJECT_TABLE, nodes.ObjectType_OBJECT_VIEW, nodes.ObjectType_OBJECT_MATVIEW:
|
|
rel := parseRelationFromRangeVar(n.Relation)
|
|
return &ast.AlterTableSetSchemaStmt{
|
|
Table: rel.TableName(),
|
|
NewSchema: makeString(n.Newschema),
|
|
MissingOk: n.MissingOk,
|
|
}, nil
|
|
|
|
case nodes.ObjectType_OBJECT_TYPE:
|
|
rel, err := parseRelation(n.Object)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &ast.AlterTypeSetSchemaStmt{
|
|
Type: rel.TypeName(),
|
|
NewSchema: makeString(n.Newschema),
|
|
}, nil
|
|
}
|
|
return nil, errSkip
|
|
|
|
case *nodes.Node_AlterTableStmt:
|
|
n := inner.AlterTableStmt
|
|
rel := parseRelationFromRangeVar(n.Relation)
|
|
at := &ast.AlterTableStmt{
|
|
Table: rel.TableName(),
|
|
Cmds: &ast.List{},
|
|
MissingOk: n.MissingOk,
|
|
}
|
|
for _, cmd := range n.Cmds {
|
|
switch cmdOneOf := cmd.Node.(type) {
|
|
case *nodes.Node_AlterTableCmd:
|
|
altercmd := cmdOneOf.AlterTableCmd
|
|
item := &ast.AlterTableCmd{Name: &altercmd.Name, MissingOk: altercmd.MissingOk}
|
|
|
|
switch altercmd.Subtype {
|
|
case nodes.AlterTableType_AT_AddColumn:
|
|
d, ok := altercmd.Def.Node.(*nodes.Node_ColumnDef)
|
|
if !ok {
|
|
return nil, fmt.Errorf("expected alter table definition to be a ColumnDef")
|
|
}
|
|
|
|
rel, err := parseRelationFromNodes(d.ColumnDef.TypeName.Names)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
item.Subtype = ast.AT_AddColumn
|
|
item.Def = &ast.ColumnDef{
|
|
Colname: d.ColumnDef.Colname,
|
|
TypeName: columnTypeName(rel, d.ColumnDef.TypeName),
|
|
IsNotNull: isNotNull(d.ColumnDef),
|
|
IsArray: isArray(d.ColumnDef.TypeName),
|
|
ArrayDims: len(d.ColumnDef.TypeName.ArrayBounds),
|
|
}
|
|
|
|
case nodes.AlterTableType_AT_AlterColumnType:
|
|
d, ok := altercmd.Def.Node.(*nodes.Node_ColumnDef)
|
|
if !ok {
|
|
return nil, fmt.Errorf("expected alter table definition to be a ColumnDef")
|
|
}
|
|
col := ""
|
|
if altercmd.Name != "" {
|
|
col = altercmd.Name
|
|
} else if d.ColumnDef.Colname != "" {
|
|
col = d.ColumnDef.Colname
|
|
} else {
|
|
return nil, fmt.Errorf("unknown name for alter column type")
|
|
}
|
|
rel, err := parseRelationFromNodes(d.ColumnDef.TypeName.Names)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
item.Subtype = ast.AT_AlterColumnType
|
|
item.Def = &ast.ColumnDef{
|
|
Colname: col,
|
|
TypeName: columnTypeName(rel, d.ColumnDef.TypeName),
|
|
IsNotNull: isNotNull(d.ColumnDef),
|
|
IsArray: isArray(d.ColumnDef.TypeName),
|
|
ArrayDims: len(d.ColumnDef.TypeName.ArrayBounds),
|
|
}
|
|
|
|
case nodes.AlterTableType_AT_DropColumn:
|
|
item.Subtype = ast.AT_DropColumn
|
|
|
|
case nodes.AlterTableType_AT_DropNotNull:
|
|
item.Subtype = ast.AT_DropNotNull
|
|
|
|
case nodes.AlterTableType_AT_SetNotNull:
|
|
item.Subtype = ast.AT_SetNotNull
|
|
|
|
default:
|
|
continue
|
|
}
|
|
|
|
at.Cmds.Items = append(at.Cmds.Items, item)
|
|
}
|
|
}
|
|
return at, nil
|
|
|
|
case *nodes.Node_CommentStmt:
|
|
n := inner.CommentStmt
|
|
switch n.Objtype {
|
|
|
|
case nodes.ObjectType_OBJECT_COLUMN:
|
|
col, tbl, err := parseColName(n.Object)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("COMMENT ON COLUMN: %w", err)
|
|
}
|
|
return &ast.CommentOnColumnStmt{
|
|
Col: col,
|
|
Table: tbl,
|
|
Comment: makeString(n.Comment),
|
|
}, nil
|
|
|
|
case nodes.ObjectType_OBJECT_SCHEMA:
|
|
o, ok := n.Object.Node.(*nodes.Node_String_)
|
|
if !ok {
|
|
return nil, fmt.Errorf("COMMENT ON SCHEMA: unexpected node type: %T", n.Object)
|
|
}
|
|
return &ast.CommentOnSchemaStmt{
|
|
Schema: &ast.String{Str: o.String_.Sval},
|
|
Comment: makeString(n.Comment),
|
|
}, nil
|
|
|
|
case nodes.ObjectType_OBJECT_TABLE:
|
|
rel, err := parseRelation(n.Object)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("COMMENT ON TABLE: %w", err)
|
|
}
|
|
return &ast.CommentOnTableStmt{
|
|
Table: rel.TableName(),
|
|
Comment: makeString(n.Comment),
|
|
}, nil
|
|
|
|
case nodes.ObjectType_OBJECT_TYPE:
|
|
rel, err := parseRelation(n.Object)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &ast.CommentOnTypeStmt{
|
|
Type: rel.TypeName(),
|
|
Comment: makeString(n.Comment),
|
|
}, nil
|
|
|
|
case nodes.ObjectType_OBJECT_VIEW:
|
|
rel, err := parseRelation(n.Object)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("COMMENT ON VIEW: %w", err)
|
|
}
|
|
return &ast.CommentOnViewStmt{
|
|
View: rel.TableName(),
|
|
Comment: makeString(n.Comment),
|
|
}, nil
|
|
|
|
}
|
|
return nil, errSkip
|
|
|
|
case *nodes.Node_CompositeTypeStmt:
|
|
n := inner.CompositeTypeStmt
|
|
rel := parseRelationFromRangeVar(n.Typevar)
|
|
stmt := &ast.CompositeTypeStmt{
|
|
TypeName: rel.TypeName(),
|
|
Coldeflist: &ast.List{},
|
|
}
|
|
for _, node := range n.Coldeflist {
|
|
field, ok := node.Node.(*nodes.Node_ColumnDef)
|
|
if !ok {
|
|
continue
|
|
}
|
|
rel, err := parseRelationFromNodes(field.ColumnDef.TypeName.Names)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stmt.Coldeflist.Items = append(stmt.Coldeflist.Items, &ast.ColumnDef{
|
|
Colname: field.ColumnDef.Colname,
|
|
TypeName: columnTypeName(rel, field.ColumnDef.TypeName),
|
|
IsArray: isArray(field.ColumnDef.TypeName),
|
|
ArrayDims: len(field.ColumnDef.TypeName.ArrayBounds),
|
|
})
|
|
}
|
|
return stmt, nil
|
|
|
|
case *nodes.Node_CreateStmt:
|
|
n := inner.CreateStmt
|
|
rel := parseRelationFromRangeVar(n.Relation)
|
|
create := &ast.CreateTableStmt{
|
|
Name: rel.TableName(),
|
|
IfNotExists: n.IfNotExists,
|
|
}
|
|
for _, node := range n.InhRelations {
|
|
switch item := node.Node.(type) {
|
|
case *nodes.Node_RangeVar:
|
|
if item.RangeVar.Inh {
|
|
rel := parseRelationFromRangeVar(item.RangeVar)
|
|
create.Inherits = append(create.Inherits, rel.TableName())
|
|
}
|
|
}
|
|
}
|
|
primaryKey := make(map[string]bool)
|
|
for _, elt := range n.TableElts {
|
|
switch item := elt.Node.(type) {
|
|
case *nodes.Node_Constraint:
|
|
if item.Constraint.Contype == nodes.ConstrType_CONSTR_PRIMARY {
|
|
for _, key := range item.Constraint.Keys {
|
|
// FIXME: Possible nil pointer dereference
|
|
primaryKey[key.Node.(*nodes.Node_String_).String_.Sval] = true
|
|
}
|
|
}
|
|
|
|
case *nodes.Node_TableLikeClause:
|
|
rel := parseRelationFromRangeVar(item.TableLikeClause.Relation)
|
|
create.ReferTable = rel.TableName()
|
|
}
|
|
}
|
|
for _, elt := range n.TableElts {
|
|
switch item := elt.Node.(type) {
|
|
case *nodes.Node_ColumnDef:
|
|
rel, err := parseRelationFromNodes(item.ColumnDef.TypeName.Names)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
primary := false
|
|
for _, con := range item.ColumnDef.Constraints {
|
|
if constraint, ok := con.Node.(*nodes.Node_Constraint); ok {
|
|
primary = constraint.Constraint.Contype == nodes.ConstrType_CONSTR_PRIMARY
|
|
}
|
|
}
|
|
|
|
create.Cols = append(create.Cols, &ast.ColumnDef{
|
|
Colname: item.ColumnDef.Colname,
|
|
TypeName: columnTypeName(rel, item.ColumnDef.TypeName),
|
|
IsNotNull: isNotNull(item.ColumnDef) || primaryKey[item.ColumnDef.Colname],
|
|
IsArray: isArray(item.ColumnDef.TypeName),
|
|
ArrayDims: len(item.ColumnDef.TypeName.ArrayBounds),
|
|
PrimaryKey: primary,
|
|
})
|
|
}
|
|
}
|
|
return create, nil
|
|
|
|
case *nodes.Node_CreateEnumStmt:
|
|
n := inner.CreateEnumStmt
|
|
rel, err := parseRelationFromNodes(n.TypeName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stmt := &ast.CreateEnumStmt{
|
|
TypeName: rel.TypeName(),
|
|
Vals: &ast.List{},
|
|
}
|
|
for _, val := range n.Vals {
|
|
switch v := val.Node.(type) {
|
|
case *nodes.Node_String_:
|
|
stmt.Vals.Items = append(stmt.Vals.Items, &ast.String{
|
|
Str: v.String_.Sval,
|
|
})
|
|
}
|
|
}
|
|
return stmt, nil
|
|
|
|
case *nodes.Node_CreateFunctionStmt:
|
|
n := inner.CreateFunctionStmt
|
|
fn, err := parseRelationFromNodes(n.Funcname)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var rt *ast.TypeName
|
|
if n.ReturnType != nil {
|
|
rel, err := parseRelationFromNodes(n.ReturnType.Names)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rt = rel.TypeName()
|
|
}
|
|
stmt := &ast.CreateFunctionStmt{
|
|
Func: fn.FuncName(),
|
|
ReturnType: rt,
|
|
Replace: n.Replace,
|
|
Params: &ast.List{},
|
|
Options: convertSlice(n.Options),
|
|
}
|
|
for _, item := range n.Parameters {
|
|
arg := item.Node.(*nodes.Node_FunctionParameter).FunctionParameter
|
|
rel, err := parseRelationFromNodes(arg.ArgType.Names)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
mode, err := convertFuncParamMode(arg.Mode)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fp := &ast.FuncParam{
|
|
Name: &arg.Name,
|
|
Type: rel.TypeName(),
|
|
Mode: mode,
|
|
}
|
|
if arg.Defexpr != nil {
|
|
fp.DefExpr = &ast.TODO{}
|
|
}
|
|
stmt.Params.Items = append(stmt.Params.Items, fp)
|
|
}
|
|
return stmt, nil
|
|
|
|
case *nodes.Node_CreateSchemaStmt:
|
|
n := inner.CreateSchemaStmt
|
|
return &ast.CreateSchemaStmt{
|
|
Name: makeString(n.Schemaname),
|
|
IfNotExists: n.IfNotExists,
|
|
}, nil
|
|
|
|
case *nodes.Node_DropStmt:
|
|
n := inner.DropStmt
|
|
switch n.RemoveType {
|
|
|
|
case nodes.ObjectType_OBJECT_FUNCTION:
|
|
drop := &ast.DropFunctionStmt{
|
|
MissingOk: n.MissingOk,
|
|
}
|
|
for _, obj := range n.Objects {
|
|
nowa, ok := obj.Node.(*nodes.Node_ObjectWithArgs)
|
|
if !ok {
|
|
return nil, fmt.Errorf("nodes.DropStmt: FUNCTION: unknown type in objects list: %T", obj)
|
|
}
|
|
owa := nowa.ObjectWithArgs
|
|
fn, err := parseRelationFromNodes(owa.Objname)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("nodes.DropStmt: FUNCTION: %w", err)
|
|
}
|
|
args := make([]*ast.TypeName, len(owa.Objargs))
|
|
for i, objarg := range owa.Objargs {
|
|
tn, ok := objarg.Node.(*nodes.Node_TypeName)
|
|
if !ok {
|
|
return nil, fmt.Errorf("nodes.DropStmt: FUNCTION: unknown type in objargs list: %T", objarg)
|
|
}
|
|
at, err := parseRelationFromNodes(tn.TypeName.Names)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("nodes.DropStmt: FUNCTION: %w", err)
|
|
}
|
|
args[i] = at.TypeName()
|
|
}
|
|
drop.Funcs = append(drop.Funcs, &ast.FuncSpec{
|
|
Name: fn.FuncName(),
|
|
Args: args,
|
|
HasArgs: !owa.ArgsUnspecified,
|
|
})
|
|
}
|
|
return drop, nil
|
|
|
|
case nodes.ObjectType_OBJECT_SCHEMA:
|
|
drop := &ast.DropSchemaStmt{
|
|
MissingOk: n.MissingOk,
|
|
}
|
|
for _, obj := range n.Objects {
|
|
val, ok := obj.Node.(*nodes.Node_String_)
|
|
if !ok {
|
|
return nil, fmt.Errorf("nodes.DropStmt: SCHEMA: unknown type in objects list: %T", obj)
|
|
}
|
|
drop.Schemas = append(drop.Schemas, &ast.String{Str: val.String_.Sval})
|
|
}
|
|
return drop, nil
|
|
|
|
case nodes.ObjectType_OBJECT_TABLE, nodes.ObjectType_OBJECT_VIEW, nodes.ObjectType_OBJECT_MATVIEW:
|
|
drop := &ast.DropTableStmt{
|
|
IfExists: n.MissingOk,
|
|
}
|
|
for _, obj := range n.Objects {
|
|
name, err := parseRelation(obj)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("nodes.DropStmt: TABLE: %w", err)
|
|
}
|
|
drop.Tables = append(drop.Tables, name.TableName())
|
|
}
|
|
return drop, nil
|
|
|
|
case nodes.ObjectType_OBJECT_TYPE:
|
|
drop := &ast.DropTypeStmt{
|
|
IfExists: n.MissingOk,
|
|
}
|
|
for _, obj := range n.Objects {
|
|
name, err := parseRelation(obj)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("nodes.DropStmt: TYPE: %w", err)
|
|
}
|
|
drop.Types = append(drop.Types, name.TypeName())
|
|
}
|
|
return drop, nil
|
|
|
|
}
|
|
return nil, errSkip
|
|
|
|
case *nodes.Node_RenameStmt:
|
|
n := inner.RenameStmt
|
|
switch n.RenameType {
|
|
|
|
case nodes.ObjectType_OBJECT_COLUMN:
|
|
rel := parseRelationFromRangeVar(n.Relation)
|
|
return &ast.RenameColumnStmt{
|
|
Table: rel.TableName(),
|
|
Col: &ast.ColumnRef{Name: n.Subname},
|
|
NewName: makeString(n.Newname),
|
|
MissingOk: n.MissingOk,
|
|
}, nil
|
|
|
|
case nodes.ObjectType_OBJECT_TABLE, nodes.ObjectType_OBJECT_MATVIEW, nodes.ObjectType_OBJECT_VIEW:
|
|
rel := parseRelationFromRangeVar(n.Relation)
|
|
return &ast.RenameTableStmt{
|
|
Table: rel.TableName(),
|
|
NewName: makeString(n.Newname),
|
|
MissingOk: n.MissingOk,
|
|
}, nil
|
|
|
|
case nodes.ObjectType_OBJECT_TYPE:
|
|
rel, err := parseRelation(n.Object)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("nodes.RenameStmt: TYPE: %w", err)
|
|
}
|
|
return &ast.RenameTypeStmt{
|
|
Type: rel.TypeName(),
|
|
NewName: makeString(n.Newname),
|
|
}, nil
|
|
|
|
}
|
|
return nil, errSkip
|
|
|
|
default:
|
|
return convert(node)
|
|
}
|
|
}
|
|
|
|
// columnTypeName is a column's type as the catalog needs it: the name the
|
|
// relation resolves, with the type modifiers the parser reported. Every
|
|
// modifier is an integer constant, except that an interval's first one is a
|
|
// bit mask of the fields it keeps, which is decoded into the words
|
|
// format_type prints, so that "interval day to second" carries "day to
|
|
// second" as its first argument.
|
|
func columnTypeName(rel *relation, tn *nodes.TypeName) *ast.TypeName {
|
|
out := rel.TypeName()
|
|
if tn == nil || len(tn.Typmods) == 0 {
|
|
return out
|
|
}
|
|
out.Typmods = &ast.List{}
|
|
for i, mod := range tn.Typmods {
|
|
c, ok := mod.Node.(*nodes.Node_AConst)
|
|
if !ok {
|
|
continue
|
|
}
|
|
ival, ok := c.AConst.Val.(*nodes.A_Const_Ival)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if i == 0 && rel.Name == "interval" {
|
|
fields, ok := intervalFields[ival.Ival.Ival]
|
|
if !ok {
|
|
continue
|
|
}
|
|
if fields != "" {
|
|
out.Typmods.Items = append(out.Typmods.Items, &ast.String{Str: fields})
|
|
}
|
|
continue
|
|
}
|
|
out.Typmods.Items = append(out.Typmods.Items, &ast.A_Const{Val: &ast.Integer{Ival: int64(ival.Ival.Ival)}})
|
|
}
|
|
if len(out.Typmods.Items) == 0 {
|
|
out.Typmods = nil
|
|
}
|
|
return out
|
|
}
|
|
|
|
// intervalFields decodes the field mask an interval typmod starts with —
|
|
// INTERVAL_MASK(YEAR) | INTERVAL_MASK(MONTH) and so on, with the field
|
|
// numbers PostgreSQL's datetime.h assigns — into the words a declaration
|
|
// spells. The full range is written as nothing.
|
|
var intervalFields = func() map[int32]string {
|
|
const (
|
|
month = 1 << 1
|
|
year = 1 << 2
|
|
day = 1 << 3
|
|
hour = 1 << 10
|
|
minute = 1 << 11
|
|
second = 1 << 12
|
|
)
|
|
return map[int32]string{
|
|
0x7FFF: "",
|
|
year: "year",
|
|
month: "month",
|
|
day: "day",
|
|
hour: "hour",
|
|
minute: "minute",
|
|
second: "second",
|
|
year | month: "year to month",
|
|
day | hour: "day to hour",
|
|
day | hour | minute: "day to minute",
|
|
day | hour | minute | second: "day to second",
|
|
hour | minute: "hour to minute",
|
|
hour | minute | second: "hour to second",
|
|
minute | second: "minute to second",
|
|
}
|
|
}()
|