diff --git a/src/pl/plisql/src/Makefile b/src/pl/plisql/src/Makefile index 5a9abf2142f..2cf66fd5de7 100755 --- a/src/pl/plisql/src/Makefile +++ b/src/pl/plisql/src/Makefile @@ -64,7 +64,7 @@ REGRESS = plisql_array plisql_call plisql_control plisql_copy plisql_domain \ plisql_trap plisql_trigger plisql_varprops plisql_nested_subproc \ plisql_nested_subproc2 plisql_out_parameter plisql_type_rowtype \ plisql_exception plisql_for_loop_implicit plisql_autonomous plisql_bugs plisql_rownum \ - plisql_package_type_record + plisql_package_type_record plisql_execute_immediate # where to find ora_gen_keywordlist.pl and subsidiary files TOOLSDIR = $(top_srcdir)/src/tools diff --git a/src/pl/plisql/src/expected/plisql_execute_immediate.out b/src/pl/plisql/src/expected/plisql_execute_immediate.out new file mode 100644 index 00000000000..560a399d971 --- /dev/null +++ b/src/pl/plisql/src/expected/plisql_execute_immediate.out @@ -0,0 +1,92 @@ +-- +-- Tests for the Oracle-compatible EXECUTE IMMEDIATE statement +-- +CREATE TABLE ei_test (id int, label text); +-- DDL through EXECUTE IMMEDIATE +do $$ begin + execute immediate 'CREATE TABLE ei_ddl (id int)'; +end $$; +select count(*) as ei_ddl_rows from ei_ddl; + ei_ddl_rows +------------- + 0 +(1 row) + +-- DML with Oracle-style :n bind parameters +do $$ begin + execute immediate 'INSERT INTO ei_test VALUES (:1, :2)' using 1, 'one'; + execute immediate 'INSERT INTO ei_test VALUES (:1, :2)' using 2, 'two'; +end $$; +select id, label from ei_test order by id; + id | label +----+------- + 1 | one + 2 | two +(2 rows) + +-- single-row query into a variable +do $$ declare + n int; +begin + execute immediate 'SELECT count(*) FROM ei_test' into n; + raise notice 'count = %', n; +end $$; +NOTICE: count = 2 +-- INTO before USING (clauses in either order) +do $$ declare + lbl text; +begin + execute immediate 'SELECT label FROM ei_test WHERE id = :1' + into lbl using 2; + raise notice 'label = %', lbl; +end $$; +NOTICE: label = two +-- record target +do $$ declare + r ei_test%ROWTYPE; +begin + execute immediate 'SELECT * FROM ei_test WHERE id = :1' into r using 1; + raise notice 'row = %, %', r.id, r.label; +end $$; +NOTICE: row = 1, one +-- dynamic anonymous PL/iSQL block with a bind argument +do $$ begin + execute immediate 'BEGIN INSERT INTO ei_test VALUES (:1, :2); END;' + using 3, 'three'; +end $$; +select count(*) as total from ei_test; + total +------- + 3 +(1 row) + +-- dynamic string built at runtime +do $$ declare + v_sql text; +begin + v_sql := 'UPDATE ei_test SET label = label || ''!'' WHERE id = ' || 2::text; + execute immediate v_sql; +end $$; +select label from ei_test where id = 2; + label +------- + two! +(1 row) + +-- NULL dynamic string +do $$ begin + execute immediate null; +end $$; +ERROR: query string argument of EXECUTE is null +CONTEXT: PL/iSQL function inline_code_block line 2 at EXECUTE +-- plain EXECUTE keeps working and shares the same clauses +do $$ declare + v int; +begin + execute 'DELETE FROM ei_test WHERE id = 3'; + execute 'SELECT count(*) FROM ei_test' into v; + raise notice 'after delete = %', v; +end $$; +NOTICE: after delete = 2 +DROP TABLE ei_test; +DROP TABLE ei_ddl; diff --git a/src/pl/plisql/src/pl_gram.y b/src/pl/plisql/src/pl_gram.y index b20fdb87d25..975c8854a68 100644 --- a/src/pl/plisql/src/pl_gram.y +++ b/src/pl/plisql/src/pl_gram.y @@ -363,6 +363,7 @@ static PLiSQL_expr *build_call_expr(int firsttoken, int location, YYSTYPE *yylv %token K_GET %token K_HINT %token K_IF +%token K_IMMEDIATE %token K_IMPORT %token K_IN %token K_INFO @@ -2935,6 +2936,21 @@ stmt_dynexecute : K_EXECUTE PLiSQL_stmt_dynexecute *new; PLiSQL_expr *expr; int endtoken; + int tok; + + /* + * Oracle spells the dynamic-SQL statement + * "EXECUTE IMMEDIATE"; accept and skip IMMEDIATE if + * present, pushing back anything else for the + * expression reader. A variable named "immediate" + * resolves to the keyword in this position (the + * scanner prefers keywords right after a + * statement-introducing keyword), consistent with + * other unreserved keywords. + */ + tok = yylex(&yylval, &yylloc, yyscanner); + if (tok != K_IMMEDIATE) + plisql_push_back_token(tok, &yylval, &yylloc, yyscanner); expr = read_sql_construct(K_INTO, K_USING, ';', "INTO or USING or ;", @@ -3474,6 +3490,7 @@ unreserved_keyword : | K_FORWARD | K_GET | K_HINT + | K_IMMEDIATE | K_IMPORT | K_INFO | K_INSERT diff --git a/src/pl/plisql/src/pl_unreserved_kwlist.h b/src/pl/plisql/src/pl_unreserved_kwlist.h index e1d41fd5c20..ef3fdb1ca62 100755 --- a/src/pl/plisql/src/pl_unreserved_kwlist.h +++ b/src/pl/plisql/src/pl_unreserved_kwlist.h @@ -75,6 +75,7 @@ PG_KEYWORD("first", K_FIRST) PG_KEYWORD("forward", K_FORWARD) PG_KEYWORD("get", K_GET) PG_KEYWORD("hint", K_HINT) +PG_KEYWORD("immediate", K_IMMEDIATE) PG_KEYWORD("import", K_IMPORT) PG_KEYWORD("info", K_INFO) PG_KEYWORD("insert", K_INSERT) diff --git a/src/pl/plisql/src/sql/plisql_execute_immediate.sql b/src/pl/plisql/src/sql/plisql_execute_immediate.sql new file mode 100644 index 00000000000..93a3cff5c14 --- /dev/null +++ b/src/pl/plisql/src/sql/plisql_execute_immediate.sql @@ -0,0 +1,80 @@ +-- +-- Tests for the Oracle-compatible EXECUTE IMMEDIATE statement +-- + +CREATE TABLE ei_test (id int, label text); + +-- DDL through EXECUTE IMMEDIATE +do $$ begin + execute immediate 'CREATE TABLE ei_ddl (id int)'; +end $$; + +select count(*) as ei_ddl_rows from ei_ddl; + +-- DML with Oracle-style :n bind parameters +do $$ begin + execute immediate 'INSERT INTO ei_test VALUES (:1, :2)' using 1, 'one'; + execute immediate 'INSERT INTO ei_test VALUES (:1, :2)' using 2, 'two'; +end $$; + +select id, label from ei_test order by id; + +-- single-row query into a variable +do $$ declare + n int; +begin + execute immediate 'SELECT count(*) FROM ei_test' into n; + raise notice 'count = %', n; +end $$; + +-- INTO before USING (clauses in either order) +do $$ declare + lbl text; +begin + execute immediate 'SELECT label FROM ei_test WHERE id = :1' + into lbl using 2; + raise notice 'label = %', lbl; +end $$; + +-- record target +do $$ declare + r ei_test%ROWTYPE; +begin + execute immediate 'SELECT * FROM ei_test WHERE id = :1' into r using 1; + raise notice 'row = %, %', r.id, r.label; +end $$; + +-- dynamic anonymous PL/iSQL block with a bind argument +do $$ begin + execute immediate 'BEGIN INSERT INTO ei_test VALUES (:1, :2); END;' + using 3, 'three'; +end $$; + +select count(*) as total from ei_test; + +-- dynamic string built at runtime +do $$ declare + v_sql text; +begin + v_sql := 'UPDATE ei_test SET label = label || ''!'' WHERE id = ' || 2::text; + execute immediate v_sql; +end $$; + +select label from ei_test where id = 2; + +-- NULL dynamic string +do $$ begin + execute immediate null; +end $$; + +-- plain EXECUTE keeps working and shares the same clauses +do $$ declare + v int; +begin + execute 'DELETE FROM ei_test WHERE id = 3'; + execute 'SELECT count(*) FROM ei_test' into v; + raise notice 'after delete = %', v; +end $$; + +DROP TABLE ei_test; +DROP TABLE ei_ddl;