blob: d41a457d1c782ab9c803726227063b13fce3d1d8 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
**/
#include <algorithm>
#include <cstddef>
#include <memory>
#include <sstream>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include "gtest/gtest.h"
#include "catalog/CatalogAttribute.hpp"
#include "catalog/CatalogRelation.hpp"
#include "catalog/CatalogTypedefs.hpp"
#include "expressions/predicate/ComparisonPredicate.hpp"
#include "expressions/scalar/Scalar.hpp"
#include "expressions/scalar/ScalarAttribute.hpp"
#include "expressions/scalar/ScalarLiteral.hpp"
#include "storage/BasicColumnStoreTupleStorageSubBlock.hpp"
#include "storage/StorageBlockLayout.hpp"
#include "storage/StorageBlockLayout.pb.h"
#include "storage/StorageConstants.hpp"
#include "storage/StorageErrors.hpp"
#include "storage/TupleIdSequence.hpp"
#include "storage/TupleStorageSubBlock.hpp"
#include "storage/tests/TupleStorePredicateUtil.hpp"
#include "types/CharType.hpp"
#include "types/DoubleType.hpp"
#include "types/IntType.hpp"
#include "types/Type.hpp"
#include "types/TypeFactory.hpp"
#include "types/TypedValue.hpp"
#include "types/TypeID.hpp"
#include "types/VarCharType.hpp"
#include "types/containers/Tuple.hpp"
#include "types/operations/comparisons/Comparison.hpp"
#include "types/operations/comparisons/ComparisonFactory.hpp"
#include "types/operations/comparisons/ComparisonID.hpp"
#include "types/operations/comparisons/ComparisonUtil.hpp"
#include "utility/BitVector.hpp"
#include "utility/ScopedBuffer.hpp"
using std::size_t;
using std::string;
using std::vector;
namespace quickstep {
class BasicColumnStoreTupleStorageSubBlockTest : public ::testing::TestWithParam<bool> {
protected:
static const size_t kSubBlockSize = 0x100000; // 1 MB.
virtual void SetUp() {
// Create a sample relation with a variety of attribute types.
relation_.reset(new CatalogRelation(NULL, "TestRelation"));
CatalogAttribute *int_attr = new CatalogAttribute(relation_.get(),
"int_attr",
TypeFactory::GetType(kInt, GetParam()));
ASSERT_EQ(0, relation_->addAttribute(int_attr));
CatalogAttribute *double_attr = new CatalogAttribute(relation_.get(),
"double_attr",
TypeFactory::GetType(kDouble, GetParam()));
ASSERT_EQ(1, relation_->addAttribute(double_attr));
CatalogAttribute *narrow_char_attr = new CatalogAttribute(relation_.get(),
"narrow_char_attr",
TypeFactory::GetType(kChar, 4, GetParam()));
ASSERT_EQ(2, relation_->addAttribute(narrow_char_attr));
CatalogAttribute *wide_char_attr = new CatalogAttribute(relation_.get(),
"wide_char_attr",
TypeFactory::GetType(kChar, 32, GetParam()));
ASSERT_EQ(3, relation_->addAttribute(wide_char_attr));
// Records the 'base_value' of a tuple used in createSampleTuple.
CatalogAttribute *base_value_attr = new CatalogAttribute(relation_.get(),
"base_value",
TypeFactory::GetType(kInt, false));
ASSERT_EQ(4, relation_->addAttribute(base_value_attr));
// Don't initialize the block yet. Different tests will use different sort
// columns.
tuple_store_memory_.reset();
tuple_store_description_.reset();
tuple_store_.reset();
}
void createBlock(const attribute_id sort_column, const size_t block_memory_size) {
tuple_store_memory_.reset(block_memory_size);
tuple_store_description_.reset(new TupleStorageSubBlockDescription());
tuple_store_description_->set_sub_block_type(TupleStorageSubBlockDescription::BASIC_COLUMN_STORE);
if (sort_column != kInvalidCatalogId) {
tuple_store_description_->SetExtension(
BasicColumnStoreTupleStorageSubBlockDescription::sort_attribute_id,
sort_column);
}
tuple_store_.reset(new BasicColumnStoreTupleStorageSubBlock(*relation_,
*tuple_store_description_,
true,
tuple_store_memory_.get(),
block_memory_size));
}
// Caller takes ownership of new heap-created Tuple.
Tuple* createSampleTuple(const int base_value) const {
std::vector<TypedValue> attrs;
// int_attr
if (GetParam() && (base_value % 8 == 0)) {
// Throw in a NULL integer for every eighth value.
attrs.emplace_back(kInt);
} else {
attrs.emplace_back(base_value);
}
// double_attr
if (GetParam() && (base_value % 8 == 2)) {
// NULL very eighth value.
attrs.emplace_back(kDouble);
} else {
attrs.emplace_back(static_cast<double>(0.25 * base_value));
}
// narrow_char_attr
if (GetParam() && (base_value % 8 == 4)) {
// NULL very eighth value.
attrs.emplace_back(CharType::InstanceNullable(4).makeNullValue());
} else {
std::ostringstream char_buffer;
char_buffer << base_value;
std::string string_literal(char_buffer.str());
attrs.emplace_back(CharType::InstanceNonNullable(4).makeValue(
string_literal.c_str(),
string_literal.size() > 3 ? 4
: string_literal.size() + 1));
attrs.back().ensureNotReference();
}
// wide_char_attr
if (GetParam() && (base_value % 8 == 6)) {
// NULL very eighth value.
attrs.emplace_back(CharType::InstanceNullable(32).makeNullValue());
} else {
std::ostringstream char_buffer;
char_buffer << "Here are some numbers: " << base_value;
std::string string_literal(char_buffer.str());
attrs.emplace_back(CharType::InstanceNonNullable(32).makeValue(
string_literal.c_str(),
string_literal.size() + 1));
attrs.back().ensureNotReference();
}
// base_value
attrs.emplace_back(base_value);
return new Tuple(std::move(attrs));
}
// Make a tuple out of the surrogate values used to sort nulls. Caller takes
// ownership of new heap-created Tuple.
Tuple* createTupleWithLastValues() const {
std::vector<TypedValue> attrs;
attrs.emplace_back(GetLastValueForType(TypeFactory::GetType(kInt, GetParam())));
attrs.emplace_back(GetLastValueForType(TypeFactory::GetType(kDouble, GetParam())));
attrs.emplace_back(GetLastValueForType(TypeFactory::GetType(kChar, 4, GetParam())));
attrs.emplace_back(GetLastValueForType(TypeFactory::GetType(kChar, 32, GetParam())));
attrs.emplace_back(-1);
return new Tuple(std::move(attrs));
}
int computeRowCapacity() const {
int initial_estimate = ((kSubBlockSize - 2 * sizeof(tuple_id)) << 3)
/ ((relation_->getFixedByteLength() << 3) + relation_->numNullableAttributes());
return (kSubBlockSize
- (2 * sizeof(tuple_id))
- (relation_->numNullableAttributes() * BitVector<false>::BytesNeeded(initial_estimate)))
/ relation_->getFixedByteLength();
}
int computeNullsInSortColumn(const attribute_id sort_column_id) {
if (sort_column_id == kInvalidCatalogId || !GetParam()) {
return 0;
}
int row_capacity = computeRowCapacity();
int full_strides = (row_capacity - 2) / 8;
int stride_remainder = (row_capacity - 2) % 8;
switch (sort_column_id) {
case 0:
return full_strides + (stride_remainder > 0 ? 1 : 0);
case 1:
return full_strides + (stride_remainder > 2 ? 1 : 0);
case 2:
return full_strides + (stride_remainder > 4 ? 1 : 0);
case 3:
return full_strides + (stride_remainder > 6 ? 1 : 0);
default:
return 0;
}
}
void fillBlockWithSampleData(const bool random_order, const bool ad_hoc) {
std::unique_ptr<Tuple> current_tuple;
if (random_order) {
std::vector<int> base_values;
for (int i = 0; i < computeRowCapacity() - 2; ++i) {
base_values.push_back(i);
}
std::random_shuffle(base_values.begin(), base_values.end());
if (ad_hoc) {
// First, insert a tuple with special "last" values.
TupleStorageSubBlock::InsertResult result(-1, false);
current_tuple.reset(createTupleWithLastValues());
result = tuple_store_->insertTuple(*current_tuple);
EXPECT_EQ(0, result.inserted_id);
EXPECT_FALSE(result.ids_mutated);
int inserted = 1;
// Regular tuples.
for (const int base_value : base_values) {
current_tuple.reset(createSampleTuple(base_value));
result = tuple_store_->insertTuple(*current_tuple);
EXPECT_GE(result.inserted_id, 0);
++inserted;
if (result.inserted_id == (inserted - 1)) {
EXPECT_FALSE(result.ids_mutated);
} else {
EXPECT_TRUE(result.ids_mutated);
}
}
// Another "last" tuple.
current_tuple.reset(createTupleWithLastValues());
result = tuple_store_->insertTuple(*current_tuple);
EXPECT_GE(result.inserted_id, 0);
++inserted;
if (result.inserted_id == (inserted - 1)) {
EXPECT_FALSE(result.ids_mutated);
} else {
EXPECT_TRUE(result.ids_mutated);
}
// No more space.
result = tuple_store_->insertTuple(*current_tuple);
EXPECT_EQ(-1, result.inserted_id);
} else {
current_tuple.reset(createTupleWithLastValues());
EXPECT_TRUE(tuple_store_->insertTupleInBatch(*current_tuple));
for (const int base_value : base_values) {
current_tuple.reset(createSampleTuple(base_value));
EXPECT_TRUE(tuple_store_->insertTupleInBatch(*current_tuple));
}
current_tuple.reset(createTupleWithLastValues());
EXPECT_TRUE(tuple_store_->insertTupleInBatch(*current_tuple));
// No more space.
EXPECT_FALSE(tuple_store_->insertTupleInBatch(*current_tuple));
tuple_store_->rebuild();
}
} else {
// Fixed order.
if (ad_hoc) {
TupleStorageSubBlock::InsertResult result(-1, false);
current_tuple.reset(createTupleWithLastValues());
result = tuple_store_->insertTuple(*current_tuple);
EXPECT_EQ(0, result.inserted_id);
EXPECT_FALSE(result.ids_mutated);
int inserted = 1;
for (int base_value = 0; base_value < computeRowCapacity() - 2; ++base_value) {
current_tuple.reset(createSampleTuple(base_value));
result = tuple_store_->insertTuple(*current_tuple);
EXPECT_GE(result.inserted_id, 0);
++inserted;
if (result.inserted_id == (inserted - 1)) {
EXPECT_FALSE(result.ids_mutated);
} else {
EXPECT_TRUE(result.ids_mutated);
}
}
current_tuple.reset(createTupleWithLastValues());
result = tuple_store_->insertTuple(*current_tuple);
EXPECT_GE(result.inserted_id, 0);
++inserted;
if (result.inserted_id == (inserted - 1)) {
EXPECT_FALSE(result.ids_mutated);
} else {
EXPECT_TRUE(result.ids_mutated);
}
// No more space.
result = tuple_store_->insertTuple(*current_tuple);
EXPECT_EQ(-1, result.inserted_id);
} else {
current_tuple.reset(createTupleWithLastValues());
EXPECT_TRUE(tuple_store_->insertTupleInBatch(*current_tuple));
for (int base_value = 0; base_value < computeRowCapacity() - 2; ++base_value) {
current_tuple.reset(createSampleTuple(base_value));
EXPECT_TRUE(tuple_store_->insertTupleInBatch(*current_tuple));
}
current_tuple.reset(createTupleWithLastValues());
EXPECT_TRUE(tuple_store_->insertTupleInBatch(*current_tuple));
// No more space.
EXPECT_FALSE(tuple_store_->insertTupleInBatch(*current_tuple));
tuple_store_->rebuild();
}
}
}
void checkBlockValues(const attribute_id sort_attribute_id,
const size_t regular_tuples_deleted,
const size_t last_tuples_deleted) const {
EXPECT_EQ(computeRowCapacity() - regular_tuples_deleted - last_tuples_deleted,
static_cast<std::size_t>(tuple_store_->getMaxTupleID() + 1));
std::unique_ptr<UncheckedComparator> sort_attribute_comparator;
if (sort_attribute_id != kInvalidCatalogId) {
const Type &sort_attribute_type =
relation_->getAttributeById(sort_attribute_id)->getType();
sort_attribute_comparator.reset(
ComparisonFactory::GetComparison(ComparisonID::kLessOrEqual)
.makeUncheckedComparatorForTypes(sort_attribute_type,
sort_attribute_type));
}
std::vector<bool> existence_check_vector;
existence_check_vector.resize(computeRowCapacity() - 2, false);
size_t num_last_tuples = 0;
size_t num_regular_tuples = 0;
std::unique_ptr<TypedValue> previous_sort_attribute_value;
for (tuple_id tid = 0;
tid <= tuple_store_->getMaxTupleID();
++tid) {
EXPECT_TRUE(tuple_store_->hasTupleWithID(tid));
const int base_value = *static_cast<const int*>(tuple_store_->getAttributeValue(tid, 4));
ASSERT_LT(base_value, computeRowCapacity() - 2);
std::unique_ptr<Tuple> comparison_tuple;
if (base_value == -1) {
++num_last_tuples;
comparison_tuple.reset(createTupleWithLastValues());
} else {
++num_regular_tuples;
EXPECT_FALSE(existence_check_vector[base_value]);
existence_check_vector[base_value] = true;
comparison_tuple.reset(createSampleTuple(base_value));
}
// Check attribute values.
for (const CatalogAttribute &attr : *relation_) {
const void *untyped_value = tuple_store_->getAttributeValue(tid, attr.getID());
TypedValue typed_value = tuple_store_->getAttributeValueTyped(tid, attr.getID());
EXPECT_TRUE(typed_value.isPlausibleInstanceOf(attr.getType().getSignature()));
if (comparison_tuple->getAttributeValue(attr.getID()).isNull()) {
EXPECT_EQ(nullptr, untyped_value);
EXPECT_TRUE(typed_value.isNull());
} else {
EXPECT_TRUE(CheckUntypedValuesEqual(attr.getType(),
comparison_tuple->getAttributeValue(attr.getID()).getDataPtr(),
untyped_value));
EXPECT_TRUE(CheckUntypedValuesEqual(attr.getType(),
comparison_tuple->getAttributeValue(attr.getID()).getDataPtr(),
typed_value.getDataPtr()));
}
}
// Check ordering if block is sorted.
if (sort_attribute_id != kInvalidCatalogId) {
TypedValue sort_attribute_value =
tuple_store_->getAttributeValueTyped(tid, sort_attribute_id);
if (previous_sort_attribute_value) {
if (previous_sort_attribute_value->isNull()) {
EXPECT_TRUE(sort_attribute_value.isNull());
} else if (!sort_attribute_value.isNull()) {
EXPECT_TRUE(sort_attribute_comparator->compareTypedValues(
*previous_sort_attribute_value,
sort_attribute_value));
}
}
previous_sort_attribute_value.reset(new TypedValue(sort_attribute_value));
}
}
EXPECT_EQ(2 - last_tuples_deleted, num_last_tuples);
EXPECT_EQ(computeRowCapacity() - 2 - regular_tuples_deleted,
num_regular_tuples);
}
void runDeleteTest(const attribute_id sort_attribute_id) {
createBlock(sort_attribute_id, kSubBlockSize);
fillBlockWithSampleData(true, false);
int row_capacity = computeRowCapacity();
// Delete last tuple.
EXPECT_FALSE(tuple_store_->deleteTuple(row_capacity - 1));
// Delete first tuple.
EXPECT_TRUE(tuple_store_->deleteTuple(0));
// Delete a sequence of tuples.
TupleIdSequence delete_sequence(tuple_store_->getMaxTupleID() + 1);
for (tuple_id tid = 0;
tid < row_capacity - computeNullsInSortColumn(sort_attribute_id) - 2 - 2;
tid += 5) {
// Some non-null values.
delete_sequence.set(tid, true);
}
// Some nulls, if any exist.
for (tuple_id tid = row_capacity - computeNullsInSortColumn(sort_attribute_id) + 1;
tid < row_capacity - 2;
tid += 5) {
delete_sequence.set(tid, true);
}
// One of the special "last" values.
if (sort_attribute_id != kInvalidCatalogId && GetParam()) {
delete_sequence.set(row_capacity - computeNullsInSortColumn(sort_attribute_id) - 2, true);
}
EXPECT_TRUE(tuple_store_->bulkDeleteTuples(&delete_sequence));
if (sort_attribute_id == kInvalidCatalogId) {
// There is no special treatment of "last" values without sorting.
checkBlockValues(sort_attribute_id, delete_sequence.numTuples(), 2);
} else {
checkBlockValues(sort_attribute_id, delete_sequence.numTuples() + 2 - 1, 1);
}
}
// Create a ComparisonPredicate of the form "attribute comp literal".
template <typename AttributeType>
Predicate* generateNumericComparisonPredicate(const ComparisonID comp,
const attribute_id attribute,
const typename AttributeType::cpptype literal) const {
ScalarAttribute *scalar_attribute = new ScalarAttribute(*relation_->getAttributeById(attribute));
ScalarLiteral *scalar_literal
= new ScalarLiteral(AttributeType::InstanceNonNullable().makeValue(&literal),
AttributeType::InstanceNonNullable());
return new ComparisonPredicate(ComparisonFactory::GetComparison(comp), scalar_attribute, scalar_literal);
}
Predicate* generateStringComparisonPredicate(const ComparisonID comp,
const attribute_id attribute,
const string &literal) const {
ScalarAttribute *scalar_attribute = new ScalarAttribute(*relation_->getAttributeById(attribute));
ScalarLiteral *scalar_literal = new ScalarLiteral(
VarCharType::InstanceNonNullable(literal.size()).makeValue(
literal.c_str(),
literal.size() + 1).ensureNotReference(),
VarCharType::InstanceNonNullable(literal.size()));
return new ComparisonPredicate(ComparisonFactory::GetComparison(comp),
scalar_attribute,
scalar_literal);
}
template <typename ValueType>
inline static bool matchExpected(const ComparisonID comp,
const ValueType &left,
const ValueType &right) {
switch (comp) {
case ComparisonID::kEqual:
return (left == right);
case ComparisonID::kNotEqual:
return (left != right);
case ComparisonID::kLess:
return (left < right);
case ComparisonID::kLessOrEqual:
return (left <= right);
case ComparisonID::kGreater:
return (left > right);
case ComparisonID::kGreaterOrEqual:
return (left >= right);
default:
FATAL_ERROR("Unrecognized ComparisonID");
}
}
void checkPredicateIntAttr(const ComparisonID comp) const {
int comparison_literal = computeRowCapacity() >> 1;
std::unique_ptr<Predicate> predicate(
generateNumericComparisonPredicate<IntType>(comp,
0,
comparison_literal));
std::unique_ptr<TupleIdSequence> matches(
TupleStorePredicateUtil::GetMatchesForPredicateOnTupleStore(*predicate, *tuple_store_));
for (tuple_id tid = 0;
tid < static_cast<tuple_id>(matches->length());
++tid) {
int insert_order_value = *static_cast<const int*>(tuple_store_->getAttributeValue(tid, 4));
if (GetParam() && (insert_order_value % 8 == 0)) {
// Value is NULL.
EXPECT_FALSE(matches->get(tid));
} else if (insert_order_value == -1) {
// Special last value.
if (matchExpected(comp,
GetLastValueForType(TypeFactory::GetType(kInt, GetParam())).getLiteral<int>(),
comparison_literal)) {
EXPECT_TRUE(matches->get(tid));
} else {
EXPECT_FALSE(matches->get(tid));
}
} else {
if (matchExpected(comp,
insert_order_value,
comparison_literal)) {
EXPECT_TRUE(matches->get(tid));
} else {
EXPECT_FALSE(matches->get(tid));
}
}
}
}
void checkPredicateDoubleAttr(const ComparisonID comp) const {
double comparison_literal = (computeRowCapacity() >> 1) * 0.25;
std::unique_ptr<Predicate> predicate(
generateNumericComparisonPredicate<DoubleType>(comp,
1,
comparison_literal));
std::unique_ptr<TupleIdSequence> matches(
TupleStorePredicateUtil::GetMatchesForPredicateOnTupleStore(*predicate, *tuple_store_));
for (tuple_id tid = 0;
tid < static_cast<tuple_id>(matches->length());
++tid) {
int insert_order_value = *static_cast<const int*>(tuple_store_->getAttributeValue(tid, 4));
if (GetParam() && (insert_order_value % 8 == 2)) {
// Value is NULL.
EXPECT_FALSE(matches->get(tid));
} else if (insert_order_value == -1) {
// Special last value.
if (matchExpected(comp,
GetLastValueForType(TypeFactory::GetType(kDouble, GetParam())).getLiteral<double>(),
comparison_literal)) {
EXPECT_TRUE(matches->get(tid));
} else {
EXPECT_FALSE(matches->get(tid));
}
} else {
if (matchExpected(comp,
insert_order_value * 0.25,
comparison_literal)) {
EXPECT_TRUE(matches->get(tid));
} else {
EXPECT_FALSE(matches->get(tid));
}
}
}
}
void checkPredicateNarrowCharAttr(const ComparisonID comp) const {
string comparison_literal = "500";
std::unique_ptr<Predicate> predicate(
generateStringComparisonPredicate(comp,
2,
comparison_literal));
std::unique_ptr<TupleIdSequence> matches(
TupleStorePredicateUtil::GetMatchesForPredicateOnTupleStore(*predicate, *tuple_store_));
for (tuple_id tid = 0;
tid < static_cast<tuple_id>(matches->length());
++tid) {
int insert_order_value = *static_cast<const int*>(tuple_store_->getAttributeValue(tid, 4));
if (GetParam() && (insert_order_value % 8 == 4)) {
// Value is NULL.
EXPECT_FALSE(matches->get(tid));
} else if (insert_order_value == -1) {
// Special last value.
if (matchExpected(comp,
std::string(static_cast<const char*>(
GetLastValueForType(TypeFactory::GetType(kChar, 4, GetParam())).getDataPtr())),
comparison_literal)) {
EXPECT_TRUE(matches->get(tid));
} else {
EXPECT_FALSE(matches->get(tid));
}
} else {
std::ostringstream char_buffer;
char_buffer << insert_order_value;
std::string string_literal(char_buffer.str());
if (string_literal.size() > 4) {
string_literal.resize(4);
}
if (matchExpected(comp,
string_literal,
comparison_literal)) {
EXPECT_TRUE(matches->get(tid));
} else {
EXPECT_FALSE(matches->get(tid));
}
}
}
}
void checkPredicateWideCharAttr(const ComparisonID comp) const {
string comparison_literal = "Here are some numbers: 5000";
std::unique_ptr<Predicate> predicate(
generateStringComparisonPredicate(comp,
3,
comparison_literal));
std::unique_ptr<TupleIdSequence> matches(
TupleStorePredicateUtil::GetMatchesForPredicateOnTupleStore(*predicate, *tuple_store_));
for (tuple_id tid = 0;
tid < static_cast<tuple_id>(matches->length());
++tid) {
int insert_order_value = *static_cast<const int*>(tuple_store_->getAttributeValue(tid, 4));
if (GetParam() && (insert_order_value % 8 == 6)) {
// Value is NULL.
EXPECT_FALSE(matches->get(tid));
} else if (insert_order_value == -1) {
// Special last value.
if (matchExpected(comp,
std::string(static_cast<const char*>(
GetLastValueForType(TypeFactory::GetType(kChar, 32, GetParam())).getDataPtr())),
comparison_literal)) {
EXPECT_TRUE(matches->get(tid));
} else {
EXPECT_FALSE(matches->get(tid));
}
} else {
std::ostringstream char_buffer;
char_buffer << "Here are some numbers: " << insert_order_value;
std::string string_literal(char_buffer.str());
if (matchExpected(comp,
string_literal,
comparison_literal)) {
EXPECT_TRUE(matches->get(tid));
} else {
EXPECT_FALSE(matches->get(tid));
}
}
}
}
void runCheckPredicateTest(const attribute_id sort_attribute_id) {
createBlock(sort_attribute_id, kSubBlockSize);
fillBlockWithSampleData(true, false);
checkPredicateIntAttr(ComparisonID::kEqual);
checkPredicateIntAttr(ComparisonID::kNotEqual);
checkPredicateIntAttr(ComparisonID::kLess);
checkPredicateIntAttr(ComparisonID::kLessOrEqual);
checkPredicateIntAttr(ComparisonID::kGreater);
checkPredicateIntAttr(ComparisonID::kGreaterOrEqual);
checkPredicateDoubleAttr(ComparisonID::kEqual);
checkPredicateDoubleAttr(ComparisonID::kNotEqual);
checkPredicateDoubleAttr(ComparisonID::kLess);
checkPredicateDoubleAttr(ComparisonID::kLessOrEqual);
checkPredicateDoubleAttr(ComparisonID::kGreater);
checkPredicateDoubleAttr(ComparisonID::kGreaterOrEqual);
checkPredicateNarrowCharAttr(ComparisonID::kEqual);
checkPredicateNarrowCharAttr(ComparisonID::kNotEqual);
checkPredicateNarrowCharAttr(ComparisonID::kLess);
checkPredicateNarrowCharAttr(ComparisonID::kLessOrEqual);
checkPredicateNarrowCharAttr(ComparisonID::kGreater);
checkPredicateNarrowCharAttr(ComparisonID::kGreaterOrEqual);
checkPredicateWideCharAttr(ComparisonID::kEqual);
checkPredicateWideCharAttr(ComparisonID::kNotEqual);
checkPredicateWideCharAttr(ComparisonID::kLess);
checkPredicateWideCharAttr(ComparisonID::kLessOrEqual);
checkPredicateWideCharAttr(ComparisonID::kGreater);
checkPredicateWideCharAttr(ComparisonID::kGreaterOrEqual);
}
void runSetAttributeValueInPlaceTypedTest(const attribute_id sort_attribute_id) {
createBlock(sort_attribute_id, kSubBlockSize);
fillBlockWithSampleData(true, false);
const tuple_id target_tid = tuple_store_->getMaxTupleID() >> 1;
TypedValue new_int_value(-42);
TypedValue new_double_value(static_cast<double>(-4.2));
const char new_narrow_char_lit[4] = "foo";
TypedValue new_narrow_char_value(kChar, new_narrow_char_lit, 4);
const char new_wide_char_lit[32] = "I am not a number!";
TypedValue new_wide_char_value(kChar, new_wide_char_lit, 32);
std::unordered_map<attribute_id, TypedValue> proposed_values;
if (sort_attribute_id != kInvalidCatalogId) {
switch (sort_attribute_id) {
case 0:
proposed_values.emplace(0, new_int_value);
break;
case 1:
proposed_values.emplace(1, new_double_value);
break;
case 2:
proposed_values.emplace(2, new_narrow_char_value);
break;
case 3:
proposed_values.emplace(3, new_wide_char_value);
break;
}
// Can't overwrite a sort column value in-place.
EXPECT_FALSE(tuple_store_->canSetAttributeValuesInPlaceTyped(
target_tid, proposed_values));
}
// Other column values should be OK.
proposed_values.clear();
proposed_values.emplace(0, new_int_value);
proposed_values.emplace(1, new_double_value);
proposed_values.emplace(2, new_narrow_char_value);
proposed_values.emplace(3, new_wide_char_value);
if (sort_attribute_id != kInvalidCatalogId) {
proposed_values.erase(sort_attribute_id);
}
EXPECT_TRUE(tuple_store_->canSetAttributeValuesInPlaceTyped(target_tid, proposed_values));
// Actually set values.
for (const auto &update_pair : proposed_values) {
tuple_store_->setAttributeValueInPlaceTyped(target_tid, update_pair.first, update_pair.second);
}
if (sort_attribute_id != 0) {
ASSERT_FALSE(tuple_store_->getAttributeValueTyped(target_tid, 0).isNull());
EXPECT_EQ(new_int_value.getLiteral<int>(),
tuple_store_->getAttributeValueTyped(target_tid, 0).getLiteral<int>());
}
if (sort_attribute_id != 1) {
ASSERT_FALSE(tuple_store_->getAttributeValueTyped(target_tid, 1).isNull());
EXPECT_EQ(new_double_value.getLiteral<double>(),
tuple_store_->getAttributeValueTyped(target_tid, 1).getLiteral<double>());
}
if (sort_attribute_id != 2) {
ASSERT_FALSE(tuple_store_->getAttributeValueTyped(target_tid, 2).isNull());
EXPECT_STREQ(static_cast<const char*>(new_narrow_char_value.getDataPtr()),
static_cast<const char*>(tuple_store_->getAttributeValueTyped(target_tid, 2).getDataPtr()));
}
if (sort_attribute_id != 3) {
ASSERT_FALSE(tuple_store_->getAttributeValueTyped(target_tid, 3).isNull());
EXPECT_STREQ(static_cast<const char*>(new_wide_char_value.getDataPtr()),
static_cast<const char*>(tuple_store_->getAttributeValueTyped(target_tid, 3).getDataPtr()));
}
if (GetParam()) {
// Set NULL values.
for (const auto &update_pair : proposed_values) {
tuple_store_->setAttributeValueInPlaceTyped(target_tid,
update_pair.first,
TypedValue(update_pair.second.getTypeID()));
}
for (attribute_id attr_id = 0; attr_id < 4; ++attr_id) {
if (attr_id != sort_attribute_id) {
EXPECT_TRUE(tuple_store_->getAttributeValueTyped(target_tid, attr_id).isNull());
}
}
// Set back to non-null values.
for (const auto &update_pair : proposed_values) {
tuple_store_->setAttributeValueInPlaceTyped(target_tid, update_pair.first, update_pair.second);
}
if (sort_attribute_id != 0) {
ASSERT_FALSE(tuple_store_->getAttributeValueTyped(target_tid, 0).isNull());
EXPECT_EQ(new_int_value.getLiteral<int>(),
tuple_store_->getAttributeValueTyped(target_tid, 0).getLiteral<int>());
}
if (sort_attribute_id != 1) {
ASSERT_FALSE(tuple_store_->getAttributeValueTyped(target_tid, 1).isNull());
EXPECT_EQ(new_double_value.getLiteral<double>(),
tuple_store_->getAttributeValueTyped(target_tid, 1).getLiteral<double>());
}
if (sort_attribute_id != 2) {
ASSERT_FALSE(tuple_store_->getAttributeValueTyped(target_tid, 2).isNull());
EXPECT_STREQ(static_cast<const char*>(new_narrow_char_value.getDataPtr()),
static_cast<const char*>(tuple_store_->getAttributeValueTyped(target_tid, 2).getDataPtr()));
}
if (sort_attribute_id != 3) {
ASSERT_FALSE(tuple_store_->getAttributeValueTyped(target_tid, 3).isNull());
EXPECT_STREQ(static_cast<const char*>(new_wide_char_value.getDataPtr()),
static_cast<const char*>(tuple_store_->getAttributeValueTyped(target_tid, 3).getDataPtr()));
}
}
}
std::unique_ptr<CatalogRelation> relation_;
ScopedBuffer tuple_store_memory_;
std::unique_ptr<TupleStorageSubBlockDescription> tuple_store_description_;
std::unique_ptr<BasicColumnStoreTupleStorageSubBlock> tuple_store_;
};
typedef BasicColumnStoreTupleStorageSubBlockTest BasicColumnStoreTupleStorageSubBlockDeathTest;
TEST_P(BasicColumnStoreTupleStorageSubBlockTest, DescriptionIsValidTest) {
// The descriptions we use for the other tests should be valid.
for (const CatalogAttribute &attr : *relation_) {
tuple_store_description_.reset(
new TupleStorageSubBlockDescription());
tuple_store_description_->set_sub_block_type(
TupleStorageSubBlockDescription::BASIC_COLUMN_STORE);
tuple_store_description_->SetExtension(
BasicColumnStoreTupleStorageSubBlockDescription::sort_attribute_id,
attr.getID());
EXPECT_TRUE(BasicColumnStoreTupleStorageSubBlock::DescriptionIsValid(
*relation_,
*tuple_store_description_));
}
// Also check a description that doesn't specify a sort column.
tuple_store_description_.reset(new TupleStorageSubBlockDescription());
tuple_store_description_->set_sub_block_type(
TupleStorageSubBlockDescription::BASIC_COLUMN_STORE);
EXPECT_TRUE(BasicColumnStoreTupleStorageSubBlock::DescriptionIsValid(
*relation_,
*tuple_store_description_));
// An uninitialized description is not valid.
tuple_store_description_.reset(new TupleStorageSubBlockDescription());
EXPECT_FALSE(BasicColumnStoreTupleStorageSubBlock::DescriptionIsValid(
*relation_,
*tuple_store_description_));
// A description that specifies the wrong sub_block_type is not valid.
tuple_store_description_->set_sub_block_type(
TupleStorageSubBlockDescription::SPLIT_ROW_STORE);
EXPECT_FALSE(BasicColumnStoreTupleStorageSubBlock::DescriptionIsValid(
*relation_,
*tuple_store_description_));
// A relation with a variable-length attribute can't be used with this block type.
std::unique_ptr<CatalogRelation> variable_length_relation(
new CatalogRelation(nullptr, "variable_length_relation"));
CatalogAttribute *variable_length_attribute = new CatalogAttribute(
variable_length_relation.get(),
"variable_length_attr",
TypeFactory::GetType(kVarChar, 20, false));
ASSERT_EQ(0, variable_length_relation->addAttribute(variable_length_attribute));
tuple_store_description_.reset(new TupleStorageSubBlockDescription());
tuple_store_description_->set_sub_block_type(
TupleStorageSubBlockDescription::BASIC_COLUMN_STORE);
tuple_store_description_->SetExtension(
BasicColumnStoreTupleStorageSubBlockDescription::sort_attribute_id,
0);
EXPECT_FALSE(BasicColumnStoreTupleStorageSubBlock::DescriptionIsValid(*variable_length_relation,
*tuple_store_description_));
}
TEST_P(BasicColumnStoreTupleStorageSubBlockDeathTest, ConstructWithInvalidDescriptionTest) {
EXPECT_DEATH(createBlock(10, kSubBlockSize), "");
}
TEST_P(BasicColumnStoreTupleStorageSubBlockTest, BlockTooSmallTest) {
EXPECT_THROW(createBlock(0, 32), BlockMemoryTooSmall);
}
TEST_P(BasicColumnStoreTupleStorageSubBlockTest, InsertWithNoSortColumnTest) {
// Non-random, batch insert.
createBlock(kInvalidCatalogId, kSubBlockSize);
fillBlockWithSampleData(false, false);
// Non-random, ad-hoc insert.
createBlock(kInvalidCatalogId, kSubBlockSize);
fillBlockWithSampleData(false, true);
// Random order, batch insert.
createBlock(kInvalidCatalogId, kSubBlockSize);
fillBlockWithSampleData(true, false);
// Random order, ad-hoc insert.
createBlock(kInvalidCatalogId, kSubBlockSize);
fillBlockWithSampleData(true, true);
}
TEST_P(BasicColumnStoreTupleStorageSubBlockTest, InsertWithIntSortColumnTest) {
// Non-random, batch insert.
createBlock(0, kSubBlockSize);
fillBlockWithSampleData(false, false);
// Non-random, ad-hoc insert.
createBlock(0, kSubBlockSize);
fillBlockWithSampleData(false, true);
// Random order, batch insert.
createBlock(0, kSubBlockSize);
fillBlockWithSampleData(true, false);
// Random order, ad-hoc insert.
createBlock(0, kSubBlockSize);
fillBlockWithSampleData(true, true);
}
TEST_P(BasicColumnStoreTupleStorageSubBlockTest, InsertWithDoubleSortColumnTest) {
// Non-random, batch insert.
createBlock(1, kSubBlockSize);
fillBlockWithSampleData(false, false);
// Non-random, ad-hoc insert.
createBlock(1, kSubBlockSize);
fillBlockWithSampleData(false, true);
// Random order, batch insert.
createBlock(1, kSubBlockSize);
fillBlockWithSampleData(true, false);
// Random order, ad-hoc insert.
createBlock(1, kSubBlockSize);
fillBlockWithSampleData(true, true);
}
TEST_P(BasicColumnStoreTupleStorageSubBlockTest, InsertWithNarrowCharSortColumnTest) {
// Non-random, batch insert.
createBlock(2, kSubBlockSize);
fillBlockWithSampleData(false, false);
// Non-random, ad-hoc insert.
createBlock(2, kSubBlockSize);
fillBlockWithSampleData(false, true);
// Random order, batch insert.
createBlock(2, kSubBlockSize);
fillBlockWithSampleData(true, false);
// Random order, ad-hoc insert.
createBlock(2, kSubBlockSize);
fillBlockWithSampleData(true, true);
}
TEST_P(BasicColumnStoreTupleStorageSubBlockTest, InsertWithWideCharSortColumnTest) {
// Non-random, batch insert.
createBlock(3, kSubBlockSize);
fillBlockWithSampleData(false, false);
// Non-random, ad-hoc insert.
createBlock(3, kSubBlockSize);
fillBlockWithSampleData(false, true);
// Random order, batch insert.
createBlock(3, kSubBlockSize);
fillBlockWithSampleData(true, false);
// Random order, ad-hoc insert.
createBlock(3, kSubBlockSize);
fillBlockWithSampleData(true, true);
}
TEST_P(BasicColumnStoreTupleStorageSubBlockTest, GetValuesWithNoSortColumnTest) {
createBlock(kInvalidCatalogId, kSubBlockSize);
fillBlockWithSampleData(true, false);
checkBlockValues(kInvalidCatalogId, 0, 0);
}
TEST_P(BasicColumnStoreTupleStorageSubBlockTest, GetValuesWithIntSortColumnTest) {
createBlock(0, kSubBlockSize);
fillBlockWithSampleData(true, false);
checkBlockValues(0, 0, 0);
}
TEST_P(BasicColumnStoreTupleStorageSubBlockTest, GetValuesWithDoubleSortColumnTest) {
createBlock(1, kSubBlockSize);
fillBlockWithSampleData(true, false);
checkBlockValues(1, 0, 0);
}
TEST_P(BasicColumnStoreTupleStorageSubBlockTest, GetValuesWithNarrowCharSortColumnTest) {
createBlock(2, kSubBlockSize);
fillBlockWithSampleData(true, false);
checkBlockValues(2, 0, 0);
}
TEST_P(BasicColumnStoreTupleStorageSubBlockTest, GetValuesWithWideCharSortColumnTest) {
createBlock(3, kSubBlockSize);
fillBlockWithSampleData(true, false);
checkBlockValues(3, 0, 0);
}
TEST_P(BasicColumnStoreTupleStorageSubBlockTest, DeleteWithNoSortColumnTest) {
runDeleteTest(kInvalidCatalogId);
}
TEST_P(BasicColumnStoreTupleStorageSubBlockTest, DeleteWithIntSortColumnTest) {
runDeleteTest(0);
}
TEST_P(BasicColumnStoreTupleStorageSubBlockTest, DeleteWithDoubleSortColumnTest) {
runDeleteTest(1);
}
TEST_P(BasicColumnStoreTupleStorageSubBlockTest, DeleteWithNarrowCharSortColumnTest) {
runDeleteTest(2);
}
TEST_P(BasicColumnStoreTupleStorageSubBlockTest, DeleteWithWideCharSortColumnTest) {
runDeleteTest(3);
}
TEST_P(BasicColumnStoreTupleStorageSubBlockTest, GetMatchesForPredicateWithNoSortColumnTest) {
runCheckPredicateTest(kInvalidCatalogId);
}
TEST_P(BasicColumnStoreTupleStorageSubBlockTest, GetMatchesForPredicateWithIntSortColumnTest) {
runCheckPredicateTest(0);
}
TEST_P(BasicColumnStoreTupleStorageSubBlockTest, GetMatchesForPredicateWithDoubleSortColumnTest) {
runCheckPredicateTest(1);
}
TEST_P(BasicColumnStoreTupleStorageSubBlockTest, GetMatchesForPredicateWithNarrowCharSortColumnTest) {
runCheckPredicateTest(2);
}
TEST_P(BasicColumnStoreTupleStorageSubBlockTest, GetMatchesForPredicateWithWideCharSortColumnTest) {
runCheckPredicateTest(3);
}
TEST_P(BasicColumnStoreTupleStorageSubBlockTest, SetAttributeValueInPlaceTypedWithNoSortColumnTest) {
runSetAttributeValueInPlaceTypedTest(kInvalidCatalogId);
}
TEST_P(BasicColumnStoreTupleStorageSubBlockTest, SetAttributeValueInPlaceTypedWithIntSortColumnTest) {
runSetAttributeValueInPlaceTypedTest(0);
}
TEST_P(BasicColumnStoreTupleStorageSubBlockTest, SetAttributeValueInPlaceTypedWithDoubleSortColumnTest) {
runSetAttributeValueInPlaceTypedTest(1);
}
TEST_P(BasicColumnStoreTupleStorageSubBlockTest, SetAttributeValueInPlaceTypedWithNarrowCharSortColumnTest) {
runSetAttributeValueInPlaceTypedTest(2);
}
TEST_P(BasicColumnStoreTupleStorageSubBlockTest, SetAttributeValueInPlaceTypedWithWideCharSortColumnTest) {
runSetAttributeValueInPlaceTypedTest(3);
}
TEST(BasicColumnStoreTupleStorageSubBlockNullTypeTest, NullTypeTest) {
// Set up a relation with an int attribute (need a sortable attribute to use
// BasicColumnStore) and a single NullType attribute.
CatalogRelation test_relation(nullptr, "TestRelation");
CatalogAttribute *int_attr = new CatalogAttribute(&test_relation,
"int_attr",
TypeFactory::GetType(kInt, false));
ASSERT_EQ(0, test_relation.addAttribute(int_attr));
CatalogAttribute *nulltype_attr = new CatalogAttribute(&test_relation,
"nulltype_attr",
TypeFactory::GetType(kNullType, true));
ASSERT_EQ(1, test_relation.addAttribute(nulltype_attr));
// Set up a minimal StorageBlockLayoutDescription.
StorageBlockLayoutDescription layout_desc;
layout_desc.set_num_slots(1);
layout_desc.mutable_tuple_store_description()->set_sub_block_type(
TupleStorageSubBlockDescription::BASIC_COLUMN_STORE);
layout_desc.mutable_tuple_store_description()->SetExtension(
BasicColumnStoreTupleStorageSubBlockDescription::sort_attribute_id,
0);
// Check that the description is considered valid.
EXPECT_TRUE(StorageBlockLayout::DescriptionIsValid(test_relation, layout_desc));
StorageBlockLayout layout(test_relation, layout_desc);
// Construct an actual PackedRowStoreTupleStorageSubBlock.
ScopedBuffer tuple_store_memory(kSlotSizeBytes);
BasicColumnStoreTupleStorageSubBlock tuple_store(test_relation,
layout_desc.tuple_store_description(),
true,
tuple_store_memory.get(),
kSlotSizeBytes);
// Insert some NullType values.
std::vector<TypedValue> attr_values;
attr_values.emplace_back(0);
attr_values.emplace_back(kNullType);
Tuple tuple(std::move(attr_values));
for (tuple_id tid = 0; tid < 100; ++tid) {
tuple_store.insertTuple(tuple);
}
EXPECT_EQ(100, tuple_store.numTuples());
// Read out values.
for (tuple_id tid = 0; tid < 100; ++tid) {
ASSERT_TRUE(tuple_store.hasTupleWithID(tid));
EXPECT_EQ(nullptr, tuple_store.getAttributeValue(tid, 1));
TypedValue value = tuple_store.getAttributeValueTyped(tid, 1);
EXPECT_TRUE(value.isNull());
EXPECT_EQ(kNullType, value.getTypeID());
}
// Delete some values.
TupleIdSequence delete_sequence(100);
delete_sequence.set(5, true);
delete_sequence.set(25, true);
delete_sequence.set(45, true);
delete_sequence.set(65, true);
delete_sequence.set(85, true);
EXPECT_TRUE(tuple_store.bulkDeleteTuples(&delete_sequence));
EXPECT_EQ(95, tuple_store.numTuples());
ASSERT_EQ(94, tuple_store.getMaxTupleID());
// Read out the remaining undeleted values.
for (tuple_id tid = 0; tid < 95; ++tid) {
ASSERT_TRUE(tuple_store.hasTupleWithID(tid));
EXPECT_EQ(nullptr, tuple_store.getAttributeValue(tid, 1));
TypedValue value = tuple_store.getAttributeValueTyped(tid, 1);
EXPECT_TRUE(value.isNull());
EXPECT_EQ(kNullType, value.getTypeID());
}
}
// Note: INSTANTIATE_TEST_CASE_P has variadic arguments part. If the variable argument part
// is empty, C++11 standard says it should produce a warning. A warning is converted
// to an error since we use -Werror as a compiler parameter. It causes Travis to build.
// This is the reason that we must give an empty string argument as a last parameter
// to supress warning that clang gives.
INSTANTIATE_TEST_CASE_P(WithAndWithoutNullableAttributes,
BasicColumnStoreTupleStorageSubBlockTest,
::testing::Bool(),); // NOLINT(whitespace/comma)
INSTANTIATE_TEST_CASE_P(WithAndWithoutNullableAttributes,
BasicColumnStoreTupleStorageSubBlockDeathTest,
::testing::Bool(),); // NOLINT(whitespace/comma)
} // namespace quickstep