bool IndexScanExecutor::p_execute(const NValueArray ¶ms) { assert(m_node); assert(m_node == dynamic_cast<IndexScanPlanNode*>(m_abstractNode)); // update local target table with its most recent reference Table* targetTable = m_node->getTargetTable(); TableIndex *tableIndex = targetTable->index(m_node->getTargetIndexName()); IndexCursor indexCursor(tableIndex->getTupleSchema()); TableTuple searchKey(tableIndex->getKeySchema()); searchKey.moveNoHeader(m_searchKeyBackingStore); assert(m_lookupType != INDEX_LOOKUP_TYPE_EQ || searchKey.getSchema()->columnCount() == m_numOfSearchkeys); int activeNumOfSearchKeys = m_numOfSearchkeys; IndexLookupType localLookupType = m_lookupType; SortDirectionType localSortDirection = m_sortDirection; // // INLINE LIMIT // LimitPlanNode* limit_node = dynamic_cast<LimitPlanNode*>(m_abstractNode->getInlinePlanNode(PLAN_NODE_TYPE_LIMIT)); TableTuple temp_tuple; ProgressMonitorProxy pmp(m_engine, this); if (m_aggExec != NULL) { const TupleSchema * inputSchema = tableIndex->getTupleSchema(); if (m_projectionNode != NULL) { inputSchema = m_projectionNode->getOutputTable()->schema(); } temp_tuple = m_aggExec->p_execute_init(params, &pmp, inputSchema, m_outputTable); } else { temp_tuple = m_outputTable->tempTuple(); } // Short-circuit an empty scan if (m_node->isEmptyScan()) { VOLT_DEBUG ("Empty Index Scan :\n %s", m_outputTable->debug().c_str()); if (m_aggExec != NULL) { m_aggExec->p_execute_finish(); } return true; } // // SEARCH KEY // bool earlyReturnForSearchKeyOutOfRange = false; searchKey.setAllNulls(); VOLT_TRACE("Initial (all null) search key: '%s'", searchKey.debugNoHeader().c_str()); for (int ctr = 0; ctr < activeNumOfSearchKeys; ctr++) { NValue candidateValue = m_searchKeyArray[ctr]->eval(NULL, NULL); if (candidateValue.isNull()) { // when any part of the search key is NULL, the result is false when it compares to anything. // do early return optimization, our index comparator may not handle null comparison correctly. earlyReturnForSearchKeyOutOfRange = true; break; } try { searchKey.setNValue(ctr, candidateValue); } catch (const SQLException &e) { // This next bit of logic handles underflow, overflow and search key length // exceeding variable length column size (variable lenght mismatch) when // setting up the search keys. // e.g. TINYINT > 200 or INT <= 6000000000 // VarChar(3 bytes) < "abcd" or VarChar(3) > "abbd" // re-throw if not an overflow, underflow or variable length mismatch // currently, it's expected to always be an overflow or underflow if ((e.getInternalFlags() & (SQLException::TYPE_OVERFLOW | SQLException::TYPE_UNDERFLOW | SQLException::TYPE_VAR_LENGTH_MISMATCH)) == 0) { throw e; } // handle the case where this is a comparison, rather than equality match // comparison is the only place where the executor might return matching tuples // e.g. TINYINT < 1000 should return all values if ((localLookupType != INDEX_LOOKUP_TYPE_EQ) && (ctr == (activeNumOfSearchKeys - 1))) { if (e.getInternalFlags() & SQLException::TYPE_OVERFLOW) { if ((localLookupType == INDEX_LOOKUP_TYPE_GT) || (localLookupType == INDEX_LOOKUP_TYPE_GTE)) { // gt or gte when key overflows returns nothing except inline agg earlyReturnForSearchKeyOutOfRange = true; break; } else { // for overflow on reverse scan, we need to // do a forward scan to find the correct start // point, which is exactly what LTE would do. // so, set the lookupType to LTE and the missing // searchkey will be handled by extra post filters localLookupType = INDEX_LOOKUP_TYPE_LTE; } } if (e.getInternalFlags() & SQLException::TYPE_UNDERFLOW) { if ((localLookupType == INDEX_LOOKUP_TYPE_LT) || (localLookupType == INDEX_LOOKUP_TYPE_LTE)) { // lt or lte when key underflows returns nothing except inline agg earlyReturnForSearchKeyOutOfRange = true; break; } else { // don't allow GTE because it breaks null handling localLookupType = INDEX_LOOKUP_TYPE_GT; } } if (e.getInternalFlags() & SQLException::TYPE_VAR_LENGTH_MISMATCH) { // shrink the search key and add the updated key to search key table tuple searchKey.shrinkAndSetNValue(ctr, candidateValue); // search will be performed on shrinked key, so update lookup operation // to account for it switch (localLookupType) { case INDEX_LOOKUP_TYPE_LT: case INDEX_LOOKUP_TYPE_LTE: localLookupType = INDEX_LOOKUP_TYPE_LTE; break; case INDEX_LOOKUP_TYPE_GT: case INDEX_LOOKUP_TYPE_GTE: localLookupType = INDEX_LOOKUP_TYPE_GT; break; default: assert(!"IndexScanExecutor::p_execute - can't index on not equals"); return false; } } // if here, means all tuples with the previous searchkey // columns need to be scanned. Note, if only one column, // then all tuples will be scanned. Only exception to this // case is setting of search key in search tuple was due // to search key length exceeding the search column length // of variable length type if (!(e.getInternalFlags() & SQLException::TYPE_VAR_LENGTH_MISMATCH)) { // for variable length mismatch error, the needed search key to perform the search // has been generated and added to the search tuple. So no need to decrement // activeNumOfSearchKeys activeNumOfSearchKeys--; } if (localSortDirection == SORT_DIRECTION_TYPE_INVALID) { localSortDirection = SORT_DIRECTION_TYPE_ASC; } } // if a EQ comparison is out of range, then return no tuples else { earlyReturnForSearchKeyOutOfRange = true; break; } break; } } if (earlyReturnForSearchKeyOutOfRange) { if (m_aggExec != NULL) { m_aggExec->p_execute_finish(); } return true; } assert((activeNumOfSearchKeys == 0) || (searchKey.getSchema()->columnCount() > 0)); VOLT_TRACE("Search key after substitutions: '%s', # of active search keys: %d", searchKey.debugNoHeader().c_str(), activeNumOfSearchKeys); // // END EXPRESSION // AbstractExpression* end_expression = m_node->getEndExpression(); if (end_expression != NULL) { VOLT_DEBUG("End Expression:\n%s", end_expression->debug(true).c_str()); } // // POST EXPRESSION // AbstractExpression* post_expression = m_node->getPredicate(); if (post_expression != NULL) { VOLT_DEBUG("Post Expression:\n%s", post_expression->debug(true).c_str()); } // INITIAL EXPRESSION AbstractExpression* initial_expression = m_node->getInitialExpression(); if (initial_expression != NULL) { VOLT_DEBUG("Initial Expression:\n%s", initial_expression->debug(true).c_str()); } // // SKIP NULL EXPRESSION // AbstractExpression* skipNullExpr = m_node->getSkipNullPredicate(); // For reverse scan edge case NULL values and forward scan underflow case. if (skipNullExpr != NULL) { VOLT_DEBUG("COUNT NULL Expression:\n%s", skipNullExpr->debug(true).c_str()); } // // An index scan has three parts: // (1) Lookup tuples using the search key // (2) For each tuple that comes back, check whether the // end_expression is false. // If it is, then we stop scanning. Otherwise... // (3) Check whether the tuple satisfies the post expression. // If it does, then add it to the output table // // Use our search key to prime the index iterator // Now loop through each tuple given to us by the iterator // TableTuple tuple; if (activeNumOfSearchKeys > 0) { VOLT_TRACE("INDEX_LOOKUP_TYPE(%d) m_numSearchkeys(%d) key:%s", localLookupType, activeNumOfSearchKeys, searchKey.debugNoHeader().c_str()); if (localLookupType == INDEX_LOOKUP_TYPE_EQ) { tableIndex->moveToKey(&searchKey, indexCursor); } else if (localLookupType == INDEX_LOOKUP_TYPE_GT) { tableIndex->moveToGreaterThanKey(&searchKey, indexCursor); } else if (localLookupType == INDEX_LOOKUP_TYPE_GTE) { tableIndex->moveToKeyOrGreater(&searchKey, indexCursor); } else if (localLookupType == INDEX_LOOKUP_TYPE_LT) { tableIndex->moveToLessThanKey(&searchKey, indexCursor); } else if (localLookupType == INDEX_LOOKUP_TYPE_LTE) { // find the entry whose key is greater than search key, // do a forward scan using initialExpr to find the correct // start point to do reverse scan bool isEnd = tableIndex->moveToGreaterThanKey(&searchKey, indexCursor); if (isEnd) { tableIndex->moveToEnd(false, indexCursor); } else { while (!(tuple = tableIndex->nextValue(indexCursor)).isNullTuple()) { pmp.countdownProgress(); if (initial_expression != NULL && !initial_expression->eval(&tuple, NULL).isTrue()) { // just passed the first failed entry, so move 2 backward tableIndex->moveToBeforePriorEntry(indexCursor); break; } } if (tuple.isNullTuple()) { tableIndex->moveToEnd(false, indexCursor); } } } else { return false; } } else { bool toStartActually = (localSortDirection != SORT_DIRECTION_TYPE_DESC); tableIndex->moveToEnd(toStartActually, indexCursor); } int tuple_ctr = 0; int tuples_skipped = 0; // for offset int limit = -1; int offset = -1; if (limit_node != NULL) { limit_node->getLimitAndOffsetByReference(params, limit, offset); } // // We have to different nextValue() methods for different lookup types // while ((limit == -1 || tuple_ctr < limit) && ((localLookupType == INDEX_LOOKUP_TYPE_EQ && !(tuple = tableIndex->nextValueAtKey(indexCursor)).isNullTuple()) || ((localLookupType != INDEX_LOOKUP_TYPE_EQ || activeNumOfSearchKeys == 0) && !(tuple = tableIndex->nextValue(indexCursor)).isNullTuple()))) { if (tuple.isPendingDelete()) { continue; } VOLT_TRACE("LOOPING in indexscan: tuple: '%s'\n", tuple.debug("tablename").c_str()); pmp.countdownProgress(); // // First check to eliminate the null index rows for UNDERFLOW case only // if (skipNullExpr != NULL) { if (skipNullExpr->eval(&tuple, NULL).isTrue()) { VOLT_DEBUG("Index scan: find out null rows or columns."); continue; } else { skipNullExpr = NULL; } } // // First check whether the end_expression is now false // if (end_expression != NULL && !end_expression->eval(&tuple, NULL).isTrue()) { VOLT_TRACE("End Expression evaluated to false, stopping scan"); break; } // // Then apply our post-predicate to do further filtering // if (post_expression == NULL || post_expression->eval(&tuple, NULL).isTrue()) { // // INLINE OFFSET // if (tuples_skipped < offset) { tuples_skipped++; continue; } tuple_ctr++; if (m_projector.numSteps() > 0) { m_projector.exec(temp_tuple, tuple); if (m_aggExec != NULL) { if (m_aggExec->p_execute_tuple(temp_tuple)) { break; } } else { m_outputTable->insertTupleNonVirtual(temp_tuple); } } else { if (m_aggExec != NULL) { if (m_aggExec->p_execute_tuple(tuple)) { break; } } else { // // Straight Insert // m_outputTable->insertTupleNonVirtual(tuple); } } pmp.countdownProgress(); } } if (m_aggExec != NULL) { m_aggExec->p_execute_finish(); } VOLT_DEBUG ("Index Scanned :\n %s", m_outputTable->debug().c_str()); return true; }
bool SeqScanExecutor::p_execute(const NValueArray ¶ms) { SeqScanPlanNode* node = dynamic_cast<SeqScanPlanNode*>(m_abstractNode); assert(node); Table* output_table = node->getOutputTable(); assert(output_table); Table* input_table = (node->isSubQuery()) ? node->getChildren()[0]->getOutputTable(): node->getTargetTable(); assert(input_table); //* for debug */std::cout << "SeqScanExecutor: node id " << node->getPlanNodeId() << //* for debug */ " input table " << (void*)input_table << //* for debug */ " has " << input_table->activeTupleCount() << " tuples " << std::endl; VOLT_TRACE("Sequential Scanning table :\n %s", input_table->debug().c_str()); VOLT_DEBUG("Sequential Scanning table : %s which has %d active, %d" " allocated", input_table->name().c_str(), (int)input_table->activeTupleCount(), (int)input_table->allocatedTupleCount()); // // OPTIMIZATION: NESTED PROJECTION // // Since we have the input params, we need to call substitute to // change any nodes in our expression tree to be ready for the // projection operations in execute // int num_of_columns = -1; ProjectionPlanNode* projection_node = dynamic_cast<ProjectionPlanNode*>(node->getInlinePlanNode(PLAN_NODE_TYPE_PROJECTION)); if (projection_node != NULL) { num_of_columns = static_cast<int> (projection_node->getOutputColumnExpressions().size()); } // // OPTIMIZATION: NESTED LIMIT // How nice! We can also cut off our scanning with a nested limit! // LimitPlanNode* limit_node = dynamic_cast<LimitPlanNode*>(node->getInlinePlanNode(PLAN_NODE_TYPE_LIMIT)); // // OPTIMIZATION: // // If there is no predicate and no Projection for this SeqScan, // then we have already set the node's OutputTable to just point // at the TargetTable. Therefore, there is nothing we more we need // to do here // if (node->getPredicate() != NULL || projection_node != NULL || limit_node != NULL || m_aggExec != NULL) { // // Just walk through the table using our iterator and apply // the predicate to each tuple. For each tuple that satisfies // our expression, we'll insert them into the output table. // TableTuple tuple(input_table->schema()); TableIterator iterator = input_table->iteratorDeletingAsWeGo(); AbstractExpression *predicate = node->getPredicate(); if (predicate) { VOLT_TRACE("SCAN PREDICATE A:\n%s\n", predicate->debug(true).c_str()); } int limit = -1; int offset = -1; if (limit_node) { limit_node->getLimitAndOffsetByReference(params, limit, offset); } int tuple_ctr = 0; int tuple_skipped = 0; TempTable* output_temp_table = dynamic_cast<TempTable*>(output_table); ProgressMonitorProxy pmp(m_engine, this, node->isSubQuery() ? NULL : input_table); TableTuple temp_tuple; if (m_aggExec != NULL) { const TupleSchema * inputSchema = input_table->schema(); if (projection_node != NULL) { inputSchema = projection_node->getOutputTable()->schema(); } temp_tuple = m_aggExec->p_execute_init(params, &pmp, inputSchema, output_temp_table); } else { temp_tuple = output_temp_table->tempTuple(); } while ((limit == -1 || tuple_ctr < limit) && iterator.next(tuple)) { VOLT_TRACE("INPUT TUPLE: %s, %d/%d\n", tuple.debug(input_table->name()).c_str(), tuple_ctr, (int)input_table->activeTupleCount()); pmp.countdownProgress(); // // For each tuple we need to evaluate it against our predicate // if (predicate == NULL || predicate->eval(&tuple, NULL).isTrue()) { // Check if we have to skip this tuple because of offset if (tuple_skipped < offset) { tuple_skipped++; continue; } ++tuple_ctr; // // Nested Projection // Project (or replace) values from input tuple // if (projection_node != NULL) { VOLT_TRACE("inline projection..."); for (int ctr = 0; ctr < num_of_columns; ctr++) { NValue value = projection_node->getOutputColumnExpressions()[ctr]->eval(&tuple, NULL); temp_tuple.setNValue(ctr, value); } if (m_aggExec != NULL) { if (m_aggExec->p_execute_tuple(temp_tuple)) { break; } } else { output_temp_table->insertTupleNonVirtual(temp_tuple); } } else { if (m_aggExec != NULL) { if (m_aggExec->p_execute_tuple(tuple)) { break; } } else { // // Insert the tuple into our output table // output_temp_table->insertTupleNonVirtual(tuple); } } pmp.countdownProgress(); } } if (m_aggExec != NULL) { m_aggExec->p_execute_finish(); } } //* for debug */std::cout << "SeqScanExecutor: node id " << node->getPlanNodeId() << //* for debug */ " output table " << (void*)output_table << //* for debug */ " put " << output_table->activeTupleCount() << " tuples " << std::endl; VOLT_TRACE("\n%s\n", output_table->debug().c_str()); VOLT_DEBUG("Finished Seq scanning"); return true; }
bool IndexScanExecutor::p_execute(const NValueArray ¶ms) { assert(m_node); assert(m_node == dynamic_cast<IndexScanPlanNode*>(m_abstractNode)); assert(m_outputTable); assert(m_outputTable == static_cast<TempTable*>(m_node->getOutputTable())); // update local target table with its most recent reference Table* targetTable = m_node->getTargetTable(); TableIndex *tableIndex = targetTable->index(m_node->getTargetIndexName()); TableTuple searchKey(tableIndex->getKeySchema()); searchKey.moveNoHeader(m_searchKeyBackingStore); assert(m_lookupType != INDEX_LOOKUP_TYPE_EQ || searchKey.getSchema()->columnCount() == m_numOfSearchkeys); int activeNumOfSearchKeys = m_numOfSearchkeys; IndexLookupType localLookupType = m_lookupType; SortDirectionType localSortDirection = m_sortDirection; // INLINE PROJECTION // Set params to expression tree via substitute() assert(m_numOfColumns == m_outputTable->columnCount()); if (m_projectionNode != NULL && m_projectionAllTupleArray == NULL) { for (int ctr = 0; ctr < m_numOfColumns; ctr++) { assert(m_projectionNode->getOutputColumnExpressions()[ctr]); m_projectionExpressions[ctr]->substitute(params); assert(m_projectionExpressions[ctr]); } } // // INLINE LIMIT // LimitPlanNode* limit_node = dynamic_cast<LimitPlanNode*>(m_abstractNode->getInlinePlanNode(PLAN_NODE_TYPE_LIMIT)); // // SEARCH KEY // searchKey.setAllNulls(); VOLT_TRACE("Initial (all null) search key: '%s'", searchKey.debugNoHeader().c_str()); for (int ctr = 0; ctr < activeNumOfSearchKeys; ctr++) { m_searchKeyArray[ctr]->substitute(params); NValue candidateValue = m_searchKeyArray[ctr]->eval(NULL, NULL); try { searchKey.setNValue(ctr, candidateValue); } catch (const SQLException &e) { // This next bit of logic handles underflow and overflow while // setting up the search keys. // e.g. TINYINT > 200 or INT <= 6000000000 // re-throw if not an overflow or underflow // currently, it's expected to always be an overflow or underflow if ((e.getInternalFlags() & (SQLException::TYPE_OVERFLOW | SQLException::TYPE_UNDERFLOW)) == 0) { throw e; } // handle the case where this is a comparison, rather than equality match // comparison is the only place where the executor might return matching tuples // e.g. TINYINT < 1000 should return all values if ((localLookupType != INDEX_LOOKUP_TYPE_EQ) && (ctr == (activeNumOfSearchKeys - 1))) { if (e.getInternalFlags() & SQLException::TYPE_OVERFLOW) { if ((localLookupType == INDEX_LOOKUP_TYPE_GT) || (localLookupType == INDEX_LOOKUP_TYPE_GTE)) { // gt or gte when key overflows returns nothing return true; } else { // for overflow on reverse scan, we need to // do a forward scan to find the correct start // point, which is exactly what LTE would do. // so, set the lookupType to LTE and the missing // searchkey will be handled by extra post filters localLookupType = INDEX_LOOKUP_TYPE_LTE; } } if (e.getInternalFlags() & SQLException::TYPE_UNDERFLOW) { if ((localLookupType == INDEX_LOOKUP_TYPE_LT) || (localLookupType == INDEX_LOOKUP_TYPE_LTE)) { // lt or lte when key underflows returns nothing return true; } else { // don't allow GTE because it breaks null handling localLookupType = INDEX_LOOKUP_TYPE_GT; } } // if here, means all tuples with the previous searchkey // columns need to be scaned. Note, if only one column, // then all tuples will be scanned activeNumOfSearchKeys--; if (localSortDirection == SORT_DIRECTION_TYPE_INVALID) { localSortDirection = SORT_DIRECTION_TYPE_ASC; } } // if a EQ comparison is out of range, then return no tuples else { return true; } break; } } assert((activeNumOfSearchKeys == 0) || (searchKey.getSchema()->columnCount() > 0)); VOLT_TRACE("Search key after substitutions: '%s'", searchKey.debugNoHeader().c_str()); // // END EXPRESSION // AbstractExpression* end_expression = m_node->getEndExpression(); if (end_expression != NULL) { end_expression->substitute(params); VOLT_DEBUG("End Expression:\n%s", end_expression->debug(true).c_str()); } // // POST EXPRESSION // AbstractExpression* post_expression = m_node->getPredicate(); if (post_expression != NULL) { post_expression->substitute(params); VOLT_DEBUG("Post Expression:\n%s", post_expression->debug(true).c_str()); } // INITIAL EXPRESSION AbstractExpression* initial_expression = m_node->getInitialExpression(); if (initial_expression != NULL) { initial_expression->substitute(params); VOLT_DEBUG("Initial Expression:\n%s", initial_expression->debug(true).c_str()); } // // SKIP NULL EXPRESSION // AbstractExpression* skipNullExpr = m_node->getSkipNullPredicate(); // For reverse scan edge case NULL values and forward scan underflow case. if (skipNullExpr != NULL) { skipNullExpr->substitute(params); VOLT_DEBUG("COUNT NULL Expression:\n%s", skipNullExpr->debug(true).c_str()); } ProgressMonitorProxy pmp(m_engine, targetTable); // // An index scan has three parts: // (1) Lookup tuples using the search key // (2) For each tuple that comes back, check whether the // end_expression is false. // If it is, then we stop scanning. Otherwise... // (3) Check whether the tuple satisfies the post expression. // If it does, then add it to the output table // // Use our search key to prime the index iterator // Now loop through each tuple given to us by the iterator // TableTuple tuple; if (activeNumOfSearchKeys > 0) { VOLT_TRACE("INDEX_LOOKUP_TYPE(%d) m_numSearchkeys(%d) key:%s", localLookupType, activeNumOfSearchKeys, searchKey.debugNoHeader().c_str()); if (localLookupType == INDEX_LOOKUP_TYPE_EQ) { tableIndex->moveToKey(&searchKey); } else if (localLookupType == INDEX_LOOKUP_TYPE_GT) { tableIndex->moveToGreaterThanKey(&searchKey); } else if (localLookupType == INDEX_LOOKUP_TYPE_GTE) { tableIndex->moveToKeyOrGreater(&searchKey); } else if (localLookupType == INDEX_LOOKUP_TYPE_LT) { tableIndex->moveToLessThanKey(&searchKey); } else if (localLookupType == INDEX_LOOKUP_TYPE_LTE) { // find the entry whose key is greater than search key, // do a forward scan using initialExpr to find the correct // start point to do reverse scan bool isEnd = tableIndex->moveToGreaterThanKey(&searchKey); if (isEnd) { tableIndex->moveToEnd(false); } else { while (!(tuple = tableIndex->nextValue()).isNullTuple()) { pmp.countdownProgress(); if (initial_expression != NULL && !initial_expression->eval(&tuple, NULL).isTrue()) { // just passed the first failed entry, so move 2 backward tableIndex->moveToBeforePriorEntry(); break; } } if (tuple.isNullTuple()) { tableIndex->moveToEnd(false); } } } else { return false; } } else { bool toStartActually = (localSortDirection != SORT_DIRECTION_TYPE_DESC); tableIndex->moveToEnd(toStartActually); } int tuple_ctr = 0; int tuples_skipped = 0; // for offset int limit = -1; int offset = -1; if (limit_node != NULL) { limit_node->getLimitAndOffsetByReference(params, limit, offset); } // // We have to different nextValue() methods for different lookup types // while ((limit == -1 || tuple_ctr < limit) && ((localLookupType == INDEX_LOOKUP_TYPE_EQ && !(tuple = tableIndex->nextValueAtKey()).isNullTuple()) || ((localLookupType != INDEX_LOOKUP_TYPE_EQ || activeNumOfSearchKeys == 0) && !(tuple = tableIndex->nextValue()).isNullTuple()))) { VOLT_TRACE("LOOPING in indexscan: tuple: '%s'\n", tuple.debug("tablename").c_str()); pmp.countdownProgress(); // // First check to eliminate the null index rows for UNDERFLOW case only // if (skipNullExpr != NULL) { if (skipNullExpr->eval(&tuple, NULL).isTrue()) { VOLT_DEBUG("Index scan: find out null rows or columns."); continue; } else { skipNullExpr = NULL; } } // // First check whether the end_expression is now false // if (end_expression != NULL && !end_expression->eval(&tuple, NULL).isTrue()) { VOLT_TRACE("End Expression evaluated to false, stopping scan"); break; } // // Then apply our post-predicate to do further filtering // if (post_expression == NULL || post_expression->eval(&tuple, NULL).isTrue()) { // // INLINE OFFSET // if (tuples_skipped < offset) { tuples_skipped++; continue; } tuple_ctr++; if (m_projectionNode != NULL) { TableTuple &temp_tuple = m_outputTable->tempTuple(); if (m_projectionAllTupleArray != NULL) { VOLT_TRACE("sweet, all tuples"); for (int ctr = m_numOfColumns - 1; ctr >= 0; --ctr) { temp_tuple.setNValue(ctr, tuple.getNValue(m_projectionAllTupleArray[ctr])); } } else { for (int ctr = m_numOfColumns - 1; ctr >= 0; --ctr) { temp_tuple.setNValue(ctr, m_projectionExpressions[ctr]->eval(&tuple, NULL)); } } m_outputTable->insertTupleNonVirtual(temp_tuple); } else // // Straight Insert // { // // Try to put the tuple into our output table // m_outputTable->insertTupleNonVirtual(tuple); } pmp.countdownProgress(); } } VOLT_DEBUG ("Index Scanned :\n %s", m_outputTable->debug().c_str()); return true; }
bool SeqScanExecutor::p_execute(const NValueArray ¶ms) { SeqScanPlanNode* node = dynamic_cast<SeqScanPlanNode*>(m_abstractNode); assert(node); Table* output_table = node->getOutputTable(); assert(output_table); Table* target_table = dynamic_cast<Table*>(node->getTargetTable()); assert(target_table); //cout << "SeqScanExecutor: node id" << node->getPlanNodeId() << endl; VOLT_TRACE("Sequential Scanning table :\n %s", target_table->debug().c_str()); VOLT_DEBUG("Sequential Scanning table : %s which has %d active, %d" " allocated, %d used tuples", target_table->name().c_str(), (int)target_table->activeTupleCount(), (int)target_table->allocatedTupleCount(), (int)target_table->usedTupleCount()); // // OPTIMIZATION: NESTED PROJECTION // // Since we have the input params, we need to call substitute to // change any nodes in our expression tree to be ready for the // projection operations in execute // int num_of_columns = (int)output_table->columnCount(); ProjectionPlanNode* projection_node = dynamic_cast<ProjectionPlanNode*>(node->getInlinePlanNode(PLAN_NODE_TYPE_PROJECTION)); if (projection_node != NULL) { for (int ctr = 0; ctr < num_of_columns; ctr++) { assert(projection_node->getOutputColumnExpressions()[ctr]); projection_node->getOutputColumnExpressions()[ctr]->substitute(params); } } // // OPTIMIZATION: NESTED LIMIT // How nice! We can also cut off our scanning with a nested limit! // int limit = -1; int offset = -1; LimitPlanNode* limit_node = dynamic_cast<LimitPlanNode*>(node->getInlinePlanNode(PLAN_NODE_TYPE_LIMIT)); if (limit_node != NULL) { limit_node->getLimitAndOffsetByReference(params, limit, offset); } // // OPTIMIZATION: // // If there is no predicate and no Projection for this SeqScan, // then we have already set the node's OutputTable to just point // at the TargetTable. Therefore, there is nothing we more we need // to do here // if (node->getPredicate() != NULL || projection_node != NULL || limit_node != NULL) { // // Just walk through the table using our iterator and apply // the predicate to each tuple. For each tuple that satisfies // our expression, we'll insert them into the output table. // TableTuple tuple(target_table->schema()); TableIterator iterator = target_table->iterator(); AbstractExpression *predicate = node->getPredicate(); VOLT_TRACE("SCAN PREDICATE A:\n%s\n", predicate->debug(true).c_str()); if (predicate) { predicate->substitute(params); assert(predicate != NULL); VOLT_DEBUG("SCAN PREDICATE B:\n%s\n", predicate->debug(true).c_str()); } int tuple_ctr = 0; int tuple_skipped = 0; while (iterator.next(tuple)) { VOLT_TRACE("INPUT TUPLE: %s, %d/%d\n", tuple.debug(target_table->name()).c_str(), tuple_ctr, (int)target_table->activeTupleCount()); // // For each tuple we need to evaluate it against our predicate // if (predicate == NULL || predicate->eval(&tuple, NULL).isTrue()) { // Check if we have to skip this tuple because of offset if (tuple_skipped < offset) { tuple_skipped++; continue; } // // Nested Projection // Project (or replace) values from input tuple // if (projection_node != NULL) { TableTuple &temp_tuple = output_table->tempTuple(); for (int ctr = 0; ctr < num_of_columns; ctr++) { NValue value = projection_node-> getOutputColumnExpressions()[ctr]->eval(&tuple, NULL); temp_tuple.setNValue(ctr, value); } if (!output_table->insertTuple(temp_tuple)) { VOLT_ERROR("Failed to insert tuple from table '%s' into" " output table '%s'", target_table->name().c_str(), output_table->name().c_str()); return false; } } else { // // Insert the tuple into our output table // if (!output_table->insertTuple(tuple)) { VOLT_ERROR("Failed to insert tuple from table '%s' into" " output table '%s'", target_table->name().c_str(), output_table->name().c_str()); return false; } } ++tuple_ctr; // Check whether we have gone past our limit if (limit >= 0 && tuple_ctr >= limit) { break; } } } } VOLT_TRACE("\n%s\n", output_table->debug().c_str()); VOLT_DEBUG("Finished Seq scanning"); return true; }
bool NestLoopExecutor::p_execute(const NValueArray ¶ms) { VOLT_DEBUG("executing NestLoop..."); NestLoopPlanNode* node = dynamic_cast<NestLoopPlanNode*>(m_abstractNode); assert(node); assert(node->getInputTables().size() == 2); Table* output_table_ptr = node->getOutputTable(); assert(output_table_ptr); // output table must be a temp table TempTable* output_table = dynamic_cast<TempTable*>(output_table_ptr); assert(output_table); Table* outer_table = node->getInputTables()[0]; assert(outer_table); Table* inner_table = node->getInputTables()[1]; assert(inner_table); VOLT_TRACE ("input table left:\n %s", outer_table->debug().c_str()); VOLT_TRACE ("input table right:\n %s", inner_table->debug().c_str()); // // Pre Join Expression // AbstractExpression *preJoinPredicate = node->getPreJoinPredicate(); if (preJoinPredicate) { preJoinPredicate->substitute(params); VOLT_TRACE ("Pre Join predicate: %s", preJoinPredicate == NULL ? "NULL" : preJoinPredicate->debug(true).c_str()); } // // Join Expression // AbstractExpression *joinPredicate = node->getJoinPredicate(); if (joinPredicate) { joinPredicate->substitute(params); VOLT_TRACE ("Join predicate: %s", joinPredicate == NULL ? "NULL" : joinPredicate->debug(true).c_str()); } // // Where Expression // AbstractExpression *wherePredicate = node->getWherePredicate(); if (wherePredicate) { wherePredicate->substitute(params); VOLT_TRACE ("Where predicate: %s", wherePredicate == NULL ? "NULL" : wherePredicate->debug(true).c_str()); } // Join type JoinType join_type = node->getJoinType(); assert(join_type == JOIN_TYPE_INNER || join_type == JOIN_TYPE_LEFT); int outer_cols = outer_table->columnCount(); int inner_cols = inner_table->columnCount(); TableTuple outer_tuple(node->getInputTables()[0]->schema()); TableTuple inner_tuple(node->getInputTables()[1]->schema()); TableTuple &joined = output_table->tempTuple(); TableTuple null_tuple = m_null_tuple; TableIterator iterator0 = outer_table->iterator(); while (iterator0.next(outer_tuple)) { // did this loop body find at least one match for this tuple? bool match = false; // For outer joins if outer tuple fails pre-join predicate // (join expression based on the outer table only) // it can't match any of inner tuples if (preJoinPredicate == NULL || preJoinPredicate->eval(&outer_tuple, NULL).isTrue()) { // populate output table's temp tuple with outer table's values // probably have to do this at least once - avoid doing it many // times per outer tuple joined.setNValues(0, outer_tuple, 0, outer_cols); TableIterator iterator1 = inner_table->iterator(); while (iterator1.next(inner_tuple)) { // Apply join filter to produce matches for each outer that has them, // then pad unmatched outers, then filter them all if (joinPredicate == NULL || joinPredicate->eval(&outer_tuple, &inner_tuple).isTrue()) { match = true; // Filter the joined tuple if (wherePredicate == NULL || wherePredicate->eval(&outer_tuple, &inner_tuple).isTrue()) { // Matched! Complete the joined tuple with the inner column values. joined.setNValues(outer_cols, inner_tuple, 0, inner_cols); output_table->insertTupleNonVirtual(joined); } } } } // // Left Outer Join // if (join_type == JOIN_TYPE_LEFT && !match) { // Still needs to pass the filter if (wherePredicate == NULL || wherePredicate->eval(&outer_tuple, &null_tuple).isTrue()) { joined.setNValues(outer_cols, null_tuple, 0, inner_cols); output_table->insertTupleNonVirtual(joined); } } } return (true); }
bool IndexCountExecutor::p_execute(const NValueArray ¶ms) { assert(m_node); assert(m_node == dynamic_cast<IndexCountPlanNode*>(m_abstractNode)); assert(m_outputTable); assert(m_outputTable == static_cast<TempTable*>(m_node->getOutputTable())); assert(m_targetTable); assert(m_targetTable == m_node->getTargetTable()); VOLT_DEBUG("IndexCount: %s.%s\n", m_targetTable->name().c_str(), m_index->getName().c_str()); int activeNumOfSearchKeys = m_numOfSearchkeys; IndexLookupType localLookupType = m_lookupType; bool searchKeyUnderflow = false, endKeyOverflow = false; // Overflow cases that can return early without accessing the index need this // default 0 count as their result. TableTuple& tmptup = m_outputTable->tempTuple(); tmptup.setNValue(0, ValueFactory::getBigIntValue( 0 )); // // SEARCH KEY // if (m_numOfSearchkeys != 0) { m_searchKey.setAllNulls(); VOLT_DEBUG("<Index Count>Initial (all null) search key: '%s'", m_searchKey.debugNoHeader().c_str()); for (int ctr = 0; ctr < activeNumOfSearchKeys; ctr++) { m_searchKeyArray[ctr]->substitute(params); NValue candidateValue = m_searchKeyArray[ctr]->eval(NULL, NULL); try { m_searchKey.setNValue(ctr, candidateValue); } catch (const SQLException &e) { // This next bit of logic handles underflow and overflow while // setting up the search keys. // e.g. TINYINT > 200 or INT <= 6000000000 // re-throw if not an overflow or underflow // currently, it's expected to always be an overflow or underflow if ((e.getInternalFlags() & (SQLException::TYPE_OVERFLOW | SQLException::TYPE_UNDERFLOW)) == 0) { throw e; } // handle the case where this is a comparison, rather than equality match // comparison is the only place where the executor might return matching tuples // e.g. TINYINT < 1000 should return all values if ((localLookupType != INDEX_LOOKUP_TYPE_EQ) && (ctr == (activeNumOfSearchKeys - 1))) { assert (localLookupType == INDEX_LOOKUP_TYPE_GT || localLookupType == INDEX_LOOKUP_TYPE_GTE); if (e.getInternalFlags() & SQLException::TYPE_OVERFLOW) { m_outputTable->insertTuple(tmptup); return true; } else if (e.getInternalFlags() & SQLException::TYPE_UNDERFLOW) { searchKeyUnderflow = true; break; } else { throw e; } } // if a EQ comparision is out of range, then return no tuples else { m_outputTable->insertTuple(tmptup); return true; } break; } } VOLT_TRACE("Search key after substitutions: '%s'", m_searchKey.debugNoHeader().c_str()); } if (m_numOfEndkeys != 0) { // // END KEY // m_endKey.setAllNulls(); VOLT_DEBUG("Initial (all null) end key: '%s'", m_endKey.debugNoHeader().c_str()); for (int ctr = 0; ctr < m_numOfEndkeys; ctr++) { m_endKeyArray[ctr]->substitute(params); NValue endKeyValue = m_endKeyArray[ctr]->eval(NULL, NULL); try { m_endKey.setNValue(ctr, endKeyValue); } catch (const SQLException &e) { // This next bit of logic handles underflow and overflow while // setting up the search keys. // e.g. TINYINT > 200 or INT <= 6000000000 // re-throw if not an overflow or underflow // currently, it's expected to always be an overflow or underflow if ((e.getInternalFlags() & (SQLException::TYPE_OVERFLOW | SQLException::TYPE_UNDERFLOW)) == 0) { throw e; } if (ctr == (m_numOfEndkeys - 1)) { assert (m_endType == INDEX_LOOKUP_TYPE_LT || m_endType == INDEX_LOOKUP_TYPE_LTE); if (e.getInternalFlags() & SQLException::TYPE_UNDERFLOW) { m_outputTable->insertTuple(tmptup); return true; } else if (e.getInternalFlags() & SQLException::TYPE_OVERFLOW) { endKeyOverflow = true; const ValueType type = m_endKey.getSchema()->columnType(ctr); NValue tmpEndKeyValue = ValueFactory::getBigIntValue(getMaxTypeValue(type)); m_endKey.setNValue(ctr, tmpEndKeyValue); VOLT_DEBUG("<Index count> end key out of range, MAX value: %ld...\n", (long)getMaxTypeValue(type)); break; } else { throw e; } } // if a EQ comparision is out of range, then return no tuples else { m_outputTable->insertTuple(tmptup); return true; } break; } } VOLT_TRACE("End key after substitutions: '%s'", m_endKey.debugNoHeader().c_str()); } // // POST EXPRESSION // assert (m_node->getPredicate() == NULL); assert (m_index); assert (m_index == m_targetTable->index(m_node->getTargetIndexName())); assert (m_index->isCountableIndex()); // // COUNT NULL EXPRESSION // AbstractExpression* countNULLExpr = m_node->getSkipNullPredicate(); // For reverse scan edge case NULL values and forward scan underflow case. if (countNULLExpr != NULL) { countNULLExpr->substitute(params); VOLT_DEBUG("COUNT NULL Expression:\n%s", countNULLExpr->debug(true).c_str()); } bool reverseScanNullEdgeCase = false; bool reverseScanMovedIndexToScan = false; if (m_numOfSearchkeys < m_numOfEndkeys && (m_endType == INDEX_LOOKUP_TYPE_LT || m_endType == INDEX_LOOKUP_TYPE_LTE)) { reverseScanNullEdgeCase = true; VOLT_DEBUG("Index count: reverse scan edge null case." ); } // An index count has two cases: unique and non-unique int64_t rkStart = 0, rkEnd = 0, rkRes = 0; int leftIncluded = 0, rightIncluded = 0; if (m_numOfSearchkeys != 0) { // Deal with multi-map VOLT_DEBUG("INDEX_LOOKUP_TYPE(%d) m_numSearchkeys(%d) key:%s", localLookupType, activeNumOfSearchKeys, m_searchKey.debugNoHeader().c_str()); if (searchKeyUnderflow == false) { if (localLookupType == INDEX_LOOKUP_TYPE_GT) { rkStart = m_index->getCounterLET(&m_searchKey, true); } else { // handle start inclusive cases. if (m_index->hasKey(&m_searchKey)) { leftIncluded = 1; rkStart = m_index->getCounterLET(&m_searchKey, false); if (reverseScanNullEdgeCase) { m_index->moveToKeyOrGreater(&m_searchKey); reverseScanMovedIndexToScan = true; } } else { rkStart = m_index->getCounterLET(&m_searchKey, true); } } } else { // Do not count null row or columns m_index->moveToKeyOrGreater(&m_searchKey); assert(countNULLExpr); long numNULLs = countNulls(countNULLExpr); rkStart += numNULLs; VOLT_DEBUG("Index count[underflow case]: find out %ld null rows or columns are not counted in.", numNULLs); } } if (reverseScanNullEdgeCase) { // reverse scan case if (!reverseScanMovedIndexToScan && localLookupType != INDEX_LOOKUP_TYPE_GT) { m_index->moveToEnd(true); } assert(countNULLExpr); long numNULLs = countNulls(countNULLExpr); rkStart += numNULLs; VOLT_DEBUG("Index count[reverse case]: find out %ld null rows or columns are not counted in.", numNULLs); } if (m_numOfEndkeys != 0) { if (endKeyOverflow) { rkEnd = m_index->getCounterGET(&m_endKey, true); } else { IndexLookupType localEndType = m_endType; if (localEndType == INDEX_LOOKUP_TYPE_LT) { rkEnd = m_index->getCounterGET(&m_endKey, false); } else { if (m_index->hasKey(&m_endKey)) { rightIncluded = 1; rkEnd = m_index->getCounterGET(&m_endKey, true); } else { rkEnd = m_index->getCounterGET(&m_endKey, false); } } } } else { rkEnd = m_index->getSize(); rightIncluded = 1; } rkRes = rkEnd - rkStart - 1 + leftIncluded + rightIncluded; VOLT_DEBUG("Index Count ANSWER %ld = %ld - %ld - 1 + %d + %d\n", (long)rkRes, (long)rkEnd, (long)rkStart, leftIncluded, rightIncluded); tmptup.setNValue(0, ValueFactory::getBigIntValue( rkRes )); m_outputTable->insertTuple(tmptup); VOLT_DEBUG ("Index Count :\n %s", m_outputTable->debug().c_str()); return true; }
bool NestLoopExecutor::p_execute(const NValueArray ¶ms, ReadWriteTracker *tracker) { VOLT_DEBUG("executing NestLoop..."); NestLoopPlanNode* node = dynamic_cast<NestLoopPlanNode*>(abstract_node); assert(node); assert(node->getInputTables().size() == 2); Table* output_table_ptr = node->getOutputTable(); assert(output_table_ptr); // output table must be a temp table TempTable* output_table = dynamic_cast<TempTable*>(output_table_ptr); assert(output_table); Table* outer_table = node->getInputTables()[0]; assert(outer_table); Table* inner_table = node->getInputTables()[1]; assert(inner_table); VOLT_TRACE ("input table left:\n %s", outer_table->debug().c_str()); VOLT_TRACE ("input table right:\n %s", inner_table->debug().c_str()); // // Join Expression // AbstractExpression *predicate = node->getPredicate(); if (predicate) { predicate->substitute(params); VOLT_TRACE ("predicate: %s", predicate == NULL ? "NULL" : predicate->debug(true).c_str()); } int outer_cols = outer_table->columnCount(); int inner_cols = inner_table->columnCount(); TableTuple outer_tuple(node->getInputTables()[0]->schema()); TableTuple inner_tuple(node->getInputTables()[1]->schema()); TableTuple &joined = output_table->tempTuple(); TableIterator iterator0(outer_table); while (iterator0.next(outer_tuple)) { // populate output table's temp tuple with outer table's values // probably have to do this at least once - avoid doing it many // times per outer tuple for (int col_ctr = 0; col_ctr < outer_cols; col_ctr++) { joined.setNValue(col_ctr, outer_tuple.getNValue(col_ctr)); } TableIterator iterator1(inner_table); while (iterator1.next(inner_tuple)) { if (predicate == NULL || predicate->eval(&outer_tuple, &inner_tuple).isTrue()) { // Matched! Complete the joined tuple with the inner column values. for (int col_ctr = 0; col_ctr < inner_cols; col_ctr++) { joined.setNValue(col_ctr + outer_cols, inner_tuple.getNValue(col_ctr)); } output_table->insertTupleNonVirtual(joined); } } } return (true); }
bool NestLoopExecutor::p_execute(const NValueArray ¶ms) { VOLT_DEBUG("executing NestLoop..."); NestLoopPlanNode* node = dynamic_cast<NestLoopPlanNode*>(m_abstractNode); assert(node); assert(node->getInputTableCount() == 2); // output table must be a temp table assert(m_tmpOutputTable); Table* outer_table = node->getInputTable(); assert(outer_table); Table* inner_table = node->getInputTable(1); assert(inner_table); VOLT_TRACE ("input table left:\n %s", outer_table->debug().c_str()); VOLT_TRACE ("input table right:\n %s", inner_table->debug().c_str()); // // Pre Join Expression // AbstractExpression *preJoinPredicate = node->getPreJoinPredicate(); if (preJoinPredicate) { VOLT_TRACE ("Pre Join predicate: %s", preJoinPredicate == NULL ? "NULL" : preJoinPredicate->debug(true).c_str()); } // // Join Expression // AbstractExpression *joinPredicate = node->getJoinPredicate(); if (joinPredicate) { VOLT_TRACE ("Join predicate: %s", joinPredicate == NULL ? "NULL" : joinPredicate->debug(true).c_str()); } // // Where Expression // AbstractExpression *wherePredicate = node->getWherePredicate(); if (wherePredicate) { VOLT_TRACE ("Where predicate: %s", wherePredicate == NULL ? "NULL" : wherePredicate->debug(true).c_str()); } // Join type JoinType join_type = node->getJoinType(); assert(join_type == JOIN_TYPE_INNER || join_type == JOIN_TYPE_LEFT); LimitPlanNode* limit_node = dynamic_cast<LimitPlanNode*>(node->getInlinePlanNode(PLAN_NODE_TYPE_LIMIT)); int limit = -1; int offset = -1; if (limit_node) { limit_node->getLimitAndOffsetByReference(params, limit, offset); } int outer_cols = outer_table->columnCount(); int inner_cols = inner_table->columnCount(); TableTuple outer_tuple(node->getInputTable(0)->schema()); TableTuple inner_tuple(node->getInputTable(1)->schema()); const TableTuple& null_tuple = m_null_tuple.tuple(); TableIterator iterator0 = outer_table->iteratorDeletingAsWeGo(); int tuple_ctr = 0; int tuple_skipped = 0; ProgressMonitorProxy pmp(m_engine, this, inner_table); TableTuple join_tuple; if (m_aggExec != NULL) { VOLT_TRACE("Init inline aggregate..."); const TupleSchema * aggInputSchema = node->getTupleSchemaPreAgg(); join_tuple = m_aggExec->p_execute_init(params, &pmp, aggInputSchema, m_tmpOutputTable); } else { join_tuple = m_tmpOutputTable->tempTuple(); } bool earlyReturned = false; while ((limit == -1 || tuple_ctr < limit) && iterator0.next(outer_tuple)) { pmp.countdownProgress(); // populate output table's temp tuple with outer table's values // probably have to do this at least once - avoid doing it many // times per outer tuple join_tuple.setNValues(0, outer_tuple, 0, outer_cols); // did this loop body find at least one match for this tuple? bool match = false; // For outer joins if outer tuple fails pre-join predicate // (join expression based on the outer table only) // it can't match any of inner tuples if (preJoinPredicate == NULL || preJoinPredicate->eval(&outer_tuple, NULL).isTrue()) { // By default, the delete as we go flag is false. TableIterator iterator1 = inner_table->iterator(); while ((limit == -1 || tuple_ctr < limit) && iterator1.next(inner_tuple)) { pmp.countdownProgress(); // Apply join filter to produce matches for each outer that has them, // then pad unmatched outers, then filter them all if (joinPredicate == NULL || joinPredicate->eval(&outer_tuple, &inner_tuple).isTrue()) { match = true; // Filter the joined tuple if (wherePredicate == NULL || wherePredicate->eval(&outer_tuple, &inner_tuple).isTrue()) { // Check if we have to skip this tuple because of offset if (tuple_skipped < offset) { tuple_skipped++; continue; } ++tuple_ctr; // Matched! Complete the joined tuple with the inner column values. join_tuple.setNValues(outer_cols, inner_tuple, 0, inner_cols); if (m_aggExec != NULL) { if (m_aggExec->p_execute_tuple(join_tuple)) { // Get enough rows for LIMIT earlyReturned = true; break; } } else { m_tmpOutputTable->insertTempTuple(join_tuple); pmp.countdownProgress(); } } } } // END INNER WHILE LOOP } // END IF PRE JOIN CONDITION // // Left Outer Join // if (join_type == JOIN_TYPE_LEFT && !match && (limit == -1 || tuple_ctr < limit)) { // Still needs to pass the filter if (wherePredicate == NULL || wherePredicate->eval(&outer_tuple, &null_tuple).isTrue()) { // Check if we have to skip this tuple because of offset if (tuple_skipped < offset) { tuple_skipped++; continue; } ++tuple_ctr; join_tuple.setNValues(outer_cols, null_tuple, 0, inner_cols); if (m_aggExec != NULL) { if (m_aggExec->p_execute_tuple(join_tuple)) { earlyReturned = true; } } else { m_tmpOutputTable->insertTempTuple(join_tuple); pmp.countdownProgress(); } } } // END IF LEFT OUTER JOIN if (earlyReturned) { // Get enough rows for LIMIT inlined with aggregation break; } } // END OUTER WHILE LOOP if (m_aggExec != NULL) { m_aggExec->p_execute_finish(); } cleanupInputTempTable(inner_table); cleanupInputTempTable(outer_table); return (true); }
bool NestLoopIndexExecutor::p_execute(const NValueArray ¶ms) { assert(dynamic_cast<NestLoopIndexPlanNode*>(m_abstractNode)); NestLoopIndexPlanNode* node = static_cast<NestLoopIndexPlanNode*>(m_abstractNode); // output table must be a temp table assert(m_tmpOutputTable); // target table is a persistent table assert(dynamic_cast<PersistentTable*>(m_indexNode->getTargetTable())); PersistentTable* inner_table = static_cast<PersistentTable*>(m_indexNode->getTargetTable()); TableIndex* index = inner_table->index(m_indexNode->getTargetIndexName()); assert(index); IndexCursor indexCursor(index->getTupleSchema()); //outer_table is the input table that have tuples to be iterated assert(node->getInputTableCount() == 1); Table* outer_table = node->getInputTable(); assert(outer_table); VOLT_TRACE("executing NestLoopIndex with outer table: %s, inner table: %s", outer_table->debug().c_str(), inner_table->debug().c_str()); // // Substitute parameter to SEARCH KEY Note that the expressions // will include TupleValueExpression even after this substitution // int num_of_searchkeys = static_cast <int> (m_indexNode->getSearchKeyExpressions().size()); for (int ctr = 0; ctr < num_of_searchkeys; ctr++) { VOLT_TRACE("Search Key[%d]:\n%s", ctr, m_indexNode->getSearchKeyExpressions()[ctr]->debug(true).c_str()); } // end expression AbstractExpression* end_expression = m_indexNode->getEndExpression(); if (end_expression) { VOLT_TRACE("End Expression:\n%s", end_expression->debug(true).c_str()); } // post expression AbstractExpression* post_expression = m_indexNode->getPredicate(); if (post_expression != NULL) { VOLT_TRACE("Post Expression:\n%s", post_expression->debug(true).c_str()); } // initial expression AbstractExpression* initial_expression = m_indexNode->getInitialExpression(); if (initial_expression != NULL) { VOLT_TRACE("Initial Expression:\n%s", initial_expression->debug(true).c_str()); } // SKIP NULL EXPRESSION AbstractExpression* skipNullExpr = m_indexNode->getSkipNullPredicate(); // For reverse scan edge case NULL values and forward scan underflow case. if (skipNullExpr != NULL) { VOLT_DEBUG("Skip NULL Expression:\n%s", skipNullExpr->debug(true).c_str()); } // pre join expression AbstractExpression* prejoin_expression = node->getPreJoinPredicate(); if (prejoin_expression != NULL) { VOLT_TRACE("Prejoin Expression:\n%s", prejoin_expression->debug(true).c_str()); } // where expression AbstractExpression* where_expression = node->getWherePredicate(); if (where_expression != NULL) { VOLT_TRACE("Where Expression:\n%s", where_expression->debug(true).c_str()); } LimitPlanNode* limit_node = dynamic_cast<LimitPlanNode*>(node->getInlinePlanNode(PLAN_NODE_TYPE_LIMIT)); int limit = CountingPostfilter::NO_LIMIT; int offset = CountingPostfilter::NO_OFFSET; if (limit_node) { limit_node->getLimitAndOffsetByReference(params, limit, offset); } // Init the postfilter CountingPostfilter postfilter(m_tmpOutputTable, where_expression, limit, offset); // // OUTER TABLE ITERATION // TableTuple outer_tuple(outer_table->schema()); TableTuple inner_tuple(inner_table->schema()); TableIterator outer_iterator = outer_table->iteratorDeletingAsWeGo(); int num_of_outer_cols = outer_table->columnCount(); assert (outer_tuple.sizeInValues() == outer_table->columnCount()); assert (inner_tuple.sizeInValues() == inner_table->columnCount()); const TableTuple &null_inner_tuple = m_null_inner_tuple.tuple(); ProgressMonitorProxy pmp(m_engine->getExecutorContext(), this); // The table filter to keep track of inner tuples that don't match any of outer tuples for FULL joins TableTupleFilter innerTableFilter; if (m_joinType == JOIN_TYPE_FULL) { // Prepopulate the set with all inner tuples innerTableFilter.init(inner_table); } TableTuple join_tuple; // It's not immediately obvious here, so there's some subtlety to // note with respect to the schema of the join_tuple. // // The inner_tuple is used to represent the values from the inner // table in the case of the join predicate passing, and for left // outer joins, the null_tuple is used if there is no match. Both // of these tuples include the complete schema of the table being // scanned. The inner table is being scanned via an inlined scan // node, so there is no temp table corresponding to it. // // Predicates that are evaluated against the inner table should // therefore use the complete schema of the table being scanned. // // The join_tuple is the tuple that contains the values that we // actually want to put in the output of the join (or to aggregate // if there is an inlined agg plan node). This tuple needs to // omit the unused columns from the inner table. The inlined // index scan itself has an inlined project node that defines the // columns that should be output by the join, and omits those that // are not needed. So the join_tuple contains the columns we're // using from the outer table, followed by the "projected" schema // for the inlined scan of the inner table. if (m_aggExec != NULL) { VOLT_TRACE("Init inline aggregate..."); const TupleSchema * aggInputSchema = node->getTupleSchemaPreAgg(); join_tuple = m_aggExec->p_execute_init(params, &pmp, aggInputSchema, m_tmpOutputTable, &postfilter); } else { join_tuple = m_tmpOutputTable->tempTuple(); } VOLT_TRACE("<num_of_outer_cols>: %d\n", num_of_outer_cols); while (postfilter.isUnderLimit() && outer_iterator.next(outer_tuple)) { VOLT_TRACE("outer_tuple:%s", outer_tuple.debug(outer_table->name()).c_str()); pmp.countdownProgress(); // Set the join tuple columns that originate solely from the outer tuple. // Must be outside the inner loop in case of the empty inner table. join_tuple.setNValues(0, outer_tuple, 0, num_of_outer_cols); // did this loop body find at least one match for this tuple? bool outerMatch = false; // For outer joins if outer tuple fails pre-join predicate // (join expression based on the outer table only) // it can't match any of inner tuples if (prejoin_expression == NULL || prejoin_expression->eval(&outer_tuple, NULL).isTrue()) { int activeNumOfSearchKeys = num_of_searchkeys; VOLT_TRACE ("<Nested Loop Index exec, WHILE-LOOP...> Number of searchKeys: %d \n", num_of_searchkeys); IndexLookupType localLookupType = m_lookupType; SortDirectionType localSortDirection = m_sortDirection; VOLT_TRACE("Lookup type: %d\n", m_lookupType); VOLT_TRACE("SortDirectionType: %d\n", m_sortDirection); // did setting the search key fail (usually due to overflow) bool keyException = false; // // Now use the outer table tuple to construct the search key // against the inner table // const TableTuple& index_values = m_indexValues.tuple(); index_values.setAllNulls(); for (int ctr = 0; ctr < activeNumOfSearchKeys; ctr++) { // in a normal index scan, params would be substituted here, // but this scan fills in params outside the loop NValue candidateValue = m_indexNode->getSearchKeyExpressions()[ctr]->eval(&outer_tuple, NULL); if (candidateValue.isNull()) { // when any part of the search key is NULL, the result is false when it compares to anything. // do early return optimization, our index comparator may not handle null comparison correctly. keyException = true; break; } try { index_values.setNValue(ctr, candidateValue); } catch (const SQLException &e) { // This next bit of logic handles underflow and overflow while // setting up the search keys. // e.g. TINYINT > 200 or INT <= 6000000000 // re-throw if not an overflow or underflow // currently, it's expected to always be an overflow or underflow if ((e.getInternalFlags() & (SQLException::TYPE_OVERFLOW | SQLException::TYPE_UNDERFLOW | SQLException::TYPE_VAR_LENGTH_MISMATCH)) == 0) { throw e; } // handle the case where this is a comparison, rather than equality match // comparison is the only place where the executor might return matching tuples // e.g. TINYINT < 1000 should return all values if ((localLookupType != INDEX_LOOKUP_TYPE_EQ) && (ctr == (activeNumOfSearchKeys - 1))) { if (e.getInternalFlags() & SQLException::TYPE_OVERFLOW) { if ((localLookupType == INDEX_LOOKUP_TYPE_GT) || (localLookupType == INDEX_LOOKUP_TYPE_GTE)) { // gt or gte when key overflows breaks out // and only returns for left-outer keyException = true; break; // the outer while loop } else { // overflow of LT or LTE should be treated as LTE // to issue an "initial" forward scan localLookupType = INDEX_LOOKUP_TYPE_LTE; } } if (e.getInternalFlags() & SQLException::TYPE_UNDERFLOW) { if ((localLookupType == INDEX_LOOKUP_TYPE_LT) || (localLookupType == INDEX_LOOKUP_TYPE_LTE)) { // overflow of LT or LTE should be treated as LTE // to issue an "initial" forward scans localLookupType = INDEX_LOOKUP_TYPE_LTE; } else { // don't allow GTE because it breaks null handling localLookupType = INDEX_LOOKUP_TYPE_GT; } } if (e.getInternalFlags() & SQLException::TYPE_VAR_LENGTH_MISMATCH) { // shrink the search key and add the updated key to search key table tuple index_values.shrinkAndSetNValue(ctr, candidateValue); // search will be performed on shrinked key, so update lookup operation // to account for it switch (localLookupType) { case INDEX_LOOKUP_TYPE_LT: case INDEX_LOOKUP_TYPE_LTE: localLookupType = INDEX_LOOKUP_TYPE_LTE; break; case INDEX_LOOKUP_TYPE_GT: case INDEX_LOOKUP_TYPE_GTE: localLookupType = INDEX_LOOKUP_TYPE_GT; break; default: assert(!"IndexScanExecutor::p_execute - can't index on not equals"); return false; } } // if here, means all tuples with the previous searchkey // columns need to be scaned. activeNumOfSearchKeys--; if (localSortDirection == SORT_DIRECTION_TYPE_INVALID) { localSortDirection = SORT_DIRECTION_TYPE_ASC; } } // if a EQ comparison is out of range, then the tuple from // the outer loop returns no matches (except left-outer) else { keyException = true; } break; } // End catch block for under- or overflow when setting index key } // End for each active search key VOLT_TRACE("Searching %s", index_values.debug("").c_str()); // if a search value didn't fit into the targeted index key, skip this key if (!keyException) { // // Our index scan on the inner table is going to have three parts: // (1) Lookup tuples using the search key // // (2) For each tuple that comes back, check whether the // end_expression is false. If it is, then we stop // scanning. Otherwise... // // (3) Check whether the tuple satisfies the post expression. // If it does, then add it to the output table // // Use our search key to prime the index iterator // The loop through each tuple given to us by the iterator // // Essentially cut and pasted this if ladder from // index scan executor if (num_of_searchkeys > 0) { if (localLookupType == INDEX_LOOKUP_TYPE_EQ) { index->moveToKey(&index_values, indexCursor); } else if (localLookupType == INDEX_LOOKUP_TYPE_GT) { index->moveToGreaterThanKey(&index_values, indexCursor); } else if (localLookupType == INDEX_LOOKUP_TYPE_GTE) { index->moveToKeyOrGreater(&index_values, indexCursor); } else if (localLookupType == INDEX_LOOKUP_TYPE_LT) { index->moveToLessThanKey(&index_values, indexCursor); } else if (localLookupType == INDEX_LOOKUP_TYPE_LTE) { // find the entry whose key is greater than search key, // do a forward scan using initialExpr to find the correct // start point to do reverse scan bool isEnd = index->moveToGreaterThanKey(&index_values, indexCursor); if (isEnd) { index->moveToEnd(false, indexCursor); } else { while (!(inner_tuple = index->nextValue(indexCursor)).isNullTuple()) { pmp.countdownProgress(); if (initial_expression != NULL && !initial_expression->eval(&outer_tuple, &inner_tuple).isTrue()) { // just passed the first failed entry, so move 2 backward index->moveToBeforePriorEntry(indexCursor); break; } } if (inner_tuple.isNullTuple()) { index->moveToEnd(false, indexCursor); } } } else if (localLookupType == INDEX_LOOKUP_TYPE_GEO_CONTAINS) { index->moveToCoveringCell(&index_values, indexCursor); } else { return false; } } else { bool toStartActually = (localSortDirection != SORT_DIRECTION_TYPE_DESC); index->moveToEnd(toStartActually, indexCursor); } AbstractExpression* skipNullExprIteration = skipNullExpr; while (postfilter.isUnderLimit() && IndexScanExecutor::getNextTuple(localLookupType, &inner_tuple, index, &indexCursor, num_of_searchkeys)) { if (inner_tuple.isPendingDelete()) { continue; } VOLT_TRACE("inner_tuple:%s", inner_tuple.debug(inner_table->name()).c_str()); pmp.countdownProgress(); // // First check to eliminate the null index rows for UNDERFLOW case only // if (skipNullExprIteration != NULL) { if (skipNullExprIteration->eval(&outer_tuple, &inner_tuple).isTrue()) { VOLT_DEBUG("Index scan: find out null rows or columns."); continue; } skipNullExprIteration = NULL; } // // First check whether the end_expression is now false // if (end_expression != NULL && !end_expression->eval(&outer_tuple, &inner_tuple).isTrue()) { VOLT_TRACE("End Expression evaluated to false, stopping scan\n"); break; } // // Then apply our post-predicate to do further filtering // if (post_expression == NULL || post_expression->eval(&outer_tuple, &inner_tuple).isTrue()) { outerMatch = true; // The inner tuple passed the join conditions if (m_joinType == JOIN_TYPE_FULL) { // Mark inner tuple as matched innerTableFilter.updateTuple(inner_tuple, MATCHED_TUPLE); } // Still need to pass where filtering if (postfilter.eval(&outer_tuple, &inner_tuple)) { // // Try to put the tuple into our output table // Append the inner values to the end of our join tuple // for (int col_ctr = num_of_outer_cols; col_ctr < join_tuple.sizeInValues(); ++col_ctr) { join_tuple.setNValue(col_ctr, m_outputExpressions[col_ctr]->eval(&outer_tuple, &inner_tuple)); } VOLT_TRACE("join_tuple tuple: %s", join_tuple.debug(m_tmpOutputTable->name()).c_str()); VOLT_TRACE("MATCH: %s", join_tuple.debug(m_tmpOutputTable->name()).c_str()); outputTuple(postfilter, join_tuple, pmp); } } } // END INNER WHILE LOOP } // END IF INDEX KEY EXCEPTION CONDITION } // END IF PRE JOIN CONDITION // // Left/Full Outer Join // if (m_joinType != JOIN_TYPE_INNER && !outerMatch && postfilter.isUnderLimit()) { // Still needs to pass the filter if (postfilter.eval(&outer_tuple, &null_inner_tuple)) { // Matched! Complete the joined tuple with null inner column values. for (int col_ctr = num_of_outer_cols; col_ctr < join_tuple.sizeInValues(); ++col_ctr) { join_tuple.setNValue(col_ctr, m_outputExpressions[col_ctr]->eval(&outer_tuple, &null_inner_tuple)); } outputTuple(postfilter, join_tuple, pmp); } } } // END OUTER WHILE LOOP // // FULL Outer Join. Iterate over the unmatched inner tuples // if (m_joinType == JOIN_TYPE_FULL && postfilter.isUnderLimit()) { // Preset outer columns to null const TableTuple& null_outer_tuple = m_null_outer_tuple.tuple(); join_tuple.setNValues(0, null_outer_tuple, 0, num_of_outer_cols); TableTupleFilter_iter<UNMATCHED_TUPLE> endItr = innerTableFilter.end<UNMATCHED_TUPLE>(); for (TableTupleFilter_iter<UNMATCHED_TUPLE> itr = innerTableFilter.begin<UNMATCHED_TUPLE>(); itr != endItr && postfilter.isUnderLimit(); ++itr) { // Restore the tuple value uint64_t tupleAddr = innerTableFilter.getTupleAddress(*itr); inner_tuple.move((char *)tupleAddr); // Still needs to pass the filter assert(inner_tuple.isActive()); if (postfilter.eval(&null_outer_tuple, &inner_tuple)) { // Passed! Complete the joined tuple with the inner column values. for (int col_ctr = num_of_outer_cols; col_ctr < join_tuple.sizeInValues(); ++col_ctr) { join_tuple.setNValue(col_ctr, m_outputExpressions[col_ctr]->eval(&null_outer_tuple, &inner_tuple)); } outputTuple(postfilter, join_tuple, pmp); } } } if (m_aggExec != NULL) { m_aggExec->p_execute_finish(); } VOLT_TRACE ("result table:\n %s", m_tmpOutputTable->debug().c_str()); VOLT_TRACE("Finished NestLoopIndex"); cleanupInputTempTable(inner_table); cleanupInputTempTable(outer_table); return (true); }