From c2e9e5b8aa61c826ff1bee7956ed047c26d70fa7 Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Tue, 28 Mar 2017 20:26:22 +0200
Subject: [PATCH] Add plan_cache control PRAGMA to pl/pgsql

---
 doc/src/sgml/plpgsql.sgml             | 64 +++++++++++++++++++++++++++++++++++
 src/pl/plpgsql/src/pl_comp.c          |  6 ++++
 src/pl/plpgsql/src/pl_exec.c          | 18 ++++++----
 src/pl/plpgsql/src/pl_funcs.c         | 64 +++++++++++++++++++++++++++++++++++
 src/pl/plpgsql/src/pl_gram.y          | 59 ++++++++++++++++++++++++++++++--
 src/pl/plpgsql/src/pl_scanner.c       |  4 +++
 src/pl/plpgsql/src/plpgsql.h          | 17 +++++++++-
 src/test/regress/expected/plpgsql.out | 35 +++++++++++++++++++
 src/test/regress/sql/plpgsql.sql      | 30 ++++++++++++++++
 9 files changed, 286 insertions(+), 11 deletions(-)

diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index d356deb..ba64097 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -802,6 +802,32 @@ $$ LANGUAGE plpgsql;
     happen in a plain SQL command.
    </para>
   </sect2>
+
+  <sect2 id="plpgsql-declaration-pragma">
+   <title>Block level PRAGMA</title>
+
+   <indexterm>
+    <primary>PRAGMA</>
+    <secondary>in PL/pgSQL</>
+   </indexterm>
+
+   <para>
+    The block level <literal>PRAGMA</literal> allows to change the
+    <application>PL/pgSQL</application> compiler behavior. Currently
+    only <literal>PRAGMA PLAN_CACHE</literal> is supported.
+
+<programlisting>
+CREATE FUNCTION enforce_fresh_plan(_id text) RETURNS boolean AS $$
+DECLARE
+  PRAGMA PLAN_CACHE(force_custom_plan);
+BEGIN
+  -- in this block every embedded query uses one shot plan
+  RETURN EXISTS(SELECT * FROM tab WHERE id = _id);
+END;
+$$ LANGUAGE plpgsql;
+</programlisting>
+   </para>
+  </sect2>
   </sect1>
 
   <sect1 id="plpgsql-expressions">
@@ -4649,6 +4675,44 @@ $$ LANGUAGE plpgsql;
      use of the <literal>now()</> function would still be a better idea.
     </para>
 
+
+    <sect3 id="PRAGMA-PLAN_CACHE">
+     <title>PRAGMA PLAN_CACHE</title>
+
+     <para>
+      The plan cache behavior can be controlled using
+      <literal>PRAGMA PLAN_CACHE</>. This <literal>PRAGMA</> can be used both
+      for whole function or in individual blocks. The following options are
+      possible: <literal>DEFAULT</literal> - default
+      <application>PL/pgSQL</application> implementation - the system tries
+      to decide between custom plan and generic plan after five query
+      executions, <literal>FORCE_CUSTOM_PLAN</literal> - the chosen execution
+      plan will be the one shot plan - it is specific for every set of
+      used paramaters, <literal>FORCE_GENERIC_PLAN</literal> - the generic
+      plan will be used from the start.
+     </para>
+
+     <para>
+      <indexterm>
+       <primary>PRAGMA PLAN_CACHE</>
+       <secondary>in PL/pgSQL</>
+      </indexterm>
+      The plan for <command>INSERT</command> is always a generic plan. The
+      <literal>PRAGMA PLAN_CACHE</literal> shown here affects whole function
+      so every command in this function will use a generic plan.
+<programlisting>
+CREATE FUNCTION logfunc2(logtxt text) RETURNS void AS $$
+    PRAGMA PLAN_CACHE(FORCE_GENERIC_PLAN);
+    DECLARE
+        curtime timestamp;
+    BEGIN
+        curtime := 'now';
+        INSERT INTO logtable VALUES (logtxt, curtime);
+    END;
+$$ LANGUAGE plpgsql;
+</programlisting>
+     </para>
+    </sect3>
   </sect2>
 
   </sect1>
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index bed343e..70c970d 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -85,6 +85,10 @@ static const ExceptionLabelMap exception_label_map[] = {
 	{NULL, 0}
 };
 
