Hi rebased due last changes in pg_exec.c
Regards Pavel
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml index d356deb9f5..56da4d6163 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 some + <application>PL/pgSQL</application> compiler behave. 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,43 @@ $$ 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 behave can be controlled with <literal>PRAGMA PLAN_CACHE</>. + This <literal>PRAGMA</> can be used on function or on block level (per + function, per block). The following options are possible: + <literal>DEFAULT</literal> - default <application>PL/pgSQL</application> + implementation - the system try to decide between custom plan and generic + plan after five query executions, <literal>FORCE_CUSTOM_PLAN</literal> + - the execution plan is one shot plan - it is specific for every set of + used paramaters, <literal>FORCE_GENERIC_PLAN</literal> - the query plan + is generic from start. + </para> + + <para> + <indexterm> + <primary>PRAGMA PLAN_CACHE</> + <secondary>in PL/pgSQL</> + </indexterm> + The plan for <command>INSERT</command> is generic from begin. The <literal> + PRAGMA PLAN_CACHE</literal> is related to function - etc. every command + in this function will use 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 bed343ea0c..70c970d20c 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 c27935b51b..762ee6f70f 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 93f89814b3..d074a4046e 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; +} + + +/* ---------- + * Set a 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 29729df550..0228ede279 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 not necessary + * do merge of 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 553be8c93c..6a57c35e10 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 b7e103b514..536631133c 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 04848c10a2..c23adf4475 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 of plan cache controlling +-- these tests checks only syntax +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 31dcbdffdd..2847c9dafb 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 of plan cache controlling + +-- these tests checks only syntax + +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;
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers