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