1 /*-------------------------------------------------------------------------
4 * Track statement execution in current/last transaction.
6 * Copyright (c) 2011, PostgreSQL Global Development Group
9 * contrib/pg_hint_plan/pg_hint_plan.c
11 *-------------------------------------------------------------------------
15 #include "utils/elog.h"
16 #include "utils/builtins.h"
17 #include "utils/memutils.h"
18 #include "optimizer/cost.h"
20 #ifdef PG_MODULE_MAGIC
24 #define HASH_ENTRIES 201
28 ENABLE_SEQSCAN = 0x01,
29 ENABLE_INDEXSCAN = 0x02,
30 ENABLE_BITMAPSCAN = 0x04,
31 ENABLE_TIDSCAN = 0x08,
32 ENABLE_NESTLOOP = 0x10,
33 ENABLE_MERGEJOIN = 0x20,
34 ENABLE_HASHJOIN = 0x40
37 typedef struct tidlist
43 typedef struct hash_entry
46 unsigned char enforce_mask;
47 struct hash_entry *next;
50 static HashEntry *hashent[HASH_ENTRIES];
51 static bool (*org_cost_hook)(CostHookType type, PlannerInfo *root, Path *path1, Path *path2);
52 static bool print_log = false;
53 static bool tweak_enabled = true;
55 /* Module callbacks */
58 Datum pg_add_hint(PG_FUNCTION_ARGS);
59 Datum pg_clear_hint(PG_FUNCTION_ARGS);
60 Datum pg_dump_hint(PG_FUNCTION_ARGS);
61 Datum pg_enable_hint(bool arg, bool *isnull);
62 Datum pg_enable_log(bool arg, bool *isnull);
64 static char *rels_str(PlannerInfo *root, Path *path);
65 static void dump_rels(char *label, PlannerInfo *root, Path *path, bool found, bool enabled);
66 static void dump_joinrels(char *label, PlannerInfo *root, Path *inpath, Path *outpath, bool found, bool enabled);
67 static bool my_cost_hook(CostHookType type, PlannerInfo *root, Path *path1, Path *path2);
68 static void free_hashent(HashEntry *head);
69 static unsigned int calc_hash(TidList *tidlist);
70 static HashEntry *search_ent(TidList *tidlist);
72 PG_FUNCTION_INFO_V1(pg_add_hint);
73 PG_FUNCTION_INFO_V1(pg_clear_hint);
76 * Module load callbacks
83 org_cost_hook = cost_hook;
84 cost_hook = my_cost_hook;
86 for (i = 0 ; i < HASH_ENTRIES ; i++)
91 * Module unload callback
98 cost_hook = org_cost_hook;
100 for (i = 0 ; i < HASH_ENTRIES ; i++)
102 free_hashent(hashent[i]);
108 char *rels_str(PlannerInfo *root, Path *path)
115 if (path->pathtype == T_Invalid) return strdup("");
117 tmpbms = bms_copy(path->parent->relids);
120 while ((relid = bms_first_member(tmpbms)) >= 0)
123 snprintf(idbuf, sizeof(idbuf), first ? "%d" : ", %d",
124 root->simple_rte_array[relid]->relid);
125 if (strlen(buf) + strlen(idbuf) < sizeof(buf))
133 static int oidsortcmp(const void *a, const void *b)
135 const Oid oida = *((const Oid *)a);
136 const Oid oidb = *((const Oid *)b);
141 static TidList *maketidlist(PlannerInfo *root, Path *path1, Path *path2)
144 Path *paths[2] = {path1, path2};
148 TidList *ret = (TidList *)malloc(sizeof(TidList));
150 for (i = 0 ; i < 2 ; i++)
152 if (paths[i] != NULL)
153 nrels += bms_num_members(paths[i]->parent->relids);
157 ret->oids = (Oid *)malloc(nrels * sizeof(Oid));
159 for (i = 0 ; i < 2 ; i++)
163 if (paths[i] == NULL) continue;
165 tmpbms= bms_copy(paths[i]->parent->relids);
167 while ((relid = bms_first_member(tmpbms)) >= 0)
168 ret->oids[j++] = root->simple_rte_array[relid]->relid;
172 qsort(ret->oids, nrels, sizeof(Oid), oidsortcmp);
177 static void free_tidlist(TidList *tidlist)
188 static void dump_rels(char *label, PlannerInfo *root, Path *path, bool found, bool enabled)
192 if (!print_log) return;
193 relsstr = rels_str(root, path);
194 ereport(LOG, (errmsg_internal("%04d: %s for relation %s (%s, %s)\n",
196 found ? "found" : "not found",
197 enabled ? "enabled" : "disabled")));
201 void dump_joinrels(char *label, PlannerInfo *root, Path *inpath, Path *outpath,
202 bool found, bool enabled)
204 char *irelstr, *orelstr;
206 if (!print_log) return;
207 irelstr = rels_str(root, inpath);
208 orelstr = rels_str(root, outpath);
210 ereport(LOG, (errmsg_internal("%04d: %s for relation ((%s),(%s)) (%s, %s)\n",
211 n++, label, irelstr, orelstr,
212 found ? "found" : "not found",
213 enabled ? "enabled" : "disabled")));
219 bool my_cost_hook(CostHookType type, PlannerInfo *root, Path *path1, Path *path2)
229 case COSTHOOK_seqscan:
230 return enable_seqscan;
231 case COSTHOOK_indexscan:
232 return enable_indexscan;
233 case COSTHOOK_bitmapscan:
234 return enable_bitmapscan;
235 case COSTHOOK_tidscan:
236 return enable_tidscan;
237 case COSTHOOK_nestloop:
238 return enable_nestloop;
239 case COSTHOOK_mergejoin:
240 return enable_mergejoin;
241 case COSTHOOK_hashjoin:
242 return enable_hashjoin;
244 ereport(LOG, (errmsg_internal("Unknown cost type")));
250 case COSTHOOK_seqscan:
251 tidlist = maketidlist(root, path1, path2);
252 ent = search_ent(tidlist);
253 free_tidlist(tidlist);
254 ret = (ent ? (ent->enforce_mask & ENABLE_SEQSCAN) :
256 dump_rels("cost_seqscan", root, path1, ent != NULL, ret);
258 case COSTHOOK_indexscan:
259 tidlist = maketidlist(root, path1, path2);
260 ent = search_ent(tidlist);
261 free_tidlist(tidlist);
262 ret = (ent ? (ent->enforce_mask & ENABLE_INDEXSCAN) :
264 dump_rels("cost_indexscan", root, path1, ent != NULL, ret);
266 case COSTHOOK_bitmapscan:
267 if (path1->pathtype != T_BitmapHeapScan)
270 ret = enable_bitmapscan;
274 tidlist = maketidlist(root, path1, path2);
275 ent = search_ent(tidlist);
276 free_tidlist(tidlist);
277 ret = (ent ? (ent->enforce_mask & ENABLE_BITMAPSCAN) :
280 dump_rels("cost_bitmapscan", root, path1, ent != NULL, ret);
283 case COSTHOOK_tidscan:
284 tidlist = maketidlist(root, path1, path2);
285 ent = search_ent(tidlist);
286 free_tidlist(tidlist);
287 ret = (ent ? (ent->enforce_mask & ENABLE_TIDSCAN) :
289 dump_rels("cost_tidscan", root, path1, ent != NULL, ret);
291 case COSTHOOK_nestloop:
292 tidlist = maketidlist(root, path1, path2);
293 ent = search_ent(tidlist);
294 free_tidlist(tidlist);
295 ret = (ent ? (ent->enforce_mask & ENABLE_NESTLOOP) :
297 dump_joinrels("cost_nestloop", root, path1, path2,
300 case COSTHOOK_mergejoin:
301 tidlist = maketidlist(root, path1, path2);
302 ent = search_ent(tidlist);
303 free_tidlist(tidlist);
304 ret = (ent ? (ent->enforce_mask & ENABLE_MERGEJOIN) :
306 dump_joinrels("cost_mergejoin", root, path1, path2,
309 case COSTHOOK_hashjoin:
310 tidlist = maketidlist(root, path1, path2);
311 ent = search_ent(tidlist);
312 free_tidlist(tidlist);
313 ret = (ent ? (ent->enforce_mask & ENABLE_HASHJOIN) :
315 dump_joinrels("cost_hashjoin", root, path1, path2,
319 ereport(LOG, (errmsg_internal("Unknown cost type")));
326 static void free_hashent(HashEntry *head)
328 HashEntry *next = head;
332 HashEntry *last = next;
333 if (next->tidlist.oids != NULL) free(next->tidlist.oids);
339 static HashEntry *parse_tidlist(char **str)
343 Oid tid[20]; /* ^^; */
348 while (isdigit(**str) && ntids < 20)
351 while (isdigit(**str)) (*str)++;
353 if (len >= 8) return NULL;
354 strncpy(tidstr, p0, len);
357 /* Tis 0 is valid? I don't know :-p */
358 if ((tid[ntids++] = atoi(tidstr)) == 0) return NULL;
360 if (**str == ',') (*str)++;
364 qsort(tid, ntids, sizeof(Oid), oidsortcmp);
365 ret = (HashEntry*)malloc(sizeof(HashEntry));
367 ret->enforce_mask = 0;
368 ret->tidlist.nrels = ntids;
369 ret->tidlist.oids = (Oid *)malloc(ntids * sizeof(Oid));
370 for (i = 0 ; i < ntids ; i++)
371 ret->tidlist.oids[i] = tid[i];
375 static int parse_phrase(HashEntry **head, char **str)
377 char *cmds[] = {"seq", "index", "nest", "merge", "hash", NULL};
378 unsigned char masks[] = {ENABLE_SEQSCAN, ENABLE_INDEXSCAN|ENABLE_BITMAPSCAN,
379 ENABLE_NESTLOOP, ENABLE_MERGEJOIN, ENABLE_HASHJOIN};
382 HashEntry *ent = NULL;
387 while (isalpha(**str)) (*str)++;
389 if (**str != '(' || len >= 12) return 0;
390 strncpy(req, p0, len);
392 for (cmd = 0 ; cmds[cmd] && strcmp(cmds[cmd], req) ; cmd++);
393 if (cmds[cmd] == NULL) return 0;
395 if ((ent = parse_tidlist(str)) == NULL) return 0;
396 if (*(*str)++ != ')') return 0;
397 if (**str != 0 && **str != ';') return 0;
398 if (**str == ';') (*str)++;
399 ent->enforce_mask = masks[cmd];
407 static HashEntry* parse_top(char* str)
409 HashEntry *head = NULL;
410 HashEntry *ent = NULL;
412 if (!parse_phrase(&head, &str))
421 if (!parse_phrase(&ent->next, &str))
432 static bool ent_matches(TidList *key, HashEntry *ent2)
436 if (key->nrels != ent2->tidlist.nrels)
439 for (i = 0 ; i < key->nrels ; i++)
440 if (key->oids[i] != ent2->tidlist.oids[i])
446 static unsigned int calc_hash(TidList *tidlist)
448 unsigned int hash = 0;
451 for (i = 0 ; i < tidlist->nrels ; i++)
454 for (j = 0 ; j < sizeof(Oid) ; j++)
455 hash = hash * 2 + ((tidlist->oids[i] >> (j * 8)) & 0xff);
458 return hash % HASH_ENTRIES;
462 static HashEntry *search_ent(TidList *tidlist)
465 if (tidlist == NULL) return NULL;
467 ent = hashent[calc_hash(tidlist)];
470 if (ent_matches(tidlist, ent))
479 pg_add_hint(PG_FUNCTION_ARGS)
481 HashEntry *ret = NULL;
486 ereport(ERROR, (errmsg_internal("No argument")));
488 str = text_to_cstring(PG_GETARG_TEXT_PP(0));
490 ret = parse_top(str);
493 ereport(ERROR, (errmsg_internal("Parse Error")));
497 HashEntry *etmp = NULL;
498 HashEntry *next = NULL;
499 int hash = calc_hash(&ret->tidlist);
500 while (hashent[hash] && ent_matches(&ret->tidlist, hashent[hash]))
502 etmp = hashent[hash]->next;
503 hashent[hash]->next = NULL;
504 free_hashent(hashent[hash]);
505 hashent[hash] = etmp;
508 etmp = hashent[hash];
509 while (etmp && etmp->next)
511 if (ent_matches(&ret->tidlist, etmp->next))
513 HashEntry *etmp2 = etmp->next->next;
514 etmp->next->next = NULL;
515 free_hashent(etmp->next);
523 ret->next = hashent[hash];
531 pg_clear_hint(PG_FUNCTION_ARGS)
536 for (i = 0 ; i < HASH_ENTRIES ; i++)
538 free_hashent(hashent[i]);
547 pg_enable_hint(bool arg, bool *isnull)
554 pg_enable_log(bool arg, bool *isnull)
560 static int putsbuf(char **p, char *bottom, char *str)
562 while (*p < bottom && *str)
570 static void dump_ent(HashEntry *ent, char **p, char *bottom)
572 static char typesigs[] = "SIBTNMH";
573 char sigs[sizeof(typesigs)];
576 if (!putsbuf(p, bottom, "[(")) return;
577 for (i = 0 ; i < ent->tidlist.nrels ; i++)
579 if (i && !putsbuf(p, bottom, ", ")) return;
580 if (*p >= bottom) return;
581 *p += snprintf(*p, bottom - *p, "%d", ent->tidlist.oids[i]);
583 if (!putsbuf(p, bottom, "), ")) return;
584 strcpy(sigs, typesigs);
585 for (i = 0 ; i < 7 ; i++) /* Magic number here! */
587 if(((1<<i) & ent->enforce_mask) == 0)
588 sigs[i] += 'a' - 'A';
590 if (!putsbuf(p, bottom, sigs)) return;
591 if (!putsbuf(p, bottom, "]")) return;
595 pg_dump_hint(PG_FUNCTION_ARGS)
597 char buf[16384]; /* ^^; */
598 char *bottom = buf + sizeof(buf);
603 memset(buf, 0, sizeof(buf));
604 for (i = 0 ; i < HASH_ENTRIES ; i++)
608 HashEntry *ent = hashent[i];
614 putsbuf(&p, bottom, ", ");
616 dump_ent(ent, &p, bottom);
621 if (p >= bottom) p--;
624 PG_RETURN_CSTRING(cstring_to_text(buf));