Skip to content
13 changes: 12 additions & 1 deletion cpp/src/common/allocator/byte_stream.h
Original file line number Diff line number Diff line change
Expand Up @@ -696,7 +696,18 @@ class ByteStream {
if (UNLIKELY(read_page_ == nullptr)) {
read_page_ = head_.load();
} else if (UNLIKELY((read_pos_ & page_mask_) == 0)) {
read_page_ = read_page_->next_.load();
// At a page boundary the cursor may have been parked here by a
// preceding sequential read (read_page_ is the page just
// finished, advance one) or by set_read_pos() (read_page_ is
// already the boundary page, advancing would skip it). The
// two states are indistinguishable, so recompute the page
// from the head instead of blindly following next_.
Page* p = head_.load();
uint64_t page_idx = read_pos_ / page_size_;
while (p != nullptr && page_idx-- > 0) {
p = p->next_.load();
}
read_page_ = p;
}
if (UNLIKELY(read_page_ == nullptr)) {
return common::E_OUT_OF_RANGE;
Expand Down
293 changes: 247 additions & 46 deletions cpp/src/encoding/ts2diff_decoder.h

Large diffs are not rendered by default.

34 changes: 28 additions & 6 deletions cpp/src/encoding/ts2diff_encoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,7 @@ class FloatTS2DIFFEncoder : public TS2DIFFEncoder<int32_t> {
void reset() override {
TS2DIFFEncoder<int32_t>::reset();
underflow_flags_.clear();
max_point_number_saved_ = false;
}
int flush(common::ByteStream& out_stream) override;
int encode(bool value, common::ByteStream& out_stream);
Expand Down Expand Up @@ -609,6 +610,11 @@ class FloatTS2DIFFEncoder : public TS2DIFFEncoder<int32_t> {
int max_point_number_;
double max_point_value_;
std::vector<int8_t> underflow_flags_;
// Java FloatDecoder reads maxPointNumber once per page; this flag
// makes sure only the first 128-value segment of a page carries the
// prefix. PageWriter/ValuePageWriter reset() between pages clears it,
// so every page starts with a fresh prefix (apache/tsfile#910).
bool max_point_number_saved_{false};
};

class DoubleTS2DIFFEncoder : public TS2DIFFEncoder<int64_t> {
Expand All @@ -623,6 +629,7 @@ class DoubleTS2DIFFEncoder : public TS2DIFFEncoder<int64_t> {
void reset() override {
TS2DIFFEncoder<int64_t>::reset();
underflow_flags_.clear();
max_point_number_saved_ = false;
}
int flush(common::ByteStream& out_stream) override;
int encode(bool value, common::ByteStream& out_stream);
Expand Down Expand Up @@ -667,6 +674,11 @@ class DoubleTS2DIFFEncoder : public TS2DIFFEncoder<int64_t> {
int max_point_number_;
double max_point_value_;
std::vector<int8_t> underflow_flags_;
// Java FloatDecoder reads maxPointNumber once per page; this flag
// makes sure only the first 128-value segment of a page carries the
// prefix. PageWriter/ValuePageWriter reset() between pages clears it,
// so every page starts with a fresh prefix (apache/tsfile#910).
bool max_point_number_saved_{false};
};

typedef TS2DIFFEncoder<int32_t> IntTS2DIFFEncoder;
Expand Down Expand Up @@ -784,9 +796,14 @@ FORCE_INLINE int FloatTS2DIFFEncoder::flush(common::ByteStream& out_stream) {
}
const int num_values = write_index_ + 1;
common::ByteStream inner(1024, common::MOD_TS2DIFF_OBJ, false);
if (RET_FAIL(common::SerializationUtil::write_var_uint(
static_cast<uint32_t>(max_point_number_), inner))) {
return ret;
// Java FloatDecoder reads maxPointNumber only once per page; emit it
// just for the page's first segment (apache/tsfile#910).
if (!max_point_number_saved_) {
if (RET_FAIL(common::SerializationUtil::write_var_uint(
static_cast<uint32_t>(max_point_number_), inner))) {
return ret;
}
max_point_number_saved_ = true;
}
SIMDOps<int32_t>::rebase(delta_arr_, delta_arr_min_, write_index_);
int bit_width = cal_bit_width(delta_arr_max_ - delta_arr_min_);
Expand Down Expand Up @@ -871,9 +888,14 @@ FORCE_INLINE int DoubleTS2DIFFEncoder::flush(common::ByteStream& out_stream) {
}
const int num_values = write_index_ + 1;
common::ByteStream inner(1024, common::MOD_TS2DIFF_OBJ, false);
if (RET_FAIL(common::SerializationUtil::write_var_uint(
static_cast<uint32_t>(max_point_number_), inner))) {
return ret;
// Java FloatDecoder reads maxPointNumber only once per page; emit it
// just for the page's first segment (apache/tsfile#910).
if (!max_point_number_saved_) {
if (RET_FAIL(common::SerializationUtil::write_var_uint(
static_cast<uint32_t>(max_point_number_), inner))) {
return ret;
}
max_point_number_saved_ = true;
}
SIMDOps<int64_t>::rebase(delta_arr_, delta_arr_min_, write_index_);
int bit_width = cal_bit_width(delta_arr_max_ - delta_arr_min_);
Expand Down
3 changes: 2 additions & 1 deletion cpp/src/file/read_file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ ssize_t pread(int fd, void* buf, size_t count, uint64_t offset);

#include "common/logger/elog.h"
#include "common/tsfile_common.h"
#include "file/utf8_file_open.h"
#include "utils/util_define.h" // ssize_t and other platform-compat shims

using namespace common;
Expand All @@ -54,7 +55,7 @@ int ReadFile::open(const std::string& file_path) {
#ifdef _WIN32
flags |= O_BINARY;
#endif
fd_ = ::open(file_path_.c_str(), flags);
fd_ = file_internal::open_utf8(file_path_, flags);
if (fd_ < 0) {
std::cerr << "open file " << file_path << " error: " << strerror(errno)
<< " (errno " << errno << ")" << std::endl;
Expand Down
3 changes: 2 additions & 1 deletion cpp/src/file/restorable_tsfile_io_writer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ ssize_t pread(int fd, void* buf, size_t count, uint64_t offset);
#include <unistd.h>
#endif

#include "file/utf8_file_open.h"
using namespace common;

namespace storage {
Expand Down Expand Up @@ -96,7 +97,7 @@ struct SelfCheckReader {
#ifdef _WIN32
fd_ = ::_open(path.c_str(), _O_RDONLY | _O_BINARY);
#else
fd_ = ::open(path.c_str(), O_RDONLY);
fd_ = file_internal::open_utf8(path, O_RDONLY);
#endif
if (fd_ < 0) {
return E_FILE_OPEN_ERR;
Expand Down
4 changes: 4 additions & 0 deletions cpp/src/file/tsfile_io_reader.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ class TsFileIOReader {

std::string get_file_path() const { return read_file_->file_path(); }

// Raw read access for callers that need file bytes (e.g. parsing chunk
// headers at offsets from chunk metadata).
ReadFile* get_read_file() const { return read_file_; }

TsFileMeta* get_tsfile_meta() {
load_tsfile_meta_if_necessary();
return &tsfile_meta_;
Expand Down
69 changes: 69 additions & 0 deletions cpp/src/file/utf8_file_open.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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.
*/

#pragma once

#include <cerrno>
#include <climits>
#include <fcntl.h>
#include <string>

#ifdef _WIN32
#include <io.h>
#include <windows.h>
#else
#include <unistd.h>
#endif

namespace storage {
namespace file_internal {

inline int open_utf8(const std::string& path, int flags, int mode = 0) {
#ifdef _WIN32
if (path.find('\0') != std::string::npos || path.size() > INT_MAX) {
errno = EINVAL;
return -1;
}
if (path.empty()) {
errno = ENOENT;
return -1;
}
const int size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
path.data(),
static_cast<int>(path.size()), nullptr,
0);
if (size <= 0) {
errno = EINVAL;
return -1;
}
std::wstring wide_path(static_cast<size_t>(size), L'\0');
if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, path.data(),
static_cast<int>(path.size()), &wide_path[0],
size) != size) {
errno = EINVAL;
return -1;
}
return ::_wopen(wide_path.c_str(), flags, mode);
#else
return ::open(path.c_str(), flags, mode);
#endif
}

} // namespace file_internal
} // namespace storage
3 changes: 2 additions & 1 deletion cpp/src/file/write_file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/

#include "write_file.h"
#include "file/utf8_file_open.h"

#include <errno.h>
#include <fcntl.h>
Expand Down Expand Up @@ -59,7 +60,7 @@ int WriteFile::do_create(int flags, mode_t mode) {
flags |= O_BINARY;
#endif
// TODO make sure no same file exists
fd_ = ::open(path_.c_str(), flags, mode);
fd_ = file_internal::open_utf8(path_, flags, mode);
if (fd_ < 0) {
// log_err("open file error, path=%s, errno=%d", path_.c_str(), errno);
ret = E_FILE_OPEN_ERR;
Expand Down
54 changes: 51 additions & 3 deletions cpp/src/reader/tsfile_reader.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@

#include <stdexcept>

#include "file/read_file.h"
#include "common/tsfile_common.h"
#include "common/allocator/byte_stream.h"
#include "common/schema.h"
#include "filter/time_operator.h"
#include "tsfile_executor.h"
Expand Down Expand Up @@ -438,9 +441,54 @@ int TsFileReader::get_timeseries_schema(
dt = aligned->value_ts_idx_->get_data_type();
}
}
MeasurementSchema ms(
timeseries_index->get_measurement_name().to_std_string(), dt);
result.push_back(ms);
// Report the encoding/compression the file actually stores,
// not library defaults. The 2-arg MeasurementSchema ctor fills
// get_value_encoder(dt) / get_default_compressor(), which
// mislabels e.g. a TS_2DIFF UNCOMPRESSED column as GORILLA/LZ4.
// ChunkMeta entries deserialized by the metadata index carry
// only offsets (their encoding_ fields are uninitialized), so
// read the first chunk's header bytes from the file at that
// offset — the ChunkHeader serialization carries the real
// encoding/compression.
common::TSEncoding enc = common::INVALID_ENCODING;
common::CompressionType comp = common::INVALID_COMPRESSION;
auto* chunk_meta_list = timeseries_index->get_chunk_meta_list();
if (chunk_meta_list != nullptr && chunk_meta_list->size() > 0 &&
chunk_meta_list->front() != nullptr) {
const int64_t chunk_header_offset =
chunk_meta_list->front()->offset_of_chunk_header_;
ReadFile* rf = tsfile_executor_->get_tsfile_io_reader()
->get_read_file();
if (rf != nullptr && chunk_header_offset >= 0) {
char buf[256];
int32_t read_len = 0;
if (rf->read(chunk_header_offset, buf, sizeof(buf),
read_len) == E_OK &&
read_len > 0) {
common::ByteStream in(
read_len, common::MOD_TSFILE_READER, false);
in.wrap_from(buf, read_len);
ChunkHeader ch;
if (ch.deserialize_from(in) == E_OK) {
enc = ch.encoding_type_;
comp = ch.compression_type_;
}
}
}
}
if (enc == common::INVALID_ENCODING ||
comp == common::INVALID_COMPRESSION) {
// No chunk metadata available: fall back to defaults.
MeasurementSchema ms(
timeseries_index->get_measurement_name().to_std_string(),
dt);
result.push_back(ms);
} else {
MeasurementSchema ms(
timeseries_index->get_measurement_name().to_std_string(),
dt, enc, comp);
result.push_back(ms);
}
}
}
return E_OK;
Expand Down
Loading