blob: f098e3aa2a25de8dbd0e90d82bed183a89e182b3 [file]
/*
* 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 "dataset/dataset_index.h"
#include <algorithm>
#include <cerrno>
#include <cstring>
#include <limits>
#include <sstream>
#ifdef _WIN32
#include <Windows.h>
#include <direct.h>
#include <fcntl.h>
#include <io.h>
#include <process.h>
#else
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#endif
namespace storage {
namespace dataset {
namespace {
const char DATASET_INDEX_MAGIC[8] = {'T', 'S', 'I', 'D', 'X', 0, 0, 0};
bool is_little_endian() {
const uint16_t value = 1;
return *reinterpret_cast<const uint8_t*>(&value) == 1;
}
uint64_t align64(uint64_t value) {
if (value > std::numeric_limits<uint64_t>::max() - 63) {
return 0;
}
return (value + 63) & ~static_cast<uint64_t>(63);
}
bool add_overflows(uint64_t left, uint64_t right) {
return left > std::numeric_limits<uint64_t>::max() - right;
}
bool multiply_overflows(uint64_t left, uint64_t right) {
return right != 0 && left > std::numeric_limits<uint64_t>::max() / right;
}
bool range_valid(uint32_t first, uint32_t count, uint32_t total) {
return first <= total && count <= total - first;
}
uint32_t expected_record_size(DatasetIndexSectionType type) {
switch (type) {
case DatasetIndexSectionType::STRING_OFFSETS:
return sizeof(uint32_t);
case DatasetIndexSectionType::STRING_BYTES:
return 0;
case DatasetIndexSectionType::TABLE_NAME_INDEX:
return sizeof(TableNameIndexRecord);
case DatasetIndexSectionType::TABLE_RECORD:
return sizeof(TableRecord);
case DatasetIndexSectionType::DEVICE_NAME_INDEX:
return sizeof(DeviceNameIndexRecord);
case DatasetIndexSectionType::DEVICE_RECORD:
return sizeof(DeviceRecord);
case DatasetIndexSectionType::COLUMN_NAME_INDEX:
return sizeof(ColumnNameIndexRecord);
case DatasetIndexSectionType::COLUMN_SCHEMA:
return sizeof(ColumnSchemaRecord);
case DatasetIndexSectionType::LOGICAL_SERIES:
return sizeof(LogicalSeriesRecord);
case DatasetIndexSectionType::TSFILE_RECORD:
return sizeof(TsFileRecord);
case DatasetIndexSectionType::DEVICE_FILE_SPAN:
return sizeof(DeviceFileSpanRecord);
case DatasetIndexSectionType::SERIES_FILE_SPAN:
return sizeof(SeriesFileSpanRecord);
case DatasetIndexSectionType::SERIES_LOCATOR:
return sizeof(SeriesLocatorRecord);
}
return std::numeric_limits<uint32_t>::max();
}
std::string parent_directory(const std::string& path) {
const std::string::size_type pos = path.find_last_of("/\\");
if (pos == std::string::npos) {
return ".";
}
if (pos == 0) {
return path.substr(0, 1);
}
return path.substr(0, pos);
}
std::string system_error_message(const char* operation) {
std::ostringstream stream;
stream << operation << " failed: " << std::strerror(errno);
return stream.str();
}
#ifdef _WIN32
typedef int NativeFile;
const NativeFile INVALID_NATIVE_FILE = -1;
NativeFile open_exclusive(const std::string& path) {
return _open(path.c_str(), _O_BINARY | _O_CREAT | _O_EXCL | _O_WRONLY,
0666);
}
int native_write(NativeFile file, const void* data, size_t size) {
const char* cursor = static_cast<const char*>(data);
while (size > 0) {
const unsigned int chunk = static_cast<unsigned int>(
std::min<size_t>(size, std::numeric_limits<int>::max()));
const int written = _write(file, cursor, chunk);
if (written <= 0) {
return -1;
}
cursor += written;
size -= static_cast<size_t>(written);
}
return 0;
}
int native_sync(NativeFile file) { return _commit(file); }
void native_close(NativeFile file) { _close(file); }
int process_id() { return _getpid(); }
#else
typedef int NativeFile;
const NativeFile INVALID_NATIVE_FILE = -1;
NativeFile open_exclusive(const std::string& path) {
return ::open(path.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0644);
}
int native_write(NativeFile file, const void* data, size_t size) {
const uint8_t* cursor = static_cast<const uint8_t*>(data);
while (size > 0) {
const ssize_t written = ::write(file, cursor, size);
if (written < 0) {
if (errno == EINTR) {
continue;
}
return -1;
}
if (written == 0) {
errno = EIO;
return -1;
}
cursor += written;
size -= static_cast<size_t>(written);
}
return 0;
}
int native_sync(NativeFile file) { return ::fsync(file); }
void native_close(NativeFile file) { ::close(file); }
int process_id() { return static_cast<int>(::getpid()); }
#endif
int write_zero_padding(NativeFile file, uint64_t length) {
static const uint8_t zeros[64] = {0};
while (length > 0) {
const size_t chunk =
static_cast<size_t>(std::min<uint64_t>(length, sizeof(zeros)));
if (native_write(file, zeros, chunk) != 0) {
return -1;
}
length -= chunk;
}
return 0;
}
bool string_equals(const MappedDatasetIndex& index, uint32_t sid,
const std::string& expected) {
DatasetIndexStringView value;
return index.string(sid, value) == DatasetIndexStatus::OK &&
value.length == expected.size() &&
(value.length == 0 ||
std::memcmp(value.data, expected.data(), value.length) == 0);
}
int compare_string_views(const DatasetIndexStringView& left,
const DatasetIndexStringView& right) {
const uint32_t common_length = std::min(left.length, right.length);
if (common_length != 0) {
const int comparison =
std::memcmp(left.data, right.data, common_length);
if (comparison != 0) {
return comparison;
}
}
return left.length < right.length ? -1 : left.length > right.length;
}
} // namespace
const char* dataset_index_status_name(DatasetIndexStatus status) {
switch (status) {
case DatasetIndexStatus::OK:
return "OK";
case DatasetIndexStatus::INVALID_ARGUMENT:
return "INVALID_ARGUMENT";
case DatasetIndexStatus::IO_ERROR:
return "IO_ERROR";
case DatasetIndexStatus::OUT_OF_MEMORY:
return "OUT_OF_MEMORY";
case DatasetIndexStatus::BAD_MAGIC:
return "BAD_MAGIC";
case DatasetIndexStatus::UNSUPPORTED_VERSION:
return "UNSUPPORTED_VERSION";
case DatasetIndexStatus::BAD_HEADER:
return "BAD_HEADER";
case DatasetIndexStatus::BAD_DIRECTORY:
return "BAD_DIRECTORY";
case DatasetIndexStatus::BAD_SECTION:
return "BAD_SECTION";
case DatasetIndexStatus::BAD_CHECKSUM:
return "BAD_CHECKSUM";
case DatasetIndexStatus::BAD_REFERENCE:
return "BAD_REFERENCE";
case DatasetIndexStatus::NOT_FOUND:
return "NOT_FOUND";
}
return "UNKNOWN";
}
uint32_t dataset_index_crc32c(const void* data, size_t length) {
const uint8_t* bytes = static_cast<const uint8_t*>(data);
uint32_t crc = ~static_cast<uint32_t>(0);
for (size_t i = 0; i < length; ++i) {
crc ^= bytes[i];
for (int bit = 0; bit < 8; ++bit) {
const uint32_t mask =
static_cast<uint32_t>(-(static_cast<int32_t>(crc & 1)));
crc = (crc >> 1) ^ (0x82F63B78U & mask);
}
}
return ~crc;
}
uint64_t dataset_index_name_hash(const char* data, size_t length) {
uint64_t hash = 1469598103934665603ULL;
for (size_t i = 0; i < length; ++i) {
hash ^= static_cast<uint8_t>(data[i]);
hash *= 1099511628211ULL;
}
return hash;
}
DatasetIndexStatus DatasetIndexWriter::write_atomic(
const std::string& output_path,
const std::vector<DatasetIndexSectionData>& input_sections,
std::string& error_message) {
error_message.clear();
if (output_path.empty() || !is_little_endian()) {
error_message = "empty output path or unsupported host byte order";
return DatasetIndexStatus::INVALID_ARGUMENT;
}
if (input_sections.size() != DATASET_INDEX_SECTION_COUNT) {
error_message = "current v1 writer requires exactly 13 sections";
return DatasetIndexStatus::INVALID_ARGUMENT;
}
std::vector<DatasetIndexSectionData> sections(input_sections);
std::sort(sections.begin(), sections.end(),
[](const DatasetIndexSectionData& left,
const DatasetIndexSectionData& right) {
return static_cast<uint32_t>(left.type) <
static_cast<uint32_t>(right.type);
});
DatasetIndexHeader header;
std::memset(&header, 0, sizeof(header));
std::memcpy(header.magic, DATASET_INDEX_MAGIC, sizeof(header.magic));
header.version_major = DATASET_INDEX_VERSION_MAJOR;
header.version_minor = DATASET_INDEX_VERSION_MINOR;
header.header_size = DATASET_INDEX_HEADER_SIZE;
header.directory_offset = DATASET_INDEX_HEADER_SIZE;
header.section_count = DATASET_INDEX_SECTION_COUNT;
header.directory_entry_size = DATASET_INDEX_DIRECTORY_ENTRY_SIZE;
std::vector<DatasetIndexDirectoryEntry> directory(
DATASET_INDEX_SECTION_COUNT);
uint64_t next_offset = align64(header.directory_offset +
static_cast<uint64_t>(directory.size()) *
sizeof(DatasetIndexDirectoryEntry));
if (next_offset == 0) {
error_message = "directory size overflows";
return DatasetIndexStatus::INVALID_ARGUMENT;
}
for (uint32_t i = 0; i < DATASET_INDEX_SECTION_COUNT; ++i) {
const uint32_t expected_type = i + 1;
const uint32_t actual_type = static_cast<uint32_t>(sections[i].type);
const uint32_t expected_size = expected_record_size(sections[i].type);
if (actual_type != expected_type ||
sections[i].record_size != expected_size) {
error_message = "section type or record size does not match v1";
return DatasetIndexStatus::INVALID_ARGUMENT;
}
if (expected_size != 0 &&
(multiply_overflows(sections[i].count, expected_size) ||
static_cast<uint64_t>(sections[i].count) * expected_size !=
sections[i].bytes.size())) {
error_message = "fixed-size section count does not match bytes";
return DatasetIndexStatus::INVALID_ARGUMENT;
}
if (expected_size == 0 &&
sections[i].count != sections[i].bytes.size()) {
error_message = "blob section count must equal byte length";
return DatasetIndexStatus::INVALID_ARGUMENT;
}
DatasetIndexDirectoryEntry& entry = directory[i];
std::memset(&entry, 0, sizeof(entry));
entry.section_type = actual_type;
entry.record_size = expected_size;
entry.offset = next_offset;
entry.length = sections[i].bytes.size();
entry.count = sections[i].count;
entry.crc32c = dataset_index_crc32c(
sections[i].bytes.empty() ? nullptr : sections[i].bytes.data(),
sections[i].bytes.size());
if (add_overflows(entry.offset, entry.length)) {
error_message = "section file range overflows";
return DatasetIndexStatus::INVALID_ARGUMENT;
}
next_offset = align64(entry.offset + entry.length);
if (next_offset == 0 && i + 1 < DATASET_INDEX_SECTION_COUNT) {
error_message = "section alignment overflows";
return DatasetIndexStatus::INVALID_ARGUMENT;
}
}
const DatasetIndexDirectoryEntry& last = directory.back();
header.file_length = last.offset + last.length;
header.header_crc32c = 0;
header.header_crc32c = dataset_index_crc32c(&header, sizeof(header));
std::ostringstream temp_name;
temp_name << output_path << ".tmp." << process_id();
const std::string temp_path = temp_name.str();
NativeFile file = open_exclusive(temp_path);
if (file == INVALID_NATIVE_FILE) {
error_message = system_error_message("create temporary index");
return DatasetIndexStatus::IO_ERROR;
}
DatasetIndexStatus status = DatasetIndexStatus::OK;
uint64_t cursor = 0;
if (native_write(file, &header, sizeof(header)) != 0 ||
native_write(file, directory.data(),
directory.size() * sizeof(directory[0])) != 0) {
error_message = system_error_message("write index header");
status = DatasetIndexStatus::IO_ERROR;
} else {
cursor = header.directory_offset +
directory.size() * sizeof(DatasetIndexDirectoryEntry);
}
for (uint32_t i = 0;
status == DatasetIndexStatus::OK && i < directory.size(); ++i) {
if (directory[i].offset < cursor ||
write_zero_padding(file, directory[i].offset - cursor) != 0 ||
native_write(
file,
sections[i].bytes.empty() ? nullptr : sections[i].bytes.data(),
sections[i].bytes.size()) != 0) {
error_message = system_error_message("write index section");
status = DatasetIndexStatus::IO_ERROR;
break;
}
cursor = directory[i].offset + directory[i].length;
}
if (status == DatasetIndexStatus::OK && native_sync(file) != 0) {
error_message = system_error_message("fsync index");
status = DatasetIndexStatus::IO_ERROR;
}
native_close(file);
if (status == DatasetIndexStatus::OK) {
#ifdef _WIN32
if (!MoveFileExA(temp_path.c_str(), output_path.c_str(),
MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) {
error_message = "replace index failed";
status = DatasetIndexStatus::IO_ERROR;
}
#else
if (::rename(temp_path.c_str(), output_path.c_str()) != 0) {
error_message = system_error_message("replace index");
status = DatasetIndexStatus::IO_ERROR;
} else {
const std::string directory_path = parent_directory(output_path);
const int directory_fd = ::open(directory_path.c_str(), O_RDONLY);
if (directory_fd >= 0) {
if (::fsync(directory_fd) != 0) {
error_message =
system_error_message("fsync index directory");
status = DatasetIndexStatus::IO_ERROR;
}
::close(directory_fd);
}
}
#endif
}
if (status != DatasetIndexStatus::OK) {
#ifdef _WIN32
_unlink(temp_path.c_str());
#else
::unlink(temp_path.c_str());
#endif
}
return status;
}
MappedDatasetIndex::MappedDatasetIndex()
: mapping_(nullptr),
mapping_size_(0),
header_(nullptr),
directory_(nullptr)
#ifdef _WIN32
,
file_handle_(INVALID_HANDLE_VALUE),
mapping_handle_(nullptr)
#else
,
fd_(-1)
#endif
{
}
MappedDatasetIndex::~MappedDatasetIndex() { close(); }
DatasetIndexStatus MappedDatasetIndex::fail(DatasetIndexStatus status,
const std::string& message) {
error_message_ = message;
return status;
}
DatasetIndexStatus MappedDatasetIndex::open(const std::string& path) {
close();
path_ = path;
if (path.empty() || !is_little_endian()) {
return fail(DatasetIndexStatus::INVALID_ARGUMENT,
"empty path or unsupported host byte order");
}
#ifdef _WIN32
HANDLE file =
CreateFileA(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (file == INVALID_HANDLE_VALUE) {
return fail(DatasetIndexStatus::IO_ERROR, "open index failed");
}
LARGE_INTEGER size;
if (!GetFileSizeEx(file, &size) || size.QuadPart <= 0) {
CloseHandle(file);
return fail(DatasetIndexStatus::IO_ERROR, "stat index failed");
}
HANDLE mapping =
CreateFileMappingA(file, nullptr, PAGE_READONLY, 0, 0, nullptr);
if (mapping == nullptr) {
CloseHandle(file);
return fail(DatasetIndexStatus::IO_ERROR, "map index failed");
}
const void* address = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0);
if (address == nullptr) {
CloseHandle(mapping);
CloseHandle(file);
return fail(DatasetIndexStatus::IO_ERROR, "map view failed");
}
file_handle_ = file;
mapping_handle_ = mapping;
mapping_ = static_cast<const uint8_t*>(address);
mapping_size_ = static_cast<uint64_t>(size.QuadPart);
#else
fd_ = ::open(path.c_str(), O_RDONLY);
if (fd_ < 0) {
return fail(DatasetIndexStatus::IO_ERROR,
system_error_message("open index"));
}
struct stat stat_buffer;
if (::fstat(fd_, &stat_buffer) != 0 || stat_buffer.st_size <= 0) {
close();
return fail(DatasetIndexStatus::IO_ERROR,
system_error_message("stat index"));
}
mapping_size_ = static_cast<uint64_t>(stat_buffer.st_size);
void* address =
::mmap(nullptr, mapping_size_, PROT_READ, MAP_SHARED, fd_, 0);
if (address == MAP_FAILED) {
mapping_ = nullptr;
close();
return fail(DatasetIndexStatus::IO_ERROR,
system_error_message("mmap index"));
}
mapping_ = static_cast<const uint8_t*>(address);
#endif
DatasetIndexStatus status = validate();
if (status != DatasetIndexStatus::OK) {
const std::string message = error_message_;
close();
error_message_ = message;
}
return status;
}
void MappedDatasetIndex::close() {
#ifdef _WIN32
if (mapping_ != nullptr) {
UnmapViewOfFile(mapping_);
}
if (mapping_handle_ != nullptr) {
CloseHandle(static_cast<HANDLE>(mapping_handle_));
}
if (file_handle_ != INVALID_HANDLE_VALUE) {
CloseHandle(static_cast<HANDLE>(file_handle_));
}
file_handle_ = INVALID_HANDLE_VALUE;
mapping_handle_ = nullptr;
#else
if (mapping_ != nullptr) {
::munmap(const_cast<uint8_t*>(mapping_), mapping_size_);
}
if (fd_ >= 0) {
::close(fd_);
}
fd_ = -1;
#endif
mapping_ = nullptr;
mapping_size_ = 0;
header_ = nullptr;
directory_ = nullptr;
path_.clear();
}
DatasetIndexStatus MappedDatasetIndex::section(
DatasetIndexSectionType type, DatasetIndexSectionView& result) const {
result = DatasetIndexSectionView();
if (directory_ == nullptr) {
return DatasetIndexStatus::BAD_HEADER;
}
const uint32_t raw_type = static_cast<uint32_t>(type);
if (raw_type == 0 || raw_type > header_->section_count) {
return DatasetIndexStatus::NOT_FOUND;
}
const DatasetIndexDirectoryEntry& entry = directory_[raw_type - 1];
if (entry.section_type != raw_type) {
return DatasetIndexStatus::NOT_FOUND;
}
result.data = mapping_ + entry.offset;
result.length = entry.length;
result.record_size = entry.record_size;
result.count = entry.count;
return DatasetIndexStatus::OK;
}
DatasetIndexStatus MappedDatasetIndex::string(
uint32_t sid, DatasetIndexStringView& result) const {
result = DatasetIndexStringView();
DatasetIndexSectionView offsets;
DatasetIndexSectionView bytes;
if (section(DatasetIndexSectionType::STRING_OFFSETS, offsets) !=
DatasetIndexStatus::OK ||
section(DatasetIndexSectionType::STRING_BYTES, bytes) !=
DatasetIndexStatus::OK ||
offsets.count == 0 || sid + 1 >= offsets.count) {
return DatasetIndexStatus::NOT_FOUND;
}
const uint32_t* values = reinterpret_cast<const uint32_t*>(offsets.data);
result.data = reinterpret_cast<const char*>(bytes.data + values[sid]);
result.length = values[sid + 1] - values[sid];
return DatasetIndexStatus::OK;
}
DatasetIndexStatus MappedDatasetIndex::validate() {
if (mapping_size_ < sizeof(DatasetIndexHeader)) {
return fail(DatasetIndexStatus::BAD_HEADER,
"index is shorter than header");
}
header_ = reinterpret_cast<const DatasetIndexHeader*>(mapping_);
if (std::memcmp(header_->magic, DATASET_INDEX_MAGIC,
sizeof(header_->magic)) != 0) {
return fail(DatasetIndexStatus::BAD_MAGIC, "bad dataset index magic");
}
if (header_->version_major != DATASET_INDEX_VERSION_MAJOR ||
header_->version_minor != DATASET_INDEX_VERSION_MINOR) {
return fail(DatasetIndexStatus::UNSUPPORTED_VERSION,
"unsupported dataset index version");
}
if (header_->header_size != sizeof(DatasetIndexHeader) ||
header_->directory_entry_size != sizeof(DatasetIndexDirectoryEntry) ||
header_->section_count != DATASET_INDEX_SECTION_COUNT ||
header_->directory_offset < sizeof(DatasetIndexHeader) ||
header_->file_length != mapping_size_) {
return fail(DatasetIndexStatus::BAD_HEADER,
"header size, directory shape, or file length is invalid");
}
for (size_t i = 0; i < sizeof(header_->reserved); ++i) {
if (header_->reserved[i] != 0) {
return fail(DatasetIndexStatus::BAD_HEADER,
"header reserved bytes are not zero");
}
}
DatasetIndexHeader header_copy = *header_;
const uint32_t expected_header_crc = header_copy.header_crc32c;
header_copy.header_crc32c = 0;
if (dataset_index_crc32c(&header_copy, sizeof(header_copy)) !=
expected_header_crc) {
return fail(DatasetIndexStatus::BAD_CHECKSUM,
"header checksum does not match");
}
const uint64_t directory_length =
static_cast<uint64_t>(header_->section_count) *
header_->directory_entry_size;
if (add_overflows(header_->directory_offset, directory_length) ||
header_->directory_offset + directory_length > mapping_size_) {
return fail(DatasetIndexStatus::BAD_DIRECTORY,
"section directory is outside the file");
}
directory_ = reinterpret_cast<const DatasetIndexDirectoryEntry*>(
mapping_ + header_->directory_offset);
uint64_t minimum_section_offset =
align64(header_->directory_offset + directory_length);
uint64_t previous_end = minimum_section_offset;
for (uint32_t i = 0; i < header_->section_count; ++i) {
const DatasetIndexDirectoryEntry& entry = directory_[i];
const DatasetIndexSectionType type =
static_cast<DatasetIndexSectionType>(i + 1);
if (entry.section_type != i + 1 ||
entry.record_size != expected_record_size(type)) {
return fail(DatasetIndexStatus::BAD_DIRECTORY,
"section type order or record size is invalid");
}
if (entry.offset % DATASET_INDEX_ALIGNMENT != 0 ||
entry.offset < minimum_section_offset ||
entry.offset < previous_end ||
add_overflows(entry.offset, entry.length) ||
entry.offset + entry.length > mapping_size_) {
return fail(
DatasetIndexStatus::BAD_SECTION,
"section range is unaligned, overlapping, or out of bounds");
}
if (entry.record_size == 0) {
if (entry.count != entry.length) {
return fail(DatasetIndexStatus::BAD_SECTION,
"blob count does not equal byte length");
}
} else if (multiply_overflows(entry.count, entry.record_size) ||
static_cast<uint64_t>(entry.count) * entry.record_size >
entry.length) {
return fail(DatasetIndexStatus::BAD_SECTION,
"section record count exceeds section length");
}
const uint32_t actual_crc = dataset_index_crc32c(
entry.length == 0 ? nullptr : mapping_ + entry.offset,
static_cast<size_t>(entry.length));
if (actual_crc != entry.crc32c) {
return fail(DatasetIndexStatus::BAD_CHECKSUM,
"section checksum does not match");
}
previous_end = entry.offset + entry.length;
}
DatasetIndexSectionView string_offsets_view;
DatasetIndexSectionView string_bytes_view;
section(DatasetIndexSectionType::STRING_OFFSETS, string_offsets_view);
section(DatasetIndexSectionType::STRING_BYTES, string_bytes_view);
if (string_offsets_view.count == 0) {
return fail(DatasetIndexStatus::BAD_REFERENCE,
"StringOffsets must contain the terminal offset");
}
const uint32_t* string_offsets =
reinterpret_cast<const uint32_t*>(string_offsets_view.data);
if (string_offsets[0] != 0) {
return fail(DatasetIndexStatus::BAD_REFERENCE,
"StringOffsets must start at zero");
}
for (uint32_t i = 1; i < string_offsets_view.count; ++i) {
if (string_offsets[i] < string_offsets[i - 1] ||
string_offsets[i] > string_bytes_view.length) {
return fail(DatasetIndexStatus::BAD_REFERENCE,
"StringOffsets is not monotonic or is out of bounds");
}
}
if (string_offsets[string_offsets_view.count - 1] !=
string_bytes_view.length) {
return fail(DatasetIndexStatus::BAD_REFERENCE,
"terminal string offset does not equal StringBytes length");
}
const uint32_t string_count = string_offsets_view.count - 1;
DatasetIndexSectionView table_names;
DatasetIndexSectionView tables;
DatasetIndexSectionView device_names;
DatasetIndexSectionView devices;
DatasetIndexSectionView column_names;
DatasetIndexSectionView columns;
DatasetIndexSectionView series;
DatasetIndexSectionView files;
DatasetIndexSectionView device_spans;
DatasetIndexSectionView series_spans;
DatasetIndexSectionView locators;
section(DatasetIndexSectionType::TABLE_NAME_INDEX, table_names);
section(DatasetIndexSectionType::TABLE_RECORD, tables);
section(DatasetIndexSectionType::DEVICE_NAME_INDEX, device_names);
section(DatasetIndexSectionType::DEVICE_RECORD, devices);
section(DatasetIndexSectionType::COLUMN_NAME_INDEX, column_names);
section(DatasetIndexSectionType::COLUMN_SCHEMA, columns);
section(DatasetIndexSectionType::LOGICAL_SERIES, series);
section(DatasetIndexSectionType::TSFILE_RECORD, files);
section(DatasetIndexSectionType::DEVICE_FILE_SPAN, device_spans);
section(DatasetIndexSectionType::SERIES_FILE_SPAN, series_spans);
section(DatasetIndexSectionType::SERIES_LOCATOR, locators);
#define DATASET_RECORDS(view, type) reinterpret_cast<const type*>((view).data)
const TableNameIndexRecord* table_name_records =
DATASET_RECORDS(table_names, TableNameIndexRecord);
const TableRecord* table_records = DATASET_RECORDS(tables, TableRecord);
const DeviceNameIndexRecord* device_name_records =
DATASET_RECORDS(device_names, DeviceNameIndexRecord);
const DeviceRecord* device_records = DATASET_RECORDS(devices, DeviceRecord);
const ColumnNameIndexRecord* column_name_records =
DATASET_RECORDS(column_names, ColumnNameIndexRecord);
const ColumnSchemaRecord* column_records =
DATASET_RECORDS(columns, ColumnSchemaRecord);
const LogicalSeriesRecord* series_records =
DATASET_RECORDS(series, LogicalSeriesRecord);
const TsFileRecord* file_records = DATASET_RECORDS(files, TsFileRecord);
const DeviceFileSpanRecord* device_span_records =
DATASET_RECORDS(device_spans, DeviceFileSpanRecord);
const SeriesFileSpanRecord* series_span_records =
DATASET_RECORDS(series_spans, SeriesFileSpanRecord);
const SeriesLocatorRecord* locator_records =
DATASET_RECORDS(locators, SeriesLocatorRecord);
#undef DATASET_RECORDS
if (table_names.count != tables.count) {
return fail(DatasetIndexStatus::BAD_REFERENCE,
"TableNameIndex must contain one entry per TableRecord");
}
std::vector<uint8_t> seen_table_ids(tables.count, 0);
DatasetIndexStringView previous_table_name;
uint64_t previous_table_hash = 0;
bool have_previous_table_name = false;
for (uint32_t i = 0; i < table_names.count; ++i) {
const TableNameIndexRecord& value = table_name_records[i];
if (value.name_sid >= string_count || value.table_id >= tables.count ||
seen_table_ids[value.table_id] != 0 ||
table_records[value.table_id].name_sid != value.name_sid) {
return fail(
DatasetIndexStatus::BAD_REFERENCE,
"TableNameIndex contains an invalid or duplicate reference");
}
DatasetIndexStringView table_name;
if (string(value.name_sid, table_name) != DatasetIndexStatus::OK ||
dataset_index_name_hash(table_name.data, table_name.length) !=
value.name_hash) {
return fail(DatasetIndexStatus::BAD_REFERENCE,
"TableNameIndex name hash does not match its string");
}
if (have_previous_table_name &&
(value.name_hash < previous_table_hash ||
(value.name_hash == previous_table_hash &&
compare_string_views(table_name, previous_table_name) <= 0))) {
return fail(
DatasetIndexStatus::BAD_REFERENCE,
"TableNameIndex is unsorted or has duplicate table names");
}
seen_table_ids[value.table_id] = 1;
previous_table_name = table_name;
previous_table_hash = value.name_hash;
have_previous_table_name = true;
}
for (uint32_t i = 0; i < tables.count; ++i) {
const TableRecord& value = table_records[i];
if (value.name_sid >= string_count || value.reserved0 != 0 ||
value.reserved1 != 0 ||
!range_valid(value.first_device_name_index, value.device_count,
device_names.count) ||
!range_valid(value.first_column_name_index, value.column_count,
column_names.count)) {
return fail(
DatasetIndexStatus::BAD_REFERENCE,
"TableRecord contains an invalid range or reserved value");
}
}
for (uint32_t i = 0; i < device_names.count; ++i) {
const DeviceNameIndexRecord& value = device_name_records[i];
if (value.table_id >= tables.count ||
value.device_id >= devices.count ||
value.name_sid >= string_count || value.reserved != 0) {
return fail(DatasetIndexStatus::BAD_REFERENCE,
"DeviceNameIndex contains an invalid reference");
}
}
for (uint32_t i = 0; i < devices.count; ++i) {
const DeviceRecord& value = device_records[i];
if (value.table_id >= tables.count || value.name_sid >= string_count ||
value.reserved0 != 0 || value.reserved1 != 0 ||
!range_valid(value.first_series_id, value.series_count,
series.count) ||
!range_valid(value.first_file_span, value.file_span_count,
device_spans.count) ||
(value.series_count != 0 && value.min_time > value.max_time)) {
return fail(DatasetIndexStatus::BAD_REFERENCE,
"DeviceRecord contains an invalid reference or range");
}
}
for (uint32_t i = 0; i < column_names.count; ++i) {
const ColumnNameIndexRecord& value = column_name_records[i];
if (value.table_id >= tables.count ||
value.column_id >= columns.count ||
value.name_sid >= string_count || value.reserved != 0) {
return fail(DatasetIndexStatus::BAD_REFERENCE,
"ColumnNameIndex contains an invalid reference");
}
}
for (uint32_t i = 0; i < columns.count; ++i) {
const ColumnSchemaRecord& value = column_records[i];
if (value.table_id >= tables.count || value.name_sid >= string_count ||
value.nullable > 1 || value.reserved != 0) {
return fail(DatasetIndexStatus::BAD_REFERENCE,
"ColumnSchema contains an invalid reference or enum");
}
}
for (uint32_t i = 0; i < series.count; ++i) {
const LogicalSeriesRecord& value = series_records[i];
if (value.device_id >= devices.count ||
value.column_id >= columns.count ||
!range_valid(value.first_file_span, value.file_span_count,
series_spans.count) ||
(value.file_span_count != 0 && value.min_time > value.max_time)) {
return fail(DatasetIndexStatus::BAD_REFERENCE,
"LogicalSeries contains an invalid reference or range");
}
}
for (uint32_t i = 0; i < files.count; ++i) {
const TsFileRecord& value = file_records[i];
if (value.path_sid >= string_count || value.reserved0 != 0 ||
value.reserved1 != 0) {
return fail(DatasetIndexStatus::BAD_REFERENCE,
"TsFileRecord contains an invalid reference");
}
}
for (uint32_t i = 0; i < device_spans.count; ++i) {
const DeviceFileSpanRecord& value = device_span_records[i];
if (value.device_id >= devices.count || value.file_id >= files.count ||
value.layout > 1 ||
(value.flags & ~static_cast<uint16_t>(1)) != 0 ||
(value.layout == 0 &&
(value.time_meta_offset != 0 || value.time_meta_length != 0 ||
value.row_count != 0)) ||
(value.layout == 1 &&
(value.time_meta_length == 0 || value.row_count == 0))) {
return fail(
DatasetIndexStatus::BAD_REFERENCE,
"DeviceFileSpan contains an invalid reference or layout");
}
if (value.layout == 1 &&
(add_overflows(value.time_meta_offset, value.time_meta_length) ||
value.time_meta_offset + value.time_meta_length >
file_records[value.file_id].file_size)) {
return fail(DatasetIndexStatus::BAD_REFERENCE,
"aligned time metadata range is outside its TsFile");
}
}
for (uint32_t i = 0; i < series_spans.count; ++i) {
const SeriesFileSpanRecord& value = series_span_records[i];
if (value.series_id >= series.count || value.file_id >= files.count ||
value.locator_id >= locators.count || value.reserved != 0 ||
value.min_time > value.max_time) {
return fail(
DatasetIndexStatus::BAD_REFERENCE,
"SeriesFileSpan contains an invalid reference or range");
}
}
for (uint32_t i = 0; i < locators.count; ++i) {
const SeriesLocatorRecord& value = locator_records[i];
if (value.device_file_span_id >= device_spans.count ||
value.locator_kind > 1 || value.flags != 0 || value.padding != 0 ||
value.timeseries_meta_length == 0) {
return fail(DatasetIndexStatus::BAD_REFERENCE,
"SeriesLocator contains an invalid reference or enum");
}
const DeviceFileSpanRecord& device_span =
device_span_records[value.device_file_span_id];
if (value.locator_kind != device_span.layout ||
add_overflows(value.timeseries_meta_offset,
value.timeseries_meta_length) ||
value.timeseries_meta_offset + value.timeseries_meta_length >
file_records[device_span.file_id].file_size) {
return fail(DatasetIndexStatus::BAD_REFERENCE,
"SeriesLocator metadata range is outside its TsFile");
}
}
error_message_.clear();
return DatasetIndexStatus::OK;
}
DatasetIndexStatus MappedDatasetIndex::find_table_ids(
const std::string& name, std::vector<uint32_t>& table_ids) const {
table_ids.clear();
DatasetIndexSectionView view;
if (section(DatasetIndexSectionType::TABLE_NAME_INDEX, view) !=
DatasetIndexStatus::OK) {
return DatasetIndexStatus::BAD_SECTION;
}
const TableNameIndexRecord* records =
reinterpret_cast<const TableNameIndexRecord*>(view.data);
const uint64_t hash = dataset_index_name_hash(name.data(), name.size());
uint32_t low = 0;
uint32_t high = view.count;
while (low < high) {
const uint32_t middle = low + (high - low) / 2;
if (records[middle].name_hash < hash) {
low = middle + 1;
} else {
high = middle;
}
}
for (uint32_t i = low; i < view.count && records[i].name_hash == hash;
++i) {
if (string_equals(*this, records[i].name_sid, name)) {
table_ids.push_back(records[i].table_id);
}
}
return table_ids.empty() ? DatasetIndexStatus::NOT_FOUND
: DatasetIndexStatus::OK;
}
DatasetIndexStatus MappedDatasetIndex::find_device_id(
uint32_t table_id, const std::string& name, uint32_t& device_id) const {
const TableRecord* table = nullptr;
if (record(DatasetIndexSectionType::TABLE_RECORD, table_id, table) !=
DatasetIndexStatus::OK) {
return DatasetIndexStatus::NOT_FOUND;
}
DatasetIndexSectionView view;
section(DatasetIndexSectionType::DEVICE_NAME_INDEX, view);
const DeviceNameIndexRecord* records =
reinterpret_cast<const DeviceNameIndexRecord*>(view.data);
const uint64_t hash = dataset_index_name_hash(name.data(), name.size());
uint32_t low = table->first_device_name_index;
uint32_t high = low + table->device_count;
while (low < high) {
const uint32_t middle = low + (high - low) / 2;
if (records[middle].name_hash < hash) {
low = middle + 1;
} else {
high = middle;
}
}
const uint32_t end = table->first_device_name_index + table->device_count;
for (uint32_t i = low; i < end && records[i].name_hash == hash; ++i) {
if (records[i].table_id == table_id &&
string_equals(*this, records[i].name_sid, name)) {
device_id = records[i].device_id;
return DatasetIndexStatus::OK;
}
}
return DatasetIndexStatus::NOT_FOUND;
}
DatasetIndexStatus MappedDatasetIndex::find_column_id(
uint32_t table_id, const std::string& name, uint32_t& column_id) const {
const TableRecord* table = nullptr;
if (record(DatasetIndexSectionType::TABLE_RECORD, table_id, table) !=
DatasetIndexStatus::OK) {
return DatasetIndexStatus::NOT_FOUND;
}
DatasetIndexSectionView view;
section(DatasetIndexSectionType::COLUMN_NAME_INDEX, view);
const ColumnNameIndexRecord* records =
reinterpret_cast<const ColumnNameIndexRecord*>(view.data);
const uint64_t hash = dataset_index_name_hash(name.data(), name.size());
uint32_t low = table->first_column_name_index;
uint32_t high = low + table->column_count;
while (low < high) {
const uint32_t middle = low + (high - low) / 2;
if (records[middle].name_hash < hash) {
low = middle + 1;
} else {
high = middle;
}
}
const uint32_t end = table->first_column_name_index + table->column_count;
for (uint32_t i = low; i < end && records[i].name_hash == hash; ++i) {
if (records[i].table_id == table_id &&
string_equals(*this, records[i].name_sid, name)) {
column_id = records[i].column_id;
return DatasetIndexStatus::OK;
}
}
return DatasetIndexStatus::NOT_FOUND;
}
DatasetIndexStatus MappedDatasetIndex::find_series_id(
uint32_t device_id, uint32_t column_id, uint32_t& series_id) const {
const DeviceRecord* device = nullptr;
if (record(DatasetIndexSectionType::DEVICE_RECORD, device_id, device) !=
DatasetIndexStatus::OK) {
return DatasetIndexStatus::NOT_FOUND;
}
DatasetIndexSectionView view;
section(DatasetIndexSectionType::LOGICAL_SERIES, view);
const LogicalSeriesRecord* records =
reinterpret_cast<const LogicalSeriesRecord*>(view.data);
uint32_t low = device->first_series_id;
uint32_t high = low + device->series_count;
while (low < high) {
const uint32_t middle = low + (high - low) / 2;
if (records[middle].column_id < column_id) {
low = middle + 1;
} else {
high = middle;
}
}
if (low < device->first_series_id + device->series_count &&
records[low].device_id == device_id &&
records[low].column_id == column_id) {
series_id = low;
return DatasetIndexStatus::OK;
}
return DatasetIndexStatus::NOT_FOUND;
}
} // namespace dataset
} // namespace storage