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

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",
}
}()