+PLpgSQL_settings default_settings = {
+	NULL,
+	0							/* no special cursor option */
+};
 
 /* ----------
  * static prototypes
@@ -371,6 +375,7 @@ do_compile(FunctionCallInfo fcinfo,
 	 * outermost namespace contains function parameters and other special
 	 * variables (such as FOUND), and is named after the function itself.
 	 */
+	plpgsql_settings_init(&default_settings);
 	plpgsql_ns_init();
 	plpgsql_ns_push(NameStr(procStruct->proname), PLPGSQL_LABEL_BLOCK);
 	plpgsql_DumpExecTree = false;
@@ -849,6 +854,7 @@ plpgsql_compile_inline(char *proc_source)
 	function->extra_warnings = 0;
 	function->extra_errors = 0;
 
+	plpgsql_settings_init(&default_settings);
 	plpgsql_ns_init();
 	plpgsql_ns_push(func_name, PLPGSQL_LABEL_BLOCK);
 	plpgsql_DumpExecTree = false;
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index c27935b..762ee6f 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -2336,7 +2336,7 @@ exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
 	Assert(query);
 
 	if (query->plan == NULL)
-		exec_prepare_plan(estate, query, curvar->cursor_options);
+		exec_prepare_plan(estate, query, query->cursor_options);
 
 	/*
 	 * Set up short-lived ParamListInfo
@@ -3626,7 +3626,8 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
 	{
 		ListCell   *l;
 
-		exec_prepare_plan(estate, expr, CURSOR_OPT_PARALLEL_OK);
+		exec_prepare_plan(estate, expr,
+						  expr->cursor_options | CURSOR_OPT_PARALLEL_OK);
 		stmt->mod_stmt = false;
 		foreach(l, SPI_plan_get_plan_sources(expr->plan))
 		{
@@ -4096,7 +4097,8 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
 		 */
 		query = stmt->query;
 		if (query->plan == NULL)
-			exec_prepare_plan(estate, query, stmt->cursor_options);
+			exec_prepare_plan(estate, query,
+							  query->cursor_options | stmt->cursor_options);
 	}
 	else if (stmt->dynquery != NULL)
 	{
@@ -4167,7 +4169,7 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
 
 		query = curvar->cursor_explicit_expr;
 		if (query->plan == NULL)
-			exec_prepare_plan(estate, query, curvar->cursor_options);
+			exec_prepare_plan(estate, query, query->cursor_options);
 	}
 
 	/*
@@ -4366,7 +4368,7 @@ exec_assign_expr(PLpgSQL_execstate *estate, PLpgSQL_datum *target,
 	 */
 	if (expr->plan == NULL)
 	{
-		exec_prepare_plan(estate, expr, 0);
+		exec_prepare_plan(estate, expr, expr->cursor_options);
 		if (target->dtype == PLPGSQL_DTYPE_VAR)
 			exec_check_rw_parameter(expr, target->dno);
 	}
@@ -5174,7 +5176,8 @@ exec_eval_expr(PLpgSQL_execstate *estate,
 	 * If first time through, create a plan for this expression.
 	 */
 	if (expr->plan == NULL)
-		exec_prepare_plan(estate, expr, CURSOR_OPT_PARALLEL_OK);
+		exec_prepare_plan(estate, expr,
+						  expr->cursor_options | CURSOR_OPT_PARALLEL_OK);
 
 	/*
 	 * If this is a simple expression, bypass SPI and use the executor
@@ -5259,7 +5262,8 @@ exec_run_select(PLpgSQL_execstate *estate,
 	 */
 	if (expr->plan == NULL)
 		exec_prepare_plan(estate, expr,
-						  portalP == NULL ? CURSOR_OPT_PARALLEL_OK : 0);
+						  (portalP == NULL ? CURSOR_OPT_PARALLEL_OK : 0) |
+						  expr->cursor_options);
 
 	/*
 	 * If a portal was requested, put the query into the portal
diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c
index 93f8981..9ff78cc 100644
--- a/src/pl/plpgsql/src/pl_funcs.c
+++ b/src/pl/plpgsql/src/pl_funcs.c
@@ -35,6 +35,7 @@
  * ----------
  */
 static PLpgSQL_nsitem *ns_top = NULL;
