diff --git a/oracle/ast/loc.go b/oracle/ast/loc.go index 91d4c0a2..37463c2a 100644 --- a/oracle/ast/loc.go +++ b/oracle/ast/loc.go @@ -487,6 +487,12 @@ func NodeLoc(n Node) Loc { return v.Loc case *PLSQLSubtypeDecl: return v.Loc + case *Accessor: + return v.Loc + case *ParallelEnableClause: + return v.Loc + case *StreamingClause: + return v.Loc case *PLSQLAssign: return v.Loc case *PLSQLIf: diff --git a/oracle/ast/outfuncs.go b/oracle/ast/outfuncs.go index 4ace243e..d43e054c 100644 --- a/oracle/ast/outfuncs.go +++ b/oracle/ast/outfuncs.go @@ -591,6 +591,12 @@ func writeNode(sb *strings.Builder, node Node) { writePLSQLTypeDecl(sb, n) case *PLSQLSubtypeDecl: writePLSQLSubtypeDecl(sb, n) + case *Accessor: + writeAccessor(sb, n) + case *ParallelEnableClause: + writeParallelEnableClause(sb, n) + case *StreamingClause: + writeStreamingClause(sb, n) case *PLSQLCall: writePLSQLCall(sb, n) @@ -670,6 +676,12 @@ func writeTypeName(sb *strings.Builder, n *TypeName) { sb.WriteString(" :arrayBounds ") writeNode(sb, n.ArrayBounds) } + if n.CharacterSet != "" { + sb.WriteString(fmt.Sprintf(" :characterSet %q", n.CharacterSet)) + } + if n.IsPercCharset { + sb.WriteString(" :pctCharset true") + } sb.WriteString(fmt.Sprintf(" :loc_start %d :loc_end %d", n.Loc.Start, n.Loc.End)) sb.WriteString("}") } @@ -3217,6 +3229,16 @@ func writeCreatePackageStmt(sb *strings.Builder, n *CreatePackageStmt) { sb.WriteString(" :name ") writeNode(sb, n.Name) } + if n.AuthID != "" { + sb.WriteString(fmt.Sprintf(" :authID %q", n.AuthID)) + } + if n.AccessibleBy != nil { + sb.WriteString(" :accessibleBy ") + writeNode(sb, n.AccessibleBy) + } + if n.DefaultCollation != "" { + sb.WriteString(fmt.Sprintf(" :defaultCollation %q", n.DefaultCollation)) + } if n.IsBody { sb.WriteString(" :isBody true") } @@ -3256,6 +3278,13 @@ func writeCreateProcedureStmt(sb *strings.Builder, n *CreateProcedureStmt) { sb.WriteString(" :parameters ") writeNode(sb, n.Parameters) } + if n.AccessibleBy != nil { + sb.WriteString(" :accessibleBy ") + writeNode(sb, n.AccessibleBy) + } + if n.DefaultCollation != "" { + sb.WriteString(fmt.Sprintf(" :defaultCollation %q", n.DefaultCollation)) + } if n.Wrapped { sb.WriteString(" :wrapped true") } @@ -3309,21 +3338,50 @@ func writeCreateFunctionStmt(sb *strings.Builder, n *CreateFunctionStmt) { if n.Pipelined { sb.WriteString(" :pipelined true") } + if n.Polymorphic != "" { + sb.WriteString(fmt.Sprintf(" :polymorphic %q", n.Polymorphic)) + } if n.Parallel { sb.WriteString(" :parallel true") } + if n.ParallelSpec != nil { + sb.WriteString(" :parallelSpec ") + writeNode(sb, n.ParallelSpec) + } + if n.Streaming != nil { + sb.WriteString(" :streaming ") + writeNode(sb, n.Streaming) + } if n.ResultCache { sb.WriteString(" :resultCache true") } + if n.ReliesOn != nil { + sb.WriteString(" :reliesOn ") + writeNode(sb, n.ReliesOn) + } if n.Aggregate { sb.WriteString(" :aggregate true") } + if n.Implementation != nil { + sb.WriteString(" :implementation ") + writeNode(sb, n.Implementation) + } if n.SqlMacro { sb.WriteString(" :sqlMacro true") } + if n.SqlMacroType != "" { + sb.WriteString(fmt.Sprintf(" :sqlMacroType %q", n.SqlMacroType)) + } if n.AuthID != "" { sb.WriteString(fmt.Sprintf(" :authID %q", n.AuthID)) } + if n.AccessibleBy != nil { + sb.WriteString(" :accessibleBy ") + writeNode(sb, n.AccessibleBy) + } + if n.DefaultCollation != "" { + sb.WriteString(fmt.Sprintf(" :defaultCollation %q", n.DefaultCollation)) + } if n.Body != nil { sb.WriteString(" :body ") writeNode(sb, n.Body) @@ -5391,6 +5449,41 @@ func writePLSQLSubtypeDecl(sb *strings.Builder, n *PLSQLSubtypeDecl) { sb.WriteString("}") } +func writeAccessor(sb *strings.Builder, n *Accessor) { + sb.WriteString("{ACCESSOR") + if n.UnitKind != "" { + sb.WriteString(fmt.Sprintf(" :unitKind %q", n.UnitKind)) + } + if n.Name != nil { + sb.WriteString(" :name ") + writeNode(sb, n.Name) + } + sb.WriteString(fmt.Sprintf(" :loc_start %d :loc_end %d", n.Loc.Start, n.Loc.End)) + sb.WriteString("}") +} + +func writeParallelEnableClause(sb *strings.Builder, n *ParallelEnableClause) { + sb.WriteString("{PARALLELENABLE") + sb.WriteString(fmt.Sprintf(" :argument %q :partitionBy %q", n.Argument, n.PartitionBy)) + if n.Columns != nil { + sb.WriteString(" :columns ") + writeNode(sb, n.Columns) + } + sb.WriteString(fmt.Sprintf(" :loc_start %d :loc_end %d", n.Loc.Start, n.Loc.End)) + sb.WriteString("}") +} + +func writeStreamingClause(sb *strings.Builder, n *StreamingClause) { + sb.WriteString("{STREAMING") + sb.WriteString(fmt.Sprintf(" :kind %q :argument %q", n.Kind, n.Argument)) + if n.Columns != nil { + sb.WriteString(" :columns ") + writeNode(sb, n.Columns) + } + sb.WriteString(fmt.Sprintf(" :loc_start %d :loc_end %d", n.Loc.Start, n.Loc.End)) + sb.WriteString("}") +} + func writePLSQLCall(sb *strings.Builder, n *PLSQLCall) { sb.WriteString("{PLSQLCALL") if n.Name != nil { diff --git a/oracle/ast/parsenodes.go b/oracle/ast/parsenodes.go index 167ae710..6c6be1a7 100644 --- a/oracle/ast/parsenodes.go +++ b/oracle/ast/parsenodes.go @@ -369,12 +369,14 @@ func (n *Hint) nodeTag() {} // TypeName represents a data type reference. type TypeName struct { - Names *List // type name parts (list of *String) - TypeMods *List // type modifiers (precision, scale) - IsPercType bool // %TYPE - IsPercRowtype bool // %ROWTYPE - ArrayBounds *List // array bounds if any - Loc Loc // start location + Names *List // type name parts (list of *String) + TypeMods *List // type modifiers (precision, scale) + IsPercType bool // %TYPE + IsPercRowtype bool // %ROWTYPE + ArrayBounds *List // array bounds if any + CharacterSet string // PL/SQL CHARACTER SET name, or the item named by item%CHARSET + IsPercCharset bool // CHARACTER SET item%CHARSET + Loc Loc // start location } func (n *TypeName) nodeTag() {} @@ -1986,15 +1988,18 @@ func (n *TypeBodyMember) nodeTag() {} // CreatePackageStmt represents a CREATE PACKAGE statement. type CreatePackageStmt struct { - OrReplace bool // OR REPLACE - IfNotExists bool // IF NOT EXISTS - Editionable bool // EDITIONABLE - NonEditionable bool // NONEDITIONABLE - Sharing string // SHARING = METADATA | NONE - Name *ObjectName // package name - IsBody bool // PACKAGE BODY - Body *List // package body declarations - Loc Loc // start location + OrReplace bool // OR REPLACE + IfNotExists bool // IF NOT EXISTS + Editionable bool // EDITIONABLE + NonEditionable bool // NONEDITIONABLE + Sharing string // SHARING = METADATA | NONE + Name *ObjectName // package name + AuthID string // AUTHID CURRENT_USER | DEFINER + AccessibleBy *List // ACCESSIBLE BY accessors (list of *Accessor) + DefaultCollation string // DEFAULT COLLATION option + IsBody bool // PACKAGE BODY + Body *List // package body declarations + Loc Loc // start location } func (n *CreatePackageStmt) nodeTag() {} @@ -2006,19 +2011,21 @@ func (n *CreatePackageStmt) stmtNode() {} // CreateProcedureStmt represents a CREATE PROCEDURE statement. type CreateProcedureStmt struct { - OrReplace bool // OR REPLACE - IfNotExists bool // IF NOT EXISTS - Editionable bool // EDITIONABLE - NonEditionable bool // NONEDITIONABLE - Sharing string // SHARING = METADATA | NONE - AuthID string // AUTHID CURRENT_USER | DEFINER - Name *ObjectName // procedure name - Parameters *List // parameter list (list of *Parameter) - Wrapped bool // WRAPPED procedure body - WrappedSource string // raw text from WRAPPED through the wrapped payload - Body StmtNode // procedure body (PL/SQL block); nil for a call spec - CallSpec *CallSpec // call spec implementing the procedure in Java or C - Loc Loc // start location + OrReplace bool // OR REPLACE + IfNotExists bool // IF NOT EXISTS + Editionable bool // EDITIONABLE + NonEditionable bool // NONEDITIONABLE + Sharing string // SHARING = METADATA | NONE + AuthID string // AUTHID CURRENT_USER | DEFINER + Name *ObjectName // procedure name + Parameters *List // parameter list (list of *Parameter) + AccessibleBy *List // ACCESSIBLE BY accessors (list of *Accessor) + DefaultCollation string // DEFAULT COLLATION option + Wrapped bool // WRAPPED procedure body + WrappedSource string // raw text from WRAPPED through the wrapped payload + Body StmtNode // procedure body (PL/SQL block); nil for a call spec + CallSpec *CallSpec // call spec implementing the procedure in Java or C + Loc Loc // start location } func (n *CreateProcedureStmt) nodeTag() {} @@ -2056,29 +2063,72 @@ func (n *CallSpec) nodeTag() {} // CreateFunctionStmt represents a CREATE FUNCTION statement. type CreateFunctionStmt struct { - OrReplace bool // OR REPLACE - IfNotExists bool // IF NOT EXISTS - Editionable bool // EDITIONABLE - NonEditionable bool // NONEDITIONABLE - Sharing string // SHARING = METADATA | NONE - Name *ObjectName // function name - Parameters *List // parameter list (list of *Parameter) - ReturnType *TypeName // RETURN type - Deterministic bool // DETERMINISTIC - Pipelined bool // PIPELINED - Parallel bool // PARALLEL_ENABLE - ResultCache bool // RESULT_CACHE - Aggregate bool // AGGREGATE USING - SqlMacro bool // SQL_MACRO - AuthID string // AUTHID CURRENT_USER | DEFINER - Body StmtNode // function body (PL/SQL block); nil for a call spec - CallSpec *CallSpec // call spec implementing the function in Java or C - Loc Loc // start location + OrReplace bool // OR REPLACE + IfNotExists bool // IF NOT EXISTS + Editionable bool // EDITIONABLE + NonEditionable bool // NONEDITIONABLE + Sharing string // SHARING = METADATA | NONE + Name *ObjectName // function name + Parameters *List // parameter list (list of *Parameter) + ReturnType *TypeName // RETURN type + Deterministic bool // DETERMINISTIC + Pipelined bool // PIPELINED + Polymorphic string // PIPELINED ROW or TABLE POLYMORPHIC: ROW or TABLE + Parallel bool // PARALLEL_ENABLE + ParallelSpec *ParallelEnableClause // PARALLEL_ENABLE ( PARTITION ... ), nil without one + Streaming *List // ORDER and CLUSTER streaming clauses (list of *StreamingClause) + ResultCache bool // RESULT_CACHE + ReliesOn *List // RESULT_CACHE RELIES_ON data sources (list of *ObjectName) + Aggregate bool // AGGREGATE USING + Implementation *ObjectName // AGGREGATE or PIPELINED USING implementation type; the function then has no body + SqlMacro bool // SQL_MACRO + SqlMacroType string // SQL_MACRO ( SCALAR | TABLE ), "" without the parenthesized type + AuthID string // AUTHID CURRENT_USER | DEFINER + AccessibleBy *List // ACCESSIBLE BY accessors (list of *Accessor) + DefaultCollation string // DEFAULT COLLATION option + Body StmtNode // function body (PL/SQL block); nil for a call spec + CallSpec *CallSpec // call spec implementing the function in Java or C + Loc Loc // start location } func (n *CreateFunctionStmt) nodeTag() {} func (n *CreateFunctionStmt) stmtNode() {} +// Accessor is one entry of an ACCESSIBLE BY clause. +// +// accessor ::= [ FUNCTION | PROCEDURE | PACKAGE | TRIGGER | TYPE ] [ schema. ] unit_name +type Accessor struct { + UnitKind string // FUNCTION, PROCEDURE, PACKAGE, TRIGGER, TYPE, or "" when omitted + Name *ObjectName // [schema.]unit_name + Loc Loc +} + +func (n *Accessor) nodeTag() {} + +// ParallelEnableClause is the partitioning of a PARALLEL_ENABLE function. +// +// ( PARTITION argument BY { ANY | { HASH | RANGE | VALUE } ( expr [, expr ]... ) } ) +type ParallelEnableClause struct { + Argument string // the partitioned cursor parameter + PartitionBy string // ANY, HASH, RANGE, or VALUE + Columns *List // HASH, RANGE, or VALUE expressions + Loc Loc +} + +func (n *ParallelEnableClause) nodeTag() {} + +// StreamingClause orders or clusters the rows a table function receives. +// +// { ORDER | CLUSTER } argument BY ( expr [, expr ]... ) +type StreamingClause struct { + Kind string // ORDER or CLUSTER + Argument string // the cursor parameter + Columns *List // the ordering or clustering expressions + Loc Loc +} + +func (n *StreamingClause) nodeTag() {} + // Parameter represents a procedure/function parameter. type Parameter struct { Name string // parameter name diff --git a/oracle/ast/walk_generated.go b/oracle/ast/walk_generated.go index 459d5b08..4a775301 100644 --- a/oracle/ast/walk_generated.go +++ b/oracle/ast/walk_generated.go @@ -6,6 +6,10 @@ package ast // for each child. This function is generated from parsenodes.go. func walkChildren(v Visitor, node Node) { switch n := node.(type) { + case *Accessor: + if n.Name != nil { + Walk(v, n.Name) + } case *AdminDDLStmt: if n.Name != nil { Walk(v, n.Name) @@ -503,6 +507,15 @@ func walkChildren(v Visitor, node Node) { if n.ReturnType != nil { Walk(v, n.ReturnType) } + if n.ParallelSpec != nil { + Walk(v, n.ParallelSpec) + } + walkList(v, n.Streaming) + walkList(v, n.ReliesOn) + if n.Implementation != nil { + Walk(v, n.Implementation) + } + walkList(v, n.AccessibleBy) Walk(v, n.Body) if n.CallSpec != nil { Walk(v, n.CallSpec) @@ -594,12 +607,14 @@ func walkChildren(v Visitor, node Node) { if n.Name != nil { Walk(v, n.Name) } + walkList(v, n.AccessibleBy) walkList(v, n.Body) case *CreateProcedureStmt: if n.Name != nil { Walk(v, n.Name) } walkList(v, n.Parameters) + walkList(v, n.AccessibleBy) Walk(v, n.Body) if n.CallSpec != nil { Walk(v, n.CallSpec) @@ -1229,6 +1244,8 @@ func walkChildren(v Visitor, node Node) { for _, item := range n.Stmts { Walk(v, item) } + case *ParallelEnableClause: + walkList(v, n.Columns) case *Parameter: if n.TypeName != nil { Walk(v, n.TypeName) @@ -1355,6 +1372,8 @@ func walkChildren(v Visitor, node Node) { } case *SortBy: Walk(v, n.Expr) + case *StreamingClause: + walkList(v, n.Columns) case *SubqueryExpr: Walk(v, n.Subquery) case *SubqueryRef: diff --git a/oracle/parser/alter_misc.go b/oracle/parser/alter_misc.go index 0cf05505..cd8ef0ca 100644 --- a/oracle/parser/alter_misc.go +++ b/oracle/parser/alter_misc.go @@ -3849,6 +3849,7 @@ func (p *Parser) parseAlterTypeAddDrop(stmt *nodes.AlterTypeStmt, action string) } func (p *Parser) parseAlterTypeMethodSig(stmt *nodes.AlterTypeStmt) error { + var params *nodes.List // FUNCTION or PROCEDURE if p.cur.Type == kwFUNCTION { stmt.MethodType = "FUNCTION" @@ -3869,7 +3870,8 @@ func (p *Parser) parseAlterTypeMethodSig(stmt *nodes.AlterTypeStmt) error { } if p.cur.Type == '(' { - params, parseErr126 := p.parseParameterList() + var parseErr126 error + params, parseErr126 = p.parseParameterList() if parseErr126 != nil { return parseErr126 } @@ -3899,18 +3901,22 @@ func (p *Parser) parseAlterTypeMethodSig(stmt *nodes.AlterTypeStmt) error { } } else { var parseErr127 error - stmt.MethodReturn, parseErr127 = p.parseTypeName() - if parseErr127 != - - // parseAlterTypeAttributes parses ( attribute [datatype] [, ...] ). - nil { + stmt.MethodReturn, parseErr127 = p.parsePLSQLDatatype(typeModsNone, charsetFlexible) + if parseErr127 != nil { return parseErr127 } } } - return nil + // A method signature takes the heading checks of CREATE TYPE methods: + // no TABLE or COLUMNS pseudo-type (PLS-00765), and item%CHARSET from a + // character parameter (PLS-00550). + if tn := p.tablePseudoType(params, stmt.MethodReturn); tn != nil { + return p.syntaxErrorAtType(tn) + } + return p.checkCharsetSources(params, stmt.MethodReturn) } +// parseAlterTypeAttributes parses ( attribute [datatype] [, ...] ). func (p *Parser) parseAlterTypeAttributes(withDatatype bool) ([]*nodes.TypeAttribute, error) { var attrs []*nodes.TypeAttribute diff --git a/oracle/parser/compare_test.go b/oracle/parser/compare_test.go index bf4c1d13..153e6404 100644 --- a/oracle/parser/compare_test.go +++ b/oracle/parser/compare_test.go @@ -7703,7 +7703,9 @@ func TestParseBatch101_PLSQLObjects(t *testing.T) { // CREATE FUNCTION - EDITIONABLE with SHARING "CREATE OR REPLACE EDITIONABLE FUNCTION calc RETURN NUMBER SHARING = METADATA DETERMINISTIC IS BEGIN RETURN 1; END;", // CREATE FUNCTION - AGGREGATE USING - "CREATE FUNCTION agg_fn RETURN NUMBER AGGREGATE USING agg_impl IS BEGIN RETURN 0; END;", + // AGGREGATE USING ends the function: Oracle 23ai rejects an IS body + // after it (PLS-00103). + "CREATE FUNCTION agg_fn (x NUMBER) RETURN NUMBER AGGREGATE USING agg_impl;", // CREATE PACKAGE - IF NOT EXISTS with SHARING "CREATE IF NOT EXISTS PACKAGE my_pkg SHARING = METADATA IS\n PROCEDURE do_it;\nEND my_pkg;", // CREATE PACKAGE - EDITIONABLE with AUTHID diff --git a/oracle/parser/create_proc.go b/oracle/parser/create_proc.go index de39ed63..ab2e5992 100644 --- a/oracle/parser/create_proc.go +++ b/oracle/parser/create_proc.go @@ -62,10 +62,14 @@ func (p *Parser) parseCreateProcedureStmt(start int, orReplace, ifNotExists, edi } } - var parseErr456 error - stmt.AuthID, parseErr456 = p.parseOptionalAuthID() - if parseErr456 != nil { - return nil, parseErr456 + if tn := p.tablePseudoType(stmt.Parameters, nil); tn != nil { + return nil, p.syntaxErrorAtType(tn) + } + if err := p.checkCharsetSources(stmt.Parameters, nil); err != nil { + return nil, err + } + if err := p.parseProcedureProperties(stmt, subprogramSchema); err != nil { + return nil, err } if p.isIdentLikeStr("WRAPPED") { @@ -78,10 +82,14 @@ func (p *Parser) parseCreateProcedureStmt(start int, orReplace, ifNotExists, edi p.advance() // PL/SQL block body (BEGIN ... END) or call spec + var parseErr456 error stmt.Body, stmt.CallSpec, parseErr456 = p.parseSubprogramImplementation() if parseErr456 != nil { return nil, parseErr456 } + if err := p.checkCharsetScopes(subprogramName(stmt.Name), stmt.Parameters, nil, stmt.Body); err != nil { + return nil, err + } stmt.Loc.End = p.prev.End return stmt, nil @@ -191,7 +199,7 @@ func (p *Parser) parseCreateFunctionStmt(start int, orReplace, ifNotExists, edit } p.advance() var parseErr459 error - stmt.ReturnType, parseErr459 = p.parseTypeName() + stmt.ReturnType, parseErr459 = p.parsePLSQLDatatype(typeModsNone, charsetFlexible) if parseErr459 != nil { return nil, parseErr459 } @@ -212,13 +220,16 @@ func (p *Parser) parseCreateFunctionStmt(start int, orReplace, ifNotExists, edit parseErr460 := // Optional function properties (can appear in any order before IS/AS) - p.parseFunctionProperties(stmt) + p.parseFunctionProperties(stmt, subprogramSchema) if parseErr460 != // IS | AS nil { return nil, parseErr460 } + if stmt.Implementation != nil { + return p.finishTypeImplementedFunction(stmt) + } if p.cur.Type != kwIS && p.cur.Type != kwAS { return nil, p.syntaxErrorAtCur() @@ -231,113 +242,383 @@ func (p *Parser) parseCreateFunctionStmt(start int, orReplace, ifNotExists, edit if parseErr461 != nil { return nil, parseErr461 } + if err := p.checkCharsetScopes(subprogramName(stmt.Name), stmt.Parameters, stmt.ReturnType, stmt.Body); err != nil { + return nil, err + } stmt.Loc.End = p.prev.End return stmt, nil } -// parseFunctionProperties parses optional DETERMINISTIC, PIPELINED, PARALLEL_ENABLE, RESULT_CACHE, -// AGGREGATE USING, SQL_MACRO, AUTHID, and other function property clauses. -func (p *Parser) parseFunctionProperties(stmt *nodes.CreateFunctionStmt) error { +// parseFunctionProperties parses the clauses between a function's RETURN +// type and its IS | AS or ';', in any order, each at most once (PLS-00371). +// +// Ref: https://docs.oracle.com/en/database/oracle/oracle-database/23/lnpls/CREATE-FUNCTION-statement.html +// +// [ { invoker_rights_clause | accessible_by_clause | default_collation_clause +// | deterministic_clause | parallel_enable_clause | result_cache_clause +// | aggregate_clause | pipelined_clause | sql_macro_clause +// | streaming_clause }... ] +// pipelined_clause ::= PIPELINED [ { ROW | TABLE } POLYMORPHIC ] +// result_cache_clause ::= RESULT_CACHE [ RELIES_ON ( [ data_source [, data_source ]... ] ) ] +// sql_macro_clause ::= SQL_MACRO [ ( [ TYPE => ] { SCALAR | TABLE } ) ] +// aggregate_clause ::= AGGREGATE USING [ schema. ] implementation_type +// +// A type can implement the function: AGGREGATE USING type, or USING type +// after PIPELINED. The clause list ends there and the function has no IS | AS +// body; Oracle 23ai rejects a following IS or clause (PLS-00103), USING +// without PIPELINED (PLS-00624), and AGGREGATE without USING after it. level +// limits AUTHID, ACCESSIBLE BY, and DEFAULT COLLATION as subprogramLevel says. +func (p *Parser) parseFunctionProperties(stmt *nodes.CreateFunctionStmt, level subprogramLevel) error { + seen := clauseSeen{} + // notPolymorphic is the first clause a polymorphic table function does + // not take: Oracle 23ai rejects DETERMINISTIC, PARALLEL_ENABLE, ORDER + // BY, CLUSTER BY, and AUTHID on one with PLS-00760, and RESULT_CACHE + // with PLS-00999 (its TABLE parameter), before or after PIPELINED ... + // POLYMORPHIC. + // + // notMacro is the first clause a SQL macro does not take: those, and + // PIPELINED, whose collection result a macro cannot return (PLS-00778, + // PLS-00630). A macro returns a character type: NUMBER, LONG, or a + // %ROWTYPE result is PLS-00776, while CHAR, VARCHAR2, CLOB, and the + // national types compile. + var notPolymorphic, notMacro, macroTok *Token + var partTok Token + var streamToks []Token + var resultCacheTok, pipelinedTok *Token + note := func() { + tok := p.cur + if notPolymorphic == nil { + notPolymorphic = &tok + } + if notMacro == nil { + notMacro = &tok + } + } + checkPolymorphic := func(err error) error { + switch { + case err != nil: + return err + case stmt.Polymorphic != "" && notPolymorphic != nil: + return p.syntaxErrorAtTok(*notPolymorphic) + case stmt.SqlMacro && notMacro != nil: + return p.syntaxErrorAtTok(*notMacro) + case stmt.SqlMacro && !p.mayBeCharacterType(stmt.ReturnType): + return p.syntaxErrorAtTok(*macroTok) + } + if stmt.Polymorphic == "" { + if tn := p.tablePseudoType(stmt.Parameters, stmt.ReturnType); tn != nil { + return p.syntaxErrorAtType(tn) + } + } else if err := p.checkPolymorphicSignature(stmt); err != nil { + return err + } + if err := p.checkCharsetSources(stmt.Parameters, stmt.ReturnType); err != nil { + return err + } + // An ordinary pipelined function returns a collection (PLS-00630 for a + // predefined scalar or %ROWTYPE result) and is not result-cached + // (PLS-00999); a named result type is left to the engine. + if stmt.Pipelined && stmt.Polymorphic == "" { + if _, scalar := p.predefinedTypeName(stmt.ReturnType); scalar { + return p.syntaxErrorAtTok(*pipelinedTok) + } + if resultCacheTok != nil { + return p.syntaxErrorAtTok(*resultCacheTok) + } + } + // RESULT_CACHE takes no OUT or IN OUT parameter, no parameter or + // result of a LOB, BFILE, or REF CURSOR type, and no %ROWTYPE record + // parameter (PLS-00999 on Oracle 23ai, documented among the clause's + // restrictions); a %ROWTYPE result compiles. Object types are left + // to the engine. + if resultCacheTok != nil { + if p.notResultCacheable(stmt.ReturnType) { + return p.syntaxErrorAtTok(*resultCacheTok) + } + if stmt.Parameters != nil { + for _, item := range stmt.Parameters.Items { + if pr, ok := item.(*nodes.Parameter); ok && (strings.Contains(pr.Mode, "OUT") || p.notResultCacheable(pr.TypeName) || + (pr.TypeName != nil && pr.TypeName.IsPercRowtype)) { + return p.syntaxErrorAtTok(*resultCacheTok) + } + } + } + } + return p.checkParallelArguments(stmt, partTok, streamToks) + } for { switch { case p.cur.Type == kwDETERMINISTIC: + if err := p.firstClause(seen, "DETERMINISTIC"); err != nil { + return err + } + note() stmt.Deterministic = true p.advance() case p.cur.Type == kwPIPELINED: + if err := p.firstClause(seen, "PIPELINED"); err != nil { + return err + } + if notMacro == nil { + tok := p.cur + notMacro = &tok + } + ptok := p.cur + pipelinedTok = &ptok stmt.Pipelined = true p.advance() + if (p.cur.Type == kwROW || p.cur.Type == kwTABLE) && p.isIdentLikeStrAt(p.peekNext(), "POLYMORPHIC") { + // The row kind is required (PIPELINED POLYMORPHIC is + // PLS-00103), and an object type method is not polymorphic + // (PLS-00765); a nested function may be. + if level == subprogramMethod { + return p.syntaxErrorAtCur() + } + stmt.Polymorphic = "ROW" + if p.cur.Type == kwTABLE { + stmt.Polymorphic = "TABLE" + } + p.advance() // consume ROW or TABLE + p.advance() // consume POLYMORPHIC + } case p.cur.Type == kwPARALLEL_ENABLE: + if err := p.firstClause(seen, "PARALLEL_ENABLE"); err != nil { + return err + } + // A nested function is not parallel-enabled (PLS-00712 on + // Oracle 23ai); a private package body function may be. + if level == subprogramNested { + return p.syntaxErrorAtCur() + } + note() stmt.Parallel = true p.advance() + if p.cur.Type == '(' { + spec, argTok, err := p.parseParallelEnableSpec() + if err != nil { + return err + } + stmt.ParallelSpec = spec + partTok = argTok + } + case p.cur.Type == kwORDER, p.cur.Type == kwCLUSTER: + kind := "ORDER BY" + if p.cur.Type == kwCLUSTER { + kind = "CLUSTER BY" + } + if err := p.firstClause(seen, kind); err != nil { + return err + } + note() + clause, argTok, err := p.parseStreamingClause() + if err != nil { + return err + } + streamToks = append(streamToks, argTok) + if stmt.Streaming == nil { + stmt.Streaming = &nodes.List{} + } + stmt.Streaming.Items = append(stmt.Streaming.Items, clause) case p.cur.Type == kwRESULT_CACHE: + if err := p.firstClause(seen, "RESULT_CACHE"); err != nil { + return err + } + // No subprogram in a top-level anonymous block is result-cached + // (PLS-00999); one nested in a stored unit, even in a DECLARE + // block there, compiles. + if p.inAnonymousBlock { + return p.syntaxErrorAtCur() + } + note() + rcTok := p.cur + resultCacheTok = &rcTok stmt.ResultCache = true p.advance() - case p.isIdentLikeStr("AGGREGATE"): + if p.isKeywordStr("RELIES_ON") { + list, err := p.parseResultCacheReliesOn() + if err != nil { + return err + } + stmt.ReliesOn = list + } + case p.isKeywordStr("AGGREGATE"): + aggTok := p.cur + // PIPELINED and AGGREGATE exclude each other (PLS-00371), unless + // the function is polymorphic: Oracle 23ai compiles PIPELINED + // ROW POLYMORPHIC AGGREGATE USING type, and SQL_MACRO with it. + if stmt.Pipelined && stmt.Polymorphic == "" { + return p.syntaxErrorAtCur() + } stmt.Aggregate = true p.advance() // consume AGGREGATE - if p.cur.Type == kwUSING { - p.advance() - parseDiscard463, // consume USING - parseErr462 := p.parseObjectName() - _ = // consume implementation type - parseDiscard463 - if parseErr462 != nil { - return parseErr462 - } + if p.cur.Type != kwUSING { + return p.syntaxErrorAtCur() + } + implTok := p.cur + if err := p.parseImplementationType(stmt); err != nil { + return err + } + // An aggregate's implementation type is local: Oracle 23ai + // rejects one over a database link with PLS-00331, while a + // polymorphic function's package may be remote. + if stmt.Implementation.DBLink != "" { + return p.syntaxErrorAtTok(implTok) + } + // An aggregate takes an argument: Oracle 23ai rejects one + // without parameters (PLS-00652) and compiles one with two, or + // with an OUT parameter, against its implementation type. + if stmt.Parameters == nil || stmt.Parameters.Len() == 0 { + return p.syntaxErrorAtTok(aggTok) } - case p.isIdentLikeStr("SQL_MACRO"): + return checkPolymorphic(nil) + case p.cur.Type == kwUSING: + if !stmt.Pipelined { + return p.syntaxErrorAtCur() + } + return checkPolymorphic(p.parseImplementationType(stmt)) + case p.isKeywordStr("SQL_MACRO"): + if err := p.firstClause(seen, "SQL_MACRO"); err != nil { + return err + } + // A type method is not a SQL macro (PLS-00781). + if level == subprogramMethod { + return p.syntaxErrorAtCur() + } + tok := p.cur + macroTok = &tok stmt.SqlMacro = true p.advance() // consume SQL_MACRO - // Optional ( SCALAR | TABLE ) if p.cur.Type == '(' { - p.advance() - if p.isIdentLike() { - p.advance() // consume SCALAR or TABLE - } - if p.cur.Type == ')' { - p.advance() + kind, err := p.parseSqlMacroType() + if err != nil { + return err } + stmt.SqlMacroType = kind + } + case p.isKeywordStr("AUTHID") && level == subprogramSchema: + if err := p.firstClause(seen, "AUTHID"); err != nil { + return err } - case p.isIdentLikeStr("AUTHID"): - authID, parseErr464 := p.parseOptionalAuthID() - if parseErr464 != nil { - return parseErr464 + note() + authID, err := p.parseOptionalAuthID() + if err != nil { + return err } stmt.AuthID = authID - case p.isIdentLikeStr("ACCESSIBLE"): - // accessible_by_clause: ACCESSIBLE BY ( accessor [, ...] ) - p.advance() // consume ACCESSIBLE - if p.cur.Type == kwBY { - p.advance() // consume BY + case p.isKeywordStr("ACCESSIBLE") && level.takesAccessibleBy(): + if err := p.firstClause(seen, "ACCESSIBLE BY"); err != nil { + return err } - if p.cur.Type == '(' { - p.advance() - for p.cur.Type != ')' && p.cur.Type != tokEOF { - parseDiscard465, parseErr464 := p.parseObjectName() - _ = parseDiscard465 - if parseErr464 != nil { - return parseErr464 - } - if p.cur.Type != ',' { - break - } - p.advance() - } - if p.cur.Type == ')' { - p.advance() - } + list, err := p.parseAccessibleByClause() + if err != nil { + return err + } + stmt.AccessibleBy = list + case p.atDefaultCollation() && level == subprogramSchema: + if err := p.firstClause(seen, "DEFAULT COLLATION"); err != nil { + return err } - case p.cur.Type == kwDEFAULT && p.isIdentLikeStrAt(p.peekNext(), "COLLATION"): - // default_collation_clause: DEFAULT COLLATION collation_name - p.advance() // consume DEFAULT - p.advance() // consume COLLATION - if p.isIdentLike() { - p.advance() // consume collation name + collation, err := p.parseDefaultCollationClause() + if err != nil { + return err } + stmt.DefaultCollation = collation default: - return nil + return checkPolymorphic(nil) } } +} + +// notResultCacheable reports whether tn is a predefined type a RESULT_CACHE +// function cannot take or return: a LOB, BFILE, or SYS_REFCURSOR. +func (p *Parser) notResultCacheable(tn *nodes.TypeName) bool { + switch p.predefinedTypeLead(tn) { + case "BLOB", "CLOB", "NCLOB", "BFILE", "SYS_REFCURSOR": + return true + } + return false +} + +// mayBeCharacterType reports whether tn may hold character data: it is not a +// predefined non-character type (the national character types count as +// character types) and not a %ROWTYPE record. A SQL macro returns such a +// type (PLS-00776), and item%CHARSET takes its character set from one +// (PLS-00550). A name the text does not settle is left to the engine. +func (p *Parser) mayBeCharacterType(tn *nodes.TypeName) bool { + if tn == nil { + return true + } + if tn.IsPercRowtype { + return false + } + switch lead := p.predefinedTypeLead(tn); lead { + case "NCHAR", "NVARCHAR2", "NCLOB": + return true + default: + return !plsqlNonCharacterTypes[lead] + } +} + +// parseImplementationType parses USING [ schema. ] implementation_type +// [ @dblink ], the current token at USING. Every part needs its name: Oracle +// 23ai rejects USING impl. and USING impl@ (PLS-00103), and takes a dotted +// database link name (impl@lnk.dom). +func (p *Parser) parseImplementationType(stmt *nodes.CreateFunctionStmt) error { + p.advance() // consume USING + name, err := p.parseUnitName() + if err != nil { + return err + } + if p.cur.Type == '@' { + p.advance() + if !p.isIdentLike() { + return p.syntaxErrorAtCur() + } + link, err := p.parseIdentifier() + if err != nil { + return err + } + for p.cur.Type == '.' { + p.advance() + if !p.isIdentLike() { + return p.syntaxErrorAtCur() + } + part, err := p.parseIdentifier() + if err != nil { + return err + } + link += "." + part + } + name.DBLink = link + name.Loc.End = p.prev.End + } + stmt.Implementation = name return nil } +// finishTypeImplementedFunction ends a function a type implements, whose +// clause list ends at the type name: only its ';' follows. +func (p *Parser) finishTypeImplementedFunction(stmt *nodes.CreateFunctionStmt) (*nodes.CreateFunctionStmt, error) { + if p.cur.Type != ';' { + return nil, p.syntaxErrorAtCur() + } + p.advance() + stmt.Loc.End = p.prev.End + return stmt, nil +} + func (p *Parser) parseOptionalAuthID() (string, error) { if !p.isIdentLikeStr("AUTHID") { return "", nil } p.advance() + // CURRENT_USER is one word: Oracle 23ai rejects AUTHID CURRENT and + // AUTHID CURRENT USER with PLS-00103. if p.isIdentLikeStr("CURRENT_USER") { p.advance() return "CURRENT_USER", nil } - if p.cur.Type == kwCURRENT { - p.advance() - if p.isIdentLikeStr("USER") { - p.advance() - } - return "CURRENT_USER", nil - } if p.isIdentLikeStr("DEFINER") { p.advance() return "DEFINER", nil @@ -406,38 +687,11 @@ func (p *Parser) parseCreatePackageStmt(start int, orReplace, ifNotExists, editi } } - // Optional ACCESSIBLE BY ( ... ) — skip for package body - if !stmt.IsBody && p.isIdentLikeStr("ACCESSIBLE") { - p.advance() // consume ACCESSIBLE - if p.cur.Type == kwBY { - p.advance() // consume BY - } - if p.cur.Type == '(' { - p.advance() - for p.cur.Type != ')' && p.cur.Type != tokEOF { - parseDiscard468, - // Each accessor: [ unit_kind ] [ schema. ] unit_name - parseErr467 := p.parseObjectName() - _ = parseDiscard468 - if parseErr467 != nil { - return nil, parseErr467 - } - if p.cur.Type != ',' { - break - } - p.advance() - } - if p.cur.Type == ')' { - p.advance() - } - } - } - - // Optional invoker_rights_clause: AUTHID { CURRENT_USER | DEFINER } - if !stmt.IsBody && p.isIdentLikeStr("AUTHID") { - p.advance() // consume AUTHID - if p.isIdentLike() { - p.advance() // consume CURRENT_USER or DEFINER + // ACCESSIBLE BY, AUTHID, and DEFAULT COLLATION head a specification, + // in any order; a body takes none of them. + if !stmt.IsBody { + if err := p.parsePackageProperties(stmt); err != nil { + return nil, err } } @@ -449,13 +703,30 @@ func (p *Parser) parseCreatePackageStmt(start int, orReplace, ifNotExists, editi var parseErr469 error // Package declarations/body - collect everything until END + savedInBody := p.inPackageBody + p.inPackageBody = stmt.IsBody + p.typeScopes = append(p.typeScopes, map[string]bool{}) stmt.Body, parseErr469 = p.parsePackageBody() + p.typeScopes = p.typeScopes[:len(p.typeScopes)-1] + p.inPackageBody = savedInBody if parseErr469 != // END [name] ; nil { return nil, parseErr469 } + if err := p.checkPackageCharsetScopes(subprogramName(stmt.Name), stmt.Body); err != nil { + return nil, err + } + // A package holding a SQL macro has definer's rights: Oracle 23ai + // rejects AUTHID CURRENT_USER on one with PLS-00782. + if stmt.AuthID == "CURRENT_USER" && stmt.Body != nil { + for _, item := range stmt.Body.Items { + if fn, ok := item.(*nodes.CreateFunctionStmt); ok && fn.SqlMacro { + return nil, p.syntaxErrorAtTok(Token{Type: kwFUNCTION, Loc: fn.Loc.Start, End: fn.Loc.Start + len("FUNCTION")}) + } + } + } if p.cur.Type != kwEND { return nil, p.syntaxErrorAtCur() @@ -482,7 +753,7 @@ func (p *Parser) parsePackageBody() (*nodes.List, error) { for p.cur.Type != kwEND && p.cur.Type != tokEOF { // PROCEDURE declaration/definition in package if p.cur.Type == kwPROCEDURE { - decl, parseErr470 := p.parsePackageProcDecl() + decl, parseErr470 := p.parsePackageProcDecl(subprogramPackaged) if parseErr470 != nil { return nil, parseErr470 } @@ -494,7 +765,7 @@ func (p *Parser) parsePackageBody() (*nodes.List, error) { // FUNCTION declaration/definition in package if p.cur.Type == kwFUNCTION { - decl, parseErr471 := p.parsePackageFuncDecl() + decl, parseErr471 := p.parsePackageFuncDecl(subprogramPackaged) if parseErr471 != nil { return nil, parseErr471 } @@ -528,7 +799,7 @@ func (p *Parser) parsePackageBody() (*nodes.List, error) { // // PROCEDURE name [(params)] ; -- specification // PROCEDURE name [(params)] IS|AS body ; -- body definition -func (p *Parser) parsePackageProcDecl() (*nodes.CreateProcedureStmt, error) { +func (p *Parser) parsePackageProcDecl(level subprogramLevel) (*nodes.CreateProcedureStmt, error) { start := p.pos() p.advance() // consume PROCEDURE @@ -555,6 +826,15 @@ func (p *Parser) parsePackageProcDecl() (*nodes.CreateProcedureStmt, error) { return nil, parseErr474 } } + if tn := p.tablePseudoType(stmt.Parameters, nil); tn != nil { + return nil, p.syntaxErrorAtType(tn) + } + if err := p.checkCharsetSources(stmt.Parameters, nil); err != nil { + return nil, err + } + if err := p.parseProcedureProperties(stmt, level); err != nil { + return nil, err + } if p.cur.Type == kwIS || p.cur.Type == kwAS { p.advance() @@ -577,7 +857,7 @@ func (p *Parser) parsePackageProcDecl() (*nodes.CreateProcedureStmt, error) { // // FUNCTION name [(params)] RETURN type ; -- specification // FUNCTION name [(params)] RETURN type IS|AS body ; -- body definition -func (p *Parser) parsePackageFuncDecl() (*nodes.CreateFunctionStmt, error) { +func (p *Parser) parsePackageFuncDecl(level subprogramLevel) (*nodes.CreateFunctionStmt, error) { start := p.pos() p.advance() // consume FUNCTION @@ -608,7 +888,7 @@ func (p *Parser) parsePackageFuncDecl() (*nodes.CreateFunctionStmt, error) { if p.cur.Type == kwRETURN { p.advance() var parseErr478 error - stmt.ReturnType, parseErr478 = p.parseTypeName() + stmt.ReturnType, parseErr478 = p.parsePLSQLDatatype(typeModsNone, charsetFlexible) if parseErr478 != // Optional function properties @@ -621,13 +901,16 @@ func (p *Parser) parsePackageFuncDecl() (*nodes.CreateFunctionStmt, error) { } else { return nil, p.syntaxErrorAtCur() } - parseErr479 := p.parseFunctionProperties(stmt) + parseErr479 := p.parseFunctionProperties(stmt, level) if parseErr479 != // Check for IS|AS (definition) or ; (declaration) nil { return nil, parseErr479 } + if stmt.Implementation != nil { + return p.finishTypeImplementedFunction(stmt) + } if p.cur.Type == kwIS || p.cur.Type == kwAS { p.advance() @@ -703,7 +986,8 @@ func (p *Parser) parseParameter() (*nodes.Parameter, error) { param.Mode = mode var parseErr484 error - param.TypeName, parseErr484 = p.parseTypeName() + // A parameter type is unconstrained and may name ANY_CS. + param.TypeName, parseErr484 = p.parsePLSQLDatatype(typeModsNone, charsetAnyCS) if parseErr484 != // Optional default value: := expr or DEFAULT expr diff --git a/oracle/parser/create_proc_test.go b/oracle/parser/create_proc_test.go index 6cdc78af..e840502f 100644 --- a/oracle/parser/create_proc_test.go +++ b/oracle/parser/create_proc_test.go @@ -190,7 +190,9 @@ func TestParseCreateFunctionDeterministic(t *testing.T) { } func TestParseCreateFunctionPipelined(t *testing.T) { - sql := `CREATE FUNCTION pipe_fn RETURN NUMBER PIPELINED IS BEGIN NULL; END;` + // A pipelined function returns a collection: Oracle 23ai rejects a + // NUMBER result with PLS-00630. + sql := `CREATE FUNCTION pipe_fn RETURN num_tab PIPELINED IS BEGIN NULL; END;` result, err := Parse(sql) if err != nil { t.Fatalf("parse error: %v", err) diff --git a/oracle/parser/create_trigger.go b/oracle/parser/create_trigger.go index 232b4fe1..352d21ba 100644 --- a/oracle/parser/create_trigger.go +++ b/oracle/parser/create_trigger.go @@ -196,11 +196,14 @@ func (p *Parser) parseCreateTriggerStmt(start int, orReplace, ifNotExists, editi Loc: callName.Loc, } } else if p.cur.Type == kwDECLARE || p.cur.Type == kwBEGIN || p.cur.Type == tokLABELOPEN { - var parseErr571 error - stmt.Body, parseErr571 = p.parsePLSQLBlock() + block, parseErr571 := p.parsePLSQLBlock() if parseErr571 != nil { return nil, parseErr571 } + if err := p.checkBlockCharsetScopes(block); err != nil { + return nil, err + } + stmt.Body = block } stmt.Loc.End = p.prev.End diff --git a/oracle/parser/create_type.go b/oracle/parser/create_type.go index c2fa3942..dd9e87af 100644 --- a/oracle/parser/create_type.go +++ b/oracle/parser/create_type.go @@ -348,6 +348,14 @@ func (p *Parser) parseTypeBodyProcedure(inSpec bool) (*nodes.CreateProcedureStmt return nil, parseErr583 } } + // The heading checks of other procedures hold for a method too + // (PLS-00765, PLS-00550 on Oracle 23ai). + if tn := p.tablePseudoType(stmt.Parameters, nil); tn != nil { + return nil, p.syntaxErrorAtType(tn) + } + if err := p.checkCharsetSources(stmt.Parameters, nil); err != nil { + return nil, err + } if inSpec { var err error @@ -372,6 +380,12 @@ func (p *Parser) parseTypeBodyProcedure(inSpec bool) (*nodes.CreateProcedureStmt if parseErr584 != nil { return nil, parseErr584 } + // A method's formals and locals shadow the type's attributes, so a + // %CHARSET source found among them, or qualified by the method name, is + // the one Oracle 23ai resolves. + if err := p.checkCharsetScopes(subprogramName(stmt.Name), stmt.Parameters, nil, stmt.Body); err != nil { + return nil, err + } stmt.Loc.End = p.prev.End return stmt, nil @@ -450,7 +464,7 @@ func (p *Parser) parseTypeBodyFunction(isConstructor, inSpec bool) (*nodes.Creat } } else { var parseErr587 error - stmt.ReturnType, parseErr587 = p.parseTypeName() + stmt.ReturnType, parseErr587 = p.parsePLSQLDatatype(typeModsNone, charsetFlexible) if parseErr587 != nil { return nil, parseErr587 } @@ -463,7 +477,7 @@ func (p *Parser) parseTypeBodyFunction(isConstructor, inSpec bool) (*nodes.Creat // (PLS-00103 on Oracle 23ai). return nil, p.syntaxErrorAtCur() } - parseErr588 := p.parseFunctionProperties(stmt) + parseErr588 := p.parseFunctionProperties(stmt, subprogramMethod) if parseErr588 != // IS | AS @@ -494,6 +508,12 @@ func (p *Parser) parseTypeBodyFunction(isConstructor, inSpec bool) (*nodes.Creat if parseErr589 != nil { return nil, parseErr589 } + // A method's formals and locals shadow the type's attributes, so a + // %CHARSET source found among them, or qualified by the method name, is + // the one Oracle 23ai resolves. + if err := p.checkCharsetScopes(subprogramName(stmt.Name), stmt.Parameters, stmt.ReturnType, stmt.Body); err != nil { + return nil, err + } stmt.Loc.End = p.prev.End return stmt, nil diff --git a/oracle/parser/parser.go b/oracle/parser/parser.go index 2bee07e4..afb4cd84 100644 --- a/oracle/parser/parser.go +++ b/oracle/parser/parser.go @@ -28,10 +28,48 @@ type Parser struct { // or names a column (false). decideCaseTokens fills it in one pass. caseKinds map[int]bool - // plsqlTypeMods is set while parsePLSQLTypeName parses the datatype of - // a PL/SQL declaration, where a length, precision, or scale may be an - // expression rather than an integer literal. - plsqlTypeMods bool + // typeMods says what the parenthesized modifiers of the datatype being + // parsed may hold; parsePLSQLDatatype sets it for PL/SQL datatypes. + typeMods typeModMode + + // qualifiedCharsets holds the components of each item%CHARSET source + // that is a qualified name (k.c), as opposed to one identifier that may + // itself be a quoted "K.C". + qualifiedCharsets map[*nodes.TypeName][]string + + // typeScopes holds, for each open PL/SQL declaration scope, the names + // its TYPE and SUBTYPE declarations have declared so far. Oracle 23ai + // lets one redeclare a predefined type name, NUMBER or SYS_REFCURSOR + // alike, and the redeclaration then shadows STANDARD's type. + typeScopes []map[string]bool + + // inPackageBody is set while a package body's items are parsed: its + // specification may redeclare any predefined type name out of sight. + inPackageBody bool + + // shadowedTypes records the datatypes spelled as a predefined type + // where a redeclaration of that name is, or may be, in scope. + shadowedTypes map[*nodes.TypeName]bool + + // inAnonymousBlock is set while a top-level anonymous block is parsed: + // Oracle 23ai rejects RESULT_CACHE on any subprogram in one (PLS-00999). + inAnonymousBlock bool +} + +// parseAnonymousBlock parses a top-level anonymous block, noting that its +// subprograms are in one. +func (p *Parser) parseAnonymousBlock() (nodes.StmtNode, error) { + saved := p.inAnonymousBlock + p.inAnonymousBlock = true + block, err := p.parsePLSQLBlock() + p.inAnonymousBlock = saved + if err != nil { + return nil, err + } + if err := p.checkBlockCharsetScopes(block); err != nil { + return nil, err + } + return block, nil } // Parse parses a SQL string into an AST list. @@ -305,10 +343,8 @@ func (p *Parser) parseStmt() (nodes.StmtNode, error) { return p.parseRevokeStmt() case kwCREATE: return p.parseCreateStmt() - case kwDECLARE, kwBEGIN: - return p.parsePLSQLBlock() - case tokLABELOPEN: - return p.parsePLSQLBlock() + case kwDECLARE, kwBEGIN, tokLABELOPEN: + return p.parseAnonymousBlock() default: // Handle identifier-based statements if p.isIdentLike() { diff --git a/oracle/parser/plsql_block.go b/oracle/parser/plsql_block.go index 72d5189f..c115feb3 100644 --- a/oracle/parser/plsql_block.go +++ b/oracle/parser/plsql_block.go @@ -8,6 +8,8 @@ import ( // // [<