firtst release
Revision | 3f6c9838430bc6899644bb4bd3c991b605b144ad (tree) |
---|---|
Time | 2017-05-18 19:01:20 |
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 |
@@ -84,10 +87,12 @@ PG_MODULE_MAGIC; | ||
84 | 87 | #define HINT_ARRAY_DEFAULT_INITSIZE 8 |
85 | 88 | |
86 | 89 | #define hint_ereport(str, detail) \ |
87 | - ereport(pg_hint_plan_message_level, \ | |
88 | - (errhidestmt(hidestmt), \ | |
89 | - errmsg("pg_hint_plan%s: hint syntax error at or near \"%s\"", qnostr, (str)), \ | |
90 | - errdetail detail)) | |
90 | + do { \ | |
91 | + ereport(pg_hint_plan_message_level, \ | |
92 | + (errmsg("pg_hint_plan%s: hint syntax error at or near \"%s\"", qnostr, (str)), \ | |
93 | + errdetail detail)); \ | |
94 | + msgqno = qno; \ | |
95 | + } while(0) | |
91 | 96 | |
92 | 97 | #define skip_space(str) \ |
93 | 98 | while (isspace(*str)) \ |
@@ -189,7 +194,9 @@ typedef enum HintStatus | ||
189 | 194 | (hint)->base.state == HINT_STATE_USED) |
190 | 195 | |
191 | 196 | static unsigned int qno = 0; |
197 | +static unsigned int msgqno = 0; | |
192 | 198 | static char qnostr[32]; |
199 | +static const char *current_hint_str = NULL; | |
193 | 200 | |
194 | 201 | /* common data for all hints. */ |
195 | 202 | struct Hint |
@@ -338,11 +345,7 @@ void _PG_fini(void); | ||
338 | 345 | static void push_hint(HintState *hstate); |
339 | 346 | static void pop_hint(void); |
340 | 347 | |
341 | -static void pg_hint_plan_ProcessUtility(Node *parsetree, | |
342 | - const char *queryString, | |
343 | - ProcessUtilityContext context, | |
344 | - ParamListInfo params, | |
345 | - DestReceiver *dest, char *completionTag); | |
348 | +static void pg_hint_plan_post_parse_analyze(ParseState *pstate, Query *query); | |
346 | 349 | static PlannedStmt *pg_hint_plan_planner(Query *parse, int cursorOptions, |
347 | 350 | ParamListInfo boundParams); |
348 | 351 | static void pg_hint_plan_get_relation_info(PlannerInfo *root, |
@@ -433,9 +436,6 @@ static int pg_hint_plan_message_level = INFO; | ||
433 | 436 | /* Default is off, to keep backward compatibility. */ |
434 | 437 | static bool pg_hint_plan_enable_hint_table = false; |
435 | 438 | |
436 | -/* Internal static variables. */ | |
437 | -static bool hidestmt = false; /* Allow or inhibit STATEMENT: output */ | |
438 | - | |
439 | 439 | static int plpgsql_recurse_level = 0; /* PLpgSQL recursion level */ |
440 | 440 | static int hint_inhibit_level = 0; /* Inhibit hinting if this is above 0 */ |
441 | 441 | /* (This could not be above 1) */ |
@@ -476,7 +476,7 @@ static const struct config_enum_entry parse_debug_level_options[] = { | ||
476 | 476 | }; |
477 | 477 | |
478 | 478 | /* Saved hook values in case of unload */ |
479 | -static ProcessUtility_hook_type prev_ProcessUtility = NULL; | |
479 | +static post_parse_analyze_hook_type prev_post_parse_analyze_hook = NULL; | |
480 | 480 | static planner_hook_type prev_planner = NULL; |
481 | 481 | static get_relation_info_hook_type prev_get_relation_info = NULL; |
482 | 482 | static join_search_hook_type prev_join_search = NULL; |
@@ -602,8 +602,8 @@ _PG_init(void) | ||
602 | 602 | NULL); |
603 | 603 | |
604 | 604 | /* Install hooks. */ |
605 | - prev_ProcessUtility = ProcessUtility_hook; | |
606 | - ProcessUtility_hook = pg_hint_plan_ProcessUtility; | |
605 | + prev_post_parse_analyze_hook = post_parse_analyze_hook; | |
606 | + post_parse_analyze_hook = pg_hint_plan_post_parse_analyze; | |
607 | 607 | prev_planner = planner_hook; |
608 | 608 | planner_hook = pg_hint_plan_planner; |
609 | 609 | prev_get_relation_info = get_relation_info_hook; |
@@ -628,7 +628,7 @@ _PG_fini(void) | ||
628 | 628 | PLpgSQL_plugin **var_ptr; |
629 | 629 | |
630 | 630 | /* Uninstall hooks. */ |
631 | - ProcessUtility_hook = prev_ProcessUtility; | |
631 | + post_parse_analyze_hook = prev_post_parse_analyze_hook; | |
632 | 632 | planner_hook = prev_planner; |
633 | 633 | get_relation_info_hook = prev_get_relation_info; |
634 | 634 | join_search_hook = prev_join_search; |
@@ -1135,8 +1135,8 @@ HintStateDump2(HintState *hstate) | ||
1135 | 1135 | appendStringInfoChar(&buf, '}'); |
1136 | 1136 | |
1137 | 1137 | ereport(pg_hint_plan_message_level, |
1138 | - (errhidestmt(true), | |
1139 | - errmsg("%s", buf.data))); | |
1138 | + (errmsg("%s", buf.data), | |
1139 | + errhidestmt(true))); | |
1140 | 1140 | |
1141 | 1141 | pfree(buf.data); |
1142 | 1142 | } |
@@ -1585,7 +1585,15 @@ get_hints_from_table(const char *client_query, const char *client_application) | ||
1585 | 1585 | |
1586 | 1586 | PG_TRY(); |
1587 | 1587 | { |
1588 | + bool snapshot_set = false; | |
1589 | + | |
1588 | 1590 | hint_inhibit_level++; |
1591 | + | |
1592 | + if (!ActiveSnapshotSet()) | |
1593 | + { | |
1594 | + PushActiveSnapshot(GetTransactionSnapshot()); | |
1595 | + snapshot_set = true; | |
1596 | + } | |
1589 | 1597 | |
1590 | 1598 | SPI_connect(); |
1591 | 1599 |
@@ -1622,7 +1630,10 @@ get_hints_from_table(const char *client_query, const char *client_application) | ||
1622 | 1630 | } |
1623 | 1631 | |
1624 | 1632 | SPI_finish(); |
1625 | - | |
1633 | + | |
1634 | + if (snapshot_set) | |
1635 | + PopActiveSnapshot(); | |
1636 | + | |
1626 | 1637 | hint_inhibit_level--; |
1627 | 1638 | } |
1628 | 1639 | PG_CATCH(); |
@@ -1639,27 +1650,81 @@ get_hints_from_table(const char *client_query, const char *client_application) | ||
1639 | 1650 | * Get client-supplied query string. |
1640 | 1651 | */ |
1641 | 1652 | static const char * |
1642 | -get_query_string(void) | |
1653 | +get_query_string(ParseState *pstate, Query *query, Query **jumblequery) | |
1643 | 1654 | { |
1644 | - const char *p; | |
1655 | + const char *p = debug_query_string; | |
1645 | 1656 | |
1646 | - if (plpgsql_recurse_level > 0) | |
1647 | - { | |
1648 | - /* | |
1649 | - * This is quite ugly but this is the only point I could find where | |
1650 | - * we can get the query string. | |
1651 | - */ | |
1652 | - p = (char*)error_context_stack->arg; | |
1653 | - } | |
1654 | - else if (stmt_name) | |
1657 | + if (jumblequery != NULL) | |
1658 | + *jumblequery = query; | |
1659 | + | |
1660 | + Assert(plpgsql_recurse_level == 0); | |
1661 | + | |
1662 | + if (query->commandType == CMD_UTILITY) | |
1655 | 1663 | { |
1656 | - PreparedStatement *entry; | |
1664 | + Query *target_query = query; | |
1665 | + | |
1666 | + /* Use the target query if EXPLAIN */ | |
1667 | + if (IsA(query->utilityStmt, ExplainStmt)) | |
1668 | + { | |
1669 | + ExplainStmt *stmt = (ExplainStmt *)(query->utilityStmt); | |
1670 | + Assert(IsA(stmt->query, Query)); | |
1671 | + target_query = (Query *)stmt->query; | |
1672 | + | |
1673 | + if (target_query->commandType == CMD_UTILITY && | |
1674 | + target_query->utilityStmt != NULL) | |
1675 | + target_query = (Query *)target_query->utilityStmt; | |
1676 | + | |
1677 | + if (jumblequery) | |
1678 | + *jumblequery = target_query; | |
1679 | + } | |
1680 | + | |
1681 | + if (IsA(target_query, CreateTableAsStmt)) | |
1682 | + { | |
1683 | + /* | |
1684 | + * Use the the body query for CREATE AS. The Query for jumble also | |
1685 | + * replaced with the corresponding one. | |
1686 | + */ | |
1687 | + CreateTableAsStmt *stmt = (CreateTableAsStmt *) target_query; | |
1688 | + PreparedStatement *entry; | |
1689 | + Query *ent_query; | |
1690 | + | |
1691 | + Assert(IsA(stmt->query, Query)); | |
1692 | + target_query = (Query *) stmt->query; | |
1657 | 1693 | |
1658 | - entry = FetchPreparedStatement(stmt_name, true); | |
1659 | - p = entry->plansource->query_string; | |
1694 | + if (target_query->commandType == CMD_UTILITY && | |
1695 | + IsA(target_query->utilityStmt, ExecuteStmt)) | |
1696 | + { | |
1697 | + ExecuteStmt *estmt = (ExecuteStmt *) target_query->utilityStmt; | |
1698 | + entry = FetchPreparedStatement(estmt->name, true); | |
1699 | + p = entry->plansource->query_string; | |
1700 | + ent_query = (Query *) linitial (entry->plansource->query_list); | |
1701 | + Assert(IsA(ent_query, Query)); | |
1702 | + if (jumblequery) | |
1703 | + *jumblequery = ent_query; | |
1704 | + } | |
1705 | + } | |
1706 | + else | |
1707 | + if (IsA(target_query, ExecuteStmt)) | |
1708 | + { | |
1709 | + /* | |
1710 | + * Use the prepared query for EXECUTE. The Query for jumble also | |
1711 | + * replaced with the corresponding one. | |
1712 | + */ | |
1713 | + ExecuteStmt *stmt = (ExecuteStmt *)target_query; | |
1714 | + PreparedStatement *entry; | |
1715 | + Query *ent_query; | |
1716 | + | |
1717 | + entry = FetchPreparedStatement(stmt->name, true); | |
1718 | + p = entry->plansource->query_string; | |
1719 | + ent_query = (Query *) linitial (entry->plansource->query_list); | |
1720 | + Assert(IsA(ent_query, Query)); | |
1721 | + if (jumblequery) | |
1722 | + *jumblequery = ent_query; | |
1723 | + } | |
1660 | 1724 | } |
1661 | - else | |
1662 | - p = debug_query_string; | |
1725 | + /* Return NULL if the pstate is not identical to the top-level query */ | |
1726 | + else if (strcmp(pstate->p_sourcetext, p) != 0) | |
1727 | + p = NULL; | |
1663 | 1728 | |
1664 | 1729 | return p; |
1665 | 1730 | } |
@@ -2225,10 +2290,10 @@ set_config_option_wrapper(const char *name, const char *value, | ||
2225 | 2290 | |
2226 | 2291 | ereport(elevel, |
2227 | 2292 | (errcode(errdata->sqlerrcode), |
2228 | - errhidestmt(hidestmt), | |
2229 | 2293 | errmsg("%s", errdata->message), |
2230 | 2294 | errdata->detail ? errdetail("%s", errdata->detail) : 0, |
2231 | 2295 | errdata->hint ? errhint("%s", errdata->hint) : 0)); |
2296 | + msgqno = qno; | |
2232 | 2297 | FreeErrorData(errdata); |
2233 | 2298 | } |
2234 | 2299 | PG_END_TRY(); |
@@ -2306,132 +2371,6 @@ set_join_config_options(unsigned char enforce_mask, GucContext context) | ||
2306 | 2371 | } |
2307 | 2372 | |
2308 | 2373 | /* |
2309 | - * pg_hint_plan hook functions | |
2310 | - */ | |
2311 | - | |
2312 | -static void | |
2313 | -pg_hint_plan_ProcessUtility(Node *parsetree, const char *queryString, | |
2314 | - ProcessUtilityContext context, | |
2315 | - ParamListInfo params, | |
2316 | - DestReceiver *dest, char *completionTag) | |
2317 | -{ | |
2318 | - Node *node; | |
2319 | - | |
2320 | - /* | |
2321 | - * Use standard planner if pg_hint_plan is disabled or current nesting | |
2322 | - * depth is nesting depth of SPI calls. | |
2323 | - */ | |
2324 | - if (!pg_hint_plan_enable_hint || hint_inhibit_level > 0) | |
2325 | - { | |
2326 | - if (debug_level > 1) | |
2327 | - ereport(pg_hint_plan_message_level, | |
2328 | - (errmsg ("pg_hint_plan: ProcessUtility:" | |
2329 | - " pg_hint_plan.enable_hint = off"))); | |
2330 | - if (prev_ProcessUtility) | |
2331 | - (*prev_ProcessUtility) (parsetree, queryString, | |
2332 | - context, params, | |
2333 | - dest, completionTag); | |
2334 | - else | |
2335 | - standard_ProcessUtility(parsetree, queryString, | |
2336 | - context, params, | |
2337 | - dest, completionTag); | |
2338 | - return; | |
2339 | - } | |
2340 | - | |
2341 | - node = parsetree; | |
2342 | - if (IsA(node, ExplainStmt)) | |
2343 | - { | |
2344 | - /* | |
2345 | - * Draw out parse tree of actual query from Query struct of EXPLAIN | |
2346 | - * statement. | |
2347 | - */ | |
2348 | - ExplainStmt *stmt; | |
2349 | - Query *query; | |
2350 | - | |
2351 | - stmt = (ExplainStmt *) node; | |
2352 | - | |
2353 | - Assert(IsA(stmt->query, Query)); | |
2354 | - query = (Query *) stmt->query; | |
2355 | - | |
2356 | - if (query->commandType == CMD_UTILITY && query->utilityStmt != NULL) | |
2357 | - node = query->utilityStmt; | |
2358 | - } | |
2359 | - | |
2360 | - /* | |
2361 | - * If the query was a EXECUTE or CREATE TABLE AS EXECUTE, get query string | |
2362 | - * specified to preceding PREPARE command to use it as source of hints. | |
2363 | - */ | |
2364 | - if (IsA(node, ExecuteStmt)) | |
2365 | - { | |
2366 | - ExecuteStmt *stmt; | |
2367 | - | |
2368 | - stmt = (ExecuteStmt *) node; | |
2369 | - stmt_name = stmt->name; | |
2370 | - } | |
2371 | - | |
2372 | - /* | |
2373 | - * CREATE AS EXECUTE behavior has changed since 9.2, so we must handle it | |
2374 | - * specially here. | |
2375 | - */ | |
2376 | - if (IsA(node, CreateTableAsStmt)) | |
2377 | - { | |
2378 | - CreateTableAsStmt *stmt; | |
2379 | - Query *query; | |
2380 | - | |
2381 | - stmt = (CreateTableAsStmt *) node; | |
2382 | - Assert(IsA(stmt->query, Query)); | |
2383 | - query = (Query *) stmt->query; | |
2384 | - | |
2385 | - if (query->commandType == CMD_UTILITY && | |
2386 | - IsA(query->utilityStmt, ExecuteStmt)) | |
2387 | - { | |
2388 | - ExecuteStmt *estmt = (ExecuteStmt *) query->utilityStmt; | |
2389 | - stmt_name = estmt->name; | |
2390 | - } | |
2391 | - } | |
2392 | - | |
2393 | - if (stmt_name) | |
2394 | - { | |
2395 | - if (debug_level > 1) | |
2396 | - ereport(pg_hint_plan_message_level, | |
2397 | - (errmsg ("pg_hint_plan: ProcessUtility:" | |
2398 | - " stmt_name = \"%s\", statement=\"%s\"", | |
2399 | - stmt_name, queryString))); | |
2400 | - | |
2401 | - PG_TRY(); | |
2402 | - { | |
2403 | - if (prev_ProcessUtility) | |
2404 | - (*prev_ProcessUtility) (parsetree, queryString, | |
2405 | - context, params, | |
2406 | - dest, completionTag); | |
2407 | - else | |
2408 | - standard_ProcessUtility(parsetree, queryString, | |
2409 | - context, params, | |
2410 | - dest, completionTag); | |
2411 | - } | |
2412 | - PG_CATCH(); | |
2413 | - { | |
2414 | - stmt_name = NULL; | |
2415 | - PG_RE_THROW(); | |
2416 | - } | |
2417 | - PG_END_TRY(); | |
2418 | - | |
2419 | - stmt_name = NULL; | |
2420 | - | |
2421 | - return; | |
2422 | - } | |
2423 | - | |
2424 | - if (prev_ProcessUtility) | |
2425 | - (*prev_ProcessUtility) (parsetree, queryString, | |
2426 | - context, params, | |
2427 | - dest, completionTag); | |
2428 | - else | |
2429 | - standard_ProcessUtility(parsetree, queryString, | |
2430 | - context, params, | |
2431 | - dest, completionTag); | |
2432 | -} | |
2433 | - | |
2434 | -/* | |
2435 | 2374 | * Push a hint into hint stack which is implemented with List struct. Head of |
2436 | 2375 | * list is top of stack. |
2437 | 2376 | */ |
@@ -2465,24 +2404,178 @@ pop_hint(void) | ||
2465 | 2404 | current_hint = (HintState *) lfirst(list_head(HintStateStack)); |
2466 | 2405 | } |
2467 | 2406 | |
2407 | +/* | |
2408 | + * We need only jumbled query for the root query, acquired by | |
2409 | + * get_query_string(). We cannot check that in pg_hint_plan_planner since it | |
2410 | + * cannot see the truely corresponding query string to the given Query. | |
2411 | + * We check that here instead using ParseState. | |
2412 | + */ | |
2413 | +static void | |
2414 | +pg_hint_plan_post_parse_analyze(ParseState *pstate, Query *query) | |
2415 | +{ | |
2416 | + const char *query_str; | |
2417 | + MemoryContext oldcontext; | |
2418 | + | |
2419 | + if (prev_post_parse_analyze_hook) | |
2420 | + prev_post_parse_analyze_hook(pstate, query); | |
2421 | + | |
2422 | + /* do nothing under hint table search */ | |
2423 | + if (hint_inhibit_level > 0) | |
2424 | + return; | |
2425 | + | |
2426 | + if (!pg_hint_plan_enable_hint) | |
2427 | + { | |
2428 | + if (current_hint_str) | |
2429 | + { | |
2430 | + pfree((void *)current_hint_str); | |
2431 | + current_hint_str = NULL; | |
2432 | + } | |
2433 | + return; | |
2434 | + } | |
2435 | + | |
2436 | + /* increment the query number */ | |
2437 | + qnostr[0] = 0; | |
2438 | + if (debug_level > 1) | |
2439 | + snprintf(qnostr, sizeof(qnostr), "[qno=0x%x]", qno++); | |
2440 | + qno++; | |
2441 | + | |
2442 | + /* search the hint table for a hint if requested */ | |
2443 | + if (pg_hint_plan_enable_hint_table) | |
2444 | + { | |
2445 | + int query_len; | |
2446 | + pgssJumbleState jstate; | |
2447 | + Query *jumblequery; | |
2448 | + char *normalized_query = NULL; | |
2449 | + | |
2450 | + query_str = get_query_string(pstate, query, &jumblequery); | |
2451 | + | |
2452 | + /* If this query is not for hint, just return */ | |
2453 | + if (!query_str) | |
2454 | + return; | |
2455 | + | |
2456 | + /* clear the previous hint string */ | |
2457 | + if (current_hint_str) | |
2458 | + { | |
2459 | + pfree((void *)current_hint_str); | |
2460 | + current_hint_str = NULL; | |
2461 | + } | |
2462 | + | |
2463 | + if (jumblequery) | |
2464 | + { | |
2465 | + /* | |
2466 | + * XXX: normalizing code is copied from pg_stat_statements.c, so be | |
2467 | + * careful to PostgreSQL's version up. | |
2468 | + */ | |
2469 | + jstate.jumble = (unsigned char *) palloc(JUMBLE_SIZE); | |
2470 | + jstate.jumble_len = 0; | |
2471 | + jstate.clocations_buf_size = 32; | |
2472 | + jstate.clocations = (pgssLocationLen *) | |
2473 | + palloc(jstate.clocations_buf_size * sizeof(pgssLocationLen)); | |
2474 | + jstate.clocations_count = 0; | |
2475 | + | |
2476 | + JumbleQuery(&jstate, jumblequery); | |
2477 | + | |
2478 | + /* | |
2479 | + * Normalize the query string by replacing constants with '?' | |
2480 | + */ | |
2481 | + /* | |
2482 | + * Search hint string which is stored keyed by query string | |
2483 | + * and application name. The query string is normalized to allow | |
2484 | + * fuzzy matching. | |
2485 | + * | |
2486 | + * Adding 1 byte to query_len ensures that the returned string has | |
2487 | + * a terminating NULL. | |
2488 | + */ | |
2489 | + query_len = strlen(query_str) + 1; | |
2490 | + normalized_query = | |
2491 | + generate_normalized_query(&jstate, query_str, | |
2492 | + &query_len, | |
2493 | + GetDatabaseEncoding()); | |
2494 | + | |
2495 | + /* | |
2496 | + * find a hint for the normalized query. the result should be in | |
2497 | + * TopMemoryContext | |
2498 | + */ | |
2499 | + oldcontext = MemoryContextSwitchTo(TopMemoryContext); | |
2500 | + current_hint_str = | |
2501 | + get_hints_from_table(normalized_query, application_name); | |
2502 | + MemoryContextSwitchTo(oldcontext); | |
2503 | + | |
2504 | + if (debug_level > 1) | |
2505 | + { | |
2506 | + if (current_hint_str) | |
2507 | + ereport(pg_hint_plan_message_level, | |
2508 | + (errmsg("pg_hint_plan[qno=0x%x]: " | |
2509 | + "post_parse_analyze_hook: " | |
2510 | + "hints from table: \"%s\": " | |
2511 | + "normalized_query=\"%s\", " | |
2512 | + "application name =\"%s\"", | |
2513 | + qno, current_hint_str, | |
2514 | + normalized_query, application_name), | |
2515 | + errhidestmt(msgqno != qno))); | |
2516 | + else | |
2517 | + ereport(pg_hint_plan_message_level, | |
2518 | + (errmsg("pg_hint_plan[qno=0x%x]: " | |
2519 | + "no match found in table: " | |
2520 | + "application name = \"%s\", " | |
2521 | + "normalized_query=\"%s\"", | |
2522 | + qno, application_name, | |
2523 | + normalized_query), | |
2524 | + errhidestmt(msgqno != qno))); | |
2525 | + | |
2526 | + msgqno = qno; | |
2527 | + } | |
2528 | + } | |
2529 | + | |
2530 | + /* retrun if we have hint here*/ | |
2531 | + if (current_hint_str) | |
2532 | + return; | |
2533 | + } | |
2534 | + else | |
2535 | + query_str = get_query_string(pstate, query, NULL); | |
2536 | + | |
2537 | + if (query_str) | |
2538 | + { | |
2539 | + /* | |
2540 | + * get hints from the comment. However we may have the same query | |
2541 | + * string with the previous call, but just retrieving hints is expected | |
2542 | + * to be faster than checking for identicalness before retrieval. | |
2543 | + */ | |
2544 | + if (current_hint_str) | |
2545 | + pfree((void *)current_hint_str); | |
2546 | + | |
2547 | + oldcontext = MemoryContextSwitchTo(TopMemoryContext); | |
2548 | + current_hint_str = get_hints_from_comment(query_str); | |
2549 | + MemoryContextSwitchTo(oldcontext); | |
2550 | + } | |
2551 | + | |
2552 | + if (debug_level > 1) | |
2553 | + { | |
2554 | + if (debug_level == 1 && | |
2555 | + (stmt_name || strcmp(query_str, debug_query_string))) | |
2556 | + ereport(pg_hint_plan_message_level, | |
2557 | + (errmsg("hints in comment=\"%s\"", | |
2558 | + current_hint_str ? current_hint_str : "(none)"), | |
2559 | + errhidestmt(msgqno != qno))); | |
2560 | + else | |
2561 | + ereport(pg_hint_plan_message_level, | |
2562 | + (errmsg("hints in comment=\"%s\", stmt=\"%s\", query=\"%s\", debug_query_string=\"%s\"", | |
2563 | + current_hint_str ? current_hint_str : "(none)", | |
2564 | + stmt_name, query_str, debug_query_string), | |
2565 | + errhidestmt(msgqno != qno))); | |
2566 | + msgqno = qno; | |
2567 | + } | |
2568 | +} | |
2569 | + | |
2570 | +/* | |
2571 | + * Read and set up hint information | |
2572 | + */ | |
2468 | 2573 | static PlannedStmt * |
2469 | 2574 | pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) |
2470 | 2575 | { |
2471 | - const char *hints = NULL; | |
2472 | - const char *query; | |
2473 | - char *norm_query; | |
2474 | - pgssJumbleState jstate; | |
2475 | - int query_len; | |
2476 | 2576 | int save_nestlevel; |
2477 | 2577 | PlannedStmt *result; |
2478 | 2578 | HintState *hstate; |
2479 | - char msgstr[1024]; | |
2480 | - | |
2481 | - qnostr[0] = 0; | |
2482 | - strcpy(msgstr, ""); | |
2483 | - if (debug_level > 1) | |
2484 | - snprintf(qnostr, sizeof(qnostr), "[qno=0x%x]", qno++); | |
2485 | - hidestmt = false; | |
2486 | 2579 | |
2487 | 2580 | /* |
2488 | 2581 | * Use standard planner if pg_hint_plan is disabled or current nesting |
@@ -2492,103 +2585,44 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) | ||
2492 | 2585 | if (!pg_hint_plan_enable_hint || hint_inhibit_level > 0) |
2493 | 2586 | { |
2494 | 2587 | if (debug_level > 1) |
2495 | - elog(pg_hint_plan_message_level, | |
2496 | - "pg_hint_plan%s: planner: enable_hint=%d," | |
2497 | - " hint_inhibit_level=%d", | |
2498 | - qnostr, pg_hint_plan_enable_hint, hint_inhibit_level); | |
2499 | - hidestmt = true; | |
2588 | + ereport(pg_hint_plan_message_level, | |
2589 | + (errmsg ("pg_hint_plan%s: planner: enable_hint=%d," | |
2590 | + " hint_inhibit_level=%d", | |
2591 | + qnostr, pg_hint_plan_enable_hint, | |
2592 | + hint_inhibit_level), | |
2593 | + errhidestmt(msgqno != qno))); | |
2594 | + msgqno = qno; | |
2500 | 2595 | |
2501 | 2596 | goto standard_planner_proc; |
2502 | 2597 | } |
2503 | 2598 | |
2504 | - /* Create hint struct from client-supplied query string. */ | |
2505 | - query = get_query_string(); | |
2506 | - | |
2507 | 2599 | /* |
2508 | - * Create hintstate from hint specified for the query, if any. | |
2509 | - * | |
2510 | - * First we lookup hint in pg_hint.hints table by normalized query string, | |
2511 | - * unless pg_hint_plan.enable_hint_table is OFF. | |
2512 | - * This parameter provides option to avoid overhead of table lookup during | |
2513 | - * planning. | |
2514 | - * | |
2515 | - * If no hint was found, then we try to get hint from special query comment. | |
2600 | + * Support for nested plpgsql functions. This is quite ugly but this is the | |
2601 | + * only point I could find where I can get the query string. | |
2516 | 2602 | */ |
2517 | - if (pg_hint_plan_enable_hint_table) | |
2518 | - { | |
2519 | - /* | |
2520 | - * Search hint information which is stored for the query and the | |
2521 | - * application. Query string is normalized before using in condition | |
2522 | - * in order to allow fuzzy matching. | |
2523 | - * | |
2524 | - * XXX: normalizing code is copied from pg_stat_statements.c, so be | |
2525 | - * careful when supporting PostgreSQL's version up. | |
2526 | - */ | |
2527 | - jstate.jumble = (unsigned char *) palloc(JUMBLE_SIZE); | |
2528 | - jstate.jumble_len = 0; | |
2529 | - jstate.clocations_buf_size = 32; | |
2530 | - jstate.clocations = (pgssLocationLen *) | |
2531 | - palloc(jstate.clocations_buf_size * sizeof(pgssLocationLen)); | |
2532 | - jstate.clocations_count = 0; | |
2533 | - JumbleQuery(&jstate, parse); | |
2534 | - /* | |
2535 | - * generate_normalized_query() copies exact given query_len bytes, so we | |
2536 | - * add 1 byte for null-termination here. As comments on | |
2537 | - * generate_normalized_query says, generate_normalized_query doesn't | |
2538 | - * take care of null-terminate, but additional 1 byte ensures that '\0' | |
2539 | - * byte in the source buffer to be copied into norm_query. | |
2540 | - */ | |
2541 | - query_len = strlen(query) + 1; | |
2542 | - norm_query = generate_normalized_query(&jstate, | |
2543 | - query, | |
2544 | - &query_len, | |
2545 | - GetDatabaseEncoding()); | |
2546 | - hints = get_hints_from_table(norm_query, application_name); | |
2547 | - if (debug_level > 1) | |
2548 | - { | |
2549 | - if (hints) | |
2550 | - snprintf(msgstr, 1024, "hints from table: \"%s\":" | |
2551 | - " normalzed_query=\"%s\", application name =\"%s\"", | |
2552 | - hints, norm_query, application_name); | |
2553 | - else | |
2554 | - { | |
2555 | - ereport(pg_hint_plan_message_level, | |
2556 | - (errhidestmt(hidestmt), | |
2557 | - errmsg("pg_hint_plan%s:" | |
2558 | - " no match found in table:" | |
2559 | - " application name = \"%s\"," | |
2560 | - " normalzed_query=\"%s\"", | |
2561 | - qnostr, application_name, norm_query))); | |
2562 | - hidestmt = true; | |
2563 | - } | |
2564 | - } | |
2565 | - } | |
2566 | - if (hints == NULL) | |
2603 | + if (plpgsql_recurse_level > 0) | |
2567 | 2604 | { |
2568 | - hints = get_hints_from_comment(query); | |
2605 | + MemoryContext oldcontext; | |
2569 | 2606 | |
2570 | - if (debug_level > 1) | |
2571 | - { | |
2572 | - snprintf(msgstr, 1024, "hints in comment=\"%s\"", | |
2573 | - hints ? hints : "(none)"); | |
2574 | - if (debug_level > 2 || | |
2575 | - stmt_name || strcmp(query, debug_query_string)) | |
2576 | - snprintf(msgstr + strlen(msgstr), 1024- strlen(msgstr), | |
2577 | - ", stmt=\"%s\", query=\"%s\", debug_query_string=\"%s\"", | |
2578 | - stmt_name, query, debug_query_string); | |
2579 | - } | |
2607 | + if (current_hint_str) | |
2608 | + pfree((void *)current_hint_str); | |
2609 | + | |
2610 | + oldcontext = MemoryContextSwitchTo(TopMemoryContext); | |
2611 | + current_hint_str = | |
2612 | + get_hints_from_comment((char *)error_context_stack->arg); | |
2613 | + MemoryContextSwitchTo(oldcontext); | |
2580 | 2614 | } |
2581 | 2615 | |
2582 | - hstate = create_hintstate(parse, hints); | |
2616 | + if (!current_hint_str) | |
2617 | + goto standard_planner_proc; | |
2583 | 2618 | |
2584 | - /* | |
2585 | - * Use standard planner if the statement has not valid hint. Other hook | |
2586 | - * functions try to change plan with current_hint if any, so set it to | |
2587 | - * NULL. | |
2588 | - */ | |
2619 | + /* parse the hint into hint state struct */ | |
2620 | + hstate = create_hintstate(parse, pstrdup(current_hint_str)); | |
2621 | + | |
2622 | + /* run standard planner if the statement has not valid hint */ | |
2589 | 2623 | if (!hstate) |
2590 | 2624 | goto standard_planner_proc; |
2591 | - | |
2625 | + | |
2592 | 2626 | /* |
2593 | 2627 | * Push new hint struct to the hint stack to disable previous hint context. |
2594 | 2628 | */ |
@@ -2619,10 +2653,9 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) | ||
2619 | 2653 | if (debug_level > 1) |
2620 | 2654 | { |
2621 | 2655 | ereport(pg_hint_plan_message_level, |
2622 | - (errhidestmt(hidestmt), | |
2623 | - errmsg("pg_hint_plan%s: planner: %s", | |
2624 | - qnostr, msgstr))); | |
2625 | - hidestmt = true; | |
2656 | + (errhidestmt(msgqno != qno), | |
2657 | + errmsg("pg_hint_plan%s: planner", qnostr))); | |
2658 | + msgqno = qno; | |
2626 | 2659 | } |
2627 | 2660 | |
2628 | 2661 | /* |
@@ -2667,10 +2700,10 @@ standard_planner_proc: | ||
2667 | 2700 | if (debug_level > 1) |
2668 | 2701 | { |
2669 | 2702 | ereport(pg_hint_plan_message_level, |
2670 | - (errhidestmt(hidestmt), | |
2671 | - errmsg("pg_hint_plan%s: planner: no valid hint (%s)", | |
2672 | - qnostr, msgstr))); | |
2673 | - hidestmt = true; | |
2703 | + (errhidestmt(msgqno != qno), | |
2704 | + errmsg("pg_hint_plan%s: planner: no valid hint", | |
2705 | + qnostr))); | |
2706 | + msgqno = qno; | |
2674 | 2707 | } |
2675 | 2708 | current_hint = NULL; |
2676 | 2709 | if (prev_planner) |