Commit 0b03e595 authored by Robert Haas's avatar Robert Haas

Introduce custom path and scan providers.

This allows extension modules to define their own methods for
scanning a relation, and get the core code to use them.  It's
unclear as yet how much use this capability will find, but we
won't find out if we never commit it.

KaiGai Kohei, reviewed at various times and in various levels
of detail by Shigeru Hanada, Tom Lane, Andres Freund, Álvaro
Herrera, and myself.
parent 7250d853
...@@ -724,6 +724,7 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used) ...@@ -724,6 +724,7 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
case T_CteScan: case T_CteScan:
case T_WorkTableScan: case T_WorkTableScan:
case T_ForeignScan: case T_ForeignScan:
case T_CustomScan:
*rels_used = bms_add_member(*rels_used, *rels_used = bms_add_member(*rels_used,
((Scan *) plan)->scanrelid); ((Scan *) plan)->scanrelid);
break; break;
...@@ -853,6 +854,7 @@ ExplainNode(PlanState *planstate, List *ancestors, ...@@ -853,6 +854,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
const char *sname; /* node type name for non-text output */ const char *sname; /* node type name for non-text output */
const char *strategy = NULL; const char *strategy = NULL;
const char *operation = NULL; const char *operation = NULL;
const char *custom_name = NULL;
int save_indent = es->indent; int save_indent = es->indent;
bool haschildren; bool haschildren;
...@@ -941,6 +943,14 @@ ExplainNode(PlanState *planstate, List *ancestors, ...@@ -941,6 +943,14 @@ ExplainNode(PlanState *planstate, List *ancestors,
case T_ForeignScan: case T_ForeignScan:
pname = sname = "Foreign Scan"; pname = sname = "Foreign Scan";
break; break;
case T_CustomScan:
sname = "Custom Scan";
custom_name = ((CustomScan *) plan)->methods->CustomName;
if (custom_name)
pname = psprintf("Custom Scan (%s)", custom_name);
else
pname = sname;
break;
case T_Material: case T_Material:
pname = sname = "Materialize"; pname = sname = "Materialize";
break; break;
...@@ -1042,6 +1052,8 @@ ExplainNode(PlanState *planstate, List *ancestors, ...@@ -1042,6 +1052,8 @@ ExplainNode(PlanState *planstate, List *ancestors,
ExplainPropertyText("Parent Relationship", relationship, es); ExplainPropertyText("Parent Relationship", relationship, es);
if (plan_name) if (plan_name)
ExplainPropertyText("Subplan Name", plan_name, es); ExplainPropertyText("Subplan Name", plan_name, es);
if (custom_name)
ExplainPropertyText("Custom Plan Provider", custom_name, es);
} }
switch (nodeTag(plan)) switch (nodeTag(plan))
...@@ -1055,6 +1067,7 @@ ExplainNode(PlanState *planstate, List *ancestors, ...@@ -1055,6 +1067,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
case T_CteScan: case T_CteScan:
case T_WorkTableScan: case T_WorkTableScan:
case T_ForeignScan: case T_ForeignScan:
case T_CustomScan:
ExplainScanTarget((Scan *) plan, es); ExplainScanTarget((Scan *) plan, es);
break; break;
case T_IndexScan: case T_IndexScan:
...@@ -1358,6 +1371,18 @@ ExplainNode(PlanState *planstate, List *ancestors, ...@@ -1358,6 +1371,18 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es); planstate, es);
show_foreignscan_info((ForeignScanState *) planstate, es); show_foreignscan_info((ForeignScanState *) planstate, es);
break; break;
case T_CustomScan:
{
CustomScanState *css = (CustomScanState *) planstate;
show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
if (plan->qual)
show_instrumentation_count("Rows Removed by Filter", 1,
planstate, es);
if (css->methods->ExplainCustomScan)
css->methods->ExplainCustomScan(css, ancestors, es);
}
break;
case T_NestLoop: case T_NestLoop:
show_upper_qual(((NestLoop *) plan)->join.joinqual, show_upper_qual(((NestLoop *) plan)->join.joinqual,
"Join Filter", planstate, ancestors, es); "Join Filter", planstate, ancestors, es);
......
...@@ -16,7 +16,7 @@ OBJS = execAmi.o execCurrent.o execGrouping.o execJunk.o execMain.o \ ...@@ -16,7 +16,7 @@ OBJS = execAmi.o execCurrent.o execGrouping.o execJunk.o execMain.o \
execProcnode.o execQual.o execScan.o execTuples.o \ execProcnode.o execQual.o execScan.o execTuples.o \
execUtils.o functions.o instrument.o nodeAppend.o nodeAgg.o \ execUtils.o functions.o instrument.o nodeAppend.o nodeAgg.o \
nodeBitmapAnd.o nodeBitmapOr.o \ nodeBitmapAnd.o nodeBitmapOr.o \
nodeBitmapHeapscan.o nodeBitmapIndexscan.o nodeHash.o \ nodeBitmapHeapscan.o nodeBitmapIndexscan.o nodeCustom.o nodeHash.o \
nodeHashjoin.o nodeIndexscan.o nodeIndexonlyscan.o \ nodeHashjoin.o nodeIndexscan.o nodeIndexonlyscan.o \
nodeLimit.o nodeLockRows.o \ nodeLimit.o nodeLockRows.o \
nodeMaterial.o nodeMergeAppend.o nodeMergejoin.o nodeModifyTable.o \ nodeMaterial.o nodeMergeAppend.o nodeMergejoin.o nodeModifyTable.o \
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
#include "executor/nodeBitmapIndexscan.h" #include "executor/nodeBitmapIndexscan.h"
#include "executor/nodeBitmapOr.h" #include "executor/nodeBitmapOr.h"
#include "executor/nodeCtescan.h" #include "executor/nodeCtescan.h"
#include "executor/nodeCustom.h"
#include "executor/nodeForeignscan.h" #include "executor/nodeForeignscan.h"
#include "executor/nodeFunctionscan.h" #include "executor/nodeFunctionscan.h"
#include "executor/nodeGroup.h" #include "executor/nodeGroup.h"
...@@ -49,6 +50,7 @@ ...@@ -49,6 +50,7 @@
#include "executor/nodeWindowAgg.h" #include "executor/nodeWindowAgg.h"
#include "executor/nodeWorktablescan.h" #include "executor/nodeWorktablescan.h"
#include "nodes/nodeFuncs.h" #include "nodes/nodeFuncs.h"
#include "nodes/relation.h"
#include "utils/rel.h" #include "utils/rel.h"
#include "utils/syscache.h" #include "utils/syscache.h"
...@@ -197,6 +199,10 @@ ExecReScan(PlanState *node) ...@@ -197,6 +199,10 @@ ExecReScan(PlanState *node)
ExecReScanForeignScan((ForeignScanState *) node); ExecReScanForeignScan((ForeignScanState *) node);
break; break;
case T_CustomScanState:
ExecReScanCustomScan((CustomScanState *) node);
break;
case T_NestLoopState: case T_NestLoopState:
ExecReScanNestLoop((NestLoopState *) node); ExecReScanNestLoop((NestLoopState *) node);
break; break;
...@@ -291,6 +297,10 @@ ExecMarkPos(PlanState *node) ...@@ -291,6 +297,10 @@ ExecMarkPos(PlanState *node)
ExecValuesMarkPos((ValuesScanState *) node); ExecValuesMarkPos((ValuesScanState *) node);
break; break;
case T_CustomScanState:
ExecCustomMarkPos((CustomScanState *) node);
break;
case T_MaterialState: case T_MaterialState:
ExecMaterialMarkPos((MaterialState *) node); ExecMaterialMarkPos((MaterialState *) node);
break; break;
...@@ -348,6 +358,10 @@ ExecRestrPos(PlanState *node) ...@@ -348,6 +358,10 @@ ExecRestrPos(PlanState *node)
ExecValuesRestrPos((ValuesScanState *) node); ExecValuesRestrPos((ValuesScanState *) node);
break; break;
case T_CustomScanState:
ExecCustomRestrPos((CustomScanState *) node);
break;
case T_MaterialState: case T_MaterialState:
ExecMaterialRestrPos((MaterialState *) node); ExecMaterialRestrPos((MaterialState *) node);
break; break;
...@@ -379,9 +393,9 @@ ExecRestrPos(PlanState *node) ...@@ -379,9 +393,9 @@ ExecRestrPos(PlanState *node)
* and valuesscan support is actually useless code at present.) * and valuesscan support is actually useless code at present.)
*/ */
bool bool
ExecSupportsMarkRestore(NodeTag plantype) ExecSupportsMarkRestore(Path *pathnode)
{ {
switch (plantype) switch (pathnode->pathtype)
{ {
case T_SeqScan: case T_SeqScan:
case T_IndexScan: case T_IndexScan:
...@@ -403,6 +417,16 @@ ExecSupportsMarkRestore(NodeTag plantype) ...@@ -403,6 +417,16 @@ ExecSupportsMarkRestore(NodeTag plantype)
*/ */
return false; return false;
case T_CustomScan:
{
CustomPath *cpath = (CustomPath *) pathnode;
Assert(IsA(cpath, CustomPath));
if (cpath->flags & CUSTOMPATH_SUPPORT_MARK_RESTORE)
return true;
}
break;
default: default:
break; break;
} }
...@@ -465,6 +489,16 @@ ExecSupportsBackwardScan(Plan *node) ...@@ -465,6 +489,16 @@ ExecSupportsBackwardScan(Plan *node)
return ExecSupportsBackwardScan(((SubqueryScan *) node)->subplan) && return ExecSupportsBackwardScan(((SubqueryScan *) node)->subplan) &&
TargetListSupportsBackwardScan(node->targetlist); TargetListSupportsBackwardScan(node->targetlist);
case T_CustomScan:
{
uint32 flags = ((CustomScan *) node)->flags;
if (TargetListSupportsBackwardScan(node->targetlist) &&
(flags & CUSTOMPATH_SUPPORT_BACKWARD_SCAN) != 0)
return true;
}
return false;
case T_Material: case T_Material:
case T_Sort: case T_Sort:
/* these don't evaluate tlist */ /* these don't evaluate tlist */
......
...@@ -85,6 +85,7 @@ ...@@ -85,6 +85,7 @@
#include "executor/nodeBitmapIndexscan.h" #include "executor/nodeBitmapIndexscan.h"
#include "executor/nodeBitmapOr.h" #include "executor/nodeBitmapOr.h"
#include "executor/nodeCtescan.h" #include "executor/nodeCtescan.h"
#include "executor/nodeCustom.h"
#include "executor/nodeForeignscan.h" #include "executor/nodeForeignscan.h"
#include "executor/nodeFunctionscan.h" #include "executor/nodeFunctionscan.h"
#include "executor/nodeGroup.h" #include "executor/nodeGroup.h"
...@@ -244,6 +245,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags) ...@@ -244,6 +245,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
estate, eflags); estate, eflags);
break; break;
case T_CustomScan:
result = (PlanState *) ExecInitCustomScan((CustomScan *) node,
estate, eflags);
break;
/* /*
* join nodes * join nodes
*/ */
...@@ -442,6 +448,10 @@ ExecProcNode(PlanState *node) ...@@ -442,6 +448,10 @@ ExecProcNode(PlanState *node)
result = ExecForeignScan((ForeignScanState *) node); result = ExecForeignScan((ForeignScanState *) node);
break; break;
case T_CustomScanState:
result = ExecCustomScan((CustomScanState *) node);
break;
/* /*
* join nodes * join nodes
*/ */
...@@ -678,6 +688,10 @@ ExecEndNode(PlanState *node) ...@@ -678,6 +688,10 @@ ExecEndNode(PlanState *node)
ExecEndForeignScan((ForeignScanState *) node); ExecEndForeignScan((ForeignScanState *) node);
break; break;
case T_CustomScanState:
ExecEndCustomScan((CustomScanState *) node);
break;
/* /*
* join nodes * join nodes
*/ */
......
/* ------------------------------------------------------------------------
*
* nodeCustom.c
* Routines to handle execution of custom scan node
*
* Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* ------------------------------------------------------------------------
*/
#include "postgres.h"
#include "executor/executor.h"
#include "executor/nodeCustom.h"
#include "nodes/execnodes.h"
#include "nodes/plannodes.h"
#include "parser/parsetree.h"
#include "utils/hsearch.h"
#include "utils/memutils.h"
#include "utils/rel.h"
CustomScanState *
ExecInitCustomScan(CustomScan *cscan, EState *estate, int eflags)
{
CustomScanState *css;
Relation scan_rel;
/* populate a CustomScanState according to the CustomScan */
css = (CustomScanState *) cscan->methods->CreateCustomScanState(cscan);
Assert(IsA(css, CustomScanState));
/* fill up fields of ScanState */
css->ss.ps.plan = &cscan->scan.plan;
css->ss.ps.state = estate;
/* create expression context for node */
ExecAssignExprContext(estate, &css->ss.ps);
/* initialize child expressions */
css->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) cscan->scan.plan.targetlist,
(PlanState *) css);
css->ss.ps.qual = (List *)
ExecInitExpr((Expr *) cscan->scan.plan.qual,
(PlanState *) css);
/* tuple table initialization */
ExecInitScanTupleSlot(estate, &css->ss);
ExecInitResultTupleSlot(estate, &css->ss.ps);
/* initialize scan relation */
scan_rel = ExecOpenScanRelation(estate, cscan->scan.scanrelid, eflags);
css->ss.ss_currentRelation = scan_rel;
css->ss.ss_currentScanDesc = NULL; /* set by provider */
ExecAssignScanType(&css->ss, RelationGetDescr(scan_rel));
css->ss.ps.ps_TupFromTlist = false;
/*
* Initialize result tuple type and projection info.
*/
ExecAssignResultTypeFromTL(&css->ss.ps);
ExecAssignScanProjectionInfo(&css->ss);
/*
* The callback of custom-scan provider applies the final initialization
* of the custom-scan-state node according to its logic.
*/
css->methods->BeginCustomScan(css, estate, eflags);
return css;
}
TupleTableSlot *
ExecCustomScan(CustomScanState *node)
{
Assert(node->methods->ExecCustomScan != NULL);
return node->methods->ExecCustomScan(node);
}
void
ExecEndCustomScan(CustomScanState *node)
{
Assert(node->methods->EndCustomScan != NULL);
node->methods->EndCustomScan(node);
/* Free the exprcontext */
ExecFreeExprContext(&node->ss.ps);
/* Clean out the tuple table */
ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
if (node->ss.ss_ScanTupleSlot)
ExecClearTuple(node->ss.ss_ScanTupleSlot);
/* Close the heap relation */
ExecCloseScanRelation(node->ss.ss_currentRelation);
}
void
ExecReScanCustomScan(CustomScanState *node)
{
Assert(node->methods->ReScanCustomScan != NULL);
node->methods->ReScanCustomScan(node);
}
void
ExecCustomMarkPos(CustomScanState *node)
{
if (!node->methods->MarkPosCustomScan)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("custom-scan \"%s\" does not support MarkPos",
node->methods->CustomName)));
node->methods->MarkPosCustomScan(node);
}
void
ExecCustomRestrPos(CustomScanState *node)
{
if (!node->methods->RestrPosCustomScan)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("custom-scan \"%s\" does not support MarkPos",
node->methods->CustomName)));
node->methods->RestrPosCustomScan(node);
}
...@@ -597,6 +597,29 @@ _copyForeignScan(const ForeignScan *from) ...@@ -597,6 +597,29 @@ _copyForeignScan(const ForeignScan *from)
return newnode; return newnode;
} }
/*
* _copyCustomScan
*/
static CustomScan *
_copyCustomScan(const CustomScan *from)
{
CustomScan *newnode;
newnode = from->methods->CopyCustomScan(from);
Assert(nodeTag(newnode) == nodeTag(from));
CopyScanFields((const Scan *) from, (Scan *) newnode);
COPY_SCALAR_FIELD(flags);
/*
* NOTE: The method field of CustomScan is required to be a pointer
* to a static table of callback functions. So, we don't copy the
* table itself, just reference the original one.
*/
COPY_SCALAR_FIELD(methods);
return newnode;
}
/* /*
* CopyJoinFields * CopyJoinFields
* *
...@@ -4043,6 +4066,9 @@ copyObject(const void *from) ...@@ -4043,6 +4066,9 @@ copyObject(const void *from)
case T_ForeignScan: case T_ForeignScan:
retval = _copyForeignScan(from); retval = _copyForeignScan(from);
break; break;
case T_CustomScan:
retval = _copyCustomScan(from);
break;
case T_Join: case T_Join:
retval = _copyJoin(from); retval = _copyJoin(from);
break; break;
......
...@@ -563,6 +563,18 @@ _outForeignScan(StringInfo str, const ForeignScan *node) ...@@ -563,6 +563,18 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
WRITE_BOOL_FIELD(fsSystemCol); WRITE_BOOL_FIELD(fsSystemCol);
} }
static void
_outCustomScan(StringInfo str, const CustomScan *node)
{
WRITE_NODE_TYPE("CUSTOMSCAN");
_outScanInfo(str, (const Scan *) node);
WRITE_UINT_FIELD(flags);
appendStringInfo(str, " :methods");
_outToken(str, node->methods->CustomName);
node->methods->TextOutCustomScan(str, node);
}
static void static void
_outJoin(StringInfo str, const Join *node) _outJoin(StringInfo str, const Join *node)
{ {
...@@ -1584,6 +1596,17 @@ _outForeignPath(StringInfo str, const ForeignPath *node) ...@@ -1584,6 +1596,17 @@ _outForeignPath(StringInfo str, const ForeignPath *node)
WRITE_NODE_FIELD(fdw_private); WRITE_NODE_FIELD(fdw_private);
} }
static void
_outCustomPath(StringInfo str, const CustomPath *node)
{
WRITE_NODE_TYPE("CUSTOMPATH");
_outPathInfo(str, (const Path *) node);
WRITE_UINT_FIELD(flags);
appendStringInfo(str, " :methods");
_outToken(str, node->methods->CustomName);
node->methods->TextOutCustomPath(str, node);
}
static void static void
_outAppendPath(StringInfo str, const AppendPath *node) _outAppendPath(StringInfo str, const AppendPath *node)
{ {
...@@ -2855,6 +2878,9 @@ _outNode(StringInfo str, const void *obj) ...@@ -2855,6 +2878,9 @@ _outNode(StringInfo str, const void *obj)
case T_ForeignScan: case T_ForeignScan:
_outForeignScan(str, obj); _outForeignScan(str, obj);
break; break;
case T_CustomScan:
_outCustomScan(str, obj);
break;
case T_Join: case T_Join:
_outJoin(str, obj); _outJoin(str, obj);
break; break;
...@@ -3063,6 +3089,9 @@ _outNode(StringInfo str, const void *obj) ...@@ -3063,6 +3089,9 @@ _outNode(StringInfo str, const void *obj)
case T_ForeignPath: case T_ForeignPath:
_outForeignPath(str, obj); _outForeignPath(str, obj);
break; break;
case T_CustomPath:
_outCustomPath(str, obj);
break;
case T_AppendPath: case T_AppendPath:
_outAppendPath(str, obj); _outAppendPath(str, obj);
break; break;
......
...@@ -402,6 +402,9 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) ...@@ -402,6 +402,9 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
/* Consider TID scans */ /* Consider TID scans */
create_tidscan_paths(root, rel); create_tidscan_paths(root, rel);
/* Consider custom scans, if any */
create_customscan_paths(root, rel, rte);
/* Now find the cheapest of the paths for this rel */ /* Now find the cheapest of the paths for this rel */
set_cheapest(rel); set_cheapest(rel);
} }
......
...@@ -2266,7 +2266,7 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path, ...@@ -2266,7 +2266,7 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
* it off does not entitle us to deliver an invalid plan. * it off does not entitle us to deliver an invalid plan.
*/ */
else if (innersortkeys == NIL && else if (innersortkeys == NIL &&
!ExecSupportsMarkRestore(inner_path->pathtype)) !ExecSupportsMarkRestore(inner_path))
path->materialize_inner = true; path->materialize_inner = true;
/* /*
......
...@@ -77,13 +77,15 @@ static WorkTableScan *create_worktablescan_plan(PlannerInfo *root, Path *best_pa ...@@ -77,13 +77,15 @@ static WorkTableScan *create_worktablescan_plan(PlannerInfo *root, Path *best_pa
List *tlist, List *scan_clauses); List *tlist, List *scan_clauses);
static ForeignScan *create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path, static ForeignScan *create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
List *tlist, List *scan_clauses); List *tlist, List *scan_clauses);
static Plan *create_customscan_plan(PlannerInfo *root,
CustomPath *best_path,
List *tlist, List *scan_clauses);
static NestLoop *create_nestloop_plan(PlannerInfo *root, NestPath *best_path, static NestLoop *create_nestloop_plan(PlannerInfo *root, NestPath *best_path,
Plan *outer_plan, Plan *inner_plan); Plan *outer_plan, Plan *inner_plan);
static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path, static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path,
Plan *outer_plan, Plan *inner_plan); Plan *outer_plan, Plan *inner_plan);
static HashJoin *create_hashjoin_plan(PlannerInfo *root, HashPath *best_path, static HashJoin *create_hashjoin_plan(PlannerInfo *root, HashPath *best_path,
Plan *outer_plan, Plan *inner_plan); Plan *outer_plan, Plan *inner_plan);
static Node *replace_nestloop_params(PlannerInfo *root, Node *expr);
static Node *replace_nestloop_params_mutator(Node *node, PlannerInfo *root); static Node *replace_nestloop_params_mutator(Node *node, PlannerInfo *root);
static void process_subquery_nestloop_params(PlannerInfo *root, static void process_subquery_nestloop_params(PlannerInfo *root,
List *subplan_params); List *subplan_params);
...@@ -233,6 +235,7 @@ create_plan_recurse(PlannerInfo *root, Path *best_path) ...@@ -233,6 +235,7 @@ create_plan_recurse(PlannerInfo *root, Path *best_path)
case T_CteScan: case T_CteScan:
case T_WorkTableScan: case T_WorkTableScan:
case T_ForeignScan: case T_ForeignScan:
case T_CustomScan:
plan = create_scan_plan(root, best_path); plan = create_scan_plan(root, best_path);
break; break;
case T_HashJoin: case T_HashJoin:
...@@ -409,6 +412,13 @@ create_scan_plan(PlannerInfo *root, Path *best_path) ...@@ -409,6 +412,13 @@ create_scan_plan(PlannerInfo *root, Path *best_path)
scan_clauses); scan_clauses);
break; break;
case T_CustomScan:
plan = create_customscan_plan(root,
(CustomPath *) best_path,
tlist,
scan_clauses);
break;
default: default:
elog(ERROR, "unrecognized node type: %d", elog(ERROR, "unrecognized node type: %d",
(int) best_path->pathtype); (int) best_path->pathtype);
...@@ -1072,6 +1082,52 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path) ...@@ -1072,6 +1082,52 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path)
return plan; return plan;
} }
/*
* create_custom_plan
*
* Transform a CustomPath into a Plan.
*/
static Plan *
create_customscan_plan(PlannerInfo *root, CustomPath *best_path,
List *tlist, List *scan_clauses)
{
Plan *plan;
RelOptInfo *rel = best_path->path.parent;
/*
* Right now, all we can support is CustomScan node which is associated
* with a particular base relation to be scanned.
*/
Assert(rel && rel->reloptkind == RELOPT_BASEREL);
/*
* Sort clauses into the best execution order, although custom-scan
* provider can reorder them again.
*/
scan_clauses = order_qual_clauses(root, scan_clauses);
/*
* Create a CustomScan (or its inheritance) node according to
* the supplied CustomPath.
*/
plan = best_path->methods->PlanCustomPath(root, rel, best_path, tlist,
scan_clauses);
/*
* NOTE: unlike create_foreignscan_plan(), it is responsibility of
* the custom plan provider to replace outer-relation variables
* with nestloop params, because we cannot know how many expression
* trees are held in the private fields.
*/
/*
* Copy cost data from Path to Plan; no need to make custom-plan
* providers do this
*/
copy_path_costsize(plan, &best_path->path);
return plan;
}
/***************************************************************************** /*****************************************************************************
* *
...@@ -2540,7 +2596,7 @@ create_hashjoin_plan(PlannerInfo *root, ...@@ -2540,7 +2596,7 @@ create_hashjoin_plan(PlannerInfo *root,
* root->curOuterRels are replaced by Params, and entries are added to * root->curOuterRels are replaced by Params, and entries are added to
* root->curOuterParams if not already present. * root->curOuterParams if not already present.
*/ */
static Node * Node *
replace_nestloop_params(PlannerInfo *root, Node *expr) replace_nestloop_params(PlannerInfo *root, Node *expr)
{ {
/* No setup needed for tree walk, so away we go */ /* No setup needed for tree walk, so away we go */
......
...@@ -94,7 +94,6 @@ static Plan *set_subqueryscan_references(PlannerInfo *root, ...@@ -94,7 +94,6 @@ static Plan *set_subqueryscan_references(PlannerInfo *root,
SubqueryScan *plan, SubqueryScan *plan,
int rtoffset); int rtoffset);
static bool trivial_subqueryscan(SubqueryScan *plan); static bool trivial_subqueryscan(SubqueryScan *plan);
static Node *fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset);
static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context); static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context); static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
static void set_join_references(PlannerInfo *root, Join *join, int rtoffset); static void set_join_references(PlannerInfo *root, Join *join, int rtoffset);
...@@ -579,6 +578,27 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) ...@@ -579,6 +578,27 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
} }
break; break;
case T_CustomScan:
{
CustomScan *cscan = (CustomScan *) plan;
cscan->scan.scanrelid += rtoffset;
cscan->scan.plan.targetlist =
fix_scan_list(root, cscan->scan.plan.targetlist, rtoffset);
cscan->scan.plan.qual =
fix_scan_list(root, cscan->scan.plan.qual, rtoffset);
/*
* The core implementation applies the routine to fixup
* varno on the target-list and scan qualifier.
* If custom-scan has additional expression nodes on its
* private fields, it has to apply same fixup on them.
* Otherwise, the custom-plan provider can skip this callback.
*/
if (cscan->methods->SetCustomScanRef)
cscan->methods->SetCustomScanRef(root, cscan, rtoffset);
}
break;
case T_NestLoop: case T_NestLoop:
case T_MergeJoin: case T_MergeJoin:
case T_HashJoin: case T_HashJoin:
...@@ -1063,7 +1083,7 @@ copyVar(Var *var) ...@@ -1063,7 +1083,7 @@ copyVar(Var *var)
* We assume it's okay to update opcode info in-place. So this could possibly * We assume it's okay to update opcode info in-place. So this could possibly
* scribble on the planner's input data structures, but it's OK. * scribble on the planner's input data structures, but it's OK.
*/ */
static void void
fix_expr_common(PlannerInfo *root, Node *node) fix_expr_common(PlannerInfo *root, Node *node)
{ {
/* We assume callers won't call us on a NULL pointer */ /* We assume callers won't call us on a NULL pointer */
...@@ -1161,7 +1181,7 @@ fix_param_node(PlannerInfo *root, Param *p) ...@@ -1161,7 +1181,7 @@ fix_param_node(PlannerInfo *root, Param *p)
* looking up operator opcode info for OpExpr and related nodes, * looking up operator opcode info for OpExpr and related nodes,
* and adding OIDs from regclass Const nodes into root->glob->relationOids. * and adding OIDs from regclass Const nodes into root->glob->relationOids.
*/ */
static Node * Node *
fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset) fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset)
{ {
fix_scan_expr_context context; fix_scan_expr_context context;
......
...@@ -2283,6 +2283,27 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params, ...@@ -2283,6 +2283,27 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
context.paramids = bms_add_members(context.paramids, scan_params); context.paramids = bms_add_members(context.paramids, scan_params);
break; break;
case T_CustomScan:
{
CustomScan *custom_scan = (CustomScan *) plan;
context.paramids = bms_add_members(context.paramids,
scan_params);
/*
* custom-scan provider is responsible to apply
* finalize_primnode() on the expression node of
* its private fields, but no need to apply it
* on the tlist and qual of Plan node because it
* is already done above.
*/
if (custom_scan->methods->FinalizeCustomScan)
custom_scan->methods->FinalizeCustomScan(root,
custom_scan,
finalize_primnode,
(void *)&context);
}
break;
case T_ModifyTable: case T_ModifyTable:
{ {
ModifyTable *mtplan = (ModifyTable *) plan; ModifyTable *mtplan = (ModifyTable *) plan;
......
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
#include "optimizer/var.h" #include "optimizer/var.h"
#include "parser/parsetree.h" #include "parser/parsetree.h"
#include "utils/lsyscache.h" #include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/selfuncs.h" #include "utils/selfuncs.h"
...@@ -1926,3 +1927,49 @@ reparameterize_path(PlannerInfo *root, Path *path, ...@@ -1926,3 +1927,49 @@ reparameterize_path(PlannerInfo *root, Path *path,
} }
return NULL; return NULL;
} }
/*****************************************************************************
* creation of custom-plan paths
*****************************************************************************/
static List *custom_path_providers = NIL;
/*
* register_custom_path_provider
*
* Register a table of callback functions which implements a custom-path
* provider. This allows extension to provide additional (hopefully faster)
* methods of scanning a relation.
*/
void
register_custom_path_provider(CustomPathMethods *cpp_methods)
{
MemoryContext oldcxt;
oldcxt = MemoryContextSwitchTo(TopMemoryContext);
custom_path_providers = lappend(custom_path_providers, cpp_methods);
MemoryContextSwitchTo(oldcxt);
}
/*
* create_customscan_paths
*
* Invoke custom path provider callbacks. If the callback determines that
* the custom-path provider can handle this relation, it can add one or more
* paths using add_path().
*/
void
create_customscan_paths(PlannerInfo *root,
RelOptInfo *baserel,
RangeTblEntry *rte)
{
ListCell *cell;
foreach (cell, custom_path_providers)
{
const CustomPathMethods *cpp_methods = lfirst(cell);
if (cpp_methods->CreateCustomScanPath)
cpp_methods->CreateCustomScanPath(root, baserel, rte);
}
}
...@@ -5493,6 +5493,26 @@ get_utility_query_def(Query *query, deparse_context *context) ...@@ -5493,6 +5493,26 @@ get_utility_query_def(Query *query, deparse_context *context)
} }
} }
/*
* GetSpecialCustomVar
*
* If a custom-scan provider uses a special varnode, this function will be
* called when deparsing; it should return an Expr node to be reversed-listed
* in lieu of the special Var.
*/
static Node *
GetSpecialCustomVar(CustomScanState *css, Var *varnode, PlanState **child_ps)
{
Assert(IsA(css, CustomScanState));
Assert(IS_SPECIAL_VARNO(varnode->varno));
if (!css->methods->GetSpecialCustomVar)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("%s does not support special varno reference",
css->methods->CustomName)));
return (Node *) css->methods->GetSpecialCustomVar(css, varnode, child_ps);
}
/* /*
* Display a Var appropriately. * Display a Var appropriately.
...@@ -5522,6 +5542,8 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context) ...@@ -5522,6 +5542,8 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
int netlevelsup; int netlevelsup;
deparse_namespace *dpns; deparse_namespace *dpns;
deparse_columns *colinfo; deparse_columns *colinfo;
PlanState *child_ps = NULL;
Node *expr;
char *refname; char *refname;
char *attname; char *attname;
...@@ -5546,6 +5568,29 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context) ...@@ -5546,6 +5568,29 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
colinfo = deparse_columns_fetch(var->varno, dpns); colinfo = deparse_columns_fetch(var->varno, dpns);
attnum = var->varattno; attnum = var->varattno;
} }
else if (IS_SPECIAL_VARNO(var->varno) &&
IsA(dpns->planstate, CustomScanState) &&
(expr = GetSpecialCustomVar((CustomScanState *) dpns->planstate,
var, &child_ps)) != NULL)
{
deparse_namespace save_dpns;
if (child_ps)
push_child_plan(dpns, child_ps, &save_dpns);
/*
* Force parentheses because our caller probably assumed a Var is a
* simple expression.
*/
if (!IsA(expr, Var))
appendStringInfoChar(buf, '(');
get_rule_expr((Node *) expr, context, true);
if (!IsA(expr, Var))
appendStringInfoChar(buf, ')');
if (child_ps)
pop_child_plan(dpns, &save_dpns);
return NULL;
}
else if (var->varno == OUTER_VAR && dpns->outer_tlist) else if (var->varno == OUTER_VAR && dpns->outer_tlist)
{ {
TargetEntry *tle; TargetEntry *tle;
...@@ -5760,6 +5805,7 @@ get_name_for_var_field(Var *var, int fieldno, ...@@ -5760,6 +5805,7 @@ get_name_for_var_field(Var *var, int fieldno,
AttrNumber attnum; AttrNumber attnum;
int netlevelsup; int netlevelsup;
deparse_namespace *dpns; deparse_namespace *dpns;
PlanState *child_ps = NULL;
TupleDesc tupleDesc; TupleDesc tupleDesc;
Node *expr; Node *expr;
...@@ -5834,6 +5880,30 @@ get_name_for_var_field(Var *var, int fieldno, ...@@ -5834,6 +5880,30 @@ get_name_for_var_field(Var *var, int fieldno,
rte = rt_fetch(var->varno, dpns->rtable); rte = rt_fetch(var->varno, dpns->rtable);
attnum = var->varattno; attnum = var->varattno;
} }
else if (IS_SPECIAL_VARNO(var->varno) &&
IsA(dpns->planstate, CustomScanState) &&
(expr = GetSpecialCustomVar((CustomScanState *) dpns->planstate,
var, &child_ps)) != NULL)
{
StringInfo saved = context->buf;
StringInfoData temp;
deparse_namespace save_dpns;
initStringInfo(&temp);
context->buf = &temp;
if (child_ps)
push_child_plan(dpns, child_ps, &save_dpns);
if (!IsA(expr, Var))
appendStringInfoChar(context->buf, '(');
get_rule_expr((Node *) expr, context, true);
if (!IsA(expr, Var))
appendStringInfoChar(context->buf, ')');
if (child_ps)
pop_child_plan(dpns, &save_dpns);
context->buf = saved;
return temp.data;
}
else if (var->varno == OUTER_VAR && dpns->outer_tlist) else if (var->varno == OUTER_VAR && dpns->outer_tlist)
{ {
TargetEntry *tle; TargetEntry *tle;
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#include "executor/execdesc.h" #include "executor/execdesc.h"
#include "nodes/parsenodes.h" #include "nodes/parsenodes.h"
#include "nodes/relation.h"
#include "utils/lockwaitpolicy.h" #include "utils/lockwaitpolicy.h"
...@@ -103,7 +104,7 @@ extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook; ...@@ -103,7 +104,7 @@ extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
extern void ExecReScan(PlanState *node); extern void ExecReScan(PlanState *node);
extern void ExecMarkPos(PlanState *node); extern void ExecMarkPos(PlanState *node);
extern void ExecRestrPos(PlanState *node); extern void ExecRestrPos(PlanState *node);
extern bool ExecSupportsMarkRestore(NodeTag plantype); extern bool ExecSupportsMarkRestore(Path *pathnode);
extern bool ExecSupportsBackwardScan(Plan *node); extern bool ExecSupportsBackwardScan(Plan *node);
extern bool ExecMaterializesOutput(NodeTag plantype); extern bool ExecMaterializesOutput(NodeTag plantype);
......
/* ------------------------------------------------------------------------
*
* nodeCustom.h
*
* prototypes for CustomScan nodes
*
* Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* ------------------------------------------------------------------------
*/
#ifndef NODECUSTOM_H
#define NODECUSTOM_H
#include "nodes/plannodes.h"
#include "nodes/execnodes.h"
/*
* General executor code
*/
extern CustomScanState *ExecInitCustomScan(CustomScan *custom_scan,
EState *estate, int eflags);
extern TupleTableSlot *ExecCustomScan(CustomScanState *node);
extern Node *MultiExecCustomScan(CustomScanState *node);
extern void ExecEndCustomScan(CustomScanState *node);
extern void ExecReScanCustomScan(CustomScanState *node);
extern void ExecCustomMarkPos(CustomScanState *node);
extern void ExecCustomRestrPos(CustomScanState *node);
#endif /* NODECUSTOM_H */
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "executor/instrument.h" #include "executor/instrument.h"
#include "nodes/params.h" #include "nodes/params.h"
#include "nodes/plannodes.h" #include "nodes/plannodes.h"
#include "nodes/relation.h"
#include "utils/reltrigger.h" #include "utils/reltrigger.h"
#include "utils/sortsupport.h" #include "utils/sortsupport.h"
#include "utils/tuplestore.h" #include "utils/tuplestore.h"
...@@ -1504,6 +1505,45 @@ typedef struct ForeignScanState ...@@ -1504,6 +1505,45 @@ typedef struct ForeignScanState
void *fdw_state; /* foreign-data wrapper can keep state here */ void *fdw_state; /* foreign-data wrapper can keep state here */
} ForeignScanState; } ForeignScanState;
/* ----------------
* CustomScanState information
*
* CustomScan nodes are used to execute custom code within executor.
* ----------------
*/
struct CustomExecMethods;
struct ExplainState; /* to avoid to include explain.h here */
typedef struct CustomScanState
{
ScanState ss;
uint32 flags; /* mask of CUSTOMPATH_* flags defined in relation.h*/
const struct CustomExecMethods *methods;
} CustomScanState;
typedef struct CustomExecMethods
{
const char *CustomName;
/* EXECUTOR methods */
void (*BeginCustomScan)(CustomScanState *node,
EState *estate,
int eflags);
TupleTableSlot *(*ExecCustomScan)(CustomScanState *node);
void (*EndCustomScan)(CustomScanState *node);
void (*ReScanCustomScan)(CustomScanState *node);
void (*MarkPosCustomScan)(CustomScanState *node);
void (*RestrPosCustomScan)(CustomScanState *node);
/* EXPLAIN support */
void (*ExplainCustomScan)(CustomScanState *node,
List *ancestors,
struct ExplainState *es);
Node *(*GetSpecialCustomVar)(CustomScanState *node,
Var *varnode,
PlanState **child_ps);
} CustomExecMethods;
/* ---------------------------------------------------------------- /* ----------------------------------------------------------------
* Join State Information * Join State Information
* ---------------------------------------------------------------- * ----------------------------------------------------------------
......
...@@ -62,6 +62,7 @@ typedef enum NodeTag ...@@ -62,6 +62,7 @@ typedef enum NodeTag
T_CteScan, T_CteScan,
T_WorkTableScan, T_WorkTableScan,
T_ForeignScan, T_ForeignScan,
T_CustomScan,
T_Join, T_Join,
T_NestLoop, T_NestLoop,
T_MergeJoin, T_MergeJoin,
...@@ -107,6 +108,7 @@ typedef enum NodeTag ...@@ -107,6 +108,7 @@ typedef enum NodeTag
T_CteScanState, T_CteScanState,
T_WorkTableScanState, T_WorkTableScanState,
T_ForeignScanState, T_ForeignScanState,
T_CustomScanState,
T_JoinState, T_JoinState,
T_NestLoopState, T_NestLoopState,
T_MergeJoinState, T_MergeJoinState,
...@@ -224,6 +226,7 @@ typedef enum NodeTag ...@@ -224,6 +226,7 @@ typedef enum NodeTag
T_HashPath, T_HashPath,
T_TidPath, T_TidPath,
T_ForeignPath, T_ForeignPath,
T_CustomPath,
T_AppendPath, T_AppendPath,
T_MergeAppendPath, T_MergeAppendPath,
T_ResultPath, T_ResultPath,
......
...@@ -15,8 +15,10 @@ ...@@ -15,8 +15,10 @@
#define PLANNODES_H #define PLANNODES_H
#include "access/sdir.h" #include "access/sdir.h"
#include "lib/stringinfo.h"
#include "nodes/bitmapset.h" #include "nodes/bitmapset.h"
#include "nodes/primnodes.h" #include "nodes/primnodes.h"
#include "nodes/relation.h"
#include "utils/lockwaitpolicy.h" #include "utils/lockwaitpolicy.h"
...@@ -483,6 +485,33 @@ typedef struct ForeignScan ...@@ -483,6 +485,33 @@ typedef struct ForeignScan
bool fsSystemCol; /* true if any "system column" is needed */ bool fsSystemCol; /* true if any "system column" is needed */
} ForeignScan; } ForeignScan;
/* ----------------
* CustomScan node
* ----------------
*/
struct CustomScanMethods;
typedef struct CustomScan
{
Scan scan;
uint32 flags; /* mask of CUSTOMPATH_* flags defined in relation.h */
struct CustomScanMethods *methods;
} CustomScan;
typedef struct CustomScanMethods
{
const char *CustomName;
void (*SetCustomScanRef)(struct PlannerInfo *root,
CustomScan *cscan,
int rtoffset);
void (*FinalizeCustomScan)(struct PlannerInfo *root,
CustomScan *cscan,
bool (*finalize_primnode)(),
void *finalize_context);
Node *(*CreateCustomScanState)(CustomScan *cscan);
void (*TextOutCustomScan)(StringInfo str, const CustomScan *node);
CustomScan *(*CopyCustomScan)(const CustomScan *from);
} CustomScanMethods;
/* /*
* ========== * ==========
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#define RELATION_H #define RELATION_H
#include "access/sdir.h" #include "access/sdir.h"
#include "lib/stringinfo.h"
#include "nodes/params.h" #include "nodes/params.h"
#include "nodes/parsenodes.h" #include "nodes/parsenodes.h"
#include "storage/block.h" #include "storage/block.h"
...@@ -883,6 +884,47 @@ typedef struct ForeignPath ...@@ -883,6 +884,47 @@ typedef struct ForeignPath
List *fdw_private; List *fdw_private;
} ForeignPath; } ForeignPath;
/*
* CustomPath represents a scan by some out-of-core extension.
*
* We provide a set of hooks here - which the provider must take care to
* set up correctly - to allow extensions to supply their own methods of
* scanning a relation. For example, a provider might provide GPU
* acceleration, a cache-based scan, or some other kind of logic we haven't
* dreamed up yet.
*
* Core code should avoid assuming that the CustomPath is only as large as
* the structure declared here; providers are expected to make it the first
* element in a larger structure.
*/
struct CustomPathMethods;
struct Plan; /* not to include plannodes.h here */
#define CUSTOMPATH_SUPPORT_BACKWARD_SCAN 0x0001
#define CUSTOMPATH_SUPPORT_MARK_RESTORE 0x0002
typedef struct CustomPath
{
Path path;
uint32 flags;
const struct CustomPathMethods *methods;
} CustomPath;
typedef struct CustomPathMethods
{
const char *CustomName;
void (*CreateCustomScanPath)(PlannerInfo *root,
RelOptInfo *baserel,
RangeTblEntry *rte);
struct Plan *(*PlanCustomPath)(PlannerInfo *root,
RelOptInfo *rel,
CustomPath *best_path,
List *tlist,
List *clauses);
void (*TextOutCustomPath)(StringInfo str, const CustomPath *node);
} CustomPathMethods;
/* /*
* AppendPath represents an Append plan, ie, successive execution of * AppendPath represents an Append plan, ie, successive execution of
* several member plans. * several member plans.
......
...@@ -128,6 +128,15 @@ extern Path *reparameterize_path(PlannerInfo *root, Path *path, ...@@ -128,6 +128,15 @@ extern Path *reparameterize_path(PlannerInfo *root, Path *path,
Relids required_outer, Relids required_outer,
double loop_count); double loop_count);
/*
* Interface definition of custom-scan providers
*/
extern void register_custom_path_provider(CustomPathMethods *cpp_methods);
extern void create_customscan_paths(PlannerInfo *root,
RelOptInfo *baserel,
RangeTblEntry *rte);
/* /*
* prototypes for relnode.c * prototypes for relnode.c
*/ */
......
...@@ -86,6 +86,7 @@ extern ModifyTable *make_modifytable(PlannerInfo *root, ...@@ -86,6 +86,7 @@ extern ModifyTable *make_modifytable(PlannerInfo *root,
List *withCheckOptionLists, List *returningLists, List *withCheckOptionLists, List *returningLists,
List *rowMarks, int epqParam); List *rowMarks, int epqParam);
extern bool is_projection_capable_plan(Plan *plan); extern bool is_projection_capable_plan(Plan *plan);
extern Node *replace_nestloop_params(PlannerInfo *root, Node *expr);
/* /*
* prototypes for plan/initsplan.c * prototypes for plan/initsplan.c
...@@ -130,6 +131,8 @@ extern bool query_is_distinct_for(Query *query, List *colnos, List *opids); ...@@ -130,6 +131,8 @@ extern bool query_is_distinct_for(Query *query, List *colnos, List *opids);
*/ */
extern Plan *set_plan_references(PlannerInfo *root, Plan *plan); extern Plan *set_plan_references(PlannerInfo *root, Plan *plan);
extern void fix_opfuncids(Node *node); extern void fix_opfuncids(Node *node);
extern Node *fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset);
extern void fix_expr_common(PlannerInfo *root, Node *node);
extern void set_opfuncid(OpExpr *opexpr); extern void set_opfuncid(OpExpr *opexpr);
extern void set_sa_opfuncid(ScalarArrayOpExpr *opexpr); extern void set_sa_opfuncid(ScalarArrayOpExpr *opexpr);
extern void record_plan_function_dependency(PlannerInfo *root, Oid funcid); extern void record_plan_function_dependency(PlannerInfo *root, Oid funcid);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment