From 1e5111df690bcb7cb50e7ba79162d6cbc0c8d8b8 Mon Sep 17 00:00:00 2001 From: "okbob@github.com" Date: Fri, 19 Jan 2024 09:44:58 +0100 Subject: [PATCH 10/21] PREPARE LET support In current code base it requires just small changes in parser. It is nice to have to have possibility to use prepared LET statements mainly due reduction of security risks (SQL injection), and implementation is almost cheap. Explicit preparing is not necessity for support of LET statements in PL, because PL uses SPI for query execution, but it is nice to have this possibility (completenees, ...) --- doc/src/sgml/ref/prepare.sgml | 4 +- src/backend/parser/gram.y | 3 +- src/backend/parser/parse_cte.c | 7 ++ src/bin/psql/tab-complete.in.c | 4 +- .../regress/expected/session_variables.out | 81 ++++++++++++++++++- src/test/regress/sql/session_variables.sql | 57 +++++++++++++ 6 files changed, 149 insertions(+), 7 deletions(-) diff --git a/doc/src/sgml/ref/prepare.sgml b/doc/src/sgml/ref/prepare.sgml index 8ee9439f611..45d72b1d52b 100644 --- a/doc/src/sgml/ref/prepare.sgml +++ b/doc/src/sgml/ref/prepare.sgml @@ -116,8 +116,8 @@ PREPARE name [ ( Any SELECT, INSERT, UPDATE, - DELETE, MERGE, or VALUES - statement. + DELETE, MERGE, VALUES, + or LET statement. diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 73e2ecb662c..ed656ba6364 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -12198,7 +12198,8 @@ PreparableStmt: | InsertStmt | UpdateStmt | DeleteStmt - | MergeStmt /* by default all are $$=$1 */ + | MergeStmt + | LetStmt /* by default all are $$=$1 */ ; /***************************************************************************** diff --git a/src/backend/parser/parse_cte.c b/src/backend/parser/parse_cte.c index 79673621c9b..c62761e4eae 100644 --- a/src/backend/parser/parse_cte.c +++ b/src/backend/parser/parse_cte.c @@ -126,6 +126,13 @@ transformWithClause(ParseState *pstate, WithClause *withClause) CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc); ListCell *rest; + /* LET is allowed by parser, but not supported. Reject for now */ + if (IsA(cte->ctequery, LetStmt)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("LET not supported in WITH query"), + parser_errposition(pstate, cte->location)); + for_each_cell(rest, withClause->ctes, lnext(withClause->ctes, lc)) { CommonTableExpr *cte2 = (CommonTableExpr *) lfirst(rest); diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c index 8137eb73223..054c3bba6b9 100644 --- a/src/bin/psql/tab-complete.in.c +++ b/src/bin/psql/tab-complete.in.c @@ -4689,9 +4689,9 @@ match_previous_words(int pattern_id, else if (TailMatches("VALUES") && !TailMatches("DEFAULT", "VALUES")) COMPLETE_WITH("("); -/* LET */ +/* LET, EXPLAIN LET, PREPARE LET */ /* If prev. word is LET suggest a list of variables */ - else if (Matches("LET")) + else if (TailMatches("LET")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables); /* Complete LET with "=" */ else if (TailMatches("LET", MatchAny)) diff --git a/src/test/regress/expected/session_variables.out b/src/test/regress/expected/session_variables.out index 0cae7454545..6049f689f24 100644 --- a/src/test/regress/expected/session_variables.out +++ b/src/test/regress/expected/session_variables.out @@ -940,9 +940,9 @@ DROP VARIABLE var1, var2; -- the LET statement should be disallowed in CTE CREATE VARIABLE var1 AS int; WITH x AS (LET var1 = 100) SELECT * FROM x; -ERROR: syntax error at or near "LET" +ERROR: LET not supported in WITH query LINE 1: WITH x AS (LET var1 = 100) SELECT * FROM x; - ^ + ^ -- should be ok LET var1 = generate_series(1, 1); -- should fail @@ -2066,5 +2066,82 @@ SELECT var1; 10 (1 row) +SET plan_cache_mode TO force_generic_plan; +PREPARE p1 AS LET var1 = (SELECT count(*) FROM var_tab_test_table); +LET var1 = NULL; +EXPLAIN (COSTS OFF) EXECUTE p1; + QUERY PLAN +---------------------------------------------- + SET SESSION VARIABLE + Result + InitPlan 1 + -> Aggregate + -> Seq Scan on var_tab_test_table +(5 rows) + +-- should be NULL +SELECT var1; + var1 +------ + +(1 row) + +EXPLAIN (COSTS OFF, TIMING OFF, ANALYZE, SUMMARY OFF, BUFFERS OFF) EXECUTE p1; + QUERY PLAN +----------------------------------------------------------------------- + SET SESSION VARIABLE + Result (actual rows=1 loops=1) + InitPlan 1 + -> Aggregate (actual rows=1 loops=1) + -> Seq Scan on var_tab_test_table (actual rows=10 loops=1) +(5 rows) + +-- should be 10 +SELECT var1; + var1 +------ + 10 +(1 row) + +SET plan_cache_mode TO DEFAULT; +DEALLOCATE p1; DROP VARIABLE var1; DROP TABLE var_tab_test_table; +CREATE VARIABLE var1 numeric; +SET plan_cache_mode TO force_generic_plan; +PREPARE p1(numeric) AS LET var1 = $1; +PREPARE p2 AS SELECT var1; +EXECUTE p1(pi() + 100); +EXECUTE p2; + var1 +----------------- + 103.14159265359 +(1 row) + +-- prepared plan cache invalidation test +DROP VARIABLE var1; +CREATE VARIABLE var1 numeric; +-- should be NULL +EXECUTE p2; + var1 +------ + +(1 row) + +DEALLOCATE p1; +DEALLOCATE p2; +DROP VARIABLE var1; +SET plan_cache_mode TO force_generic_plan; +CREATE VARIABLE var1 numeric[]; +PREPARE p1(int, numeric) AS LET var1[$1] = $2; +LET var1 = '{}'::numeric[]; +EXECUTE p1(1, 10.2); +EXECUTE p1(2, 10.3); +SELECT var1; + var1 +------------- + {10.2,10.3} +(1 row) + +DEALLOCATE p1; +DROP VARIABLE var1; diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql index 3d0eb8c7ad9..18b59fc9cad 100644 --- a/src/test/regress/sql/session_variables.sql +++ b/src/test/regress/sql/session_variables.sql @@ -1420,5 +1420,62 @@ EXPLAIN (COSTS OFF, TIMING OFF, ANALYZE, SUMMARY OFF, BUFFERS OFF) LET var1 = (S -- should be 10 SELECT var1; +SET plan_cache_mode TO force_generic_plan; + +PREPARE p1 AS LET var1 = (SELECT count(*) FROM var_tab_test_table); + +LET var1 = NULL; + +EXPLAIN (COSTS OFF) EXECUTE p1; + +-- should be NULL +SELECT var1; + +EXPLAIN (COSTS OFF, TIMING OFF, ANALYZE, SUMMARY OFF, BUFFERS OFF) EXECUTE p1; + +-- should be 10 +SELECT var1; + +SET plan_cache_mode TO DEFAULT; + +DEALLOCATE p1; + DROP VARIABLE var1; DROP TABLE var_tab_test_table; + +CREATE VARIABLE var1 numeric; + +SET plan_cache_mode TO force_generic_plan; + +PREPARE p1(numeric) AS LET var1 = $1; +PREPARE p2 AS SELECT var1; + +EXECUTE p1(pi() + 100); +EXECUTE p2; + +-- prepared plan cache invalidation test +DROP VARIABLE var1; +CREATE VARIABLE var1 numeric; + +-- should be NULL +EXECUTE p2; + +DEALLOCATE p1; +DEALLOCATE p2; + +DROP VARIABLE var1; + +SET plan_cache_mode TO force_generic_plan; + +CREATE VARIABLE var1 numeric[]; + +PREPARE p1(int, numeric) AS LET var1[$1] = $2; + +LET var1 = '{}'::numeric[]; +EXECUTE p1(1, 10.2); +EXECUTE p1(2, 10.3); + +SELECT var1; + +DEALLOCATE p1; +DROP VARIABLE var1; -- 2.48.1