| /*------------------------------------------------------------------------- |
| * |
| * execExpr.c |
| * Expression evaluation infrastructure. |
| * |
| * During executor startup, we compile each expression tree (which has |
| * previously been processed by the parser and planner) into an ExprState, |
| * using ExecInitExpr() et al. This converts the tree into a flat array |
| * of ExprEvalSteps, which may be thought of as instructions in a program. |
| * At runtime, we'll execute steps, starting with the first, until we reach |
| * an EEOP_DONE opcode. |
| * |
| * This file contains the "compilation" logic. It is independent of the |
| * specific execution technology we use (switch statement, computed goto, |
| * JIT compilation, etc). |
| * |
| * See src/backend/executor/README for some background, specifically the |
| * "Expression Trees and ExprState nodes", "Expression Initialization", |
| * and "Expression Evaluation" sections. |
| * |
| * |
| * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * |
| * IDENTIFICATION |
| * src/backend/executor/execExpr.c |
| * |
| *------------------------------------------------------------------------- |
| */ |
| #include "postgres.h" |
| |
| #include "access/nbtree.h" |
| #include "catalog/objectaccess.h" |
| #include "catalog/pg_proc.h" |
| #include "catalog/pg_type.h" |
| #include "executor/execExpr.h" |
| #include "executor/nodeSubplan.h" |
| #include "funcapi.h" |
| #include "jit/jit.h" |
| #include "miscadmin.h" |
| #include "nodes/makefuncs.h" |
| #include "nodes/nodeFuncs.h" |
| #include "nodes/subscripting.h" |
| #include "optimizer/optimizer.h" |
| #include "pgstat.h" |
| #include "utils/acl.h" |
| #include "utils/array.h" |
| #include "utils/builtins.h" |
| #include "utils/datum.h" |
| #include "utils/fmgroids.h" |
| #include "utils/lsyscache.h" |
| #include "utils/typcache.h" |
| |
| #include "access/detoast.h" |
| #include "access/heaptoast.h" |
| #include "catalog/pg_collation.h" |
| #include "cdb/cdbvars.h" |
| #include "utils/pg_locale.h" |
| |
| #include "port/pg_bitutils.h" |
| |
| typedef struct ExprSetupInfo |
| { |
| /* Highest attribute numbers fetched from inner/outer/scan tuple slots: */ |
| AttrNumber last_inner; |
| AttrNumber last_outer; |
| AttrNumber last_scan; |
| /* MULTIEXPR SubPlan nodes appearing in the expression: */ |
| List *multiexpr_subplans; |
| } ExprSetupInfo; |
| |
| static void ExecReadyExpr(ExprState *state); |
| static void ExecInitExprRec(Expr *node, ExprState *state, |
| Datum *resv, bool *resnull); |
| static void ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, |
| Oid funcid, Oid inputcollid, |
| ExprState *state); |
| static void ExecCreateExprSetupSteps(ExprState *state, Node *node); |
| static void ExecPushExprSetupSteps(ExprState *state, ExprSetupInfo *info); |
| static bool expr_setup_walker(Node *node, ExprSetupInfo *info); |
| static bool ExecComputeSlotInfo(ExprState *state, ExprEvalStep *op); |
| static void ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable, |
| ExprState *state); |
| static void ExecInitSubscriptingRef(ExprEvalStep *scratch, |
| SubscriptingRef *sbsref, |
| ExprState *state, |
| Datum *resv, bool *resnull); |
| static bool ExecInitScalarArrayOpFastPath(ExprEvalStep *scratch, |
| ScalarArrayOpExpr *opexpr, |
| ExprState *state, Datum *resv, bool *resnull); |
| |
| static bool isAssignmentIndirectionExpr(Expr *expr); |
| static void ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest, |
| ExprState *state, |
| Datum *resv, bool *resnull); |
| static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate, |
| ExprEvalStep *scratch, |
| FunctionCallInfo fcinfo, AggStatePerTrans pertrans, |
| int transno, int setno, int setoff, bool ishash, |
| bool nullcheck); |
| |
| |
| /* |
| * ExecInitExpr: prepare an expression tree for execution |
| * |
| * This function builds and returns an ExprState implementing the given |
| * Expr node tree. The return ExprState can then be handed to ExecEvalExpr |
| * for execution. Because the Expr tree itself is read-only as far as |
| * ExecInitExpr and ExecEvalExpr are concerned, several different executions |
| * of the same plan tree can occur concurrently. (But note that an ExprState |
| * does mutate at runtime, so it can't be re-used concurrently.) |
| * |
| * This must be called in a memory context that will last as long as repeated |
| * executions of the expression are needed. Typically the context will be |
| * the same as the per-query context of the associated ExprContext. |
| * |
| * Any Aggref, WindowFunc, or SubPlan nodes found in the tree are added to |
| * the lists of such nodes held by the parent PlanState. |
| * |
| * Note: there is no ExecEndExpr function; we assume that any resource |
| * cleanup needed will be handled by just releasing the memory context |
| * in which the state tree is built. Functions that require additional |
| * cleanup work can register a shutdown callback in the ExprContext. |
| * |
| * 'node' is the root of the expression tree to compile. |
| * 'parent' is the PlanState node that owns the expression. |
| * |
| * 'parent' may be NULL if we are preparing an expression that is not |
| * associated with a plan tree. (If so, it can't have aggs or subplans.) |
| * Such cases should usually come through ExecPrepareExpr, not directly here. |
| * |
| * Also, if 'node' is NULL, we just return NULL. This is convenient for some |
| * callers that may or may not have an expression that needs to be compiled. |
| * Note that a NULL ExprState pointer *cannot* be handed to ExecEvalExpr, |
| * although ExecQual and ExecCheck will accept one (and treat it as "true"). |
| */ |
| ExprState * |
| ExecInitExpr(Expr *node, PlanState *parent) |
| { |
| ExprState *state; |
| ExprEvalStep scratch = {0}; |
| |
| /* Special case: NULL expression produces a NULL ExprState pointer */ |
| if (node == NULL) |
| return NULL; |
| |
| /* Initialize ExprState with empty step list */ |
| state = makeNode(ExprState); |
| state->expr = node; |
| state->parent = parent; |
| state->ext_params = NULL; |
| |
| /* Insert setup steps as needed */ |
| ExecCreateExprSetupSteps(state, (Node *) node); |
| |
| /* Compile the expression proper */ |
| ExecInitExprRec(node, state, &state->resvalue, &state->resnull); |
| |
| /* Finally, append a DONE step */ |
| scratch.opcode = EEOP_DONE; |
| ExprEvalPushStep(state, &scratch); |
| |
| ExecReadyExpr(state); |
| |
| return state; |
| } |
| |
| /* |
| * ExecInitExprWithParams: prepare a standalone expression tree for execution |
| * |
| * This is the same as ExecInitExpr, except that there is no parent PlanState, |
| * and instead we may have a ParamListInfo describing PARAM_EXTERN Params. |
| */ |
| ExprState * |
| ExecInitExprWithParams(Expr *node, ParamListInfo ext_params) |
| { |
| ExprState *state; |
| ExprEvalStep scratch = {0}; |
| |
| /* Special case: NULL expression produces a NULL ExprState pointer */ |
| if (node == NULL) |
| return NULL; |
| |
| /* Initialize ExprState with empty step list */ |
| state = makeNode(ExprState); |
| state->expr = node; |
| state->parent = NULL; |
| state->ext_params = ext_params; |
| |
| /* Insert setup steps as needed */ |
| ExecCreateExprSetupSteps(state, (Node *) node); |
| |
| /* Compile the expression proper */ |
| ExecInitExprRec(node, state, &state->resvalue, &state->resnull); |
| |
| /* Finally, append a DONE step */ |
| scratch.opcode = EEOP_DONE; |
| ExprEvalPushStep(state, &scratch); |
| |
| ExecReadyExpr(state); |
| |
| return state; |
| } |
| |
| /* |
| * ExecInitQual: prepare a qual for execution by ExecQual |
| * |
| * Prepares for the evaluation of a conjunctive boolean expression (qual list |
| * with implicit AND semantics) that returns true if none of the |
| * subexpressions are false. |
| * |
| * We must return true if the list is empty. Since that's a very common case, |
| * we optimize it a bit further by translating to a NULL ExprState pointer |
| * rather than setting up an ExprState that computes constant TRUE. (Some |
| * especially hot-spot callers of ExecQual detect this and avoid calling |
| * ExecQual at all.) |
| * |
| * If any of the subexpressions yield NULL, then the result of the conjunction |
| * is false. This makes ExecQual primarily useful for evaluating WHERE |
| * clauses, since SQL specifies that tuples with null WHERE results do not |
| * get selected. |
| */ |
| ExprState * |
| ExecInitQual(List *qual, PlanState *parent) |
| { |
| ExprState *state; |
| ExprEvalStep scratch = {0}; |
| List *adjust_jumps = NIL; |
| ListCell *lc; |
| |
| /* short-circuit (here and in ExecQual) for empty restriction list */ |
| if (qual == NIL) |
| return NULL; |
| |
| Assert(IsA(qual, List)); |
| |
| state = makeNode(ExprState); |
| state->expr = (Expr *) qual; |
| state->parent = parent; |
| state->ext_params = NULL; |
| |
| /* mark expression as to be used with ExecQual() */ |
| state->flags = EEO_FLAG_IS_QUAL; |
| |
| /* Insert setup steps as needed */ |
| ExecCreateExprSetupSteps(state, (Node *) qual); |
| |
| /* |
| * ExecQual() needs to return false for an expression returning NULL. That |
| * allows us to short-circuit the evaluation the first time a NULL is |
| * encountered. As qual evaluation is a hot-path this warrants using a |
| * special opcode for qual evaluation that's simpler than BOOL_AND (which |
| * has more complex NULL handling). |
| */ |
| scratch.opcode = EEOP_QUAL; |
| |
| /* |
| * We can use ExprState's resvalue/resnull as target for each qual expr. |
| */ |
| scratch.resvalue = &state->resvalue; |
| scratch.resnull = &state->resnull; |
| |
| foreach(lc, qual) |
| { |
| Expr *node = (Expr *) lfirst(lc); |
| |
| /* first evaluate expression */ |
| ExecInitExprRec(node, state, &state->resvalue, &state->resnull); |
| |
| /* then emit EEOP_QUAL to detect if it's false (or null) */ |
| scratch.d.qualexpr.jumpdone = -1; |
| ExprEvalPushStep(state, &scratch); |
| adjust_jumps = lappend_int(adjust_jumps, |
| state->steps_len - 1); |
| } |
| |
| /* adjust jump targets */ |
| foreach(lc, adjust_jumps) |
| { |
| ExprEvalStep *as = &state->steps[lfirst_int(lc)]; |
| |
| Assert(as->opcode == EEOP_QUAL); |
| Assert(as->d.qualexpr.jumpdone == -1); |
| as->d.qualexpr.jumpdone = state->steps_len; |
| } |
| |
| /* |
| * At the end, we don't need to do anything more. The last qual expr must |
| * have yielded TRUE, and since its result is stored in the desired output |
| * location, we're done. |
| */ |
| scratch.opcode = EEOP_DONE; |
| ExprEvalPushStep(state, &scratch); |
| |
| ExecReadyExpr(state); |
| |
| return state; |
| } |
| |
| /* |
| * ExecInitCheck: prepare a check constraint for execution by ExecCheck |
| * |
| * This is much like ExecInitQual/ExecQual, except that a null result from |
| * the conjunction is treated as TRUE. This behavior is appropriate for |
| * evaluating CHECK constraints, since SQL specifies that NULL constraint |
| * conditions are not failures. |
| * |
| * Note that like ExecInitQual, this expects input in implicit-AND format. |
| * Users of ExecCheck that have expressions in normal explicit-AND format |
| * can just apply ExecInitExpr to produce suitable input for ExecCheck. |
| */ |
| ExprState * |
| ExecInitCheck(List *qual, PlanState *parent) |
| { |
| /* short-circuit (here and in ExecCheck) for empty restriction list */ |
| if (qual == NIL) |
| return NULL; |
| |
| Assert(IsA(qual, List)); |
| |
| /* |
| * Just convert the implicit-AND list to an explicit AND (if there's more |
| * than one entry), and compile normally. Unlike ExecQual, we can't |
| * short-circuit on NULL results, so the regular AND behavior is needed. |
| */ |
| return ExecInitExpr(make_ands_explicit(qual), parent); |
| } |
| |
| /* |
| * Call ExecInitExpr() on a list of expressions, return a list of ExprStates. |
| */ |
| List * |
| ExecInitExprList(List *nodes, PlanState *parent) |
| { |
| List *result = NIL; |
| ListCell *lc; |
| |
| foreach(lc, nodes) |
| { |
| Expr *e = lfirst(lc); |
| |
| result = lappend(result, ExecInitExpr(e, parent)); |
| } |
| |
| return result; |
| } |
| |
| /* |
| * ExecBuildProjectionInfo |
| * |
| * Build a ProjectionInfo node for evaluating the given tlist in the given |
| * econtext, and storing the result into the tuple slot. (Caller must have |
| * ensured that tuple slot has a descriptor matching the tlist!) |
| * |
| * inputDesc can be NULL, but if it is not, we check to see whether simple |
| * Vars in the tlist match the descriptor. It is important to provide |
| * inputDesc for relation-scan plan nodes, as a cross check that the relation |
| * hasn't been changed since the plan was made. At higher levels of a plan, |
| * there is no need to recheck. |
| * |
| * This is implemented by internally building an ExprState that performs the |
| * whole projection in one go. |
| * |
| * Caution: before PG v10, the targetList was a list of ExprStates; now it |
| * should be the planner-created targetlist, since we do the compilation here. |
| */ |
| ProjectionInfo * |
| ExecBuildProjectionInfo(List *targetList, |
| ExprContext *econtext, |
| TupleTableSlot *slot, |
| PlanState *parent, |
| TupleDesc inputDesc) |
| { |
| ProjectionInfo *projInfo = makeNode(ProjectionInfo); |
| ExprState *state; |
| ExprEvalStep scratch = {0}; |
| ListCell *lc; |
| |
| projInfo->pi_exprContext = econtext; |
| /* We embed ExprState into ProjectionInfo instead of doing extra palloc */ |
| projInfo->pi_state.type = T_ExprState; |
| state = &projInfo->pi_state; |
| state->expr = (Expr *) targetList; |
| state->parent = parent; |
| state->ext_params = NULL; |
| |
| state->resultslot = slot; |
| |
| /* Insert setup steps as needed */ |
| ExecCreateExprSetupSteps(state, (Node *) targetList); |
| |
| /* Now compile each tlist column */ |
| foreach(lc, targetList) |
| { |
| TargetEntry *tle = lfirst_node(TargetEntry, lc); |
| Var *variable = NULL; |
| AttrNumber attnum = 0; |
| bool isSafeVar = false; |
| |
| /* |
| * If tlist expression is a safe non-system Var, use the fast-path |
| * ASSIGN_*_VAR opcodes. "Safe" means that we don't need to apply |
| * CheckVarSlotCompatibility() during plan startup. If a source slot |
| * was provided, we make the equivalent tests here; if a slot was not |
| * provided, we assume that no check is needed because we're dealing |
| * with a non-relation-scan-level expression. |
| */ |
| if (tle->expr != NULL && |
| IsA(tle->expr, Var) && |
| ((Var *) tle->expr)->varattno > 0) |
| { |
| /* Non-system Var, but how safe is it? */ |
| variable = (Var *) tle->expr; |
| attnum = variable->varattno; |
| |
| if (inputDesc == NULL) |
| isSafeVar = true; /* can't check, just assume OK */ |
| else if (attnum <= inputDesc->natts) |
| { |
| Form_pg_attribute attr = TupleDescAttr(inputDesc, attnum - 1); |
| |
| /* |
| * If user attribute is dropped or has a type mismatch, don't |
| * use ASSIGN_*_VAR. Instead let the normal expression |
| * machinery handle it (which'll possibly error out). |
| */ |
| if (!attr->attisdropped && variable->vartype == attr->atttypid) |
| { |
| isSafeVar = true; |
| } |
| } |
| } |
| |
| if (isSafeVar) |
| { |
| /* Fast-path: just generate an EEOP_ASSIGN_*_VAR step */ |
| switch (variable->varno) |
| { |
| case INNER_VAR: |
| /* get the tuple from the inner node */ |
| scratch.opcode = EEOP_ASSIGN_INNER_VAR; |
| break; |
| |
| case OUTER_VAR: |
| /* get the tuple from the outer node */ |
| scratch.opcode = EEOP_ASSIGN_OUTER_VAR; |
| break; |
| |
| /* INDEX_VAR is handled by default case */ |
| |
| default: |
| /* get the tuple from the relation being scanned */ |
| scratch.opcode = EEOP_ASSIGN_SCAN_VAR; |
| break; |
| } |
| |
| scratch.d.assign_var.attnum = attnum - 1; |
| scratch.d.assign_var.resultnum = tle->resno - 1; |
| ExprEvalPushStep(state, &scratch); |
| } |
| else |
| { |
| /* |
| * Otherwise, compile the column expression normally. |
| * |
| * We can't tell the expression to evaluate directly into the |
| * result slot, as the result slot (and the exprstate for that |
| * matter) can change between executions. We instead evaluate |
| * into the ExprState's resvalue/resnull and then move. |
| */ |
| ExecInitExprRec(tle->expr, state, |
| &state->resvalue, &state->resnull); |
| |
| /* |
| * Column might be referenced multiple times in upper nodes, so |
| * force value to R/O - but only if it could be an expanded datum. |
| */ |
| if (get_typlen(exprType((Node *) tle->expr)) == -1) |
| scratch.opcode = EEOP_ASSIGN_TMP_MAKE_RO; |
| else |
| scratch.opcode = EEOP_ASSIGN_TMP; |
| scratch.d.assign_tmp.resultnum = tle->resno - 1; |
| ExprEvalPushStep(state, &scratch); |
| } |
| } |
| |
| scratch.opcode = EEOP_DONE; |
| ExprEvalPushStep(state, &scratch); |
| |
| ExecReadyExpr(state); |
| |
| return projInfo; |
| } |
| |
| /* |
| * ExecBuildUpdateProjection |
| * |
| * Build a ProjectionInfo node for constructing a new tuple during UPDATE. |
| * The projection will be executed in the given econtext and the result will |
| * be stored into the given tuple slot. (Caller must have ensured that tuple |
| * slot has a descriptor matching the target rel!) |
| * |
| * When evalTargetList is false, targetList contains the UPDATE ... SET |
| * expressions that have already been computed by a subplan node; the values |
| * from this tlist are assumed to be available in the "outer" tuple slot. |
| * When evalTargetList is true, targetList contains the UPDATE ... SET |
| * expressions that must be computed (which could contain references to |
| * the outer, inner, or scan tuple slots). |
| * |
| * In either case, targetColnos contains a list of the target column numbers |
| * corresponding to the non-resjunk entries of targetList. The tlist values |
| * are assigned into these columns of the result tuple slot. Target columns |
| * not listed in targetColnos are filled from the UPDATE's old tuple, which |
| * is assumed to be available in the "scan" tuple slot. |
| * |
| * targetList can also contain resjunk columns. These must be evaluated |
| * if evalTargetList is true, but their values are discarded. |
| * |
| * relDesc must describe the relation we intend to update. |
| * |
| * This is basically a specialized variant of ExecBuildProjectionInfo. |
| * However, it also performs sanity checks equivalent to ExecCheckPlanOutput. |
| * Since we never make a normal tlist equivalent to the whole |
| * tuple-to-be-assigned, there is no convenient way to apply |
| * ExecCheckPlanOutput, so we must do our safety checks here. |
| */ |
| ProjectionInfo * |
| ExecBuildUpdateProjection(List *targetList, |
| bool evalTargetList, |
| List *targetColnos, |
| TupleDesc relDesc, |
| ExprContext *econtext, |
| TupleTableSlot *slot, |
| PlanState *parent) |
| { |
| ProjectionInfo *projInfo = makeNode(ProjectionInfo); |
| ExprState *state; |
| int nAssignableCols; |
| bool sawJunk; |
| Bitmapset *assignedCols; |
| ExprSetupInfo deform = {0, 0, 0, NIL}; |
| ExprEvalStep scratch = {0}; |
| int outerattnum; |
| ListCell *lc, |
| *lc2; |
| |
| projInfo->pi_exprContext = econtext; |
| /* We embed ExprState into ProjectionInfo instead of doing extra palloc */ |
| projInfo->pi_state.type = T_ExprState; |
| state = &projInfo->pi_state; |
| if (evalTargetList) |
| state->expr = (Expr *) targetList; |
| else |
| state->expr = NULL; /* not used */ |
| state->parent = parent; |
| state->ext_params = NULL; |
| |
| state->resultslot = slot; |
| |
| /* |
| * Examine the targetList to see how many non-junk columns there are, and |
| * to verify that the non-junk columns come before the junk ones. |
| */ |
| nAssignableCols = 0; |
| sawJunk = false; |
| foreach(lc, targetList) |
| { |
| TargetEntry *tle = lfirst_node(TargetEntry, lc); |
| |
| if (tle->resjunk) |
| sawJunk = true; |
| else |
| { |
| if (sawJunk) |
| elog(ERROR, "subplan target list is out of order"); |
| nAssignableCols++; |
| } |
| } |
| |
| /* We should have one targetColnos entry per non-junk column */ |
| if (nAssignableCols != list_length(targetColnos)) |
| elog(ERROR, "targetColnos does not match subplan target list"); |
| |
| /* |
| * Build a bitmapset of the columns in targetColnos. (We could just use |
| * list_member_int() tests, but that risks O(N^2) behavior with many |
| * columns.) |
| */ |
| assignedCols = NULL; |
| foreach(lc, targetColnos) |
| { |
| AttrNumber targetattnum = lfirst_int(lc); |
| |
| assignedCols = bms_add_member(assignedCols, targetattnum); |
| } |
| |
| /* |
| * We need to insert EEOP_*_FETCHSOME steps to ensure the input tuples are |
| * sufficiently deconstructed. The scan tuple must be deconstructed at |
| * least as far as the last old column we need. |
| */ |
| for (int attnum = relDesc->natts; attnum > 0; attnum--) |
| { |
| Form_pg_attribute attr = TupleDescAttr(relDesc, attnum - 1); |
| |
| if (attr->attisdropped) |
| continue; |
| if (bms_is_member(attnum, assignedCols)) |
| continue; |
| deform.last_scan = attnum; |
| break; |
| } |
| |
| /* |
| * If we're actually evaluating the tlist, incorporate its input |
| * requirements too; otherwise, we'll just need to fetch the appropriate |
| * number of columns of the "outer" tuple. |
| */ |
| if (evalTargetList) |
| expr_setup_walker((Node *) targetList, &deform); |
| else |
| deform.last_outer = nAssignableCols; |
| |
| ExecPushExprSetupSteps(state, &deform); |
| |
| /* |
| * Now generate code to evaluate the tlist's assignable expressions or |
| * fetch them from the outer tuple, incidentally validating that they'll |
| * be of the right data type. The checks above ensure that the forboth() |
| * will iterate over exactly the non-junk columns. Note that we don't |
| * bother evaluating any remaining resjunk columns. |
| */ |
| outerattnum = 0; |
| forboth(lc, targetList, lc2, targetColnos) |
| { |
| TargetEntry *tle = lfirst_node(TargetEntry, lc); |
| AttrNumber targetattnum = lfirst_int(lc2); |
| Form_pg_attribute attr; |
| |
| Assert(!tle->resjunk); |
| |
| /* |
| * Apply sanity checks comparable to ExecCheckPlanOutput(). |
| */ |
| if (targetattnum <= 0 || targetattnum > relDesc->natts) |
| ereport(ERROR, |
| (errcode(ERRCODE_DATATYPE_MISMATCH), |
| errmsg("table row type and query-specified row type do not match"), |
| errdetail("Query has too many columns."))); |
| attr = TupleDescAttr(relDesc, targetattnum - 1); |
| |
| if (attr->attisdropped) |
| ereport(ERROR, |
| (errcode(ERRCODE_DATATYPE_MISMATCH), |
| errmsg("table row type and query-specified row type do not match"), |
| errdetail("Query provides a value for a dropped column at ordinal position %d.", |
| targetattnum))); |
| if (exprType((Node *) tle->expr) != attr->atttypid) |
| ereport(ERROR, |
| (errcode(ERRCODE_DATATYPE_MISMATCH), |
| errmsg("table row type and query-specified row type do not match"), |
| errdetail("Table has type %s at ordinal position %d, but query expects %s.", |
| format_type_be(attr->atttypid), |
| targetattnum, |
| format_type_be(exprType((Node *) tle->expr))))); |
| |
| /* OK, generate code to perform the assignment. */ |
| if (evalTargetList) |
| { |
| /* |
| * We must evaluate the TLE's expression and assign it. We do not |
| * bother jumping through hoops for "safe" Vars like |
| * ExecBuildProjectionInfo does; this is a relatively less-used |
| * path and it doesn't seem worth expending code for that. |
| */ |
| ExecInitExprRec(tle->expr, state, |
| &state->resvalue, &state->resnull); |
| /* Needn't worry about read-only-ness here, either. */ |
| scratch.opcode = EEOP_ASSIGN_TMP; |
| scratch.d.assign_tmp.resultnum = targetattnum - 1; |
| ExprEvalPushStep(state, &scratch); |
| } |
| else |
| { |
| /* Just assign from the outer tuple. */ |
| scratch.opcode = EEOP_ASSIGN_OUTER_VAR; |
| scratch.d.assign_var.attnum = outerattnum; |
| scratch.d.assign_var.resultnum = targetattnum - 1; |
| ExprEvalPushStep(state, &scratch); |
| } |
| outerattnum++; |
| } |
| |
| /* |
| * Now generate code to copy over any old columns that were not assigned |
| * to, and to ensure that dropped columns are set to NULL. |
| */ |
| for (int attnum = 1; attnum <= relDesc->natts; attnum++) |
| { |
| Form_pg_attribute attr = TupleDescAttr(relDesc, attnum - 1); |
| |
| if (attr->attisdropped) |
| { |
| /* Put a null into the ExprState's resvalue/resnull ... */ |
| scratch.opcode = EEOP_CONST; |
| scratch.resvalue = &state->resvalue; |
| scratch.resnull = &state->resnull; |
| scratch.d.constval.value = (Datum) 0; |
| scratch.d.constval.isnull = true; |
| ExprEvalPushStep(state, &scratch); |
| /* ... then assign it to the result slot */ |
| scratch.opcode = EEOP_ASSIGN_TMP; |
| scratch.d.assign_tmp.resultnum = attnum - 1; |
| ExprEvalPushStep(state, &scratch); |
| } |
| else if (!bms_is_member(attnum, assignedCols)) |
| { |
| /* Certainly the right type, so needn't check */ |
| scratch.opcode = EEOP_ASSIGN_SCAN_VAR; |
| scratch.d.assign_var.attnum = attnum - 1; |
| scratch.d.assign_var.resultnum = attnum - 1; |
| ExprEvalPushStep(state, &scratch); |
| } |
| } |
| |
| scratch.opcode = EEOP_DONE; |
| ExprEvalPushStep(state, &scratch); |
| |
| ExecReadyExpr(state); |
| |
| return projInfo; |
| } |
| |
| /* |
| * ExecPrepareExpr --- initialize for expression execution outside a normal |
| * Plan tree context. |
| * |
| * This differs from ExecInitExpr in that we don't assume the caller is |
| * already running in the EState's per-query context. Also, we run the |
| * passed expression tree through expression_planner() to prepare it for |
| * execution. (In ordinary Plan trees the regular planning process will have |
| * made the appropriate transformations on expressions, but for standalone |
| * expressions this won't have happened.) |
| */ |
| ExprState * |
| ExecPrepareExpr(Expr *node, EState *estate) |
| { |
| ExprState *result; |
| MemoryContext oldcontext; |
| |
| oldcontext = MemoryContextSwitchTo(estate->es_query_cxt); |
| |
| node = expression_planner(node); |
| |
| result = ExecInitExpr(node, NULL); |
| |
| MemoryContextSwitchTo(oldcontext); |
| |
| return result; |
| } |
| |
| /* |
| * ExecPrepareQual --- initialize for qual execution outside a normal |
| * Plan tree context. |
| * |
| * This differs from ExecInitQual in that we don't assume the caller is |
| * already running in the EState's per-query context. Also, we run the |
| * passed expression tree through expression_planner() to prepare it for |
| * execution. (In ordinary Plan trees the regular planning process will have |
| * made the appropriate transformations on expressions, but for standalone |
| * expressions this won't have happened.) |
| */ |
| ExprState * |
| ExecPrepareQual(List *qual, EState *estate) |
| { |
| ExprState *result; |
| MemoryContext oldcontext; |
| |
| oldcontext = MemoryContextSwitchTo(estate->es_query_cxt); |
| |
| qual = (List *) expression_planner((Expr *) qual); |
| |
| result = ExecInitQual(qual, NULL); |
| |
| MemoryContextSwitchTo(oldcontext); |
| |
| return result; |
| } |
| |
| /* |
| * ExecPrepareCheck -- initialize check constraint for execution outside a |
| * normal Plan tree context. |
| * |
| * See ExecPrepareExpr() and ExecInitCheck() for details. |
| */ |
| ExprState * |
| ExecPrepareCheck(List *qual, EState *estate) |
| { |
| ExprState *result; |
| MemoryContext oldcontext; |
| |
| oldcontext = MemoryContextSwitchTo(estate->es_query_cxt); |
| |
| qual = (List *) expression_planner((Expr *) qual); |
| |
| result = ExecInitCheck(qual, NULL); |
| |
| MemoryContextSwitchTo(oldcontext); |
| |
| return result; |
| } |
| |
| /* |
| * Call ExecPrepareExpr() on each member of a list of Exprs, and return |
| * a list of ExprStates. |
| * |
| * See ExecPrepareExpr() for details. |
| */ |
| List * |
| ExecPrepareExprList(List *nodes, EState *estate) |
| { |
| List *result = NIL; |
| MemoryContext oldcontext; |
| ListCell *lc; |
| |
| /* Ensure that the list cell nodes are in the right context too */ |
| oldcontext = MemoryContextSwitchTo(estate->es_query_cxt); |
| |
| foreach(lc, nodes) |
| { |
| Expr *e = (Expr *) lfirst(lc); |
| |
| result = lappend(result, ExecPrepareExpr(e, estate)); |
| } |
| |
| MemoryContextSwitchTo(oldcontext); |
| |
| return result; |
| } |
| |
| /* |
| * ExecCheck - evaluate a check constraint |
| * |
| * For check constraints, a null result is taken as TRUE, ie the constraint |
| * passes. |
| * |
| * The check constraint may have been prepared with ExecInitCheck |
| * (possibly via ExecPrepareCheck) if the caller had it in implicit-AND |
| * format, but a regular boolean expression prepared with ExecInitExpr or |
| * ExecPrepareExpr works too. |
| */ |
| bool |
| ExecCheck(ExprState *state, ExprContext *econtext) |
| { |
| Datum ret; |
| bool isnull; |
| |
| /* short-circuit (here and in ExecInitCheck) for empty restriction list */ |
| if (state == NULL) |
| return true; |
| |
| /* verify that expression was not compiled using ExecInitQual */ |
| Assert(!(state->flags & EEO_FLAG_IS_QUAL)); |
| |
| ret = ExecEvalExprSwitchContext(state, econtext, &isnull); |
| |
| if (isnull) |
| return true; |
| |
| return DatumGetBool(ret); |
| } |
| |
| /* |
| * Prepare a compiled expression for execution. This has to be called for |
| * every ExprState before it can be executed. |
| * |
| * NB: While this currently only calls ExecReadyInterpretedExpr(), |
| * this will likely get extended to further expression evaluation methods. |
| * Therefore this should be used instead of directly calling |
| * ExecReadyInterpretedExpr(). |
| */ |
| static void |
| ExecReadyExpr(ExprState *state) |
| { |
| if (jit_compile_expr(state)) |
| return; |
| |
| ExecReadyInterpretedExpr(state); |
| } |
| |
| /* |
| * Append the steps necessary for the evaluation of node to ExprState->steps, |
| * possibly recursing into sub-expressions of node. |
| * |
| * node - expression to evaluate |
| * state - ExprState to whose ->steps to append the necessary operations |
| * resv / resnull - where to store the result of the node into |
| */ |
| static void |
| ExecInitExprRec(Expr *node, ExprState *state, |
| Datum *resv, bool *resnull) |
| { |
| ExprEvalStep scratch = {0}; |
| |
| /* Guard against stack overflow due to overly complex expressions */ |
| check_stack_depth(); |
| |
| /* Step's output location is always what the caller gave us */ |
| Assert(resv != NULL && resnull != NULL); |
| scratch.resvalue = resv; |
| scratch.resnull = resnull; |
| |
| /* cases should be ordered as they are in enum NodeTag */ |
| switch (nodeTag(node)) |
| { |
| case T_Var: |
| { |
| Var *variable = (Var *) node; |
| |
| if (variable->varattno == InvalidAttrNumber) |
| { |
| /* whole-row Var */ |
| ExecInitWholeRowVar(&scratch, variable, state); |
| } |
| else if (variable->varattno <= 0) |
| { |
| /* system column */ |
| scratch.d.var.attnum = variable->varattno; |
| scratch.d.var.vartype = variable->vartype; |
| switch (variable->varno) |
| { |
| case INNER_VAR: |
| scratch.opcode = EEOP_INNER_SYSVAR; |
| break; |
| case OUTER_VAR: |
| scratch.opcode = EEOP_OUTER_SYSVAR; |
| break; |
| |
| /* INDEX_VAR is handled by default case */ |
| |
| default: |
| scratch.opcode = EEOP_SCAN_SYSVAR; |
| break; |
| } |
| } |
| else |
| { |
| /* regular user column */ |
| scratch.d.var.attnum = variable->varattno - 1; |
| scratch.d.var.vartype = variable->vartype; |
| switch (variable->varno) |
| { |
| case INNER_VAR: |
| scratch.opcode = EEOP_INNER_VAR; |
| break; |
| case OUTER_VAR: |
| scratch.opcode = EEOP_OUTER_VAR; |
| break; |
| |
| /* INDEX_VAR is handled by default case */ |
| |
| default: |
| scratch.opcode = EEOP_SCAN_VAR; |
| break; |
| } |
| } |
| |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| case T_Const: |
| { |
| Const *con = (Const *) node; |
| |
| scratch.opcode = EEOP_CONST; |
| scratch.d.constval.value = con->constvalue; |
| scratch.d.constval.isnull = con->constisnull; |
| |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| case T_Param: |
| { |
| Param *param = (Param *) node; |
| ParamListInfo params; |
| |
| switch (param->paramkind) |
| { |
| case PARAM_EXEC: |
| scratch.opcode = EEOP_PARAM_EXEC; |
| scratch.d.param.paramid = param->paramid; |
| scratch.d.param.paramtype = param->paramtype; |
| ExprEvalPushStep(state, &scratch); |
| break; |
| case PARAM_EXTERN: |
| |
| /* |
| * If we have a relevant ParamCompileHook, use it; |
| * otherwise compile a standard EEOP_PARAM_EXTERN |
| * step. ext_params, if supplied, takes precedence |
| * over info from the parent node's EState (if any). |
| */ |
| if (state->ext_params) |
| params = state->ext_params; |
| else if (state->parent && |
| state->parent->state) |
| params = state->parent->state->es_param_list_info; |
| else |
| params = NULL; |
| if (params && params->paramCompile) |
| { |
| params->paramCompile(params, param, state, |
| resv, resnull); |
| } |
| else |
| { |
| scratch.opcode = EEOP_PARAM_EXTERN; |
| scratch.d.param.paramid = param->paramid; |
| scratch.d.param.paramtype = param->paramtype; |
| ExprEvalPushStep(state, &scratch); |
| } |
| break; |
| default: |
| elog(ERROR, "unrecognized paramkind: %d", |
| (int) param->paramkind); |
| break; |
| } |
| break; |
| } |
| |
| case T_Aggref: |
| { |
| Aggref *aggref = (Aggref *) node; |
| |
| scratch.opcode = EEOP_AGGREF; |
| scratch.d.aggref.aggno = aggref->aggno; |
| |
| if (state->parent && IsA(state->parent, AggState)) |
| { |
| AggState *aggstate = (AggState *) state->parent; |
| |
| aggstate->aggs = lappend(aggstate->aggs, aggref); |
| } |
| else |
| { |
| /* planner messed up */ |
| elog(ERROR, "Aggref found in non-Agg plan node"); |
| } |
| |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| case T_GroupingFunc: |
| { |
| GroupingFunc *grp_node = (GroupingFunc *) node; |
| Agg *agg; |
| |
| if (!state->parent || !IsA(state->parent, AggState) || |
| !IsA(state->parent->plan, Agg)) |
| elog(ERROR, "GroupingFunc found in non-Agg plan node"); |
| |
| scratch.opcode = EEOP_GROUPING_FUNC; |
| |
| agg = (Agg *) (state->parent->plan); |
| |
| if (agg->groupingSets) |
| scratch.d.grouping_func.clauses = grp_node->cols; |
| else |
| scratch.d.grouping_func.clauses = NIL; |
| |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| case T_GroupId: |
| { |
| if (!state->parent || !IsA(state->parent, AggState) || !IsA(state->parent->plan, Agg)) |
| elog(ERROR, "parent of GROUP_ID is not Agg node"); |
| else |
| { |
| scratch.opcode = EEOP_GROUP_ID; |
| scratch.d.group_id.parent = (AggState *) state->parent; |
| |
| ExprEvalPushStep(state, &scratch); |
| } |
| } |
| break; |
| |
| case T_GroupingSetId: |
| { |
| if (!state->parent || !IsA(state->parent, AggState) || |
| !IsA(state->parent->plan, Agg)) |
| elog(ERROR, "GroupingSetId found in non-Agg plan node (parent %d, plan %d)", |
| state->parent ? state->parent->type : -1, |
| state->parent ? state->parent->plan->type : -1); |
| |
| scratch.opcode = EEOP_GROUPING_SET_ID; |
| scratch.d.grouping_set_id.parent = (AggState *) state->parent; |
| |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| case T_AggExprId: |
| { |
| if (!state->parent || !IsA(state->parent, TupleSplitState) || |
| !IsA(state->parent->plan, TupleSplit)) |
| elog(ERROR, "AggExprId found in non-TupleSplit plan node"); |
| |
| scratch.opcode = EEOP_AGGEXPR_ID; |
| scratch.d.agg_expr_id.parent = (TupleSplitState *) state->parent; |
| |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| case T_RowIdExpr: |
| { |
| /* |
| * RowIdExpr generates a number that's unique for this row, |
| * within this query execution. Different segments can |
| * generate rows in parallel, so we include the segment ID in |
| * the value so that two segments never generate the same |
| * value. |
| */ |
| scratch.opcode = EEOP_ROWIDEXPR; |
| |
| /* |
| * CBDB_PARALLEL |
| * Planner have ensured that there is enough space for num of segments and parallel workers. |
| * As we has not set ParallelWokerNumber yet now, use TotalParallelWorkerNumberOfSlice here |
| * and keep bits space for ParallelWokerNumber. |
| */ |
| if (TotalParallelWorkerNumberOfSlice > 0) |
| { |
| int parallel_bits = pg_leftmost_one_pos32(TotalParallelWorkerNumberOfSlice) + 1; |
| /* Planner has checked that there is enough room. */ |
| scratch.d.rowidexpr.rowcounter = ((int64) GpIdentity.dbid) << (48 + parallel_bits); |
| } |
| else |
| scratch.d.rowidexpr.rowcounter = ((int64) GpIdentity.dbid) << 48; |
| |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| case T_WindowFunc: |
| { |
| WindowFunc *wfunc = (WindowFunc *) node; |
| WindowFuncExprState *wfstate = makeNode(WindowFuncExprState); |
| |
| wfstate->wfunc = wfunc; |
| |
| if (state->parent && IsA(state->parent, WindowAggState)) |
| { |
| WindowAggState *winstate = (WindowAggState *) state->parent; |
| int nfuncs; |
| |
| winstate->funcs = lappend(winstate->funcs, wfstate); |
| nfuncs = ++winstate->numfuncs; |
| if (wfunc->winagg) |
| winstate->numaggs++; |
| |
| /* for now initialize agg using old style expressions */ |
| wfstate->args = ExecInitExprList(wfunc->args, |
| state->parent); |
| wfstate->aggfilter = ExecInitExpr(wfunc->aggfilter, |
| state->parent); |
| |
| /* |
| * Complain if the windowfunc's arguments contain any |
| * windowfuncs; nested window functions are semantically |
| * nonsensical. (This should have been caught earlier, |
| * but we defend against it here anyway.) |
| */ |
| if (nfuncs != winstate->numfuncs) |
| ereport(ERROR, |
| (errcode(ERRCODE_WINDOWING_ERROR), |
| errmsg("window function calls cannot be nested"))); |
| } |
| else |
| { |
| /* planner messed up */ |
| elog(ERROR, "WindowFunc found in non-WindowAgg plan node"); |
| } |
| |
| scratch.opcode = EEOP_WINDOW_FUNC; |
| scratch.d.window_func.wfstate = wfstate; |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| case T_SubscriptingRef: |
| { |
| SubscriptingRef *sbsref = (SubscriptingRef *) node; |
| |
| ExecInitSubscriptingRef(&scratch, sbsref, state, resv, resnull); |
| break; |
| } |
| |
| case T_FuncExpr: |
| { |
| FuncExpr *func = (FuncExpr *) node; |
| |
| ExecInitFunc(&scratch, node, |
| func->args, func->funcid, func->inputcollid, |
| state); |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| case T_OpExpr: |
| { |
| OpExpr *op = (OpExpr *) node; |
| |
| ExecInitFunc(&scratch, node, |
| op->args, op->opfuncid, op->inputcollid, |
| state); |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| case T_DistinctExpr: |
| { |
| DistinctExpr *op = (DistinctExpr *) node; |
| |
| ExecInitFunc(&scratch, node, |
| op->args, op->opfuncid, op->inputcollid, |
| state); |
| |
| /* |
| * Change opcode of call instruction to EEOP_DISTINCT. |
| * |
| * XXX: historically we've not called the function usage |
| * pgstat infrastructure - that seems inconsistent given that |
| * we do so for normal function *and* operator evaluation. If |
| * we decided to do that here, we'd probably want separate |
| * opcodes for FUSAGE or not. |
| */ |
| scratch.opcode = EEOP_DISTINCT; |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| case T_NullIfExpr: |
| { |
| NullIfExpr *op = (NullIfExpr *) node; |
| |
| ExecInitFunc(&scratch, node, |
| op->args, op->opfuncid, op->inputcollid, |
| state); |
| |
| /* |
| * If first argument is of varlena type, we'll need to ensure |
| * that the value passed to the comparison function is a |
| * read-only pointer. |
| */ |
| scratch.d.func.make_ro = |
| (get_typlen(exprType((Node *) linitial(op->args))) == -1); |
| |
| /* |
| * Change opcode of call instruction to EEOP_NULLIF. |
| * |
| * XXX: historically we've not called the function usage |
| * pgstat infrastructure - that seems inconsistent given that |
| * we do so for normal function *and* operator evaluation. If |
| * we decided to do that here, we'd probably want separate |
| * opcodes for FUSAGE or not. |
| */ |
| scratch.opcode = EEOP_NULLIF; |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| case T_ScalarArrayOpExpr: |
| { |
| ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node; |
| Expr *scalararg; |
| Expr *arrayarg; |
| FmgrInfo *finfo; |
| FunctionCallInfo fcinfo; |
| AclResult aclresult; |
| Oid cmpfuncid; |
| |
| /* |
| * Select the correct comparison function. When we do hashed |
| * NOT IN clauses, the opfuncid will be the inequality |
| * comparison function and negfuncid will be set to equality. |
| * We need to use the equality function for hash probes. |
| */ |
| if (OidIsValid(opexpr->negfuncid)) |
| { |
| Assert(OidIsValid(opexpr->hashfuncid)); |
| cmpfuncid = opexpr->negfuncid; |
| } |
| else |
| cmpfuncid = opexpr->opfuncid; |
| |
| Assert(list_length(opexpr->args) == 2); |
| scalararg = (Expr *) linitial(opexpr->args); |
| arrayarg = (Expr *) lsecond(opexpr->args); |
| |
| /* Check permission to call function */ |
| aclresult = object_aclcheck(ProcedureRelationId, cmpfuncid, |
| GetUserId(), |
| ACL_EXECUTE); |
| if (aclresult != ACLCHECK_OK) |
| aclcheck_error(aclresult, OBJECT_FUNCTION, |
| get_func_name(cmpfuncid)); |
| InvokeFunctionExecuteHook(cmpfuncid); |
| |
| /* GPDB: Try the hard-coded fast-path versions of these */ |
| if (ExecInitScalarArrayOpFastPath(&scratch, opexpr, |
| state, resv, resnull)) |
| break; |
| |
| if (OidIsValid(opexpr->hashfuncid)) |
| { |
| aclresult = object_aclcheck(ProcedureRelationId, opexpr->hashfuncid, |
| GetUserId(), |
| ACL_EXECUTE); |
| if (aclresult != ACLCHECK_OK) |
| aclcheck_error(aclresult, OBJECT_FUNCTION, |
| get_func_name(opexpr->hashfuncid)); |
| InvokeFunctionExecuteHook(opexpr->hashfuncid); |
| } |
| |
| /* Set up the primary fmgr lookup information */ |
| finfo = palloc0(sizeof(FmgrInfo)); |
| fcinfo = palloc0(SizeForFunctionCallInfo(2)); |
| fmgr_info(cmpfuncid, finfo); |
| fmgr_info_set_expr((Node *) node, finfo); |
| InitFunctionCallInfoData(*fcinfo, finfo, 2, |
| opexpr->inputcollid, NULL, NULL); |
| |
| /* |
| * If hashfuncid is set, we create a EEOP_HASHED_SCALARARRAYOP |
| * step instead of a EEOP_SCALARARRAYOP. This provides much |
| * faster lookup performance than the normal linear search |
| * when the number of items in the array is anything but very |
| * small. |
| */ |
| if (OidIsValid(opexpr->hashfuncid)) |
| { |
| /* Evaluate scalar directly into left function argument */ |
| ExecInitExprRec(scalararg, state, |
| &fcinfo->args[0].value, &fcinfo->args[0].isnull); |
| |
| /* |
| * Evaluate array argument into our return value. There's |
| * no danger in that, because the return value is |
| * guaranteed to be overwritten by |
| * EEOP_HASHED_SCALARARRAYOP, and will not be passed to |
| * any other expression. |
| */ |
| ExecInitExprRec(arrayarg, state, resv, resnull); |
| |
| /* And perform the operation */ |
| scratch.opcode = EEOP_HASHED_SCALARARRAYOP; |
| scratch.d.hashedscalararrayop.inclause = opexpr->useOr; |
| scratch.d.hashedscalararrayop.finfo = finfo; |
| scratch.d.hashedscalararrayop.fcinfo_data = fcinfo; |
| scratch.d.hashedscalararrayop.saop = opexpr; |
| |
| |
| ExprEvalPushStep(state, &scratch); |
| } |
| else |
| { |
| /* Evaluate scalar directly into left function argument */ |
| ExecInitExprRec(scalararg, state, |
| &fcinfo->args[0].value, |
| &fcinfo->args[0].isnull); |
| |
| /* |
| * Evaluate array argument into our return value. There's |
| * no danger in that, because the return value is |
| * guaranteed to be overwritten by EEOP_SCALARARRAYOP, and |
| * will not be passed to any other expression. |
| */ |
| ExecInitExprRec(arrayarg, state, resv, resnull); |
| |
| /* And perform the operation */ |
| scratch.opcode = EEOP_SCALARARRAYOP; |
| scratch.d.scalararrayop.element_type = InvalidOid; |
| scratch.d.scalararrayop.useOr = opexpr->useOr; |
| scratch.d.scalararrayop.finfo = finfo; |
| scratch.d.scalararrayop.fcinfo_data = fcinfo; |
| scratch.d.scalararrayop.fn_addr = finfo->fn_addr; |
| ExprEvalPushStep(state, &scratch); |
| } |
| break; |
| } |
| |
| case T_BoolExpr: |
| { |
| BoolExpr *boolexpr = (BoolExpr *) node; |
| int nargs = list_length(boolexpr->args); |
| List *adjust_jumps = NIL; |
| int off; |
| ListCell *lc; |
| |
| /* allocate scratch memory used by all steps of AND/OR */ |
| if (boolexpr->boolop != NOT_EXPR) |
| scratch.d.boolexpr.anynull = (bool *) palloc(sizeof(bool)); |
| |
| /* |
| * For each argument evaluate the argument itself, then |
| * perform the bool operation's appropriate handling. |
| * |
| * We can evaluate each argument into our result area, since |
| * the short-circuiting logic means we only need to remember |
| * previous NULL values. |
| * |
| * AND/OR is split into separate STEP_FIRST (one) / STEP (zero |
| * or more) / STEP_LAST (one) steps, as each of those has to |
| * perform different work. The FIRST/LAST split is valid |
| * because AND/OR have at least two arguments. |
| */ |
| off = 0; |
| foreach(lc, boolexpr->args) |
| { |
| Expr *arg = (Expr *) lfirst(lc); |
| |
| /* Evaluate argument into our output variable */ |
| ExecInitExprRec(arg, state, resv, resnull); |
| |
| /* Perform the appropriate step type */ |
| switch (boolexpr->boolop) |
| { |
| case AND_EXPR: |
| Assert(nargs >= 2); |
| |
| if (off == 0) |
| scratch.opcode = EEOP_BOOL_AND_STEP_FIRST; |
| else if (off + 1 == nargs) |
| scratch.opcode = EEOP_BOOL_AND_STEP_LAST; |
| else |
| scratch.opcode = EEOP_BOOL_AND_STEP; |
| break; |
| case OR_EXPR: |
| Assert(nargs >= 2); |
| |
| if (off == 0) |
| scratch.opcode = EEOP_BOOL_OR_STEP_FIRST; |
| else if (off + 1 == nargs) |
| scratch.opcode = EEOP_BOOL_OR_STEP_LAST; |
| else |
| scratch.opcode = EEOP_BOOL_OR_STEP; |
| break; |
| case NOT_EXPR: |
| Assert(nargs == 1); |
| |
| scratch.opcode = EEOP_BOOL_NOT_STEP; |
| break; |
| default: |
| elog(ERROR, "unrecognized boolop: %d", |
| (int) boolexpr->boolop); |
| break; |
| } |
| |
| scratch.d.boolexpr.jumpdone = -1; |
| ExprEvalPushStep(state, &scratch); |
| adjust_jumps = lappend_int(adjust_jumps, |
| state->steps_len - 1); |
| off++; |
| } |
| |
| /* adjust jump targets */ |
| foreach(lc, adjust_jumps) |
| { |
| ExprEvalStep *as = &state->steps[lfirst_int(lc)]; |
| |
| Assert(as->d.boolexpr.jumpdone == -1); |
| as->d.boolexpr.jumpdone = state->steps_len; |
| } |
| |
| break; |
| } |
| |
| case T_SubPlan: |
| { |
| SubPlan *subplan = (SubPlan *) node; |
| SubPlanState *sstate; |
| |
| /* |
| * Real execution of a MULTIEXPR SubPlan has already been |
| * done. What we have to do here is return a dummy NULL record |
| * value in case this targetlist element is assigned |
| * someplace. |
| */ |
| if (subplan->subLinkType == MULTIEXPR_SUBLINK) |
| { |
| scratch.opcode = EEOP_CONST; |
| scratch.d.constval.value = (Datum) 0; |
| scratch.d.constval.isnull = true; |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| if (!state->parent) |
| elog(ERROR, "SubPlan found with no parent plan"); |
| |
| sstate = ExecInitSubPlan(subplan, state->parent); |
| |
| /* add SubPlanState nodes to state->parent->subPlan */ |
| state->parent->subPlan = lappend(state->parent->subPlan, |
| sstate); |
| |
| scratch.opcode = EEOP_SUBPLAN; |
| scratch.d.subplan.sstate = sstate; |
| |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| case T_FieldSelect: |
| { |
| FieldSelect *fselect = (FieldSelect *) node; |
| |
| /* evaluate row/record argument into result area */ |
| ExecInitExprRec(fselect->arg, state, resv, resnull); |
| |
| /* and extract field */ |
| scratch.opcode = EEOP_FIELDSELECT; |
| scratch.d.fieldselect.fieldnum = fselect->fieldnum; |
| scratch.d.fieldselect.resulttype = fselect->resulttype; |
| scratch.d.fieldselect.rowcache.cacheptr = NULL; |
| |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| case T_FieldStore: |
| { |
| FieldStore *fstore = (FieldStore *) node; |
| TupleDesc tupDesc; |
| ExprEvalRowtypeCache *rowcachep; |
| Datum *values; |
| bool *nulls; |
| int ncolumns; |
| ListCell *l1, |
| *l2; |
| |
| /* find out the number of columns in the composite type */ |
| tupDesc = lookup_rowtype_tupdesc(fstore->resulttype, -1); |
| ncolumns = tupDesc->natts; |
| ReleaseTupleDesc(tupDesc); |
| |
| /* create workspace for column values */ |
| values = (Datum *) palloc(sizeof(Datum) * ncolumns); |
| nulls = (bool *) palloc(sizeof(bool) * ncolumns); |
| |
| /* create shared composite-type-lookup cache struct */ |
| rowcachep = palloc(sizeof(ExprEvalRowtypeCache)); |
| rowcachep->cacheptr = NULL; |
| |
| /* emit code to evaluate the composite input value */ |
| ExecInitExprRec(fstore->arg, state, resv, resnull); |
| |
| /* next, deform the input tuple into our workspace */ |
| scratch.opcode = EEOP_FIELDSTORE_DEFORM; |
| scratch.d.fieldstore.fstore = fstore; |
| scratch.d.fieldstore.rowcache = rowcachep; |
| scratch.d.fieldstore.values = values; |
| scratch.d.fieldstore.nulls = nulls; |
| scratch.d.fieldstore.ncolumns = ncolumns; |
| ExprEvalPushStep(state, &scratch); |
| |
| /* evaluate new field values, store in workspace columns */ |
| forboth(l1, fstore->newvals, l2, fstore->fieldnums) |
| { |
| Expr *e = (Expr *) lfirst(l1); |
| AttrNumber fieldnum = lfirst_int(l2); |
| Datum *save_innermost_caseval; |
| bool *save_innermost_casenull; |
| |
| if (fieldnum <= 0 || fieldnum > ncolumns) |
| elog(ERROR, "field number %d is out of range in FieldStore", |
| fieldnum); |
| |
| /* |
| * Use the CaseTestExpr mechanism to pass down the old |
| * value of the field being replaced; this is needed in |
| * case the newval is itself a FieldStore or |
| * SubscriptingRef that has to obtain and modify the old |
| * value. It's safe to reuse the CASE mechanism because |
| * there cannot be a CASE between here and where the value |
| * would be needed, and a field assignment can't be within |
| * a CASE either. (So saving and restoring |
| * innermost_caseval is just paranoia, but let's do it |
| * anyway.) |
| * |
| * Another non-obvious point is that it's safe to use the |
| * field's values[]/nulls[] entries as both the caseval |
| * source and the result address for this subexpression. |
| * That's okay only because (1) both FieldStore and |
| * SubscriptingRef evaluate their arg or refexpr inputs |
| * first, and (2) any such CaseTestExpr is directly the |
| * arg or refexpr input. So any read of the caseval will |
| * occur before there's a chance to overwrite it. Also, |
| * if multiple entries in the newvals/fieldnums lists |
| * target the same field, they'll effectively be applied |
| * left-to-right which is what we want. |
| */ |
| save_innermost_caseval = state->innermost_caseval; |
| save_innermost_casenull = state->innermost_casenull; |
| state->innermost_caseval = &values[fieldnum - 1]; |
| state->innermost_casenull = &nulls[fieldnum - 1]; |
| |
| ExecInitExprRec(e, state, |
| &values[fieldnum - 1], |
| &nulls[fieldnum - 1]); |
| |
| state->innermost_caseval = save_innermost_caseval; |
| state->innermost_casenull = save_innermost_casenull; |
| } |
| |
| /* finally, form result tuple */ |
| scratch.opcode = EEOP_FIELDSTORE_FORM; |
| scratch.d.fieldstore.fstore = fstore; |
| scratch.d.fieldstore.rowcache = rowcachep; |
| scratch.d.fieldstore.values = values; |
| scratch.d.fieldstore.nulls = nulls; |
| scratch.d.fieldstore.ncolumns = ncolumns; |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| case T_RelabelType: |
| { |
| /* relabel doesn't need to do anything at runtime */ |
| RelabelType *relabel = (RelabelType *) node; |
| |
| ExecInitExprRec(relabel->arg, state, resv, resnull); |
| break; |
| } |
| |
| case T_CoerceViaIO: |
| { |
| CoerceViaIO *iocoerce = (CoerceViaIO *) node; |
| Oid iofunc; |
| bool typisvarlena; |
| Oid typioparam; |
| FunctionCallInfo fcinfo_in; |
| |
| /* evaluate argument into step's result area */ |
| ExecInitExprRec(iocoerce->arg, state, resv, resnull); |
| |
| /* |
| * Prepare both output and input function calls, to be |
| * evaluated inside a single evaluation step for speed - this |
| * can be a very common operation. |
| * |
| * We don't check permissions here as a type's input/output |
| * function are assumed to be executable by everyone. |
| */ |
| scratch.opcode = EEOP_IOCOERCE; |
| |
| /* lookup the source type's output function */ |
| scratch.d.iocoerce.finfo_out = palloc0(sizeof(FmgrInfo)); |
| scratch.d.iocoerce.fcinfo_data_out = palloc0(SizeForFunctionCallInfo(1)); |
| |
| getTypeOutputInfo(exprType((Node *) iocoerce->arg), |
| &iofunc, &typisvarlena); |
| fmgr_info(iofunc, scratch.d.iocoerce.finfo_out); |
| fmgr_info_set_expr((Node *) node, scratch.d.iocoerce.finfo_out); |
| InitFunctionCallInfoData(*scratch.d.iocoerce.fcinfo_data_out, |
| scratch.d.iocoerce.finfo_out, |
| 1, InvalidOid, NULL, NULL); |
| |
| /* lookup the result type's input function */ |
| scratch.d.iocoerce.finfo_in = palloc0(sizeof(FmgrInfo)); |
| scratch.d.iocoerce.fcinfo_data_in = palloc0(SizeForFunctionCallInfo(3)); |
| |
| getTypeInputInfo(iocoerce->resulttype, |
| &iofunc, &typioparam); |
| fmgr_info(iofunc, scratch.d.iocoerce.finfo_in); |
| fmgr_info_set_expr((Node *) node, scratch.d.iocoerce.finfo_in); |
| InitFunctionCallInfoData(*scratch.d.iocoerce.fcinfo_data_in, |
| scratch.d.iocoerce.finfo_in, |
| 3, InvalidOid, NULL, NULL); |
| |
| /* |
| * We can preload the second and third arguments for the input |
| * function, since they're constants. |
| */ |
| fcinfo_in = scratch.d.iocoerce.fcinfo_data_in; |
| fcinfo_in->args[1].value = ObjectIdGetDatum(typioparam); |
| fcinfo_in->args[1].isnull = false; |
| fcinfo_in->args[2].value = Int32GetDatum(-1); |
| fcinfo_in->args[2].isnull = false; |
| |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| case T_ArrayCoerceExpr: |
| { |
| ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node; |
| Oid resultelemtype; |
| ExprState *elemstate; |
| |
| /* evaluate argument into step's result area */ |
| ExecInitExprRec(acoerce->arg, state, resv, resnull); |
| |
| resultelemtype = get_element_type(acoerce->resulttype); |
| if (!OidIsValid(resultelemtype)) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| errmsg("target type is not an array"))); |
| |
| /* |
| * Construct a sub-expression for the per-element expression; |
| * but don't ready it until after we check it for triviality. |
| * We assume it hasn't any Var references, but does have a |
| * CaseTestExpr representing the source array element values. |
| */ |
| elemstate = makeNode(ExprState); |
| elemstate->expr = acoerce->elemexpr; |
| elemstate->parent = state->parent; |
| elemstate->ext_params = state->ext_params; |
| |
| elemstate->innermost_caseval = (Datum *) palloc(sizeof(Datum)); |
| elemstate->innermost_casenull = (bool *) palloc(sizeof(bool)); |
| |
| ExecInitExprRec(acoerce->elemexpr, elemstate, |
| &elemstate->resvalue, &elemstate->resnull); |
| |
| if (elemstate->steps_len == 1 && |
| elemstate->steps[0].opcode == EEOP_CASE_TESTVAL) |
| { |
| /* Trivial, so we need no per-element work at runtime */ |
| elemstate = NULL; |
| } |
| else |
| { |
| /* Not trivial, so append a DONE step */ |
| scratch.opcode = EEOP_DONE; |
| ExprEvalPushStep(elemstate, &scratch); |
| /* and ready the subexpression */ |
| ExecReadyExpr(elemstate); |
| } |
| |
| scratch.opcode = EEOP_ARRAYCOERCE; |
| scratch.d.arraycoerce.elemexprstate = elemstate; |
| scratch.d.arraycoerce.resultelemtype = resultelemtype; |
| |
| if (elemstate) |
| { |
| /* Set up workspace for array_map */ |
| scratch.d.arraycoerce.amstate = |
| (ArrayMapState *) palloc0(sizeof(ArrayMapState)); |
| } |
| else |
| { |
| /* Don't need workspace if there's no subexpression */ |
| scratch.d.arraycoerce.amstate = NULL; |
| } |
| |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| case T_ConvertRowtypeExpr: |
| { |
| ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) node; |
| ExprEvalRowtypeCache *rowcachep; |
| |
| /* cache structs must be out-of-line for space reasons */ |
| rowcachep = palloc(2 * sizeof(ExprEvalRowtypeCache)); |
| rowcachep[0].cacheptr = NULL; |
| rowcachep[1].cacheptr = NULL; |
| |
| /* evaluate argument into step's result area */ |
| ExecInitExprRec(convert->arg, state, resv, resnull); |
| |
| /* and push conversion step */ |
| scratch.opcode = EEOP_CONVERT_ROWTYPE; |
| scratch.d.convert_rowtype.inputtype = |
| exprType((Node *) convert->arg); |
| scratch.d.convert_rowtype.outputtype = convert->resulttype; |
| scratch.d.convert_rowtype.incache = &rowcachep[0]; |
| scratch.d.convert_rowtype.outcache = &rowcachep[1]; |
| scratch.d.convert_rowtype.map = NULL; |
| |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| /* note that CaseWhen expressions are handled within this block */ |
| case T_CaseExpr: |
| { |
| CaseExpr *caseExpr = (CaseExpr *) node; |
| List *adjust_jumps = NIL; |
| Datum *caseval = NULL; |
| bool *casenull = NULL; |
| ListCell *lc; |
| |
| /* |
| * If there's a test expression, we have to evaluate it and |
| * save the value where the CaseTestExpr placeholders can find |
| * it. |
| */ |
| if (caseExpr->arg != NULL) |
| { |
| /* Evaluate testexpr into caseval/casenull workspace */ |
| caseval = palloc(sizeof(Datum)); |
| casenull = palloc(sizeof(bool)); |
| |
| ExecInitExprRec(caseExpr->arg, state, |
| caseval, casenull); |
| |
| /* |
| * Since value might be read multiple times, force to R/O |
| * - but only if it could be an expanded datum. |
| */ |
| if (get_typlen(exprType((Node *) caseExpr->arg)) == -1) |
| { |
| /* change caseval in-place */ |
| scratch.opcode = EEOP_MAKE_READONLY; |
| scratch.resvalue = caseval; |
| scratch.resnull = casenull; |
| scratch.d.make_readonly.value = caseval; |
| scratch.d.make_readonly.isnull = casenull; |
| ExprEvalPushStep(state, &scratch); |
| /* restore normal settings of scratch fields */ |
| scratch.resvalue = resv; |
| scratch.resnull = resnull; |
| } |
| } |
| |
| /* |
| * Prepare to evaluate each of the WHEN clauses in turn; as |
| * soon as one is true we return the value of the |
| * corresponding THEN clause. If none are true then we return |
| * the value of the ELSE clause, or NULL if there is none. |
| */ |
| foreach(lc, caseExpr->args) |
| { |
| CaseWhen *when = (CaseWhen *) lfirst(lc); |
| Datum *save_innermost_caseval; |
| bool *save_innermost_casenull; |
| int whenstep; |
| |
| /* |
| * Make testexpr result available to CaseTestExpr nodes |
| * within the condition. We must save and restore prior |
| * setting of innermost_caseval fields, in case this node |
| * is itself within a larger CASE. |
| * |
| * If there's no test expression, we don't actually need |
| * to save and restore these fields; but it's less code to |
| * just do so unconditionally. |
| */ |
| save_innermost_caseval = state->innermost_caseval; |
| save_innermost_casenull = state->innermost_casenull; |
| state->innermost_caseval = caseval; |
| state->innermost_casenull = casenull; |
| |
| /* evaluate condition into CASE's result variables */ |
| ExecInitExprRec(when->expr, state, resv, resnull); |
| |
| state->innermost_caseval = save_innermost_caseval; |
| state->innermost_casenull = save_innermost_casenull; |
| |
| /* If WHEN result isn't true, jump to next CASE arm */ |
| scratch.opcode = EEOP_JUMP_IF_NOT_TRUE; |
| scratch.d.jump.jumpdone = -1; /* computed later */ |
| ExprEvalPushStep(state, &scratch); |
| whenstep = state->steps_len - 1; |
| |
| /* |
| * If WHEN result is true, evaluate THEN result, storing |
| * it into the CASE's result variables. |
| */ |
| ExecInitExprRec(when->result, state, resv, resnull); |
| |
| /* Emit JUMP step to jump to end of CASE's code */ |
| scratch.opcode = EEOP_JUMP; |
| scratch.d.jump.jumpdone = -1; /* computed later */ |
| ExprEvalPushStep(state, &scratch); |
| |
| /* |
| * Don't know address for that jump yet, compute once the |
| * whole CASE expression is built. |
| */ |
| adjust_jumps = lappend_int(adjust_jumps, |
| state->steps_len - 1); |
| |
| /* |
| * But we can set WHEN test's jump target now, to make it |
| * jump to the next WHEN subexpression or the ELSE. |
| */ |
| state->steps[whenstep].d.jump.jumpdone = state->steps_len; |
| } |
| |
| /* transformCaseExpr always adds a default */ |
| Assert(caseExpr->defresult); |
| |
| /* evaluate ELSE expr into CASE's result variables */ |
| ExecInitExprRec(caseExpr->defresult, state, |
| resv, resnull); |
| |
| /* adjust jump targets */ |
| foreach(lc, adjust_jumps) |
| { |
| ExprEvalStep *as = &state->steps[lfirst_int(lc)]; |
| |
| Assert(as->opcode == EEOP_JUMP); |
| Assert(as->d.jump.jumpdone == -1); |
| as->d.jump.jumpdone = state->steps_len; |
| } |
| |
| break; |
| } |
| |
| case T_CaseTestExpr: |
| { |
| /* |
| * Read from location identified by innermost_caseval. Note |
| * that innermost_caseval could be NULL, if this node isn't |
| * actually within a CaseExpr, ArrayCoerceExpr, etc structure. |
| * That can happen because some parts of the system abuse |
| * CaseTestExpr to cause a read of a value externally supplied |
| * in econtext->caseValue_datum. We'll take care of that |
| * scenario at runtime. |
| */ |
| scratch.opcode = EEOP_CASE_TESTVAL; |
| scratch.d.casetest.value = state->innermost_caseval; |
| scratch.d.casetest.isnull = state->innermost_casenull; |
| |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| case T_ArrayExpr: |
| { |
| ArrayExpr *arrayexpr = (ArrayExpr *) node; |
| int nelems = list_length(arrayexpr->elements); |
| ListCell *lc; |
| int elemoff; |
| |
| /* |
| * Evaluate by computing each element, and then forming the |
| * array. Elements are computed into scratch arrays |
| * associated with the ARRAYEXPR step. |
| */ |
| scratch.opcode = EEOP_ARRAYEXPR; |
| scratch.d.arrayexpr.elemvalues = |
| (Datum *) palloc(sizeof(Datum) * nelems); |
| scratch.d.arrayexpr.elemnulls = |
| (bool *) palloc(sizeof(bool) * nelems); |
| scratch.d.arrayexpr.nelems = nelems; |
| |
| /* fill remaining fields of step */ |
| scratch.d.arrayexpr.multidims = arrayexpr->multidims; |
| scratch.d.arrayexpr.elemtype = arrayexpr->element_typeid; |
| |
| /* do one-time catalog lookup for type info */ |
| get_typlenbyvalalign(arrayexpr->element_typeid, |
| &scratch.d.arrayexpr.elemlength, |
| &scratch.d.arrayexpr.elembyval, |
| &scratch.d.arrayexpr.elemalign); |
| |
| /* prepare to evaluate all arguments */ |
| elemoff = 0; |
| foreach(lc, arrayexpr->elements) |
| { |
| Expr *e = (Expr *) lfirst(lc); |
| |
| ExecInitExprRec(e, state, |
| &scratch.d.arrayexpr.elemvalues[elemoff], |
| &scratch.d.arrayexpr.elemnulls[elemoff]); |
| elemoff++; |
| } |
| |
| /* and then collect all into an array */ |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| case T_RowExpr: |
| { |
| RowExpr *rowexpr = (RowExpr *) node; |
| int nelems = list_length(rowexpr->args); |
| TupleDesc tupdesc; |
| int i; |
| ListCell *l; |
| |
| /* Build tupdesc to describe result tuples */ |
| if (rowexpr->row_typeid == RECORDOID) |
| { |
| /* generic record, use types of given expressions */ |
| tupdesc = ExecTypeFromExprList(rowexpr->args); |
| /* ... but adopt RowExpr's column aliases */ |
| ExecTypeSetColNames(tupdesc, rowexpr->colnames); |
| /* Bless the tupdesc so it can be looked up later */ |
| BlessTupleDesc(tupdesc); |
| } |
| else |
| { |
| TupleDesc tmp; |
| |
| /* it's been cast to a named type, use that */ |
| tmp = lookup_rowtype_tupdesc_domain(rowexpr->row_typeid, -1, true); |
| tupdesc = CreateTupleDescCopyConstr(tmp); |
| ReleaseTupleDesc(tmp); |
| } |
| |
| /* |
| * In the named-type case, the tupdesc could have more columns |
| * than are in the args list, since the type might have had |
| * columns added since the ROW() was parsed. We want those |
| * extra columns to go to nulls, so we make sure that the |
| * workspace arrays are large enough and then initialize any |
| * extra columns to read as NULLs. |
| */ |
| Assert(nelems <= tupdesc->natts); |
| nelems = Max(nelems, tupdesc->natts); |
| |
| /* |
| * Evaluate by first building datums for each field, and then |
| * a final step forming the composite datum. |
| */ |
| scratch.opcode = EEOP_ROW; |
| scratch.d.row.tupdesc = tupdesc; |
| |
| /* space for the individual field datums */ |
| scratch.d.row.elemvalues = |
| (Datum *) palloc(sizeof(Datum) * nelems); |
| scratch.d.row.elemnulls = |
| (bool *) palloc(sizeof(bool) * nelems); |
| /* as explained above, make sure any extra columns are null */ |
| memset(scratch.d.row.elemnulls, true, sizeof(bool) * nelems); |
| |
| /* Set up evaluation, skipping any deleted columns */ |
| i = 0; |
| foreach(l, rowexpr->args) |
| { |
| Form_pg_attribute att = TupleDescAttr(tupdesc, i); |
| Expr *e = (Expr *) lfirst(l); |
| |
| if (!att->attisdropped) |
| { |
| /* |
| * Guard against ALTER COLUMN TYPE on rowtype since |
| * the RowExpr was created. XXX should we check |
| * typmod too? Not sure we can be sure it'll be the |
| * same. |
| */ |
| if (exprType((Node *) e) != att->atttypid) |
| ereport(ERROR, |
| (errcode(ERRCODE_DATATYPE_MISMATCH), |
| errmsg("ROW() column has type %s instead of type %s", |
| format_type_be(exprType((Node *) e)), |
| format_type_be(att->atttypid)))); |
| } |
| else |
| { |
| /* |
| * Ignore original expression and insert a NULL. We |
| * don't really care what type of NULL it is, so |
| * always make an int4 NULL. |
| */ |
| e = (Expr *) makeNullConst(INT4OID, -1, InvalidOid); |
| } |
| |
| /* Evaluate column expr into appropriate workspace slot */ |
| ExecInitExprRec(e, state, |
| &scratch.d.row.elemvalues[i], |
| &scratch.d.row.elemnulls[i]); |
| i++; |
| } |
| |
| /* And finally build the row value */ |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| case T_RowCompareExpr: |
| { |
| RowCompareExpr *rcexpr = (RowCompareExpr *) node; |
| int nopers = list_length(rcexpr->opnos); |
| List *adjust_jumps = NIL; |
| ListCell *l_left_expr, |
| *l_right_expr, |
| *l_opno, |
| *l_opfamily, |
| *l_inputcollid; |
| ListCell *lc; |
| |
| /* |
| * Iterate over each field, prepare comparisons. To handle |
| * NULL results, prepare jumps to after the expression. If a |
| * comparison yields a != 0 result, jump to the final step. |
| */ |
| Assert(list_length(rcexpr->largs) == nopers); |
| Assert(list_length(rcexpr->rargs) == nopers); |
| Assert(list_length(rcexpr->opfamilies) == nopers); |
| Assert(list_length(rcexpr->inputcollids) == nopers); |
| |
| forfive(l_left_expr, rcexpr->largs, |
| l_right_expr, rcexpr->rargs, |
| l_opno, rcexpr->opnos, |
| l_opfamily, rcexpr->opfamilies, |
| l_inputcollid, rcexpr->inputcollids) |
| { |
| Expr *left_expr = (Expr *) lfirst(l_left_expr); |
| Expr *right_expr = (Expr *) lfirst(l_right_expr); |
| Oid opno = lfirst_oid(l_opno); |
| Oid opfamily = lfirst_oid(l_opfamily); |
| Oid inputcollid = lfirst_oid(l_inputcollid); |
| int strategy; |
| Oid lefttype; |
| Oid righttype; |
| Oid proc; |
| FmgrInfo *finfo; |
| FunctionCallInfo fcinfo; |
| |
| get_op_opfamily_properties(opno, opfamily, false, |
| &strategy, |
| &lefttype, |
| &righttype); |
| proc = get_opfamily_proc(opfamily, |
| lefttype, |
| righttype, |
| BTORDER_PROC); |
| if (!OidIsValid(proc)) |
| elog(ERROR, "missing support function %d(%u,%u) in opfamily %u", |
| BTORDER_PROC, lefttype, righttype, opfamily); |
| |
| /* Set up the primary fmgr lookup information */ |
| finfo = palloc0(sizeof(FmgrInfo)); |
| fcinfo = palloc0(SizeForFunctionCallInfo(2)); |
| fmgr_info(proc, finfo); |
| fmgr_info_set_expr((Node *) node, finfo); |
| InitFunctionCallInfoData(*fcinfo, finfo, 2, |
| inputcollid, NULL, NULL); |
| |
| /* |
| * If we enforced permissions checks on index support |
| * functions, we'd need to make a check here. But the |
| * index support machinery doesn't do that, and thus |
| * neither does this code. |
| */ |
| |
| /* evaluate left and right args directly into fcinfo */ |
| ExecInitExprRec(left_expr, state, |
| &fcinfo->args[0].value, &fcinfo->args[0].isnull); |
| ExecInitExprRec(right_expr, state, |
| &fcinfo->args[1].value, &fcinfo->args[1].isnull); |
| |
| scratch.opcode = EEOP_ROWCOMPARE_STEP; |
| scratch.d.rowcompare_step.finfo = finfo; |
| scratch.d.rowcompare_step.fcinfo_data = fcinfo; |
| scratch.d.rowcompare_step.fn_addr = finfo->fn_addr; |
| /* jump targets filled below */ |
| scratch.d.rowcompare_step.jumpnull = -1; |
| scratch.d.rowcompare_step.jumpdone = -1; |
| |
| ExprEvalPushStep(state, &scratch); |
| adjust_jumps = lappend_int(adjust_jumps, |
| state->steps_len - 1); |
| } |
| |
| /* |
| * We could have a zero-column rowtype, in which case the rows |
| * necessarily compare equal. |
| */ |
| if (nopers == 0) |
| { |
| scratch.opcode = EEOP_CONST; |
| scratch.d.constval.value = Int32GetDatum(0); |
| scratch.d.constval.isnull = false; |
| ExprEvalPushStep(state, &scratch); |
| } |
| |
| /* Finally, examine the last comparison result */ |
| scratch.opcode = EEOP_ROWCOMPARE_FINAL; |
| scratch.d.rowcompare_final.rctype = rcexpr->rctype; |
| ExprEvalPushStep(state, &scratch); |
| |
| /* adjust jump targets */ |
| foreach(lc, adjust_jumps) |
| { |
| ExprEvalStep *as = &state->steps[lfirst_int(lc)]; |
| |
| Assert(as->opcode == EEOP_ROWCOMPARE_STEP); |
| Assert(as->d.rowcompare_step.jumpdone == -1); |
| Assert(as->d.rowcompare_step.jumpnull == -1); |
| |
| /* jump to comparison evaluation */ |
| as->d.rowcompare_step.jumpdone = state->steps_len - 1; |
| /* jump to the following expression */ |
| as->d.rowcompare_step.jumpnull = state->steps_len; |
| } |
| |
| break; |
| } |
| |
| case T_CoalesceExpr: |
| { |
| CoalesceExpr *coalesce = (CoalesceExpr *) node; |
| List *adjust_jumps = NIL; |
| ListCell *lc; |
| |
| /* We assume there's at least one arg */ |
| Assert(coalesce->args != NIL); |
| |
| /* |
| * Prepare evaluation of all coalesced arguments, after each |
| * one push a step that short-circuits if not null. |
| */ |
| foreach(lc, coalesce->args) |
| { |
| Expr *e = (Expr *) lfirst(lc); |
| |
| /* evaluate argument, directly into result datum */ |
| ExecInitExprRec(e, state, resv, resnull); |
| |
| /* if it's not null, skip to end of COALESCE expr */ |
| scratch.opcode = EEOP_JUMP_IF_NOT_NULL; |
| scratch.d.jump.jumpdone = -1; /* adjust later */ |
| ExprEvalPushStep(state, &scratch); |
| |
| adjust_jumps = lappend_int(adjust_jumps, |
| state->steps_len - 1); |
| } |
| |
| /* |
| * No need to add a constant NULL return - we only can get to |
| * the end of the expression if a NULL already is being |
| * returned. |
| */ |
| |
| /* adjust jump targets */ |
| foreach(lc, adjust_jumps) |
| { |
| ExprEvalStep *as = &state->steps[lfirst_int(lc)]; |
| |
| Assert(as->opcode == EEOP_JUMP_IF_NOT_NULL); |
| Assert(as->d.jump.jumpdone == -1); |
| as->d.jump.jumpdone = state->steps_len; |
| } |
| |
| break; |
| } |
| |
| case T_MinMaxExpr: |
| { |
| MinMaxExpr *minmaxexpr = (MinMaxExpr *) node; |
| int nelems = list_length(minmaxexpr->args); |
| TypeCacheEntry *typentry; |
| FmgrInfo *finfo; |
| FunctionCallInfo fcinfo; |
| ListCell *lc; |
| int off; |
| |
| /* Look up the btree comparison function for the datatype */ |
| typentry = lookup_type_cache(minmaxexpr->minmaxtype, |
| TYPECACHE_CMP_PROC); |
| if (!OidIsValid(typentry->cmp_proc)) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_FUNCTION), |
| errmsg("could not identify a comparison function for type %s", |
| format_type_be(minmaxexpr->minmaxtype)))); |
| |
| /* |
| * If we enforced permissions checks on index support |
| * functions, we'd need to make a check here. But the index |
| * support machinery doesn't do that, and thus neither does |
| * this code. |
| */ |
| |
| /* Perform function lookup */ |
| finfo = palloc0(sizeof(FmgrInfo)); |
| fcinfo = palloc0(SizeForFunctionCallInfo(2)); |
| fmgr_info(typentry->cmp_proc, finfo); |
| fmgr_info_set_expr((Node *) node, finfo); |
| InitFunctionCallInfoData(*fcinfo, finfo, 2, |
| minmaxexpr->inputcollid, NULL, NULL); |
| |
| scratch.opcode = EEOP_MINMAX; |
| /* allocate space to store arguments */ |
| scratch.d.minmax.values = |
| (Datum *) palloc(sizeof(Datum) * nelems); |
| scratch.d.minmax.nulls = |
| (bool *) palloc(sizeof(bool) * nelems); |
| scratch.d.minmax.nelems = nelems; |
| |
| scratch.d.minmax.op = minmaxexpr->op; |
| scratch.d.minmax.finfo = finfo; |
| scratch.d.minmax.fcinfo_data = fcinfo; |
| |
| /* evaluate expressions into minmax->values/nulls */ |
| off = 0; |
| foreach(lc, minmaxexpr->args) |
| { |
| Expr *e = (Expr *) lfirst(lc); |
| |
| ExecInitExprRec(e, state, |
| &scratch.d.minmax.values[off], |
| &scratch.d.minmax.nulls[off]); |
| off++; |
| } |
| |
| /* and push the final comparison */ |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| case T_SQLValueFunction: |
| { |
| SQLValueFunction *svf = (SQLValueFunction *) node; |
| |
| scratch.opcode = EEOP_SQLVALUEFUNCTION; |
| scratch.d.sqlvaluefunction.svf = svf; |
| |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| case T_XmlExpr: |
| { |
| XmlExpr *xexpr = (XmlExpr *) node; |
| int nnamed = list_length(xexpr->named_args); |
| int nargs = list_length(xexpr->args); |
| int off; |
| ListCell *arg; |
| |
| scratch.opcode = EEOP_XMLEXPR; |
| scratch.d.xmlexpr.xexpr = xexpr; |
| |
| /* allocate space for storing all the arguments */ |
| if (nnamed) |
| { |
| scratch.d.xmlexpr.named_argvalue = |
| (Datum *) palloc(sizeof(Datum) * nnamed); |
| scratch.d.xmlexpr.named_argnull = |
| (bool *) palloc(sizeof(bool) * nnamed); |
| } |
| else |
| { |
| scratch.d.xmlexpr.named_argvalue = NULL; |
| scratch.d.xmlexpr.named_argnull = NULL; |
| } |
| |
| if (nargs) |
| { |
| scratch.d.xmlexpr.argvalue = |
| (Datum *) palloc(sizeof(Datum) * nargs); |
| scratch.d.xmlexpr.argnull = |
| (bool *) palloc(sizeof(bool) * nargs); |
| } |
| else |
| { |
| scratch.d.xmlexpr.argvalue = NULL; |
| scratch.d.xmlexpr.argnull = NULL; |
| } |
| |
| /* prepare argument execution */ |
| off = 0; |
| foreach(arg, xexpr->named_args) |
| { |
| Expr *e = (Expr *) lfirst(arg); |
| |
| ExecInitExprRec(e, state, |
| &scratch.d.xmlexpr.named_argvalue[off], |
| &scratch.d.xmlexpr.named_argnull[off]); |
| off++; |
| } |
| |
| off = 0; |
| foreach(arg, xexpr->args) |
| { |
| Expr *e = (Expr *) lfirst(arg); |
| |
| ExecInitExprRec(e, state, |
| &scratch.d.xmlexpr.argvalue[off], |
| &scratch.d.xmlexpr.argnull[off]); |
| off++; |
| } |
| |
| /* and evaluate the actual XML expression */ |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| case T_JsonValueExpr: |
| { |
| JsonValueExpr *jve = (JsonValueExpr *) node; |
| |
| Assert(jve->raw_expr != NULL); |
| ExecInitExprRec(jve->raw_expr, state, resv, resnull); |
| Assert(jve->formatted_expr != NULL); |
| ExecInitExprRec(jve->formatted_expr, state, resv, resnull); |
| break; |
| } |
| |
| case T_JsonConstructorExpr: |
| { |
| JsonConstructorExpr *ctor = (JsonConstructorExpr *) node; |
| List *args = ctor->args; |
| ListCell *lc; |
| int nargs = list_length(args); |
| int argno = 0; |
| |
| if (ctor->func) |
| { |
| ExecInitExprRec(ctor->func, state, resv, resnull); |
| } |
| else |
| { |
| JsonConstructorExprState *jcstate; |
| |
| jcstate = palloc0(sizeof(JsonConstructorExprState)); |
| |
| scratch.opcode = EEOP_JSON_CONSTRUCTOR; |
| scratch.d.json_constructor.jcstate = jcstate; |
| |
| jcstate->constructor = ctor; |
| jcstate->arg_values = (Datum *) palloc(sizeof(Datum) * nargs); |
| jcstate->arg_nulls = (bool *) palloc(sizeof(bool) * nargs); |
| jcstate->arg_types = (Oid *) palloc(sizeof(Oid) * nargs); |
| jcstate->nargs = nargs; |
| |
| foreach(lc, args) |
| { |
| Expr *arg = (Expr *) lfirst(lc); |
| |
| jcstate->arg_types[argno] = exprType((Node *) arg); |
| |
| if (IsA(arg, Const)) |
| { |
| /* Don't evaluate const arguments every round */ |
| Const *con = (Const *) arg; |
| |
| jcstate->arg_values[argno] = con->constvalue; |
| jcstate->arg_nulls[argno] = con->constisnull; |
| } |
| else |
| { |
| ExecInitExprRec(arg, state, |
| &jcstate->arg_values[argno], |
| &jcstate->arg_nulls[argno]); |
| } |
| argno++; |
| } |
| |
| ExprEvalPushStep(state, &scratch); |
| } |
| |
| if (ctor->coercion) |
| { |
| Datum *innermost_caseval = state->innermost_caseval; |
| bool *innermost_isnull = state->innermost_casenull; |
| |
| state->innermost_caseval = resv; |
| state->innermost_casenull = resnull; |
| |
| ExecInitExprRec(ctor->coercion, state, resv, resnull); |
| |
| state->innermost_caseval = innermost_caseval; |
| state->innermost_casenull = innermost_isnull; |
| } |
| } |
| break; |
| |
| case T_JsonIsPredicate: |
| { |
| JsonIsPredicate *pred = (JsonIsPredicate *) node; |
| |
| ExecInitExprRec((Expr *) pred->expr, state, resv, resnull); |
| |
| scratch.opcode = EEOP_IS_JSON; |
| scratch.d.is_json.pred = pred; |
| |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| case T_NullTest: |
| { |
| NullTest *ntest = (NullTest *) node; |
| |
| if (ntest->nulltesttype == IS_NULL) |
| { |
| if (ntest->argisrow) |
| scratch.opcode = EEOP_NULLTEST_ROWISNULL; |
| else |
| scratch.opcode = EEOP_NULLTEST_ISNULL; |
| } |
| else if (ntest->nulltesttype == IS_NOT_NULL) |
| { |
| if (ntest->argisrow) |
| scratch.opcode = EEOP_NULLTEST_ROWISNOTNULL; |
| else |
| scratch.opcode = EEOP_NULLTEST_ISNOTNULL; |
| } |
| else |
| { |
| elog(ERROR, "unrecognized nulltesttype: %d", |
| (int) ntest->nulltesttype); |
| } |
| /* initialize cache in case it's a row test */ |
| scratch.d.nulltest_row.rowcache.cacheptr = NULL; |
| |
| /* first evaluate argument into result variable */ |
| ExecInitExprRec(ntest->arg, state, |
| resv, resnull); |
| |
| /* then push the test of that argument */ |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| case T_BooleanTest: |
| { |
| BooleanTest *btest = (BooleanTest *) node; |
| |
| /* |
| * Evaluate argument, directly into result datum. That's ok, |
| * because resv/resnull is definitely not used anywhere else, |
| * and will get overwritten by the below EEOP_BOOLTEST_IS_* |
| * step. |
| */ |
| ExecInitExprRec(btest->arg, state, resv, resnull); |
| |
| switch (btest->booltesttype) |
| { |
| case IS_TRUE: |
| scratch.opcode = EEOP_BOOLTEST_IS_TRUE; |
| break; |
| case IS_NOT_TRUE: |
| scratch.opcode = EEOP_BOOLTEST_IS_NOT_TRUE; |
| break; |
| case IS_FALSE: |
| scratch.opcode = EEOP_BOOLTEST_IS_FALSE; |
| break; |
| case IS_NOT_FALSE: |
| scratch.opcode = EEOP_BOOLTEST_IS_NOT_FALSE; |
| break; |
| case IS_UNKNOWN: |
| /* Same as scalar IS NULL test */ |
| scratch.opcode = EEOP_NULLTEST_ISNULL; |
| break; |
| case IS_NOT_UNKNOWN: |
| /* Same as scalar IS NOT NULL test */ |
| scratch.opcode = EEOP_NULLTEST_ISNOTNULL; |
| break; |
| default: |
| elog(ERROR, "unrecognized booltesttype: %d", |
| (int) btest->booltesttype); |
| } |
| |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| case T_CoerceToDomain: |
| { |
| CoerceToDomain *ctest = (CoerceToDomain *) node; |
| |
| ExecInitCoerceToDomain(&scratch, ctest, state, |
| resv, resnull); |
| break; |
| } |
| |
| case T_CoerceToDomainValue: |
| { |
| /* |
| * Read from location identified by innermost_domainval. Note |
| * that innermost_domainval could be NULL, if we're compiling |
| * a standalone domain check rather than one embedded in a |
| * larger expression. In that case we must read from |
| * econtext->domainValue_datum. We'll take care of that |
| * scenario at runtime. |
| */ |
| scratch.opcode = EEOP_DOMAIN_TESTVAL; |
| /* we share instruction union variant with case testval */ |
| scratch.d.casetest.value = state->innermost_domainval; |
| scratch.d.casetest.isnull = state->innermost_domainnull; |
| |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| case T_CurrentOfExpr: |
| { |
| scratch.opcode = EEOP_CURRENTOFEXPR; |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| case T_NextValueExpr: |
| { |
| NextValueExpr *nve = (NextValueExpr *) node; |
| |
| scratch.opcode = EEOP_NEXTVALUEEXPR; |
| scratch.d.nextvalueexpr.seqid = nve->seqid; |
| scratch.d.nextvalueexpr.seqtypid = nve->typeId; |
| |
| ExprEvalPushStep(state, &scratch); |
| break; |
| } |
| |
| default: |
| elog(ERROR, "unrecognized node type: %d", |
| (int) nodeTag(node)); |
| break; |
| } |
| } |
| |
| /* |
| * Add another expression evaluation step to ExprState->steps. |
| * |
| * Note that this potentially re-allocates es->steps, therefore no pointer |
| * into that array may be used while the expression is still being built. |
| */ |
| void |
| ExprEvalPushStep(ExprState *es, const ExprEvalStep *s) |
| { |
| if (es->steps_alloc == 0) |
| { |
| es->steps_alloc = 16; |
| es->steps = palloc(sizeof(ExprEvalStep) * es->steps_alloc); |
| } |
| else if (es->steps_alloc == es->steps_len) |
| { |
| es->steps_alloc *= 2; |
| es->steps = repalloc(es->steps, |
| sizeof(ExprEvalStep) * es->steps_alloc); |
| } |
| |
| memcpy(&es->steps[es->steps_len++], s, sizeof(ExprEvalStep)); |
| } |
| |
| /* |
| * Perform setup necessary for the evaluation of a function-like expression, |
| * appending argument evaluation steps to the steps list in *state, and |
| * setting up *scratch so it is ready to be pushed. |
| * |
| * *scratch is not pushed here, so that callers may override the opcode, |
| * which is useful for function-like cases like DISTINCT. |
| */ |
| static void |
| ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid, |
| Oid inputcollid, ExprState *state) |
| { |
| int nargs = list_length(args); |
| AclResult aclresult; |
| FmgrInfo *flinfo; |
| FunctionCallInfo fcinfo; |
| int argno; |
| ListCell *lc; |
| |
| /* Check permission to call function */ |
| aclresult = object_aclcheck(ProcedureRelationId, funcid, GetUserId(), ACL_EXECUTE); |
| if (aclresult != ACLCHECK_OK) |
| aclcheck_error(aclresult, OBJECT_FUNCTION, get_func_name(funcid)); |
| InvokeFunctionExecuteHook(funcid); |
| |
| /* |
| * Safety check on nargs. Under normal circumstances this should never |
| * fail, as parser should check sooner. But possibly it might fail if |
| * server has been compiled with FUNC_MAX_ARGS smaller than some functions |
| * declared in pg_proc? |
| */ |
| if (nargs > FUNC_MAX_ARGS) |
| ereport(ERROR, |
| (errcode(ERRCODE_TOO_MANY_ARGUMENTS), |
| errmsg_plural("cannot pass more than %d argument to a function", |
| "cannot pass more than %d arguments to a function", |
| FUNC_MAX_ARGS, |
| FUNC_MAX_ARGS))); |
| |
| /* Allocate function lookup data and parameter workspace for this call */ |
| scratch->d.func.finfo = palloc0(sizeof(FmgrInfo)); |
| scratch->d.func.fcinfo_data = palloc0(SizeForFunctionCallInfo(nargs)); |
| flinfo = scratch->d.func.finfo; |
| fcinfo = scratch->d.func.fcinfo_data; |
| |
| /* Set up the primary fmgr lookup information */ |
| fmgr_info(funcid, flinfo); |
| fmgr_info_set_expr((Node *) node, flinfo); |
| |
| /* Initialize function call parameter structure too */ |
| InitFunctionCallInfoData(*fcinfo, flinfo, |
| nargs, inputcollid, NULL, NULL); |
| |
| /* Keep extra copies of this info to save an indirection at runtime */ |
| scratch->d.func.fn_addr = flinfo->fn_addr; |
| scratch->d.func.nargs = nargs; |
| |
| /* We only support non-set functions here */ |
| if (flinfo->fn_retset) |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("set-valued function called in context that cannot accept a set"), |
| state->parent ? |
| executor_errposition(state->parent->state, |
| exprLocation((Node *) node)) : 0)); |
| |
| /* Build code to evaluate arguments directly into the fcinfo struct */ |
| argno = 0; |
| foreach(lc, args) |
| { |
| Expr *arg = (Expr *) lfirst(lc); |
| |
| if (IsA(arg, Const)) |
| { |
| /* |
| * Don't evaluate const arguments every round; especially |
| * interesting for constants in comparisons. |
| */ |
| Const *con = (Const *) arg; |
| |
| fcinfo->args[argno].value = con->constvalue; |
| fcinfo->args[argno].isnull = con->constisnull; |
| } |
| else |
| { |
| ExecInitExprRec(arg, state, |
| &fcinfo->args[argno].value, |
| &fcinfo->args[argno].isnull); |
| } |
| argno++; |
| } |
| |
| /* Insert appropriate opcode depending on strictness and stats level */ |
| if (pgstat_track_functions <= flinfo->fn_stats) |
| { |
| if (flinfo->fn_strict && nargs > 0) |
| scratch->opcode = EEOP_FUNCEXPR_STRICT; |
| else |
| scratch->opcode = EEOP_FUNCEXPR; |
| } |
| else |
| { |
| if (flinfo->fn_strict && nargs > 0) |
| scratch->opcode = EEOP_FUNCEXPR_STRICT_FUSAGE; |
| else |
| scratch->opcode = EEOP_FUNCEXPR_FUSAGE; |
| } |
| } |
| |
| /* |
| * Add expression steps performing setup that's needed before any of the |
| * main execution of the expression. |
| */ |
| static void |
| ExecCreateExprSetupSteps(ExprState *state, Node *node) |
| { |
| ExprSetupInfo info = {0, 0, 0, NIL}; |
| |
| /* Prescan to find out what we need. */ |
| expr_setup_walker(node, &info); |
| |
| /* And generate those steps. */ |
| ExecPushExprSetupSteps(state, &info); |
| } |
| |
| /* |
| * Add steps performing expression setup as indicated by "info". |
| * This is useful when building an ExprState covering more than one expression. |
| */ |
| static void |
| ExecPushExprSetupSteps(ExprState *state, ExprSetupInfo *info) |
| { |
| ExprEvalStep scratch = {0}; |
| ListCell *lc; |
| |
| scratch.resvalue = NULL; |
| scratch.resnull = NULL; |
| |
| /* |
| * Add steps deforming the ExprState's inner/outer/scan slots as much as |
| * required by any Vars appearing in the expression. |
| */ |
| if (info->last_inner > 0) |
| { |
| scratch.opcode = EEOP_INNER_FETCHSOME; |
| scratch.d.fetch.last_var = info->last_inner; |
| scratch.d.fetch.fixed = false; |
| scratch.d.fetch.kind = NULL; |
| scratch.d.fetch.known_desc = NULL; |
| if (ExecComputeSlotInfo(state, &scratch)) |
| ExprEvalPushStep(state, &scratch); |
| } |
| if (info->last_outer > 0) |
| { |
| scratch.opcode = EEOP_OUTER_FETCHSOME; |
| scratch.d.fetch.last_var = info->last_outer; |
| scratch.d.fetch.fixed = false; |
| scratch.d.fetch.kind = NULL; |
| scratch.d.fetch.known_desc = NULL; |
| if (ExecComputeSlotInfo(state, &scratch)) |
| ExprEvalPushStep(state, &scratch); |
| } |
| if (info->last_scan > 0) |
| { |
| scratch.opcode = EEOP_SCAN_FETCHSOME; |
| scratch.d.fetch.last_var = info->last_scan; |
| scratch.d.fetch.fixed = false; |
| scratch.d.fetch.kind = NULL; |
| scratch.d.fetch.known_desc = NULL; |
| if (ExecComputeSlotInfo(state, &scratch)) |
| ExprEvalPushStep(state, &scratch); |
| } |
| |
| /* |
| * Add steps to execute any MULTIEXPR SubPlans appearing in the |
| * expression. We need to evaluate these before any of the Params |
| * referencing their outputs are used, but after we've prepared for any |
| * Var references they may contain. (There cannot be cross-references |
| * between MULTIEXPR SubPlans, so we needn't worry about their order.) |
| */ |
| foreach(lc, info->multiexpr_subplans) |
| { |
| SubPlan *subplan = (SubPlan *) lfirst(lc); |
| SubPlanState *sstate; |
| |
| Assert(subplan->subLinkType == MULTIEXPR_SUBLINK); |
| |
| /* This should match what ExecInitExprRec does for other SubPlans: */ |
| |
| if (!state->parent) |
| elog(ERROR, "SubPlan found with no parent plan"); |
| |
| sstate = ExecInitSubPlan(subplan, state->parent); |
| |
| /* add SubPlanState nodes to state->parent->subPlan */ |
| state->parent->subPlan = lappend(state->parent->subPlan, |
| sstate); |
| |
| scratch.opcode = EEOP_SUBPLAN; |
| scratch.d.subplan.sstate = sstate; |
| |
| /* The result can be ignored, but we better put it somewhere */ |
| scratch.resvalue = &state->resvalue; |
| scratch.resnull = &state->resnull; |
| |
| ExprEvalPushStep(state, &scratch); |
| } |
| } |
| |
| /* |
| * expr_setup_walker: expression walker for ExecCreateExprSetupSteps |
| */ |
| static bool |
| expr_setup_walker(Node *node, ExprSetupInfo *info) |
| { |
| if (node == NULL) |
| return false; |
| if (IsA(node, Var)) |
| { |
| Var *variable = (Var *) node; |
| AttrNumber attnum = variable->varattno; |
| |
| switch (variable->varno) |
| { |
| case INNER_VAR: |
| info->last_inner = Max(info->last_inner, attnum); |
| break; |
| |
| case OUTER_VAR: |
| info->last_outer = Max(info->last_outer, attnum); |
| break; |
| |
| /* INDEX_VAR is handled by default case */ |
| |
| default: |
| info->last_scan = Max(info->last_scan, attnum); |
| break; |
| } |
| return false; |
| } |
| |
| /* Collect all MULTIEXPR SubPlans, too */ |
| if (IsA(node, SubPlan)) |
| { |
| SubPlan *subplan = (SubPlan *) node; |
| |
| if (subplan->subLinkType == MULTIEXPR_SUBLINK) |
| info->multiexpr_subplans = lappend(info->multiexpr_subplans, |
| subplan); |
| } |
| |
| /* |
| * Don't examine the arguments or filters of Aggrefs or WindowFuncs, |
| * because those do not represent expressions to be evaluated within the |
| * calling expression's econtext. GroupingFunc arguments are never |
| * evaluated at all. |
| */ |
| if (IsA(node, Aggref)) |
| return false; |
| if (IsA(node, WindowFunc)) |
| return false; |
| if (IsA(node, GroupingFunc)) |
| return false; |
| return expression_tree_walker(node, expr_setup_walker, |
| (void *) info); |
| } |
| |
| /* |
| * Compute additional information for EEOP_*_FETCHSOME ops. |
| * |
| * The goal is to determine whether a slot is 'fixed', that is, every |
| * evaluation of the expression will have the same type of slot, with an |
| * equivalent descriptor. |
| * |
| * Returns true if the deforming step is required, false otherwise. |
| */ |
| static bool |
| ExecComputeSlotInfo(ExprState *state, ExprEvalStep *op) |
| { |
| PlanState *parent = state->parent; |
| TupleDesc desc = NULL; |
| const TupleTableSlotOps *tts_ops = NULL; |
| bool isfixed = false; |
| ExprEvalOp opcode = op->opcode; |
| |
| Assert(opcode == EEOP_INNER_FETCHSOME || |
| opcode == EEOP_OUTER_FETCHSOME || |
| opcode == EEOP_SCAN_FETCHSOME); |
| |
| if (op->d.fetch.known_desc != NULL) |
| { |
| desc = op->d.fetch.known_desc; |
| tts_ops = op->d.fetch.kind; |
| isfixed = op->d.fetch.kind != NULL; |
| } |
| else if (!parent) |
| { |
| isfixed = false; |
| } |
| else if (opcode == EEOP_INNER_FETCHSOME) |
| { |
| PlanState *is = innerPlanState(parent); |
| |
| if (parent->inneropsset && !parent->inneropsfixed) |
| { |
| isfixed = false; |
| } |
| else if (parent->inneropsset && parent->innerops) |
| { |
| isfixed = true; |
| tts_ops = parent->innerops; |
| desc = ExecGetResultType(is); |
| } |
| else if (is) |
| { |
| tts_ops = ExecGetResultSlotOps(is, &isfixed); |
| desc = ExecGetResultType(is); |
| } |
| } |
| else if (opcode == EEOP_OUTER_FETCHSOME) |
| { |
| PlanState *os = outerPlanState(parent); |
| |
| if (parent->outeropsset && !parent->outeropsfixed) |
| { |
| isfixed = false; |
| } |
| else if (parent->outeropsset && parent->outerops) |
| { |
| isfixed = true; |
| tts_ops = parent->outerops; |
| desc = ExecGetResultType(os); |
| } |
| else if (os) |
| { |
| tts_ops = ExecGetResultSlotOps(os, &isfixed); |
| desc = ExecGetResultType(os); |
| } |
| } |
| else if (opcode == EEOP_SCAN_FETCHSOME) |
| { |
| desc = parent->scandesc; |
| |
| if (parent->scanops) |
| tts_ops = parent->scanops; |
| |
| if (parent->scanopsset) |
| isfixed = parent->scanopsfixed; |
| } |
| |
| if (isfixed && desc != NULL && tts_ops != NULL) |
| { |
| op->d.fetch.fixed = true; |
| op->d.fetch.kind = tts_ops; |
| op->d.fetch.known_desc = desc; |
| } |
| else |
| { |
| op->d.fetch.fixed = false; |
| op->d.fetch.kind = NULL; |
| op->d.fetch.known_desc = NULL; |
| } |
| |
| /* if the slot is known to always virtual we never need to deform */ |
| if (op->d.fetch.fixed && op->d.fetch.kind == &TTSOpsVirtual) |
| return false; |
| |
| return true; |
| } |
| |
| /* |
| * Prepare step for the evaluation of a whole-row variable. |
| * The caller still has to push the step. |
| */ |
| static void |
| ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable, ExprState *state) |
| { |
| PlanState *parent = state->parent; |
| |
| /* fill in all but the target */ |
| scratch->opcode = EEOP_WHOLEROW; |
| scratch->d.wholerow.var = variable; |
| scratch->d.wholerow.first = true; |
| scratch->d.wholerow.slow = false; |
| scratch->d.wholerow.tupdesc = NULL; /* filled at runtime */ |
| scratch->d.wholerow.junkFilter = NULL; |
| |
| /* |
| * If the input tuple came from a subquery, it might contain "resjunk" |
| * columns (such as GROUP BY or ORDER BY columns), which we don't want to |
| * keep in the whole-row result. We can get rid of such columns by |
| * passing the tuple through a JunkFilter --- but to make one, we have to |
| * lay our hands on the subquery's targetlist. Fortunately, there are not |
| * very many cases where this can happen, and we can identify all of them |
| * by examining our parent PlanState. We assume this is not an issue in |
| * standalone expressions that don't have parent plans. (Whole-row Vars |
| * can occur in such expressions, but they will always be referencing |
| * table rows.) |
| */ |
| if (parent) |
| { |
| PlanState *subplan = NULL; |
| |
| switch (nodeTag(parent)) |
| { |
| case T_SubqueryScanState: |
| subplan = ((SubqueryScanState *) parent)->subplan; |
| break; |
| case T_CteScanState: |
| subplan = ((CteScanState *) parent)->cteplanstate; |
| break; |
| default: |
| break; |
| } |
| |
| if (subplan) |
| { |
| bool junk_filter_needed = false; |
| ListCell *tlist; |
| |
| /* Detect whether subplan tlist actually has any junk columns */ |
| foreach(tlist, subplan->plan->targetlist) |
| { |
| TargetEntry *tle = (TargetEntry *) lfirst(tlist); |
| |
| if (tle->resjunk) |
| { |
| junk_filter_needed = true; |
| break; |
| } |
| } |
| |
| /* If so, build the junkfilter now */ |
| if (junk_filter_needed) |
| { |
| scratch->d.wholerow.junkFilter = |
| ExecInitJunkFilter(subplan->plan->targetlist, |
| ExecInitExtraTupleSlot(parent->state, NULL, |
| &TTSOpsVirtual), |
| NULL); |
| } |
| } |
| } |
| } |
| |
| /* |
| * Prepare evaluation of a SubscriptingRef expression. |
| */ |
| static void |
| ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref, |
| ExprState *state, Datum *resv, bool *resnull) |
| { |
| bool isAssignment = (sbsref->refassgnexpr != NULL); |
| int nupper = list_length(sbsref->refupperindexpr); |
| int nlower = list_length(sbsref->reflowerindexpr); |
| const SubscriptRoutines *sbsroutines; |
| SubscriptingRefState *sbsrefstate; |
| SubscriptExecSteps methods; |
| char *ptr; |
| List *adjust_jumps = NIL; |
| ListCell *lc; |
| int i; |
| |
| /* Look up the subscripting support methods */ |
| sbsroutines = getSubscriptingRoutines(sbsref->refcontainertype, NULL); |
| if (!sbsroutines) |
| ereport(ERROR, |
| (errcode(ERRCODE_DATATYPE_MISMATCH), |
| errmsg("cannot subscript type %s because it does not support subscripting", |
| format_type_be(sbsref->refcontainertype)), |
| state->parent ? |
| executor_errposition(state->parent->state, |
| exprLocation((Node *) sbsref)) : 0)); |
| |
| /* Allocate sbsrefstate, with enough space for per-subscript arrays too */ |
| sbsrefstate = palloc0(MAXALIGN(sizeof(SubscriptingRefState)) + |
| (nupper + nlower) * (sizeof(Datum) + |
| 2 * sizeof(bool))); |
| |
| /* Fill constant fields of SubscriptingRefState */ |
| sbsrefstate->isassignment = isAssignment; |
| sbsrefstate->numupper = nupper; |
| sbsrefstate->numlower = nlower; |
| /* Set up per-subscript arrays */ |
| ptr = ((char *) sbsrefstate) + MAXALIGN(sizeof(SubscriptingRefState)); |
| sbsrefstate->upperindex = (Datum *) ptr; |
| ptr += nupper * sizeof(Datum); |
| sbsrefstate->lowerindex = (Datum *) ptr; |
| ptr += nlower * sizeof(Datum); |
| sbsrefstate->upperprovided = (bool *) ptr; |
| ptr += nupper * sizeof(bool); |
| sbsrefstate->lowerprovided = (bool *) ptr; |
| ptr += nlower * sizeof(bool); |
| sbsrefstate->upperindexnull = (bool *) ptr; |
| ptr += nupper * sizeof(bool); |
| sbsrefstate->lowerindexnull = (bool *) ptr; |
| /* ptr += nlower * sizeof(bool); */ |
| |
| /* |
| * Let the container-type-specific code have a chance. It must fill the |
| * "methods" struct with function pointers for us to possibly use in |
| * execution steps below; and it can optionally set up some data pointed |
| * to by the workspace field. |
| */ |
| memset(&methods, 0, sizeof(methods)); |
| sbsroutines->exec_setup(sbsref, sbsrefstate, &methods); |
| |
| /* |
| * Evaluate array input. It's safe to do so into resv/resnull, because we |
| * won't use that as target for any of the other subexpressions, and it'll |
| * be overwritten by the final EEOP_SBSREF_FETCH/ASSIGN step, which is |
| * pushed last. |
| */ |
| ExecInitExprRec(sbsref->refexpr, state, resv, resnull); |
| |
| /* |
| * If refexpr yields NULL, and the operation should be strict, then result |
| * is NULL. We can implement this with just JUMP_IF_NULL, since we |
| * evaluated the array into the desired target location. |
| */ |
| if (!isAssignment && sbsroutines->fetch_strict) |
| { |
| scratch->opcode = EEOP_JUMP_IF_NULL; |
| scratch->d.jump.jumpdone = -1; /* adjust later */ |
| ExprEvalPushStep(state, scratch); |
| adjust_jumps = lappend_int(adjust_jumps, |
| state->steps_len - 1); |
| } |
| |
| /* Evaluate upper subscripts */ |
| i = 0; |
| foreach(lc, sbsref->refupperindexpr) |
| { |
| Expr *e = (Expr *) lfirst(lc); |
| |
| /* When slicing, individual subscript bounds can be omitted */ |
| if (!e) |
| { |
| sbsrefstate->upperprovided[i] = false; |
| sbsrefstate->upperindexnull[i] = true; |
| } |
| else |
| { |
| sbsrefstate->upperprovided[i] = true; |
| /* Each subscript is evaluated into appropriate array entry */ |
| ExecInitExprRec(e, state, |
| &sbsrefstate->upperindex[i], |
| &sbsrefstate->upperindexnull[i]); |
| } |
| i++; |
| } |
| |
| /* Evaluate lower subscripts similarly */ |
| i = 0; |
| foreach(lc, sbsref->reflowerindexpr) |
| { |
| Expr *e = (Expr *) lfirst(lc); |
| |
| /* When slicing, individual subscript bounds can be omitted */ |
| if (!e) |
| { |
| sbsrefstate->lowerprovided[i] = false; |
| sbsrefstate->lowerindexnull[i] = true; |
| } |
| else |
| { |
| sbsrefstate->lowerprovided[i] = true; |
| /* Each subscript is evaluated into appropriate array entry */ |
| ExecInitExprRec(e, state, |
| &sbsrefstate->lowerindex[i], |
| &sbsrefstate->lowerindexnull[i]); |
| } |
| i++; |
| } |
| |
| /* SBSREF_SUBSCRIPTS checks and converts all the subscripts at once */ |
| if (methods.sbs_check_subscripts) |
| { |
| scratch->opcode = EEOP_SBSREF_SUBSCRIPTS; |
| scratch->d.sbsref_subscript.subscriptfunc = methods.sbs_check_subscripts; |
| scratch->d.sbsref_subscript.state = sbsrefstate; |
| scratch->d.sbsref_subscript.jumpdone = -1; /* adjust later */ |
| ExprEvalPushStep(state, scratch); |
| adjust_jumps = lappend_int(adjust_jumps, |
| state->steps_len - 1); |
| } |
| |
| if (isAssignment) |
| { |
| Datum *save_innermost_caseval; |
| bool *save_innermost_casenull; |
| |
| /* Check for unimplemented methods */ |
| if (!methods.sbs_assign) |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("type %s does not support subscripted assignment", |
| format_type_be(sbsref->refcontainertype)))); |
| |
| /* |
| * We might have a nested-assignment situation, in which the |
| * refassgnexpr is itself a FieldStore or SubscriptingRef that needs |
| * to obtain and modify the previous value of the array element or |
| * slice being replaced. If so, we have to extract that value from |
| * the array and pass it down via the CaseTestExpr mechanism. It's |
| * safe to reuse the CASE mechanism because there cannot be a CASE |
| * between here and where the value would be needed, and an array |
| * assignment can't be within a CASE either. (So saving and restoring |
| * innermost_caseval is just paranoia, but let's do it anyway.) |
| * |
| * Since fetching the old element might be a nontrivial expense, do it |
| * only if the argument actually needs it. |
| */ |
| if (isAssignmentIndirectionExpr(sbsref->refassgnexpr)) |
| { |
| if (!methods.sbs_fetch_old) |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("type %s does not support subscripted assignment", |
| format_type_be(sbsref->refcontainertype)))); |
| scratch->opcode = EEOP_SBSREF_OLD; |
| scratch->d.sbsref.subscriptfunc = methods.sbs_fetch_old; |
| scratch->d.sbsref.state = sbsrefstate; |
| ExprEvalPushStep(state, scratch); |
| } |
| |
| /* SBSREF_OLD puts extracted value into prevvalue/prevnull */ |
| save_innermost_caseval = state->innermost_caseval; |
| save_innermost_casenull = state->innermost_casenull; |
| state->innermost_caseval = &sbsrefstate->prevvalue; |
| state->innermost_casenull = &sbsrefstate->prevnull; |
| |
| /* evaluate replacement value into replacevalue/replacenull */ |
| ExecInitExprRec(sbsref->refassgnexpr, state, |
| &sbsrefstate->replacevalue, &sbsrefstate->replacenull); |
| |
| state->innermost_caseval = save_innermost_caseval; |
| state->innermost_casenull = save_innermost_casenull; |
| |
| /* and perform the assignment */ |
| scratch->opcode = EEOP_SBSREF_ASSIGN; |
| scratch->d.sbsref.subscriptfunc = methods.sbs_assign; |
| scratch->d.sbsref.state = sbsrefstate; |
| ExprEvalPushStep(state, scratch); |
| } |
| else |
| { |
| /* array fetch is much simpler */ |
| scratch->opcode = EEOP_SBSREF_FETCH; |
| scratch->d.sbsref.subscriptfunc = methods.sbs_fetch; |
| scratch->d.sbsref.state = sbsrefstate; |
| ExprEvalPushStep(state, scratch); |
| } |
| |
| /* adjust jump targets */ |
| foreach(lc, adjust_jumps) |
| { |
| ExprEvalStep *as = &state->steps[lfirst_int(lc)]; |
| |
| if (as->opcode == EEOP_SBSREF_SUBSCRIPTS) |
| { |
| Assert(as->d.sbsref_subscript.jumpdone == -1); |
| as->d.sbsref_subscript.jumpdone = state->steps_len; |
| } |
| else |
| { |
| Assert(as->opcode == EEOP_JUMP_IF_NULL); |
| Assert(as->d.jump.jumpdone == -1); |
| as->d.jump.jumpdone = state->steps_len; |
| } |
| } |
| } |
| |
| /* |
| * Helper for preparing SubscriptingRef expressions for evaluation: is expr |
| * a nested FieldStore or SubscriptingRef that needs the old element value |
| * passed down? |
| * |
| * (We could use this in FieldStore too, but in that case passing the old |
| * value is so cheap there's no need.) |
| * |
| * Note: it might seem that this needs to recurse, but in most cases it does |
| * not; the CaseTestExpr, if any, will be directly the arg or refexpr of the |
| * top-level node. Nested-assignment situations give rise to expression |
| * trees in which each level of assignment has its own CaseTestExpr, and the |
| * recursive structure appears within the newvals or refassgnexpr field. |
| * There is an exception, though: if the array is an array-of-domain, we will |
| * have a CoerceToDomain or RelabelType as the refassgnexpr, and we need to |
| * be able to look through that. |
| */ |
| static bool |
| isAssignmentIndirectionExpr(Expr *expr) |
| { |
| if (expr == NULL) |
| return false; /* just paranoia */ |
| if (IsA(expr, FieldStore)) |
| { |
| FieldStore *fstore = (FieldStore *) expr; |
| |
| if (fstore->arg && IsA(fstore->arg, CaseTestExpr)) |
| return true; |
| } |
| else if (IsA(expr, SubscriptingRef)) |
| { |
| SubscriptingRef *sbsRef = (SubscriptingRef *) expr; |
| |
| if (sbsRef->refexpr && IsA(sbsRef->refexpr, CaseTestExpr)) |
| return true; |
| } |
| else if (IsA(expr, CoerceToDomain)) |
| { |
| CoerceToDomain *cd = (CoerceToDomain *) expr; |
| |
| return isAssignmentIndirectionExpr(cd->arg); |
| } |
| else if (IsA(expr, RelabelType)) |
| { |
| RelabelType *r = (RelabelType *) expr; |
| |
| return isAssignmentIndirectionExpr(r->arg); |
| } |
| return false; |
| } |
| |
| /* |
| * Prepare evaluation of a CoerceToDomain expression. |
| */ |
| static void |
| ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest, |
| ExprState *state, Datum *resv, bool *resnull) |
| { |
| DomainConstraintRef *constraint_ref; |
| Datum *domainval = NULL; |
| bool *domainnull = NULL; |
| ListCell *l; |
| |
| scratch->d.domaincheck.resulttype = ctest->resulttype; |
| /* we'll allocate workspace only if needed */ |
| scratch->d.domaincheck.checkvalue = NULL; |
| scratch->d.domaincheck.checknull = NULL; |
| |
| /* |
| * Evaluate argument - it's fine to directly store it into resv/resnull, |
| * if there's constraint failures there'll be errors, otherwise it's what |
| * needs to be returned. |
| */ |
| ExecInitExprRec(ctest->arg, state, resv, resnull); |
| |
| /* |
| * Note: if the argument is of varlena type, it could be a R/W expanded |
| * object. We want to return the R/W pointer as the final result, but we |
| * have to pass a R/O pointer as the value to be tested by any functions |
| * in check expressions. We don't bother to emit a MAKE_READONLY step |
| * unless there's actually at least one check expression, though. Until |
| * we've tested that, domainval/domainnull are NULL. |
| */ |
| |
| /* |
| * Collect the constraints associated with the domain. |
| * |
| * Note: before PG v10 we'd recheck the set of constraints during each |
| * evaluation of the expression. Now we bake them into the ExprState |
| * during executor initialization. That means we don't need typcache.c to |
| * provide compiled exprs. |
| */ |
| constraint_ref = (DomainConstraintRef *) |
| palloc(sizeof(DomainConstraintRef)); |
| InitDomainConstraintRef(ctest->resulttype, |
| constraint_ref, |
| CurrentMemoryContext, |
| false); |
| |
| /* |
| * Compile code to check each domain constraint. NOTNULL constraints can |
| * just be applied on the resv/resnull value, but for CHECK constraints we |
| * need more pushups. |
| */ |
| foreach(l, constraint_ref->constraints) |
| { |
| DomainConstraintState *con = (DomainConstraintState *) lfirst(l); |
| Datum *save_innermost_domainval; |
| bool *save_innermost_domainnull; |
| |
| scratch->d.domaincheck.constraintname = con->name; |
| |
| switch (con->constrainttype) |
| { |
| case DOM_CONSTRAINT_NOTNULL: |
| scratch->opcode = EEOP_DOMAIN_NOTNULL; |
| ExprEvalPushStep(state, scratch); |
| break; |
| case DOM_CONSTRAINT_CHECK: |
| /* Allocate workspace for CHECK output if we didn't yet */ |
| if (scratch->d.domaincheck.checkvalue == NULL) |
| { |
| scratch->d.domaincheck.checkvalue = |
| (Datum *) palloc(sizeof(Datum)); |
| scratch->d.domaincheck.checknull = |
| (bool *) palloc(sizeof(bool)); |
| } |
| |
| /* |
| * If first time through, determine where CoerceToDomainValue |
| * nodes should read from. |
| */ |
| if (domainval == NULL) |
| { |
| /* |
| * Since value might be read multiple times, force to R/O |
| * - but only if it could be an expanded datum. |
| */ |
| if (get_typlen(ctest->resulttype) == -1) |
| { |
| ExprEvalStep scratch2 = {0}; |
| |
| /* Yes, so make output workspace for MAKE_READONLY */ |
| domainval = (Datum *) palloc(sizeof(Datum)); |
| domainnull = (bool *) palloc(sizeof(bool)); |
| |
| /* Emit MAKE_READONLY */ |
| scratch2.opcode = EEOP_MAKE_READONLY; |
| scratch2.resvalue = domainval; |
| scratch2.resnull = domainnull; |
| scratch2.d.make_readonly.value = resv; |
| scratch2.d.make_readonly.isnull = resnull; |
| ExprEvalPushStep(state, &scratch2); |
| } |
| else |
| { |
| /* No, so it's fine to read from resv/resnull */ |
| domainval = resv; |
| domainnull = resnull; |
| } |
| } |
| |
| /* |
| * Set up value to be returned by CoerceToDomainValue nodes. |
| * We must save and restore innermost_domainval/null fields, |
| * in case this node is itself within a check expression for |
| * another domain. |
| */ |
| save_innermost_domainval = state->innermost_domainval; |
| save_innermost_domainnull = state->innermost_domainnull; |
| state->innermost_domainval = domainval; |
| state->innermost_domainnull = domainnull; |
| |
| /* evaluate check expression value */ |
| ExecInitExprRec(con->check_expr, state, |
| scratch->d.domaincheck.checkvalue, |
| scratch->d.domaincheck.checknull); |
| |
| state->innermost_domainval = save_innermost_domainval; |
| state->innermost_domainnull = save_innermost_domainnull; |
| |
| /* now test result */ |
| scratch->opcode = EEOP_DOMAIN_CHECK; |
| ExprEvalPushStep(state, scratch); |
| |
| break; |
| default: |
| elog(ERROR, "unrecognized constraint type: %d", |
| (int) con->constrainttype); |
| break; |
| } |
| } |
| } |
| |
| /* |
| * GPDB: For a few built-in equality operators, we have a specialized fast-path |
| * version to evaluate a ScalarArrayOpExpr. In the fast path, the array |
| * argument must be a constant. It is decomposed into a C array here for faster |
| * evaluation at runtime. |
| */ |
| static bool |
| ExecInitScalarArrayOpFastPath(ExprEvalStep *scratch, |
| ScalarArrayOpExpr *opexpr, |
| ExprState *state, Datum *resv, bool *resnull) |
| { |
| Oid opfuncid = opexpr->opfuncid; |
| Oid collid = opexpr->inputcollid; |
| Expr *scalararg; |
| Expr *arrayarg; |
| Const *arrayconst; |
| ArrayType *arr; |
| int16 typlen; |
| bool typbyval; |
| char typalign; |
| char *s; |
| bits8 *bitmap; |
| int bitmask; |
| int nelems; |
| int *fp_len; |
| Datum *fp_datum; |
| |
| Assert(list_length(opexpr->args) == 2); |
| scalararg = (Expr *) linitial(opexpr->args); |
| arrayarg = (Expr *) lsecond(opexpr->args); |
| |
| /* IN will be evaluated as OR */ |
| if (!opexpr->useOr) |
| return false; |
| |
| /* only if the array arg is const */ |
| if (!IsA(arrayarg, Const)) |
| return false; |
| arrayconst = (Const *) arrayarg; |
| |
| /* We do not handle null */ |
| if (arrayconst->constisnull) |
| return false; |
| |
| arr = DatumGetArrayTypeP(arrayconst->constvalue); |
| nelems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr)); |
| |
| /* We do not handle this case */ |
| if (nelems <= 0) |
| return false; |
| |
| if (opfuncid == F_INT2EQ || opfuncid == F_INT4EQ || opexpr->opfuncid == F_INT8EQ || |
| opfuncid == F_DATE_EQ) |
| { |
| /* ok */ |
| } |
| else if (opfuncid == F_TEXTEQ || opfuncid == F_BPCHAREQ) |
| { |
| /* |
| * Since we're not calling texteq(), sanity check here that collation was |
| * reosolved. This is the same as check_collation_set(collid). |
| */ |
| if (!OidIsValid(collid)) |
| { |
| /* |
| * This typically means that the parser could not resolve a conflict |
| * of implicit collations, so report it that way. |
| */ |
| ereport(ERROR, |
| (errcode(ERRCODE_INDETERMINATE_COLLATION), |
| errmsg("could not determine which collation to use for string comparison"), |
| errhint("Use the COLLATE clause to set the collation explicitly."))); |
| } |
| |
| /* |
| * Only ok if we can use memcmp(). These conditions should match those |
| * in texteq()! |
| */ |
| if (lc_collate_is_c(collid) || |
| collid == DEFAULT_COLLATION_OID || |
| pg_newlocale_from_collation(collid)->deterministic) |
| { |
| /* ok */ |
| } |
| else |
| return false; |
| } |
| else |
| return false; |
| |
| /* All looks good. Deconstruct the array. */ |
| get_typlenbyvalalign(ARR_ELEMTYPE(arr), |
| &typlen, |
| &typbyval, |
| &typalign); |
| s = (char *) ARR_DATA_PTR(arr); |
| bitmap = ARR_NULLBITMAP(arr); |
| bitmask = 1; |
| |
| fp_len = (int *) palloc(sizeof(int) * nelems); |
| fp_datum = (Datum *) palloc(sizeof(Datum) * nelems); |
| |
| for (int i = 0; i < nelems; i++) |
| { |
| Datum elt; |
| Datum datum; |
| int len = 0; |
| |
| /* Do not deal with null yet */ |
| if (bitmap && (*bitmap & bitmask) == 0) |
| return false; |
| |
| elt = fetch_att(s, typbyval, typlen); |
| s = att_addlength_pointer(s, typlen, PointerGetDatum(s)); |
| s = (char *) att_align_nominal(s, typalign); |
| |
| /* int type */ |
| if (opfuncid == F_INT2EQ) |
| datum = Int16GetDatum(DatumGetInt16(elt)); |
| else if (opfuncid == F_INT4EQ || opfuncid == F_DATE_EQ) |
| datum = Int32GetDatum(DatumGetInt32(elt)); |
| else if (opfuncid == F_INT8EQ) |
| datum = elt; |
| else if (opfuncid == F_TEXTEQ || opfuncid == F_BPCHAREQ) |
| { |
| char *p; |
| void *tofree; |
| char *pdest; |
| |
| varattrib_untoast_ptr_len(elt, &p, &len, &tofree); |
| |
| /* bpchareq, rid of trailing white space. see bpeq and bcTruelen */ |
| if (opfuncid == F_BPCHAREQ) |
| { |
| while(len > 0 && p[len-1] == ' ') |
| --len; |
| } |
| |
| pdest = palloc(len); |
| datum = PointerGetDatum(pdest); |
| |
| memcpy(pdest, p, len); |
| |
| if (tofree) |
| pfree(tofree); |
| } |
| else |
| { |
| Assert(!"Wrong optimize_funcoid"); |
| return false; |
| } |
| |
| fp_len[i] = len; |
| fp_datum[i] = datum; |
| |
| /* advance bitmap pointer if any */ |
| if (bitmap) |
| { |
| bitmask <<= 1; |
| if (bitmask == 0x100 /* 1<<8 */) |
| { |
| bitmap++; |
| bitmask = 1; |
| } |
| } |
| } |
| |
| /* |
| * Evaluate scalar argument into our return value. There's no danger in |
| * that, because the return value is guaranteed to be overwritten by |
| * EEOP_SCALARARRAYOP_FAST_* step, and will not be passed to any other |
| * expression. (In the slow path, the array argument is similarly |
| * evaluated into the return value) |
| */ |
| ExecInitExprRec(scalararg, state, resv, resnull); |
| |
| /* And perform the operation */ |
| if (opfuncid == F_INT2EQ || opfuncid == F_INT4EQ || opfuncid == F_INT8EQ || |
| opfuncid == F_DATE_EQ) |
| { |
| scratch->opcode = EEOP_SCALARARRAYOP_FAST_INT; |
| } |
| else if (opfuncid == F_TEXTEQ || opfuncid == F_BPCHAREQ) |
| scratch->opcode = EEOP_SCALARARRAYOP_FAST_STR; |
| else |
| { |
| Assert(!"Wrong optimize_funcoid"); |
| return false; |
| } |
| |
| scratch->d.scalararrayop_fast.opfuncid = opfuncid; |
| scratch->d.scalararrayop_fast.fp_n = nelems; |
| scratch->d.scalararrayop_fast.fp_len = fp_len; |
| scratch->d.scalararrayop_fast.fp_datum = fp_datum; |
| ExprEvalPushStep(state, scratch); |
| |
| return true; |
| } |
| |
| /* |
| * Build transition/combine function invocations for all aggregate transition |
| * / combination function invocations in a grouping sets phase. This has to |
| * invoke all sort based transitions in a phase (if doSort is true), all hash |
| * based transitions (if doHash is true), or both (both true). |
| * |
| * The resulting expression will, for each set of transition values, first |
| * check for filters, evaluate aggregate input, check that that input is not |
| * NULL for a strict transition function, and then finally invoke the |
| * transition for each of the concurrently computed grouping sets. |
| * |
| * If nullcheck is true, the generated code will check for a NULL pointer to |
| * the array of AggStatePerGroup, and skip evaluation if so. |
| */ |
| ExprState * |
| ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase, |
| bool doSort, bool doHash, bool nullcheck) |
| { |
| ExprState *state = makeNode(ExprState); |
| PlanState *parent = &aggstate->ss.ps; |
| ExprEvalStep scratch = {0}; |
| ExprSetupInfo deform = {0, 0, 0, NIL}; |
| |
| state->expr = (Expr *) aggstate; |
| state->parent = parent; |
| |
| scratch.resvalue = &state->resvalue; |
| scratch.resnull = &state->resnull; |
| |
| /* |
| * First figure out which slots, and how many columns from each, we're |
| * going to need. |
| */ |
| for (int transno = 0; transno < aggstate->numtrans; transno++) |
| { |
| AggStatePerTrans pertrans = &aggstate->pertrans[transno]; |
| |
| if (!bms_is_member(transno, aggstate->aggs_used)) |
| continue; |
| expr_setup_walker((Node *) pertrans->aggref->aggdirectargs, |
| &deform); |
| expr_setup_walker((Node *) pertrans->aggref->args, |
| &deform); |
| expr_setup_walker((Node *) pertrans->aggref->aggorder, |
| &deform); |
| expr_setup_walker((Node *) pertrans->aggref->aggdistinct, |
| &deform); |
| expr_setup_walker((Node *) pertrans->aggref->aggfilter, |
| &deform); |
| |
| if (aggstate->AggExprId_AttrNum > 0) |
| deform.last_outer = Max(deform.last_outer, |
| aggstate->AggExprId_AttrNum); |
| } |
| ExecPushExprSetupSteps(state, &deform); |
| |
| /* |
| * Emit instructions for each transition value / grouping set combination. |
| */ |
| for (int transno = 0; transno < aggstate->numtrans; transno++) |
| { |
| AggStatePerTrans pertrans = &aggstate->pertrans[transno]; |
| FunctionCallInfo trans_fcinfo = pertrans->transfn_fcinfo; |
| List *adjust_bailout = NIL; |
| NullableDatum *strictargs = NULL; |
| bool *strictnulls = NULL; |
| int argno; |
| ListCell *bail; |
| if (!bms_is_member(transno, aggstate->aggs_used)) |
| continue; |
| |
| bool isCombine = DO_AGGSPLIT_COMBINE(pertrans->aggref->aggsplit); |
| /* |
| * If filter present, emit. Do so before evaluating the input, to |
| * avoid potentially unneeded computations, or even worse, unintended |
| * side-effects. When combining, all the necessary filtering has |
| * already been done. |
| */ |
| if (pertrans->aggref->aggfilter && !isCombine) |
| { |
| /* evaluate filter expression */ |
| ExecInitExprRec(pertrans->aggref->aggfilter, state, |
| &state->resvalue, &state->resnull); |
| /* and jump out if false */ |
| scratch.opcode = EEOP_JUMP_IF_NOT_TRUE; |
| scratch.d.jump.jumpdone = -1; /* adjust later */ |
| ExprEvalPushStep(state, &scratch); |
| adjust_bailout = lappend_int(adjust_bailout, |
| state->steps_len - 1); |
| } |
| |
| /* if AggExprId is in input, trans function bitmap should match */ |
| if (aggstate->AggExprId_AttrNum > 0) |
| { |
| Expr *aggexpr_matches_expr; |
| |
| /* evaluate expression: <AggExprId column> == pertrans->agg_aggref->expr_id - 1 */ |
| aggexpr_matches_expr = (Expr *) |
| makeFuncExpr(F_INT4EQ, |
| BOOLOID, |
| list_make2( |
| makeVar(OUTER_VAR, |
| aggstate->AggExprId_AttrNum, |
| INT4OID, |
| -1, |
| InvalidOid, |
| 0), |
| makeConst(INT4OID, |
| -1, |
| InvalidOid, |
| sizeof(int32), |
| Int32GetDatum(pertrans->aggref->agg_expr_id - 1), |
| false, |
| true)), |
| InvalidOid, |
| InvalidOid, |
| COERCE_EXPLICIT_CALL); |
| ExecInitExprRec(aggexpr_matches_expr, state, |
| &state->resvalue, &state->resnull); |
| /* and jump out if false */ |
| scratch.opcode = EEOP_JUMP_IF_NOT_TRUE; |
| scratch.d.jump.jumpdone = -1; /* adjust later */ |
| ExprEvalPushStep(state, &scratch); |
| adjust_bailout = lappend_int(adjust_bailout, |
| state->steps_len - 1); |
| } |
| |
| /* |
| * Evaluate arguments to aggregate/combine function. |
| */ |
| argno = 0; |
| if (isCombine) |
| { |
| /* |
| * Combining two aggregate transition values. Instead of directly |
| * coming from a tuple the input is a, potentially deserialized, |
| * transition value. |
| */ |
| TargetEntry *source_tle; |
| |
| Assert(pertrans->numSortCols == 0); |
| Assert(list_length(pertrans->aggref->args) == 1); |
| |
| strictargs = trans_fcinfo->args + 1; |
| source_tle = (TargetEntry *) linitial(pertrans->aggref->args); |
| |
| /* |
| * deserialfn_oid will be set if we must deserialize the input |
| * state before calling the combine function. |
| */ |
| if (!OidIsValid(pertrans->deserialfn_oid)) |
| { |
| /* |
| * Start from 1, since the 0th arg will be the transition |
| * value |
| */ |
| ExecInitExprRec(source_tle->expr, state, |
| &trans_fcinfo->args[argno + 1].value, |
| &trans_fcinfo->args[argno + 1].isnull); |
| } |
| else |
| { |
| FunctionCallInfo ds_fcinfo = pertrans->deserialfn_fcinfo; |
| |
| /* evaluate argument */ |
| ExecInitExprRec(source_tle->expr, state, |
| &ds_fcinfo->args[0].value, |
| &ds_fcinfo->args[0].isnull); |
| |
| /* Dummy second argument for type-safety reasons */ |
| ds_fcinfo->args[1].value = PointerGetDatum(NULL); |
| ds_fcinfo->args[1].isnull = false; |
| |
| /* |
| * Don't call a strict deserialization function with NULL |
| * input |
| */ |
| if (pertrans->deserialfn.fn_strict) |
| scratch.opcode = EEOP_AGG_STRICT_DESERIALIZE; |
| else |
| scratch.opcode = EEOP_AGG_DESERIALIZE; |
| |
| scratch.d.agg_deserialize.fcinfo_data = ds_fcinfo; |
| scratch.d.agg_deserialize.jumpnull = -1; /* adjust later */ |
| scratch.resvalue = &trans_fcinfo->args[argno + 1].value; |
| scratch.resnull = &trans_fcinfo->args[argno + 1].isnull; |
| |
| ExprEvalPushStep(state, &scratch); |
| /* don't add an adjustment unless the function is strict */ |
| if (pertrans->deserialfn.fn_strict) |
| adjust_bailout = lappend_int(adjust_bailout, |
| state->steps_len - 1); |
| |
| /* restore normal settings of scratch fields */ |
| scratch.resvalue = &state->resvalue; |
| scratch.resnull = &state->resnull; |
| } |
| argno++; |
| |
| Assert(pertrans->numInputs == argno); |
| } |
| else if (!pertrans->aggsortrequired) |
| { |
| ListCell *arg; |
| |
| /* |
| * Normal transition function without ORDER BY / DISTINCT or with |
| * ORDER BY / DISTINCT but the planner has given us pre-sorted |
| * input. |
| */ |
| strictargs = trans_fcinfo->args + 1; |
| |
| foreach(arg, pertrans->aggref->args) |
| { |
| TargetEntry *source_tle = (TargetEntry *) lfirst(arg); |
| |
| /* |
| * Don't initialize args for any ORDER BY clause that might |
| * exist in a presorted aggregate. |
| */ |
| if (argno == pertrans->numTransInputs) |
| break; |
| |
| /* |
| * Start from 1, since the 0th arg will be the transition |
| * value |
| */ |
| ExecInitExprRec(source_tle->expr, state, |
| &trans_fcinfo->args[argno + 1].value, |
| &trans_fcinfo->args[argno + 1].isnull); |
| argno++; |
| } |
| Assert(pertrans->numTransInputs == argno); |
| } |
| else if (pertrans->numInputs == 1) |
| { |
| /* |
| * Non-presorted DISTINCT and/or ORDER BY case, with a single |
| * column sorted on. |
| */ |
| TargetEntry *source_tle = |
| (TargetEntry *) linitial(pertrans->aggref->args); |
| |
| Assert(list_length(pertrans->aggref->args) == 1); |
| |
| ExecInitExprRec(source_tle->expr, state, |
| &state->resvalue, |
| &state->resnull); |
| strictnulls = &state->resnull; |
| argno++; |
| |
| Assert(pertrans->numInputs == argno); |
| } |
| else |
| { |
| /* |
| * Non-presorted DISTINCT and/or ORDER BY case, with multiple |
| * columns sorted on. |
| */ |
| Datum *values = pertrans->sortslot->tts_values; |
| bool *nulls = pertrans->sortslot->tts_isnull; |
| ListCell *arg; |
| |
| strictnulls = nulls; |
| |
| foreach(arg, pertrans->aggref->args) |
| { |
| TargetEntry *source_tle = (TargetEntry *) lfirst(arg); |
| |
| ExecInitExprRec(source_tle->expr, state, |
| &values[argno], &nulls[argno]); |
| argno++; |
| } |
| Assert(pertrans->numInputs == argno); |
| } |
| |
| /* |
| * For a strict transfn, nothing happens when there's a NULL input; we |
| * just keep the prior transValue. This is true for both plain and |
| * sorted/distinct aggregates. |
| */ |
| if (trans_fcinfo->flinfo->fn_strict && pertrans->numTransInputs > 0) |
| { |
| if (strictnulls) |
| scratch.opcode = EEOP_AGG_STRICT_INPUT_CHECK_NULLS; |
| else |
| scratch.opcode = EEOP_AGG_STRICT_INPUT_CHECK_ARGS; |
| scratch.d.agg_strict_input_check.nulls = strictnulls; |
| scratch.d.agg_strict_input_check.args = strictargs; |
| scratch.d.agg_strict_input_check.jumpnull = -1; /* adjust later */ |
| scratch.d.agg_strict_input_check.nargs = pertrans->numTransInputs; |
| ExprEvalPushStep(state, &scratch); |
| adjust_bailout = lappend_int(adjust_bailout, |
| state->steps_len - 1); |
| } |
| |
| /* Handle DISTINCT aggregates which have pre-sorted input */ |
| if (pertrans->numDistinctCols > 0 && !pertrans->aggsortrequired) |
| { |
| if (pertrans->numDistinctCols > 1) |
| scratch.opcode = EEOP_AGG_PRESORTED_DISTINCT_MULTI; |
| else |
| scratch.opcode = EEOP_AGG_PRESORTED_DISTINCT_SINGLE; |
| |
| scratch.d.agg_presorted_distinctcheck.pertrans = pertrans; |
| scratch.d.agg_presorted_distinctcheck.jumpdistinct = -1; /* adjust later */ |
| ExprEvalPushStep(state, &scratch); |
| adjust_bailout = lappend_int(adjust_bailout, |
| state->steps_len - 1); |
| } |
| |
| /* |
| * Call transition function (once for each concurrently evaluated |
| * grouping set). Do so for both sort and hash based computations, as |
| * applicable. |
| */ |
| if (doSort) |
| { |
| int processGroupingSets = Max(phase->numsets, 1); |
| int setoff = 0; |
| |
| for (int setno = 0; setno < processGroupingSets; setno++) |
| { |
| ExecBuildAggTransCall(state, aggstate, &scratch, trans_fcinfo, |
| pertrans, transno, setno, setoff, false, |
| nullcheck); |
| setoff++; |
| } |
| } |
| |
| if (doHash) |
| { |
| int numHashes = aggstate->num_hashes; |
| int setoff; |
| |
| /* in MIXED mode, there'll be preceding transition values */ |
| if (aggstate->aggstrategy != AGG_HASHED) |
| setoff = aggstate->maxsets; |
| else |
| setoff = 0; |
| |
| for (int setno = 0; setno < numHashes; setno++) |
| { |
| ExecBuildAggTransCall(state, aggstate, &scratch, trans_fcinfo, |
| pertrans, transno, setno, setoff, true, |
| nullcheck); |
| setoff++; |
| } |
| } |
| |
| /* adjust early bail out jump target(s) */ |
| foreach(bail, adjust_bailout) |
| { |
| ExprEvalStep *as = &state->steps[lfirst_int(bail)]; |
| |
| if (as->opcode == EEOP_JUMP_IF_NOT_TRUE) |
| { |
| Assert(as->d.jump.jumpdone == -1); |
| as->d.jump.jumpdone = state->steps_len; |
| } |
| else if (as->opcode == EEOP_AGG_STRICT_INPUT_CHECK_ARGS || |
| as->opcode == EEOP_AGG_STRICT_INPUT_CHECK_NULLS) |
| { |
| Assert(as->d.agg_strict_input_check.jumpnull == -1); |
| as->d.agg_strict_input_check.jumpnull = state->steps_len; |
| } |
| else if (as->opcode == EEOP_AGG_STRICT_DESERIALIZE) |
| { |
| Assert(as->d.agg_deserialize.jumpnull == -1); |
| as->d.agg_deserialize.jumpnull = state->steps_len; |
| } |
| else if (as->opcode == EEOP_AGG_PRESORTED_DISTINCT_SINGLE || |
| as->opcode == EEOP_AGG_PRESORTED_DISTINCT_MULTI) |
| { |
| Assert(as->d.agg_presorted_distinctcheck.jumpdistinct == -1); |
| as->d.agg_presorted_distinctcheck.jumpdistinct = state->steps_len; |
| } |
| else |
| Assert(false); |
| } |
| } |
| |
| scratch.resvalue = NULL; |
| scratch.resnull = NULL; |
| scratch.opcode = EEOP_DONE; |
| ExprEvalPushStep(state, &scratch); |
| |
| ExecReadyExpr(state); |
| |
| return state; |
| } |
| |
| /* |
| * Build transition/combine function invocation for a single transition |
| * value. This is separated from ExecBuildAggTrans() because there are |
| * multiple callsites (hash and sort in some grouping set cases). |
| */ |
| static void |
| ExecBuildAggTransCall(ExprState *state, AggState *aggstate, |
| ExprEvalStep *scratch, |
| FunctionCallInfo fcinfo, AggStatePerTrans pertrans, |
| int transno, int setno, int setoff, bool ishash, |
| bool nullcheck) |
| { |
| ExprContext *aggcontext; |
| int adjust_jumpnull = -1; |
| |
| if (ishash) |
| aggcontext = aggstate->hashcontext; |
| else |
| aggcontext = aggstate->aggcontexts[setno]; |
| |
| /* add check for NULL pointer? */ |
| if (nullcheck) |
| { |
| scratch->opcode = EEOP_AGG_PLAIN_PERGROUP_NULLCHECK; |
| scratch->d.agg_plain_pergroup_nullcheck.setoff = setoff; |
| /* adjust later */ |
| scratch->d.agg_plain_pergroup_nullcheck.jumpnull = -1; |
| ExprEvalPushStep(state, scratch); |
| adjust_jumpnull = state->steps_len - 1; |
| } |
| |
| /* |
| * Determine appropriate transition implementation. |
| * |
| * For non-ordered aggregates and ORDER BY / DISTINCT aggregates with |
| * presorted input: |
| * |
| * If the initial value for the transition state doesn't exist in the |
| * pg_aggregate table then we will let the first non-NULL value returned |
| * from the outer procNode become the initial value. (This is useful for |
| * aggregates like max() and min().) The noTransValue flag signals that we |
| * need to do so. If true, generate a |
| * EEOP_AGG_INIT_STRICT_PLAIN_TRANS{,_BYVAL} step. This step also needs to |
| * do the work described next: |
| * |
| * If the function is strict, but does have an initial value, choose |
| * EEOP_AGG_STRICT_PLAIN_TRANS{,_BYVAL}, which skips the transition |
| * function if the transition value has become NULL (because a previous |
| * transition function returned NULL). This step also needs to do the work |
| * described next: |
| * |
| * Otherwise we call EEOP_AGG_PLAIN_TRANS{,_BYVAL}, which does not have to |
| * perform either of the above checks. |
| * |
| * Having steps with overlapping responsibilities is not nice, but |
| * aggregations are very performance sensitive, making this worthwhile. |
| * |
| * For ordered aggregates: |
| * |
| * Only need to choose between the faster path for a single ordered |
| * column, and the one between multiple columns. Checking strictness etc |
| * is done when finalizing the aggregate. See |
| * process_ordered_aggregate_{single, multi} and |
| * advance_transition_function. |
| */ |
| if (!pertrans->aggsortrequired) |
| { |
| if (pertrans->transtypeByVal) |
| { |
| if (fcinfo->flinfo->fn_strict && |
| pertrans->initValueIsNull) |
| scratch->opcode = EEOP_AGG_PLAIN_TRANS_INIT_STRICT_BYVAL; |
| else if (fcinfo->flinfo->fn_strict) |
| scratch->opcode = EEOP_AGG_PLAIN_TRANS_STRICT_BYVAL; |
| else |
| scratch->opcode = EEOP_AGG_PLAIN_TRANS_BYVAL; |
| } |
| else |
| { |
| if (fcinfo->flinfo->fn_strict && |
| pertrans->initValueIsNull) |
| scratch->opcode = EEOP_AGG_PLAIN_TRANS_INIT_STRICT_BYREF; |
| else if (fcinfo->flinfo->fn_strict) |
| scratch->opcode = EEOP_AGG_PLAIN_TRANS_STRICT_BYREF; |
| else |
| scratch->opcode = EEOP_AGG_PLAIN_TRANS_BYREF; |
| } |
| } |
| else if (pertrans->numInputs == 1) |
| scratch->opcode = EEOP_AGG_ORDERED_TRANS_DATUM; |
| else |
| scratch->opcode = EEOP_AGG_ORDERED_TRANS_TUPLE; |
| |
| scratch->d.agg_trans.pertrans = pertrans; |
| scratch->d.agg_trans.setno = setno; |
| scratch->d.agg_trans.setoff = setoff; |
| scratch->d.agg_trans.transno = transno; |
| scratch->d.agg_trans.aggcontext = aggcontext; |
| ExprEvalPushStep(state, scratch); |
| |
| /* fix up jumpnull */ |
| if (adjust_jumpnull != -1) |
| { |
| ExprEvalStep *as = &state->steps[adjust_jumpnull]; |
| |
| Assert(as->opcode == EEOP_AGG_PLAIN_PERGROUP_NULLCHECK); |
| Assert(as->d.agg_plain_pergroup_nullcheck.jumpnull == -1); |
| as->d.agg_plain_pergroup_nullcheck.jumpnull = state->steps_len; |
| } |
| } |
| |
| /* |
| * Build equality expression that can be evaluated using ExecQual(), returning |
| * true if the expression context's inner/outer tuple are NOT DISTINCT. I.e |
| * two nulls match, a null and a not-null don't match. |
| * |
| * desc: tuple descriptor of the to-be-compared tuples |
| * numCols: the number of attributes to be examined |
| * keyColIdx: array of attribute column numbers |
| * eqFunctions: array of function oids of the equality functions to use |
| * parent: parent executor node |
| */ |
| ExprState * |
| ExecBuildGroupingEqual(TupleDesc ldesc, TupleDesc rdesc, |
| const TupleTableSlotOps *lops, const TupleTableSlotOps *rops, |
| int numCols, |
| const AttrNumber *keyColIdx, |
| const Oid *eqfunctions, |
| const Oid *collations, |
| PlanState *parent) |
| { |
| ExprState *state = makeNode(ExprState); |
| ExprEvalStep scratch = {0}; |
| int maxatt = -1; |
| List *adjust_jumps = NIL; |
| ListCell *lc; |
| |
| /* |
| * When no columns are actually compared, the result's always true. See |
| * special case in ExecQual(). |
| */ |
| if (numCols == 0) |
| return NULL; |
| |
| state->expr = NULL; |
| state->flags = EEO_FLAG_IS_QUAL; |
| state->parent = parent; |
| |
| scratch.resvalue = &state->resvalue; |
| scratch.resnull = &state->resnull; |
| |
| /* compute max needed attribute */ |
| for (int natt = 0; natt < numCols; natt++) |
| { |
| int attno = keyColIdx[natt]; |
| |
| if (attno > maxatt) |
| maxatt = attno; |
| } |
| Assert(maxatt >= 0); |
| |
| /* push deform steps */ |
| scratch.opcode = EEOP_INNER_FETCHSOME; |
| scratch.d.fetch.last_var = maxatt; |
| scratch.d.fetch.fixed = false; |
| scratch.d.fetch.known_desc = ldesc; |
| scratch.d.fetch.kind = lops; |
| if (ExecComputeSlotInfo(state, &scratch)) |
| ExprEvalPushStep(state, &scratch); |
| |
| scratch.opcode = EEOP_OUTER_FETCHSOME; |
| scratch.d.fetch.last_var = maxatt; |
| scratch.d.fetch.fixed = false; |
| scratch.d.fetch.known_desc = rdesc; |
| scratch.d.fetch.kind = rops; |
| if (ExecComputeSlotInfo(state, &scratch)) |
| ExprEvalPushStep(state, &scratch); |
| |
| /* |
| * Start comparing at the last field (least significant sort key). That's |
| * the most likely to be different if we are dealing with sorted input. |
| */ |
| for (int natt = numCols; --natt >= 0;) |
| { |
| int attno = keyColIdx[natt]; |
| Form_pg_attribute latt = TupleDescAttr(ldesc, attno - 1); |
| Form_pg_attribute ratt = TupleDescAttr(rdesc, attno - 1); |
| Oid foid = eqfunctions[natt]; |
| Oid collid = collations[natt]; |
| FmgrInfo *finfo; |
| FunctionCallInfo fcinfo; |
| AclResult aclresult; |
| |
| /* Check permission to call function */ |
| aclresult = object_aclcheck(ProcedureRelationId, foid, GetUserId(), ACL_EXECUTE); |
| if (aclresult != ACLCHECK_OK) |
| aclcheck_error(aclresult, OBJECT_FUNCTION, get_func_name(foid)); |
| |
| InvokeFunctionExecuteHook(foid); |
| |
| /* Set up the primary fmgr lookup information */ |
| finfo = palloc0(sizeof(FmgrInfo)); |
| fcinfo = palloc0(SizeForFunctionCallInfo(2)); |
| fmgr_info(foid, finfo); |
| fmgr_info_set_expr(NULL, finfo); |
| InitFunctionCallInfoData(*fcinfo, finfo, 2, |
| collid, NULL, NULL); |
| |
| /* left arg */ |
| scratch.opcode = EEOP_INNER_VAR; |
| scratch.d.var.attnum = attno - 1; |
| scratch.d.var.vartype = latt->atttypid; |
| scratch.resvalue = &fcinfo->args[0].value; |
| scratch.resnull = &fcinfo->args[0].isnull; |
| ExprEvalPushStep(state, &scratch); |
| |
| /* right arg */ |
| scratch.opcode = EEOP_OUTER_VAR; |
| scratch.d.var.attnum = attno - 1; |
| scratch.d.var.vartype = ratt->atttypid; |
| scratch.resvalue = &fcinfo->args[1].value; |
| scratch.resnull = &fcinfo->args[1].isnull; |
| ExprEvalPushStep(state, &scratch); |
| |
| /* evaluate distinctness */ |
| scratch.opcode = EEOP_NOT_DISTINCT; |
| scratch.d.func.finfo = finfo; |
| scratch.d.func.fcinfo_data = fcinfo; |
| scratch.d.func.fn_addr = finfo->fn_addr; |
| scratch.d.func.nargs = 2; |
| scratch.resvalue = &state->resvalue; |
| scratch.resnull = &state->resnull; |
| ExprEvalPushStep(state, &scratch); |
| |
| /* then emit EEOP_QUAL to detect if result is false (or null) */ |
| scratch.opcode = EEOP_QUAL; |
| scratch.d.qualexpr.jumpdone = -1; |
| scratch.resvalue = &state->resvalue; |
| scratch.resnull = &state->resnull; |
| ExprEvalPushStep(state, &scratch); |
| adjust_jumps = lappend_int(adjust_jumps, |
| state->steps_len - 1); |
| } |
| |
| /* adjust jump targets */ |
| foreach(lc, adjust_jumps) |
| { |
| ExprEvalStep *as = &state->steps[lfirst_int(lc)]; |
| |
| Assert(as->opcode == EEOP_QUAL); |
| Assert(as->d.qualexpr.jumpdone == -1); |
| as->d.qualexpr.jumpdone = state->steps_len; |
| } |
| |
| scratch.resvalue = NULL; |
| scratch.resnull = NULL; |
| scratch.opcode = EEOP_DONE; |
| ExprEvalPushStep(state, &scratch); |
| |
| ExecReadyExpr(state); |
| |
| return state; |
| } |
| |
| /* |
| * Build equality expression that can be evaluated using ExecQual(), returning |
| * true if the expression context's inner/outer tuples are equal. Datums in |
| * the inner/outer slots are assumed to be in the same order and quantity as |
| * the 'eqfunctions' parameter. NULLs are treated as equal. |
| * |
| * desc: tuple descriptor of the to-be-compared tuples |
| * lops: the slot ops for the inner tuple slots |
| * rops: the slot ops for the outer tuple slots |
| * eqFunctions: array of function oids of the equality functions to use |
| * this must be the same length as the 'param_exprs' list. |
| * collations: collation Oids to use for equality comparison. Must be the |
| * same length as the 'param_exprs' list. |
| * parent: parent executor node |
| */ |
| ExprState * |
| ExecBuildParamSetEqual(TupleDesc desc, |
| const TupleTableSlotOps *lops, |
| const TupleTableSlotOps *rops, |
| const Oid *eqfunctions, |
| const Oid *collations, |
| const List *param_exprs, |
| PlanState *parent) |
| { |
| ExprState *state = makeNode(ExprState); |
| ExprEvalStep scratch = {0}; |
| int maxatt = list_length(param_exprs); |
| List *adjust_jumps = NIL; |
| ListCell *lc; |
| |
| state->expr = NULL; |
| state->flags = EEO_FLAG_IS_QUAL; |
| state->parent = parent; |
| |
| scratch.resvalue = &state->resvalue; |
| scratch.resnull = &state->resnull; |
| |
| /* push deform steps */ |
| scratch.opcode = EEOP_INNER_FETCHSOME; |
| scratch.d.fetch.last_var = maxatt; |
| scratch.d.fetch.fixed = false; |
| scratch.d.fetch.known_desc = desc; |
| scratch.d.fetch.kind = lops; |
| if (ExecComputeSlotInfo(state, &scratch)) |
| ExprEvalPushStep(state, &scratch); |
| |
| scratch.opcode = EEOP_OUTER_FETCHSOME; |
| scratch.d.fetch.last_var = maxatt; |
| scratch.d.fetch.fixed = false; |
| scratch.d.fetch.known_desc = desc; |
| scratch.d.fetch.kind = rops; |
| if (ExecComputeSlotInfo(state, &scratch)) |
| ExprEvalPushStep(state, &scratch); |
| |
| for (int attno = 0; attno < maxatt; attno++) |
| { |
| Form_pg_attribute att = TupleDescAttr(desc, attno); |
| Oid foid = eqfunctions[attno]; |
| Oid collid = collations[attno]; |
| FmgrInfo *finfo; |
| FunctionCallInfo fcinfo; |
| AclResult aclresult; |
| |
| /* Check permission to call function */ |
| aclresult = object_aclcheck(ProcedureRelationId, foid, GetUserId(), ACL_EXECUTE); |
| if (aclresult != ACLCHECK_OK) |
| aclcheck_error(aclresult, OBJECT_FUNCTION, get_func_name(foid)); |
| |
| InvokeFunctionExecuteHook(foid); |
| |
| /* Set up the primary fmgr lookup information */ |
| finfo = palloc0(sizeof(FmgrInfo)); |
| fcinfo = palloc0(SizeForFunctionCallInfo(2)); |
| fmgr_info(foid, finfo); |
| fmgr_info_set_expr(NULL, finfo); |
| InitFunctionCallInfoData(*fcinfo, finfo, 2, |
| collid, NULL, NULL); |
| |
| /* left arg */ |
| scratch.opcode = EEOP_INNER_VAR; |
| scratch.d.var.attnum = attno; |
| scratch.d.var.vartype = att->atttypid; |
| scratch.resvalue = &fcinfo->args[0].value; |
| scratch.resnull = &fcinfo->args[0].isnull; |
| ExprEvalPushStep(state, &scratch); |
| |
| /* right arg */ |
| scratch.opcode = EEOP_OUTER_VAR; |
| scratch.d.var.attnum = attno; |
| scratch.d.var.vartype = att->atttypid; |
| scratch.resvalue = &fcinfo->args[1].value; |
| scratch.resnull = &fcinfo->args[1].isnull; |
| ExprEvalPushStep(state, &scratch); |
| |
| /* evaluate distinctness */ |
| scratch.opcode = EEOP_NOT_DISTINCT; |
| scratch.d.func.finfo = finfo; |
| scratch.d.func.fcinfo_data = fcinfo; |
| scratch.d.func.fn_addr = finfo->fn_addr; |
| scratch.d.func.nargs = 2; |
| scratch.resvalue = &state->resvalue; |
| scratch.resnull = &state->resnull; |
| ExprEvalPushStep(state, &scratch); |
| |
| /* then emit EEOP_QUAL to detect if result is false (or null) */ |
| scratch.opcode = EEOP_QUAL; |
| scratch.d.qualexpr.jumpdone = -1; |
| scratch.resvalue = &state->resvalue; |
| scratch.resnull = &state->resnull; |
| ExprEvalPushStep(state, &scratch); |
| adjust_jumps = lappend_int(adjust_jumps, |
| state->steps_len - 1); |
| } |
| |
| /* adjust jump targets */ |
| foreach(lc, adjust_jumps) |
| { |
| ExprEvalStep *as = &state->steps[lfirst_int(lc)]; |
| |
| Assert(as->opcode == EEOP_QUAL); |
| Assert(as->d.qualexpr.jumpdone == -1); |
| as->d.qualexpr.jumpdone = state->steps_len; |
| } |
| |
| scratch.resvalue = NULL; |
| scratch.resnull = NULL; |
| scratch.opcode = EEOP_DONE; |
| ExprEvalPushStep(state, &scratch); |
| |
| ExecReadyExpr(state); |
| |
| return state; |
| } |
| |
| /* ---------------------------------------------------------------- |
| * isJoinExprNull |
| * |
| * Checks if the join expression evaluates to NULL for a given |
| * input tuple. |
| * |
| * The input tuple has to be present in the correct TupleTableSlot |
| * in the ExprContext. For example, if all the expressions |
| * in joinExpr refer to the inner side of the join, |
| * econtext->ecxt_innertuple must be valid. |
| * ---------------------------------------------------------------- |
| */ |
| bool |
| isJoinExprNull(List *joinExpr, ExprContext *econtext) |
| { |
| ListCell *lc; |
| bool joinkeys_null = true; |
| |
| Assert(joinExpr != NIL); |
| |
| foreach(lc, joinExpr) |
| { |
| ExprState *keyexpr = (ExprState *) lfirst(lc); |
| bool isNull = false; |
| |
| /* |
| * Evaluate the current join attribute value of the tuple |
| */ |
| ExecEvalExpr(keyexpr, econtext, &isNull); |
| |
| if (!isNull) |
| { |
| /* Found at least one non-null join expression, we're done */ |
| joinkeys_null = false; |
| break; |
| } |
| } |
| |
| return joinkeys_null; |
| } |
| |
| |
| |
| /* |
| * ExecIsExprUnsafeToConst_walker |
| * |
| * Almost all of the expressions are not allowed without the executor. |
| * Returns true as soon as possible we find such unsafe nodes. |
| */ |
| static bool |
| ExecIsExprUnsafeToConst_walker(Node *node, void *context) |
| { |
| switch(nodeTag(node)) |
| { |
| /* |
| * Param can be a Const in some situation, but the demanded use case |
| * so far doesn't want it. |
| */ |
| case T_Const: |
| case T_CaseTestExpr: |
| case T_FuncExpr: |
| case T_OpExpr: |
| case T_DistinctExpr: |
| case T_ScalarArrayOpExpr: |
| case T_BoolExpr: |
| case T_CaseExpr: |
| case T_CoalesceExpr: |
| case T_MinMaxExpr: |
| case T_NullIfExpr: |
| case T_NullTest: |
| case T_BooleanTest: |
| case T_List: |
| case T_TypeCast: |
| return false; |
| |
| default: |
| return true; |
| } |
| } |
| |
| /* |
| * ExecIsExprUnsafeToConst |
| * |
| * Returns true if the expression cannot be evaluated to a const value. |
| */ |
| static bool |
| ExecIsExprUnsafeToConst(Node *node) |
| { |
| Assert(node != NULL); |
| return ExecIsExprUnsafeToConst_walker(node, NULL); |
| } |
| |
| /* |
| * ExecEvalFunctionArgToConst |
| * |
| * Evaluates an argument of function expression and returns the result. |
| * This is assumed to be used in the parser stage, where |
| * dynamic evaluation such like Var is not available, though we put it |
| * here so that we can extend it to be useful in other places later. |
| */ |
| Datum |
| ExecEvalFunctionArgToConst(FuncExpr *fexpr, int argno, bool *isnull) |
| { |
| Expr *aexpr; |
| Oid argtype; |
| int32 argtypmod; |
| Oid argcollation; |
| Const *result; |
| |
| /* argument number sanity check */ |
| if (argno < 0 || list_length(fexpr->args) <= argno) |
| elog(ERROR, "invalid argument number found during evaluating function argument"); |
| |
| aexpr = (Expr *) list_nth(fexpr->args, argno); |
| /* |
| * Check if the expression can be evaluated in the Const fasion. |
| */ |
| if (ExecIsExprUnsafeToConst((Node *) aexpr)) |
| ereport(ERROR, |
| (errcode(ERRCODE_DATA_EXCEPTION), |
| errmsg("unable to resolve function argument"), |
| errposition(exprLocation((Node *) aexpr)))); |
| |
| argtype = exprType((Node *) aexpr); |
| if (!OidIsValid(argtype)) |
| ereport(ERROR, |
| (errcode(ERRCODE_INDETERMINATE_DATATYPE), |
| errmsg("unable to resolve function argument type"), |
| errposition(exprLocation((Node *) aexpr)))); |
| argtypmod = exprTypmod((Node *) aexpr); |
| argcollation = exprCollation((Node *) aexpr); |
| |
| result = (Const *) evaluate_expr(aexpr, argtype, argtypmod, argcollation); |
| /* evaluate_expr always returns Const */ |
| Assert(IsA(result, Const)); |
| |
| if (isnull) |
| *isnull = result->constisnull; |
| |
| return result->constvalue; |
| } |