+static PLpgSQL_settings *current_settings = NULL;
 
 
 /* ----------
@@ -71,6 +72,15 @@ plpgsql_ns_pop(void)
 	Assert(ns_top != NULL);
 	while (ns_top->itemtype != PLPGSQL_NSTYPE_LABEL)
 		ns_top = ns_top->prev;
+
+	/* Release local settings copy if was created */
+	if (ns_top->local_settings != NULL)
+	{
+		current_settings = ns_top->local_settings->prev;
+		pfree(ns_top->local_settings);
+		ns_top->local_settings = NULL;
+	}
+
 	ns_top = ns_top->prev;
 }
 
@@ -103,6 +113,7 @@ plpgsql_ns_additem(PLpgSQL_nsitem_type itemtype, int itemno, const char *name)
 	nse->itemtype = itemtype;
 	nse->itemno = itemno;
 	nse->prev = ns_top;
+	nse->local_settings = NULL;
 	strcpy(nse->name, name);
 	ns_top = nse;
 }
@@ -227,6 +238,55 @@ plpgsql_ns_find_nearest_loop(PLpgSQL_nsitem *ns_cur)
 }
 
 
+/* ----------
+ * Returns pointer to current compiler settings
+ * ----------
+ */
+PLpgSQL_settings *
+plpgsql_current_settings(void)
+{
+	return current_settings;
+}
+
+
+/* ----------
+ * Setup default compiler settings
+ * ----------
+ */
+void
+plpgsql_settings_init(PLpgSQL_settings *settings)
+{
+	current_settings = settings;
+}
+
+
+/* ----------
+ * Set compiler settings
+ * ----------
+ */
+void
+plpgsql_settings_set(PLpgSQL_settings *settings)
+{
+	PLpgSQL_nsitem *ns_cur = ns_top;
+
+	/*
+	 * Modify settings directly, when ns has local settings data.
+	 * When ns uses shared settings, create settings first.
+	 */
+	while (ns_cur->itemtype != PLPGSQL_NSTYPE_LABEL)
+		ns_cur = ns_cur->prev;
+
+	if (ns_cur->local_settings == NULL)
+	{
+		ns_cur->local_settings = palloc(sizeof(PLpgSQL_settings));
+		ns_cur->local_settings->prev = current_settings;
+		current_settings = ns_cur->local_settings;
+	}
+
+	current_settings->cursor_options = settings->cursor_options;
+}
+
+
 /*
  * Statement type as a string, for use in error messages etc.
  */
@@ -1537,6 +1597,10 @@ static void
 dump_expr(PLpgSQL_expr *expr)
 {
 	printf("'%s'", expr->query);
+	if (expr->cursor_options & CURSOR_OPT_GENERIC_PLAN)
+		printf("/* GENERIC_PLAN! */");
+	if (expr->cursor_options & CURSOR_OPT_CUSTOM_PLAN)
+		printf("/* CUSTOM_PLAN! */");
 }
 
 void
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 29729df..fc543a2 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -218,6 +218,8 @@ static	void			check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %type <ival>	opt_scrollable
 %type <fetch>	opt_fetch_direction
 
+%type <ival>	plan_cache_option
+
 %type <keyword>	unreserved_keyword
 
 
@@ -285,6 +287,8 @@ static	void			check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %token <keyword>	K_FETCH
 %token <keyword>	K_FIRST
 %token <keyword>	K_FOR
+%token <keyword>	K_FORCE_CUSTOM_PLAN
+%token <keyword>	K_FORCE_GENERIC_PLAN
 %token <keyword>	K_FOREACH
 %token <keyword>	K_FORWARD
 %token <keyword>	K_FROM
@@ -317,6 +321,8 @@ static	void			check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %token <keyword>	K_PG_EXCEPTION_CONTEXT
 %token <keyword>	K_PG_EXCEPTION_DETAIL
 %token <keyword>	K_PG_EXCEPTION_HINT
+%token <keyword>	K_PLAN_CACHE
+%token <keyword>	K_PRAGMA
 %token <keyword>	K_PRINT_STRICT_PARAMS
 %token <keyword>	K_PRIOR
 %token <keyword>	K_QUERY
@@ -350,9 +356,9 @@ static	void			check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 
 %%
 
-pl_function		: comp_options pl_block opt_semi
+pl_function		: comp_options pragmas_opt pl_block opt_semi
 					{
-						plpgsql_parse_result = (PLpgSQL_stmt_block *) $2;
+						plpgsql_parse_result = (PLpgSQL_stmt_block *) $3;
 					}
 				;
 
