|
| 1 | +/* ------------------------------------------------------------------------- |
| 2 | + * |
| 3 | + * nodeCustomTempScan.c |
| 4 | + * Implements strategy which allows to build and execute partial paths for |
| 5 | + * a query which contains tempoorary table scans. |
| 6 | + * |
| 7 | + * Copyright (c) 2017-2024, Postgres Professional |
| 8 | + * |
| 9 | + * IDENTIFICATION |
| 10 | + * contrib/tempscan/nodeCustomTempScan.c |
| 11 | + * |
| 12 | + * ------------------------------------------------------------------------- |
| 13 | + */ |
| 14 | +#include "postgres.h" |
| 15 | + |
| 16 | +#include "nodes/extensible.h" |
| 17 | +#include "optimizer/cost.h" |
| 18 | +#include "optimizer/pathnode.h" |
| 19 | +#include "optimizer/paths.h" |
| 20 | +#include "utils/guc.h" |
| 21 | +#include "utils/lsyscache.h" |
| 22 | +#include "utils/rel.h" |
| 23 | + |
| 24 | +PG_MODULE_MAGIC; |
| 25 | + |
| 26 | +#define MODULENAME "tempscan" |
| 27 | +#define NODENAME "nodeCustomTempScan" |
| 28 | + |
| 29 | +static Plan *create_partial_tempscan_plan(PlannerInfo *root, |
| 30 | + RelOptInfo *rel, |
| 31 | + CustomPath *best_path, |
| 32 | + List *tlist, |
| 33 | + List *scan_clauses, |
| 34 | + List *custom_plans); |
| 35 | +static Node *create_tempscan_state(CustomScan *cscan); |
| 36 | +static void BeginTempScan(CustomScanState *node, EState *estate, int eflags); |
| 37 | +static TupleTableSlot *ExecTempScan(CustomScanState *node); |
| 38 | +static void EndTempScan(CustomScanState *node); |
| 39 | +static void ReScanTempScan(CustomScanState *node); |
| 40 | + |
| 41 | +static CustomPathMethods path_methods = |
| 42 | +{ |
| 43 | + .CustomName = NODENAME, |
| 44 | + .PlanCustomPath = create_partial_tempscan_plan, |
| 45 | + .ReparameterizeCustomPathByChild = NULL |
| 46 | +}; |
| 47 | + |
| 48 | +static CustomScanMethods plan_methods = |
| 49 | +{ |
| 50 | + .CustomName = NODENAME, |
| 51 | + .CreateCustomScanState = create_tempscan_state |
| 52 | +}; |
| 53 | + |
| 54 | +static CustomExecMethods exec_methods = |
| 55 | +{ |
| 56 | + .CustomName = NODENAME, |
| 57 | + |
| 58 | + .BeginCustomScan = BeginTempScan, |
| 59 | + .ExecCustomScan = ExecTempScan, |
| 60 | + .EndCustomScan = EndTempScan, |
| 61 | + .ReScanCustomScan = ReScanTempScan, |
| 62 | + .MarkPosCustomScan = NULL, |
| 63 | + .RestrPosCustomScan = NULL, |
| 64 | + .EstimateDSMCustomScan = NULL, |
| 65 | + .InitializeDSMCustomScan = NULL, |
| 66 | + .ReInitializeDSMCustomScan = NULL, |
| 67 | + .InitializeWorkerCustomScan = NULL, |
| 68 | + .ShutdownCustomScan = NULL, |
| 69 | + .ExplainCustomScan = NULL |
| 70 | +}; |
| 71 | + |
| 72 | +static set_rel_pathlist_hook_type set_rel_pathlist_hook_next = NULL; |
| 73 | +static bool tempscan_enable = false; |
| 74 | + |
| 75 | +void _PG_init(void); |
| 76 | + |
| 77 | +/* |
| 78 | + * The input path shouldn't be a part of the relation pathlist. |
| 79 | + */ |
| 80 | +static CustomPath * |
| 81 | +create_partial_tempscan_path(PlannerInfo *root, RelOptInfo *rel, |
| 82 | + Path *path) |
| 83 | +{ |
| 84 | + CustomPath *cpath; |
| 85 | + Path *pathnode; |
| 86 | + |
| 87 | + cpath = makeNode(CustomPath); |
| 88 | + pathnode = &cpath->path; |
| 89 | + |
| 90 | + pathnode->pathtype = T_CustomScan; |
| 91 | + pathnode->parent = rel; |
| 92 | + pathnode->pathtarget = rel->reltarget; |
| 93 | + pathnode->rows = rel->rows; |
| 94 | + |
| 95 | + /* XXX: Just for now */ |
| 96 | + pathnode->param_info = NULL; |
| 97 | + |
| 98 | + pathnode->parallel_safe = true; |
| 99 | + pathnode->parallel_aware = false; |
| 100 | + pathnode->parallel_workers = path->parallel_workers; |
| 101 | + |
| 102 | + /* DEBUGGING purposes only */ |
| 103 | + pathnode->startup_cost = path->startup_cost / disable_cost; |
| 104 | + pathnode->total_cost = path->total_cost / disable_cost; |
| 105 | + |
| 106 | + cpath->custom_paths = list_make1(path); |
| 107 | + cpath->custom_private = NIL; |
| 108 | + cpath->custom_restrictinfo = NIL; |
| 109 | + cpath->methods = &path_methods; |
| 110 | + |
| 111 | + return cpath; |
| 112 | +} |
| 113 | + |
| 114 | +static Plan * |
| 115 | +create_partial_tempscan_plan(PlannerInfo *root, RelOptInfo *rel, |
| 116 | + CustomPath *best_path, List *tlist, |
| 117 | + List *scan_clauses, List *custom_plans) |
| 118 | +{ |
| 119 | + CustomScan *cscan = makeNode(CustomScan); |
| 120 | + |
| 121 | + Assert(list_length(custom_plans) == 1); |
| 122 | + Assert(best_path->path.parallel_safe = true && |
| 123 | + best_path->path.parallel_workers > 0); |
| 124 | + |
| 125 | + |
| 126 | + cscan->scan.plan.targetlist = cscan->custom_scan_tlist = tlist; |
| 127 | + cscan->scan.scanrelid = 0; |
| 128 | + cscan->custom_exprs = NIL; |
| 129 | + cscan->custom_plans = custom_plans; |
| 130 | + cscan->methods = &plan_methods; |
| 131 | + cscan->flags = best_path->flags; |
| 132 | + cscan->custom_private = best_path->custom_private; |
| 133 | + |
| 134 | + return &cscan->scan.plan; |
| 135 | +} |
| 136 | + |
| 137 | +static Node * |
| 138 | +create_tempscan_state(CustomScan *cscan) |
| 139 | +{ |
| 140 | + CustomScanState *cstate = makeNode(CustomScanState); |
| 141 | + |
| 142 | + cstate->methods = &exec_methods; |
| 143 | + |
| 144 | + return (Node *) cstate; |
| 145 | +} |
| 146 | + |
| 147 | +static void |
| 148 | +BeginTempScan(CustomScanState *node, EState *estate, int eflags) |
| 149 | +{ |
| 150 | + CustomScan *cscan = (CustomScan *) node->ss.ps.plan; |
| 151 | + Plan *subplan; |
| 152 | + PlanState *pstate; |
| 153 | + |
| 154 | + Assert(list_length(cscan->custom_plans) == 1); |
| 155 | + |
| 156 | + subplan = (Plan *) linitial(cscan->custom_plans); |
| 157 | + pstate = ExecInitNode(subplan, estate, eflags); |
| 158 | + node->custom_ps = lappend(node->custom_ps, (void *) pstate); |
| 159 | +} |
| 160 | + |
| 161 | +static TupleTableSlot * |
| 162 | +ExecTempScan(CustomScanState *node) |
| 163 | +{ |
| 164 | + Assert(list_length(node->custom_ps) == 1); |
| 165 | + |
| 166 | + return ExecProcNode((PlanState *) linitial(node->custom_ps)); |
| 167 | +} |
| 168 | + |
| 169 | +static void |
| 170 | +EndTempScan(CustomScanState *node) |
| 171 | +{ |
| 172 | + ExecClearTuple(node->ss.ss_ScanTupleSlot); |
| 173 | + ExecEndNode((PlanState *) linitial(node->custom_ps)); |
| 174 | +} |
| 175 | + |
| 176 | +static void |
| 177 | +ReScanTempScan(CustomScanState *node) |
| 178 | +{ |
| 179 | + PlanState *child; |
| 180 | + |
| 181 | + ExecClearTuple(node->ss.ps.ps_ResultTupleSlot); |
| 182 | + |
| 183 | + child = (PlanState *) linitial(node->custom_ps); |
| 184 | + |
| 185 | + if (node->ss.ps.chgParam != NULL) |
| 186 | + UpdateChangedParamSet(child, node->ss.ps.chgParam); |
| 187 | + |
| 188 | + ExecReScan(child); |
| 189 | +} |
| 190 | + |
| 191 | +/* |
| 192 | + * Try to add partial paths to the scan of a temporary table. |
| 193 | + * |
| 194 | + * In contrast to the hook on a JOIN paths creation, here we already at the end |
| 195 | + * of paths creation procedure, right before insertion of a gather node. |
| 196 | + * So, we can discover pathlist and choose any base path we can and want to use |
| 197 | + * in parallel scan. |
| 198 | + * |
| 199 | + * TODO: add inner strategy for temp table scan (parallel_workers == 0, |
| 200 | + * parallel_safe == true). Right now it looks a bit more difficult to implement. |
| 201 | + */ |
| 202 | +static void |
| 203 | +try_partial_tempscan(PlannerInfo *root, RelOptInfo *rel, Index rti, |
| 204 | + RangeTblEntry *rte) |
| 205 | +{ |
| 206 | + int parallel_workers; |
| 207 | + Path *path; |
| 208 | + |
| 209 | + /* |
| 210 | + * Some extension intercept this hook earlier. Allow it to do a work |
| 211 | + * before us. |
| 212 | + */ |
| 213 | + if (set_rel_pathlist_hook_next) |
| 214 | + (*set_rel_pathlist_hook_next)(root, rel, rti, rte); |
| 215 | + |
| 216 | + if (rel->consider_parallel) |
| 217 | + return; |
| 218 | + |
| 219 | + if (rte->rtekind != RTE_RELATION || |
| 220 | + get_rel_persistence(rte->relid) != RELPERSISTENCE_TEMP) |
| 221 | + return; |
| 222 | + |
| 223 | + parallel_workers = compute_parallel_worker(rel, rel->pages, -1, |
| 224 | + max_parallel_workers_per_gather); |
| 225 | + |
| 226 | + /* If any limit was set to zero, the user doesn't want a parallel scan. */ |
| 227 | + if (parallel_workers <= 0) |
| 228 | + return; |
| 229 | + |
| 230 | + /* HACK */ |
| 231 | + rel->consider_parallel = true; |
| 232 | + |
| 233 | + path = create_seqscan_path(root, rel, NULL, parallel_workers); |
| 234 | + if (path) |
| 235 | + { |
| 236 | + /* Add an unordered partial path based on a parallel sequential scan. */ |
| 237 | + add_partial_path(rel, (Path *) |
| 238 | + create_partial_tempscan_path(root, rel, path)); |
| 239 | + } |
| 240 | + |
| 241 | + if (!bms_equal(rel->relids, root->all_query_rels)) |
| 242 | + rel->consider_parallel = false; |
| 243 | + Assert(IsA(linitial(rel->partial_pathlist), CustomPath)); |
| 244 | +} |
| 245 | + |
| 246 | +void |
| 247 | +_PG_init(void) |
| 248 | +{ |
| 249 | + DefineCustomBoolVariable("tempscan.enable", |
| 250 | + "Enable feature of the parallel temporary table scan.", |
| 251 | + "Right now no any other purpose except debugging", |
| 252 | + &tempscan_enable, |
| 253 | + false, |
| 254 | + PGC_SUSET, |
| 255 | + 0, |
| 256 | + NULL, |
| 257 | + NULL, |
| 258 | + NULL |
| 259 | + ); |
| 260 | + |
| 261 | + set_rel_pathlist_hook_next = set_rel_pathlist_hook; |
| 262 | + set_rel_pathlist_hook = try_partial_tempscan; |
| 263 | + |
| 264 | + MarkGUCPrefixReserved(MODULENAME); |
| 265 | +} |
0 commit comments