/* ---------------------------------------------------------------- * ExecScan * * Scans the relation using the 'access method' indicated and * returns the next qualifying tuple in the direction specified * in the global variable ExecDirection. * The access method returns the next tuple and ExecScan() is * responsible for checking the tuple returned against the qual-clause. * * A 'recheck method' must also be provided that can check an * arbitrary tuple of the relation against any qual conditions * that are implemented internal to the access method. * * Conditions: * -- the "cursor" maintained by the AMI is positioned at the tuple * returned previously. * * Initial States: * -- the relation indicated is opened for scanning so that the * "cursor" is positioned before the first qualifying tuple. * ---------------------------------------------------------------- */ TupleTableSlot * ExecScan(ScanState *node, ExecScanAccessMtd accessMtd, /* function returning a tuple */ ExecScanRecheckMtd recheckMtd) { ExprContext *econtext; List *qual; ProjectionInfo *projInfo; /* * Fetch data from node */ qual = node->ps.qual; projInfo = node->ps.ps_ProjInfo; econtext = node->ps.ps_ExprContext; /* * If we have neither a qual to check nor a projection to do, just skip * all the overhead and return the raw scan tuple. */ if (!qual && !projInfo) { ResetExprContext(econtext); return ExecScanFetch(node, accessMtd, recheckMtd); } /* * Reset per-tuple memory context to free any expression evaluation * storage allocated in the previous tuple cycle. */ ResetExprContext(econtext); /* * get a tuple from the access method. Loop until we obtain a tuple that * passes the qualification. */ for (;;) { TupleTableSlot *slot; CHECK_FOR_INTERRUPTS(); slot = ExecScanFetch(node, accessMtd, recheckMtd); /* * if the slot returned by the accessMtd contains NULL, then it means * there is nothing more to scan so we just return an empty slot, * being careful to use the projection result slot so it has correct * tupleDesc. */ if (TupIsNull(slot)) { if (projInfo) return ExecClearTuple(projInfo->pi_slot); else return slot; } /* * place the current tuple into the expr context */ econtext->ecxt_scantuple = slot; /* * check that the current tuple satisfies the qual-clause * * check for non-nil qual here to avoid a function call to ExecQual() * when the qual is nil ... saves only a few cycles, but they add up * ... */ if (!qual || ExecQual(qual, econtext, false)) { /* * Found a satisfactory scan tuple. */ if (projInfo) { /* * Form a projection tuple, store it in the result tuple slot * and return it. */ return ExecProject(projInfo); } else { /* * Here, we aren't projecting, so just return scan tuple. */ return slot; } } else InstrCountFiltered1(node, 1); /* * Tuple fails qual, so free per-tuple memory and try again. */ ResetExprContext(econtext); } }
/* * ExecFilterRecommend * * This function just borrows a tuple descriptor from the RecView, * but we create the data ourselves through various means. */ static TupleTableSlot* ExecFilterRecommend(RecScanState *recnode, ExecScanAccessMtd accessMtd, ExecScanRecheckMtd recheckMtd) { ExprContext *econtext; List *qual; ProjectionInfo *projInfo; ExprDoneCond isDone; TupleTableSlot *resultSlot; ScanState *node; AttributeInfo *attributes; node = recnode->subscan; attributes = (AttributeInfo*) recnode->attributes; /* * Fetch data from node */ qual = node->ps.qual; projInfo = node->ps.ps_ProjInfo; econtext = node->ps.ps_ExprContext; /* * Check to see if we're still projecting out tuples from a previous scan * tuple (because there is a function-returning-set in the projection * expressions). If so, try to project another one. */ if (node->ps.ps_TupFromTlist) { Assert(projInfo); /* can't get here if not projecting */ resultSlot = ExecProject(projInfo, &isDone); if (isDone == ExprMultipleResult) return resultSlot; /* Done with that source tuple... */ node->ps.ps_TupFromTlist = false; } /* * Reset per-tuple memory context to free any expression evaluation * storage allocated in the previous tuple cycle. Note this can't happen * until we're done projecting out tuples from a scan tuple. */ ResetExprContext(econtext); /* * get a tuple from the access method. Loop until we obtain a tuple that * passes the qualification. */ for (;;) { TupleTableSlot *slot; int natts, i, userID, userindex, itemID, itemindex; CHECK_FOR_INTERRUPTS(); slot = recnode->ss.ps.ps_ResultTupleSlot; /* The first thing we need to do is initialize our recommender * model and other things, if we haven't done so already. */ if (!recnode->initialized) InitializeRecommender(recnode); /* * If we've exhausted our item list, then we're totally * finished. We set a flag for this. It's possible that * we'll be in the inner loop of a join, through poor * planning, so we'll reset the appropriate data in case * we have to do this again, though our JoinRecommend * should assure this doesn't happen. */ if (recnode->finished) { recnode->finished = false; recnode->userNum = 0; recnode->itemNum = 0; return NULL; } /* We're only going to fetch one tuple and store its tuple * descriptor. We can use this tuple descriptor to make as * many new tuples as we want. */ if (recnode->base_slot == NULL) { slot = ExecRecFetch(node, accessMtd, recheckMtd); recnode->base_slot = CreateTupleDescCopy(slot->tts_tupleDescriptor); } /* Create a new slot to operate on. */ slot = MakeSingleTupleTableSlot(recnode->base_slot); slot->tts_isempty = false; /* * place the current tuple into the expr context */ econtext->ecxt_scantuple = slot; /* Mark all slots as usable. */ natts = slot->tts_tupleDescriptor->natts; for (i = 0; i < natts; i++) { /* Mark slot. */ slot->tts_values[i] = Int32GetDatum(0); slot->tts_isnull[i] = false; slot->tts_nvalid++; } /* While we're here, record what tuple attributes * correspond to our key columns. This will save * us unnecessary strcmp functions. */ if (recnode->useratt < 0) { for (i = 0; i < natts; i++) { char* col_name = slot->tts_tupleDescriptor->attrs[i]->attname.data; //printf("%s\n",col_name); if (strcmp(col_name,attributes->userkey) == 0) recnode->useratt = i; else if (strcmp(col_name,attributes->itemkey) == 0) recnode->itematt = i; else if (strcmp(col_name,attributes->eventval) == 0) recnode->eventatt = i; } } /* * We now have a problem: we need to create prediction structures * for a user before we do filtering, so that we can have a proper * item list. But we also need to filter before creating those * structures, so we don't end up taking forever with it. The * solution is to filter twice. */ userID = -1; itemID = -1; /* First, replace the user ID. */ userindex = recnode->userNum; userID = recnode->userList[userindex]; /* * We now have a blank tuple slot that we need to fill with data. * We have a working user ID, but not a valid item list. We'd like to * use the filter to determine if this is a good user, but we can't * do that without an item, in many cases. The solution is to add in * dummy items, then compare it against the filter. If a given user ID * doesn't make it past the filter with any item ID, then that user is * being filtered out, and we'll move on to the next. */ if (recnode->newUser) { recnode->fullItemNum = 0; itemindex = recnode->fullItemNum; itemID = recnode->fullItemList[itemindex]; slot->tts_values[recnode->useratt] = Int32GetDatum(userID); slot->tts_values[recnode->itematt] = Int32GetDatum(itemID); slot->tts_values[recnode->eventatt] = Int32GetDatum(-1); /* We have a preliminary slot - let's test it. */ while (qual && !ExecQual(qual, econtext, false)) { /* We failed the test. Try the next item. */ recnode->fullItemNum++; if (recnode->fullItemNum >= recnode->fullTotalItems) { /* If we've reached the last item, move onto the next user. * If we've reached the last user, we're done. */ InstrCountFiltered1(node, recnode->fullTotalItems); recnode->userNum++; recnode->newUser = true; recnode->fullItemNum = 0; if (recnode->userNum >= recnode->totalUsers) { recnode->userNum = 0; recnode->itemNum = 0; return NULL; } userindex = recnode->userNum; userID = recnode->userList[userindex]; } itemindex = recnode->fullItemNum; itemID = recnode->fullItemList[itemindex]; slot->tts_values[recnode->useratt] = Int32GetDatum(userID); slot->tts_values[recnode->itematt] = Int32GetDatum(itemID); } /* If we get here, then we found a user who will be actually * returned in the results. One quick reset here. */ recnode->fullItemNum = 0; } /* Mark the user ID and index. */ attributes->userID = userID; recnode->userindex = userindex; /* With the user ID determined, we need to investigate and see * if this is a new user. If so, attempt to create prediction * data structures, or report that this user is invalid. We have * to do this here, so we can establish the item list. */ if (recnode->newUser) { recnode->validUser = prepUserForRating(recnode,userID); recnode->newUser = false; } /* Now replace the item ID, if the user is valid. Otherwise, * leave the item ID as is, as it doesn't matter what it is. */ if (recnode->validUser) itemID = recnode->itemList[recnode->itemNum]; while (recnode->fullItemList[recnode->fullItemNum] < itemID) recnode->fullItemNum++; itemindex = recnode->fullItemNum; if (recnode->fullItemList[itemindex] > itemID) elog(ERROR, "critical item mismatch in ExecRecommend"); /* Plug in the data, marking those columns full. We also need to * mark the rating column with something temporary. */ slot->tts_values[recnode->useratt] = Int32GetDatum(userID); slot->tts_values[recnode->itematt] = Int32GetDatum(itemID); slot->tts_values[recnode->eventatt] = Int32GetDatum(-1); /* It's possible our filter criteria involves the RecScore somehow. * If that's the case, we need to calculate it before we do the * qual filtering. Also, if we're doing a JoinRecommend, we should * not calculate the RecScore in this node. In the current version * of RecDB, an OP_NOFILTER shouldn't be allowed. */ if (attributes->opType == OP_NOFILTER) applyRecScore(recnode, slot, itemID, itemindex); /* Move onto the next item, for next time. If we're doing a RecJoin, * though, we'll move onto the next user instead. */ recnode->itemNum++; if (recnode->itemNum >= recnode->totalItems || attributes->opType == OP_JOIN || attributes->opType == OP_GENERATEJOIN) { /* If we've reached the last item, move onto the next user. * If we've reached the last user, we're done. */ recnode->userNum++; recnode->newUser = true; recnode->itemNum = 0; recnode->fullItemNum = 0; if (recnode->userNum >= recnode->totalUsers) recnode->finished = true; } /* * check that the current tuple satisfies the qual-clause * * check for non-nil qual here to avoid a function call to ExecQual() * when the qual is nil ... saves only a few cycles, but they add up * ... */ if (!qual || ExecQual(qual, econtext, false)) { /* * If this is an invalid user, then we'll skip this tuple, * adding one to the filter count. */ if (!recnode->validUser) { InstrCountFiltered1(node, 1); ResetExprContext(econtext); ExecDropSingleTupleTableSlot(slot); continue; } /* * Found a satisfactory scan tuple. This is usually when * we will calculate and apply the RecScore. */ if (attributes->opType == OP_FILTER || attributes->opType == OP_GENERATE) applyRecScore(recnode, slot, itemID, itemindex); if (projInfo) { /* * Form a projection tuple, store it in the result tuple slot * and return it --- unless we find we can project no tuples * from this scan tuple, in which case continue scan. */ resultSlot = ExecProject(projInfo, &isDone); if (isDone != ExprEndResult) { node->ps.ps_TupFromTlist = (isDone == ExprMultipleResult); return resultSlot; } } else { /* * Here, we aren't projecting, so just return scan tuple. */ return slot; } } else InstrCountFiltered1(node, 1); /* * Tuple fails qual, so free per-tuple memory and try again. */ ResetExprContext(econtext); ExecDropSingleTupleTableSlot(slot); } }
/* * ExecGroup - * * Return one tuple for each group of matching input tuples. */ TupleTableSlot * ExecGroup(GroupState *node) { ExprContext *econtext; int numCols; AttrNumber *grpColIdx; TupleTableSlot *firsttupleslot; TupleTableSlot *outerslot; /* * get state info from node */ if (node->grp_done) return NULL; econtext = node->ss.ps.ps_ExprContext; numCols = ((Group *) node->ss.ps.plan)->numCols; grpColIdx = ((Group *) node->ss.ps.plan)->grpColIdx; /* * Check to see if we're still projecting out tuples from a previous group * tuple (because there is a function-returning-set in the projection * expressions). If so, try to project another one. */ if (node->ss.ps.ps_TupFromTlist) { TupleTableSlot *result; ExprDoneCond isDone; result = ExecProject(node->ss.ps.ps_ProjInfo, &isDone); if (isDone == ExprMultipleResult) return result; /* Done with that source tuple... */ node->ss.ps.ps_TupFromTlist = false; } /* * The ScanTupleSlot holds the (copied) first tuple of each group. */ firsttupleslot = node->ss.ss_ScanTupleSlot; /* * We need not call ResetExprContext here because execTuplesMatch will * reset the per-tuple memory context once per input tuple. */ /* * If first time through, acquire first input tuple and determine whether * to return it or not. */ if (TupIsNull(firsttupleslot)) { outerslot = ExecProcNode(outerPlanState(node)); if (TupIsNull(outerslot)) { /* empty input, so return nothing */ node->grp_done = TRUE; return NULL; } /* Copy tuple into firsttupleslot */ ExecCopySlot(firsttupleslot, outerslot); /* * Set it up as input for qual test and projection. The expressions * will access the input tuple as varno OUTER. */ econtext->ecxt_outertuple = firsttupleslot; /* * Check the qual (HAVING clause); if the group does not match, ignore * it and fall into scan loop. */ if (ExecQual(node->ss.ps.qual, econtext, false)) { /* * Form and return a projection tuple using the first input tuple. */ TupleTableSlot *result; ExprDoneCond isDone; result = ExecProject(node->ss.ps.ps_ProjInfo, &isDone); if (isDone != ExprEndResult) { node->ss.ps.ps_TupFromTlist = (isDone == ExprMultipleResult); return result; } } else InstrCountFiltered1(node, 1); } /* * This loop iterates once per input tuple group. At the head of the * loop, we have finished processing the first tuple of the group and now * need to scan over all the other group members. */ for (;;) { /* * Scan over all remaining tuples that belong to this group */ for (;;) { outerslot = ExecProcNode(outerPlanState(node)); if (TupIsNull(outerslot)) { /* no more groups, so we're done */ node->grp_done = TRUE; return NULL; } /* * Compare with first tuple and see if this tuple is of the same * group. If so, ignore it and keep scanning. */ if (!execTuplesMatch(firsttupleslot, outerslot, numCols, grpColIdx, node->eqfunctions, econtext->ecxt_per_tuple_memory)) break; } /* * We have the first tuple of the next input group. See if we want to * return it. */ /* Copy tuple, set up as input for qual test and projection */ ExecCopySlot(firsttupleslot, outerslot); econtext->ecxt_outertuple = firsttupleslot; /* * Check the qual (HAVING clause); if the group does not match, ignore * it and loop back to scan the rest of the group. */ if (ExecQual(node->ss.ps.qual, econtext, false)) { /* * Form and return a projection tuple using the first input tuple. */ TupleTableSlot *result; ExprDoneCond isDone; result = ExecProject(node->ss.ps.ps_ProjInfo, &isDone); if (isDone != ExprEndResult) { node->ss.ps.ps_TupFromTlist = (isDone == ExprMultipleResult); return result; } } else InstrCountFiltered1(node, 1); } }
/* * ExecIndexRecommend * * This function obtains data directly from the RecView, which we * assume is populated with predictions for this user. */ static TupleTableSlot* ExecIndexRecommend(RecScanState *recnode, ExecScanAccessMtd accessMtd, ExecScanRecheckMtd recheckMtd) { ExprContext *econtext; List *qual; ProjectionInfo *projInfo; ExprDoneCond isDone; TupleTableSlot *resultSlot; ScanState *node; AttributeInfo *attributes; node = recnode->subscan; attributes = (AttributeInfo*) recnode->attributes; /* * Fetch data from node */ qual = node->ps.qual; projInfo = node->ps.ps_ProjInfo; econtext = node->ps.ps_ExprContext; /* * Check to see if we're still projecting out tuples from a previous scan * tuple (because there is a function-returning-set in the projection * expressions). If so, try to project another one. */ if (node->ps.ps_TupFromTlist) { Assert(projInfo); /* can't get here if not projecting */ resultSlot = ExecProject(projInfo, &isDone); if (isDone == ExprMultipleResult) return resultSlot; /* Done with that source tuple... */ node->ps.ps_TupFromTlist = false; } /* * Reset per-tuple memory context to free any expression evaluation * storage allocated in the previous tuple cycle. Note this can't happen * until we're done projecting out tuples from a scan tuple. */ ResetExprContext(econtext); /* * get a tuple from the access method. Loop until we obtain a tuple that * passes the qualification. */ for (;;) { TupleTableSlot *slot; int userID; bool recQual = true; CHECK_FOR_INTERRUPTS(); slot = recnode->ss.ps.ps_ResultTupleSlot; /* If we're using the RecView, we're going to fetch a new * tuple every time. */ slot = ExecRecFetch(node, accessMtd, recheckMtd); /* If the slot is null now, then we've run out of tuples * to return, so we're done. */ if (TupIsNull(slot)) { if (projInfo) return ExecClearTuple(projInfo->pi_slot); else return slot; } /* * Before we check the qualifications, we're going to manually check * to see that the tuple matches the provided user ID, because this * is always necessary and it's easier than messing with the target * list. */ /* First, we'll make sure we're dealing with the right user. */ userID = getTupleInt(slot,attributes->userkey); /* How we could fail to find the user ID, I don't know. */ if (userID < 0) elog(ERROR, "user ID column not found"); /* If this tuple doesn't match the user ID, just skip it * and move on. */ if (userID != attributes->userID) recQual = false; /* * place the current tuple into the expr context */ econtext->ecxt_scantuple = slot; /* * check that the current tuple satisfies the qual-clause * * check for non-nil qual here to avoid a function call to ExecQual() * when the qual is nil ... saves only a few cycles, but they add up * ... * * we also make sure that the tuple passes our recommender qual */ if (recQual && (!qual || ExecQual(qual, econtext, false))) { /* * Found a satisfactory scan tuple. */ if (projInfo) { /* * Form a projection tuple, store it in the result tuple slot * and return it --- unless we find we can project no tuples * from this scan tuple, in which case continue scan. */ resultSlot = ExecProject(projInfo, &isDone); if (isDone != ExprEndResult) { node->ps.ps_TupFromTlist = (isDone == ExprMultipleResult); return resultSlot; } } else { /* * Here, we aren't projecting, so just return scan tuple. */ return slot; } } else InstrCountFiltered1(node, 1); /* * Tuple fails qual, so free per-tuple memory and try again. */ ResetExprContext(econtext); } }
/* ---------------------------------------------------------------- * ExecScan * * Scans the relation using the 'access method' indicated and * returns the next qualifying tuple in the direction specified * in the global variable ExecDirection. * The access method returns the next tuple and execScan() is * responsible for checking the tuple returned against the qual-clause. * * A 'recheck method' must also be provided that can check an * arbitrary tuple of the relation against any qual conditions * that are implemented internal to the access method. * * Conditions: * -- the "cursor" maintained by the AMI is positioned at the tuple * returned previously. * * Initial States: * -- the relation indicated is opened for scanning so that the * "cursor" is positioned before the first qualifying tuple. * ---------------------------------------------------------------- */ TupleTableSlot * ExecScan(ScanState *node, ExecScanAccessMtd accessMtd, /* function returning a tuple */ ExecScanRecheckMtd recheckMtd) { ExprContext *econtext; List *qual; ProjectionInfo *projInfo; ExprDoneCond isDone; TupleTableSlot *resultSlot; /* * Fetch data from node */ qual = node->ps.qual; projInfo = node->ps.ps_ProjInfo; econtext = node->ps.ps_ExprContext; /* * If we have neither a qual to check nor a projection to do, just skip * all the overhead and return the raw scan tuple. */ if (!qual && !projInfo) { ResetExprContext(econtext); return ExecScanFetch(node, accessMtd, recheckMtd); } /* * Check to see if we're still projecting out tuples from a previous scan * tuple (because there is a function-returning-set in the projection * expressions). If so, try to project another one. */ if (node->ps.ps_TupFromTlist) { Assert(projInfo); /* can't get here if not projecting */ resultSlot = ExecProject(projInfo, &isDone); if (isDone == ExprMultipleResult) return resultSlot; /* Done with that source tuple... */ node->ps.ps_TupFromTlist = false; } /* * Reset per-tuple memory context to free any expression evaluation * storage allocated in the previous tuple cycle. Note this can't happen * until we're done projecting out tuples from a scan tuple. */ ResetExprContext(econtext); /* * get a tuple from the access method. Loop until we obtain a tuple that * passes the qualification. */ for (;;) { TupleTableSlot *slot; CHECK_FOR_INTERRUPTS(); slot = ExecScanFetch(node, accessMtd, recheckMtd); /* * if the slot returned by the accessMtd contains NULL, then it means * there is nothing more to scan so we just return an empty slot, * being careful to use the projection result slot so it has correct * tupleDesc. */ if (TupIsNull(slot)) { if (projInfo) return ExecClearTuple(projInfo->pi_slot); else return slot; } /* * place the current tuple into the expr context */ econtext->ecxt_scantuple = slot; /* * check that the current tuple satisfies the qual-clause * * check for non-nil qual here to avoid a function call to ExecQual() * when the qual is nil ... saves only a few cycles, but they add up * ... */ if (!qual || ExecQual(qual, econtext, false)) { /* * Found a satisfactory scan tuple. */ if (projInfo) { /* * Form a projection tuple, store it in the result tuple slot * and return it --- unless we find we can project no tuples * from this scan tuple, in which case continue scan. */ resultSlot = ExecProject(projInfo, &isDone); if (isDone != ExprEndResult) { node->ps.ps_TupFromTlist = (isDone == ExprMultipleResult); return resultSlot; } } else { /* * Here, we aren't projecting, so just return scan tuple. */ return slot; } } else InstrCountFiltered1(node, 1); /* * Tuple fails qual, so free per-tuple memory and try again. */ ResetExprContext(econtext); } }