Rework custom scans to work more like the new extensible node stuff.
Per discussion, the new extensible node framework is thought to be better designed than the custom path/scan/scanstate stuff we added in PostgreSQL 9.5. Rework the latter to be more like the former. This is not backward-compatible, but we generally don't promise that for C APIs, and there probably aren't many people using this yet anyway. KaiGai Kohei, reviewed by Petr Jelinek and me. Some further cosmetic changes by me.
This commit is contained in:
parent
534da37927
commit
f9143d102f
@ -21,6 +21,7 @@
|
|||||||
#include "commands/prepare.h"
|
#include "commands/prepare.h"
|
||||||
#include "executor/hashjoin.h"
|
#include "executor/hashjoin.h"
|
||||||
#include "foreign/fdwapi.h"
|
#include "foreign/fdwapi.h"
|
||||||
|
#include "nodes/extensible.h"
|
||||||
#include "nodes/nodeFuncs.h"
|
#include "nodes/nodeFuncs.h"
|
||||||
#include "optimizer/clauses.h"
|
#include "optimizer/clauses.h"
|
||||||
#include "optimizer/planmain.h"
|
#include "optimizer/planmain.h"
|
||||||
|
@ -24,61 +24,87 @@
|
|||||||
#include "utils/hsearch.h"
|
#include "utils/hsearch.h"
|
||||||
|
|
||||||
static HTAB *extensible_node_methods = NULL;
|
static HTAB *extensible_node_methods = NULL;
|
||||||
|
static HTAB *custom_scan_methods = NULL;
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
char extnodename[EXTNODENAME_MAX_LEN];
|
char extnodename[EXTNODENAME_MAX_LEN];
|
||||||
const ExtensibleNodeMethods *methods;
|
const void *extnodemethods;
|
||||||
} ExtensibleNodeEntry;
|
} ExtensibleNodeEntry;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* An internal function to register a new callback structure
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
RegisterExtensibleNodeEntry(HTAB **p_htable, const char *htable_label,
|
||||||
|
const char *extnodename,
|
||||||
|
const void *extnodemethods)
|
||||||
|
{
|
||||||
|
ExtensibleNodeEntry *entry;
|
||||||
|
bool found;
|
||||||
|
|
||||||
|
if (*p_htable == NULL)
|
||||||
|
{
|
||||||
|
HASHCTL ctl;
|
||||||
|
|
||||||
|
memset(&ctl, 0, sizeof(HASHCTL));
|
||||||
|
ctl.keysize = EXTNODENAME_MAX_LEN;
|
||||||
|
ctl.entrysize = sizeof(ExtensibleNodeEntry);
|
||||||
|
|
||||||
|
*p_htable = hash_create(htable_label, 100, &ctl, HASH_ELEM);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen(extnodename) >= EXTNODENAME_MAX_LEN)
|
||||||
|
elog(ERROR, "extensible node name is too long");
|
||||||
|
|
||||||
|
entry = (ExtensibleNodeEntry *) hash_search(*p_htable,
|
||||||
|
extnodename,
|
||||||
|
HASH_ENTER, &found);
|
||||||
|
if (found)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_DUPLICATE_OBJECT),
|
||||||
|
errmsg("extensible node type \"%s\" already exists",
|
||||||
|
extnodename)));
|
||||||
|
|
||||||
|
entry->extnodemethods = extnodemethods;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Register a new type of extensible node.
|
* Register a new type of extensible node.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
RegisterExtensibleNodeMethods(const ExtensibleNodeMethods *methods)
|
RegisterExtensibleNodeMethods(const ExtensibleNodeMethods *methods)
|
||||||
{
|
{
|
||||||
ExtensibleNodeEntry *entry;
|
RegisterExtensibleNodeEntry(&extensible_node_methods,
|
||||||
bool found;
|
"Extensible Node Methods",
|
||||||
|
methods->extnodename,
|
||||||
if (extensible_node_methods == NULL)
|
methods);
|
||||||
{
|
|
||||||
HASHCTL ctl;
|
|
||||||
|
|
||||||
memset(&ctl, 0, sizeof(HASHCTL));
|
|
||||||
ctl.keysize = EXTNODENAME_MAX_LEN;
|
|
||||||
ctl.entrysize = sizeof(ExtensibleNodeEntry);
|
|
||||||
extensible_node_methods = hash_create("Extensible Node Methods",
|
|
||||||
100, &ctl, HASH_ELEM);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strlen(methods->extnodename) >= EXTNODENAME_MAX_LEN)
|
|
||||||
elog(ERROR, "extensible node name is too long");
|
|
||||||
|
|
||||||
entry = (ExtensibleNodeEntry *) hash_search(extensible_node_methods,
|
|
||||||
methods->extnodename,
|
|
||||||
HASH_ENTER, &found);
|
|
||||||
if (found)
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_DUPLICATE_OBJECT),
|
|
||||||
errmsg("extensible node type \"%s\" already exists",
|
|
||||||
methods->extnodename)));
|
|
||||||
|
|
||||||
entry->methods = methods;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get the methods for a given type of extensible node.
|
* Register a new type of custom scan node
|
||||||
*/
|
*/
|
||||||
const ExtensibleNodeMethods *
|
void
|
||||||
GetExtensibleNodeMethods(const char *extnodename, bool missing_ok)
|
RegisterCustomScanMethods(const CustomScanMethods *methods)
|
||||||
|
{
|
||||||
|
RegisterExtensibleNodeEntry(&custom_scan_methods,
|
||||||
|
"Custom Scan Methods",
|
||||||
|
methods->CustomName,
|
||||||
|
methods);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* An internal routine to get an ExtensibleNodeEntry by the given identifier
|
||||||
|
*/
|
||||||
|
static const void *
|
||||||
|
GetExtensibleNodeEntry(HTAB *htable, const char *extnodename, bool missing_ok)
|
||||||
{
|
{
|
||||||
ExtensibleNodeEntry *entry = NULL;
|
ExtensibleNodeEntry *entry = NULL;
|
||||||
|
|
||||||
if (extensible_node_methods != NULL)
|
if (htable != NULL)
|
||||||
entry = (ExtensibleNodeEntry *) hash_search(extensible_node_methods,
|
entry = (ExtensibleNodeEntry *) hash_search(htable,
|
||||||
extnodename,
|
extnodename,
|
||||||
HASH_FIND, NULL);
|
HASH_FIND, NULL);
|
||||||
|
|
||||||
if (!entry)
|
if (!entry)
|
||||||
{
|
{
|
||||||
if (missing_ok)
|
if (missing_ok)
|
||||||
@ -89,5 +115,29 @@ GetExtensibleNodeMethods(const char *extnodename, bool missing_ok)
|
|||||||
extnodename)));
|
extnodename)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return entry->methods;
|
return entry->extnodemethods;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the methods for a given type of extensible node.
|
||||||
|
*/
|
||||||
|
const ExtensibleNodeMethods *
|
||||||
|
GetExtensibleNodeMethods(const char *extnodename, bool missing_ok)
|
||||||
|
{
|
||||||
|
return (const ExtensibleNodeMethods *)
|
||||||
|
GetExtensibleNodeEntry(extensible_node_methods,
|
||||||
|
extnodename,
|
||||||
|
missing_ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the methods for a given name of CustomScanMethods
|
||||||
|
*/
|
||||||
|
const CustomScanMethods *
|
||||||
|
GetCustomScanMethods(const char *CustomName, bool missing_ok)
|
||||||
|
{
|
||||||
|
return (const CustomScanMethods *)
|
||||||
|
GetExtensibleNodeEntry(custom_scan_methods,
|
||||||
|
CustomName,
|
||||||
|
missing_ok);
|
||||||
}
|
}
|
||||||
|
@ -632,11 +632,9 @@ _outCustomScan(StringInfo str, const CustomScan *node)
|
|||||||
WRITE_NODE_FIELD(custom_private);
|
WRITE_NODE_FIELD(custom_private);
|
||||||
WRITE_NODE_FIELD(custom_scan_tlist);
|
WRITE_NODE_FIELD(custom_scan_tlist);
|
||||||
WRITE_BITMAPSET_FIELD(custom_relids);
|
WRITE_BITMAPSET_FIELD(custom_relids);
|
||||||
/* Dump library and symbol name instead of raw pointer */
|
/* CustomName is a key to lookup CustomScanMethods */
|
||||||
appendStringInfoString(str, " :methods ");
|
appendStringInfoString(str, " :methods ");
|
||||||
_outToken(str, node->methods->LibraryName);
|
_outToken(str, node->methods->CustomName);
|
||||||
appendStringInfoChar(str, ' ');
|
|
||||||
_outToken(str, node->methods->SymbolName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -1827,8 +1827,7 @@ static CustomScan *
|
|||||||
_readCustomScan(void)
|
_readCustomScan(void)
|
||||||
{
|
{
|
||||||
READ_LOCALS(CustomScan);
|
READ_LOCALS(CustomScan);
|
||||||
char *library_name;
|
char *custom_name;
|
||||||
char *symbol_name;
|
|
||||||
const CustomScanMethods *methods;
|
const CustomScanMethods *methods;
|
||||||
|
|
||||||
ReadCommonScan(&local_node->scan);
|
ReadCommonScan(&local_node->scan);
|
||||||
@ -1840,19 +1839,11 @@ _readCustomScan(void)
|
|||||||
READ_NODE_FIELD(custom_scan_tlist);
|
READ_NODE_FIELD(custom_scan_tlist);
|
||||||
READ_BITMAPSET_FIELD(custom_relids);
|
READ_BITMAPSET_FIELD(custom_relids);
|
||||||
|
|
||||||
/*
|
/* Lookup CustomScanMethods by CustomName */
|
||||||
* Reconstruction of methods using library and symbol name
|
|
||||||
*/
|
|
||||||
token = pg_strtok(&length); /* skip methods: */
|
token = pg_strtok(&length); /* skip methods: */
|
||||||
token = pg_strtok(&length); /* LibraryName */
|
token = pg_strtok(&length); /* CustomName */
|
||||||
library_name = nullable_string(token, length);
|
custom_name = nullable_string(token, length);
|
||||||
token = pg_strtok(&length); /* SymbolName */
|
methods = GetCustomScanMethods(custom_name, false);
|
||||||
symbol_name = nullable_string(token, length);
|
|
||||||
|
|
||||||
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;
|
local_node->methods = methods;
|
||||||
|
|
||||||
READ_DONE();
|
READ_DONE();
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
#include "catalog/pg_class.h"
|
#include "catalog/pg_class.h"
|
||||||
#include "foreign/fdwapi.h"
|
#include "foreign/fdwapi.h"
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
|
#include "nodes/extensible.h"
|
||||||
#include "nodes/makefuncs.h"
|
#include "nodes/makefuncs.h"
|
||||||
#include "nodes/nodeFuncs.h"
|
#include "nodes/nodeFuncs.h"
|
||||||
#include "optimizer/clauses.h"
|
#include "optimizer/clauses.h"
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
#include "access/parallel.h"
|
#include "access/parallel.h"
|
||||||
#include "nodes/execnodes.h"
|
#include "nodes/execnodes.h"
|
||||||
|
#include "nodes/extensible.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* General executor code
|
* General executor code
|
||||||
|
@ -1606,38 +1606,7 @@ typedef struct ForeignScanState
|
|||||||
* the BeginCustomScan method.
|
* the BeginCustomScan method.
|
||||||
* ----------------
|
* ----------------
|
||||||
*/
|
*/
|
||||||
struct ParallelContext; /* avoid including parallel.h here */
|
struct CustomExecMethods;
|
||||||
struct shm_toc; /* avoid including shm_toc.h here */
|
|
||||||
struct ExplainState; /* avoid including explain.h here */
|
|
||||||
struct CustomScanState;
|
|
||||||
|
|
||||||
typedef struct CustomExecMethods
|
|
||||||
{
|
|
||||||
const char *CustomName;
|
|
||||||
|
|
||||||
/* Executor methods: mark/restore are optional, the rest are required */
|
|
||||||
void (*BeginCustomScan) (struct CustomScanState *node,
|
|
||||||
EState *estate,
|
|
||||||
int eflags);
|
|
||||||
TupleTableSlot *(*ExecCustomScan) (struct CustomScanState *node);
|
|
||||||
void (*EndCustomScan) (struct CustomScanState *node);
|
|
||||||
void (*ReScanCustomScan) (struct CustomScanState *node);
|
|
||||||
void (*MarkPosCustomScan) (struct CustomScanState *node);
|
|
||||||
void (*RestrPosCustomScan) (struct CustomScanState *node);
|
|
||||||
/* Optional: parallel execution support */
|
|
||||||
Size (*EstimateDSMCustomScan) (struct CustomScanState *node,
|
|
||||||
struct ParallelContext *pcxt);
|
|
||||||
void (*InitializeDSMCustomScan) (struct CustomScanState *node,
|
|
||||||
struct ParallelContext *pcxt,
|
|
||||||
void *coordinate);
|
|
||||||
void (*InitializeWorkerCustomScan) (struct CustomScanState *node,
|
|
||||||
struct shm_toc *toc,
|
|
||||||
void *coordinate);
|
|
||||||
/* Optional: print additional information in EXPLAIN */
|
|
||||||
void (*ExplainCustomScan) (struct CustomScanState *node,
|
|
||||||
List *ancestors,
|
|
||||||
struct ExplainState *es);
|
|
||||||
} CustomExecMethods;
|
|
||||||
|
|
||||||
typedef struct CustomScanState
|
typedef struct CustomScanState
|
||||||
{
|
{
|
||||||
@ -1645,7 +1614,7 @@ typedef struct CustomScanState
|
|||||||
uint32 flags; /* mask of CUSTOMPATH_* flags, see relation.h */
|
uint32 flags; /* mask of CUSTOMPATH_* flags, see relation.h */
|
||||||
List *custom_ps; /* list of child PlanState nodes, if any */
|
List *custom_ps; /* list of child PlanState nodes, if any */
|
||||||
Size pscan_len; /* size of parallel coordination information */
|
Size pscan_len; /* size of parallel coordination information */
|
||||||
const CustomExecMethods *methods;
|
const struct CustomExecMethods *methods;
|
||||||
} CustomScanState;
|
} CustomScanState;
|
||||||
|
|
||||||
/* ----------------------------------------------------------------
|
/* ----------------------------------------------------------------
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*-------------------------------------------------------------------------
|
/*-------------------------------------------------------------------------
|
||||||
*
|
*
|
||||||
* extensible.h
|
* extensible.h
|
||||||
* Definitions for extensible node type
|
* Definitions for extensible nodes and custom scans
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
|
||||||
@ -14,8 +14,13 @@
|
|||||||
#ifndef EXTENSIBLE_H
|
#ifndef EXTENSIBLE_H
|
||||||
#define EXTENSIBLE_H
|
#define EXTENSIBLE_H
|
||||||
|
|
||||||
#include "nodes/nodes.h"
|
#include "access/parallel.h"
|
||||||
|
#include "commands/explain.h"
|
||||||
|
#include "nodes/execnodes.h"
|
||||||
|
#include "nodes/plannodes.h"
|
||||||
|
#include "nodes/relation.h"
|
||||||
|
|
||||||
|
/* maximum length of an extensible node identifier */
|
||||||
#define EXTNODENAME_MAX_LEN 64
|
#define EXTNODENAME_MAX_LEN 64
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -69,4 +74,80 @@ extern void RegisterExtensibleNodeMethods(const ExtensibleNodeMethods *method);
|
|||||||
extern const ExtensibleNodeMethods *GetExtensibleNodeMethods(const char *name,
|
extern const ExtensibleNodeMethods *GetExtensibleNodeMethods(const char *name,
|
||||||
bool missing_ok);
|
bool missing_ok);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Flags for custom paths, indicating what capabilities the resulting scan
|
||||||
|
* will have.
|
||||||
|
*/
|
||||||
|
#define CUSTOMPATH_SUPPORT_BACKWARD_SCAN 0x0001
|
||||||
|
#define CUSTOMPATH_SUPPORT_MARK_RESTORE 0x0002
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Custom path methods. Mostly, we just need to know how to convert a
|
||||||
|
* CustomPath to a plan.
|
||||||
|
*/
|
||||||
|
typedef struct CustomPathMethods
|
||||||
|
{
|
||||||
|
const char *CustomName;
|
||||||
|
|
||||||
|
/* Convert Path to a Plan */
|
||||||
|
struct Plan *(*PlanCustomPath) (PlannerInfo *root,
|
||||||
|
RelOptInfo *rel,
|
||||||
|
struct CustomPath *best_path,
|
||||||
|
List *tlist,
|
||||||
|
List *clauses,
|
||||||
|
List *custom_plans);
|
||||||
|
} CustomPathMethods;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Custom scan. Here again, there's not much to do: we need to be able to
|
||||||
|
* generate a ScanState corresponding to the scan.
|
||||||
|
*/
|
||||||
|
typedef struct CustomScanMethods
|
||||||
|
{
|
||||||
|
const char *CustomName;
|
||||||
|
|
||||||
|
/* Create execution state (CustomScanState) from a CustomScan plan node */
|
||||||
|
Node *(*CreateCustomScanState) (CustomScan *cscan);
|
||||||
|
} CustomScanMethods;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Execution-time methods for a CustomScanState. This is more complex than
|
||||||
|
* what we need for a custom path or scan.
|
||||||
|
*/
|
||||||
|
typedef struct CustomExecMethods
|
||||||
|
{
|
||||||
|
const char *CustomName;
|
||||||
|
|
||||||
|
/* Required executor methods */
|
||||||
|
void (*BeginCustomScan) (CustomScanState *node,
|
||||||
|
EState *estate,
|
||||||
|
int eflags);
|
||||||
|
TupleTableSlot *(*ExecCustomScan) (CustomScanState *node);
|
||||||
|
void (*EndCustomScan) (CustomScanState *node);
|
||||||
|
void (*ReScanCustomScan) (CustomScanState *node);
|
||||||
|
|
||||||
|
/* Optional methods: needed if mark/restore is supported */
|
||||||
|
void (*MarkPosCustomScan) (CustomScanState *node);
|
||||||
|
void (*RestrPosCustomScan) (CustomScanState *node);
|
||||||
|
|
||||||
|
/* Optional methods: needed if parallel execution is supported */
|
||||||
|
Size (*EstimateDSMCustomScan) (CustomScanState *node,
|
||||||
|
ParallelContext *pcxt);
|
||||||
|
void (*InitializeDSMCustomScan) (CustomScanState *node,
|
||||||
|
ParallelContext *pcxt,
|
||||||
|
void *coordinate);
|
||||||
|
void (*InitializeWorkerCustomScan) (CustomScanState *node,
|
||||||
|
shm_toc *toc,
|
||||||
|
void *coordinate);
|
||||||
|
|
||||||
|
/* Optional: print additional information in EXPLAIN */
|
||||||
|
void (*ExplainCustomScan) (CustomScanState *node,
|
||||||
|
List *ancestors,
|
||||||
|
ExplainState *es);
|
||||||
|
} CustomExecMethods;
|
||||||
|
|
||||||
|
extern void RegisterCustomScanMethods(const CustomScanMethods *methods);
|
||||||
|
extern const CustomScanMethods *GetCustomScanMethods(const char *CustomName,
|
||||||
|
bool missing_ok);
|
||||||
|
|
||||||
#endif /* EXTENSIBLE_H */
|
#endif /* EXTENSIBLE_H */
|
||||||
|
@ -555,17 +555,7 @@ typedef struct ForeignScan
|
|||||||
* a larger struct will not work.
|
* a larger struct will not work.
|
||||||
* ----------------
|
* ----------------
|
||||||
*/
|
*/
|
||||||
struct CustomScan;
|
struct CustomScanMethods;
|
||||||
|
|
||||||
typedef struct CustomScanMethods
|
|
||||||
{
|
|
||||||
const char *CustomName;
|
|
||||||
const char *LibraryName;
|
|
||||||
const char *SymbolName;
|
|
||||||
|
|
||||||
/* Create execution state (CustomScanState) from a CustomScan plan node */
|
|
||||||
Node *(*CreateCustomScanState) (struct CustomScan *cscan);
|
|
||||||
} CustomScanMethods;
|
|
||||||
|
|
||||||
typedef struct CustomScan
|
typedef struct CustomScan
|
||||||
{
|
{
|
||||||
@ -577,7 +567,7 @@ typedef struct CustomScan
|
|||||||
List *custom_scan_tlist; /* optional tlist describing scan
|
List *custom_scan_tlist; /* optional tlist describing scan
|
||||||
* tuple */
|
* tuple */
|
||||||
Bitmapset *custom_relids; /* RTIs generated by this scan */
|
Bitmapset *custom_relids; /* RTIs generated by this scan */
|
||||||
const CustomScanMethods *methods;
|
const struct CustomScanMethods *methods;
|
||||||
} CustomScan;
|
} CustomScan;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1030,23 +1030,8 @@ typedef struct ForeignPath
|
|||||||
* FDW case, we provide a "custom_private" field in CustomPath; providers
|
* FDW case, we provide a "custom_private" field in CustomPath; providers
|
||||||
* may prefer to use that rather than define another struct type.
|
* may prefer to use that rather than define another struct type.
|
||||||
*/
|
*/
|
||||||
struct CustomPath;
|
|
||||||
|
|
||||||
#define CUSTOMPATH_SUPPORT_BACKWARD_SCAN 0x0001
|
struct CustomPathMethods;
|
||||||
#define CUSTOMPATH_SUPPORT_MARK_RESTORE 0x0002
|
|
||||||
|
|
||||||
typedef struct CustomPathMethods
|
|
||||||
{
|
|
||||||
const char *CustomName;
|
|
||||||
|
|
||||||
/* Convert Path to a Plan */
|
|
||||||
struct Plan *(*PlanCustomPath) (PlannerInfo *root,
|
|
||||||
RelOptInfo *rel,
|
|
||||||
struct CustomPath *best_path,
|
|
||||||
List *tlist,
|
|
||||||
List *clauses,
|
|
||||||
List *custom_plans);
|
|
||||||
} CustomPathMethods;
|
|
||||||
|
|
||||||
typedef struct CustomPath
|
typedef struct CustomPath
|
||||||
{
|
{
|
||||||
@ -1054,7 +1039,7 @@ typedef struct CustomPath
|
|||||||
uint32 flags; /* mask of CUSTOMPATH_* flags, see above */
|
uint32 flags; /* mask of CUSTOMPATH_* flags, see above */
|
||||||
List *custom_paths; /* list of child Path nodes, if any */
|
List *custom_paths; /* list of child Path nodes, if any */
|
||||||
List *custom_private;
|
List *custom_private;
|
||||||
const CustomPathMethods *methods;
|
const struct CustomPathMethods *methods;
|
||||||
} CustomPath;
|
} CustomPath;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
Loading…
x
Reference in New Issue
Block a user