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 | 76 +++++++++++++++++++++++++ src/backend/nodes/outfuncs.c | 45 ++++++++++++--- src/backend/nodes/readfuncs.c | 46 ++++++++++----- src/include/nodes/execnodes.h | 2 +- src/include/nodes/nodes.h | 24 ++++++++ src/include/nodes/plannodes.h | 5 +- src/include/nodes/relation.h | 6 +- 11 files changed, 283 insertions(+), 61 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 25d8ca0..2fc16de 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -892,7 +892,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 640289e..55d5e1f 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 5877037..a98bd88 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 aea3880..d31b2d7 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,76 @@ */ 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. + */ +static HTAB *extensible_node_methods = NULL; + +typedef struct +{ + char extnodename[NAMEDATALEN]; + const ExtensibleNodeMethods *methods; +} ExtensibleNodeEntry; + +void +RegisterExtensibleNodeMethods(const ExtensibleNodeMethods *methods) +{ + ExtensibleNodeEntry *entry; + bool found; + + if (!extensible_node_methods) + { + HASHCTL ctl; + + memset(&ctl, 0, sizeof(HASHCTL)); + ctl.keysize = NAMEDATALEN; + ctl.entrysize = sizeof(const ExtensibleNodeEntry); + extensible_node_methods = hash_create("Extensible Node Methods", + 100, &ctl, HASH_ELEM); + } + + if (strlen(methods->extnodename) >= NAMEDATALEN) + ereport(ERROR, + (errcode(ERRCODE_NAME_TOO_LONG), + errmsg("ExtensibleNodeMethods \"%s\" name too long", + methods->extnodename))); + + entry = (ExtensibleNodeEntry *) hash_search(extensible_node_methods, + methods->extnodename, + HASH_ENTER, &found); + if (found) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("ExtensibleNodeMethods \"%s\" already exists", + methods->extnodename))); + /* setup entry */ + entry->methods = methods; +} + +const ExtensibleNodeMethods * +GetExtensibleNodeMethods(const char *extnodename, bool missing_ok) +{ + ExtensibleNodeEntry *entry = NULL; + + if (!extnodename) + return NULL; + if (extensible_node_methods) + { + entry = (ExtensibleNodeEntry *) hash_search(extensible_node_methods, + extnodename, + HASH_FIND, NULL); + } + + if (!entry && !missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("ExtensibleNodeMethods \"%s\" was not registered", + extnodename))); + return entry ? entry->methods : NULL; +} diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index b5e0b55..90800c8 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -140,6 +140,13 @@ _outToken(StringInfo str, const char *s) } } +/* outToken - entrypoint for extensions */ +void +outToken(StringInfo str, const char *s) +{ + _outToken(str, s); +} + static void _outList(StringInfo str, const List *node) { @@ -196,6 +203,13 @@ _outBitmapset(StringInfo str, const Bitmapset *bms) appendStringInfoChar(str, ')'); } +/* outBitmapset - entrypoint for extensions */ +void +outBitmapset(StringInfo str, const Bitmapset *bms) +{ + _outBitmapset(str, bms); +} + /* * Print the value of a Datum given its type. */ @@ -231,7 +245,6 @@ _outDatum(StringInfo str, Datum value, int typlen, bool typbyval) } } - /* * Stuff from plannodes.h */ @@ -587,6 +600,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 +614,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 +633,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 @@ -1684,17 +1705,25 @@ _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_outerpath); 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); @@ -1703,9 +1732,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 a67b337..49030ea 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 07cd20a..d8693b2 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1608,7 +1608,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 cf09db4..9957e0a 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -527,6 +527,12 @@ extern PGDLLIMPORT Node *newNodeMacroHolder; */ extern char *nodeToString(const void *obj); +struct Bitmapset; /* not to include bitmapset.h here */ +struct StringInfoData; /* not to include stringinfo.h here */ +extern void outToken(struct StringInfoData *str, const char *s); +extern void outBitmapset(struct StringInfoData *str, + const struct Bitmapset *bms); + /* * nodes/{readfuncs.c,read.c} */ @@ -542,6 +548,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 e823c83..b0cbe1a 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 b233b62..3fa6ed0 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -918,6 +918,7 @@ typedef struct TidPath typedef struct ForeignPath { Path path; + const char *extnodename; Path *fdw_outerpath; List *fdw_private; } ForeignPath; @@ -947,7 +948,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, @@ -956,9 +957,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