doc/src/sgml/custom-scan.sgml | 115 +++++++++++++++++++++++++++++--------- src/backend/commands/explain.c | 2 +- src/backend/executor/nodeCustom.c | 4 +- src/backend/nodes/copyfuncs.c | 19 +++++-- src/backend/nodes/nodes.c | 82 +++++++++++++++++++++++++++ src/backend/nodes/outfuncs.c | 30 +++++++--- src/backend/nodes/readfuncs.c | 46 ++++++++++----- src/include/nodes/execnodes.h | 2 +- src/include/nodes/nodes.h | 18 ++++++ src/include/nodes/plannodes.h | 5 +- src/include/nodes/relation.h | 6 +- 11 files changed, 269 insertions(+), 60 deletions(-) diff --git a/doc/src/sgml/custom-scan.sgml b/doc/src/sgml/custom-scan.sgml index 5bba125..933a9bd 100644 --- a/doc/src/sgml/custom-scan.sgml +++ b/doc/src/sgml/custom-scan.sgml @@ -78,14 +78,16 @@ typedef struct CustomPath nodes used by this custom-path node; these will be transformed into Plan nodes by planner. custom_private can be used to store the custom path's - private data. Private data should be stored in a form that can be handled - by nodeToString, so that debugging routines that attempt to - print the custom path will work as designed. methods must - point to a (usually statically allocated) object implementing the required - custom path methods, of which there is currently only one. The - LibraryName and SymbolName fields must also - be initialized so that the dynamic loader can resolve them to locate the - method table. + private data. Private data should be stored in a form that can be handled + by nodeToString. methods must point to a + (usually statically allocated) object implementing the required custom + path methods, of which there is currently only one. + The first field of CustomPathMethods is + ExtensibleNodeMethods. If custom scan provider defines + own structure larger than CustomPathMethods to keep + private information on the extra area, it must support callbacks of + the ExtensibleNodeMethods to treat these private fields. + See for more details. @@ -125,19 +127,6 @@ Plan *(*PlanCustomPath) (PlannerInfo *root, be a CustomScan object, which the callback must allocate and initialize. See for more details. - - - -void (*TextOutCustomPath) (StringInfo str, - const CustomPath *node); - - Generate additional output when nodeToString is invoked on - this custom path. This callback is optional. Since - nodeToString will automatically dump all fields in the - structure that it can see, including custom_private, this - is only useful if the CustomPath is actually embedded in a - larger struct containing additional fields. - @@ -198,10 +187,15 @@ typedef struct CustomScan Plan trees must be able to be duplicated using copyObject, so all the data stored within the custom fields must consist of - nodes that that function can handle. Furthermore, custom scan providers - cannot substitute a larger structure that embeds - a CustomScan for the structure itself, as would be possible - for a CustomPath or CustomScanState. + nodes that that function can handle. In case when custom scan providers + can substitute a larger structure that embeds a CustomScan + for the structure itself, as would be possible for CustomPath + or CustomScanState. + If custom scan provider keeps its private fields in the extra area of the + above own defined structure, it also have to implement each callbacks of + ExtensibleNodeMethods to support serialization and + deserialization. + See for more details. @@ -328,4 +322,75 @@ void (*ExplainCustomScan) (CustomScanState *node, + + + Private fields with own structure + + Some node types, like CustomScan or + ForeignScan, allows extension to have itself as a part of + larger structure for their private fields with arbitrary forms. + In this case, extension has to ensure its private fields are kept + correctly on node operations, like copy, serialization and deserialization. + + + ExtensibleNodeMethods is a collection of callbacks that + enable extension to get control on node operations, to process its private + fields. + All the ExtensibleNodeMethods implementation has to be + registered with a unique name. It is key to lookup proper collection of + the callbacks later. Usually, it shall be done on _PG_init. + + + Specification of the individual callbacks are below. + + + +const char *extnodename; + + A unique name of this collection of the node operations callback. + It should not be duplicated with one preliminary registered. + + + +Size node_size; + + Size of the self define structure. Of course, it has to be larger + than or equal to the structure to be embedded in. + + + +void nodeCopy(Node *newnode, const Node *oldnode); + + This callback copies the private fields on oldnode to + the newnode. Extension does not need to copy the known + fields in the structure that is embedded in; it is the responsibility + of the core backend. + + + +Size nodeEqual(const Node *a, const Node *b); + + This callback compares private fields on a and b, + then returns true if equivalent. Elsewhere, false. + + + +Size nodeOut(StringInfo str, const Node *node); + + This callback serializes the private fields in text form, then put this + string on the supplied StringInfo. All extension has to + write out is its own private fields. The known fields on the structure + embedded in are written out by the core backend. + + + +Size nodeRead(Node *node); + + This callback deserializes the private fields, written by the above + nodeOut, on the supplied node object. + So, these callbacks have to be always symmetric. + It shall be invoked in the context of stringToNode, thus + pg_strtok will fetch next token to read. + + diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 183d3d9..2986308 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -891,7 +891,7 @@ ExplainNode(PlanState *planstate, List *ancestors, break; case T_CustomScan: sname = "Custom Scan"; - custom_name = ((CustomScan *) plan)->methods->CustomName; + custom_name = ((CustomScan *) plan)->methods->xnode.extnodename; if (custom_name) pname = psprintf("Custom Scan (%s)", custom_name); else diff --git a/src/backend/executor/nodeCustom.c b/src/backend/executor/nodeCustom.c index 0a022df..553fe96 100644 --- a/src/backend/executor/nodeCustom.c +++ b/src/backend/executor/nodeCustom.c @@ -145,7 +145,7 @@ ExecCustomMarkPos(CustomScanState *node) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("custom-scan \"%s\" does not support MarkPos", - node->methods->CustomName))); + node->methods->xnode.extnodename))); node->methods->MarkPosCustomScan(node); } @@ -156,6 +156,6 @@ ExecCustomRestrPos(CustomScanState *node) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("custom-scan \"%s\" does not support MarkPos", - node->methods->CustomName))); + node->methods->xnode.extnodename))); node->methods->RestrPosCustomScan(node); } diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 26264cb..c4bb76e 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -635,8 +635,12 @@ _copyWorkTableScan(const WorkTableScan *from) static ForeignScan * _copyForeignScan(const ForeignScan *from) { - ForeignScan *newnode = makeNode(ForeignScan); - + const ExtensibleNodeMethods *methods + = GetExtensibleNodeMethods(from->extnodename, true); + ForeignScan *newnode = (ForeignScan *) newNode(!methods + ? sizeof(ForeignScan) + : methods->node_size, + T_ForeignScan); /* * copy node superclass fields */ @@ -645,6 +649,7 @@ _copyForeignScan(const ForeignScan *from) /* * copy remainder of node */ + COPY_STRING_FIELD(extnodename); COPY_SCALAR_FIELD(fs_server); COPY_NODE_FIELD(fdw_exprs); COPY_NODE_FIELD(fdw_private); @@ -652,6 +657,8 @@ _copyForeignScan(const ForeignScan *from) COPY_NODE_FIELD(fdw_recheck_quals); COPY_BITMAPSET_FIELD(fs_relids); COPY_SCALAR_FIELD(fsSystemCol); + if (methods && methods->nodeCopy) + methods->nodeCopy((Node *) newnode, (const Node *) from); return newnode; } @@ -662,8 +669,9 @@ _copyForeignScan(const ForeignScan *from) static CustomScan * _copyCustomScan(const CustomScan *from) { - CustomScan *newnode = makeNode(CustomScan); - + const ExtensibleNodeMethods *methods = &from->methods->xnode; + CustomScan *newnode = (CustomScan *) newNode(methods->node_size, + T_CustomScan); /* * copy node superclass fields */ @@ -686,6 +694,9 @@ _copyCustomScan(const CustomScan *from) */ COPY_SCALAR_FIELD(methods); + if (methods->nodeCopy) + methods->nodeCopy((Node *) newnode, (const Node *) from); + return newnode; } diff --git a/src/backend/nodes/nodes.c b/src/backend/nodes/nodes.c index 9c0b3fe..f8dd993 100644 --- a/src/backend/nodes/nodes.c +++ b/src/backend/nodes/nodes.c @@ -19,6 +19,9 @@ #include "postgres.h" #include "nodes/nodes.h" +#include "nodes/pg_list.h" +#include "utils/hsearch.h" +#include "utils/memutils.h" /* * Support for newNode() macro @@ -29,3 +32,82 @@ */ Node *newNodeMacroHolder; + +/* + * Extensible node support - We allow extension to have own structure that + * contains well known structure (like ForeignScan or CustomScan), to keep + * their private data fields. These structure also have to support + * serialization / deserialization using node functions. A collection of + * callback enables to handle private fields also, not only well known + * portion. + */ +#define XNODES_NUM_SLOTS 256 +static List **xnodes_methods_slots = NULL; + +void +RegisterExtensibleNodeMethods(const ExtensibleNodeMethods *methods) +{ + uint32 hash; + int index; + ListCell *lc; + MemoryContext oldcxt; + + if (!xnodes_methods_slots) + xnodes_methods_slots = MemoryContextAllocZero(TopMemoryContext, + sizeof(List *) * XNODES_NUM_SLOTS); + + hash = hash_any(methods->extnodename, strlen(methods->extnodename)); + index = hash % XNODES_NUM_SLOTS; + + /* duplication check */ + foreach (lc, xnodes_methods_slots[index]) + { + const ExtensibleNodeMethods *curr = lfirst(lc); + + if (strcmp(methods->extnodename, curr->extnodename) == 0) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("ExtensibleNodeMethods \"%s\" already exists", + methods->extnodename))); + } + /* ok, register this entry */ + oldcxt = MemoryContextSwitchTo(TopMemoryContext); + xnodes_methods_slots[index] = lappend(xnodes_methods_slots[index], + (void *)methods); + MemoryContextSwitchTo(oldcxt); +} + +const ExtensibleNodeMethods * +GetExtensibleNodeMethods(const char *extnodename, bool missing_ok) +{ + const ExtensibleNodeMethods *methods = NULL; + + if (!extnodename) + return NULL; + + if (xnodes_methods_slots) + { + uint32 hash = hash_any(extnodename, strlen(extnodename)); + int index = hash % XNODES_NUM_SLOTS; + ListCell *lc; + + /* search the registered table */ + foreach (lc, xnodes_methods_slots[index]) + { + const ExtensibleNodeMethods *curr = lfirst(lc); + + if (strcmp(curr->extnodename, extnodename) == 0) + { + methods = curr; + break; + } + } + } + + if (!methods && !missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("ExtensibleNodeMethods \"%s\" was not registered", + extnodename))); + return methods; +} diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 012c14b..3903aea 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -587,6 +587,9 @@ _outWorkTableScan(StringInfo str, const WorkTableScan *node) static void _outForeignScan(StringInfo str, const ForeignScan *node) { + const ExtensibleNodeMethods *methods + = GetExtensibleNodeMethods(node->extnodename, true); + WRITE_NODE_TYPE("FOREIGNSCAN"); _outScanInfo(str, (const Scan *) node); @@ -598,11 +601,15 @@ _outForeignScan(StringInfo str, const ForeignScan *node) WRITE_NODE_FIELD(fdw_recheck_quals); WRITE_BITMAPSET_FIELD(fs_relids); WRITE_BOOL_FIELD(fsSystemCol); + if (methods && methods->nodeOut) + methods->nodeOut(str, (const Node *) node); } static void _outCustomScan(StringInfo str, const CustomScan *node) { + const ExtensibleNodeMethods *methods = &node->methods->xnode; + WRITE_NODE_TYPE("CUSTOMSCAN"); _outScanInfo(str, (const Scan *) node); @@ -613,11 +620,12 @@ _outCustomScan(StringInfo str, const CustomScan *node) WRITE_NODE_FIELD(custom_private); WRITE_NODE_FIELD(custom_scan_tlist); WRITE_BITMAPSET_FIELD(custom_relids); - /* Dump library and symbol name instead of raw pointer */ + /* Dump CustomName; alias of extnodename */ appendStringInfoString(str, " :methods "); - _outToken(str, node->methods->LibraryName); - appendStringInfoChar(str, ' '); - _outToken(str, node->methods->SymbolName); + _outToken(str, methods->extnodename); + /* Dump private fields if any */ + if (methods && methods->nodeOut) + methods->nodeOut(str, (const Node *) node); } static void @@ -1679,16 +1687,24 @@ _outTidPath(StringInfo str, const TidPath *node) static void _outForeignPath(StringInfo str, const ForeignPath *node) { + const ExtensibleNodeMethods *methods + = GetExtensibleNodeMethods(node->extnodename, true); + WRITE_NODE_TYPE("FOREIGNPATH"); _outPathInfo(str, (const Path *) node); + WRITE_STRING_FIELD(extnodename); WRITE_NODE_FIELD(fdw_private); + if (methods && methods->nodeOut) + methods->nodeOut(str, (Node *) node); } static void _outCustomPath(StringInfo str, const CustomPath *node) { + const ExtensibleNodeMethods *methods = &node->methods->xnode; + WRITE_NODE_TYPE("CUSTOMPATH"); _outPathInfo(str, (const Path *) node); @@ -1697,9 +1713,9 @@ _outCustomPath(StringInfo str, const CustomPath *node) WRITE_NODE_FIELD(custom_paths); WRITE_NODE_FIELD(custom_private); appendStringInfoString(str, " :methods "); - _outToken(str, node->methods->CustomName); - if (node->methods->TextOutCustomPath) - node->methods->TextOutCustomPath(str, node); + _outToken(str, methods->extnodename); + if (methods && methods->nodeOut) + methods->nodeOut(str, (Node *)node); } static void diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 222e2ed..00e81b7 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -1792,10 +1792,12 @@ _readWorkTableScan(void) static ForeignScan * _readForeignScan(void) { + const ExtensibleNodeMethods *methods; READ_LOCALS(ForeignScan); ReadCommonScan(&local_node->scan); + READ_STRING_FIELD(extnodename); READ_OID_FIELD(fs_server); READ_NODE_FIELD(fdw_exprs); READ_NODE_FIELD(fdw_private); @@ -1804,6 +1806,19 @@ _readForeignScan(void) READ_BITMAPSET_FIELD(fs_relids); READ_BOOL_FIELD(fsSystemCol); + methods = GetExtensibleNodeMethods(local_node->extnodename, true); + if (methods && methods->nodeRead) + { + ForeignScan *local_temp = palloc0(methods->node_size); + + Assert(methods->node_size >= sizeof(ForeignScan)); + + memcpy(local_temp, local_node, sizeof(ForeignScan)); + methods->nodeRead((Node *) local_temp); + + pfree(local_node); + local_node = local_temp; + } READ_DONE(); } @@ -1813,10 +1828,9 @@ _readForeignScan(void) static CustomScan * _readCustomScan(void) { + const ExtensibleNodeMethods *methods; + const char *extnodename; READ_LOCALS(CustomScan); - char *library_name; - char *symbol_name; - const CustomScanMethods *methods; ReadCommonScan(&local_node->scan); @@ -1831,18 +1845,24 @@ _readCustomScan(void) * Reconstruction of methods using library and symbol name */ token = pg_strtok(&length); /* skip methods: */ - token = pg_strtok(&length); /* LibraryName */ - library_name = nullable_string(token, length); - token = pg_strtok(&length); /* SymbolName */ - symbol_name = nullable_string(token, length); + token = pg_strtok(&length); /* CustomName (alias of extnodename) */ + extnodename = nullable_string(token, length); + methods = GetExtensibleNodeMethods(extnodename, false); - methods = (const CustomScanMethods *) - load_external_function(library_name, symbol_name, true, NULL); - Assert(strcmp(methods->LibraryName, library_name) == 0 && - strcmp(methods->SymbolName, symbol_name) == 0); - local_node->methods = methods; + if (methods->nodeRead) + { + CustomScan *local_temp = palloc0(methods->node_size); - READ_DONE(); + Assert(methods->node_size >= sizeof(CustomScan)); + memcpy(local_temp, local_node, sizeof(CustomScan)); + methods->nodeRead((Node *) local_temp); + + pfree(local_node); + local_node = local_temp; + } + local_node->methods = (const CustomScanMethods *) methods; + + READ_DONE(); } /* diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index eb3591a..9e79049 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1607,7 +1607,7 @@ struct CustomScanState; typedef struct CustomExecMethods { - const char *CustomName; + ExtensibleNodeMethods xnode; /* collection of node operations */ /* Executor methods: mark/restore are optional, the rest are required */ void (*BeginCustomScan) (struct CustomScanState *node, diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 94bdb7c..0ac4565 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -542,6 +542,24 @@ extern void *copyObject(const void *obj); */ extern bool equal(const void *a, const void *b); +/* + * ExtensibleNodeMethods - a collection of node operation callbacks. + */ +struct StringInfoData; /* not to include stringinfo.h here */ + +typedef struct ExtensibleNodeMethods +{ + const char *extnodename; + Size node_size; + void (*nodeCopy)(Node *newnode, const Node *oldnode); + bool (*nodeEqual)(const Node *a, const Node *b); + void (*nodeOut)(struct StringInfoData *str, const Node *node); + void (*nodeRead)(Node *node); +} ExtensibleNodeMethods; + +extern void RegisterExtensibleNodeMethods(const ExtensibleNodeMethods *method); +extern const ExtensibleNodeMethods *GetExtensibleNodeMethods(const char *name, + bool missing_ok); /* * Typedefs for identifying qualifier selectivities and plan costs as such. diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 37086c6..9b99f65 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -530,6 +530,7 @@ typedef struct WorkTableScan typedef struct ForeignScan { Scan scan; + const char *extnodename; /* collection of node operation callbacks */ Oid fs_server; /* OID of foreign server */ List *fdw_exprs; /* expressions that FDW may evaluate */ List *fdw_private; /* private data for FDW */ @@ -556,9 +557,7 @@ struct CustomScan; typedef struct CustomScanMethods { - const char *CustomName; - const char *LibraryName; - const char *SymbolName; + ExtensibleNodeMethods xnode; /* collection of node operations */ /* Create execution state (CustomScanState) from a CustomScan plan node */ Node *(*CreateCustomScanState) (struct CustomScan *cscan); diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 9a0dd28..8e4d735 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -909,6 +909,7 @@ typedef struct TidPath typedef struct ForeignPath { Path path; + const char *extnodename; List *fdw_private; } ForeignPath; @@ -937,7 +938,7 @@ struct CustomPath; typedef struct CustomPathMethods { - const char *CustomName; + ExtensibleNodeMethods xnode; /* collection of node operations */ /* Convert Path to a Plan */ struct Plan *(*PlanCustomPath) (PlannerInfo *root, @@ -946,9 +947,6 @@ typedef struct CustomPathMethods List *tlist, List *clauses, List *custom_plans); - /* Optional: print additional fields besides "private" */ - void (*TextOutCustomPath) (StringInfo str, - const struct CustomPath *node); } CustomPathMethods; typedef struct CustomPath