bool operator()(TableTuple ta, TableTuple tb) { for (size_t i = 0; i < m_keyCount; ++i) { AbstractExpression* k = m_keys[i]; SortDirectionType dir = m_dirs[i]; int cmp = k->eval(&ta, NULL).compare(k->eval(&tb, NULL)); if (dir == SORT_DIRECTION_TYPE_ASC) { if (cmp < 0) return true; if (cmp > 0) return false; } else if (dir == SORT_DIRECTION_TYPE_DESC) { if (cmp < 0) return false; if (cmp > 0) return true; } else { throw SerializableEEException(VOLT_EE_EXCEPTION_TYPE_EEEXCEPTION, "Attempted to sort using" " SORT_DIRECTION_TYPE_INVALID"); } } return false; // ta == tb on these keys }
bool AbstractExecutor::TupleComparer::operator()(TableTuple ta, TableTuple tb) const { for (size_t i = 0; i < m_keyCount; ++i) { AbstractExpression* k = m_keys[i]; SortDirectionType dir = m_dirs[i]; int cmp = k->eval(&ta, NULL).compare(k->eval(&tb, NULL)); if (cmp < 0) return (dir == SORT_DIRECTION_TYPE_ASC); if (cmp > 0) return (dir == SORT_DIRECTION_TYPE_DESC); } return false; // ta == tb on these keys }
bool DistinctExecutor::p_execute(const NValueArray ¶ms) { DistinctPlanNode* node = dynamic_cast<DistinctPlanNode*>(m_abstractNode); assert(node); Table* output_table = node->getOutputTable(); assert(output_table); Table* input_table = node->getInputTables()[0]; assert(input_table); TableIterator iterator = input_table->iterator(); TableTuple tuple(input_table->schema()); // substitute params for distinct expression AbstractExpression *distinctExpression = node->getDistinctExpression(); distinctExpression->substitute(params); std::set<NValue, NValue::ltNValue> found_values; while (iterator.next(tuple)) { // // Check whether this value already exists in our list // NValue tuple_value = distinctExpression->eval(&tuple, NULL); if (found_values.find(tuple_value) == found_values.end()) { found_values.insert(tuple_value); if (!output_table->insertTuple(tuple)) { VOLT_ERROR("Failed to insert tuple from input table '%s' into" " output table '%s'", input_table->name().c_str(), output_table->name().c_str()); return false; } } } return true; }
bool MaterializedScanExecutor::p_execute(const NValueArray ¶ms) { MaterializedScanPlanNode* node = dynamic_cast<MaterializedScanPlanNode*>(m_abstractNode); assert(node); // output table has one column Table* output_table = node->getOutputTable(); TableTuple& tmptup = output_table->tempTuple(); assert(output_table); assert ((int)output_table->columnCount() == 1); // get the output type const TupleSchema::ColumnInfo *columnInfo = output_table->schema()->getColumnInfo(0); ValueType outputType = columnInfo->getVoltType(); bool outputCantBeNull = !columnInfo->allowNull; AbstractExpression* rowsExpression = node->getTableRowsExpression(); assert(rowsExpression); // get array nvalue NValue arrayNValue = rowsExpression->eval(); SortDirectionType sort_direction = node->getSortDirection(); // make a set to eliminate unique values in O(nlogn) time std::vector<NValue> sortedUniques; // iterate over the array of values and build a sorted/deduped set of // values that don't overflow or violate unique constaints arrayNValue.castAndSortAndDedupArrayForInList(outputType, sortedUniques); // insert all items in the set in order if (sort_direction != SORT_DIRECTION_TYPE_DESC) { std::vector<NValue>::const_iterator iter; for (iter = sortedUniques.begin(); iter != sortedUniques.end(); iter++) { if ((*iter).isNull() && outputCantBeNull) { continue; } tmptup.setNValue(0, *iter); output_table->insertTuple(tmptup); } } else { std::vector<NValue>::reverse_iterator reverse_iter; for (reverse_iter = sortedUniques.rbegin(); reverse_iter != sortedUniques.rend(); reverse_iter++) { if ((*reverse_iter).isNull() && outputCantBeNull) { continue; } tmptup.setNValue(0, *reverse_iter); output_table->insertTuple(tmptup); } } VOLT_TRACE("\n%s\n", output_table->debug().c_str()); VOLT_DEBUG("Finished Materializing a Table"); return true; }
TEST_F(FilterTest, ComplexFilter) { // WHERE val1=1 AND val2=2 AND val3=3 AND val4=4 // shared_ptr<AbstractExpression> equal1 // = ComparisonExpression::getInstance(EXPRESSION_TYPE_COMPARE_EQUAL, TupleValueExpression::getInstance(1), ConstantValueExpression::getInstance(voltdb::Value::newBigIntValue(1))); // shared_ptr<AbstractExpression> equal2 // = ComparisonExpression::getInstance(EXPRESSION_TYPE_COMPARE_EQUAL, TupleValueExpression::getInstance(2), ConstantValueExpression::getInstance(voltdb::Value::newBigIntValue(2))); // shared_ptr<AbstractExpression> equal3 // = ComparisonExpression::getInstance(EXPRESSION_TYPE_COMPARE_EQUAL, TupleValueExpression::getInstance(3), ConstantValueExpression::getInstance(voltdb::Value::newBigIntValue(3))); // shared_ptr<AbstractExpression> equal4 // = ComparisonExpression::getInstance(EXPRESSION_TYPE_COMPARE_EQUAL, TupleValueExpression::getInstance(4), ConstantValueExpression::getInstance(voltdb::Value::newBigIntValue(4))); // // shared_ptr<AbstractExpression> predicate3 // = ConjunctionExpression::getInstance(EXPRESSION_TYPE_CONJUNCTION_AND, equal3, equal4); // shared_ptr<AbstractExpression> predicate2 // = ConjunctionExpression::getInstance(EXPRESSION_TYPE_CONJUNCTION_AND, equal2, predicate3); // // ConjunctionExpression predicate(EXPRESSION_TYPE_CONJUNCTION_AND, equal1, predicate2); AbstractExpression *equal1 = comparisonFactory(EXPRESSION_TYPE_COMPARE_EQUAL, new TupleValueExpression(1, std::string("tablename"), std::string("colname")), constantValueFactory(ValueFactory::getBigIntValue(1))); AbstractExpression *equal2 = comparisonFactory(EXPRESSION_TYPE_COMPARE_EQUAL, new TupleValueExpression(2, std::string("tablename"), std::string("colname")), constantValueFactory(ValueFactory::getBigIntValue(2))); AbstractExpression *equal3 = comparisonFactory(EXPRESSION_TYPE_COMPARE_EQUAL, new TupleValueExpression(3, std::string("tablename"), std::string("colname")), constantValueFactory(ValueFactory::getBigIntValue(3))); AbstractExpression *equal4 = comparisonFactory(EXPRESSION_TYPE_COMPARE_EQUAL, new TupleValueExpression(4, std::string("tablename"), std::string("colname")), constantValueFactory(ValueFactory::getBigIntValue(4))); AbstractExpression *predicate3 = conjunctionFactory(EXPRESSION_TYPE_CONJUNCTION_AND, equal3, equal4); AbstractExpression *predicate2 = conjunctionFactory(EXPRESSION_TYPE_CONJUNCTION_AND, equal2, predicate3); AbstractExpression *predicate = conjunctionFactory(EXPRESSION_TYPE_CONJUNCTION_AND, equal1, predicate2); // ::printf("\nFilter:%s\n", predicate->debug().c_str()); int count = 0; TableIterator iter = table->iterator(); TableTuple match(table->schema()); while (iter.next(match)) { if (predicate->eval(&match, NULL).isTrue()) { //::printf(" match:%s\n", match->debug(table).c_str()); ++count; } } ASSERT_EQ(5, count); delete predicate; }
TEST_F(FilterTest, SubstituteFilter) { // WHERE id <= 20 AND val4=$1 // shared_ptr<AbstractExpression> equal1 // = ComparisonExpression::getInstance(EXPRESSION_TYPE_COMPARE_LESSTHANOREQUALTO, TupleValueExpression::getInstance(0), ConstantValueExpression::getInstance(voltdb::Value::newBigIntValue(20))); // // shared_ptr<AbstractExpression> equal2 // = ComparisonExpression::getInstance(EXPRESSION_TYPE_COMPARE_EQUAL, TupleValueExpression::getInstance(4), ParameterValueExpression::getInstance(0)); // // ConjunctionExpression predicate(EXPRESSION_TYPE_CONJUNCTION_AND, equal1, equal2); AbstractExpression *tv1 = new TupleValueExpression(0, std::string("tablename"), std::string("colname")); AbstractExpression *cv1 = constantValueFactory(ValueFactory::getBigIntValue(20)); AbstractExpression *equal1 = comparisonFactory(EXPRESSION_TYPE_COMPARE_LESSTHANOREQUALTO, tv1, cv1); AbstractExpression *tv2 = new TupleValueExpression(4, std::string("tablename"), std::string("colname")); AbstractExpression *pv2 = parameterValueFactory(0); AbstractExpression *equal2 = comparisonFactory(EXPRESSION_TYPE_COMPARE_EQUAL, tv2, pv2); AbstractExpression *predicate = conjunctionFactory(EXPRESSION_TYPE_CONJUNCTION_AND, equal1, equal2); // ::printf("\nFilter:%s\n", predicate->debug().c_str()); for (int64_t implantedValue = 1; implantedValue < 5; ++implantedValue) { NValueArray params(1); params[0] = ValueFactory::getBigIntValue(implantedValue); predicate->substitute(params); // ::printf("\nSubstituted Filter:%s\n", predicate->debug().c_str()); // ::printf("\tLEFT: %s\n", predicate->getLeft()->debug().c_str()); // ::printf("\tRIGHT: %s\n", predicate->getRight()->debug().c_str()); int count = 0; TableIterator iter = table->iterator(); TableTuple match(table->schema()); while (iter.next(match)) { if (predicate->eval(&match, NULL).isTrue()) { ++count; } } ASSERT_EQ(3, count); } delete predicate; }
/* * * Helper method responsible for inserting the results of the * aggregation into a new tuple in the output table as well as passing * through any additional columns from the input table. */ inline void WindowFunctionExecutor::insertOutputTuple() { TableTuple& tempTuple = m_tmpOutputTable->tempTuple(); // We copy the aggregate values into the output tuple, // then the passthrough columns. WindowAggregate** aggs = m_aggregateRow->getAggregates(); for (int ii = 0; ii < getAggregateCount(); ii++) { NValue result = aggs[ii]->finalize(tempTuple.getSchema()->columnType(ii)); tempTuple.setNValue(ii, result); } VOLT_TRACE("Setting passthrough columns"); size_t tupleSize = tempTuple.sizeInValues(); for (int ii = getAggregateCount(); ii < tupleSize; ii += 1) { AbstractExpression *expr = m_outputColumnExpressions[ii]; tempTuple.setNValue(ii, expr->eval(&(m_aggregateRow->getPassThroughTuple()))); } m_tmpOutputTable->insertTempTuple(tempTuple); VOLT_TRACE("output_table:\n%s", m_tmpOutputTable->debug().c_str()); }
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)); // 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* 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 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 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 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); }
NValue SubqueryExpression::eval(const TableTuple *tuple1, const TableTuple *tuple2) const { // Get the subquery context with the last evaluation result and parameters used to obtain that result ExecutorContext* exeContext = ExecutorContext::getExecutorContext(); SubqueryContext* context = exeContext->getSubqueryContext(m_subqueryId); bool hasPriorResult = (context != NULL) && context->hasValidResult(); bool paramsChanged = false; NValueArray& parameterContainer = *(exeContext->getParameterContainer()); VOLT_TRACE ("Running subquery: %d", m_subqueryId); // Substitute parameters. if (m_tveParams.get() != NULL) { size_t paramsCnt = m_tveParams->size(); for (size_t i = 0; i < paramsCnt; ++i) { AbstractExpression* tveParam = (*m_tveParams)[i]; NValue param = tveParam->eval(tuple1, tuple2); // compare the new param value with the previous one. Since this parameter is set // by this subquery, no other subquery can change its value. So, we don't need to // save its value on the side for future comparisons. NValue& prevParam = parameterContainer[m_paramIdxs[i]]; if (hasPriorResult) { if (param.compare(prevParam) == VALUE_COMPARE_EQUAL) { continue; } paramsChanged = true; } // Update the value stored in the executor context's parameter container: prevParam = param.copyNValue(); } } // Note the other (non-tve) parameter values and check if they've changed since the last invocation. if (hasPriorResult) { std::vector<NValue>& lastParams = context->accessLastParams(); assert(lastParams.size() == m_otherParamIdxs.size()); for (size_t i = 0; i < lastParams.size(); ++i) { NValue& prevParam = parameterContainer[m_otherParamIdxs[i]]; if (lastParams[i].compare(prevParam) != VALUE_COMPARE_EQUAL) { lastParams[i] = prevParam.copyNValue(); paramsChanged = true; } } if (paramsChanged) { // If parameters have changed since the last execution, // the cached result of the prior execution is obsolete. // In particular, it should not be mistaken for the correct result for the current // parameters in the event that the current execution fails. // This subquery context will be restored to validity when its new result is set // after execution succeeds. context->invalidateResult(); } else { // If the parameters haven't changed since the last execution, reuse the known result. return context->getResult(); } } // Out of luck. Need to run the executors. Clean up the output tables with cached results exeContext->cleanupExecutorsForSubquery(m_subqueryId); exeContext->executeExecutors(m_subqueryId); if (context == NULL) { // Preserve the value for the next run. Only 'other' parameters need to be copied std::vector<NValue> lastParams; lastParams.reserve(m_otherParamIdxs.size()); for (size_t i = 0; i < m_otherParamIdxs.size(); ++i) { NValue& prevParam = parameterContainer[m_otherParamIdxs[i]]; lastParams.push_back(prevParam.copyNValue()); } context = exeContext->setSubqueryContext(m_subqueryId, lastParams); } // Update the cached result for the current params. All params are already updated NValue retval = ValueFactory::getIntegerValue(m_subqueryId); context->setResult(retval); return retval; }