From 7602c0585958367880ac6558f343735a528a5a7b Mon Sep 17 00:00:00 2001 From: "okbob@github.com" Date: Fri, 19 Jan 2024 09:44:58 +0100 Subject: [PATCH 07/19] 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.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 8ee9439f61..45d72b1d52 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 2845a82095..ebe0a4a158 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -12094,7 +12094,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 6826d4f36a..b01b930cf2 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.c b/src/bin/psql/tab-complete.c index ed304f4397..ba92024f29 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -4255,9 +4255,9 @@ psql_completion(const char *text, int start, int end) 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 e096bca569..f2781dcce5 100644 --- a/src/test/regress/expected/session_variables.out +++ b/src/test/regress/expected/session_variables.out @@ -403,9 +403,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 @@ -1261,5 +1261,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) 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 423c4ef367..d102770cf0 100644 --- a/src/test/regress/sql/session_variables.sql +++ b/src/test/regress/sql/session_variables.sql @@ -839,5 +839,62 @@ EXPLAIN (COSTS OFF, TIMING OFF, ANALYZE, SUMMARY OFF) LET var1 = (SELECT count(* -- 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) 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.44.0