firtst release
Revision | 48b7b15ef663232881995ce7fdba5ff31a7065e6 (tree) |
---|---|
Time | 2017-05-18 19:52:34 |
Author | Kyotaro Horiguchi <horiguchi.kyotaro@lab....> |
Commiter | 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.
@@ -15,6 +15,7 @@ | ||
15 | 15 | #include "mb/pg_wchar.h" |
16 | 16 | #include "miscadmin.h" |
17 | 17 | #include "nodes/nodeFuncs.h" |
18 | +#include "nodes/params.h" | |
18 | 19 | #include "optimizer/clauses.h" |
19 | 20 | #include "optimizer/cost.h" |
20 | 21 | #include "optimizer/geqo.h" |
@@ -25,12 +26,14 @@ | ||
25 | 26 | #include "optimizer/planner.h" |
26 | 27 | #include "optimizer/prep.h" |
27 | 28 | #include "optimizer/restrictinfo.h" |
29 | +#include "parser/analyze.h" | |
28 | 30 | #include "parser/scansup.h" |
29 | 31 | #include "tcop/utility.h" |
30 | 32 | #include "utils/builtins.h" |
31 | 33 | #include "utils/lsyscache.h" |
32 | 34 | #include "utils/memutils.h" |
33 | 35 | #include "utils/rel.h" |
36 | +#include "utils/snapmgr.h" | |
34 | 37 | #include "utils/syscache.h" |
35 | 38 | #include "utils/resowner.h" |
36 | 39 |
@@ -94,10 +97,12 @@ PG_MODULE_MAGIC; | ||
94 | 97 | #define HINT_ARRAY_DEFAULT_INITSIZE 8 |
95 | 98 | |
96 | 99 | #define hint_ereport(str, detail) \ |
97 | - ereport(pg_hint_plan_message_level, \ | |
98 | - (errhidestmt(hidestmt), \ | |
99 | - errmsg("pg_hint_plan%s: hint syntax error at or near \"%s\"", qnostr, (str)), \ | |
100 | - errdetail detail)) | |
100 | + do { \ | |
101 | + ereport(pg_hint_plan_message_level, \ | |
102 | + (errmsg("pg_hint_plan%s: hint syntax error at or near \"%s\"", qnostr, (str)), \ | |
103 | + errdetail detail)); \ | |
104 | + msgqno = qno; \ | |
105 | + } while(0) | |
101 | 106 | |
102 | 107 | #define skip_space(str) \ |
103 | 108 | while (isspace(*str)) \ |
@@ -199,7 +204,9 @@ typedef enum HintStatus | ||
199 | 204 | (hint)->base.state == HINT_STATE_USED) |
200 | 205 | |
201 | 206 | static unsigned int qno = 0; |
207 | +static unsigned int msgqno = 0; | |
202 | 208 | static char qnostr[32]; |
209 | +static const char *current_hint_str = NULL; | |
203 | 210 | |
204 | 211 | /* common data for all hints. */ |
205 | 212 | struct Hint |
@@ -348,11 +355,7 @@ void _PG_fini(void); | ||
348 | 355 | static void push_hint(HintState *hstate); |
349 | 356 | static void pop_hint(void); |
350 | 357 | |
351 | -static void pg_hint_plan_ProcessUtility(Node *parsetree, | |
352 | - const char *queryString, | |
353 | - ProcessUtilityContext context, | |
354 | - ParamListInfo params, | |
355 | - DestReceiver *dest, char *completionTag); | |
358 | +static void pg_hint_plan_post_parse_analyze(ParseState *pstate, Query *query); | |
356 | 359 | static PlannedStmt *pg_hint_plan_planner(Query *parse, int cursorOptions, |
357 | 360 | ParamListInfo boundParams); |
358 | 361 | static void pg_hint_plan_get_relation_info(PlannerInfo *root, |
@@ -443,9 +446,6 @@ static int pg_hint_plan_message_level = INFO; | ||
443 | 446 | /* Default is off, to keep backward compatibility. */ |
444 | 447 | static bool pg_hint_plan_enable_hint_table = false; |
445 | 448 | |
446 | -/* Internal static variables. */ | |
447 | -static bool hidestmt = false; /* Allow or inhibit STATEMENT: output */ | |
448 | - | |
449 | 449 | static int plpgsql_recurse_level = 0; /* PLpgSQL recursion level */ |
450 | 450 | static int hint_inhibit_level = 0; /* Inhibit hinting if this is above 0 */ |
451 | 451 | /* (This could not be above 1) */ |
@@ -486,7 +486,7 @@ static const struct config_enum_entry parse_debug_level_options[] = { | ||
486 | 486 | }; |
487 | 487 | |
488 | 488 | /* Saved hook values in case of unload */ |
489 | -static ProcessUtility_hook_type prev_ProcessUtility = NULL; | |
489 | +static post_parse_analyze_hook_type prev_post_parse_analyze_hook = NULL; | |
490 | 490 | static planner_hook_type prev_planner = NULL; |
491 | 491 | static get_relation_info_hook_type prev_get_relation_info = NULL; |
492 | 492 | static join_search_hook_type prev_join_search = NULL; |
@@ -612,8 +612,8 @@ _PG_init(void) | ||
612 | 612 | NULL); |
613 | 613 | |
614 | 614 | /* Install hooks. */ |
615 | - prev_ProcessUtility = ProcessUtility_hook; | |
616 | - ProcessUtility_hook = pg_hint_plan_ProcessUtility; | |
615 | + prev_post_parse_analyze_hook = post_parse_analyze_hook; | |
616 | + post_parse_analyze_hook = pg_hint_plan_post_parse_analyze; | |
617 | 617 | prev_planner = planner_hook; |
618 | 618 | planner_hook = pg_hint_plan_planner; |
619 | 619 | prev_get_relation_info = get_relation_info_hook; |
@@ -638,7 +638,7 @@ _PG_fini(void) | ||
638 | 638 | PLpgSQL_plugin **var_ptr; |
639 | 639 | |
640 | 640 | /* Uninstall hooks. */ |
641 | - ProcessUtility_hook = prev_ProcessUtility; | |
641 | + post_parse_analyze_hook = prev_post_parse_analyze_hook; | |
642 | 642 | planner_hook = prev_planner; |
643 | 643 | get_relation_info_hook = prev_get_relation_info; |
644 | 644 | join_search_hook = prev_join_search; |
@@ -1145,8 +1145,8 @@ HintStateDump2(HintState *hstate) | ||
1145 | 1145 | appendStringInfoChar(&buf, '}'); |
1146 | 1146 | |
1147 | 1147 | ereport(pg_hint_plan_message_level, |
1148 | - (errhidestmt(true), | |
1149 | - errmsg("%s", buf.data))); | |
1148 | + (errmsg("%s", buf.data), | |
1149 | + errhidestmt(true))); | |
1150 | 1150 | |
1151 | 1151 | pfree(buf.data); |
1152 | 1152 | } |
@@ -1595,7 +1595,15 @@ get_hints_from_table(const char *client_query, const char *client_application) | ||
1595 | 1595 | |
1596 | 1596 | PG_TRY(); |
1597 | 1597 | { |
1598 | + bool snapshot_set = false; | |
1599 | + | |
1598 | 1600 | hint_inhibit_level++; |
1601 | + | |
1602 | + if (!ActiveSnapshotSet()) | |
1603 | + { | |
1604 | + PushActiveSnapshot(GetTransactionSnapshot()); | |
1605 | + snapshot_set = true; | |
1606 | + } | |
1599 | 1607 | |
1600 | 1608 | SPI_connect(); |
1601 | 1609 |
@@ -1632,7 +1640,10 @@ get_hints_from_table(const char *client_query, const char *client_application) | ||
1632 | 1640 | } |
1633 | 1641 | |
1634 | 1642 | SPI_finish(); |
1635 | - | |
1643 | + | |
1644 | + if (snapshot_set) | |
1645 | + PopActiveSnapshot(); | |
1646 | + | |
1636 | 1647 | hint_inhibit_level--; |
1637 | 1648 | } |
1638 | 1649 | PG_CATCH(); |
@@ -1646,30 +1657,87 @@ get_hints_from_table(const char *client_query, const char *client_application) | ||
1646 | 1657 | } |
1647 | 1658 | |
1648 | 1659 | /* |
1649 | - * Get client-supplied query string. | |
1660 | + * Get client-supplied query string. Addtion to that the jumbled query is | |
1661 | + * supplied if the caller requested. From the restriction of JumbleQuery, some | |
1662 | + * kind of query needs special amendments. Reutrns NULL if the current hint | |
1663 | + * string is still valid. | |
1650 | 1664 | */ |
1651 | 1665 | static const char * |
1652 | -get_query_string(void) | |
1666 | +get_query_string(ParseState *pstate, Query *query, Query **jumblequery) | |
1653 | 1667 | { |
1654 | - const char *p; | |
1668 | + const char *p = debug_query_string; | |
1655 | 1669 | |
1656 | - if (plpgsql_recurse_level > 0) | |
1657 | - { | |
1658 | - /* | |
1659 | - * This is quite ugly but this is the only point I could find where | |
1660 | - * we can get the query string. | |
1661 | - */ | |
1662 | - p = (char*)error_context_stack->arg; | |
1663 | - } | |
1664 | - else if (stmt_name) | |
1670 | + if (jumblequery != NULL) | |
1671 | + *jumblequery = query; | |
1672 | + | |
1673 | + Assert(plpgsql_recurse_level == 0); | |
1674 | + | |
1675 | + if (query->commandType == CMD_UTILITY) | |
1665 | 1676 | { |
1666 | - PreparedStatement *entry; | |
1677 | + Query *target_query = query; | |
1678 | + | |
1679 | + /* Use the target query if EXPLAIN */ | |
1680 | + if (IsA(query->utilityStmt, ExplainStmt)) | |
1681 | + { | |
1682 | + ExplainStmt *stmt = (ExplainStmt *)(query->utilityStmt); | |
1683 | + Assert(IsA(stmt->query, Query)); | |
1684 | + target_query = (Query *)stmt->query; | |
1685 | + | |
1686 | + if (target_query->commandType == CMD_UTILITY && | |
1687 | + target_query->utilityStmt != NULL) | |
1688 | + target_query = (Query *)target_query->utilityStmt; | |
1689 | + | |
1690 | + if (jumblequery) | |
1691 | + *jumblequery = target_query; | |
1692 | + } | |
1693 | + | |
1694 | + if (IsA(target_query, CreateTableAsStmt)) | |
1695 | + { | |
1696 | + /* | |
1697 | + * Use the the body query for CREATE AS. The Query for jumble also | |
1698 | + * replaced with the corresponding one. | |
1699 | + */ | |
1700 | + CreateTableAsStmt *stmt = (CreateTableAsStmt *) target_query; | |
1701 | + PreparedStatement *entry; | |
1702 | + Query *ent_query; | |
1703 | + | |
1704 | + Assert(IsA(stmt->query, Query)); | |
1705 | + target_query = (Query *) stmt->query; | |
1667 | 1706 | |
1668 | - entry = FetchPreparedStatement(stmt_name, true); | |
1669 | - p = entry->plansource->query_string; | |
1707 | + if (target_query->commandType == CMD_UTILITY && | |
1708 | + IsA(target_query->utilityStmt, ExecuteStmt)) | |
1709 | + { | |
1710 | + ExecuteStmt *estmt = (ExecuteStmt *) target_query->utilityStmt; | |
1711 | + entry = FetchPreparedStatement(estmt->name, true); | |
1712 | + p = entry->plansource->query_string; | |
1713 | + ent_query = (Query *) linitial (entry->plansource->query_list); | |
1714 | + Assert(IsA(ent_query, Query)); | |
1715 | + if (jumblequery) | |
1716 | + *jumblequery = ent_query; | |
1717 | + } | |
1718 | + } | |
1719 | + else | |
1720 | + if (IsA(target_query, ExecuteStmt)) | |
1721 | + { | |
1722 | + /* | |
1723 | + * Use the prepared query for EXECUTE. The Query for jumble also | |
1724 | + * replaced with the corresponding one. | |
1725 | + */ | |
1726 | + ExecuteStmt *stmt = (ExecuteStmt *)target_query; | |
1727 | + PreparedStatement *entry; | |
1728 | + Query *ent_query; | |
1729 | + | |
1730 | + entry = FetchPreparedStatement(stmt->name, true); | |
1731 | + p = entry->plansource->query_string; | |
1732 | + ent_query = (Query *) linitial (entry->plansource->query_list); | |
1733 | + Assert(IsA(ent_query, Query)); | |
1734 | + if (jumblequery) | |
1735 | + *jumblequery = ent_query; | |
1736 | + } | |
1670 | 1737 | } |
1671 | - else | |
1672 | - p = debug_query_string; | |
1738 | + /* Return NULL if the pstate is not identical to the top-level query */ | |
1739 | + else if (strcmp(pstate->p_sourcetext, p) != 0) | |
1740 | + p = NULL; | |
1673 | 1741 | |
1674 | 1742 | return p; |
1675 | 1743 | } |
@@ -2235,10 +2303,10 @@ set_config_option_wrapper(const char *name, const char *value, | ||
2235 | 2303 | |
2236 | 2304 | ereport(elevel, |
2237 | 2305 | (errcode(errdata->sqlerrcode), |
2238 | - errhidestmt(hidestmt), | |
2239 | 2306 | errmsg("%s", errdata->message), |
2240 | 2307 | errdata->detail ? errdetail("%s", errdata->detail) : 0, |
2241 | 2308 | errdata->hint ? errhint("%s", errdata->hint) : 0)); |
2309 | + msgqno = qno; | |
2242 | 2310 | FreeErrorData(errdata); |
2243 | 2311 | } |
2244 | 2312 | PG_END_TRY(); |
@@ -2316,132 +2384,6 @@ set_join_config_options(unsigned char enforce_mask, GucContext context) | ||
2316 | 2384 | } |
2317 | 2385 | |
2318 | 2386 | /* |
2319 | - * pg_hint_plan hook functions | |
2320 | - */ | |
2321 | - | |
2322 | -static void | |
2323 | -pg_hint_plan_ProcessUtility(Node *parsetree, const char *queryString, | |
2324 | - ProcessUtilityContext context, | |
2325 | - ParamListInfo params, | |
2326 | - DestReceiver *dest, char *completionTag) | |
2327 | -{ | |
2328 | - Node *node; | |
2329 | - | |
2330 | - /* | |
2331 | - * Use standard planner if pg_hint_plan is disabled or current nesting | |
2332 | - * depth is nesting depth of SPI calls. | |
2333 | - */ | |
2334 | - if (!pg_hint_plan_enable_hint || hint_inhibit_level > 0) | |
2335 | - { | |
2336 | - if (debug_level > 1) | |
2337 | - ereport(pg_hint_plan_message_level, | |
2338 | - (errmsg ("pg_hint_plan: ProcessUtility:" | |
2339 | - " pg_hint_plan.enable_hint = off"))); | |
2340 | - if (prev_ProcessUtility) | |
2341 | - (*prev_ProcessUtility) (parsetree, queryString, | |
2342 | - context, params, | |
2343 | - dest, completionTag); | |
2344 | - else | |
2345 | - standard_ProcessUtility(parsetree, queryString, | |
2346 | - context, params, | |
2347 | - dest, completionTag); | |
2348 | - return; | |
2349 | - } | |
2350 | - | |
2351 | - node = parsetree; | |
2352 | - if (IsA(node, ExplainStmt)) | |
2353 | - { | |
2354 | - /* | |
2355 | - * Draw out parse tree of actual query from Query struct of EXPLAIN | |
2356 | - * statement. | |
2357 | - */ | |
2358 | - ExplainStmt *stmt; | |
2359 | - Query *query; | |
2360 | - | |
2361 | - stmt = (ExplainStmt *) node; | |
2362 | - | |
2363 | - Assert(IsA(stmt->query, Query)); | |
2364 | - query = (Query *) stmt->query; | |
2365 | - | |
2366 | - if (query->commandType == CMD_UTILITY && query->utilityStmt != NULL) | |
2367 | - node = query->utilityStmt; | |
2368 | - } | |
2369 | - | |
2370 | - /* | |
2371 | - * If the query was a EXECUTE or CREATE TABLE AS EXECUTE, get query string | |
2372 | - * specified to preceding PREPARE command to use it as source of hints. | |
2373 | - */ | |
2374 | - if (IsA(node, ExecuteStmt)) | |
2375 | - { | |
2376 | - ExecuteStmt *stmt; | |
2377 | - | |
2378 | - stmt = (ExecuteStmt *) node; | |
2379 | - stmt_name = stmt->name; | |
2380 | - } | |
2381 | - | |
2382 | - /* | |
2383 | - * CREATE AS EXECUTE behavior has changed since 9.2, so we must handle it | |
2384 | - * specially here. | |
2385 | - */ | |
2386 | - if (IsA(node, CreateTableAsStmt)) | |
2387 | - { | |
2388 | - CreateTableAsStmt *stmt; | |
2389 | - Query *query; | |
2390 | - | |
2391 | - stmt = (CreateTableAsStmt *) node; | |
2392 | - Assert(IsA(stmt->query, Query)); | |
2393 | - query = (Query *) stmt->query; | |
2394 | - | |
2395 | - if (query->commandType == CMD_UTILITY && | |
2396 | - IsA(query->utilityStmt, ExecuteStmt)) | |
2397 | - { | |
2398 | - ExecuteStmt *estmt = (ExecuteStmt *) query->utilityStmt; | |
2399 | - stmt_name = estmt->name; | |
2400 | - } | |
2401 | - } | |
2402 | - | |
2403 | - if (stmt_name) | |
2404 | - { | |
2405 | - if (debug_level > 1) | |
2406 | - ereport(pg_hint_plan_message_level, | |
2407 | - (errmsg ("pg_hint_plan: ProcessUtility:" | |
2408 | - " stmt_name = \"%s\", statement=\"%s\"", | |
2409 | - stmt_name, queryString))); | |
2410 | - | |
2411 | - PG_TRY(); | |
2412 | - { | |
2413 | - if (prev_ProcessUtility) | |
2414 | - (*prev_ProcessUtility) (parsetree, queryString, | |
2415 | - context, params, | |
2416 | - dest, completionTag); | |
2417 | - else | |
2418 | - standard_ProcessUtility(parsetree, queryString, | |
2419 | - context, params, | |
2420 | - dest, completionTag); | |
2421 | - } | |
2422 | - PG_CATCH(); | |
2423 | - { | |
2424 | - stmt_name = NULL; | |
2425 | - PG_RE_THROW(); | |
2426 | - } | |
2427 | - PG_END_TRY(); | |
2428 | - | |
2429 | - stmt_name = NULL; | |
2430 | - | |
2431 | - return; | |
2432 | - } | |
2433 | - | |
2434 | - if (prev_ProcessUtility) | |
2435 | - (*prev_ProcessUtility) (parsetree, queryString, | |
2436 | - context, params, | |
2437 | - dest, completionTag); | |
2438 | - else | |
2439 | - standard_ProcessUtility(parsetree, queryString, | |
2440 | - context, params, | |
2441 | - dest, completionTag); | |
2442 | -} | |
2443 | - | |
2444 | -/* | |
2445 | 2387 | * Push a hint into hint stack which is implemented with List struct. Head of |
2446 | 2388 | * list is top of stack. |
2447 | 2389 | */ |
@@ -2475,24 +2417,178 @@ pop_hint(void) | ||
2475 | 2417 | current_hint = (HintState *) lfirst(list_head(HintStateStack)); |
2476 | 2418 | } |
2477 | 2419 | |
2420 | +/* | |
2421 | + * Retrieve and store a hint string from given query or from the hint table. | |
2422 | + * If we are using the hint table, the query string is needed to be normalized. | |
2423 | + * However, ParseState, which is not available in planner_hook, is required to | |
2424 | + * check if the query tree (Query) is surely corresponding to the target query. | |
2425 | + */ | |
2426 | +static void | |
2427 | +pg_hint_plan_post_parse_analyze(ParseState *pstate, Query *query) | |
2428 | +{ | |
2429 | + const char *query_str; | |
2430 | + MemoryContext oldcontext; | |
2431 | + | |
2432 | + if (prev_post_parse_analyze_hook) | |
2433 | + prev_post_parse_analyze_hook(pstate, query); | |
2434 | + | |
2435 | + /* do nothing under hint table search */ | |
2436 | + if (hint_inhibit_level > 0) | |
2437 | + return; | |
2438 | + | |
2439 | + if (!pg_hint_plan_enable_hint) | |
2440 | + { | |
2441 | + if (current_hint_str) | |
2442 | + { | |
2443 | + pfree((void *)current_hint_str); | |
2444 | + current_hint_str = NULL; | |
2445 | + } | |
2446 | + return; | |
2447 | + } | |
2448 | + | |
2449 | + /* increment the query number */ | |
2450 | + qnostr[0] = 0; | |
2451 | + if (debug_level > 1) | |
2452 | + snprintf(qnostr, sizeof(qnostr), "[qno=0x%x]", qno++); | |
2453 | + qno++; | |
2454 | + | |
2455 | + /* search the hint table for a hint if requested */ | |
2456 | + if (pg_hint_plan_enable_hint_table) | |
2457 | + { | |
2458 | + int query_len; | |
2459 | + pgssJumbleState jstate; | |
2460 | + Query *jumblequery; | |
2461 | + char *normalized_query = NULL; | |
2462 | + | |
2463 | + query_str = get_query_string(pstate, query, &jumblequery); | |
2464 | + | |
2465 | + /* If this query is not for hint, just return */ | |
2466 | + if (!query_str) | |
2467 | + return; | |
2468 | + | |
2469 | + /* clear the previous hint string */ | |
2470 | + if (current_hint_str) | |
2471 | + { | |
2472 | + pfree((void *)current_hint_str); | |
2473 | + current_hint_str = NULL; | |
2474 | + } | |
2475 | + | |
2476 | + if (jumblequery) | |
2477 | + { | |
2478 | + /* | |
2479 | + * XXX: normalizing code is copied from pg_stat_statements.c, so be | |
2480 | + * careful to PostgreSQL's version up. | |
2481 | + */ | |
2482 | + jstate.jumble = (unsigned char *) palloc(JUMBLE_SIZE); | |
2483 | + jstate.jumble_len = 0; | |
2484 | + jstate.clocations_buf_size = 32; | |
2485 | + jstate.clocations = (pgssLocationLen *) | |
2486 | + palloc(jstate.clocations_buf_size * sizeof(pgssLocationLen)); | |
2487 | + jstate.clocations_count = 0; | |
2488 | + | |
2489 | + JumbleQuery(&jstate, jumblequery); | |
2490 | + | |
2491 | + /* | |
2492 | + * Normalize the query string by replacing constants with '?' | |
2493 | + */ | |
2494 | + /* | |
2495 | + * Search hint string which is stored keyed by query string | |
2496 | + * and application name. The query string is normalized to allow | |
2497 | + * fuzzy matching. | |
2498 | + * | |
2499 | + * Adding 1 byte to query_len ensures that the returned string has | |
2500 | + * a terminating NULL. | |
2501 | + */ | |
2502 | + query_len = strlen(query_str) + 1; | |
2503 | + normalized_query = | |
2504 | + generate_normalized_query(&jstate, query_str, | |
2505 | + &query_len, | |
2506 | + GetDatabaseEncoding()); | |
2507 | + | |
2508 | + /* | |
2509 | + * find a hint for the normalized query. the result should be in | |
2510 | + * TopMemoryContext | |
2511 | + */ | |
2512 | + oldcontext = MemoryContextSwitchTo(TopMemoryContext); | |
2513 | + current_hint_str = | |
2514 | + get_hints_from_table(normalized_query, application_name); | |
2515 | + MemoryContextSwitchTo(oldcontext); | |
2516 | + | |
2517 | + if (debug_level > 1) | |
2518 | + { | |
2519 | + if (current_hint_str) | |
2520 | + ereport(pg_hint_plan_message_level, | |
2521 | + (errmsg("pg_hint_plan[qno=0x%x]: " | |
2522 | + "post_parse_analyze_hook: " | |
2523 | + "hints from table: \"%s\": " | |
2524 | + "normalized_query=\"%s\", " | |
2525 | + "application name =\"%s\"", | |
2526 | + qno, current_hint_str, | |
2527 | + normalized_query, application_name), | |
2528 | + errhidestmt(msgqno != qno))); | |
2529 | + else | |
2530 | + ereport(pg_hint_plan_message_level, | |
2531 | + (errmsg("pg_hint_plan[qno=0x%x]: " | |
2532 | + "no match found in table: " | |
2533 | + "application name = \"%s\", " | |
2534 | + "normalized_query=\"%s\"", | |
2535 | + qno, application_name, | |
2536 | + normalized_query), | |
2537 | + errhidestmt(msgqno != qno))); | |
2538 | + | |
2539 | + msgqno = qno; | |
2540 | + } | |
2541 | + } | |
2542 | + | |
2543 | + /* retrun if we have hint here*/ | |
2544 | + if (current_hint_str) | |
2545 | + return; | |
2546 | + } | |
2547 | + else | |
2548 | + query_str = get_query_string(pstate, query, NULL); | |
2549 | + | |
2550 | + if (query_str) | |
2551 | + { | |
2552 | + /* | |
2553 | + * get hints from the comment. However we may have the same query | |
2554 | + * string with the previous call, but just retrieving hints is expected | |
2555 | + * to be faster than checking for identicalness before retrieval. | |
2556 | + */ | |
2557 | + if (current_hint_str) | |
2558 | + pfree((void *)current_hint_str); | |
2559 | + | |
2560 | + oldcontext = MemoryContextSwitchTo(TopMemoryContext); | |
2561 | + current_hint_str = get_hints_from_comment(query_str); | |
2562 | + MemoryContextSwitchTo(oldcontext); | |
2563 | + } | |
2564 | + | |
2565 | + if (debug_level > 1) | |
2566 | + { | |
2567 | + if (debug_level == 1 && | |
2568 | + (stmt_name || strcmp(query_str, debug_query_string))) | |
2569 | + ereport(pg_hint_plan_message_level, | |
2570 | + (errmsg("hints in comment=\"%s\"", | |
2571 | + current_hint_str ? current_hint_str : "(none)"), | |
2572 | + errhidestmt(msgqno != qno))); | |
2573 | + else | |
2574 | + ereport(pg_hint_plan_message_level, | |
2575 | + (errmsg("hints in comment=\"%s\", stmt=\"%s\", query=\"%s\", debug_query_string=\"%s\"", | |
2576 | + current_hint_str ? current_hint_str : "(none)", | |
2577 | + stmt_name, query_str, debug_query_string), | |
2578 | + errhidestmt(msgqno != qno))); | |
2579 | + msgqno = qno; | |
2580 | + } | |
2581 | +} | |
2582 | + | |
2583 | +/* | |
2584 | + * Read and set up hint information | |
2585 | + */ | |
2478 | 2586 | static PlannedStmt * |
2479 | 2587 | pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) |
2480 | 2588 | { |
2481 | - const char *hints = NULL; | |
2482 | - const char *query; | |
2483 | - char *norm_query; | |
2484 | - pgssJumbleState jstate; | |
2485 | - int query_len; | |
2486 | 2589 | int save_nestlevel; |
2487 | 2590 | PlannedStmt *result; |
2488 | 2591 | HintState *hstate; |
2489 | - char msgstr[1024]; | |
2490 | - | |
2491 | - qnostr[0] = 0; | |
2492 | - strcpy(msgstr, ""); | |
2493 | - if (debug_level > 1) | |
2494 | - snprintf(qnostr, sizeof(qnostr), "[qno=0x%x]", qno++); | |
2495 | - hidestmt = false; | |
2496 | 2592 | |
2497 | 2593 | /* |
2498 | 2594 | * Use standard planner if pg_hint_plan is disabled or current nesting |
@@ -2502,103 +2598,44 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) | ||
2502 | 2598 | if (!pg_hint_plan_enable_hint || hint_inhibit_level > 0) |
2503 | 2599 | { |
2504 | 2600 | if (debug_level > 1) |
2505 | - elog(pg_hint_plan_message_level, | |
2506 | - "pg_hint_plan%s: planner: enable_hint=%d," | |
2507 | - " hint_inhibit_level=%d", | |
2508 | - qnostr, pg_hint_plan_enable_hint, hint_inhibit_level); | |
2509 | - hidestmt = true; | |
2601 | + ereport(pg_hint_plan_message_level, | |
2602 | + (errmsg ("pg_hint_plan%s: planner: enable_hint=%d," | |
2603 | + " hint_inhibit_level=%d", | |
2604 | + qnostr, pg_hint_plan_enable_hint, | |
2605 | + hint_inhibit_level), | |
2606 | + errhidestmt(msgqno != qno))); | |
2607 | + msgqno = qno; | |
2510 | 2608 | |
2511 | 2609 | goto standard_planner_proc; |
2512 | 2610 | } |
2513 | 2611 | |
2514 | - /* Create hint struct from client-supplied query string. */ | |
2515 | - query = get_query_string(); | |
2516 | - | |
2517 | 2612 | /* |
2518 | - * Create hintstate from hint specified for the query, if any. | |
2519 | - * | |
2520 | - * First we lookup hint in pg_hint.hints table by normalized query string, | |
2521 | - * unless pg_hint_plan.enable_hint_table is OFF. | |
2522 | - * This parameter provides option to avoid overhead of table lookup during | |
2523 | - * planning. | |
2524 | - * | |
2525 | - * If no hint was found, then we try to get hint from special query comment. | |
2613 | + * Support for nested plpgsql functions. This is quite ugly but this is the | |
2614 | + * only point I could find where I can get the query string. | |
2526 | 2615 | */ |
2527 | - if (pg_hint_plan_enable_hint_table) | |
2528 | - { | |
2529 | - /* | |
2530 | - * Search hint information which is stored for the query and the | |
2531 | - * application. Query string is normalized before using in condition | |
2532 | - * in order to allow fuzzy matching. | |
2533 | - * | |
2534 | - * XXX: normalizing code is copied from pg_stat_statements.c, so be | |
2535 | - * careful when supporting PostgreSQL's version up. | |
2536 | - */ | |
2537 | - jstate.jumble = (unsigned char *) palloc(JUMBLE_SIZE); | |
2538 | - jstate.jumble_len = 0; | |
2539 | - jstate.clocations_buf_size = 32; | |
2540 | - jstate.clocations = (pgssLocationLen *) | |
2541 | - palloc(jstate.clocations_buf_size * sizeof(pgssLocationLen)); | |
2542 | - jstate.clocations_count = 0; | |
2543 | - JumbleQuery(&jstate, parse); | |
2544 | - /* | |
2545 | - * generate_normalized_query() copies exact given query_len bytes, so we | |
2546 | - * add 1 byte for null-termination here. As comments on | |
2547 | - * generate_normalized_query says, generate_normalized_query doesn't | |
2548 | - * take care of null-terminate, but additional 1 byte ensures that '\0' | |
2549 | - * byte in the source buffer to be copied into norm_query. | |
2550 | - */ | |
2551 | - query_len = strlen(query) + 1; | |
2552 | - norm_query = generate_normalized_query(&jstate, | |
2553 | - query, | |
2554 | - &query_len, | |
2555 | - GetDatabaseEncoding()); | |
2556 | - hints = get_hints_from_table(norm_query, application_name); | |
2557 | - if (debug_level > 1) | |
2558 | - { | |
2559 | - if (hints) | |
2560 | - snprintf(msgstr, 1024, "hints from table: \"%s\":" | |
2561 | - " normalzed_query=\"%s\", application name =\"%s\"", | |
2562 | - hints, norm_query, application_name); | |
2563 | - else | |
2564 | - { | |
2565 | - ereport(pg_hint_plan_message_level, | |
2566 | - (errhidestmt(hidestmt), | |
2567 | - errmsg("pg_hint_plan%s:" | |
2568 | - " no match found in table:" | |
2569 | - " application name = \"%s\"," | |
2570 | - " normalzed_query=\"%s\"", | |
2571 | - qnostr, application_name, norm_query))); | |
2572 | - hidestmt = true; | |
2573 | - } | |
2574 | - } | |
2575 | - } | |
2576 | - if (hints == NULL) | |
2616 | + if (plpgsql_recurse_level > 0) | |
2577 | 2617 | { |
2578 | - hints = get_hints_from_comment(query); | |
2618 | + MemoryContext oldcontext; | |
2579 | 2619 | |
2580 | - if (debug_level > 1) | |
2581 | - { | |
2582 | - snprintf(msgstr, 1024, "hints in comment=\"%s\"", | |
2583 | - hints ? hints : "(none)"); | |
2584 | - if (debug_level > 2 || | |
2585 | - stmt_name || strcmp(query, debug_query_string)) | |
2586 | - snprintf(msgstr + strlen(msgstr), 1024- strlen(msgstr), | |
2587 | - ", stmt=\"%s\", query=\"%s\", debug_query_string=\"%s\"", | |
2588 | - stmt_name, query, debug_query_string); | |
2589 | - } | |
2620 | + if (current_hint_str) | |
2621 | + pfree((void *)current_hint_str); | |
2622 | + | |
2623 | + oldcontext = MemoryContextSwitchTo(TopMemoryContext); | |
2624 | + current_hint_str = | |
2625 | + get_hints_from_comment((char *)error_context_stack->arg); | |
2626 | + MemoryContextSwitchTo(oldcontext); | |
2590 | 2627 | } |
2591 | 2628 | |
2592 | - hstate = create_hintstate(parse, hints); | |
2629 | + if (!current_hint_str) | |
2630 | + goto standard_planner_proc; | |
2593 | 2631 | |
2594 | - /* | |
2595 | - * Use standard planner if the statement has not valid hint. Other hook | |
2596 | - * functions try to change plan with current_hint if any, so set it to | |
2597 | - * NULL. | |
2598 | - */ | |
2632 | + /* parse the hint into hint state struct */ | |
2633 | + hstate = create_hintstate(parse, pstrdup(current_hint_str)); | |
2634 | + | |
2635 | + /* run standard planner if the statement has not valid hint */ | |
2599 | 2636 | if (!hstate) |
2600 | 2637 | goto standard_planner_proc; |
2601 | - | |
2638 | + | |
2602 | 2639 | /* |
2603 | 2640 | * Push new hint struct to the hint stack to disable previous hint context. |
2604 | 2641 | */ |
@@ -2629,10 +2666,9 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) | ||
2629 | 2666 | if (debug_level > 1) |
2630 | 2667 | { |
2631 | 2668 | ereport(pg_hint_plan_message_level, |
2632 | - (errhidestmt(hidestmt), | |
2633 | - errmsg("pg_hint_plan%s: planner: %s", | |
2634 | - qnostr, msgstr))); | |
2635 | - hidestmt = true; | |
2669 | + (errhidestmt(msgqno != qno), | |
2670 | + errmsg("pg_hint_plan%s: planner", qnostr))); | |
2671 | + msgqno = qno; | |
2636 | 2672 | } |
2637 | 2673 | |
2638 | 2674 | /* |
@@ -2677,10 +2713,10 @@ standard_planner_proc: | ||
2677 | 2713 | if (debug_level > 1) |
2678 | 2714 | { |
2679 | 2715 | ereport(pg_hint_plan_message_level, |
2680 | - (errhidestmt(hidestmt), | |
2681 | - errmsg("pg_hint_plan%s: planner: no valid hint (%s)", | |
2682 | - qnostr, msgstr))); | |
2683 | - hidestmt = true; | |
2716 | + (errhidestmt(msgqno != qno), | |
2717 | + errmsg("pg_hint_plan%s: planner: no valid hint", | |
2718 | + qnostr))); | |
2719 | + msgqno = qno; | |
2684 | 2720 | } |
2685 | 2721 | current_hint = NULL; |
2686 | 2722 | if (prev_planner) |