v20240801-0017-plpgsql-implementation-for-LET-statement.patch
text/x-patch
Filename: v20240801-0017-plpgsql-implementation-for-LET-statement.patch
Type: text/x-patch
Part: 1
Message:
Re: proposal: schema variables
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v20240801-0017
Subject: plpgsql implementation for LET statement
| File | + | − |
|---|---|---|
| src/backend/executor/spi.c | 3 | 0 |
| src/backend/parser/analyze.c | 12 | 0 |
| src/backend/parser/gram.y | 8 | 0 |
| src/backend/parser/parser.c | 1 | 0 |
| src/include/nodes/parsenodes.h | 2 | 0 |
| src/include/parser/parser.h | 4 | 0 |
| src/pl/plpgsql/src/pl_exec.c | 55 | 0 |
| src/pl/plpgsql/src/pl_funcs.c | 24 | 0 |
| src/pl/plpgsql/src/pl_gram.y | 27 | 1 |
| src/pl/plpgsql/src/plpgsql.h | 12 | 0 |
| src/pl/plpgsql/src/pl_reserved_kwlist.h | 1 | 0 |
| src/tools/pgindent/typedefs.list | 1 | 0 |
From f51f3ff72e7044c10072bf14d990d091f1d26656 Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <okbob@github.com>
Date: Sat, 20 Jan 2024 08:56:17 +0100
Subject: [PATCH 17/21] plpgsql implementation for LET statement
PLpgSQL allows to call expression executor directly (for simple expression).
This possibility strongly reduces overhead related to execution. Reimplementation
of LET statement (inside PLpgSQL) allows to use this possibility, and strongly
increase performance:
CREATE VARIABLE svar int;
DO $$
BEGIN
FOR i IN 1..10000
LOOP
LET svar = i;
END LOOP;
END;
$$;
From 120ms to 8ms (with assertions) (this is best case, but without it, the LET statement
can be bottle neck).
An alternative can be reimplementation of LET statement inside expression
executor, and then SQL LET command can be executed in simple expression
execution, but this increase an complexity of executor, but still the
benefits is only inside plpgsql, so it is better to this optimization
inside plpgsql.
---
src/backend/executor/spi.c | 3 ++
src/backend/parser/analyze.c | 12 ++++++
src/backend/parser/gram.y | 8 ++++
src/backend/parser/parser.c | 1 +
src/include/nodes/parsenodes.h | 2 +
src/include/parser/parser.h | 4 ++
src/pl/plpgsql/src/pl_exec.c | 55 +++++++++++++++++++++++++
src/pl/plpgsql/src/pl_funcs.c | 24 +++++++++++
src/pl/plpgsql/src/pl_gram.y | 28 ++++++++++++-
src/pl/plpgsql/src/pl_reserved_kwlist.h | 1 +
src/pl/plpgsql/src/plpgsql.h | 12 ++++++
src/tools/pgindent/typedefs.list | 1 +
12 files changed, 150 insertions(+), 1 deletion(-)
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index e516c0a67c6..c3191fdc432 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -2983,6 +2983,9 @@ _SPI_error_callback(void *arg)
case RAW_PARSE_PLPGSQL_ASSIGN3:
errcontext("PL/pgSQL assignment \"%s\"", query);
break;
+ case RAW_PARSE_PLPGSQL_LET:
+ errcontext("LET statement \"%s\"", query);
+ break;
default:
errcontext("SQL statement \"%s\"", query);
break;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index f630ef07ffc..ad5550b6f67 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -1922,6 +1922,18 @@ transformLetStmt(ParseState *pstate, LetStmt *stmt)
stmt->query = (Node *) query;
+ /*
+ * Inside PL/pgSQL we don't want to execute LET statement as utility
+ * command, because it disallow to execute expression as simple
+ * expression. So for PL/pgSQL we have extra path, and we return SELECT.
+ * Then it can be executed by exec_eval_expr. Result is dirrectly assigned
+ * to target session variable inside PL/pgSQL LET statement handler. This
+ * is extra code, extra path, but possibility to get faster execution is
+ * too attractive.
+ */
+ if (stmt->plpgsql_mode)
+ return query;
+
/* represent the command as a utility Query */
result = makeNode(Query);
result->commandType = CMD_UTILITY;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index aae22b45673..ebc0babbd94 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -832,6 +832,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%token MODE_PLPGSQL_ASSIGN1
%token MODE_PLPGSQL_ASSIGN2
%token MODE_PLPGSQL_ASSIGN3
+%token MODE_PLPGSQL_LET
/* Precedence: lowest to highest */
@@ -963,6 +964,13 @@ parse_toplevel:
pg_yyget_extra(yyscanner)->parsetree =
list_make1(makeRawStmt((Node *) n, 0));
}
+ | MODE_PLPGSQL_LET LetStmt
+ {
+ LetStmt *n = (LetStmt *) $2;
+ n->plpgsql_mode = true;
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt((Node *) n, 0));
+ }
;
/*
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index 118488c3f30..f9430a32919 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -62,6 +62,7 @@ raw_parser(const char *str, RawParseMode mode)
[RAW_PARSE_PLPGSQL_ASSIGN1] = MODE_PLPGSQL_ASSIGN1,
[RAW_PARSE_PLPGSQL_ASSIGN2] = MODE_PLPGSQL_ASSIGN2,
[RAW_PARSE_PLPGSQL_ASSIGN3] = MODE_PLPGSQL_ASSIGN3,
+ [RAW_PARSE_PLPGSQL_LET] = MODE_PLPGSQL_LET,
};
yyextra.have_lookahead = true;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 76d7656d7f7..0cc560d538f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2109,6 +2109,8 @@ typedef struct LetStmt
NodeTag type;
List *target; /* target variable */
Node *query; /* source expression */
+ bool plpgsql_mode; /* true, when command will be executed
+ * (parsed) by plpgsql runtime */
int location;
} LetStmt;
diff --git a/src/include/parser/parser.h b/src/include/parser/parser.h
index be184ec5066..efbc76f0ad4 100644
--- a/src/include/parser/parser.h
+++ b/src/include/parser/parser.h
@@ -33,6 +33,9 @@
* RAW_PARSE_PLPGSQL_ASSIGNn: parse a PL/pgSQL assignment statement,
* and return a one-element List containing a RawStmt node. "n"
* gives the number of dotted names comprising the target ColumnRef.
+ *
+ * RAW_PARSE_PLPGSQL_LET: parse a LET statement, and return a
+ * one-element List containing a RawStmt node.
*/
typedef enum
{
@@ -42,6 +45,7 @@ typedef enum
RAW_PARSE_PLPGSQL_ASSIGN1,
RAW_PARSE_PLPGSQL_ASSIGN2,
RAW_PARSE_PLPGSQL_ASSIGN3,
+ RAW_PARSE_PLPGSQL_LET,
} RawParseMode;
/* Values for the backslash_quote GUC */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 239b3250a95..80d512d30a0 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -24,6 +24,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
+#include "commands/session_variable.h"
#include "executor/execExpr.h"
#include "executor/spi.h"
#include "executor/tstoreReceiver.h"
@@ -328,6 +329,8 @@ static int exec_stmt_commit(PLpgSQL_execstate *estate,
PLpgSQL_stmt_commit *stmt);
static int exec_stmt_rollback(PLpgSQL_execstate *estate,
PLpgSQL_stmt_rollback *stmt);
+static int exec_stmt_let(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_let *let);
static void plpgsql_estate_setup(PLpgSQL_execstate *estate,
PLpgSQL_function *func,
@@ -2120,6 +2123,10 @@ exec_stmts(PLpgSQL_execstate *estate, List *stmts)
rc = exec_stmt_rollback(estate, (PLpgSQL_stmt_rollback *) stmt);
break;
+ case PLPGSQL_STMT_LET:
+ rc = exec_stmt_let(estate, (PLpgSQL_stmt_let *) stmt);
+ break;
+
default:
/* point err_stmt to parent, since this one seems corrupt */
estate->err_stmt = save_estmt;
@@ -5001,6 +5008,54 @@ exec_stmt_rollback(PLpgSQL_execstate *estate, PLpgSQL_stmt_rollback *stmt)
return PLPGSQL_RC_OK;
}
+/* ----------
+ * exec_stmt_let Evaluate an expression and
+ * put the result into a session variable.
+ * ----------
+ */
+static int
+exec_stmt_let(PLpgSQL_execstate *estate, PLpgSQL_stmt_let *stmt)
+{
+ bool isNull;
+ Oid valtype;
+ int32 valtypmod;
+ Datum value;
+ Oid varid;
+
+ List *plansources;
+ CachedPlanSource *plansource;
+
+ value = exec_eval_expr(estate,
+ stmt->expr,
+ &isNull,
+ &valtype,
+ &valtypmod);
+
+ /*
+ * Oid of target session variable is stored in Query structure. It is
+ * safer to read this value directly from the plan than to hold this value
+ * in the plpgsql context, because it's not necessary to handle
+ * invalidation of the cached value. Next operations are read only without
+ * any allocations, so we can expect that retrieving varid from Query
+ * should be fast.
+ */
+ plansources = SPI_plan_get_plan_sources(stmt->expr->plan);
+ if (list_length(plansources) != 1)
+ elog(ERROR, "unexpected length of plansources of query for LET statement");
+
+ plansource = (CachedPlanSource *) linitial(plansources);
+ if (list_length(plansource->query_list) != 1)
+ elog(ERROR, "unexpected length of plansource of query for LET statement");
+
+ varid = linitial_node(Query, plansource->query_list)->resultVariable;
+ if (!OidIsValid(varid))
+ elog(ERROR, "oid of target session variable is not valid");
+
+ SetSessionVariableWithSecurityCheck(varid, value, isNull);
+
+ return PLPGSQL_RC_OK;
+}
+
/* ----------
* exec_assign_expr Put an expression's result into a variable.
* ----------
diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c
index eeb7c4d7c04..1b39351db7f 100644
--- a/src/pl/plpgsql/src/pl_funcs.c
+++ b/src/pl/plpgsql/src/pl_funcs.c
@@ -288,6 +288,8 @@ plpgsql_stmt_typename(PLpgSQL_stmt *stmt)
return "COMMIT";
case PLPGSQL_STMT_ROLLBACK:
return "ROLLBACK";
+ case PLPGSQL_STMT_LET:
+ return "LET";
}
return "unknown";
@@ -370,6 +372,7 @@ static void free_perform(PLpgSQL_stmt_perform *stmt);
static void free_call(PLpgSQL_stmt_call *stmt);
static void free_commit(PLpgSQL_stmt_commit *stmt);
static void free_rollback(PLpgSQL_stmt_rollback *stmt);
+static void free_let(PLpgSQL_stmt_let *stmt);
static void free_expr(PLpgSQL_expr *expr);
@@ -459,6 +462,9 @@ free_stmt(PLpgSQL_stmt *stmt)
case PLPGSQL_STMT_ROLLBACK:
free_rollback((PLpgSQL_stmt_rollback *) stmt);
break;
+ case PLPGSQL_STMT_LET:
+ free_let((PLpgSQL_stmt_let *) stmt);
+ break;
default:
elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
break;
@@ -713,6 +719,12 @@ free_getdiag(PLpgSQL_stmt_getdiag *stmt)
{
}
+static void
+free_let(PLpgSQL_stmt_let *stmt)
+{
+ free_expr(stmt->expr);
+}
+
static void
free_expr(PLpgSQL_expr *expr)
{
@@ -815,6 +827,7 @@ static void dump_perform(PLpgSQL_stmt_perform *stmt);
static void dump_call(PLpgSQL_stmt_call *stmt);
static void dump_commit(PLpgSQL_stmt_commit *stmt);
static void dump_rollback(PLpgSQL_stmt_rollback *stmt);
+static void dump_let(PLpgSQL_stmt_let *stmt);
static void dump_expr(PLpgSQL_expr *expr);
@@ -914,6 +927,9 @@ dump_stmt(PLpgSQL_stmt *stmt)
case PLPGSQL_STMT_ROLLBACK:
dump_rollback((PLpgSQL_stmt_rollback *) stmt);
break;
+ case PLPGSQL_STMT_LET:
+ dump_let((PLpgSQL_stmt_let *) stmt);
+ break;
default:
elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
break;
@@ -1590,6 +1606,14 @@ dump_getdiag(PLpgSQL_stmt_getdiag *stmt)
printf("\n");
}
+static void
+dump_let(PLpgSQL_stmt_let *stmt)
+{
+ dump_ind();
+ dump_expr(stmt->expr);
+ printf("\n");
+}
+
static void
dump_expr(PLpgSQL_expr *expr)
{
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 0671ff78722..4f98261ac69 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -199,7 +199,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
%type <stmt> stmt_return stmt_raise stmt_assert stmt_execsql
%type <stmt> stmt_dynexecute stmt_for stmt_perform stmt_call stmt_getdiag
%type <stmt> stmt_open stmt_fetch stmt_move stmt_close stmt_null
-%type <stmt> stmt_commit stmt_rollback
+%type <stmt> stmt_commit stmt_rollback stmt_let
%type <stmt> stmt_case stmt_foreach_a
%type <list> proc_exceptions
@@ -306,6 +306,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
%token <keyword> K_INTO
%token <keyword> K_IS
%token <keyword> K_LAST
+%token <keyword> K_LET
%token <keyword> K_LOG
%token <keyword> K_LOOP
%token <keyword> K_MERGE
@@ -875,6 +876,8 @@ proc_stmt : pl_block ';'
{ $$ = $1; }
| stmt_rollback
{ $$ = $1; }
+ | stmt_let
+ { $$ = $1; }
;
stmt_perform : K_PERFORM
@@ -991,6 +994,29 @@ stmt_assign : T_DATUM
}
;
+stmt_let : K_LET
+ {
+ PLpgSQL_stmt_let *new;
+ RawParseMode pmode;
+
+ pmode = RAW_PARSE_PLPGSQL_LET;
+
+ new = palloc0(sizeof(PLpgSQL_stmt_let));
+ new->cmd_type = PLPGSQL_STMT_LET;
+ new->lineno = plpgsql_location_to_lineno(@1);
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+
+ /* Push back the head name to include it in the stmt */
+ plpgsql_push_back_token(K_LET);
+ new->expr = read_sql_construct(';', 0, 0, ";",
+ pmode,
+ false, true,
+ NULL, NULL);
+
+ $$ = (PLpgSQL_stmt *)new;
+ }
+ ;
+
stmt_getdiag : K_GET getdiag_area_opt K_DIAGNOSTICS getdiag_list ';'
{
PLpgSQL_stmt_getdiag *new;
diff --git a/src/pl/plpgsql/src/pl_reserved_kwlist.h b/src/pl/plpgsql/src/pl_reserved_kwlist.h
index d338e9f6374..be94aa751a4 100644
--- a/src/pl/plpgsql/src/pl_reserved_kwlist.h
+++ b/src/pl/plpgsql/src/pl_reserved_kwlist.h
@@ -40,6 +40,7 @@ PG_KEYWORD("from", K_FROM)
PG_KEYWORD("if", K_IF)
PG_KEYWORD("in", K_IN)
PG_KEYWORD("into", K_INTO)
+PG_KEYWORD("let", K_LET)
PG_KEYWORD("loop", K_LOOP)
PG_KEYWORD("not", K_NOT)
PG_KEYWORD("null", K_NULL)
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 50c3b28472b..bb400629adb 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -128,6 +128,7 @@ typedef enum PLpgSQL_stmt_type
PLPGSQL_STMT_CALL,
PLPGSQL_STMT_COMMIT,
PLPGSQL_STMT_ROLLBACK,
+ PLPGSQL_STMT_LET,
} PLpgSQL_stmt_type;
/*
@@ -520,6 +521,17 @@ typedef struct PLpgSQL_stmt_assign
PLpgSQL_expr *expr;
} PLpgSQL_stmt_assign;
+/*
+ * Let statement
+ */
+typedef struct PLpgSQL_stmt_let
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ PLpgSQL_expr *expr;
+} PLpgSQL_stmt_let;
+
/*
* PERFORM statement
*/
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 0ab9ef134d0..9424bef2baa 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1886,6 +1886,7 @@ PLpgSQL_stmt_forq
PLpgSQL_stmt_fors
PLpgSQL_stmt_getdiag
PLpgSQL_stmt_if
+PLpgSQL_stmt_let
PLpgSQL_stmt_loop
PLpgSQL_stmt_open
PLpgSQL_stmt_perform
--
2.45.2