TEST_F(TableTupleTest, VolatileStandAloneTuple) { UniqueEngine engine = UniqueEngineBuilder().build(); // A schema with // - one fixed size column // - one inlined variable-length column // - one non-inlined variable-length column ScopedTupleSchema schema{Tools::buildSchema(VALUE_TYPE_BIGINT, std::make_pair(VALUE_TYPE_VARCHAR, 12), std::make_pair(VALUE_TYPE_VARCHAR, 256))}; StandAloneTupleStorage standAloneTuple{schema.get()}; TableTuple tuple = standAloneTuple.tuple(); Tools::setTupleValues(&tuple, int64_t(0), "foo", "foo bar"); // Stand alone tuples are similar to pool-backed tuples. ASSERT_TRUE(tuple.inlinedDataIsVolatile()); ASSERT_FALSE(tuple.nonInlinedDataIsVolatile()); NValue nv = tuple.getNValue(0); ASSERT_FALSE(nv.getVolatile()); nv = tuple.getNValue(1); ASSERT_TRUE(nv.getVolatile()); nv = tuple.getNValue(2); ASSERT_FALSE(nv.getVolatile()); }
void MaterializedViewMetadata::processTupleDelete(TableTuple &oldTuple) { // don't change the view if this tuple doesn't match the predicate if (m_filterPredicate && (m_filterPredicate->eval(&oldTuple, NULL).isFalse())) return; // this will assert if the tuple isn't there as param expected is true findExistingTuple(oldTuple, true); // clear the tuple that will be built to insert or overwrite memset(m_updatedTupleBackingStore, 0, m_target->schema()->tupleLength() + 1); //printf(" Existing tuple: %s.\n", m_existingTuple.debugNoHeader().c_str()); //fflush(stdout); // set up the first column, which is a count NValue count = m_existingTuple.getNValue(m_groupByColumnCount).op_decrement(); //printf(" Count is: %d.\n", (int)(m_existingTuple.getSlimValue(m_groupByColumnCount).getBigInt())); //fflush(stdout); // check if we should remove the tuple if (count.isZero()) { m_target->deleteTuple(m_existingTuple, true); return; } // assume from here that we're just updating the existing row int colindex = 0; // set up the first n columns, based on group-by columns for (colindex = 0; colindex < m_groupByColumnCount; colindex++) { m_updatedTuple.setNValue(colindex, oldTuple.getNValue(m_groupByColumns[colindex])); } m_updatedTuple.setNValue(colindex, count); colindex++; // set values for the other columns for (int i = colindex; i < m_outputColumnCount; i++) { NValue oldValue = oldTuple.getNValue(m_outputColumnSrcTableIndexes[i]); NValue existingValue = m_existingTuple.getNValue(i); if (m_outputColumnAggTypes[i] == EXPRESSION_TYPE_AGGREGATE_SUM) { m_updatedTuple.setNValue(i, existingValue.op_subtract(oldValue)); } else if (m_outputColumnAggTypes[i] == EXPRESSION_TYPE_AGGREGATE_COUNT) { m_updatedTuple.setNValue(i, existingValue.op_decrement()); } else { throw SerializableEEException(VOLT_EE_EXCEPTION_TYPE_EEEXCEPTION, "Error in materialized view table" " update."); } } // update the row // shouldn't need to update indexes as this shouldn't ever change the key m_target->updateTuple(m_updatedTuple, m_existingTuple, false); }
void MaterializedViewMetadata::processTupleInsert(TableTuple &newTuple) { // don't change the view if this tuple doesn't match the predicate if (m_filterPredicate && (m_filterPredicate->eval(&newTuple, NULL).isFalse())) return; bool exists = findExistingTuple(newTuple); if (!exists) { // create a blank tuple m_existingTuple.move(m_emptyTupleBackingStore); } // clear the tuple that will be built to insert or overwrite memset(m_updatedTupleBackingStore, 0, m_target->schema()->tupleLength() + 1); int colindex = 0; // set up the first n columns, based on group-by columns for (colindex = 0; colindex < m_groupByColumnCount; colindex++) { m_updatedTuple.setNValue(colindex, newTuple.getNValue(m_groupByColumns[colindex])); } // set up the next column, which is a count m_updatedTuple.setNValue(colindex, m_existingTuple.getNValue(colindex).op_increment()); colindex++; // set values for the other columns for (int i = colindex; i < m_outputColumnCount; i++) { NValue newValue = newTuple.getNValue(m_outputColumnSrcTableIndexes[i]); NValue existingValue = m_existingTuple.getNValue(i); if (m_outputColumnAggTypes[i] == EXPRESSION_TYPE_AGGREGATE_SUM) { m_updatedTuple.setNValue(i, newValue.op_add(existingValue)); } else if (m_outputColumnAggTypes[i] == EXPRESSION_TYPE_AGGREGATE_COUNT) { m_updatedTuple.setNValue(i, existingValue.op_increment()); } else { char message[128]; sprintf(message, "Error in materialized view table update for" " col %d. Expression type %d", i, m_outputColumnAggTypes[i]); throw SerializableEEException(VOLT_EE_EXCEPTION_TYPE_EEEXCEPTION, message); } } // update or insert the row if (exists) { // shouldn't need to update indexes as this shouldn't ever change the // key m_target->updateTuple(m_updatedTuple, m_existingTuple, false); } else { m_target->insertTuple(m_updatedTuple); } }
TEST_F(TableTupleTest, VolatileTempTuplePersistent) { UniqueEngine engine = UniqueEngineBuilder().build(); // A schema with // - one fixed-length column // - one inlined variable-length column // - one non-inlined variable-length column TupleSchema *schema = Tools::buildSchema(VALUE_TYPE_BIGINT, std::make_pair(VALUE_TYPE_VARCHAR, 12), std::make_pair(VALUE_TYPE_VARCHAR, 256)); std::vector<std::string> columnNames{"id", "inlined", "noninlined"}; char signature[20]; std::unique_ptr<Table> table{TableFactory::getPersistentTable(0, "perstbl", schema, columnNames, signature)}; TableTuple tuple = table->tempTuple(); Tools::setTupleValues(&tuple, int64_t(0), "foo", "foo bar"); ASSERT_TRUE(tuple.inlinedDataIsVolatile()); ASSERT_FALSE(tuple.nonInlinedDataIsVolatile()); NValue nv = tuple.getNValue(0); ASSERT_FALSE(nv.getVolatile()); nv = tuple.getNValue(1); ASSERT_TRUE(nv.getVolatile()); nv = tuple.getNValue(2); ASSERT_FALSE(nv.getVolatile()); table->insertTuple(tuple); TableIterator it = table->iterator(); TableTuple iterTuple{schema}; while (it.next(iterTuple)) { // Regular, TupleBlock-backed tuples are never volatile. ASSERT_FALSE(iterTuple.inlinedDataIsVolatile()); ASSERT_FALSE(iterTuple.nonInlinedDataIsVolatile()); nv = iterTuple.getNValue(0); ASSERT_FALSE(nv.getVolatile()); nv = iterTuple.getNValue(1); ASSERT_FALSE(nv.getVolatile()); nv = iterTuple.getNValue(2); ASSERT_FALSE(nv.getVolatile()); } }
int WindowFunctionExecutor::compareTuples(const TableTuple &tuple1, const TableTuple &tuple2) const { const TupleSchema *schema = tuple1.getSchema(); assert (schema == tuple2.getSchema()); for (int ii = schema->columnCount() - 1; ii >= 0; --ii) { int cmp = tuple2.getNValue(ii) .compare(tuple1.getNValue(ii)); if (cmp != 0) { return cmp; } } return 0; }
TEST_F(TableTupleFilterTest, tableTupleFilterTest) { static const int MARKER = 33; TempTable* table = getTempTable(); TableTupleFilter tableFilter; tableFilter.init(table); int tuplePerBlock = table->getTuplesPerBlock(); // make sure table spans more than one block ASSERT_TRUE(NUM_OF_TUPLES / tuplePerBlock > 1); TableTuple tuple = table->tempTuple(); TableIterator iterator = table->iterator(); // iterator over and mark every 5th tuple int counter = 0; std::multiset<int64_t> control_values; while(iterator.next(tuple)) { if (++counter % 5 == 0) { NValue nvalue = tuple.getNValue(1); int64_t value = ValuePeeker::peekBigInt(nvalue); control_values.insert(value); tableFilter.updateTuple(tuple, MARKER); } } TableTupleFilter_iter<MARKER> endItr = tableFilter.end<MARKER>(); for (TableTupleFilter_iter<MARKER> itr = tableFilter.begin<MARKER>(); itr != endItr; ++itr) { uint64_t tupleAddr = tableFilter.getTupleAddress(*itr); tuple.move((char *)tupleAddr); ASSERT_TRUE(tuple.isActive()); NValue nvalue = tuple.getNValue(1); int64_t value = ValuePeeker::peekBigInt(nvalue); ASSERT_FALSE(control_values.empty()); auto it = control_values.find(value); ASSERT_NE(it, control_values.end()); control_values.erase(it); } ASSERT_TRUE(control_values.empty()); }
void DRTupleStream::updateTxnHash(TableTuple &tuple, int partitionColumn) { if (partitionColumn != -1 && m_txnPkHash != LONG_MAX) { int64_t txnPkHash = (int64_t)tuple.getNValue(partitionColumn).murmurHash3(); if (m_txnPkHash == LONG_MIN) { m_txnPkHash = txnPkHash; } else if (txnPkHash != m_txnPkHash) { m_txnPkHash = LONG_MAX; } } }
NValue compare_tuple(const TableTuple& tuple1, const TableTuple& tuple2) { assert(tuple1.getSchema()->columnCount() == tuple2.getSchema()->columnCount()); NValue fallback_result = OP::includes_equality() ? NValue::getTrue() : NValue::getFalse(); int schemaSize = tuple1.getSchema()->columnCount(); for (int columnIdx = 0; columnIdx < schemaSize; ++columnIdx) { NValue value1 = tuple1.getNValue(columnIdx); if (value1.isNull()) { fallback_result = NValue::getNullValue(VALUE_TYPE_BOOLEAN); if (OP::implies_null_for_row()) { return fallback_result; } continue; } NValue value2 = tuple2.getNValue(columnIdx); if (value2.isNull()) { fallback_result = NValue::getNullValue(VALUE_TYPE_BOOLEAN); if (OP::implies_null_for_row()) { return fallback_result; } continue; } if (OP::compare_withoutNull(value1, tuple2.getNValue(columnIdx)).isTrue()) { if (OP::implies_true_for_row(value1, value2)) { // allow early return on strict inequality return NValue::getTrue(); } } else { if (OP::implies_false_for_row(value1, value2)) { // allow early return on strict inequality return NValue::getFalse(); } } } // The only cases that have not already short-circuited involve all equal columns. // Each op either includes or excludes that particular case. return fallback_result; }
TEST_F(PersistentTableMemStatsTest, UpdateAndUndoTest) { initTable(true); tableutil::addRandomTuples(m_table, 10); int64_t orig_size = m_table->nonInlinedMemorySize(); //cout << "Original non-inline size: " << orig_size << endl; TableTuple tuple(m_tableSchema); tableutil::getRandomTuple(m_table, tuple); //cout << "Retrieved random tuple " << endl << tuple.debugNoHeader() << endl; size_t removed_bytes = StringRef::computeStringMemoryUsed(ValuePeeker::peekObjectLength(tuple.getNValue(1))) + StringRef::computeStringMemoryUsed(ValuePeeker::peekObjectLength(tuple.getNValue(2))); //cout << "Removing bytes from table: " << removed_bytes << endl; /* * A copy of the tuple to modify and use as a source tuple when * updating the new tuple. */ TableTuple tempTuple = m_table->tempTuple(); tempTuple.copy(tuple); string strval = "123456"; NValue new_string = ValueFactory::getStringValue(strval); tempTuple.setNValue(1, new_string); //cout << "Created random tuple " << endl << tempTuple.debugNoHeader() << endl; size_t added_bytes = StringRef::computeStringMemoryUsed(ValuePeeker::peekObjectLength(tempTuple.getNValue(1))) + StringRef::computeStringMemoryUsed(ValuePeeker::peekObjectLength(tempTuple.getNValue(2))); //cout << "Adding bytes to table: " << added_bytes << endl; m_engine->setUndoToken(INT64_MIN + 2); // this next line is a testing hack until engine data is // de-duplicated with executorcontext data m_engine->getExecutorContext(); m_table->updateTuple(tempTuple, tuple, true); ASSERT_EQ(orig_size + added_bytes - removed_bytes, m_table->nonInlinedMemorySize()); m_engine->undoUndoToken(INT64_MIN + 2); ASSERT_EQ(orig_size, m_table->nonInlinedMemorySize()); //tuple.freeObjectColumns(); //tempTuple.freeObjectColumns(); //delete [] tuple.address(); //delete[] tempTuple.address(); new_string.free(); }
bool MaterializedViewMetadata::findExistingTuple(TableTuple &oldTuple, bool expected) { // find the key for this tuple (which is the group by columns) for (int i = 0; i < m_groupByColumnCount; i++) m_searchKey.setNValue(i, oldTuple.getNValue(m_groupByColumns[i])); // determine if the row exists (create the empty one if it doesn't) m_index->moveToKey(&m_searchKey); m_existingTuple = m_index->nextValueAtKey(); if (m_existingTuple.isNullTuple()) { if (expected) { throw SerializableEEException(VOLT_EE_EXCEPTION_TYPE_EEEXCEPTION, "MaterializedViewMetadata went" " looking for a tuple in the view and" " expected to find it but didn't"); } return false; } else { return true; } }
bool MaterializedViewMetadata::findExistingTuple(TableTuple &oldTuple, bool expected) { // find the key for this tuple (which is the group by columns) for (int i = 0; i < m_groupByColumnCount; i++) { m_searchKey.setNValue(i, oldTuple.getNValue(m_groupByColumns[i])); } // determine if the row exists (create the empty one if it doesn't) m_index->moveToKey(&m_searchKey); m_existingTuple = m_index->nextValueAtKey(); if (m_existingTuple.isNullTuple()) { if (expected) { std::string name = m_target->name(); throwFatalException("MaterializedViewMetadata for table %s went" " looking for a tuple in the view and" " expected to find it but didn't", name.c_str()); } return false; } else { return true; } }
bool UpdateExecutor::p_execute(const NValueArray ¶ms, ReadWriteTracker *tracker) { assert(m_inputTable); assert(m_targetTable); VOLT_TRACE("INPUT TABLE: %s\n", m_inputTable->debug().c_str()); VOLT_TRACE("TARGET TABLE - BEFORE: %s\n", m_targetTable->debug().c_str()); assert(m_inputTuple.sizeInValues() == m_inputTable->columnCount()); assert(m_targetTuple.sizeInValues() == m_targetTable->columnCount()); TableIterator input_iterator(m_inputTable); while (input_iterator.next(m_inputTuple)) { // // OPTIMIZATION: Single-Sited Query Plans // If our beloved UpdatePlanNode is apart of a single-site query plan, // then the first column in the input table will be the address of a // tuple on the target table that we will want to update. This saves us // the trouble of having to do an index lookup // void *target_address = m_inputTuple.getNValue(0).castAsAddress(); m_targetTuple.move(target_address); // Read/Write Set Tracking if (tracker != NULL) { tracker->markTupleWritten(m_targetTable, &m_targetTuple); } // Loop through INPUT_COL_IDX->TARGET_COL_IDX mapping and only update // the values that we need to. The key thing to note here is that we // grab a temp tuple that is a copy of the target tuple (i.e., the tuple // we want to update). This insures that if the input tuple is somehow // bringing garbage with it, we're only going to copy what we really // need to into the target tuple. // TableTuple &tempTuple = m_targetTable->getTempTupleInlined(m_targetTuple); for (int map_ctr = 0; map_ctr < m_inputTargetMapSize; map_ctr++) { tempTuple.setNValue(m_inputTargetMap[map_ctr].second, m_inputTuple.getNValue(m_inputTargetMap[map_ctr].first)); } // if there is a partition column for the target table if (m_partitionColumn != -1) { // check for partition problems // get the value for the partition column NValue value = tempTuple.getNValue(m_partitionColumn); bool isLocal = m_engine->isLocalSite(value); // if it doesn't map to this site if (!isLocal) { VOLT_ERROR("Mispartitioned tuple in single-partition plan for" " table '%s'", m_targetTable->name().c_str()); return false; } } #ifdef ARIES if(m_engine->isARIESEnabled()){ // add persistency check: PersistentTable* table = dynamic_cast<PersistentTable*>(m_targetTable); // only log if we are writing to a persistent table. if (table != NULL) { // before image -- target is old val with no updates // XXX: what about uninlined fields? // should we not be doing // m_targetTable->getTempTupleInlined(m_targetTuple); instead? TableTuple *beforeImage = &m_targetTuple; // after image -- temp is NEW, created using target and input TableTuple *afterImage = &tempTuple; TableTuple *keyTuple = NULL; char *keydata = NULL; std::vector<int32_t> modifiedCols; int32_t numCols = -1; // See if we can do better by using an index instead TableIndex *index = table->primaryKeyIndex(); if (index != NULL) { // First construct tuple for primary key keydata = new char[index->getKeySchema()->tupleLength()]; keyTuple = new TableTuple(keydata, index->getKeySchema()); for (int i = 0; i < index->getKeySchema()->columnCount(); i++) { keyTuple->setNValue(i, beforeImage->getNValue(index->getColumnIndices()[i])); } // no before image need be recorded, just the primary key beforeImage = NULL; } // Set the modified column list numCols = m_inputTargetMapSize; modifiedCols.resize(m_inputTargetMapSize, -1); for (int map_ctr = 0; map_ctr < m_inputTargetMapSize; map_ctr++) { // can't use column-id directly, otherwise we would go over vector bounds int pos = m_inputTargetMap[map_ctr].first - 1; modifiedCols.at(pos) = m_inputTargetMap[map_ctr].second; } // Next, let the input tuple be the diff after image afterImage = &m_inputTuple; LogRecord *logrecord = new LogRecord(computeTimeStamp(), LogRecord::T_UPDATE,// this is an update record LogRecord::T_FORWARD,// the system is running normally -1,// XXX: prevLSN must be fetched from table! m_engine->getExecutorContext()->currentTxnId() ,// txn id m_engine->getSiteId(),// which execution site m_targetTable->name(),// the table affected keyTuple,// primary key numCols, (numCols > 0) ? &modifiedCols : NULL, beforeImage, afterImage ); size_t logrecordLength = logrecord->getEstimatedLength(); char *logrecordBuffer = new char[logrecordLength]; FallbackSerializeOutput output; output.initializeWithPosition(logrecordBuffer, logrecordLength, 0); logrecord->serializeTo(output); LogManager* m_logManager = this->m_engine->getLogManager(); Logger m_ariesLogger = m_logManager->getAriesLogger(); //VOLT_WARN("m_logManager : %p AriesLogger : %p",&m_logManager, &m_ariesLogger); const Logger *logger = m_logManager->getThreadLogger(LOGGERID_MM_ARIES); logger->log(LOGLEVEL_INFO, output.data(), output.position()); delete[] logrecordBuffer; logrecordBuffer = NULL; delete logrecord; logrecord = NULL; if (keydata != NULL) { delete[] keydata; keydata = NULL; } if (keyTuple != NULL) { delete keyTuple; keyTuple = NULL; } } } #endif if (!m_targetTable->updateTuple(tempTuple, m_targetTuple, m_updatesIndexes)) { VOLT_INFO("Failed to update tuple from table '%s'", m_targetTable->name().c_str()); return false; } } VOLT_TRACE("TARGET TABLE - AFTER: %s\n", m_targetTable->debug().c_str()); // TODO lets output result table here, not in result executor. same thing in // delete/insert // add to the planfragments count of modified tuples m_engine->m_tuplesModified += m_inputTable->activeTupleCount(); return true; }
NValue compare(const TableTuple& tuple) const { assert(tuple.getSchema()->columnCount() == 1); return compare<OP>(tuple.getNValue(0)); }
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; }
void setSearchKeyFromTuple(TableTuple &source) { keyTuple.setNValue(0, source.getNValue(1)); keyTuple.setNValue(1, source.getNValue(2)); }
bool DeleteExecutor::p_execute(const NValueArray ¶ms, ReadWriteTracker *tracker) { assert(m_targetTable); if (m_truncate) { VOLT_TRACE("truncating table %s...", m_targetTable->name().c_str()); // count the truncated tuples as deleted m_engine->m_tuplesModified += m_inputTable->activeTupleCount(); #ifdef ARIES if(m_engine->isARIESEnabled()){ // no need of persistency check, m_targetTable is // always persistent for deletes LogRecord *logrecord = new LogRecord(computeTimeStamp(), LogRecord::T_TRUNCATE,// this is a truncate record LogRecord::T_FORWARD,// the system is running normally -1,// XXX: prevLSN must be fetched from table! m_engine->getExecutorContext()->currentTxnId() ,// txn id m_engine->getSiteId(),// which execution site m_targetTable->name(),// the table affected NULL,// primary key irrelevant -1,// irrelevant numCols NULL,// list of modified cols irrelevant NULL,// before image irrelevant NULL// after image irrelevant ); size_t logrecordLength = logrecord->getEstimatedLength(); char *logrecordBuffer = new char[logrecordLength]; FallbackSerializeOutput output; output.initializeWithPosition(logrecordBuffer, logrecordLength, 0); logrecord->serializeTo(output); LogManager* m_logManager = this->m_engine->getLogManager(); Logger m_ariesLogger = m_logManager->getAriesLogger(); //VOLT_WARN("m_logManager : %p AriesLogger : %p",&m_logManager, &m_ariesLogger); const Logger *logger = m_logManager->getThreadLogger(LOGGERID_MM_ARIES); logger->log(LOGLEVEL_INFO, output.data(), output.position()); delete[] logrecordBuffer; logrecordBuffer = NULL; delete logrecord; logrecord = NULL; } #endif //m_engine->context().incrementTuples(m_targetTable->activeTupleCount()); // actually delete all the tuples m_targetTable->deleteAllTuples(true); return true; } // XXX : ARIES : Not sure if else is needed ? assert(m_inputTable); assert(m_inputTuple.sizeInValues() == m_inputTable->columnCount()); assert(m_targetTuple.sizeInValues() == m_targetTable->columnCount()); TableIterator inputIterator(m_inputTable); while (inputIterator.next(m_inputTuple)) { // // OPTIMIZATION: Single-Sited Query Plans // If our beloved DeletePlanNode is apart of a single-site query plan, // then the first column in the input table will be the address of a // tuple on the target table that we will want to blow away. This saves // us the trouble of having to do an index lookup // void *targetAddress = m_inputTuple.getNValue(0).castAsAddress(); m_targetTuple.move(targetAddress); // Read/Write Set Tracking if (tracker != NULL) { tracker->markTupleWritten(m_targetTable, &m_targetTuple); } #ifdef ARIES if(m_engine->isARIESEnabled()){ // no need of persistency check, m_targetTable is // always persistent for deletes // before image -- target is tuple to be deleted. TableTuple *beforeImage = &m_targetTuple; TableTuple *keyTuple = NULL; char *keydata = NULL; // See if we use an index instead TableIndex *index = m_targetTable->primaryKeyIndex(); if (index != NULL) { // First construct tuple for primary key keydata = new char[index->getKeySchema()->tupleLength()]; keyTuple = new TableTuple(keydata, index->getKeySchema()); for (int i = 0; i < index->getKeySchema()->columnCount(); i++) { keyTuple->setNValue(i, beforeImage->getNValue(index->getColumnIndices()[i])); } // no before image need be recorded, just the primary key beforeImage = NULL; } LogRecord *logrecord = new LogRecord(computeTimeStamp(), LogRecord::T_DELETE,// this is a delete record LogRecord::T_FORWARD,// the system is running normally -1,// XXX: prevLSN must be fetched from table! m_engine->getExecutorContext()->currentTxnId() ,// txn id m_engine->getSiteId(),// which execution site m_targetTable->name(),// the table affected keyTuple,// primary key -1,// must delete all columns NULL,// no list of modified cols beforeImage, NULL// no after image ); size_t logrecordLength = logrecord->getEstimatedLength(); char *logrecordBuffer = new char[logrecordLength]; FallbackSerializeOutput output; output.initializeWithPosition(logrecordBuffer, logrecordLength, 0); logrecord->serializeTo(output); LogManager* m_logManager = this->m_engine->getLogManager(); Logger m_ariesLogger = m_logManager->getAriesLogger(); //VOLT_WARN("m_logManager : %p AriesLogger : %p",&m_logManager, &m_ariesLogger); const Logger *logger = m_logManager->getThreadLogger(LOGGERID_MM_ARIES); logger->log(LOGLEVEL_INFO, output.data(), output.position()); delete[] logrecordBuffer; logrecordBuffer = NULL; delete logrecord; logrecord = NULL; if (keydata != NULL) { delete[] keydata; keydata = NULL; } if (keyTuple != NULL) { delete keyTuple; keyTuple = NULL; } } #endif // Delete from target table if (!m_targetTable->deleteTuple(m_targetTuple, true)) { VOLT_ERROR("Failed to delete tuple from table '%s'", m_targetTable->name().c_str()); return false; } } // add to the planfragments count of modified tuples m_engine->m_tuplesModified += m_inputTable->activeTupleCount(); //m_engine->context().incrementTuples(m_inputTable->activeTupleCount()); return true; }
/** * Generate hash value for key. */ ElasticHash ElasticIndex::generateHash(const PersistentTable &table, const TableTuple &tuple) { return tuple.getNValue(table.partitionColumn()).murmurHash3(); }
// Scan some records in the table, verifying that points that are // supposed to be inside are, and those that are not aren't. // Print out some stats about how long things took. void scanSomeRecords(PersistentTable *table, int numTuples, int numScans) { std::cout << " Scanning for containing polygons on " << numScans << " points...\n"; auto start = std::chrono::high_resolution_clock::now(); std::chrono::microseconds usSpentScanning = std::chrono::duration_cast<microseconds>(start - start); std::chrono::microseconds usSpentContainsing = std::chrono::duration_cast<microseconds>(start - start); CoveringCellIndex* ccIndex = static_cast<CoveringCellIndex*>(table->index("poly_idx")); TableTuple tempTuple = table->tempTuple(); StandAloneTupleStorage searchKey(ccIndex->getKeySchema()); int numContainingCells = 0; int numContainingPolygons = 0; for (int i = 0; i < numScans; ++i) { // Pick a tuple at random. int pk = std::rand() % numTuples; tempTuple.setNValue(PK_COL_INDEX, ValueFactory::getIntegerValue(pk)); TableTuple sampleTuple = table->lookupTupleByValues(tempTuple); ASSERT_FALSE(sampleTuple.isNullTuple()); NValue geog = sampleTuple.getNValue(GEOG_COL_INDEX); if (geog.isNull()) { // There is one null row in the table. continue; } // The centroid will be inside polygons with one ring, and // not inside polygons with two rings (because the second // ring is a hole in the center). NValue centroid = geog.callUnary<FUNC_VOLT_POLYGON_CENTROID>(); int32_t numInteriorRings = ValuePeeker::peekAsBigInt(geog.callUnary<FUNC_VOLT_POLYGON_NUM_INTERIOR_RINGS>()); bool isValid = ValuePeeker::peekBoolean(geog.callUnary<FUNC_VOLT_VALIDATE_POLYGON>()); if (! isValid) { std::ostringstream oss; int32_t len; const char* reasonChars = ValuePeeker::peekObject_withoutNull(geog.callUnary<FUNC_VOLT_POLYGON_INVALID_REASON>(), &len); std::string reason = std::string(reasonChars, len); oss << "At " << i << "th scan, expected a valid polygon at pk " << pk << " but isValid says its not because \"" << reason << "\". WKT:\n" << nvalToWkt(geog); ASSERT_TRUE_WITH_MESSAGE(isValid, oss.str().c_str()); } start = std::chrono::high_resolution_clock::now(); searchKey.tuple().setNValue(0, centroid); IndexCursor cursor(ccIndex->getTupleSchema()); bool foundSamplePoly = false; bool b = ccIndex->moveToCoveringCell(&searchKey.tuple(), cursor); if (b) { TableTuple foundTuple = ccIndex->nextValueAtKey(cursor); while (! foundTuple.isNullTuple()) { ++numContainingCells; auto startContains = std::chrono::high_resolution_clock::now(); bool polygonContains = ValuePeeker::peekBoolean(NValue::call<FUNC_VOLT_CONTAINS>({geog, centroid})); auto endContains = std::chrono::high_resolution_clock::now(); usSpentContainsing += std::chrono::duration_cast<microseconds>(endContains - startContains); if (polygonContains) ++numContainingPolygons; int foundPk = ValuePeeker::peekAsInteger(foundTuple.getNValue(PK_COL_INDEX)); if (foundPk == pk && polygonContains) { foundSamplePoly = true; } foundTuple = ccIndex->nextValueAtKey(cursor); } } auto end = std::chrono::high_resolution_clock::now(); usSpentScanning += std::chrono::duration_cast<microseconds>(end - start); ASSERT_TRUE(numInteriorRings == 0 || numInteriorRings == 1); if (numInteriorRings == 0 && !foundSamplePoly) { std::ostringstream oss; oss << "At " << i << "th scan, expected to find centroid in polygon with primary key " << pk << ", centroid WKT:\n" << nvalToWkt(centroid) << "\npolygon WKT:\n" << nvalToWkt(geog); ASSERT_TRUE_WITH_MESSAGE(foundSamplePoly, oss.str().c_str()); } else if (numInteriorRings == 1) { // There was a hole in the center so the centroid is not in the polygon ASSERT_TRUE_WITH_MESSAGE(!foundSamplePoly, "Expected to not find centroid contained by polygon with hole in the center"); } } auto avgTotalUsSpentScanning = usSpentScanning.count() / numScans; auto avgUsSpentContainsing = usSpentContainsing.count() / numScans; auto avgUsSpentScanning = avgTotalUsSpentScanning - avgUsSpentContainsing; std::cout << " Average duration of each index lookup total: " << avgTotalUsSpentScanning << " us\n"; std::cout << " Average duration spent on CONTAINS: " << avgUsSpentContainsing << " us\n"; std::cout << " Average duration spent on B-tree traversal: " << avgUsSpentScanning << " us\n"; double pctFalsePositives = (double(numContainingCells - numContainingPolygons) / numContainingCells) * 100.0; std::cout << " Percent false positives (point in cell but not polygon): " << pctFalsePositives << "%\n"; double avgCellsContainingPoint = numContainingCells / double(numScans); double avgPolygonsContainingPoint = numContainingPolygons / double(numScans); std::cout << " On average, each point was in " << avgCellsContainingPoint << " cells\n"; std::cout << " On average, each point was in " << avgPolygonsContainingPoint << " polygons\n"; }
bool InsertExecutor::p_execute(const NValueArray ¶ms) { assert(m_node == dynamic_cast<InsertPlanNode*>(m_abstractNode)); assert(m_node); assert(m_inputTable == dynamic_cast<TempTable*>(m_node->getInputTable())); assert(m_inputTable); // Target table can be StreamedTable or PersistentTable and must not be NULL // Update target table reference from table delegate Table* targetTable = m_node->getTargetTable(); assert(targetTable); assert((targetTable == dynamic_cast<PersistentTable*>(targetTable)) || (targetTable == dynamic_cast<StreamedTable*>(targetTable))); PersistentTable* persistentTable = m_isStreamed ? NULL : static_cast<PersistentTable*>(targetTable); TableTuple upsertTuple = TableTuple(targetTable->schema()); VOLT_TRACE("INPUT TABLE: %s\n", m_inputTable->debug().c_str()); // count the number of successful inserts int modifiedTuples = 0; Table* outputTable = m_node->getOutputTable(); assert(outputTable); TableTuple templateTuple = m_templateTuple.tuple(); std::vector<int>::iterator it; for (it = m_nowFields.begin(); it != m_nowFields.end(); ++it) { templateTuple.setNValue(*it, NValue::callConstant<FUNC_CURRENT_TIMESTAMP>()); } VOLT_DEBUG("This is a %s-row insert on partition with id %d", m_node->getChildren()[0]->getPlanNodeType() == PLAN_NODE_TYPE_MATERIALIZE ? "single" : "multi", m_engine->getPartitionId()); VOLT_DEBUG("Offset of partition column is %d", m_partitionColumn); // // An insert is quite simple really. We just loop through our m_inputTable // and insert any tuple that we find into our targetTable. It doesn't get any easier than that! // TableTuple inputTuple(m_inputTable->schema()); assert (inputTuple.sizeInValues() == m_inputTable->columnCount()); TableIterator iterator = m_inputTable->iterator(); while (iterator.next(inputTuple)) { for (int i = 0; i < m_node->getFieldMap().size(); ++i) { // Most executors will just call setNValue instead of // setNValueAllocateForObjectCopies. // // However, We need to call // setNValueAlocateForObjectCopies here. Sometimes the // input table's schema has an inlined string field, and // it's being assigned to the target table's outlined // string field. In this case we need to tell the NValue // where to allocate the string data. templateTuple.setNValueAllocateForObjectCopies(m_node->getFieldMap()[i], inputTuple.getNValue(i), ExecutorContext::getTempStringPool()); } VOLT_TRACE("Inserting tuple '%s' into target table '%s' with table schema: %s", templateTuple.debug(targetTable->name()).c_str(), targetTable->name().c_str(), targetTable->schema()->debug().c_str()); // if there is a partition column for the target table if (m_partitionColumn != -1) { // get the value for the partition column NValue value = templateTuple.getNValue(m_partitionColumn); bool isLocal = m_engine->isLocalSite(value); // if it doesn't map to this site if (!isLocal) { if (!m_multiPartition) { throw ConstraintFailureException( dynamic_cast<PersistentTable*>(targetTable), templateTuple, "Mispartitioned tuple in single-partition insert statement."); } // don't insert continue; } } // for multi partition export tables, only insert into one // place (the partition with hash(0)), if the data is from a // replicated source. If the data is coming from a subquery // with partitioned tables, we need to perform the insert on // every partition. if (m_isStreamed && m_multiPartition && !m_sourceIsPartitioned) { bool isLocal = m_engine->isLocalSite(ValueFactory::getBigIntValue(0)); if (!isLocal) continue; } if (! m_isUpsert) { // try to put the tuple into the target table if (m_hasPurgeFragment) { if (!executePurgeFragmentIfNeeded(&persistentTable)) return false; // purge fragment might have truncated the table, and // refreshed the persistent table pointer. Make sure to // use it when doing the insert below. targetTable = persistentTable; } if (!targetTable->insertTuple(templateTuple)) { VOLT_ERROR("Failed to insert tuple from input table '%s' into" " target table '%s'", m_inputTable->name().c_str(), targetTable->name().c_str()); return false; } } else { // upsert execution logic assert(persistentTable->primaryKeyIndex() != NULL); TableTuple existsTuple = persistentTable->lookupTupleByValues(templateTuple); if (existsTuple.isNullTuple()) { // try to put the tuple into the target table if (m_hasPurgeFragment) { if (!executePurgeFragmentIfNeeded(&persistentTable)) return false; } if (!persistentTable->insertTuple(templateTuple)) { VOLT_ERROR("Failed to insert tuple from input table '%s' into" " target table '%s'", m_inputTable->name().c_str(), persistentTable->name().c_str()); return false; } } else { // tuple exists already, try to update the tuple instead upsertTuple.move(templateTuple.address()); TableTuple &tempTuple = persistentTable->getTempTupleInlined(upsertTuple); if (!persistentTable->updateTupleWithSpecificIndexes(existsTuple, tempTuple, persistentTable->allIndexes())) { VOLT_INFO("Failed to update existsTuple from table '%s'", persistentTable->name().c_str()); return false; } } } // successfully inserted or updated modifiedTuples++; } TableTuple& count_tuple = outputTable->tempTuple(); count_tuple.setNValue(0, ValueFactory::getBigIntValue(modifiedTuples)); // try to put the tuple into the output table if (!outputTable->insertTuple(count_tuple)) { VOLT_ERROR("Failed to insert tuple count (%d) into" " output table '%s'", modifiedTuples, outputTable->name().c_str()); return false; } // add to the planfragments count of modified tuples m_engine->addToTuplesModified(modifiedTuples); VOLT_DEBUG("Finished inserting %d tuples", modifiedTuples); return true; }
int64_t TupleTrackerManager::getPrimaryKey(std::string tableName, uint32_t tupleId){ Table* table = voltDBEngine->getTable(tableName); TableTuple tuple = TableTuple(table->schema()); tuple.move(table->dataPtrForTuple(tupleId)); TableIndex *m_index = table->primaryKeyIndex(); std::vector<int> column_indices_vector = m_index->getColumnIndices(); std::vector<int>::iterator it = column_indices_vector.begin(); NValue colValue; int i = 0; while (it != column_indices_vector.end()) // this is for non composite key { colValue = tuple.getNValue(*it); it++; i++; } return colValue.castAsBigIntAndGetValue(); //return i; //return colValue.isNull(); /* it = std::find(column_indices_vector.begin(), column_indices_vector.end(), tupleId); return (int) std:: distance(column_indices_vector.begin(), it); TableTuple outputTuple = ... // tuple for your output table TableTuple inputTuple = ... // tuple from your tracking table TableTuple origTuple = ... // tuple from the original PersistantTable foreach (inputTuple in tracking table) { // (1) Get offset from inputTuple and move the origTuple to that location origTuple.move(table->dataPtrForTuple(tupleId)); // (2) Now iterate over the pkey column offsets and copy the values into the inputTuple int col_idx = 0; for (pkey_offset in m_index->getColumnIndices()) { NValue colValue = origTuple.getNValue(pkey_offset); outputTuple.setNValue(col_idx, colValue); col_idx++; } // (3) Insert outputTuple into output table } int colCount = (int)column_indices_vector.size(); if (colCount < tupleId) return -1; return column_indices_vector[tupleId]; //*/ }
void InsertExecutor::p_execute_tuple_internal(TableTuple &tuple) { const std::vector<int>& fieldMap = m_node->getFieldMap(); std::size_t mapSize = fieldMap.size(); for (int i = 0; i < mapSize; ++i) { // Most executors will just call setNValue instead of // setNValueAllocateForObjectCopies. // // However, We need to call // setNValueAllocateForObjectCopies here. Sometimes the // input table's schema has an inlined string field, and // it's being assigned to the target table's non-inlined // string field. In this case we need to tell the NValue // where to allocate the string data. // For an "upsert", this templateTuple setup has two effects -- // It sets the primary key column(s) and it sets the // updated columns to their new values. // If the primary key value (combination) is new, the // templateTuple is the exact combination of new values // and default values required by the insert. // If the primary key value (combination) already exists, // only the NEW values stored on the templateTuple get updated // in the existing tuple and its other columns keep their existing // values -- the DEFAULT values that are stored in templateTuple // DO NOT get copied to an existing tuple. m_templateTuple.setNValueAllocateForObjectCopies(fieldMap[i], tuple.getNValue(i), m_tempPool); } VOLT_TRACE("Inserting tuple '%s' into target table '%s' with table schema: %s", m_templateTuple.debug(m_targetTable->name()).c_str(), m_targetTable->name().c_str(), m_targetTable->schema()->debug().c_str()); // If there is a partition column for the target table if (m_partitionColumn != -1) { // get the value for the partition column NValue value = m_templateTuple.getNValue(m_partitionColumn); bool isLocal = m_engine->isLocalSite(value); // if it doesn't map to this partiton if (!isLocal) { if (m_multiPartition) { // The same row is presumed to also be generated // on some other partition, where the partition key // belongs. return; } // When a streamed table has no views, let an SP insert execute. // This is backward compatible with when there were only export // tables with no views on them. // When there are views, be strict and throw mispartitioned // tuples to force partitioned data to be generated only // where partitioned view rows are maintained. if (!m_isStreamed || m_hasStreamView) { throw ConstraintFailureException(m_targetTable, m_templateTuple, "Mispartitioned tuple in single-partition insert statement."); } } } if (m_isUpsert) { // upsert execution logic assert(m_persistentTable->primaryKeyIndex() != NULL); TableTuple existsTuple = m_persistentTable->lookupTupleByValues(m_templateTuple); if (!existsTuple.isNullTuple()) { // The tuple exists already, update (only) the templateTuple columns // that were initialized from the input tuple via the field map. // Technically, this includes setting primary key values, // but they are getting set to equivalent values, so that's OK. // A simple setNValue works here because any required object // allocations were handled when copying the input values into // the templateTuple. m_upsertTuple.move(m_templateTuple.address()); TableTuple &tempTuple = m_persistentTable->copyIntoTempTuple(existsTuple); for (int i = 0; i < mapSize; ++i) { tempTuple.setNValue(fieldMap[i], m_templateTuple.getNValue(fieldMap[i])); } m_persistentTable->updateTupleWithSpecificIndexes(existsTuple, tempTuple, m_persistentTable->allIndexes()); // successfully updated ++m_modifiedTuples; return; } // else, the primary key did not match, // so fall through to the "insert" logic } // try to put the tuple into the target table if (m_hasPurgeFragment) { executePurgeFragmentIfNeeded(&m_persistentTable); // purge fragment might have truncated the table, and // refreshed the persistent table pointer. Make sure to // use it when doing the insert below. m_targetTable = m_persistentTable; } m_targetTable->insertTuple(m_templateTuple); VOLT_TRACE("Target table:\n%s\n", m_targetTable->debug().c_str()); // successfully inserted ++m_modifiedTuples; return; }
void MaterializedViewMetadata::processTupleInsert(TableTuple &newTuple, bool fallible) { // don't change the view if this tuple doesn't match the predicate if (m_filterPredicate && (m_filterPredicate->eval(&newTuple, NULL).isFalse())) { return; } bool exists = findExistingTuple(newTuple); if (!exists) { // create a blank tuple m_existingTuple.move(m_emptyTupleBackingStore); } // clear the tuple that will be built to insert or overwrite memset(m_updatedTupleBackingStore, 0, m_target->schema()->tupleLength() + 1); int colindex = 0; // set up the first n columns, based on group-by columns for (colindex = 0; colindex < m_groupByColumnCount; colindex++) { // note that if the tuple is in the mv's target table, // tuple values should be pulled from the existing tuple in // that table. This works around a memory ownership issue // related to out-of-line strings. if (exists) { m_updatedTuple.setNValue(colindex, m_existingTuple.getNValue(colindex)); } else { m_updatedTuple.setNValue(colindex, newTuple.getNValue(m_groupByColumns[colindex])); } } // set up the next column, which is a count m_updatedTuple.setNValue(colindex, m_existingTuple.getNValue(colindex).op_increment()); colindex++; // set values for the other columns for (int i = colindex; i < m_outputColumnCount; i++) { NValue newValue = newTuple.getNValue(m_outputColumnSrcTableIndexes[i]); NValue existingValue = m_existingTuple.getNValue(i); if (m_outputColumnAggTypes[i] == EXPRESSION_TYPE_AGGREGATE_SUM) { m_updatedTuple.setNValue(i, newValue.op_add(existingValue)); } else if (m_outputColumnAggTypes[i] == EXPRESSION_TYPE_AGGREGATE_COUNT) { m_updatedTuple.setNValue(i, existingValue.op_increment()); } else { char message[128]; snprintf(message, 128, "Error in materialized view table update for" " col %d. Expression type %d", i, m_outputColumnAggTypes[i]); throw SerializableEEException(VOLT_EE_EXCEPTION_TYPE_EEEXCEPTION, message); } } // update or insert the row if (exists) { // Shouldn't need to update group-key-only indexes such as the primary key // since their keys shouldn't ever change, but do update other indexes. m_target->updateTupleWithSpecificIndexes(m_existingTuple, m_updatedTuple, m_updatableIndexList, fallible); } else { m_target->insertPersistentTuple(m_updatedTuple, fallible); } }