Commit a2bf1c66 authored by Philipp Götze's avatar Philipp Götze

🔀 Merge branch 'wip/testcases'

parents 46ddfacb fe2a2882
......@@ -5,17 +5,18 @@ A persistent analytical table structure for non-volatile memory.
Please see the file [COPYING](COPYING) for license information.
### TODOs:
- [ ] Tests
- [x] Tests
- [ ] Get rid of expensive transactions
- [x] Improve benchmarks
- [ ] Describe usage
- [ ] The BlockIterator currently suffers from bugs (SEGSIGV with too big structures)
### Installation ###
We use the C++17 standard, thus, you need a very recent C++ compiler and CMake (3.2 or newer) build environment.
We use the C++14 standard, thus, you need a recent C++ compiler and CMake (3.2 or newer) build environment.
In addition some third party libraries such as [Catch](https://github.com/philsquared/Catch) for testing, [Google Benchmark](https://github.com/google/benchmark.git) for benchmarking, [Format](https://github.com/fmtlib/fmt.git), and [NVML](https://github.com/pmem/nvml.git) are used
These, however, are either included or downloaded during the build process.
In addition some third party libraries such as [Catch](https://github.com/philsquared/Catch) for testing, [Google Benchmark](https://github.com/google/benchmark.git) for benchmarking, [Format](https://github.com/fmtlib/fmt.git), and [PMDK](https://github.com/pmem/pmdk.git) are used.
These, however, are both downloaded and included during the build process.
After cloning and switching into the project directory, you can build the repository with with:
```
......@@ -29,4 +30,7 @@ make test
### Usage ###
The test cases and benchmarks use the directory _/mnt/pmem/test_ for storing data. Thus, make sure the pmem device is mounted at _/mnt/pmem_ and the subfolder _test_ has write permissions for everybody. Alternatively, you can change the directory in [common.h](src/bench/common.h).
The test cases and benchmarks use the directory _/mnt/pmem/test_ for storing data. Thus, make sure the pmem device is mounted at _/mnt/pmem_ and the subfolder _test_ has write permissions for everybody.
Alternatively, you can change the directory in the customization section in [CMakeLists.txt](src/CMakeLists.txt).
A detailed usage description will follow soon.
Till then the general usage can be found out by looking at test cases and benchmarks.
......@@ -44,7 +44,7 @@ if (BUILD_GOOGLE_BENCH)
# Google Benchmark framework
download_project(PROJ benchmark
GIT_REPOSITORY https://github.com/google/benchmark.git
GIT_TAG master
GIT_TAG releasing
UPDATE_DISCONNECTED 1
QUIET
)
......@@ -64,17 +64,17 @@ add_custom_command(
endif()
#--------------------------------------------------------------------------------
# Non-Volatile Memory Library (pmem.io)
download_project(PROJ nvml
GIT_REPOSITORY https://github.com/pmem/nvml.git
# Peristent Memory Development Kit (pmem.io)
download_project(PROJ pmdk
GIT_REPOSITORY https://github.com/pmem/pmdk.git
GIT_TAG master
UPDATE_DISCONNECTED 1
QUIET
)
add_custom_command(
OUTPUT ${THIRD_PARTY_DIR}/nvml
COMMAND ${CMAKE_COMMAND} -E chdir ${nvml_SOURCE_DIR} $(MAKE)
COMMAND ${CMAKE_COMMAND} -E chdir ${nvml_SOURCE_DIR} $(MAKE) install prefix=${THIRD_PARTY_DIR}/nvml
OUTPUT ${THIRD_PARTY_DIR}/pmdk
COMMAND ${CMAKE_COMMAND} -E chdir ${pmdk_SOURCE_DIR} $(MAKE)
COMMAND ${CMAKE_COMMAND} -E chdir ${pmdk_SOURCE_DIR} $(MAKE) install prefix=${THIRD_PARTY_DIR}/pmdk
)
#--------------------------------------------------------------------------------
......
......@@ -3,15 +3,24 @@
# Define a macro to simplify building and linking test executables
#
#=============================================
macro( build_executable arg )
macro( build_test arg )
include_directories("${PROJECT_SOURCE_DIR}/test")
add_executable( ${arg} "${arg}.cpp" )
target_link_libraries( ${arg}
ptable
${NVML_LIBRARIES}
${BENCHMARK_LIB}
)
endmacro( build_executable )
add_executable( ${arg} "${arg}.cpp" $<TARGET_OBJECTS:MainTest>)
target_link_libraries( ${arg}
ptable
${PMDK_LIBRARIES}
${BENCHMARK_LIB}
)
endmacro( build_test )
macro( build_bench arg )
add_executable( ${arg} "${arg}.cpp")
target_link_libraries( ${arg}
ptable
${PMDK_LIBRARIES}
${BENCHMARK_LIB}
)
endmacro( build_bench )
#=============================================
#
......@@ -23,9 +32,17 @@ macro (do_test arg)
NAME ${arg}
COMMAND "${CMAKE_CURRENT_BINARY_DIR}/${arg}"
)
build_executable( ${arg} )
build_test( ${arg} )
endmacro(do_test)
macro (do_bench arg)
add_test(
NAME ${arg}
COMMAND "${CMAKE_CURRENT_BINARY_DIR}/${arg}"
)
build_bench( ${arg} )
endmacro(do_bench)
set (CTEST_ENVIRONMENT
"DYLD_FALLBACK_LIBRARY_PATH=${DYLD_LIBRARY_PATH}"
)
......@@ -39,7 +39,7 @@ else()
endif()
# C++ compiler flags
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -Wall -Wno-deprecated -g -O2 -Wsign-compare")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -Wall -g -O2 -Wsign-compare")
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-local-typedefs -Wno-#pragma-messages")
elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU")
......@@ -77,18 +77,18 @@ else()
endif()
########################
# Non-Volatile Memory Library
# Persistent Memory Development Kit
########################
#
set (NVML_LIBRARIES
"${THIRD_PARTY_DIR}/nvml/lib/libpmemblk.a"
"${THIRD_PARTY_DIR}/nvml/lib/libpmemlog.a"
"${THIRD_PARTY_DIR}/nvml/lib/libpmemobj.a"
"${THIRD_PARTY_DIR}/nvml/lib/libpmempool.a"
"${THIRD_PARTY_DIR}/nvml/lib/libpmem.a"
set (PMDK_LIBRARIES
"${THIRD_PARTY_DIR}/pmdk/lib/libpmemblk.a"
"${THIRD_PARTY_DIR}/pmdk/lib/libpmemlog.a"
"${THIRD_PARTY_DIR}/pmdk/lib/libpmemobj.a"
"${THIRD_PARTY_DIR}/pmdk/lib/libpmempool.a"
"${THIRD_PARTY_DIR}/pmdk/lib/libpmem.a"
${DYLIB_LIBRARY}
)
include_directories("${THIRD_PARTY_DIR}/nvml/include")
include_directories("${THIRD_PARTY_DIR}/pmdk/include")
include_directories("${THIRD_PARTY_DIR}/variant/include")
#-----------------------------------------------------------------------------------------
#
......@@ -104,10 +104,10 @@ include_directories(${PROJECT_SOURCE_DIR}
include_directories("${THIRD_PARTY_DIR}")
set(core_sources
core/PTableInfo.cpp
core/PTableInfo.cpp
${THIRD_PARTY_DIR}/fmt
${THIRD_PARTY_DIR}/catch
${THIRD_PARTY_DIR}/nvml
${THIRD_PARTY_DIR}/pmdk
${THIRD_PARTY_DIR}/variant
)
......@@ -119,7 +119,7 @@ if(BUILD_GOOGLE_BENCH)
endif()
add_library(ptable SHARED ${core_sources})
target_link_libraries(ptable ${NVML_LIBRARIES})
target_link_libraries(ptable ${PMDK_LIBRARIES})
#-----------------------------------------------------------------------------------------
......@@ -150,7 +150,8 @@ foreach(LIB ${PTABLE_LIBS})
install(TARGETS ${LIB} DESTINATION ${PTABLE_LIB_DIR})
endforeach(LIB)
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/"
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/core"
"${CMAKE_BINARY_DIR}/generated/"
"${THIRD_PARTY_DIR}/variant/include/mpark"
"${CMAKE_BINARY_DIR}/generated/"
DESTINATION ${PTABLE_INCLUDE_DIR}
......@@ -159,5 +160,10 @@ install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/"
PATTERN "*.h"
)
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/PBPTree.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/PTable.hpp"
DESTINATION ${PTABLE_INCLUDE_DIR}
)
# show used compiler
message("Using Compiler: ${CMAKE_CXX_COMPILER_ID}.")
......@@ -35,11 +35,11 @@
namespace ptable {
using nvml::obj::delete_persistent;
using nvml::obj::make_persistent;
using nvml::obj::p;
using nvml::obj::persistent_ptr;
using nvml::obj::transaction;
using pmem::obj::delete_persistent;
using pmem::obj::make_persistent;
using pmem::obj::p;
using pmem::obj::persistent_ptr;
using pmem::obj::transaction;
/**
* A persistent memory implementation of a B+ tree.
......@@ -61,7 +61,7 @@ class PBPTree {
#ifndef UNIT_TESTS
private:
#else
public:f
public:
#endif
// Forward declarations
......@@ -195,7 +195,7 @@ class PBPTree {
* @param val the value that is associated with the key
*/
void insert(const KeyType &key, const ValueType &val) {
auto pop = nvml::obj::pool_by_vptr(this);
auto pop = pmem::obj::pool_by_vptr(this);
transaction::exec_tx(pop, [&] {
SplitInfo splitInfo;
......@@ -321,6 +321,7 @@ class PBPTree {
// for each key-value pair within the range call func
for (auto i = 0u; i < leaf->numKeys; i++) {
auto &key = leaf->keys.get_ro()[i];
if (key < minKey) continue;
if (key > maxKey) return;
auto &val = leaf->values.get_ro()[i];
......@@ -1028,7 +1029,7 @@ class PBPTree {
* Create a new empty leaf node
*/
persistent_ptr<LeafNode> newLeafNode() {
auto pop = nvml::obj::pool_by_vptr(this);
auto pop = pmem::obj::pool_by_vptr(this);
persistent_ptr<LeafNode> newNode = nullptr;
transaction::exec_tx(pop, [&] {
newNode = make_persistent<LeafNode>();
......@@ -1037,7 +1038,7 @@ class PBPTree {
}
void deleteLeafNode(persistent_ptr<LeafNode> node) {
auto pop = nvml::obj::pool_by_vptr(this);
auto pop = pmem::obj::pool_by_vptr(this);
transaction::exec_tx(pop, [&] {
delete_persistent<LeafNode>(node);
});
......@@ -1047,7 +1048,7 @@ class PBPTree {
* Create a new empty branch node
*/
persistent_ptr<BranchNode> newBranchNode() {
auto pop = nvml::obj::pool_by_vptr(this);
auto pop = pmem::obj::pool_by_vptr(this);
persistent_ptr<BranchNode> newNode = nullptr;
transaction::exec_tx(pop, [&] {
newNode = make_persistent<BranchNode>();
......@@ -1056,7 +1057,7 @@ class PBPTree {
}
void deleteBranchNode(persistent_ptr<BranchNode> node) {
auto pop = nvml::obj::pool_by_vptr(this);
auto pop = pmem::obj::pool_by_vptr(this);
transaction::exec_tx(pop, [&] {
delete_persistent<BranchNode>(node);
});
......
......@@ -20,6 +20,7 @@
#ifndef PTable_hpp_
#define PTable_hpp_
#include <algorithm>
#include <bitset>
#include "mpark/variant.hpp"
......@@ -29,13 +30,14 @@
#include "core/PTuple.hpp"
#include "core/serialize.hpp"
#include "core/utils.hpp"
#include "core/VTableInfo.hpp"
#include "PBPTree.hpp"
namespace ptable {
const std::string LAYOUT = "PTable";
auto const TARGET_INDEX_NODE_SIZE = 4 * 1024; // 4KB
auto const TARGET_INDEX_NODE_SIZE = 1 * 1024; // 1KB
/** Abort reason codes: */
const auto NOT_ENOUGH_SPACE = 1;
......@@ -51,22 +53,28 @@ using ColumnRangeMap = std::unordered_map<uint16_t, std::pair<IntDoubleString, I
*
* \author Philipp Goetze <philipp.goetze@tu-ilmenau.de>
*****************************************************************************/
template<class Tuple, typename KeyType>
class PTable {
template<typename KeyType, class TupleType>
class PTable;
template<typename KeyType, class... Types>
class PTable<KeyType, std::tuple<Types...>> {
using Tuple = std::tuple<Types...>;
static const auto BRANCHKEYS = ((TARGET_INDEX_NODE_SIZE - 28) / (sizeof(KeyType) + 24)) & ~1;
static const auto
LEAFKEYS = ((TARGET_INDEX_NODE_SIZE - 36) / (sizeof(KeyType) + sizeof(PTuple<Tuple, KeyType>))) & ~1;
static const auto LEAFKEYS = ((TARGET_INDEX_NODE_SIZE - 36) /
(sizeof(KeyType) + sizeof(PTuple<KeyType, Tuple>))) & ~1;
using VTableInfoType = VTableInfo<KeyType, Tuple>;
using PTableInfoType = PTableInfo<KeyType, Tuple>;
using ColumnIntMap = std::map<uint16_t, uint16_t>;
using IndexType = PBPTree<KeyType, PTuple<Tuple, KeyType>, BRANCHKEYS, LEAFKEYS>;
using DataNodePtr = persistent_ptr<struct DataNode<KeyType>>;
using IndexType = PBPTree<KeyType, PTuple<KeyType, Tuple>, BRANCHKEYS, LEAFKEYS>;
using DataNodePtr = persistent_ptr<DataNode<KeyType>>;
/************************************************************************//**
* \brief Iterator to iterate over all tuples using the blocks.
***************************************************************************/
class BlockIterator {
using DataNodeVector = std::vector<persistent_ptr<DataNode<KeyType>>>;
const PTable<Tuple, KeyType> parent;
const PTable<KeyType, Tuple>& parent;
const ColumnRangeMap predicates;
const DataNodeVector candidates;
typename DataNodeVector::const_iterator currentNode;
......@@ -74,32 +82,26 @@ class PTable {
uint16_t currentPos = 0u;
public:
BlockIterator(const PTable<Tuple, KeyType> &_parent, const ColumnRangeMap &_predicates) :
parent(_parent),
predicates(_predicates),
candidates(parent.getCandidateBlocks(predicates)),
currentNode(candidates.cbegin()),
currentCnt(
candidates.size() > 0 ? reinterpret_cast<const uint32_t &>((*currentNode)->block.get_ro()[gCountPos]) : 0) {
if (candidates.size() > 0) ++(*this);
else currentPos = 1; // --> setting to end()
}
BlockIterator(const PTable<KeyType, Tuple> &_parent) :
parent(_parent), currentNode(nullptr), currentPos(1) {}
BlockIterator(const PTable<Tuple, KeyType> &_parent,
BlockIterator(const PTable<KeyType, Tuple> &_parent,
const ColumnRangeMap &_predicates) :
BlockIterator(_parent, _predicates, _parent.getCandidateBlocks(_predicates)) {}
BlockIterator(const PTable<KeyType, Tuple> &_parent,
const ColumnRangeMap &_predicates,
const DataNodeVector &_candidates) :
parent(_parent),
predicates(_predicates),
candidates(_candidates),
currentNode(candidates.cbegin()),
currentCnt(
candidates.size() > 0 ? reinterpret_cast<const uint32_t &>((*currentNode)->block.get_ro()[gCountPos]) : 0) {
currentCnt(candidates.size() > 0 ?
reinterpret_cast<const uint32_t &>((*currentNode)->block.get_ro()[gCountPos]) : 0) {
if (candidates.size() > 0) ++(*this);
else currentPos = 1; // --> setting to end()
}
BlockIterator(const PTable<Tuple, KeyType> &_parent) :
parent(_parent), currentNode(nullptr), currentPos(1) {}
BlockIterator &operator++() {
if (++currentPos > currentCnt) {
......@@ -142,13 +144,25 @@ class PTable {
bool operator==(BlockIterator other) const {
return (
// currentBlock == other.currentBlock && // This prevents the comparison with end()
currentPos == other.currentPos &&
currentCnt == other.currentCnt);
currentPos == other.currentPos && currentCnt == other.currentCnt);
}
bool operator!=(BlockIterator other) const { return !(*this == other); }
PTuple<Tuple, KeyType> operator*() {
std::array<uint16_t, PTuple<Tuple, KeyType>::NUM_ATTRIBUTES> pTupleOffsets;
PTuple<KeyType, Tuple> operator*() {
std::array<uint16_t, PTuple<KeyType, Tuple>::NUM_ATTRIBUTES> pTupleOffsets;
/*
auto setPTupleOffsets = [&](auto element, std::size_t idx) {
pTupleOffsets[idx] = ColumnAttributes<toColumnType<decltype(element)>()>::dataOffsetPos(
currentNode->get()->block.get_ro(),
gDataOffsetPos + idx * gAttrOffsetSize,
currentPos
);
};
forEachTypeInTuple<decltype(setPTupleOffsets), Types...>(setPTupleOffsets);
return PTuple<KeyType, Tuple>(*currentNode, pTupleOffsets);
*/
auto idx = 0u;
for (const auto &c : *parent.root->tInfo) {
const auto
......@@ -156,15 +170,15 @@ class PTable {
reinterpret_cast<const uint16_t &>((*currentNode)->block.get_ro()[gDataOffsetPos + idx * gAttrOffsetSize]);
uint16_t dataOffset;
switch (c.getType()) {
case Int_Type: {
case ColumnType::IntType: {
dataOffset = dataPos + (currentPos - 1) * sizeof(int);
}
break;
case Double_Type: {
case ColumnType::DoubleType: {
dataOffset = dataPos + (currentPos - 1) * sizeof(double);
}
break;
case String_Type: {
case ColumnType::StringType: {
dataOffset = reinterpret_cast<const uint16_t &>((*currentNode)->block.get_ro()[dataPos
+ (currentPos - 1) * gOffsetSize]);
}
......@@ -174,14 +188,14 @@ class PTable {
pTupleOffsets[idx] = dataOffset;
++idx;
}
return *(new PTuple<Tuple, KeyType>(*currentNode, pTupleOffsets));
return std::move(PTuple<KeyType, Tuple>(*currentNode, pTupleOffsets));
}
// iterator traits
using difference_type = long;
using value_type = PTuple<Tuple, KeyType>;
using pointer = const PTuple<Tuple, KeyType> *;
using reference = const PTuple<Tuple, KeyType> &;
using value_type = PTuple<KeyType, Tuple>;
using pointer = const PTuple<KeyType, Tuple> *;
using reference = const PTuple<KeyType, Tuple> &;
using iterator_category = std::forward_iterator_tag;
};
......@@ -191,12 +205,12 @@ class PTable {
***************************************************************************/
class iterator {
public:
using Predicate = std::function<bool(const PTuple<Tuple, KeyType> &)>;
using Predicate = std::function<bool(const PTuple<KeyType, Tuple> &)>;
using TreeIter = typename IndexType::iterator;
iterator() : treeIter() {}
iterator(TreeIter _iter, TreeIter _end, Predicate _pred = [](const PTuple<Tuple, KeyType> &) { return true; })
iterator(TreeIter _iter, TreeIter _end, Predicate _pred = [](const PTuple<KeyType, Tuple> &) { return true; })
: treeIter(_iter), end(_end), pred(_pred) {
while (isValid() && !pred((*treeIter).second))
treeIter++;
......@@ -230,15 +244,15 @@ class PTable {
bool operator!=(iterator other) const { return !(treeIter == other.treeIter); }
PTuple<Tuple, KeyType> operator*() {
PTuple<KeyType, Tuple> operator*() {
return (*treeIter).second;
}
// iterator traits
using difference_type = long;
using value_type = PTuple<Tuple, KeyType>;
using pointer = const PTuple<Tuple, KeyType> *;
using reference = const PTuple<Tuple, KeyType> &;
using value_type = PTuple<KeyType, Tuple>;
using pointer = const PTuple<KeyType, Tuple> *;
using reference = const PTuple<KeyType, Tuple> &;
using iterator_category = std::forward_iterator_tag;
protected:
TreeIter treeIter, end;
......@@ -269,7 +283,7 @@ class PTable {
/************************************************************************//**
* \brief Constructor for a given schema (using TableInfo) and dimension clustering.
***************************************************************************/
PTable(const VTableInfo &tInfo, const Dimensions &_bdccInfo = Dimensions()) {
PTable(const VTableInfoType &tInfo, const Dimensions &_bdccInfo = Dimensions()) {
auto pop = pool_by_vptr(this);
transaction::exec_tx(pop, [&] { init(tInfo, _bdccInfo); });
}
......@@ -404,13 +418,24 @@ class PTable {
std::size_t nres;
auto pop = pool_by_vptr(this);
transaction::exec_tx(pop, [&] {
//TODO: Also delete data?
//delete_persistent<PTuple<Tuple>>(getByKey(key));
nres = static_cast<std::size_t>(root->index->erase(key));
//TODO: Delete from Historgram
auto ptp = getByKey(key);
// indicate the deleted tuple
const auto &dataPos = reinterpret_cast<const uint16_t &>(ptp.getNode()->block.get_ro()[gDataOffsetPos]);
const auto colSize = ColumnAttributes<toColumnType<typename std::tuple_element<0, Tuple>::type>()>::dataSize;
const auto pos = (ptp.getOffsetAt(0) - dataPos) / colSize;
ptp.getNode()->deleted.get_rw().emplace_back(pos);
//TODO: Delete from histogram?
// const auto &ptp = getByKey(key);
// const auto xtr = static_cast<uint32_t>(getBDCCFromTuple(*ptp.createTuple()).to_ulong());
// ptp.getNode()->histogram[xtr]--;
// delete from index
nres = static_cast<std::size_t>(root->index->erase(key));
// free space of PTuple
// delete_persistent<PTuple<KeyType, Tuple>>(ptp);
});
return nres;
}
......@@ -426,8 +451,8 @@ class PTable {
* \return
* the PTuple associated with the given key
***************************************************************************/
PTuple<Tuple, KeyType> getByKey(KeyType key) const {
PTuple<Tuple, KeyType> val;
PTuple<KeyType, Tuple> getByKey(KeyType key) const {
PTuple<KeyType, Tuple> val;
if (root->index->lookup(key, &val)) {
return val;
} else {
......@@ -450,6 +475,10 @@ class PTable {
return BlockIterator(*this, rangePredicates);
}
void rangeScan2(const KeyType &min, const KeyType &max, typename IndexType::ScanFunc func) const {
root->index->scan(min, max, func);
}
/************************************************************************//**
* @brief Return the number of tuples stored in the table.
*
......@@ -459,7 +488,8 @@ class PTable {
auto cnt = 0ul;
auto targetNode = root->dataNodes;
do {
cnt += reinterpret_cast<const uint32_t &>(targetNode->block.get_ro()[gCountPos]);
cnt += reinterpret_cast<const uint32_t &>(targetNode->block.get_ro()[gCountPos])
- targetNode->deleted.get_ro().size();
targetNode = targetNode->next;
} while (targetNode != nullptr);
return cnt;
......@@ -504,7 +534,7 @@ class PTable {
/* Header/General information */
std::cout << "\nBDCC Range min: " << key1 << '\n'
<< "BDCC Range max: " << key2 << '\n'
<< "Tuple count: " << cnt << '\n'
<< "Tuple count: " << cnt - currentNode->deleted.get_ro().size() << '\n'
<< "Header size: " << headerSize << " Bytes" << '\n'
<< "Body size: " << bodySize << " Bytes" << '\n'
<< "Free Space: " << space << " Bytes" << std::endl;
......@@ -512,92 +542,33 @@ class PTable {
/* Body/Column/Minipage data */
if (cnt > 0) {
size_t idx = 0;
for (const auto &c : tInfo) {
std::cout << "Column Info: " << c.getName() << ": " << c.getType() << std::endl;
const auto &smaPos = reinterpret_cast<const uint16_t &>(b[gSmaOffsetPos + idx * gAttrOffsetSize]);
const auto nCols = std::tuple_size<Tuple>::value;
auto printColumns = [&] (auto element, std::size_t idx) {
const auto colName = tInfo.columnInfo(idx).getName();
using cAttributes = ColumnAttributes<toColumnType<decltype(element)>()>;
std::cout << "Column Info: " << colName << ": " << cAttributes::typeName << std::endl;
const auto smas = cAttributes::smaPos(b, gSmaOffsetPos + idx * gAttrOffsetSize);
const auto &dataPos = reinterpret_cast<const uint16_t &>(b[gDataOffsetPos + idx * gAttrOffsetSize]);
const auto &data = reinterpret_cast<const int (&)[cnt]>(b[dataPos]);
switch (c.getType()) {
case Int_Type: {
const auto &smaMin = reinterpret_cast<const int &>(b[smaPos]);
const auto &smaMax = reinterpret_cast<const int &>(b[smaPos + sizeof(int)]);
const auto &data = reinterpret_cast<const int (&)[cnt]>(b[dataPos]);
/* Remaining Space */
auto nextSmaPos = (colCnt == idx + 1) ?
gBlockSize :
reinterpret_cast<const uint16_t &>(b[gSmaOffsetPos + (idx + 1) * gAttrOffsetSize]);
const auto freeSpaceMiniPage = nextSmaPos - dataPos - (cnt * sizeof(int));
std::cout << "Column[" << idx << "]: " << c.getName()
<< "\n\tSpace left: " << freeSpaceMiniPage << " Bytes"
<< "\n\tsmaMin: " << smaMin
<< "\n\tsmaMax: " << smaMax
<< "\n\tData: {";
// const char *padding = "";
// for (auto i = 0u; i < cnt; i++) {
// std::cout << padding << data[i];
// padding = ", ";
// }
std::cout << "}\n";
}
break;
/* Remaining Space */
const auto freeSpace = cAttributes::freeSpace(b, dataPos, cnt, nCols, idx);
case Double_Type: {
const auto &smaMin = reinterpret_cast<const double &>(b[smaPos]);
const auto &smaMax = reinterpret_cast<const double &>(b[smaPos + sizeof(double)]);
const auto &data = reinterpret_cast<const double (&)[cnt]>(b[dataPos]);
/* Remaining Space */
auto nextSmaPos = (colCnt == idx + 1) ?
gBlockSize :
reinterpret_cast<const uint16_t &>(b[gSmaOffsetPos + (idx + 1) * gAttrOffsetSize]);
const auto freeSpaceMiniPage = nextSmaPos - dataPos - (cnt * sizeof(double));
std::cout << "Column[" << idx << "]: " << c.getName()
<< "\n\tSpace left: " << freeSpaceMiniPage << " Bytes"
<< "\n\tsmaMin: " << smaMin
<< "\n\tsmaMax: " << smaMax
<< "\n\tData: {";
std::cout << "Column[" << idx << "]: " << colName
<< "\n\tSpace left: " << freeSpace << " Bytes"
<< "\n\tsmaMin: " << smas.first
<< "\n\tsmaMax: " << smas.second
<< "\n\tData: {";
// const char *padding = "";
// for (auto i = 0u; i < cnt; i++) {
// std::cout << padding << data[i];
// padding = ", ";
// }
std::cout << "}\n";
}
break;
case String_Type: {
auto &smaMinPos = (uint16_t &) b[smaPos];
auto &smaMaxPos = (uint16_t &) b[smaPos + gOffsetSize];
auto &stringPos = (uint16_t (&)[cnt]) b[dataPos];
auto smaMin(reinterpret_cast<const char (&)[]>(b[smaMinPos]));
auto smaMax(reinterpret_cast<const char (&)[]>(b[smaMaxPos]));
auto currentOffsetPos = dataPos + cnt * gOffsetSize;
auto currentOffset = reinterpret_cast<const uint16_t &>(b[currentOffsetPos - gOffsetSize]);
auto freeSpaceMiniPage = currentOffset - currentOffsetPos;