diff --git a/src/backend/executor/nodeBitmapIndexscan.c b/src/backend/executor/nodeBitmapIndexscan.c index 902eeb35a4..46e8c14652 100644 --- a/src/backend/executor/nodeBitmapIndexscan.c +++ b/src/backend/executor/nodeBitmapIndexscan.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/nodeBitmapIndexscan.c,v 1.11 2005/11/22 18:17:10 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/executor/nodeBitmapIndexscan.c,v 1.12 2005/11/25 19:47:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -43,6 +43,7 @@ MultiExecBitmapIndexScan(BitmapIndexScanState *node) ItemPointerData tids[MAX_TIDS]; int32 ntids; double nTuples = 0; + bool doscan; /* must provide our own instrumentation support */ if (node->ss.ps.instrument) @@ -55,9 +56,18 @@ MultiExecBitmapIndexScan(BitmapIndexScanState *node) /* * If we have runtime keys and they've not already been set up, do it now. + * Array keys are also treated as runtime keys; note that if ExecReScan + * returns with biss_RuntimeKeysReady still false, then there is an + * empty array key so we should do nothing. */ - if (node->biss_RuntimeKeyInfo && !node->biss_RuntimeKeysReady) + if (!node->biss_RuntimeKeysReady && + (node->biss_NumRuntimeKeys != 0 || node->biss_NumArrayKeys != 0)) + { ExecReScan((PlanState *) node, NULL); + doscan = node->biss_RuntimeKeysReady; + } + else + doscan = true; /* * Prepare the result bitmap. Normally we just create a new one to pass @@ -79,7 +89,7 @@ MultiExecBitmapIndexScan(BitmapIndexScanState *node) /* * Get TIDs from index and insert into bitmap */ - for (;;) + while (doscan) { bool more = index_getmulti(scandesc, tids, MAX_TIDS, &ntids); @@ -89,10 +99,15 @@ MultiExecBitmapIndexScan(BitmapIndexScanState *node) nTuples += ntids; } - if (!more) - break; - CHECK_FOR_INTERRUPTS(); + + if (!more) + { + doscan = ExecIndexAdvanceArrayKeys(node->biss_ArrayKeys, + node->biss_NumArrayKeys); + if (doscan) /* reset index scan */ + index_rescan(node->biss_ScanDesc, node->biss_ScanKeys); + } } /* must provide our own instrumentation support */ @@ -113,10 +128,8 @@ void ExecBitmapIndexReScan(BitmapIndexScanState *node, ExprContext *exprCtxt) { ExprContext *econtext; - ExprState **runtimeKeyInfo; econtext = node->biss_RuntimeContext; /* context for runtime keys */ - runtimeKeyInfo = node->biss_RuntimeKeyInfo; if (econtext) { @@ -137,19 +150,27 @@ ExecBitmapIndexReScan(BitmapIndexScanState *node, ExprContext *exprCtxt) /* * If we are doing runtime key calculations (ie, the index keys depend on - * data from an outer scan), compute the new key values + * data from an outer scan), compute the new key values. + * + * Array keys are also treated as runtime keys; note that if we + * return with biss_RuntimeKeysReady still false, then there is an + * empty array key so no index scan is needed. */ - if (runtimeKeyInfo) - { + if (node->biss_NumRuntimeKeys != 0) ExecIndexEvalRuntimeKeys(econtext, - runtimeKeyInfo, - node->biss_ScanKeys, - node->biss_NumScanKeys); + node->biss_RuntimeKeys, + node->biss_NumRuntimeKeys); + if (node->biss_NumArrayKeys != 0) + node->biss_RuntimeKeysReady = + ExecIndexEvalArrayKeys(econtext, + node->biss_ArrayKeys, + node->biss_NumArrayKeys); + else node->biss_RuntimeKeysReady = true; - } /* reset index scan */ - index_rescan(node->biss_ScanDesc, node->biss_ScanKeys); + if (node->biss_RuntimeKeysReady) + index_rescan(node->biss_ScanDesc, node->biss_ScanKeys); } /* ---------------------------------------------------------------- @@ -193,10 +214,6 @@ BitmapIndexScanState * ExecInitBitmapIndexScan(BitmapIndexScan *node, EState *estate) { BitmapIndexScanState *indexstate; - ScanKey scanKeys; - int numScanKeys; - ExprState **runtimeKeyInfo; - bool have_runtime_keys; /* * create state structure @@ -236,26 +253,25 @@ ExecInitBitmapIndexScan(BitmapIndexScan *node, EState *estate) /* * build the index scan keys from the index qualification */ - have_runtime_keys = - ExecIndexBuildScanKeys((PlanState *) indexstate, - node->indexqual, - node->indexstrategy, - node->indexsubtype, - &runtimeKeyInfo, - &scanKeys, - &numScanKeys); - - indexstate->biss_RuntimeKeyInfo = runtimeKeyInfo; - indexstate->biss_ScanKeys = scanKeys; - indexstate->biss_NumScanKeys = numScanKeys; + ExecIndexBuildScanKeys((PlanState *) indexstate, + node->indexqual, + node->indexstrategy, + node->indexsubtype, + &indexstate->biss_ScanKeys, + &indexstate->biss_NumScanKeys, + &indexstate->biss_RuntimeKeys, + &indexstate->biss_NumRuntimeKeys, + &indexstate->biss_ArrayKeys, + &indexstate->biss_NumArrayKeys); /* - * If we have runtime keys, we need an ExprContext to evaluate them. We - * could just create a "standard" plan node exprcontext, but to keep the - * code looking similar to nodeIndexscan.c, it seems better to stick with - * the approach of using a separate ExprContext. + * If we have runtime keys or array keys, we need an ExprContext to + * evaluate them. We could just create a "standard" plan node exprcontext, + * but to keep the code looking similar to nodeIndexscan.c, it seems + * better to stick with the approach of using a separate ExprContext. */ - if (have_runtime_keys) + if (indexstate->biss_NumRuntimeKeys != 0 || + indexstate->biss_NumArrayKeys != 0) { ExprContext *stdecontext = indexstate->ss.ps.ps_ExprContext; @@ -286,8 +302,8 @@ ExecInitBitmapIndexScan(BitmapIndexScan *node, EState *estate) indexstate->biss_ScanDesc = index_beginscan_multi(indexstate->biss_RelationDesc, estate->es_snapshot, - numScanKeys, - scanKeys); + indexstate->biss_NumScanKeys, + indexstate->biss_ScanKeys); /* * all done. diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c index 4f6fadfde4..f34a14c859 100644 --- a/src/backend/executor/nodeIndexscan.c +++ b/src/backend/executor/nodeIndexscan.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/nodeIndexscan.c,v 1.106 2005/11/25 04:24:48 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/nodeIndexscan.c,v 1.107 2005/11/25 19:47:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -32,6 +32,8 @@ #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" #include "parser/parsetree.h" +#include "utils/array.h" +#include "utils/lsyscache.h" #include "utils/memutils.h" @@ -138,7 +140,7 @@ ExecIndexScan(IndexScanState *node) /* * If we have runtime keys and they've not already been set up, do it now. */ - if (node->iss_RuntimeKeyInfo && !node->iss_RuntimeKeysReady) + if (node->iss_NumRuntimeKeys != 0 && !node->iss_RuntimeKeysReady) ExecReScan((PlanState *) node, NULL); /* @@ -162,16 +164,10 @@ ExecIndexReScan(IndexScanState *node, ExprContext *exprCtxt) { EState *estate; ExprContext *econtext; - ScanKey scanKeys; - ExprState **runtimeKeyInfo; - int numScanKeys; Index scanrelid; estate = node->ss.ps.state; econtext = node->iss_RuntimeContext; /* context for runtime keys */ - scanKeys = node->iss_ScanKeys; - runtimeKeyInfo = node->iss_RuntimeKeyInfo; - numScanKeys = node->iss_NumScanKeys; scanrelid = ((IndexScan *) node->ss.ps.plan)->scan.scanrelid; if (econtext) @@ -202,14 +198,11 @@ ExecIndexReScan(IndexScanState *node, ExprContext *exprCtxt) * If we are doing runtime key calculations (ie, the index keys depend on * data from an outer scan), compute the new key values */ - if (runtimeKeyInfo) - { + if (node->iss_NumRuntimeKeys != 0) ExecIndexEvalRuntimeKeys(econtext, - runtimeKeyInfo, - scanKeys, - numScanKeys); - node->iss_RuntimeKeysReady = true; - } + node->iss_RuntimeKeys, + node->iss_NumRuntimeKeys); + node->iss_RuntimeKeysReady = true; /* If this is re-scanning of PlanQual ... */ if (estate->es_evTuple != NULL && @@ -220,7 +213,7 @@ ExecIndexReScan(IndexScanState *node, ExprContext *exprCtxt) } /* reset index scan */ - index_rescan(node->iss_ScanDesc, scanKeys); + index_rescan(node->iss_ScanDesc, node->iss_ScanKeys); } @@ -230,18 +223,21 @@ ExecIndexReScan(IndexScanState *node, ExprContext *exprCtxt) */ void ExecIndexEvalRuntimeKeys(ExprContext *econtext, - ExprState **run_keys, - ScanKey scan_keys, - int n_keys) + IndexRuntimeKeyInfo *runtimeKeys, int numRuntimeKeys) { int j; - for (j = 0; j < n_keys; j++) + for (j = 0; j < numRuntimeKeys; j++) { + ScanKey scan_key = runtimeKeys[j].scan_key; + ExprState *key_expr = runtimeKeys[j].key_expr; + Datum scanvalue; + bool isNull; + /* - * If we have a run-time key, then extract the run-time expression and + * For each run-time key, extract the run-time expression and * evaluate it with respect to the current outer tuple. We then stick - * the result into the scan key. + * the result into the proper scan key. * * Note: the result of the eval could be a pass-by-ref value that's * stored in the outer scan's tuple, not in @@ -250,24 +246,140 @@ ExecIndexEvalRuntimeKeys(ExprContext *econtext, * the result into our context explicitly, but I think that's not * necessary... */ - if (run_keys[j] != NULL) - { - Datum scanvalue; - bool isNull; - - scanvalue = ExecEvalExprSwitchContext(run_keys[j], - econtext, - &isNull, - NULL); - scan_keys[j].sk_argument = scanvalue; - if (isNull) - scan_keys[j].sk_flags |= SK_ISNULL; - else - scan_keys[j].sk_flags &= ~SK_ISNULL; - } + scanvalue = ExecEvalExprSwitchContext(key_expr, + econtext, + &isNull, + NULL); + scan_key->sk_argument = scanvalue; + if (isNull) + scan_key->sk_flags |= SK_ISNULL; + else + scan_key->sk_flags &= ~SK_ISNULL; } } +/* + * ExecIndexEvalArrayKeys + * Evaluate any array key values, and set up to iterate through arrays. + * + * Returns TRUE if there are array elements to consider; FALSE means there + * is at least one null or empty array, so no match is possible. On TRUE + * result, the scankeys are initialized with the first elements of the arrays. + */ +bool +ExecIndexEvalArrayKeys(ExprContext *econtext, + IndexArrayKeyInfo *arrayKeys, int numArrayKeys) +{ + bool result = true; + int j; + MemoryContext oldContext; + + /* We want to keep the arrays in per-tuple memory */ + oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); + + for (j = 0; j < numArrayKeys; j++) + { + ScanKey scan_key = arrayKeys[j].scan_key; + ExprState *array_expr = arrayKeys[j].array_expr; + Datum arraydatum; + bool isNull; + ArrayType *arrayval; + int16 elmlen; + bool elmbyval; + char elmalign; + int num_elems; + Datum *elem_values; + bool *elem_nulls; + + /* + * Compute and deconstruct the array expression. + * (Notes in ExecIndexEvalRuntimeKeys() apply here too.) + */ + arraydatum = ExecEvalExpr(array_expr, + econtext, + &isNull, + NULL); + if (isNull) + { + result = false; + break; /* no point in evaluating more */ + } + arrayval = DatumGetArrayTypeP(arraydatum); + /* We could cache this data, but not clear it's worth it */ + get_typlenbyvalalign(ARR_ELEMTYPE(arrayval), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(arrayval, + ARR_ELEMTYPE(arrayval), + elmlen, elmbyval, elmalign, + &elem_values, &elem_nulls, &num_elems); + if (num_elems <= 0) + { + result = false; + break; /* no point in evaluating more */ + } + + /* + * Note: we expect the previous array data, if any, to be automatically + * freed by resetting the per-tuple context; hence no pfree's here. + */ + arrayKeys[j].elem_values = elem_values; + arrayKeys[j].elem_nulls = elem_nulls; + arrayKeys[j].num_elems = num_elems; + scan_key->sk_argument = elem_values[0]; + if (elem_nulls[0]) + scan_key->sk_flags |= SK_ISNULL; + else + scan_key->sk_flags &= ~SK_ISNULL; + arrayKeys[j].next_elem = 1; + } + + MemoryContextSwitchTo(oldContext); + + return result; +} + +/* + * ExecIndexAdvanceArrayKeys + * Advance to the next set of array key values, if any. + * + * Returns TRUE if there is another set of values to consider, FALSE if not. + * On TRUE result, the scankeys are initialized with the next set of values. + */ +bool +ExecIndexAdvanceArrayKeys(IndexArrayKeyInfo *arrayKeys, int numArrayKeys) +{ + bool found = false; + int j; + + for (j = 0; j < numArrayKeys; j++) + { + ScanKey scan_key = arrayKeys[j].scan_key; + int next_elem = arrayKeys[j].next_elem; + int num_elems = arrayKeys[j].num_elems; + Datum *elem_values = arrayKeys[j].elem_values; + bool *elem_nulls = arrayKeys[j].elem_nulls; + + if (next_elem >= num_elems) + { + next_elem = 0; + found = false; /* need to advance next array key */ + } + else + found = true; + scan_key->sk_argument = elem_values[next_elem]; + if (elem_nulls[next_elem]) + scan_key->sk_flags |= SK_ISNULL; + else + scan_key->sk_flags &= ~SK_ISNULL; + arrayKeys[j].next_elem = next_elem + 1; + if (found) + break; + } + + return found; +} + + /* ---------------------------------------------------------------- * ExecEndIndexScan * ---------------------------------------------------------------- @@ -352,10 +464,6 @@ IndexScanState * ExecInitIndexScan(IndexScan *node, EState *estate) { IndexScanState *indexstate; - ScanKey scanKeys; - int numScanKeys; - ExprState **runtimeKeyInfo; - bool have_runtime_keys; RangeTblEntry *rtentry; Index relid; Oid reloid; @@ -412,18 +520,16 @@ ExecInitIndexScan(IndexScan *node, EState *estate) /* * build the index scan keys from the index qualification */ - have_runtime_keys = - ExecIndexBuildScanKeys((PlanState *) indexstate, - node->indexqual, - node->indexstrategy, - node->indexsubtype, - &runtimeKeyInfo, - &scanKeys, - &numScanKeys); - - indexstate->iss_RuntimeKeyInfo = runtimeKeyInfo; - indexstate->iss_ScanKeys = scanKeys; - indexstate->iss_NumScanKeys = numScanKeys; + ExecIndexBuildScanKeys((PlanState *) indexstate, + node->indexqual, + node->indexstrategy, + node->indexsubtype, + &indexstate->iss_ScanKeys, + &indexstate->iss_NumScanKeys, + &indexstate->iss_RuntimeKeys, + &indexstate->iss_NumRuntimeKeys, + NULL, /* no ArrayKeys */ + NULL); /* * If we have runtime keys, we need an ExprContext to evaluate them. The @@ -431,7 +537,7 @@ ExecInitIndexScan(IndexScan *node, EState *estate) * for every tuple. So, build another context just like the other one... * -tgl 7/11/00 */ - if (have_runtime_keys) + if (indexstate->iss_NumRuntimeKeys != 0) { ExprContext *stdecontext = indexstate->ss.ps.ps_ExprContext; @@ -471,8 +577,8 @@ ExecInitIndexScan(IndexScan *node, EState *estate) indexstate->iss_ScanDesc = index_beginscan(currentRelation, indexstate->iss_RelationDesc, estate->es_snapshot, - numScanKeys, - scanKeys); + indexstate->iss_NumScanKeys, + indexstate->iss_ScanKeys); /* * Initialize result tuple type and projection info. @@ -489,7 +595,26 @@ ExecInitIndexScan(IndexScan *node, EState *estate) /* * ExecIndexBuildScanKeys - * Build the index scan keys from the index qualification + * Build the index scan keys from the index qualification expressions + * + * The index quals are passed to the index AM in the form of a ScanKey array. + * This routine sets up the ScanKeys, fills in all constant fields of the + * ScanKeys, and prepares information about the keys that have non-constant + * comparison values. We divide index qual expressions into three types: + * + * 1. Simple operator with constant comparison value ("indexkey op constant"). + * For these, we just fill in a ScanKey containing the constant value. + * + * 2. Simple operator with non-constant value ("indexkey op expression"). + * For these, we create a ScanKey with everything filled in except the + * expression value, and set up an IndexRuntimeKeyInfo struct to drive + * evaluation of the expression at the right times. + * + * 3. ScalarArrayOpExpr ("indexkey op ANY (array-expression)"). For these, + * we create a ScanKey with everything filled in except the comparison value, + * and set up an IndexArrayKeyInfo struct to drive processing of the qual. + * (Note that we treat all array-expressions as requiring runtime evaluation, + * even if they happen to be constants.) * * Input params are: * @@ -500,33 +625,43 @@ ExecInitIndexScan(IndexScan *node, EState *estate) * * Output params are: * - * *runtimeKeyInfo: receives ptr to array of runtime key exprstates - * (NULL if no runtime keys) * *scanKeys: receives ptr to array of ScanKeys - * *numScanKeys: receives number of scankeys/runtime keys + * *numScanKeys: receives number of scankeys + * *runtimeKeys: receives ptr to array of IndexRuntimeKeyInfos, or NULL if none + * *numRuntimeKeys: receives number of runtime keys + * *arrayKeys: receives ptr to array of IndexArrayKeyInfos, or NULL if none + * *numArrayKeys: receives number of array keys * - * Return value is TRUE if any runtime key expressions were found, else FALSE. + * Caller may pass NULL for arrayKeys and numArrayKeys to indicate that + * ScalarArrayOpExpr quals are not supported. */ -bool +void ExecIndexBuildScanKeys(PlanState *planstate, List *quals, List *strategies, List *subtypes, - ExprState ***runtimeKeyInfo, - ScanKey *scanKeys, int *numScanKeys) + ScanKey *scanKeys, int *numScanKeys, + IndexRuntimeKeyInfo **runtimeKeys, int *numRuntimeKeys, + IndexArrayKeyInfo **arrayKeys, int *numArrayKeys) { - bool have_runtime_keys = false; ListCell *qual_cell; ListCell *strategy_cell; ListCell *subtype_cell; - int n_keys; ScanKey scan_keys; - ExprState **run_keys; + IndexRuntimeKeyInfo *runtime_keys; + IndexArrayKeyInfo *array_keys; + int n_scan_keys; + int n_runtime_keys; + int n_array_keys; int j; - n_keys = list_length(quals); - scan_keys = (n_keys <= 0) ? NULL : - (ScanKey) palloc(n_keys * sizeof(ScanKeyData)); - run_keys = (n_keys <= 0) ? NULL : - (ExprState **) palloc(n_keys * sizeof(ExprState *)); + n_scan_keys = list_length(quals); + scan_keys = (ScanKey) palloc(n_scan_keys * sizeof(ScanKeyData)); + /* Allocate these arrays as large as they could possibly need to be */ + runtime_keys = (IndexRuntimeKeyInfo *) + palloc(n_scan_keys * sizeof(IndexRuntimeKeyInfo)); + array_keys = (IndexArrayKeyInfo *) + palloc0(n_scan_keys * sizeof(IndexArrayKeyInfo)); + n_runtime_keys = 0; + n_array_keys = 0; /* * for each opclause in the given qual, convert each qual's opclause into @@ -536,122 +671,171 @@ ExecIndexBuildScanKeys(PlanState *planstate, List *quals, strategy_cell = list_head(strategies); subtype_cell = list_head(subtypes); - for (j = 0; j < n_keys; j++) + for (j = 0; j < n_scan_keys; j++) { - OpExpr *clause; /* one clause of index qual */ - Expr *leftop; /* expr on lhs of operator */ - Expr *rightop; /* expr on rhs ... */ - int flags = 0; - AttrNumber varattno; /* att number used in scan */ + ScanKey this_scan_key = &scan_keys[j]; + Expr *clause; /* one clause of index qual */ + RegProcedure opfuncid; /* operator proc id used in scan */ StrategyNumber strategy; /* op's strategy number */ Oid subtype; /* op's strategy subtype */ - RegProcedure opfuncid; /* operator proc id used in scan */ - Datum scanvalue; /* value used in scan (if const) */ + Expr *leftop; /* expr on lhs of operator */ + Expr *rightop; /* expr on rhs ... */ + AttrNumber varattno; /* att number used in scan */ /* * extract clause information from the qualification */ - clause = (OpExpr *) lfirst(qual_cell); + clause = (Expr *) lfirst(qual_cell); qual_cell = lnext(qual_cell); strategy = lfirst_int(strategy_cell); strategy_cell = lnext(strategy_cell); subtype = lfirst_oid(subtype_cell); subtype_cell = lnext(subtype_cell); - if (!IsA(clause, OpExpr)) - elog(ERROR, "indexqual is not an OpExpr"); - - opfuncid = clause->opfuncid; - - /* - * Here we figure out the contents of the index qual. The usual case - * is (var op const) which means we form a scan key for the attribute - * listed in the var node and use the value of the const as comparison - * data. - * - * If we don't have a const node, it means our scan key is a function - * of information obtained during the execution of the plan, in which - * case we need to recalculate the index scan key at run time. Hence, - * we set have_runtime_keys to true and place the appropriate - * subexpression in run_keys. The corresponding scan key values are - * recomputed at run time. - */ - run_keys[j] = NULL; - - /* - * determine information in leftop - */ - leftop = (Expr *) get_leftop((Expr *) clause); - - if (leftop && IsA(leftop, RelabelType)) - leftop = ((RelabelType *) leftop)->arg; - - Assert(leftop != NULL); - - if (!(IsA(leftop, Var) && - var_is_rel((Var *) leftop))) - elog(ERROR, "indexqual doesn't have key on left side"); - - varattno = ((Var *) leftop)->varattno; - - /* - * now determine information in rightop - */ - rightop = (Expr *) get_rightop((Expr *) clause); - - if (rightop && IsA(rightop, RelabelType)) - rightop = ((RelabelType *) rightop)->arg; - - Assert(rightop != NULL); - - if (IsA(rightop, Const)) + if (IsA(clause, OpExpr)) { + /* indexkey op const or indexkey op expression */ + int flags = 0; + Datum scanvalue; + + opfuncid = ((OpExpr *) clause)->opfuncid; + /* - * if the rightop is a const node then it means it identifies the - * value to place in our scan key. + * leftop should be the index key Var, possibly relabeled */ - scanvalue = ((Const *) rightop)->constvalue; - if (((Const *) rightop)->constisnull) - flags |= SK_ISNULL; + leftop = (Expr *) get_leftop(clause); + + if (leftop && IsA(leftop, RelabelType)) + leftop = ((RelabelType *) leftop)->arg; + + Assert(leftop != NULL); + + if (!(IsA(leftop, Var) && + var_is_rel((Var *) leftop))) + elog(ERROR, "indexqual doesn't have key on left side"); + + varattno = ((Var *) leftop)->varattno; + + /* + * rightop is the constant or variable comparison value + */ + rightop = (Expr *) get_rightop(clause); + + if (rightop && IsA(rightop, RelabelType)) + rightop = ((RelabelType *) rightop)->arg; + + Assert(rightop != NULL); + + if (IsA(rightop, Const)) + { + /* OK, simple constant comparison value */ + scanvalue = ((Const *) rightop)->constvalue; + if (((Const *) rightop)->constisnull) + flags |= SK_ISNULL; + } + else + { + /* Need to treat this one as a runtime key */ + runtime_keys[n_runtime_keys].scan_key = this_scan_key; + runtime_keys[n_runtime_keys].key_expr = + ExecInitExpr(rightop, planstate); + n_runtime_keys++; + scanvalue = (Datum) 0; + } + + /* + * initialize the scan key's fields appropriately + */ + ScanKeyEntryInitialize(this_scan_key, + flags, + varattno, /* attribute number to scan */ + strategy, /* op's strategy */ + subtype, /* strategy subtype */ + opfuncid, /* reg proc to use */ + scanvalue); /* constant */ + } + else if (IsA(clause, ScalarArrayOpExpr)) + { + /* indexkey op ANY (array-expression) */ + ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause; + + Assert(saop->useOr); + opfuncid = saop->opfuncid; + + /* + * leftop should be the index key Var, possibly relabeled + */ + leftop = (Expr *) linitial(saop->args); + + if (leftop && IsA(leftop, RelabelType)) + leftop = ((RelabelType *) leftop)->arg; + + Assert(leftop != NULL); + + if (!(IsA(leftop, Var) && + var_is_rel((Var *) leftop))) + elog(ERROR, "indexqual doesn't have key on left side"); + + varattno = ((Var *) leftop)->varattno; + + /* + * rightop is the constant or variable array value + */ + rightop = (Expr *) lsecond(saop->args); + + if (rightop && IsA(rightop, RelabelType)) + rightop = ((RelabelType *) rightop)->arg; + + Assert(rightop != NULL); + + array_keys[n_array_keys].scan_key = this_scan_key; + array_keys[n_array_keys].array_expr = + ExecInitExpr(rightop, planstate); + /* the remaining fields were zeroed by palloc0 */ + n_array_keys++; + + /* + * initialize the scan key's fields appropriately + */ + ScanKeyEntryInitialize(this_scan_key, + 0, /* flags */ + varattno, /* attribute number to scan */ + strategy, /* op's strategy */ + subtype, /* strategy subtype */ + opfuncid, /* reg proc to use */ + (Datum) 0); /* constant */ } else - { - /* - * otherwise, the rightop contains an expression evaluable at - * runtime to figure out the value to place in our scan key. - */ - have_runtime_keys = true; - run_keys[j] = ExecInitExpr(rightop, planstate); - scanvalue = (Datum) 0; - } - - /* - * initialize the scan key's fields appropriately - */ - ScanKeyEntryInitialize(&scan_keys[j], - flags, - varattno, /* attribute number to scan */ - strategy, /* op's strategy */ - subtype, /* strategy subtype */ - opfuncid, /* reg proc to use */ - scanvalue); /* constant */ + elog(ERROR, "unsupported indexqual type: %d", + (int) nodeTag(clause)); } - /* If no runtime keys, get rid of speculatively-allocated array */ - if (run_keys && !have_runtime_keys) + /* Get rid of any unused arrays */ + if (n_runtime_keys == 0) { - pfree(run_keys); - run_keys = NULL; + pfree(runtime_keys); + runtime_keys = NULL; + } + if (n_array_keys == 0) + { + pfree(array_keys); + array_keys = NULL; } /* - * Return the info to our caller. + * Return info to our caller. */ - *numScanKeys = n_keys; *scanKeys = scan_keys; - *runtimeKeyInfo = run_keys; - - return have_runtime_keys; + *numScanKeys = n_scan_keys; + *runtimeKeys = runtime_keys; + *numRuntimeKeys = n_runtime_keys; + if (arrayKeys) + { + *arrayKeys = array_keys; + *numArrayKeys = n_array_keys; + } + else if (n_array_keys != 0) + elog(ERROR, "ScalarArrayOpExpr index qual found where not allowed"); } int diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c index 9a4990898e..eba6bf1d14 100644 --- a/src/backend/optimizer/path/clausesel.c +++ b/src/backend/optimizer/path/clausesel.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/clausesel.c,v 1.75 2005/10/15 02:49:19 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/path/clausesel.c,v 1.76 2005/11/25 19:47:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -624,12 +624,45 @@ clause_selectivity(PlannerInfo *root, */ s1 = (Selectivity) 0.5; } - else if (IsA(clause, DistinctExpr) || - IsA(clause, ScalarArrayOpExpr)) + else if (IsA(clause, DistinctExpr)) { /* can we do better? */ s1 = (Selectivity) 0.5; } + else if (IsA(clause, ScalarArrayOpExpr)) + { + /* First, decide if it's a join clause, same as for OpExpr */ + bool is_join_clause; + + if (varRelid != 0) + { + /* + * If we are considering a nestloop join then all clauses are + * restriction clauses, since we are only interested in the one + * relation. + */ + is_join_clause = false; + } + else + { + /* + * Otherwise, it's a join if there's more than one relation used. + * We can optimize this calculation if an rinfo was passed. + */ + if (rinfo) + is_join_clause = (bms_membership(rinfo->clause_relids) == + BMS_MULTIPLE); + else + is_join_clause = (NumRelids(clause) > 1); + } + + /* Use node specific selectivity calculation function */ + s1 = scalararraysel(root, + (ScalarArrayOpExpr *) clause, + is_join_clause, + varRelid, + jointype); + } else if (IsA(clause, NullTest)) { /* Use node specific selectivity calculation function */ diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index 6f3157a23f..fde8945568 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.193 2005/11/22 18:17:12 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.194 2005/11/25 19:47:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -28,6 +28,7 @@ #include "optimizer/paths.h" #include "optimizer/predtest.h" #include "optimizer/restrictinfo.h" +#include "optimizer/var.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -40,9 +41,6 @@ */ #define DoneMatchingIndexKeys(classes) (classes[0] == InvalidOid) -#define is_indexable_operator(clause,opclass,indexkey_on_left) \ - (indexable_operator(clause,opclass,indexkey_on_left) != InvalidOid) - #define IsBooleanOpclass(opclass) \ ((opclass) == BOOL_BTREE_OPS_OID || (opclass) == BOOL_HASH_OPS_OID) @@ -50,16 +48,18 @@ static List *find_usable_indexes(PlannerInfo *root, RelOptInfo *rel, List *clauses, List *outer_clauses, bool istoplevel, bool isjoininner, - Relids outer_relids); + Relids outer_relids, + SaOpControl saop_control); static Path *choose_bitmap_and(PlannerInfo *root, RelOptInfo *rel, List *paths); static int bitmap_path_comparator(const void *a, const void *b); static Cost bitmap_and_cost_est(PlannerInfo *root, RelOptInfo *rel, List *paths); static bool match_clause_to_indexcol(IndexOptInfo *index, int indexcol, Oid opclass, RestrictInfo *rinfo, - Relids outer_relids); -static Oid indexable_operator(Expr *clause, Oid opclass, - bool indexkey_on_left); + Relids outer_relids, + SaOpControl saop_control); +static bool is_indexable_operator(Oid expr_op, Oid opclass, + bool indexkey_on_left); static Relids indexable_outerrelids(RelOptInfo *rel); static bool matches_any_index(RestrictInfo *rinfo, RelOptInfo *rel, Relids outer_relids); @@ -150,7 +150,7 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel) */ indexpaths = find_usable_indexes(root, rel, rel->baserestrictinfo, NIL, - true, false, NULL); + true, false, NULL, SAOP_FORBID); /* * We can submit them all to add_path. (This generates access paths for @@ -228,6 +228,7 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel) * given clauses are join clauses) * 'outer_relids' identifies the outer side of the join (pass NULL * if not isjoininner) + * 'saop_control' indicates whether ScalarArrayOpExpr clauses can be used * * Note: check_partial_indexes() must have been run previously. *---------- @@ -236,7 +237,8 @@ static List * find_usable_indexes(PlannerInfo *root, RelOptInfo *rel, List *clauses, List *outer_clauses, bool istoplevel, bool isjoininner, - Relids outer_relids) + Relids outer_relids, + SaOpControl saop_control) { List *result = NIL; List *all_clauses = NIL; /* not computed till needed */ @@ -267,6 +269,10 @@ find_usable_indexes(PlannerInfo *root, RelOptInfo *rel, * predOK index to an arm of an OR, which would be a legal but * pointlessly inefficient plan. (A better plan will be generated by * just scanning the predOK index alone, no OR.) + * + * If saop_control is SAOP_REQUIRE and istoplevel is false, the caller + * is only interested in indexquals involving ScalarArrayOps, so don't + * set useful_predicate to true. */ useful_predicate = false; if (index->indpred != NIL) @@ -292,7 +298,8 @@ find_usable_indexes(PlannerInfo *root, RelOptInfo *rel, if (!predicate_implied_by(index->indpred, all_clauses)) continue; /* can't use it at all */ - if (!predicate_implied_by(index->indpred, outer_clauses)) + if (saop_control != SAOP_REQUIRE && + !predicate_implied_by(index->indpred, outer_clauses)) useful_predicate = true; } } @@ -300,12 +307,14 @@ find_usable_indexes(PlannerInfo *root, RelOptInfo *rel, /* * 1. Match the index against the available restriction clauses. * found_clause is set true only if at least one of the current - * clauses was used. + * clauses was used (and, if saop_control is SAOP_REQUIRE, it + * has to have been a ScalarArrayOpExpr clause). */ restrictclauses = group_clauses_by_indexkey(index, clauses, outer_clauses, outer_relids, + saop_control, &found_clause); /* @@ -380,9 +389,9 @@ find_usable_indexes(PlannerInfo *root, RelOptInfo *rel, /* * generate_bitmap_or_paths - * Look through the list of clauses to find OR clauses, and generate - * a BitmapOrPath for each one we can handle that way. Return a list - * of the generated BitmapOrPaths. + * Look through the list of clauses to find OR clauses and + * ScalarArrayOpExpr clauses, and generate a BitmapOrPath for each one + * we can handle that way. Return a list of the generated BitmapOrPaths. * * outer_clauses is a list of additional clauses that can be assumed true * for the purpose of generating indexquals, but are not to be searched for @@ -396,6 +405,7 @@ generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel, { List *result = NIL; List *all_clauses; + bool have_saop = false; ListCell *l; /* @@ -412,9 +422,16 @@ generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel, ListCell *j; Assert(IsA(rinfo, RestrictInfo)); - /* Ignore RestrictInfos that aren't ORs */ + /* + * In this loop we ignore RestrictInfos that aren't ORs; but take + * note of ScalarArrayOpExpr for later. + */ if (!restriction_is_or_clause(rinfo)) + { + if (IsA(rinfo->clause, ScalarArrayOpExpr)) + have_saop = true; continue; + } /* * We must be able to match at least one index to each of the arms of @@ -436,7 +453,8 @@ generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel, all_clauses, false, isjoininner, - outer_relids); + outer_relids, + SAOP_ALLOW); /* Recurse in case there are sub-ORs */ indlist = list_concat(indlist, generate_bitmap_or_paths(root, rel, @@ -454,7 +472,8 @@ generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel, all_clauses, false, isjoininner, - outer_relids); + outer_relids, + SAOP_ALLOW); } /* @@ -486,6 +505,29 @@ generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel, } } + /* + * If we saw any top-level ScalarArrayOpExpr clauses, see if we can + * generate a bitmap index path that uses those but not any OR clauses. + */ + if (have_saop) + { + List *pathlist; + Path *bitmapqual; + + pathlist = find_usable_indexes(root, rel, + clauses, + outer_clauses, + false, + isjoininner, + outer_relids, + SAOP_REQUIRE); + if (pathlist != NIL) + { + bitmapqual = (Path *) create_bitmap_or_path(root, rel, pathlist); + result = lappend(result, bitmapqual); + } + } + return result; } @@ -526,7 +568,8 @@ choose_bitmap_and(PlannerInfo *root, RelOptInfo *rel, List *paths) * * We also make some effort to detect directly redundant input paths, as * can happen if there are multiple possibly usable indexes. For this we - * look only at plain IndexPath inputs, not at sub-OR clauses. And we + * look only at plain IndexPath and single-element BitmapOrPath inputs + * (the latter can arise in the presence of ScalarArrayOpExpr quals). We * consider an index redundant if all its index conditions were already * used by earlier indexes. (We could use predicate_implied_by to have a * more intelligent, but much more expensive, check --- but in most cases @@ -555,10 +598,17 @@ choose_bitmap_and(PlannerInfo *root, RelOptInfo *rel, List *paths) paths = list_make1(patharray[0]); costsofar = bitmap_and_cost_est(root, rel, paths); + qualsofar = NIL; if (IsA(patharray[0], IndexPath)) qualsofar = list_copy(((IndexPath *) patharray[0])->indexclauses); - else - qualsofar = NIL; + else if (IsA(patharray[0], BitmapOrPath)) + { + List *orquals = ((BitmapOrPath *) patharray[0])->bitmapquals; + + if (list_length(orquals) == 1 && + IsA(linitial(orquals), IndexPath)) + qualsofar = list_copy(((IndexPath *) linitial(orquals))->indexclauses); + } lastcell = list_head(paths); /* for quick deletions */ for (i = 1; i < npaths; i++) @@ -573,6 +623,16 @@ choose_bitmap_and(PlannerInfo *root, RelOptInfo *rel, List *paths) if (list_difference_ptr(newqual, qualsofar) == NIL) continue; /* redundant */ } + else if (IsA(newpath, BitmapOrPath)) + { + List *orquals = ((BitmapOrPath *) newpath)->bitmapquals; + + if (list_length(orquals) == 1 && + IsA(linitial(orquals), IndexPath)) + newqual = ((IndexPath *) linitial(orquals))->indexclauses; + if (list_difference_ptr(newqual, qualsofar) == NIL) + continue; /* redundant */ + } paths = lappend(paths, newpath); newcost = bitmap_and_cost_est(root, rel, paths); @@ -665,6 +725,10 @@ bitmap_and_cost_est(PlannerInfo *root, RelOptInfo *rel, List *paths) * outer_relids determines what Vars will be allowed on the other side * of a possible index qual; see match_clause_to_indexcol(). * + * 'saop_control' indicates whether ScalarArrayOpExpr clauses can be used. + * When it's SAOP_REQUIRE, *found_clause is set TRUE only if we used at least + * one ScalarArrayOpExpr from the current clauses list. + * * If the index has amoptionalkey = false, we give up and return NIL when * there are no restriction clauses matching the first index key. Otherwise, * we return NIL if there are no restriction clauses matching any index key. @@ -682,6 +746,7 @@ List * group_clauses_by_indexkey(IndexOptInfo *index, List *clauses, List *outer_clauses, Relids outer_relids, + SaOpControl saop_control, bool *found_clause) { List *clausegroup_list = NIL; @@ -710,10 +775,13 @@ group_clauses_by_indexkey(IndexOptInfo *index, indexcol, curClass, rinfo, - outer_relids)) + outer_relids, + saop_control)) { clausegroup = list_append_unique_ptr(clausegroup, rinfo); - *found_clause = true; + if (saop_control != SAOP_REQUIRE || + IsA(rinfo->clause, ScalarArrayOpExpr)) + *found_clause = true; } } @@ -727,7 +795,8 @@ group_clauses_by_indexkey(IndexOptInfo *index, indexcol, curClass, rinfo, - outer_relids)) + outer_relids, + saop_control)) { clausegroup = list_append_unique_ptr(clausegroup, rinfo); found_outer_clause = true; @@ -785,6 +854,11 @@ group_clauses_by_indexkey(IndexOptInfo *index, * We do not actually do the commuting here, but we check whether a * suitable commutator operator is available. * + * It is also possible to match ScalarArrayOpExpr clauses to indexes, when + * the clause is of the form "indexkey op ANY (arrayconst)". Since the + * executor can only handle these in the context of bitmap index scans, + * our caller specifies whether to allow these or not. + * * For boolean indexes, it is also possible to match the clause directly * to the indexkey; or perhaps the clause is (NOT indexkey). * @@ -792,6 +866,7 @@ group_clauses_by_indexkey(IndexOptInfo *index, * 'indexcol' is a column number of 'index' (counting from 0). * 'opclass' is the corresponding operator class. * 'rinfo' is the clause to be tested (as a RestrictInfo node). + * 'saop_control' indicates whether ScalarArrayOpExpr clauses can be used. * * Returns true if the clause can be used with this index key. * @@ -803,11 +878,16 @@ match_clause_to_indexcol(IndexOptInfo *index, int indexcol, Oid opclass, RestrictInfo *rinfo, - Relids outer_relids) + Relids outer_relids, + SaOpControl saop_control) { Expr *clause = rinfo->clause; Node *leftop, *rightop; + Relids left_relids; + Relids right_relids; + Oid expr_op; + bool plain_op; /* First check for boolean-index cases. */ if (IsBooleanOpclass(opclass)) @@ -816,12 +896,37 @@ match_clause_to_indexcol(IndexOptInfo *index, return true; } - /* Else clause must be a binary opclause. */ - if (!is_opclause(clause)) - return false; - leftop = get_leftop(clause); - rightop = get_rightop(clause); - if (!leftop || !rightop) + /* + * Clause must be a binary opclause, or possibly a ScalarArrayOpExpr + * (which is always binary, by definition). + */ + if (is_opclause(clause)) + { + leftop = get_leftop(clause); + rightop = get_rightop(clause); + if (!leftop || !rightop) + return false; + left_relids = rinfo->left_relids; + right_relids = rinfo->right_relids; + expr_op = ((OpExpr *) clause)->opno; + plain_op = true; + } + else if (saop_control != SAOP_FORBID && + clause && IsA(clause, ScalarArrayOpExpr)) + { + ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause; + + /* We only accept ANY clauses, not ALL */ + if (!saop->useOr) + return false; + leftop = (Node *) linitial(saop->args); + rightop = (Node *) lsecond(saop->args); + left_relids = NULL; /* not actually needed */ + right_relids = pull_varnos(rightop); + expr_op = saop->opno; + plain_op = false; + } + else return false; /* @@ -829,26 +934,28 @@ match_clause_to_indexcol(IndexOptInfo *index, * (constant operator indexkey). See above notes about const-ness. */ if (match_index_to_operand(leftop, indexcol, index) && - bms_is_subset(rinfo->right_relids, outer_relids) && + bms_is_subset(right_relids, outer_relids) && !contain_volatile_functions(rightop)) { - if (is_indexable_operator(clause, opclass, true)) + if (is_indexable_operator(expr_op, opclass, true)) return true; /* * If we didn't find a member of the index's opclass, see whether it * is a "special" indexable operator. */ - if (match_special_index_operator(clause, opclass, true)) + if (plain_op && + match_special_index_operator(clause, opclass, true)) return true; return false; } - if (match_index_to_operand(rightop, indexcol, index) && - bms_is_subset(rinfo->left_relids, outer_relids) && + if (plain_op && + match_index_to_operand(rightop, indexcol, index) && + bms_is_subset(left_relids, outer_relids) && !contain_volatile_functions(leftop)) { - if (is_indexable_operator(clause, opclass, false)) + if (is_indexable_operator(expr_op, opclass, false)) return true; /* @@ -864,36 +971,26 @@ match_clause_to_indexcol(IndexOptInfo *index, } /* - * indexable_operator - * Does a binary opclause contain an operator matching the index opclass? + * is_indexable_operator + * Does the operator match the specified index opclass? * * If the indexkey is on the right, what we actually want to know * is whether the operator has a commutator operator that matches - * the index's opclass. - * - * Returns the OID of the matching operator, or InvalidOid if no match. - * (Formerly, this routine might return a binary-compatible operator - * rather than the original one, but that kluge is history.) + * the opclass. */ -static Oid -indexable_operator(Expr *clause, Oid opclass, bool indexkey_on_left) +static bool +is_indexable_operator(Oid expr_op, Oid opclass, bool indexkey_on_left) { - Oid expr_op = ((OpExpr *) clause)->opno; - Oid commuted_op; - /* Get the commuted operator if necessary */ - if (indexkey_on_left) - commuted_op = expr_op; - else - commuted_op = get_commutator(expr_op); - if (commuted_op == InvalidOid) - return InvalidOid; + if (!indexkey_on_left) + { + expr_op = get_commutator(expr_op); + if (expr_op == InvalidOid) + return false; + } /* OK if the (commuted) operator is a member of the index's opclass */ - if (op_in_opclass(commuted_op, opclass)) - return expr_op; - - return InvalidOid; + return op_in_opclass(expr_op, opclass); } /**************************************************************************** @@ -1031,7 +1128,8 @@ matches_any_index(RestrictInfo *rinfo, RelOptInfo *rel, Relids outer_relids) indexcol, curClass, rinfo, - outer_relids)) + outer_relids, + SAOP_ALLOW)) return true; indexcol++; @@ -1137,16 +1235,17 @@ best_inner_indexscan(PlannerInfo *root, RelOptInfo *rel, /* * Find all the index paths that are usable for this join, except for - * stuff involving OR clauses. + * stuff involving OR and ScalarArrayOpExpr clauses. */ indexpaths = find_usable_indexes(root, rel, clause_list, NIL, false, true, - outer_relids); + outer_relids, + SAOP_FORBID); /* - * Generate BitmapOrPaths for any suitable OR-clauses present in the - * clause list. + * Generate BitmapOrPaths for any suitable OR-clauses or ScalarArrayOpExpr + * clauses present in the clause list. */ bitindexpaths = generate_bitmap_or_paths(root, rel, clause_list, NIL, @@ -1384,7 +1483,10 @@ identify_ignorable_ordering_cols(PlannerInfo *root, bool varonleft; bool ispc; - /* We know this clause passed match_clause_to_indexcol */ + /* + * We know this clause passed match_clause_to_indexcol as a + * toplevel clause; so it's not a ScalarArrayOp. + */ /* First check for boolean-index cases. */ if (IsBooleanOpclass(opclass)) @@ -1923,6 +2025,13 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups) } } + /* Next check for ScalarArrayOp cases */ + if (IsA(rinfo->clause, ScalarArrayOpExpr)) + { + resultquals = lappend(resultquals, rinfo); + continue; + } + resultquals = list_concat(resultquals, expand_indexqual_condition(rinfo, curClass)); @@ -2001,7 +2110,7 @@ expand_boolean_index_clause(Node *clause, /* * expand_indexqual_condition --- expand a single indexqual condition - * (other than a boolean-qual case) + * (other than a boolean-qual or ScalarArrayOp case) * * The input is a single RestrictInfo, the output a list of RestrictInfos */ diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 978419842e..3bd760fda3 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.203 2005/11/22 18:17:12 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.204 2005/11/25 19:47:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1069,17 +1069,28 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual, subindexquals = lappend(subindexquals, make_ands_explicit(subindexqual)); } - plan = (Plan *) make_bitmap_or(subplans); - plan->startup_cost = opath->path.startup_cost; - plan->total_cost = opath->path.total_cost; - plan->plan_rows = - clamp_row_est(opath->bitmapselectivity * opath->path.parent->tuples); - plan->plan_width = 0; /* meaningless */ + /* + * In the presence of ScalarArrayOpExpr quals, we might have built + * BitmapOrPaths with just one subpath; don't add an OR step. + */ + if (list_length(subplans) == 1) + { + plan = (Plan *) linitial(subplans); + } + else + { + plan = (Plan *) make_bitmap_or(subplans); + plan->startup_cost = opath->path.startup_cost; + plan->total_cost = opath->path.total_cost; + plan->plan_rows = + clamp_row_est(opath->bitmapselectivity * opath->path.parent->tuples); + plan->plan_width = 0; /* meaningless */ + } /* * If there were constant-TRUE subquals, the OR reduces to constant * TRUE. Also, avoid generating one-element ORs, which could happen - * due to redundancy elimination. + * due to redundancy elimination or ScalarArrayOpExpr quals. */ if (const_true_subqual) *qual = NIL; @@ -1531,18 +1542,14 @@ fix_indexqual_references(List *indexquals, IndexPath *index_path, foreach(l, indexquals) { RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); - OpExpr *clause; - OpExpr *newclause; + Expr *clause; + Oid clause_op; Oid opclass; int stratno; Oid stratsubtype; bool recheck; Assert(IsA(rinfo, RestrictInfo)); - clause = (OpExpr *) rinfo->clause; - if (!IsA(clause, OpExpr) || - list_length(clause->args) != 2) - elog(ERROR, "indexqual clause is not binary opclause"); /* * Make a copy that will become the fixed clause. @@ -1551,33 +1558,62 @@ fix_indexqual_references(List *indexquals, IndexPath *index_path, * is a subplan in the arguments of the opclause. So just do a full * copy. */ - newclause = (OpExpr *) copyObject((Node *) clause); + clause = (Expr *) copyObject((Node *) rinfo->clause); - /* - * Check to see if the indexkey is on the right; if so, commute the - * clause. The indexkey should be the side that refers to (only) the - * base relation. - */ - if (!bms_equal(rinfo->left_relids, index->rel->relids)) - CommuteClause(newclause); + if (IsA(clause, OpExpr)) + { + OpExpr *op = (OpExpr *) clause; - /* - * Now, determine which index attribute this is, change the indexkey - * operand as needed, and get the index opclass. - */ - linitial(newclause->args) = - fix_indexqual_operand(linitial(newclause->args), - index, - &opclass); + if (list_length(op->args) != 2) + elog(ERROR, "indexqual clause is not binary opclause"); - *fixed_indexquals = lappend(*fixed_indexquals, newclause); + /* + * Check to see if the indexkey is on the right; if so, commute + * the clause. The indexkey should be the side that refers to + * (only) the base relation. + */ + if (!bms_equal(rinfo->left_relids, index->rel->relids)) + CommuteClause(op); + + /* + * Now, determine which index attribute this is, change the + * indexkey operand as needed, and get the index opclass. + */ + linitial(op->args) = fix_indexqual_operand(linitial(op->args), + index, + &opclass); + clause_op = op->opno; + } + else if (IsA(clause, ScalarArrayOpExpr)) + { + ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause; + + /* Never need to commute... */ + + /* + * Now, determine which index attribute this is, change the + * indexkey operand as needed, and get the index opclass. + */ + linitial(saop->args) = fix_indexqual_operand(linitial(saop->args), + index, + &opclass); + clause_op = saop->opno; + } + else + { + elog(ERROR, "unsupported indexqual type: %d", + (int) nodeTag(clause)); + continue; /* keep compiler quiet */ + } + + *fixed_indexquals = lappend(*fixed_indexquals, clause); /* * Look up the (possibly commuted) operator in the operator class to * get its strategy numbers and the recheck indicator. This also * double-checks that we found an operator matching the index. */ - get_op_opclass_properties(newclause->opno, opclass, + get_op_opclass_properties(clause_op, opclass, &stratno, &stratsubtype, &recheck); *indexstrategy = lappend_int(*indexstrategy, stratno); diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c index f3a49b3006..f4a3c605c3 100644 --- a/src/backend/optimizer/plan/planagg.c +++ b/src/backend/optimizer/plan/planagg.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.11 2005/11/22 18:17:13 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.12 2005/11/25 19:47:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -347,6 +347,7 @@ build_minmax_path(PlannerInfo *root, RelOptInfo *rel, MinMaxAggInfo *info) index->rel->baserestrictinfo, NIL, NULL, + SAOP_FORBID, &found_clause); if (list_length(restrictclauses) < indexcol) diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c index dc135ae9b3..a14117032b 100644 --- a/src/backend/optimizer/util/restrictinfo.c +++ b/src/backend/optimizer/util/restrictinfo.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/restrictinfo.c,v 1.44 2005/11/22 18:17:15 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/restrictinfo.c,v 1.45 2005/11/25 19:47:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -171,7 +171,7 @@ make_restrictinfo_from_bitmapqual(Path *bitmapqual, /* * Avoid generating one-element ORs, which could happen due to - * redundancy elimination. + * redundancy elimination or ScalarArrayOpExpr quals. */ if (list_length(withris) <= 1) result = withris; diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index 9ff98f05cb..a92ff26260 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -15,7 +15,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/selfuncs.c,v 1.193 2005/11/22 18:17:23 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/selfuncs.c,v 1.194 2005/11/25 19:47:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1299,6 +1299,173 @@ nulltestsel(PlannerInfo *root, NullTestType nulltesttype, return (Selectivity) selec; } +/* + * scalararraysel - Selectivity of ScalarArrayOpExpr Node. + */ +Selectivity +scalararraysel(PlannerInfo *root, + ScalarArrayOpExpr *clause, + bool is_join_clause, + int varRelid, JoinType jointype) +{ + Oid operator = clause->opno; + bool useOr = clause->useOr; + Node *leftop; + Node *rightop; + RegProcedure oprsel; + FmgrInfo oprselproc; + Datum selarg4; + Selectivity s1; + + /* + * First, look up the underlying operator's selectivity estimator. + * Punt if it hasn't got one. + */ + if (is_join_clause) + { + oprsel = get_oprjoin(operator); + selarg4 = Int16GetDatum(jointype); + + } + else + { + oprsel = get_oprrest(operator); + selarg4 = Int32GetDatum(varRelid); + } + if (!oprsel) + return (Selectivity) 0.5; + fmgr_info(oprsel, &oprselproc); + + /* + * We consider three cases: + * + * 1. rightop is an Array constant: deconstruct the array, apply the + * operator's selectivity function for each array element, and merge + * the results in the same way that clausesel.c does for AND/OR + * combinations. + * + * 2. rightop is an ARRAY[] construct: apply the operator's selectivity + * function for each element of the ARRAY[] construct, and merge. + * + * 3. otherwise, make a guess ... + */ + Assert(list_length(clause->args) == 2); + leftop = (Node *) linitial(clause->args); + rightop = (Node *) lsecond(clause->args); + + if (rightop && IsA(rightop, Const)) + { + Datum arraydatum = ((Const *) rightop)->constvalue; + bool arrayisnull = ((Const *) rightop)->constisnull; + ArrayType *arrayval; + int16 elmlen; + bool elmbyval; + char elmalign; + int num_elems; + Datum *elem_values; + bool *elem_nulls; + int i; + + if (arrayisnull) /* qual can't succeed if null array */ + return (Selectivity) 0.0; + arrayval = DatumGetArrayTypeP(arraydatum); + get_typlenbyvalalign(ARR_ELEMTYPE(arrayval), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(arrayval, + ARR_ELEMTYPE(arrayval), + elmlen, elmbyval, elmalign, + &elem_values, &elem_nulls, &num_elems); + s1 = useOr ? 0.0 : 1.0; + for (i = 0; i < num_elems; i++) + { + List *args; + Selectivity s2; + + args = list_make2(leftop, + makeConst(ARR_ELEMTYPE(arrayval), + elmlen, + elem_values[i], + elem_nulls[i], + elmbyval)); + s2 = DatumGetFloat8(FunctionCall4(&oprselproc, + PointerGetDatum(root), + ObjectIdGetDatum(operator), + PointerGetDatum(args), + selarg4)); + if (useOr) + s1 = s1 + s2 - s1 * s2; + else + s1 = s1 * s2; + } + } + else if (rightop && IsA(rightop, ArrayExpr) && + !((ArrayExpr *) rightop)->multidims) + { + ArrayExpr *arrayexpr = (ArrayExpr *) rightop; + int16 elmlen; + bool elmbyval; + ListCell *l; + + get_typlenbyval(arrayexpr->element_typeid, + &elmlen, &elmbyval); + s1 = useOr ? 0.0 : 1.0; + foreach(l, arrayexpr->elements) + { + List *args; + Selectivity s2; + + args = list_make2(leftop, lfirst(l)); + s2 = DatumGetFloat8(FunctionCall4(&oprselproc, + PointerGetDatum(root), + ObjectIdGetDatum(operator), + PointerGetDatum(args), + selarg4)); + if (useOr) + s1 = s1 + s2 - s1 * s2; + else + s1 = s1 * s2; + } + } + else + { + CaseTestExpr *dummyexpr; + List *args; + Selectivity s2; + int i; + + /* + * We need a dummy rightop to pass to the operator selectivity + * routine. It can be pretty much anything that doesn't look like + * a constant; CaseTestExpr is a convenient choice. + */ + dummyexpr = makeNode(CaseTestExpr); + dummyexpr->typeId = get_element_type(exprType(rightop)); + dummyexpr->typeMod = -1; + args = list_make2(leftop, dummyexpr); + s2 = DatumGetFloat8(FunctionCall4(&oprselproc, + PointerGetDatum(root), + ObjectIdGetDatum(operator), + PointerGetDatum(args), + selarg4)); + s1 = useOr ? 0.0 : 1.0; + /* + * Arbitrarily assume 10 elements in the eventual array value + */ + for (i = 0; i < 10; i++) + { + if (useOr) + s1 = s1 + s2 - s1 * s2; + else + s1 = s1 * s2; + } + } + + /* result should be in range, but make sure... */ + CLAMP_PROBABILITY(s1); + + return s1; +} + /* * eqjoinsel - Join selectivity of "=" */ @@ -4330,6 +4497,7 @@ btcostestimate(PG_FUNCTION_ARGS) List *indexBoundQuals; int indexcol; bool eqQualHere; + bool found_saop; ListCell *l; /* @@ -4341,26 +4509,52 @@ btcostestimate(PG_FUNCTION_ARGS) * for estimating numIndexTuples. So we must examine the given indexQuals * to find out which ones count as boundary quals. We rely on the * knowledge that they are given in index column order. + * + * If there's a ScalarArrayOpExpr in the quals, we'll actually perform + * N index scans not one, but the ScalarArrayOpExpr's operator can be + * considered to act the same as it normally does. */ indexBoundQuals = NIL; indexcol = 0; eqQualHere = false; + found_saop = false; foreach(l, indexQuals) { RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); Expr *clause; + Node *leftop, + *rightop; Oid clause_op; int op_strategy; Assert(IsA(rinfo, RestrictInfo)); clause = rinfo->clause; - Assert(IsA(clause, OpExpr)); - clause_op = ((OpExpr *) clause)->opno; - if (match_index_to_operand(get_leftop(clause), indexcol, index)) + if (IsA(clause, OpExpr)) + { + leftop = get_leftop(clause); + rightop = get_rightop(clause); + clause_op = ((OpExpr *) clause)->opno; + } + else if (IsA(clause, ScalarArrayOpExpr)) + { + ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause; + + leftop = (Node *) linitial(saop->args); + rightop = (Node *) lsecond(saop->args); + clause_op = saop->opno; + found_saop = true; + } + else + { + elog(ERROR, "unsupported indexqual type: %d", + (int) nodeTag(clause)); + continue; /* keep compiler quiet */ + } + if (match_index_to_operand(leftop, indexcol, index)) { /* clause_op is correct */ } - else if (match_index_to_operand(get_rightop(clause), indexcol, index)) + else if (match_index_to_operand(rightop, indexcol, index)) { /* Must flip operator to get the opclass member */ clause_op = get_commutator(clause_op); @@ -4372,12 +4566,11 @@ btcostestimate(PG_FUNCTION_ARGS) break; /* done if no '=' qual for indexcol */ indexcol++; eqQualHere = false; - if (match_index_to_operand(get_leftop(clause), indexcol, index)) + if (match_index_to_operand(leftop, indexcol, index)) { /* clause_op is correct */ } - else if (match_index_to_operand(get_rightop(clause), - indexcol, index)) + else if (match_index_to_operand(rightop, indexcol, index)) { /* Must flip operator to get the opclass member */ clause_op = get_commutator(clause_op); @@ -4401,7 +4594,10 @@ btcostestimate(PG_FUNCTION_ARGS) * just assume numIndexTuples = 1 and skip the expensive * clauselist_selectivity calculations. */ - if (index->unique && indexcol == index->ncolumns - 1 && eqQualHere) + if (index->unique && + indexcol == index->ncolumns - 1 && + eqQualHere && + !found_saop) numIndexTuples = 1.0; else { @@ -4424,7 +4620,14 @@ btcostestimate(PG_FUNCTION_ARGS) * is that multiple columns dilute the importance of the first column's * ordering, but don't negate it entirely. Before 8.0 we divided the * correlation by the number of columns, but that seems too strong.) + * + * We can skip all this if we found a ScalarArrayOpExpr, because then + * the call must be for a bitmap index scan, and the caller isn't going + * to care what the index correlation is. */ + if (found_saop) + PG_RETURN_VOID(); + if (index->indexkeys[0] != 0) { /* Simple variable --- look to stats for the underlying table */ diff --git a/src/include/executor/nodeIndexscan.h b/src/include/executor/nodeIndexscan.h index 7f280c892e..21bb254f63 100644 --- a/src/include/executor/nodeIndexscan.h +++ b/src/include/executor/nodeIndexscan.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/executor/nodeIndexscan.h,v 1.24 2005/10/15 02:49:44 momjian Exp $ + * $PostgreSQL: pgsql/src/include/executor/nodeIndexscan.h,v 1.25 2005/11/25 19:47:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -25,13 +25,15 @@ extern void ExecIndexRestrPos(IndexScanState *node); extern void ExecIndexReScan(IndexScanState *node, ExprContext *exprCtxt); /* routines exported to share code with nodeBitmapIndexscan.c */ -extern bool ExecIndexBuildScanKeys(PlanState *planstate, List *quals, +extern void ExecIndexBuildScanKeys(PlanState *planstate, List *quals, List *strategies, List *subtypes, - ExprState ***runtimeKeyInfo, - ScanKey *scanKeys, int *numScanKeys); + ScanKey *scanKeys, int *numScanKeys, + IndexRuntimeKeyInfo **runtimeKeys, int *numRuntimeKeys, + IndexArrayKeyInfo **arrayKeys, int *numArrayKeys); extern void ExecIndexEvalRuntimeKeys(ExprContext *econtext, - ExprState **run_keys, - ScanKey scan_keys, - int n_keys); + IndexRuntimeKeyInfo *runtimeKeys, int numRuntimeKeys); +extern bool ExecIndexEvalArrayKeys(ExprContext *econtext, + IndexArrayKeyInfo *arrayKeys, int numArrayKeys); +extern bool ExecIndexAdvanceArrayKeys(IndexArrayKeyInfo *arrayKeys, int numArrayKeys); #endif /* NODEINDEXSCAN_H */ diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 63e864e463..f70847798e 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.141 2005/11/22 18:17:30 momjian Exp $ + * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.142 2005/11/25 19:47:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -871,16 +871,37 @@ typedef struct ScanState */ typedef ScanState SeqScanState; +/* + * These structs store information about index quals that don't have simple + * constant right-hand sides. See comments for ExecIndexBuildScanKeys() + * for discussion. + */ +typedef struct +{ + ScanKey scan_key; /* scankey to put value into */ + ExprState *key_expr; /* expr to evaluate to get value */ +} IndexRuntimeKeyInfo; + +typedef struct +{ + ScanKey scan_key; /* scankey to put value into */ + ExprState *array_expr; /* expr to evaluate to get array value */ + int next_elem; /* next array element to use */ + int num_elems; /* number of elems in current array value */ + Datum *elem_values; /* array of num_elems Datums */ + bool *elem_nulls; /* array of num_elems is-null flags */ +} IndexArrayKeyInfo; + /* ---------------- * IndexScanState information * * indexqualorig execution state for indexqualorig expressions * ScanKeys Skey structures to scan index rel * NumScanKeys number of Skey structs - * RuntimeKeyInfo array of exprstates for Skeys - * that will be evaluated at runtime - * RuntimeContext expr context for evaling runtime Skeys + * RuntimeKeys info about Skeys that must be evaluated at runtime + * NumRuntimeKeys number of RuntimeKeys structs * RuntimeKeysReady true if runtime Skeys have been computed + * RuntimeContext expr context for evaling runtime Skeys * RelationDesc index relation descriptor * ScanDesc index scan descriptor * ---------------- @@ -891,9 +912,10 @@ typedef struct IndexScanState List *indexqualorig; ScanKey iss_ScanKeys; int iss_NumScanKeys; - ExprState **iss_RuntimeKeyInfo; - ExprContext *iss_RuntimeContext; + IndexRuntimeKeyInfo *iss_RuntimeKeys; + int iss_NumRuntimeKeys; bool iss_RuntimeKeysReady; + ExprContext *iss_RuntimeContext; Relation iss_RelationDesc; IndexScanDesc iss_ScanDesc; } IndexScanState; @@ -904,10 +926,12 @@ typedef struct IndexScanState * result bitmap to return output into, or NULL * ScanKeys Skey structures to scan index rel * NumScanKeys number of Skey structs - * RuntimeKeyInfo array of exprstates for Skeys - * that will be evaluated at runtime - * RuntimeContext expr context for evaling runtime Skeys + * RuntimeKeys info about Skeys that must be evaluated at runtime + * NumRuntimeKeys number of RuntimeKeys structs + * ArrayKeys info about Skeys that come from ScalarArrayOpExprs + * NumArrayKeys number of ArrayKeys structs * RuntimeKeysReady true if runtime Skeys have been computed + * RuntimeContext expr context for evaling runtime Skeys * RelationDesc index relation descriptor * ScanDesc index scan descriptor * ---------------- @@ -918,9 +942,12 @@ typedef struct BitmapIndexScanState TIDBitmap *biss_result; ScanKey biss_ScanKeys; int biss_NumScanKeys; - ExprState **biss_RuntimeKeyInfo; - ExprContext *biss_RuntimeContext; + IndexRuntimeKeyInfo *biss_RuntimeKeys; + int biss_NumRuntimeKeys; + IndexArrayKeyInfo *biss_ArrayKeys; + int biss_NumArrayKeys; bool biss_RuntimeKeysReady; + ExprContext *biss_RuntimeContext; Relation biss_RelationDesc; IndexScanDesc biss_ScanDesc; } BitmapIndexScanState; diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 4020f4bf49..eba65c699c 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/optimizer/paths.h,v 1.88 2005/10/15 02:49:45 momjian Exp $ + * $PostgreSQL: pgsql/src/include/optimizer/paths.h,v 1.89 2005/11/25 19:47:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -34,6 +34,14 @@ extern void debug_print_rel(PlannerInfo *root, RelOptInfo *rel); * indxpath.c * routines to generate index paths */ +typedef enum +{ + /* Whether to use ScalarArrayOpExpr to build index qualifications */ + SAOP_FORBID, /* Do not use ScalarArrayOpExpr */ + SAOP_ALLOW, /* OK to use ScalarArrayOpExpr */ + SAOP_REQUIRE /* Require ScalarArrayOpExpr */ +} SaOpControl; + extern void create_index_paths(PlannerInfo *root, RelOptInfo *rel); extern List *generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel, List *clauses, List *outer_clauses, @@ -44,6 +52,7 @@ extern Path *best_inner_indexscan(PlannerInfo *root, RelOptInfo *rel, extern List *group_clauses_by_indexkey(IndexOptInfo *index, List *clauses, List *outer_clauses, Relids outer_relids, + SaOpControl saop_control, bool *found_clause); extern bool match_index_to_operand(Node *operand, int indexcol, IndexOptInfo *index); diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h index 7ba2dde1d9..2c021ad250 100644 --- a/src/include/utils/selfuncs.h +++ b/src/include/utils/selfuncs.h @@ -8,7 +8,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/selfuncs.h,v 1.25 2005/11/07 17:36:47 tgl Exp $ + * $PostgreSQL: pgsql/src/include/utils/selfuncs.h,v 1.26 2005/11/25 19:47:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -112,6 +112,10 @@ extern Selectivity booltestsel(PlannerInfo *root, BoolTestType booltesttype, Node *arg, int varRelid, JoinType jointype); extern Selectivity nulltestsel(PlannerInfo *root, NullTestType nulltesttype, Node *arg, int varRelid); +extern Selectivity scalararraysel(PlannerInfo *root, + ScalarArrayOpExpr *clause, + bool is_join_clause, + int varRelid, JoinType jointype); extern void mergejoinscansel(PlannerInfo *root, Node *clause, Selectivity *leftscan,