リビジョン | c05bb31d1e15f2f04f6da842eee9bfef868ec466 (tree) |
---|---|
日時 | 2017-05-18 19:52:13 |
作者 | Kyotaro Horiguchi <horiguchi.kyotaro@lab....> |
コミッター | Kyotaro Horiguchi |
Fix a crash bug on complex views when enable_hint_table is on
The Query that planner receives sometimes irrelevant to
debug_query_string. If enable_hint_table is on, pg_hint_plan_planner
normalizes debug_query_string using query-jumble information created
from the irrelevant Query the can lead to crash. To avoid this
situation, retrieve hints in post_parse_analyze_hook, where
corresponding pairs of a query string and a parsed Query.
@@ -16,6 +16,7 @@ | ||
16 | 16 | #include "mb/pg_wchar.h" |
17 | 17 | #include "miscadmin.h" |
18 | 18 | #include "nodes/nodeFuncs.h" |
19 | +#include "nodes/params.h" | |
19 | 20 | #include "optimizer/clauses.h" |
20 | 21 | #include "optimizer/cost.h" |
21 | 22 | #include "optimizer/geqo.h" |
@@ -26,12 +27,14 @@ | ||
26 | 27 | #include "optimizer/planner.h" |
27 | 28 | #include "optimizer/prep.h" |
28 | 29 | #include "optimizer/restrictinfo.h" |
30 | +#include "parser/analyze.h" | |
29 | 31 | #include "parser/scansup.h" |
30 | 32 | #include "tcop/utility.h" |
31 | 33 | #include "utils/builtins.h" |
32 | 34 | #include "utils/lsyscache.h" |
33 | 35 | #include "utils/memutils.h" |
34 | 36 | #include "utils/rel.h" |
37 | +#include "utils/snapmgr.h" | |
35 | 38 | #include "utils/syscache.h" |
36 | 39 | #include "utils/resowner.h" |
37 | 40 |
@@ -87,10 +90,12 @@ PG_MODULE_MAGIC; | ||
87 | 90 | #define HINT_ARRAY_DEFAULT_INITSIZE 8 |
88 | 91 | |
89 | 92 | #define hint_ereport(str, detail) \ |
90 | - ereport(pg_hint_plan_message_level, \ | |
91 | - (errhidestmt(hidestmt), \ | |
92 | - errmsg("pg_hint_plan%s: hint syntax error at or near \"%s\"", qnostr, (str)), \ | |
93 | - errdetail detail)) | |
93 | + do { \ | |
94 | + ereport(pg_hint_plan_message_level, \ | |
95 | + (errmsg("pg_hint_plan%s: hint syntax error at or near \"%s\"", qnostr, (str)), \ | |
96 | + errdetail detail)); \ | |
97 | + msgqno = qno; \ | |
98 | + } while(0) | |
94 | 99 | |
95 | 100 | #define skip_space(str) \ |
96 | 101 | while (isspace(*str)) \ |
@@ -213,7 +218,9 @@ typedef enum HintStatus | ||
213 | 218 | (hint)->base.state == HINT_STATE_USED) |
214 | 219 | |
215 | 220 | static unsigned int qno = 0; |
221 | +static unsigned int msgqno = 0; | |
216 | 222 | static char qnostr[32]; |
223 | +static const char *current_hint_str = NULL; | |
217 | 224 | |
218 | 225 | /* common data for all hints. */ |
219 | 226 | struct Hint |
@@ -373,11 +380,7 @@ void _PG_fini(void); | ||
373 | 380 | static void push_hint(HintState *hstate); |
374 | 381 | static void pop_hint(void); |
375 | 382 | |
376 | -static void pg_hint_plan_ProcessUtility(Node *parsetree, | |
377 | - const char *queryString, | |
378 | - ProcessUtilityContext context, | |
379 | - ParamListInfo params, | |
380 | - DestReceiver *dest, char *completionTag); | |
383 | +static void pg_hint_plan_post_parse_analyze(ParseState *pstate, Query *query); | |
381 | 384 | static PlannedStmt *pg_hint_plan_planner(Query *parse, int cursorOptions, |
382 | 385 | ParamListInfo boundParams); |
383 | 386 | static RelOptInfo *pg_hint_plan_join_search(PlannerInfo *root, |
@@ -496,9 +499,6 @@ static int pg_hint_plan_message_level = INFO; | ||
496 | 499 | /* Default is off, to keep backward compatibility. */ |
497 | 500 | static bool pg_hint_plan_enable_hint_table = false; |
498 | 501 | |
499 | -/* Internal static variables. */ | |
500 | -static bool hidestmt = false; /* Allow or inhibit STATEMENT: output */ | |
501 | - | |
502 | 502 | static int plpgsql_recurse_level = 0; /* PLpgSQL recursion level */ |
503 | 503 | static int hint_inhibit_level = 0; /* Inhibit hinting if this is above 0 */ |
504 | 504 | /* (This could not be above 1) */ |
@@ -540,7 +540,7 @@ static const struct config_enum_entry parse_debug_level_options[] = { | ||
540 | 540 | }; |
541 | 541 | |
542 | 542 | /* Saved hook values in case of unload */ |
543 | -static ProcessUtility_hook_type prev_ProcessUtility = NULL; | |
543 | +static post_parse_analyze_hook_type prev_post_parse_analyze_hook = NULL; | |
544 | 544 | static planner_hook_type prev_planner = NULL; |
545 | 545 | static join_search_hook_type prev_join_search = NULL; |
546 | 546 | static set_rel_pathlist_hook_type prev_set_rel_pathlist = NULL; |
@@ -671,8 +671,8 @@ _PG_init(void) | ||
671 | 671 | NULL); |
672 | 672 | |
673 | 673 | /* Install hooks. */ |
674 | - prev_ProcessUtility = ProcessUtility_hook; | |
675 | - ProcessUtility_hook = pg_hint_plan_ProcessUtility; | |
674 | + prev_post_parse_analyze_hook = post_parse_analyze_hook; | |
675 | + post_parse_analyze_hook = pg_hint_plan_post_parse_analyze; | |
676 | 676 | prev_planner = planner_hook; |
677 | 677 | planner_hook = pg_hint_plan_planner; |
678 | 678 | prev_join_search = join_search_hook; |
@@ -697,7 +697,7 @@ _PG_fini(void) | ||
697 | 697 | PLpgSQL_plugin **var_ptr; |
698 | 698 | |
699 | 699 | /* Uninstall hooks. */ |
700 | - ProcessUtility_hook = prev_ProcessUtility; | |
700 | + post_parse_analyze_hook = prev_post_parse_analyze_hook; | |
701 | 701 | planner_hook = prev_planner; |
702 | 702 | join_search_hook = prev_join_search; |
703 | 703 | set_rel_pathlist_hook = prev_set_rel_pathlist; |
@@ -1266,8 +1266,9 @@ HintStateDump2(HintState *hstate) | ||
1266 | 1266 | appendStringInfoChar(&buf, '}'); |
1267 | 1267 | |
1268 | 1268 | ereport(pg_hint_plan_message_level, |
1269 | - (errhidestmt(true), | |
1270 | - errmsg("%s", buf.data))); | |
1269 | + (errmsg("%s", buf.data), | |
1270 | + errhidestmt(true), | |
1271 | + errhidecontext(true))); | |
1271 | 1272 | |
1272 | 1273 | pfree(buf.data); |
1273 | 1274 | } |
@@ -1722,7 +1723,15 @@ get_hints_from_table(const char *client_query, const char *client_application) | ||
1722 | 1723 | |
1723 | 1724 | PG_TRY(); |
1724 | 1725 | { |
1726 | + bool snapshot_set = false; | |
1727 | + | |
1725 | 1728 | hint_inhibit_level++; |
1729 | + | |
1730 | + if (!ActiveSnapshotSet()) | |
1731 | + { | |
1732 | + PushActiveSnapshot(GetTransactionSnapshot()); | |
1733 | + snapshot_set = true; | |
1734 | + } | |
1726 | 1735 | |
1727 | 1736 | SPI_connect(); |
1728 | 1737 |
@@ -1759,7 +1768,10 @@ get_hints_from_table(const char *client_query, const char *client_application) | ||
1759 | 1768 | } |
1760 | 1769 | |
1761 | 1770 | SPI_finish(); |
1762 | - | |
1771 | + | |
1772 | + if (snapshot_set) | |
1773 | + PopActiveSnapshot(); | |
1774 | + | |
1763 | 1775 | hint_inhibit_level--; |
1764 | 1776 | } |
1765 | 1777 | PG_CATCH(); |
@@ -1773,30 +1785,87 @@ get_hints_from_table(const char *client_query, const char *client_application) | ||
1773 | 1785 | } |
1774 | 1786 | |
1775 | 1787 | /* |
1776 | - * Get client-supplied query string. | |
1788 | + * Get client-supplied query string. Addtion to that the jumbled query is | |
1789 | + * supplied if the caller requested. From the restriction of JumbleQuery, some | |
1790 | + * kind of query needs special amendments. Reutrns NULL if the current hint | |
1791 | + * string is still valid. | |
1777 | 1792 | */ |
1778 | 1793 | static const char * |
1779 | -get_query_string(void) | |
1794 | +get_query_string(ParseState *pstate, Query *query, Query **jumblequery) | |
1780 | 1795 | { |
1781 | - const char *p; | |
1796 | + const char *p = debug_query_string; | |
1782 | 1797 | |
1783 | - if (plpgsql_recurse_level > 0) | |
1784 | - { | |
1785 | - /* | |
1786 | - * This is quite ugly but this is the only point I could find where | |
1787 | - * we can get the query string. | |
1788 | - */ | |
1789 | - p = (char*)error_context_stack->arg; | |
1790 | - } | |
1791 | - else if (stmt_name) | |
1798 | + if (jumblequery != NULL) | |
1799 | + *jumblequery = query; | |
1800 | + | |
1801 | + Assert(plpgsql_recurse_level == 0); | |
1802 | + | |
1803 | + if (query->commandType == CMD_UTILITY) | |
1792 | 1804 | { |
1793 | - PreparedStatement *entry; | |
1805 | + Query *target_query = query; | |
1806 | + | |
1807 | + /* Use the target query if EXPLAIN */ | |
1808 | + if (IsA(query->utilityStmt, ExplainStmt)) | |
1809 | + { | |
1810 | + ExplainStmt *stmt = (ExplainStmt *)(query->utilityStmt); | |
1811 | + Assert(IsA(stmt->query, Query)); | |
1812 | + target_query = (Query *)stmt->query; | |
1813 | + | |
1814 | + if (target_query->commandType == CMD_UTILITY && | |
1815 | + target_query->utilityStmt != NULL) | |
1816 | + target_query = (Query *)target_query->utilityStmt; | |
1817 | + | |
1818 | + if (jumblequery) | |
1819 | + *jumblequery = target_query; | |
1820 | + } | |
1821 | + | |
1822 | + if (IsA(target_query, CreateTableAsStmt)) | |
1823 | + { | |
1824 | + /* | |
1825 | + * Use the the body query for CREATE AS. The Query for jumble also | |
1826 | + * replaced with the corresponding one. | |
1827 | + */ | |
1828 | + CreateTableAsStmt *stmt = (CreateTableAsStmt *) target_query; | |
1829 | + PreparedStatement *entry; | |
1830 | + Query *ent_query; | |
1831 | + | |
1832 | + Assert(IsA(stmt->query, Query)); | |
1833 | + target_query = (Query *) stmt->query; | |
1794 | 1834 | |
1795 | - entry = FetchPreparedStatement(stmt_name, true); | |
1796 | - p = entry->plansource->query_string; | |
1835 | + if (target_query->commandType == CMD_UTILITY && | |
1836 | + IsA(target_query->utilityStmt, ExecuteStmt)) | |
1837 | + { | |
1838 | + ExecuteStmt *estmt = (ExecuteStmt *) target_query->utilityStmt; | |
1839 | + entry = FetchPreparedStatement(estmt->name, true); | |
1840 | + p = entry->plansource->query_string; | |
1841 | + ent_query = (Query *) linitial (entry->plansource->query_list); | |
1842 | + Assert(IsA(ent_query, Query)); | |
1843 | + if (jumblequery) | |
1844 | + *jumblequery = ent_query; | |
1845 | + } | |
1846 | + } | |
1847 | + else | |
1848 | + if (IsA(target_query, ExecuteStmt)) | |
1849 | + { | |
1850 | + /* | |
1851 | + * Use the prepared query for EXECUTE. The Query for jumble also | |
1852 | + * replaced with the corresponding one. | |
1853 | + */ | |
1854 | + ExecuteStmt *stmt = (ExecuteStmt *)target_query; | |
1855 | + PreparedStatement *entry; | |
1856 | + Query *ent_query; | |
1857 | + | |
1858 | + entry = FetchPreparedStatement(stmt->name, true); | |
1859 | + p = entry->plansource->query_string; | |
1860 | + ent_query = (Query *) linitial (entry->plansource->query_list); | |
1861 | + Assert(IsA(ent_query, Query)); | |
1862 | + if (jumblequery) | |
1863 | + *jumblequery = ent_query; | |
1864 | + } | |
1797 | 1865 | } |
1798 | - else | |
1799 | - p = debug_query_string; | |
1866 | + /* Return NULL if the pstate is not identical to the top-level query */ | |
1867 | + else if (strcmp(pstate->p_sourcetext, p) != 0) | |
1868 | + p = NULL; | |
1800 | 1869 | |
1801 | 1870 | return p; |
1802 | 1871 | } |
@@ -2473,10 +2542,10 @@ set_config_option_noerror(const char *name, const char *value, | ||
2473 | 2542 | |
2474 | 2543 | ereport(elevel, |
2475 | 2544 | (errcode(errdata->sqlerrcode), |
2476 | - errhidestmt(hidestmt), | |
2477 | 2545 | errmsg("%s", errdata->message), |
2478 | 2546 | errdata->detail ? errdetail("%s", errdata->detail) : 0, |
2479 | 2547 | errdata->hint ? errhint("%s", errdata->hint) : 0)); |
2548 | + msgqno = qno; | |
2480 | 2549 | FreeErrorData(errdata); |
2481 | 2550 | } |
2482 | 2551 | PG_END_TRY(); |
@@ -2625,132 +2694,6 @@ set_join_config_options(unsigned char enforce_mask, GucContext context) | ||
2625 | 2694 | } |
2626 | 2695 | |
2627 | 2696 | /* |
2628 | - * pg_hint_plan hook functions | |
2629 | - */ | |
2630 | - | |
2631 | -static void | |
2632 | -pg_hint_plan_ProcessUtility(Node *parsetree, const char *queryString, | |
2633 | - ProcessUtilityContext context, | |
2634 | - ParamListInfo params, | |
2635 | - DestReceiver *dest, char *completionTag) | |
2636 | -{ | |
2637 | - Node *node; | |
2638 | - | |
2639 | - /* | |
2640 | - * Use standard planner if pg_hint_plan is disabled or current nesting | |
2641 | - * depth is nesting depth of SPI calls. | |
2642 | - */ | |
2643 | - if (!pg_hint_plan_enable_hint || hint_inhibit_level > 0) | |
2644 | - { | |
2645 | - if (debug_level > 1) | |
2646 | - ereport(pg_hint_plan_message_level, | |
2647 | - (errmsg ("pg_hint_plan: ProcessUtility:" | |
2648 | - " pg_hint_plan.enable_hint = off"))); | |
2649 | - if (prev_ProcessUtility) | |
2650 | - (*prev_ProcessUtility) (parsetree, queryString, | |
2651 | - context, params, | |
2652 | - dest, completionTag); | |
2653 | - else | |
2654 | - standard_ProcessUtility(parsetree, queryString, | |
2655 | - context, params, | |
2656 | - dest, completionTag); | |
2657 | - return; | |
2658 | - } | |
2659 | - | |
2660 | - node = parsetree; | |
2661 | - if (IsA(node, ExplainStmt)) | |
2662 | - { | |
2663 | - /* | |
2664 | - * Draw out parse tree of actual query from Query struct of EXPLAIN | |
2665 | - * statement. | |
2666 | - */ | |
2667 | - ExplainStmt *stmt; | |
2668 | - Query *query; | |
2669 | - | |
2670 | - stmt = (ExplainStmt *) node; | |
2671 | - | |
2672 | - Assert(IsA(stmt->query, Query)); | |
2673 | - query = (Query *) stmt->query; | |
2674 | - | |
2675 | - if (query->commandType == CMD_UTILITY && query->utilityStmt != NULL) | |
2676 | - node = query->utilityStmt; | |
2677 | - } | |
2678 | - | |
2679 | - /* | |
2680 | - * If the query was a EXECUTE or CREATE TABLE AS EXECUTE, get query string | |
2681 | - * specified to preceding PREPARE command to use it as source of hints. | |
2682 | - */ | |
2683 | - if (IsA(node, ExecuteStmt)) | |
2684 | - { | |
2685 | - ExecuteStmt *stmt; | |
2686 | - | |
2687 | - stmt = (ExecuteStmt *) node; | |
2688 | - stmt_name = stmt->name; | |
2689 | - } | |
2690 | - | |
2691 | - /* | |
2692 | - * CREATE AS EXECUTE behavior has changed since 9.2, so we must handle it | |
2693 | - * specially here. | |
2694 | - */ | |
2695 | - if (IsA(node, CreateTableAsStmt)) | |
2696 | - { | |
2697 | - CreateTableAsStmt *stmt; | |
2698 | - Query *query; | |
2699 | - | |
2700 | - stmt = (CreateTableAsStmt *) node; | |
2701 | - Assert(IsA(stmt->query, Query)); | |
2702 | - query = (Query *) stmt->query; | |
2703 | - | |
2704 | - if (query->commandType == CMD_UTILITY && | |
2705 | - IsA(query->utilityStmt, ExecuteStmt)) | |
2706 | - { | |
2707 | - ExecuteStmt *estmt = (ExecuteStmt *) query->utilityStmt; | |
2708 | - stmt_name = estmt->name; | |
2709 | - } | |
2710 | - } | |
2711 | - | |
2712 | - if (stmt_name) | |
2713 | - { | |
2714 | - if (debug_level > 1) | |
2715 | - ereport(pg_hint_plan_message_level, | |
2716 | - (errmsg ("pg_hint_plan: ProcessUtility:" | |
2717 | - " stmt_name = \"%s\", statement=\"%s\"", | |
2718 | - stmt_name, queryString))); | |
2719 | - | |
2720 | - PG_TRY(); | |
2721 | - { | |
2722 | - if (prev_ProcessUtility) | |
2723 | - (*prev_ProcessUtility) (parsetree, queryString, | |
2724 | - context, params, | |
2725 | - dest, completionTag); | |
2726 | - else | |
2727 | - standard_ProcessUtility(parsetree, queryString, | |
2728 | - context, params, | |
2729 | - dest, completionTag); | |
2730 | - } | |
2731 | - PG_CATCH(); | |
2732 | - { | |
2733 | - stmt_name = NULL; | |
2734 | - PG_RE_THROW(); | |
2735 | - } | |
2736 | - PG_END_TRY(); | |
2737 | - | |
2738 | - stmt_name = NULL; | |
2739 | - | |
2740 | - return; | |
2741 | - } | |
2742 | - | |
2743 | - if (prev_ProcessUtility) | |
2744 | - (*prev_ProcessUtility) (parsetree, queryString, | |
2745 | - context, params, | |
2746 | - dest, completionTag); | |
2747 | - else | |
2748 | - standard_ProcessUtility(parsetree, queryString, | |
2749 | - context, params, | |
2750 | - dest, completionTag); | |
2751 | -} | |
2752 | - | |
2753 | -/* | |
2754 | 2697 | * Push a hint into hint stack which is implemented with List struct. Head of |
2755 | 2698 | * list is top of stack. |
2756 | 2699 | */ |
@@ -2784,24 +2727,182 @@ pop_hint(void) | ||
2784 | 2727 | current_hint_state = (HintState *) lfirst(list_head(HintStateStack)); |
2785 | 2728 | } |
2786 | 2729 | |
2730 | +/* | |
2731 | + * Retrieve and store a hint string from given query or from the hint table. | |
2732 | + * If we are using the hint table, the query string is needed to be normalized. | |
2733 | + * However, ParseState, which is not available in planner_hook, is required to | |
2734 | + * check if the query tree (Query) is surely corresponding to the target query. | |
2735 | + */ | |
2736 | +static void | |
2737 | +pg_hint_plan_post_parse_analyze(ParseState *pstate, Query *query) | |
2738 | +{ | |
2739 | + const char *query_str; | |
2740 | + MemoryContext oldcontext; | |
2741 | + | |
2742 | + if (prev_post_parse_analyze_hook) | |
2743 | + prev_post_parse_analyze_hook(pstate, query); | |
2744 | + | |
2745 | + /* do nothing under hint table search */ | |
2746 | + if (hint_inhibit_level > 0) | |
2747 | + return; | |
2748 | + | |
2749 | + if (!pg_hint_plan_enable_hint) | |
2750 | + { | |
2751 | + if (current_hint_str) | |
2752 | + { | |
2753 | + pfree((void *)current_hint_str); | |
2754 | + current_hint_str = NULL; | |
2755 | + } | |
2756 | + return; | |
2757 | + } | |
2758 | + | |
2759 | + /* increment the query number */ | |
2760 | + qnostr[0] = 0; | |
2761 | + if (debug_level > 1) | |
2762 | + snprintf(qnostr, sizeof(qnostr), "[qno=0x%x]", qno++); | |
2763 | + qno++; | |
2764 | + | |
2765 | + /* search the hint table for a hint if requested */ | |
2766 | + if (pg_hint_plan_enable_hint_table) | |
2767 | + { | |
2768 | + int query_len; | |
2769 | + pgssJumbleState jstate; | |
2770 | + Query *jumblequery; | |
2771 | + char *normalized_query = NULL; | |
2772 | + | |
2773 | + query_str = get_query_string(pstate, query, &jumblequery); | |
2774 | + | |
2775 | + /* If this query is not for hint, just return */ | |
2776 | + if (!query_str) | |
2777 | + return; | |
2778 | + | |
2779 | + /* clear the previous hint string */ | |
2780 | + if (current_hint_str) | |
2781 | + { | |
2782 | + pfree((void *)current_hint_str); | |
2783 | + current_hint_str = NULL; | |
2784 | + } | |
2785 | + | |
2786 | + if (jumblequery) | |
2787 | + { | |
2788 | + /* | |
2789 | + * XXX: normalizing code is copied from pg_stat_statements.c, so be | |
2790 | + * careful to PostgreSQL's version up. | |
2791 | + */ | |
2792 | + jstate.jumble = (unsigned char *) palloc(JUMBLE_SIZE); | |
2793 | + jstate.jumble_len = 0; | |
2794 | + jstate.clocations_buf_size = 32; | |
2795 | + jstate.clocations = (pgssLocationLen *) | |
2796 | + palloc(jstate.clocations_buf_size * sizeof(pgssLocationLen)); | |
2797 | + jstate.clocations_count = 0; | |
2798 | + | |
2799 | + JumbleQuery(&jstate, jumblequery); | |
2800 | + | |
2801 | + /* | |
2802 | + * Normalize the query string by replacing constants with '?' | |
2803 | + */ | |
2804 | + /* | |
2805 | + * Search hint string which is stored keyed by query string | |
2806 | + * and application name. The query string is normalized to allow | |
2807 | + * fuzzy matching. | |
2808 | + * | |
2809 | + * Adding 1 byte to query_len ensures that the returned string has | |
2810 | + * a terminating NULL. | |
2811 | + */ | |
2812 | + query_len = strlen(query_str) + 1; | |
2813 | + normalized_query = | |
2814 | + generate_normalized_query(&jstate, query_str, | |
2815 | + &query_len, | |
2816 | + GetDatabaseEncoding()); | |
2817 | + | |
2818 | + /* | |
2819 | + * find a hint for the normalized query. the result should be in | |
2820 | + * TopMemoryContext | |
2821 | + */ | |
2822 | + oldcontext = MemoryContextSwitchTo(TopMemoryContext); | |
2823 | + current_hint_str = | |
2824 | + get_hints_from_table(normalized_query, application_name); | |
2825 | + MemoryContextSwitchTo(oldcontext); | |
2826 | + | |
2827 | + if (debug_level > 1) | |
2828 | + { | |
2829 | + if (current_hint_str) | |
2830 | + ereport(pg_hint_plan_message_level, | |
2831 | + (errmsg("pg_hint_plan[qno=0x%x]: " | |
2832 | + "post_parse_analyze_hook: " | |
2833 | + "hints from table: \"%s\": " | |
2834 | + "normalized_query=\"%s\", " | |
2835 | + "application name =\"%s\"", | |
2836 | + qno, current_hint_str, | |
2837 | + normalized_query, application_name), | |
2838 | + errhidestmt(msgqno != qno), | |
2839 | + errhidecontext(msgqno != qno))); | |
2840 | + else | |
2841 | + ereport(pg_hint_plan_message_level, | |
2842 | + (errmsg("pg_hint_plan[qno=0x%x]: " | |
2843 | + "no match found in table: " | |
2844 | + "application name = \"%s\", " | |
2845 | + "normalized_query=\"%s\"", | |
2846 | + qno, application_name, | |
2847 | + normalized_query), | |
2848 | + errhidestmt(msgqno != qno), | |
2849 | + errhidecontext(msgqno != qno))); | |
2850 | + | |
2851 | + msgqno = qno; | |
2852 | + } | |
2853 | + } | |
2854 | + | |
2855 | + /* retrun if we have hint here*/ | |
2856 | + if (current_hint_str) | |
2857 | + return; | |
2858 | + } | |
2859 | + else | |
2860 | + query_str = get_query_string(pstate, query, NULL); | |
2861 | + | |
2862 | + if (query_str) | |
2863 | + { | |
2864 | + /* | |
2865 | + * get hints from the comment. However we may have the same query | |
2866 | + * string with the previous call, but just retrieving hints is expected | |
2867 | + * to be faster than checking for identicalness before retrieval. | |
2868 | + */ | |
2869 | + if (current_hint_str) | |
2870 | + pfree((void *)current_hint_str); | |
2871 | + | |
2872 | + oldcontext = MemoryContextSwitchTo(TopMemoryContext); | |
2873 | + current_hint_str = get_hints_from_comment(query_str); | |
2874 | + MemoryContextSwitchTo(oldcontext); | |
2875 | + } | |
2876 | + | |
2877 | + if (debug_level > 1) | |
2878 | + { | |
2879 | + if (debug_level == 1 && | |
2880 | + (stmt_name || strcmp(query_str, debug_query_string))) | |
2881 | + ereport(pg_hint_plan_message_level, | |
2882 | + (errmsg("hints in comment=\"%s\"", | |
2883 | + current_hint_str ? current_hint_str : "(none)"), | |
2884 | + errhidestmt(msgqno != qno), | |
2885 | + errhidecontext(msgqno != qno))); | |
2886 | + else | |
2887 | + ereport(pg_hint_plan_message_level, | |
2888 | + (errmsg("hints in comment=\"%s\", stmt=\"%s\", query=\"%s\", debug_query_string=\"%s\"", | |
2889 | + current_hint_str ? current_hint_str : "(none)", | |
2890 | + stmt_name, query_str, debug_query_string), | |
2891 | + errhidestmt(msgqno != qno), | |
2892 | + errhidecontext(msgqno != qno))); | |
2893 | + msgqno = qno; | |
2894 | + } | |
2895 | +} | |
2896 | + | |
2897 | +/* | |
2898 | + * Read and set up hint information | |
2899 | + */ | |
2787 | 2900 | static PlannedStmt * |
2788 | 2901 | pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) |
2789 | 2902 | { |
2790 | - const char *hints = NULL; | |
2791 | - const char *query; | |
2792 | - char *norm_query; | |
2793 | - pgssJumbleState jstate; | |
2794 | - int query_len; | |
2795 | 2903 | int save_nestlevel; |
2796 | 2904 | PlannedStmt *result; |
2797 | 2905 | HintState *hstate; |
2798 | - char msgstr[1024]; | |
2799 | - | |
2800 | - qnostr[0] = 0; | |
2801 | - strcpy(msgstr, ""); | |
2802 | - if (debug_level > 1) | |
2803 | - snprintf(qnostr, sizeof(qnostr), "[qno=0x%x]", qno++); | |
2804 | - hidestmt = false; | |
2805 | 2906 | |
2806 | 2907 | /* |
2807 | 2908 | * Use standard planner if pg_hint_plan is disabled or current nesting |
@@ -2811,103 +2912,44 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) | ||
2811 | 2912 | if (!pg_hint_plan_enable_hint || hint_inhibit_level > 0) |
2812 | 2913 | { |
2813 | 2914 | if (debug_level > 1) |
2814 | - elog(pg_hint_plan_message_level, | |
2815 | - "pg_hint_plan%s: planner: enable_hint=%d," | |
2816 | - " hint_inhibit_level=%d", | |
2817 | - qnostr, pg_hint_plan_enable_hint, hint_inhibit_level); | |
2818 | - hidestmt = true; | |
2915 | + ereport(pg_hint_plan_message_level, | |
2916 | + (errmsg ("pg_hint_plan%s: planner: enable_hint=%d," | |
2917 | + " hint_inhibit_level=%d", | |
2918 | + qnostr, pg_hint_plan_enable_hint, | |
2919 | + hint_inhibit_level), | |
2920 | + errhidestmt(msgqno != qno))); | |
2921 | + msgqno = qno; | |
2819 | 2922 | |
2820 | 2923 | goto standard_planner_proc; |
2821 | 2924 | } |
2822 | 2925 | |
2823 | - /* Create hint struct from client-supplied query string. */ | |
2824 | - query = get_query_string(); | |
2825 | - | |
2826 | 2926 | /* |
2827 | - * Create hintstate from hint specified for the query, if any. | |
2828 | - * | |
2829 | - * First we lookup hint in pg_hint.hints table by normalized query string, | |
2830 | - * unless pg_hint_plan.enable_hint_table is OFF. | |
2831 | - * This parameter provides option to avoid overhead of table lookup during | |
2832 | - * planning. | |
2833 | - * | |
2834 | - * If no hint was found, then we try to get hint from special query comment. | |
2927 | + * Support for nested plpgsql functions. This is quite ugly but this is the | |
2928 | + * only point I could find where I can get the query string. | |
2835 | 2929 | */ |
2836 | - if (pg_hint_plan_enable_hint_table) | |
2837 | - { | |
2838 | - /* | |
2839 | - * Search hint information which is stored for the query and the | |
2840 | - * application. Query string is normalized before using in condition | |
2841 | - * in order to allow fuzzy matching. | |
2842 | - * | |
2843 | - * XXX: normalizing code is copied from pg_stat_statements.c, so be | |
2844 | - * careful when supporting PostgreSQL's version up. | |
2845 | - */ | |
2846 | - jstate.jumble = (unsigned char *) palloc(JUMBLE_SIZE); | |
2847 | - jstate.jumble_len = 0; | |
2848 | - jstate.clocations_buf_size = 32; | |
2849 | - jstate.clocations = (pgssLocationLen *) | |
2850 | - palloc(jstate.clocations_buf_size * sizeof(pgssLocationLen)); | |
2851 | - jstate.clocations_count = 0; | |
2852 | - JumbleQuery(&jstate, parse); | |
2853 | - /* | |
2854 | - * generate_normalized_query() copies exact given query_len bytes, so we | |
2855 | - * add 1 byte for null-termination here. As comments on | |
2856 | - * generate_normalized_query says, generate_normalized_query doesn't | |
2857 | - * take care of null-terminate, but additional 1 byte ensures that '\0' | |
2858 | - * byte in the source buffer to be copied into norm_query. | |
2859 | - */ | |
2860 | - query_len = strlen(query) + 1; | |
2861 | - norm_query = generate_normalized_query(&jstate, | |
2862 | - query, | |
2863 | - &query_len, | |
2864 | - GetDatabaseEncoding()); | |
2865 | - hints = get_hints_from_table(norm_query, application_name); | |
2866 | - if (debug_level > 1) | |
2867 | - { | |
2868 | - if (hints) | |
2869 | - snprintf(msgstr, 1024, "hints from table: \"%s\":" | |
2870 | - " normalzed_query=\"%s\", application name =\"%s\"", | |
2871 | - hints, norm_query, application_name); | |
2872 | - else | |
2873 | - { | |
2874 | - ereport(pg_hint_plan_message_level, | |
2875 | - (errhidestmt(hidestmt), | |
2876 | - errmsg("pg_hint_plan%s:" | |
2877 | - " no match found in table:" | |
2878 | - " application name = \"%s\"," | |
2879 | - " normalzed_query=\"%s\"", | |
2880 | - qnostr, application_name, norm_query))); | |
2881 | - hidestmt = true; | |
2882 | - } | |
2883 | - } | |
2884 | - } | |
2885 | - if (hints == NULL) | |
2930 | + if (plpgsql_recurse_level > 0) | |
2886 | 2931 | { |
2887 | - hints = get_hints_from_comment(query); | |
2932 | + MemoryContext oldcontext; | |
2888 | 2933 | |
2889 | - if (debug_level > 1) | |
2890 | - { | |
2891 | - snprintf(msgstr, 1024, "hints in comment=\"%s\"", | |
2892 | - hints ? hints : "(none)"); | |
2893 | - if (debug_level > 2 || | |
2894 | - stmt_name || strcmp(query, debug_query_string)) | |
2895 | - snprintf(msgstr + strlen(msgstr), 1024- strlen(msgstr), | |
2896 | - ", stmt=\"%s\", query=\"%s\", debug_query_string=\"%s\"", | |
2897 | - stmt_name, query, debug_query_string); | |
2898 | - } | |
2934 | + if (current_hint_str) | |
2935 | + pfree((void *)current_hint_str); | |
2936 | + | |
2937 | + oldcontext = MemoryContextSwitchTo(TopMemoryContext); | |
2938 | + current_hint_str = | |
2939 | + get_hints_from_comment((char *)error_context_stack->arg); | |
2940 | + MemoryContextSwitchTo(oldcontext); | |
2899 | 2941 | } |
2900 | 2942 | |
2901 | - hstate = create_hintstate(parse, hints); | |
2943 | + if (!current_hint_str) | |
2944 | + goto standard_planner_proc; | |
2902 | 2945 | |
2903 | - /* | |
2904 | - * Use standard planner if the statement has not valid hint. Other hook | |
2905 | - * functions try to change plan with current_hint_state if any, so set it | |
2906 | - * to NULL. | |
2907 | - */ | |
2946 | + /* parse the hint into hint state struct */ | |
2947 | + hstate = create_hintstate(parse, pstrdup(current_hint_str)); | |
2948 | + | |
2949 | + /* run standard planner if the statement has not valid hint */ | |
2908 | 2950 | if (!hstate) |
2909 | 2951 | goto standard_planner_proc; |
2910 | - | |
2952 | + | |
2911 | 2953 | /* |
2912 | 2954 | * Push new hint struct to the hint stack to disable previous hint context. |
2913 | 2955 | */ |
@@ -2939,10 +2981,9 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) | ||
2939 | 2981 | if (debug_level > 1) |
2940 | 2982 | { |
2941 | 2983 | ereport(pg_hint_plan_message_level, |
2942 | - (errhidestmt(hidestmt), | |
2943 | - errmsg("pg_hint_plan%s: planner: %s", | |
2944 | - qnostr, msgstr))); | |
2945 | - hidestmt = true; | |
2984 | + (errhidestmt(msgqno != qno), | |
2985 | + errmsg("pg_hint_plan%s: planner", qnostr))); | |
2986 | + msgqno = qno; | |
2946 | 2987 | } |
2947 | 2988 | |
2948 | 2989 | /* |
@@ -2987,10 +3028,10 @@ standard_planner_proc: | ||
2987 | 3028 | if (debug_level > 1) |
2988 | 3029 | { |
2989 | 3030 | ereport(pg_hint_plan_message_level, |
2990 | - (errhidestmt(hidestmt), | |
2991 | - errmsg("pg_hint_plan%s: planner: no valid hint (%s)", | |
2992 | - qnostr, msgstr))); | |
2993 | - hidestmt = true; | |
3031 | + (errhidestmt(msgqno != qno), | |
3032 | + errmsg("pg_hint_plan%s: planner: no valid hint", | |
3033 | + qnostr))); | |
3034 | + msgqno = qno; | |
2994 | 3035 | } |
2995 | 3036 | current_hint_state = NULL; |
2996 | 3037 | if (prev_planner) |