/* * GetMultiPlanString returns either NULL, if the plan is not a distributed * one, or the string representing the distributed plan. */ static char * GetMultiPlanString(PlannedStmt *result) { FunctionScan *fauxFunctionScan = NULL; RangeTblFunction *fauxFunction = NULL; FuncExpr *fauxFuncExpr = NULL; Const *multiPlanData = NULL; if (!IsA(result->planTree, FunctionScan)) { return NULL; } fauxFunctionScan = (FunctionScan *) result->planTree; if (list_length(fauxFunctionScan->functions) != 1) { return NULL; } fauxFunction = linitial(fauxFunctionScan->functions); if (!IsA(fauxFunction->funcexpr, FuncExpr)) { return NULL; } fauxFuncExpr = (FuncExpr *) fauxFunction->funcexpr; if (fauxFuncExpr->funcid != CitusExtraDataContainerFuncId()) { return NULL; } if (list_length(fauxFuncExpr->args) != 1) { ereport(ERROR, (errmsg("unexpected number of function arguments to " "citus_extradata_container"))); } multiPlanData = (Const *) linitial(fauxFuncExpr->args); Assert(IsA(multiPlanData, Const)); Assert(multiPlanData->consttype == CSTRINGOID); return DatumGetCString(multiPlanData->constvalue); }
/* * SetRangeTblExtraData adds additional data to a RTE, overwriting previous * values, if present. * * The data is stored as RTE_FUNCTION type RTE of a special * citus_extradata_container function, with the extra data serialized into the * function arguments. That works, because these RTEs aren't used by Postgres * to any significant degree, and Citus' variant of ruleutils.c knows how to * deal with these extended RTEs. Note that rte->eref needs to be set prior * to calling SetRangeTblExtraData to ensure the funccolcount can be set * correctly. * * NB: If used for postgres defined RTEKinds, fields specific to that RTEKind * will not be handled by out/readfuncs.c. For the current uses that's ok. */ void SetRangeTblExtraData(RangeTblEntry *rte, CitusRTEKind rteKind, char *fragmentSchemaName, char *fragmentTableName, List *tableIdList) { RangeTblFunction *fauxFunction = NULL; FuncExpr *fauxFuncExpr = NULL; Const *rteKindData = NULL; Const *fragmentSchemaData = NULL; Const *fragmentTableData = NULL; Const *tableIdListData = NULL; Assert(rte->eref); /* store RTE kind as a plain int4 */ rteKindData = makeNode(Const); rteKindData->consttype = INT4OID; rteKindData->constlen = 4; rteKindData->constvalue = Int32GetDatum(rteKind); rteKindData->constbyval = true; rteKindData->constisnull = false; rteKindData->location = -1; /* store the fragment schema as a cstring */ fragmentSchemaData = makeNode(Const); fragmentSchemaData->consttype = CSTRINGOID; fragmentSchemaData->constlen = -2; fragmentSchemaData->constvalue = CStringGetDatum(fragmentSchemaName); fragmentSchemaData->constbyval = false; fragmentSchemaData->constisnull = fragmentSchemaName == NULL; fragmentSchemaData->location = -1; /* store the fragment name as a cstring */ fragmentTableData = makeNode(Const); fragmentTableData->consttype = CSTRINGOID; fragmentTableData->constlen = -2; fragmentTableData->constvalue = CStringGetDatum(fragmentTableName); fragmentTableData->constbyval = false; fragmentTableData->constisnull = fragmentTableName == NULL; fragmentTableData->location = -1; /* store the table id list as an array of integers: FIXME */ tableIdListData = makeNode(Const); tableIdListData->consttype = CSTRINGOID; tableIdListData->constbyval = false; tableIdListData->constlen = -2; tableIdListData->location = -1; /* serialize tableIdList to a string, seems simplest that way */ if (tableIdList != NIL) { char *serializedList = nodeToString(tableIdList); tableIdListData->constisnull = false; tableIdListData->constvalue = CStringGetDatum(serializedList); } else { tableIdListData->constisnull = true; } /* create function expression to store our faux arguments in */ fauxFuncExpr = makeNode(FuncExpr); fauxFuncExpr->funcid = CitusExtraDataContainerFuncId(); fauxFuncExpr->funcretset = true; fauxFuncExpr->location = -1; fauxFuncExpr->args = list_make4(rteKindData, fragmentSchemaData, fragmentTableData, tableIdListData); fauxFunction = makeNode(RangeTblFunction); fauxFunction->funcexpr = (Node *) fauxFuncExpr; /* set the column count to pass ruleutils checks, not used elsewhere */ fauxFunction->funccolcount = list_length(rte->eref->colnames); rte->rtekind = RTE_FUNCTION; rte->functions = list_make1(fauxFunction); }
/* * ExtractRangeTblExtraData extracts extra data stored for a range table entry * that previously has been stored with * Set/ModifyRangeTblExtraData. Parameters can be NULL if unintersting. It is * valid to use the function on a RTE without extra data. */ void ExtractRangeTblExtraData(RangeTblEntry *rte, CitusRTEKind *rteKind, char **fragmentSchemaName, char **fragmentTableName, List **tableIdList) { RangeTblFunction *fauxFunction = NULL; FuncExpr *fauxFuncExpr = NULL; Const *tmpConst = NULL; /* set base rte kind first, so this can be used for 'non-extended' RTEs as well */ if (rteKind != NULL) { *rteKind = (CitusRTEKind) rte->rtekind; } /* reset values of optionally-present fields, will later be overwritten, if present */ if (fragmentSchemaName != NULL) { *fragmentSchemaName = NULL; } if (fragmentTableName != NULL) { *fragmentTableName = NULL; } if (tableIdList != NULL) { *tableIdList = NIL; } /* only function RTEs have our special extra data */ if (rte->rtekind != RTE_FUNCTION) { return; } /* we only ever generate one argument */ if (list_length(rte->functions) != 1) { return; } /* should pretty much always be a FuncExpr, but be liberal in what we expect... */ fauxFunction = linitial(rte->functions); if (!IsA(fauxFunction->funcexpr, FuncExpr)) { return; } fauxFuncExpr = (FuncExpr *) fauxFunction->funcexpr; /* * There will never be a range table entry with this function id, but for * the purpose of this file. */ if (fauxFuncExpr->funcid != CitusExtraDataContainerFuncId()) { return; } /* * Extra data for rtes is stored in the function arguments. The first * argument stores the rtekind, second fragmentSchemaName, third * fragmentTableName, fourth tableIdList. */ if (list_length(fauxFuncExpr->args) != 4) { ereport(ERROR, (errmsg("unexpected number of function arguments to " "citus_extradata_container"))); return; } /* extract rteKind */ tmpConst = (Const *) linitial(fauxFuncExpr->args); Assert(IsA(tmpConst, Const)); Assert(tmpConst->consttype == INT4OID); if (rteKind != NULL) { *rteKind = DatumGetInt32(tmpConst->constvalue); } /* extract fragmentSchemaName */ tmpConst = (Const *) lsecond(fauxFuncExpr->args); Assert(IsA(tmpConst, Const)); Assert(tmpConst->consttype == CSTRINGOID); if (fragmentSchemaName != NULL && !tmpConst->constisnull) { *fragmentSchemaName = DatumGetCString(tmpConst->constvalue); } /* extract fragmentTableName */ tmpConst = (Const *) lthird(fauxFuncExpr->args); Assert(IsA(tmpConst, Const)); Assert(tmpConst->consttype == CSTRINGOID); if (fragmentTableName != NULL && !tmpConst->constisnull) { *fragmentTableName = DatumGetCString(tmpConst->constvalue); } /* extract tableIdList, stored as a serialized integer list */ tmpConst = (Const *) lfourth(fauxFuncExpr->args); Assert(IsA(tmpConst, Const)); Assert(tmpConst->consttype == CSTRINGOID); if (tableIdList != NULL && !tmpConst->constisnull) { Node *deserializedList = stringToNode(DatumGetCString(tmpConst->constvalue)); Assert(IsA(deserializedList, IntList)); *tableIdList = (List *) deserializedList; } }
/* * CreateCitusToplevelNode creates the top-level planTree node for a * distributed statement. That top-level node is a) recognizable by the * executor hooks, allowing them to redirect execution, b) contains the * parameters required for distributed execution. * * The exact representation of the top-level node is an implementation detail * which should not be referred to outside this file, as it's likely to become * version dependant. Use GetMultiPlan() and HasCitusToplevelNode() to access. * * Internally the data is stored as arguments to a 'citus_extradata_container' * function, which has to be removed from the really executed plan tree before * query execution. */ PlannedStmt * MultiQueryContainerNode(PlannedStmt *result, MultiPlan *multiPlan) { FunctionScan *fauxFunctionScan = NULL; RangeTblFunction *fauxFunction = NULL; FuncExpr *fauxFuncExpr = NULL; Const *multiPlanData = NULL; char *serializedPlan = NULL; /* pass multiPlan serialized as a constant function argument */ serializedPlan = CitusNodeToString(multiPlan); multiPlanData = makeNode(Const); multiPlanData->consttype = CSTRINGOID; multiPlanData->constlen = strlen(serializedPlan); multiPlanData->constvalue = CStringGetDatum(serializedPlan); multiPlanData->constbyval = false; multiPlanData->location = -1; fauxFuncExpr = makeNode(FuncExpr); fauxFuncExpr->funcid = CitusExtraDataContainerFuncId(); fauxFuncExpr->funcretset = true; fauxFuncExpr->location = -1; fauxFuncExpr->args = list_make1(multiPlanData); fauxFunction = makeNode(RangeTblFunction); fauxFunction->funcexpr = (Node *) fauxFuncExpr; fauxFunctionScan = makeNode(FunctionScan); fauxFunctionScan->functions = lappend(fauxFunctionScan->functions, fauxFunction); /* copy original targetlist, accessed for RETURNING queries */ fauxFunctionScan->scan.plan.targetlist = copyObject(result->planTree->targetlist); /* * Add set returning function to target list if the original (postgres * created) plan doesn't support backward scans; doing so prevents * backward scans being supported by the new plantree as well. This is * ugly as hell, but until we can rely on custom scans (which can signal * this via CUSTOMPATH_SUPPORT_BACKWARD_SCAN), there's not really a pretty * method to achieve this. * * FIXME: This should really be done on the master select plan. */ if (!ExecSupportsBackwardScan(result->planTree)) { FuncExpr *funcExpr = makeNode(FuncExpr); TargetEntry *targetEntry = NULL; bool resjunkAttribute = true; funcExpr->funcretset = true; targetEntry = makeTargetEntry((Expr *) funcExpr, InvalidAttrNumber, NULL, resjunkAttribute); fauxFunctionScan->scan.plan.targetlist = lappend(fauxFunctionScan->scan.plan.targetlist, targetEntry); } result->planTree = (Plan *) fauxFunctionScan; return result; }