From c47c63c4996827aa7a9efc9859795e2a74f58449 Mon Sep 17 00:00:00 2001 From: Drew Kimball Date: Thu, 28 Sep 2023 16:39:56 -0600 Subject: [PATCH 1/3] plpgsql: correctly handle parsing errors This patch ensures that PLpgSQL parsing errors are correctly propagated in all cases. Previously, there were a few cases (like variable declaration type parsing) where an error didn't halt parsing. The contract for `GetTypeFromValidSQLSyntax` is also clarified, since it is ok to call with an invalid type name as long as the error is properly handled. Informs #105254 Release note: None --- pkg/sql/parser/parse.go | 4 ++-- pkg/sql/plpgsql/parser/plpgsql.y | 5 ++--- pkg/sql/plpgsql/parser/testdata/decl_header | 17 +++++++++++++++++ 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/pkg/sql/parser/parse.go b/pkg/sql/parser/parse.go index f3938fd689e7..6df3486283a5 100644 --- a/pkg/sql/parser/parse.go +++ b/pkg/sql/parser/parse.go @@ -388,8 +388,8 @@ func GetTypeReferenceFromName(typeName tree.Name) (tree.ResolvableTypeReference, // GetTypeFromValidSQLSyntax retrieves a type from its SQL syntax. The caller is // responsible for guaranteeing that the type expression is valid -// SQL. This includes verifying that complex identifiers are enclosed -// in double quotes, etc. +// SQL (or handling the resulting error). This includes verifying that complex +// identifiers are enclosed in double quotes, etc. func GetTypeFromValidSQLSyntax(sql string) (tree.ResolvableTypeReference, error) { expr, err := ParseExpr(fmt.Sprintf("1::%s", sql)) if err != nil { diff --git a/pkg/sql/plpgsql/parser/plpgsql.y b/pkg/sql/plpgsql/parser/plpgsql.y index 5ddc6a4698af..59977577f457 100644 --- a/pkg/sql/plpgsql/parser/plpgsql.y +++ b/pkg/sql/plpgsql/parser/plpgsql.y @@ -548,10 +548,9 @@ decl_datatype: sqlStr, _ := plpgsqllex.(*lexer).ReadSqlConstruct( ';', COLLATE, NOT, '=', COLON_EQUALS, DECLARE, ) - // TODO(drewk): need to ensure the syntax for the type is correct. typ, err := plpgsqllex.(*lexer).GetTypeFromValidSQLSyntax(sqlStr) if err != nil { - setErr(plpgsqllex, err) + return setErr(plpgsqllex, err) } $$.val = typ } @@ -819,7 +818,7 @@ getdiag_item: unreserved_keyword { $$.val = plpgsqltree.GetDiagnosticsReturnedSQLState; default: // TODO(jane): Should this use an unimplemented error instead? - setErr(plpgsqllex, errors.Newf("unrecognized GET DIAGNOSTICS item: %s", redact.Safe($1))) + return setErr(plpgsqllex, errors.Newf("unrecognized GET DIAGNOSTICS item: %s", redact.Safe($1))) } } ; diff --git a/pkg/sql/plpgsql/parser/testdata/decl_header b/pkg/sql/plpgsql/parser/testdata/decl_header index 3cf7d9132b35..97644cc9bf9c 100644 --- a/pkg/sql/plpgsql/parser/testdata/decl_header +++ b/pkg/sql/plpgsql/parser/testdata/decl_header @@ -58,3 +58,20 @@ BEGIN END ---- at or near "(": syntax error: unimplemented: this syntax + +# Correctly handle parsing errors for variable types. +parse +DECLARE + var1 one.two.three.four; +BEGIN +END +---- +at or near "four": at or near ".": syntax error + +parse +DECLARE + var1 one.two.three.four := 0; +BEGIN +END +---- +at or near "four": at or near ".": syntax error From 7f40d3fd3d91beb3c7e55d6c83acdc98128ecec3 Mon Sep 17 00:00:00 2001 From: Drew Kimball Date: Wed, 11 Oct 2023 14:08:02 -0600 Subject: [PATCH 2/3] plpgsql: handle multiple expressions when one expression is expected Previously, the PLpgSQL parser could panic when the user supplied more than one expression in a location where only one was expected, for example, in a return statement. This was because the PLpgSQL parser delegated to the SQL parser's `ParseExpr` function, which expects exactly one input expression. This commit returns a syntax error instead of the panic by switching to use `ParseExprs`, which can handle multiple input expressions. Informs #109342 Release note: None --- pkg/sql/plpgsql/parser/lexer.go | 11 +++++++++- pkg/sql/plpgsql/parser/testdata/stmt_assign | 23 +++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/pkg/sql/plpgsql/parser/lexer.go b/pkg/sql/plpgsql/parser/lexer.go index 517540e5d1ed..c5fe8f78d07f 100644 --- a/pkg/sql/plpgsql/parser/lexer.go +++ b/pkg/sql/plpgsql/parser/lexer.go @@ -463,5 +463,14 @@ func (l *lexer) GetTypeFromValidSQLSyntax(sqlStr string) (tree.ResolvableTypeRef } func (l *lexer) ParseExpr(sqlStr string) (plpgsqltree.Expr, error) { - return parser.ParseExpr(sqlStr) + // Use ParseExprs instead of ParseExpr in order to correctly handle the case + // when multiple expressions are incorrectly passed. + exprs, err := parser.ParseExprs([]string{sqlStr}) + if err != nil { + return nil, err + } + if len(exprs) != 1 { + return nil, pgerror.Newf(pgcode.Syntax, "query returned %d columns", len(exprs)) + } + return exprs[0], nil } diff --git a/pkg/sql/plpgsql/parser/testdata/stmt_assign b/pkg/sql/plpgsql/parser/testdata/stmt_assign index 92a4acb01ffb..1beaab827ec5 100644 --- a/pkg/sql/plpgsql/parser/testdata/stmt_assign +++ b/pkg/sql/plpgsql/parser/testdata/stmt_assign @@ -22,6 +22,29 @@ BEGIN a := NULL; END +parse +DECLARE +BEGIN + a := 1, 'string'; +END +---- +at or near ";": syntax error: query returned 2 columns + +parse +DECLARE +BEGIN + a := 1, (2, 3, 4, 5); +END +---- +at or near ";": syntax error: query returned 2 columns + +parse +DECLARE +BEGIN + a := 1, (2, 3, 4, 5), 'abcd', true, ((1)); +END +---- +at or near ";": syntax error: query returned 5 columns feature-count DECLARE From 6e331e6583e17d2e50e3b0c64934626f588b6748 Mon Sep 17 00:00:00 2001 From: Drew Kimball Date: Wed, 11 Oct 2023 14:08:43 -0600 Subject: [PATCH 3/3] plpgsql: return correct error for invalid parantheses and missing expression This patch fixes error messages in the PLpgSQL parser for the case when the parenthesis nesting is invalid, and for the case when no expression (or statement) is supplied. Previously, invalid parentheses would cause a panic without an error code, and a missing expression had the incorrect message, since it wasn't checked until the SQL parser attempted to read an empty string. Now, both cases are checked immediately by the PLpgSQL parser and the correct error is propagated. Fixes #109342 Release note: None --- pkg/sql/plpgsql/parser/lexer.go | 99 ++++++++------- pkg/sql/plpgsql/parser/plpgsql.y | 117 ++++++++++++++---- pkg/sql/plpgsql/parser/testdata/stmt_assign | 32 +++++ pkg/sql/plpgsql/parser/testdata/stmt_exec_sql | 24 ++++ .../plpgsql/parser/testdata/stmt_fetch_move | 2 +- pkg/sql/plpgsql/parser/testdata/stmt_open | 8 ++ pkg/sql/plpgsql/parser/testdata/stmt_return | 62 +++++++++- 7 files changed, 269 insertions(+), 75 deletions(-) diff --git a/pkg/sql/plpgsql/parser/lexer.go b/pkg/sql/plpgsql/parser/lexer.go index c5fe8f78d07f..eb99d3a3036e 100644 --- a/pkg/sql/plpgsql/parser/lexer.go +++ b/pkg/sql/plpgsql/parser/lexer.go @@ -128,9 +128,9 @@ func (l *lexer) MakeExecSqlStmt() (*plpgsqltree.Execute, error) { } // Push back the first token so that it's included in the SQL string. l.PushBack(1) - startPos, endPos, _ := l.readSQLConstruct(';') - if endPos <= startPos || startPos <= 0 { - return nil, errors.New("expected SQL statement") + startPos, endPos, _, err := l.readSQLConstruct(false /* isExpr */, ';') + if err != nil { + return nil, err } // Move past the semicolon. l.lastPos++ @@ -200,8 +200,11 @@ func (l *lexer) MakeExecSqlStmt() (*plpgsqltree.Execute, error) { }, nil } -func (l *lexer) MakeDynamicExecuteStmt() *plpgsqltree.DynamicExecute { - cmdStr, _ := l.ReadSqlConstruct(INTO, USING, ';') +func (l *lexer) MakeDynamicExecuteStmt() (*plpgsqltree.DynamicExecute, error) { + cmdStr, _, err := l.ReadSqlStatement(INTO, USING, ';') + if err != nil { + return nil, err + } ret := &plpgsqltree.DynamicExecute{ Query: cmdStr, } @@ -211,7 +214,7 @@ func (l *lexer) MakeDynamicExecuteStmt() *plpgsqltree.DynamicExecute { for { if lval.id == INTO { if ret.Into { - l.setErr(errors.AssertionFailedf("seen multiple INTO")) + return nil, errors.New("multiple INTO keywords") } ret.Into = true nextTok := l.Peek() @@ -221,15 +224,21 @@ func (l *lexer) MakeDynamicExecuteStmt() *plpgsqltree.DynamicExecute { } // TODO we need to read each "INTO" variable name instead of just a // string. - l.ReadSqlExpressionStr2(USING, ';') + _, _, err = l.ReadSqlExpr(USING, ';') + if err != nil { + return nil, err + } l.Lex(&lval) } else if lval.id == USING { if ret.Params != nil { - l.setErr(errors.AssertionFailedf("seen multiple USINGs")) + return nil, errors.New("multiple USING keywords") } ret.Params = make([]plpgsqltree.Expr, 0) for { - l.ReadSqlConstruct(',', ';', INTO) + _, _, err = l.ReadSqlExpr(',', ';', INTO) + if err != nil { + return nil, err + } ret.Params = append(ret.Params, nil) l.Lex(&lval) if lval.id == ';' { @@ -239,32 +248,16 @@ func (l *lexer) MakeDynamicExecuteStmt() *plpgsqltree.DynamicExecute { } else if lval.id == ';' { break } else { - l.setErr(errors.AssertionFailedf("syntax error")) + return nil, errors.Newf("unexpected token: %s", lval.id) } } - return ret -} - -// ReadSqlExpressionStr returns the string from the l.lastPos till it sees -// the terminator for the first time. The returned string is made by tokens -// between the starting index (included) to the terminator (not included). -// TODO(plpgsql-team): pass the output to the sql parser -// (i.e. sqlParserImpl.Parse()). -func (l *lexer) ReadSqlExpressionStr(terminator int) (sqlStr string) { - sqlStr, _ = l.ReadSqlConstruct(terminator, 0, 0) - return sqlStr -} - -func (l *lexer) ReadSqlExpressionStr2( - terminator1 int, terminator2 int, -) (sqlStr string, terminatorMet int) { - return l.ReadSqlConstruct(terminator1, terminator2, 0) + return ret, nil } func (l *lexer) readSQLConstruct( - terminator1 int, terminators ...int, -) (startPos, endPos, terminatorMet int) { + isExpr bool, terminator1 int, terminators ...int, +) (startPos, endPos, terminatorMet int, err error) { if l.parser.Lookahead() != -1 { // Push back the lookahead token so that it can be included. l.PushBack(1) @@ -290,24 +283,26 @@ func (l *lexer) readSQLConstruct( } else if tok.id == ')' || tok.id == ']' { parenLevel-- if parenLevel < 0 { - panic(errors.AssertionFailedf("wrongly nested parentheses")) + return 0, 0, 0, errors.New("mismatched parentheses") } } l.lastPos++ } if parenLevel != 0 { - panic(errors.AssertionFailedf("parentheses is badly nested")) - } - if startPos > l.lastPos { - //TODO(jane): show the terminator in the panic message. - l.setErr(errors.New("missing SQL expression")) - return 0, 0, 0 + return 0, 0, 0, errors.New("mismatched parentheses") } endPos = l.lastPos + 1 if endPos > len(l.tokens) { endPos = len(l.tokens) } - return startPos, endPos, terminatorMet + if endPos <= startPos { + if isExpr { + return 0, 0, 0, errors.New("missing expression") + } else { + return 0, 0, 0, errors.New("missing SQL statement") + } + } + return startPos, endPos, terminatorMet, nil } func (l *lexer) MakeFetchOrMoveStmt(isMove bool) (plpgsqltree.Statement, error) { @@ -319,7 +314,10 @@ func (l *lexer) MakeFetchOrMoveStmt(isMove bool) (plpgsqltree.Statement, error) if isMove { prefix = "MOVE " } - sqlStr, terminator := l.ReadSqlConstruct(INTO, ';') + sqlStr, terminator, err := l.ReadSqlStatement(INTO, ';') + if err != nil { + return nil, err + } sqlStr = prefix + sqlStr sqlStmt, err := parser.ParseOne(sqlStr) if err != nil { @@ -341,7 +339,10 @@ func (l *lexer) MakeFetchOrMoveStmt(isMove bool) (plpgsqltree.Statement, error) } // Read past the INTO. l.lastPos++ - startPos, endPos, _ := l.readSQLConstruct(';') + startPos, endPos, _, err := l.readSQLConstruct(true /* isExpr */, ';') + if err != nil { + return nil, err + } for pos := startPos; pos < endPos; pos += 2 { tok := l.tokens[pos] if tok.id != IDENT { @@ -366,12 +367,24 @@ func (l *lexer) MakeFetchOrMoveStmt(isMove bool) (plpgsqltree.Statement, error) }, nil } -func (l *lexer) ReadSqlConstruct( +func (l *lexer) ReadSqlExpr( + terminator1 int, terminators ...int, +) (sqlStr string, terminatorMet int, err error) { + var startPos, endPos int + startPos, endPos, terminatorMet, err = l.readSQLConstruct( + true /* isExpr */, terminator1, terminators..., + ) + return l.getStr(startPos, endPos), terminatorMet, err +} + +func (l *lexer) ReadSqlStatement( terminator1 int, terminators ...int, -) (sqlStr string, terminatorMet int) { +) (sqlStr string, terminatorMet int, err error) { var startPos, endPos int - startPos, endPos, terminatorMet = l.readSQLConstruct(terminator1, terminators...) - return l.getStr(startPos, endPos), terminatorMet + startPos, endPos, terminatorMet, err = l.readSQLConstruct( + false /* isExpr */, terminator1, terminators..., + ) + return l.getStr(startPos, endPos), terminatorMet, err } func (l *lexer) getStr(startPos, endPos int) string { diff --git a/pkg/sql/plpgsql/parser/plpgsql.y b/pkg/sql/plpgsql/parser/plpgsql.y index 59977577f457..10e5710683a5 100644 --- a/pkg/sql/plpgsql/parser/plpgsql.y +++ b/pkg/sql/plpgsql/parser/plpgsql.y @@ -316,7 +316,7 @@ func (u *plpgsqlSymUnion) sqlStatement() tree.Statement { %type decl_datatype %type decl_collate -%type expr_until_semi expr_until_paren +%type expr_until_semi expr_until_paren stmt_until_semi %type expr_until_then expr_until_loop opt_expr_until_when %type opt_exitcond @@ -480,7 +480,7 @@ opt_scrollable: } ; -decl_cursor_query: expr_until_semi ';' +decl_cursor_query: stmt_until_semi ';' { stmts, err := parser.Parse($1) if err != nil { @@ -545,9 +545,12 @@ decl_datatype: { // Read until reaching one of the tokens that can follow a declaration // data type. - sqlStr, _ := plpgsqllex.(*lexer).ReadSqlConstruct( + sqlStr, _, err := plpgsqllex.(*lexer).ReadSqlExpr( ';', COLLATE, NOT, '=', COLON_EQUALS, DECLARE, ) + if err != nil { + return setErr(plpgsqllex, err) + } typ, err := plpgsqllex.(*lexer).GetTypeFromValidSQLSyntax(sqlStr) if err != nil { return setErr(plpgsqllex, err) @@ -596,11 +599,19 @@ decl_defval: ';' decl_defkey: assign_operator { - $$ = plpgsqllex.(*lexer).ReadSqlExpressionStr(';') + sqlStr, _, err := plpgsqllex.(*lexer).ReadSqlExpr(';') + if err != nil { + return setErr(plpgsqllex, err) + } + $$ = sqlStr } | DEFAULT { - $$ = plpgsqllex.(*lexer).ReadSqlExpressionStr(';') + sqlStr, _, err := plpgsqllex.(*lexer).ReadSqlExpr(';') + if err != nil { + return setErr(plpgsqllex, err) + } + $$ = sqlStr } ; @@ -711,7 +722,7 @@ proc_stmt:pl_block ';' { } ; -stmt_perform: PERFORM expr_until_semi ';' +stmt_perform: PERFORM stmt_until_semi ';' { return unimplemented(plpgsqllex, "perform") } @@ -729,7 +740,10 @@ stmt_call: CALL call_cmd ';' call_cmd: { - plpgsqllex.(*lexer).ReadSqlExpressionStr(';') + _, _, err := plpgsqllex.(*lexer).ReadSqlExpr(';') + if err != nil { + return setErr(plpgsqllex, err) + } } ; @@ -889,12 +903,15 @@ stmt_case: CASE opt_expr_until_when case_when_list opt_case_else END_CASE CASE ' opt_expr_until_when: { - expr := "" - tok := plpgsqllex.(*lexer).Peek() - if tok.id != WHEN { - expr = plpgsqllex.(*lexer).ReadSqlExpressionStr(WHEN) + if plpgsqllex.(*lexer).Peek().id != WHEN { + sqlStr, _, err := plpgsqllex.(*lexer).ReadSqlExpr(WHEN) + if err != nil { + return setErr(plpgsqllex, err) + } + $$ = sqlStr + } else { + $$ = "" } - $$ = expr } ; @@ -1040,11 +1057,11 @@ stmt_return: RETURN return_variable ';' Expr: $2.expr(), } } -| RETURN_NEXT NEXT return_variable ';' +| RETURN_NEXT NEXT { return unimplemented(plpgsqllex, "return next") } -| RETURN_QUERY QUERY query_options ';' +| RETURN_QUERY QUERY { return unimplemented (plpgsqllex, "return query") } @@ -1053,11 +1070,17 @@ stmt_return: RETURN return_variable ';' query_options: { - _, terminator := plpgsqllex.(*lexer).ReadSqlExpressionStr2(EXECUTE, ';') + _, terminator, err := plpgsqllex.(*lexer).ReadSqlExpr(EXECUTE, ';') + if err != nil { + return setErr(plpgsqllex, err) + } if terminator == EXECUTE { return unimplemented (plpgsqllex, "return dynamic sql query") } - plpgsqllex.(*lexer).ReadSqlExpressionStr(';') + _, _, err = plpgsqllex.(*lexer).ReadSqlExpr(';') + if err != nil { + return setErr(plpgsqllex, err) + } } ; @@ -1152,7 +1175,10 @@ option_expr: option_type assign_operator { // Read until reaching one of the tokens that can follow a raise option. - sqlStr, _ := plpgsqllex.(*lexer).ReadSqlConstruct(',', ';') + sqlStr, _, err := plpgsqllex.(*lexer).ReadSqlExpr(',', ';') + if err != nil { + return setErr(plpgsqllex, err) + } optionExpr, err := plpgsqllex.(*lexer).ParseExpr(sqlStr) if err != nil { return setErr(plpgsqllex, err) @@ -1201,7 +1227,10 @@ format_exprs: format_expr: ',' { // Read until reaching a token that can follow a raise format parameter. - sqlStr, _ := plpgsqllex.(*lexer).ReadSqlConstruct(',', ';', USING) + sqlStr, _, err := plpgsqllex.(*lexer).ReadSqlExpr(',', ';', USING) + if err != nil { + return setErr(plpgsqllex, err) + } param, err := plpgsqllex.(*lexer).ParseExpr(sqlStr) if err != nil { return setErr(plpgsqllex, err) @@ -1218,9 +1247,15 @@ stmt_assert: ASSERT assert_cond ';' assert_cond: { - _, terminator := plpgsqllex.(*lexer).ReadSqlExpressionStr2(',', ';') + _, terminator, err := plpgsqllex.(*lexer).ReadSqlExpr(',', ';') + if err != nil { + return setErr(plpgsqllex, err) + } if terminator == ',' { - plpgsqllex.(*lexer).ReadSqlExpressionStr(';') + _, _, err = plpgsqllex.(*lexer).ReadSqlExpr(';') + if err != nil { + return setErr(plpgsqllex, err) + } } } ; @@ -1250,7 +1285,11 @@ stmt_execsql_start: stmt_dynexecute: EXECUTE { - $$.val = plpgsqllex.(*lexer).MakeDynamicExecuteStmt() + stmt, err := plpgsqllex.(*lexer).MakeDynamicExecuteStmt() + if err != nil { + return setErr(plpgsqllex, err) + } + $$.val = stmt } ; @@ -1262,7 +1301,7 @@ stmt_open: OPEN IDENT ';' { return unimplemented(plpgsqllex, "cursor for execute") } -| OPEN IDENT opt_scrollable FOR expr_until_semi ';' +| OPEN IDENT opt_scrollable FOR stmt_until_semi ';' { stmts, err := parser.Parse($5) if err != nil { @@ -1386,25 +1425,51 @@ proc_condition: any_identifier expr_until_semi: { - $$ = plpgsqllex.(*lexer).ReadSqlExpressionStr(';') + sqlStr, _, err := plpgsqllex.(*lexer).ReadSqlExpr(';') + if err != nil { + return setErr(plpgsqllex, err) + } + $$ = sqlStr + } +; + +stmt_until_semi: + { + sqlStr, _, err := plpgsqllex.(*lexer).ReadSqlStatement(';') + if err != nil { + return setErr(plpgsqllex, err) + } + $$ = sqlStr } ; expr_until_then: { - $$ = plpgsqllex.(*lexer).ReadSqlExpressionStr(THEN) + sqlStr, _, err := plpgsqllex.(*lexer).ReadSqlExpr(THEN) + if err != nil { + return setErr(plpgsqllex, err) + } + $$ = sqlStr } ; expr_until_loop: { - $$ = plpgsqllex.(*lexer).ReadSqlExpressionStr(LOOP) + sqlStr, _, err := plpgsqllex.(*lexer).ReadSqlExpr(LOOP) + if err != nil { + return setErr(plpgsqllex, err) + } + $$ = sqlStr } ; expr_until_paren : { - $$ = plpgsqllex.(*lexer).ReadSqlExpressionStr(')') + sqlStr, _, err := plpgsqllex.(*lexer).ReadSqlExpr(')') + if err != nil { + return setErr(plpgsqllex, err) + } + $$ = sqlStr } ; diff --git a/pkg/sql/plpgsql/parser/testdata/stmt_assign b/pkg/sql/plpgsql/parser/testdata/stmt_assign index 1beaab827ec5..29d37e8bbb7d 100644 --- a/pkg/sql/plpgsql/parser/testdata/stmt_assign +++ b/pkg/sql/plpgsql/parser/testdata/stmt_assign @@ -22,6 +22,38 @@ BEGIN a := NULL; END +parse +DECLARE +BEGIN + a :=; +END +---- +at or near ":": syntax error: missing expression + +parse +DECLARE +BEGIN +johnny := (NULL; +END +---- +at or near "EOF": syntax error: mismatched parentheses + +parse +DECLARE +BEGIN +johnny := NULL); +END +---- +at or near "null": syntax error: mismatched parentheses + +parse +DECLARE +BEGIN +johnny := (1 + (2); +END +---- +at or near "EOF": syntax error: mismatched parentheses + parse DECLARE BEGIN diff --git a/pkg/sql/plpgsql/parser/testdata/stmt_exec_sql b/pkg/sql/plpgsql/parser/testdata/stmt_exec_sql index 1c8e2dc97a38..5d291fee54e8 100644 --- a/pkg/sql/plpgsql/parser/testdata/stmt_exec_sql +++ b/pkg/sql/plpgsql/parser/testdata/stmt_exec_sql @@ -131,3 +131,27 @@ SELECT max(x) FROM xy INTO i; INSERT INTO xy VALUES (10, 10) RETURNING x INTO i; RETURN i; END + +parse +DECLARE +BEGIN + INSERT INTO t1 VALUES 1,2); +END +---- +at or near "2": syntax error: mismatched parentheses + +parse +DECLARE +BEGIN + INSERT INTO t1 VALUES (1,2; +END +---- +at or near "EOF": syntax error: mismatched parentheses + +parse +DECLARE +BEGIN + INSERT INTO t1 (VALUES (1,2); +END +---- +at or near "EOF": syntax error: mismatched parentheses diff --git a/pkg/sql/plpgsql/parser/testdata/stmt_fetch_move b/pkg/sql/plpgsql/parser/testdata/stmt_fetch_move index bceef82ba3b2..9b65d5cea7d8 100644 --- a/pkg/sql/plpgsql/parser/testdata/stmt_fetch_move +++ b/pkg/sql/plpgsql/parser/testdata/stmt_fetch_move @@ -70,7 +70,7 @@ BEGIN FETCH emp_cur INTO; END ---- -at or near "into": syntax error: expected INTO target +at or near "into": syntax error: missing expression parse DECLARE diff --git a/pkg/sql/plpgsql/parser/testdata/stmt_open b/pkg/sql/plpgsql/parser/testdata/stmt_open index d91b472f6352..d56a7fcffeea 100644 --- a/pkg/sql/plpgsql/parser/testdata/stmt_open +++ b/pkg/sql/plpgsql/parser/testdata/stmt_open @@ -49,3 +49,11 @@ OPEN curs2 SCROLL FOR EXECUTE SELECT $1, $2 FROM foo WHERE key = mykey USING hel END ---- at or near "execute": syntax error: unimplemented: this syntax + +parse +DECLARE +BEGIN +OPEN curs1 FOR; +END +---- +at or near "for": syntax error: missing SQL statement diff --git a/pkg/sql/plpgsql/parser/testdata/stmt_return b/pkg/sql/plpgsql/parser/testdata/stmt_return index 7fa05fb71140..201fcfae4f7b 100644 --- a/pkg/sql/plpgsql/parser/testdata/stmt_return +++ b/pkg/sql/plpgsql/parser/testdata/stmt_return @@ -22,7 +22,6 @@ x := 1 + 2; RETURN x; END - parse DECLARE BEGIN @@ -34,21 +33,74 @@ BEGIN RETURN (1, 'string'); END +parse +DECLARE +BEGIN + RETURN QUERY SELECT 1 + 1; +END +---- +at or near "query": syntax error: unimplemented: this syntax +parse +DECLARE +BEGIN + RETURN QUERY EXECUTE a dynamic command; +END +---- +at or near "query": syntax error: unimplemented: this syntax parse DECLARE BEGIN - RETURN QUERY SELECT 1 + 1; + RETURN NEXT 1 + 1; END ---- -at or near ";": syntax error: unimplemented: this syntax +at or near "next": syntax error: unimplemented: this syntax +parse +DECLARE +BEGIN + RETURN; +END +---- +at or near "return": syntax error: missing expression parse DECLARE BEGIN - RETURN QUERY EXECUTE a dynamic command; + RETURN (NULL; +END +---- +at or near "EOF": syntax error: mismatched parentheses + +parse +DECLARE +BEGIN + RETURN NULL); +END +---- +at or near "null": syntax error: mismatched parentheses + +parse +DECLARE +BEGIN + RETURN (1, ('string'); +END +---- +at or near "EOF": syntax error: mismatched parentheses + +parse +DECLARE +BEGIN + RETURN 1, 'string'; +END +---- +at or near "string": syntax error: query returned 2 columns + +parse +DECLARE +BEGIN + RETURN 1, (2, 3, 4, 5); END ---- -at or near ";": syntax error: unimplemented: this syntax +at or near ")": syntax error: query returned 2 columns