@@ -400,6 +406,42 @@ opt_semi		:
 				| ';'
 				;
 
+plan_cache_option : K_DEFAULT
+					{
+						$$ = 0;
+					}
+				| K_FORCE_GENERIC_PLAN
+					{
+						$$ = CURSOR_OPT_GENERIC_PLAN;
+					}
+				| K_FORCE_CUSTOM_PLAN
+					{
+						$$ = CURSOR_OPT_CUSTOM_PLAN;
+					}
+				;
+
+pragma			: K_PRAGMA K_PLAN_CACHE '(' plan_cache_option ')' ';'
+					{
+						PLpgSQL_settings nsettings;
+
+						/*
+						 * Currently only cursor_option is allowed. Is is not
+						 * necessary to merge the settings.
+						 */
+						nsettings.cursor_options = $4;
+
+						plpgsql_settings_set(&nsettings);
+					}
+				;
+
+pragmas			: pragmas pragma
+				| pragma
+				;
+
+pragmas_opt		:
+				| pragmas
+				;
+
 pl_block		: decl_sect K_BEGIN proc_sect exception_sect K_END opt_label
 					{
 						PLpgSQL_stmt_block *new;
@@ -450,6 +492,7 @@ decl_start		: K_DECLARE
 					{
 						/* Forget any variables created before block */
 						plpgsql_add_initdatums(NULL);
+
 						/*
 						 * Disable scanner lookup of identifiers while
 						 * we process the decl_stmts
@@ -478,6 +521,7 @@ decl_stmt		: decl_statement
 								 errmsg("block label must be placed before DECLARE, not after"),
 								 parser_errposition(@1)));
 					}
+				| pragma
 				;
 
 decl_statement	: decl_varname decl_const decl_datatype decl_collate decl_notnull decl_defval
@@ -583,12 +627,12 @@ decl_statement	: decl_varname decl_const decl_datatype decl_collate decl_notnull
 						curname_def->query = pstrdup(buf);
 						new->default_val = curname_def;
 
+						$7->cursor_options |= CURSOR_OPT_FAST_PLAN | $2;
 						new->cursor_explicit_expr = $7;
 						if ($5 == NULL)
 							new->cursor_explicit_argrow = -1;
 						else
 							new->cursor_explicit_argrow = $5->dno;
-						new->cursor_options = CURSOR_OPT_FAST_PLAN | $2;
 					}
 				;
 
@@ -2429,6 +2473,8 @@ unreserved_keyword	:
 				| K_EXIT
 				| K_FETCH
 				| K_FIRST
+				| K_FORCE_CUSTOM_PLAN
+				| K_FORCE_GENERIC_PLAN
 				| K_FORWARD
 				| K_GET
 				| K_HINT
@@ -2452,6 +2498,7 @@ unreserved_keyword	:
 				| K_PG_EXCEPTION_CONTEXT
 				| K_PG_EXCEPTION_DETAIL
 				| K_PG_EXCEPTION_HINT
+				| K_PLAN_CACHE
 				| K_PRINT_STRICT_PARAMS
 				| K_PRIOR
 				| K_QUERY
@@ -2608,6 +2655,7 @@ read_sql_construct(int until,
 	int					startlocation = -1;
 	int					parenlevel = 0;
 	PLpgSQL_expr		*expr;
+	PLpgSQL_settings	*settings = plpgsql_current_settings();
 
 	initStringInfo(&ds);
 	appendStringInfoString(&ds, sqlstart);
@@ -2691,6 +2739,7 @@ read_sql_construct(int until,
 	expr->paramnos		= NULL;
 	expr->rwparam		= -1;
 	expr->ns			= plpgsql_ns_top();
+	expr->cursor_options = settings->cursor_options;
 	pfree(ds.data);
 
 	if (valid_sql)
@@ -2850,6 +2899,7 @@ make_execsql_stmt(int firsttoken, int location)
 	bool				have_strict = false;
 	int					into_start_loc = -1;
 	int					into_end_loc = -1;
+	PLpgSQL_settings	*settings = plpgsql_current_settings();
 
 	initStringInfo(&ds);
 
@@ -2939,6 +2989,7 @@ make_execsql_stmt(int firsttoken, int location)
 	expr->paramnos		= NULL;
 	expr->rwparam		= -1;
 	expr->ns			= plpgsql_ns_top();
+	expr->cursor_options = settings->cursor_options;
 	pfree(ds.data);
 
 	check_sql_expr(expr->query, location, 0);
@@ -3675,6 +3726,7 @@ read_cursor_args(PLpgSQL_var *cursor, int until, const char *expected)
 	StringInfoData ds;
 	char	   *sqlstart = "SELECT ";
 	bool		any_named = false;
+	PLpgSQL_settings *settings = plpgsql_current_settings();
 
 	tok = yylex();
 	if (cursor->cursor_explicit_argrow < 0)
@@ -3823,6 +3875,7 @@ read_cursor_args(PLpgSQL_var *cursor, int until, const char *expected)
 	expr->paramnos		= NULL;
 	expr->rwparam		= -1;
 	expr->ns            = plpgsql_ns_top();
+	expr->cursor_options = settings->cursor_options;
 	pfree(ds.data);
 
 	/* Next we'd better find the until token */
diff --git a/src/pl/plpgsql/src/pl_scanner.c b/src/pl/plpgsql/src/pl_scanner.c
index 553be8c..6a57c35 100644
--- a/src/pl/plpgsql/src/pl_scanner.c
+++ b/src/pl/plpgsql/src/pl_scanner.c
@@ -86,6 +86,7 @@ static const ScanKeyword reserved_keywords[] = {
 	PG_KEYWORD("not", K_NOT, RESERVED_KEYWORD)
 	PG_KEYWORD("null", K_NULL, RESERVED_KEYWORD)
 	PG_KEYWORD("or", K_OR, RESERVED_KEYWORD)
+	PG_KEYWORD("pragma", K_PRAGMA, RESERVED_KEYWORD)
 	PG_KEYWORD("strict", K_STRICT, RESERVED_KEYWORD)
 	PG_KEYWORD("then", K_THEN, RESERVED_KEYWORD)
 	PG_KEYWORD("to", K_TO, RESERVED_KEYWORD)
@@ -126,6 +127,8 @@ static const ScanKeyword unreserved_keywords[] = {
 	PG_KEYWORD("exit", K_EXIT, UNRESERVED_KEYWORD)
 	PG_KEYWORD("fetch", K_FETCH, UNRESERVED_KEYWORD)
 	PG_KEYWORD("first", K_FIRST, UNRESERVED_KEYWORD)
+	PG_KEYWORD("force_custom_plan", K_FORCE_CUSTOM_PLAN, UNRESERVED_KEYWORD)
+	PG_KEYWORD("force_generic_plan", K_FORCE_GENERIC_PLAN, UNRESERVED_KEYWORD)
 	PG_KEYWORD("forward", K_FORWARD, UNRESERVED_KEYWORD)
 	PG_KEYWORD("get", K_GET, UNRESERVED_KEYWORD)
 	PG_KEYWORD("hint", K_HINT, UNRESERVED_KEYWORD)
@@ -149,6 +152,7 @@ static const ScanKeyword unreserved_keywords[] = {
 	PG_KEYWORD("pg_exception_context", K_PG_EXCEPTION_CONTEXT, UNRESERVED_KEYWORD)
 	PG_KEYWORD("pg_exception_detail", K_PG_EXCEPTION_DETAIL, UNRESERVED_KEYWORD)
 	PG_KEYWORD("pg_exception_hint", K_PG_EXCEPTION_HINT, UNRESERVED_KEYWORD)
+	PG_KEYWORD("plan_cache", K_PLAN_CACHE, UNRESERVED_KEYWORD)
 	PG_KEYWORD("print_strict_params", K_PRINT_STRICT_PARAMS, UNRESERVED_KEYWORD)
 	PG_KEYWORD("prior", K_PRIOR, UNRESERVED_KEYWORD)
 	PG_KEYWORD("query", K_QUERY, UNRESERVED_KEYWORD)
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index b7e103b..5366311 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -222,6 +222,7 @@ typedef struct PLpgSQL_expr
 	int			dno;
 	char	   *query;
 	SPIPlanPtr	plan;
+	int			cursor_options;
 	Bitmapset  *paramnos;		/* all dnos referenced by this query */
 	int			rwparam;		/* dno of read/write param, or -1 if none */
 
@@ -264,7 +265,6 @@ typedef struct PLpgSQL_var
 	PLpgSQL_expr *default_val;
 	PLpgSQL_expr *cursor_explicit_expr;
 	int			cursor_explicit_argrow;
-	int			cursor_options;
 
 	Datum		value;
 	bool		isnull;
@@ -344,6 +344,15 @@ typedef struct PLpgSQL_arrayelem
 } PLpgSQL_arrayelem;
 
 /*
+ * Compiler settings
+ */
+typedef struct PLpgSQL_settings
+{
+	struct PLpgSQL_settings *prev;
+	int			cursor_options;
+} PLpgSQL_settings;
+
+/*
  * Item in the compilers namespace tree
  */
 typedef struct PLpgSQL_nsitem
@@ -355,6 +364,7 @@ typedef struct PLpgSQL_nsitem
 	 */
 	int			itemno;
 	struct PLpgSQL_nsitem *prev;
+	PLpgSQL_settings	  *local_settings;		/* has own copy of PRAGMA settings */
 	char		name[FLEXIBLE_ARRAY_MEMBER];	/* nul-terminated string */
 } PLpgSQL_nsitem;
 
@@ -1002,6 +1012,7 @@ typedef struct PLwdatum
 	List	   *idents;			/* valid if composite name */
 } PLwdatum;
 
+
 /**********************************************************************
  * Global variable declarations
  **********************************************************************/
@@ -1119,6 +1130,10 @@ extern PLpgSQL_nsitem *plpgsql_ns_lookup(PLpgSQL_nsitem *ns_cur, bool localmode,
 extern PLpgSQL_nsitem *plpgsql_ns_lookup_label(PLpgSQL_nsitem *ns_cur,
 						const char *name);
 extern PLpgSQL_nsitem *plpgsql_ns_find_nearest_loop(PLpgSQL_nsitem *ns_cur);
+extern PLpgSQL_settings *plpgsql_current_settings(void);
+extern void plpgsql_settings_init(PLpgSQL_settings *settings);
+extern void plpgsql_settings_set(PLpgSQL_settings *settings);
+
 
 /*
  * Other functions in pl_funcs.c
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 04848c1..104791e 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -5684,3 +5684,38 @@ end;
 $$;
 ERROR:  value for domain plpgsql_arr_domain violates check constraint "plpgsql_arr_domain_check"
 CONTEXT:  PL/pgSQL function inline_code_block line 4 at assignment
+-- test plan cache control
+-- these tests only check the syntax, not the plan selection
+create table pragma_plan_cache_foo(a int);
+insert into pragma_plan_cache_foo values(10);
+create or replace function pragma_plan_cache(_a int)
+returns void as $$
+pragma plan_cache(default);
+pragma plan_cache(force_custom_plan);
+pragma plan_cache(force_generic_plan);
+declare
+  pragma plan_cache(default);
+  aux int;
+  pragma plan_cache(force_custom_plan);
+  pragma plan_cache(force_generic_plan);
+begin
+  select a into aux from pragma_plan_cache_foo where a = _a;
+  raise notice '<<%>>', aux;
+end;
+$$ language plpgsql;
+select pragma_plan_cache(0);
+NOTICE:  <<<NULL>>>
+ pragma_plan_cache 
+-------------------
+ 
+(1 row)
+
+select pragma_plan_cache(10);
+NOTICE:  <<10>>
+ pragma_plan_cache 
+-------------------
+ 
+(1 row)
+
+drop function pragma_plan_cache(int);
+drop table pragma_plan_cache_foo;
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index 31dcbdf..f027d09 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -4475,3 +4475,33 @@ begin
   v_test := 0 || v_test;  -- fail
 end;
 $$;
+
+
+-- test plan cache control
+
+-- these tests only check the syntax, not the plan selection
+
+create table pragma_plan_cache_foo(a int);
+insert into pragma_plan_cache_foo values(10);
+
+create or replace function pragma_plan_cache(_a int)
+returns void as $$
+pragma plan_cache(default);
+pragma plan_cache(force_custom_plan);
+pragma plan_cache(force_generic_plan);
+declare
+  pragma plan_cache(default);
+  aux int;
+  pragma plan_cache(force_custom_plan);
+  pragma plan_cache(force_generic_plan);
+begin
+  select a into aux from pragma_plan_cache_foo where a = _a;
+  raise notice '<<%>>', aux;
+end;
+$$ language plpgsql;
+
+select pragma_plan_cache(0);
+select pragma_plan_cache(10);
+
+drop function pragma_plan_cache(int);
+drop table pragma_plan_cache_foo;
-- 
2.7.4

