Skip to content

Add C++ and Python TsFile properties support - #897

Open
ColinLeeo wants to merge 3 commits into
apache:developfrom
ColinLeeo:feature/cpp-python-tsfile-properties
Open

Add C++ and Python TsFile properties support#897
ColinLeeo wants to merge 3 commits into
apache:developfrom
ColinLeeo:feature/cpp-python-tsfile-properties

Conversation

@ColinLeeo

Copy link
Copy Markdown
Contributor

Summary

Add file-level TsFile properties read/write support to the C++ and Python APIs.

Properties can be added or replaced while a writer is open, including after flush(). Property values are stored as binary data and remain compatible with Java's Map<String, byte[]> representation.

Changes

C++

  • Add TsFilePropertyValue and TsFileProperties.
  • Add property setters to TsFileWriter and TsFileTableWriter.
  • Add TsFileReader::get_tsfile_properties().
  • Preserve the distinction between:
    • null values
    • non-null zero-length values
    • arbitrary binary values
  • Support embedded NUL and non-text bytes without truncation.
  • Copy property values immediately and replace previous values when the same key is used.
  • Allow properties to be changed after flush().
  • Reject modifications after the writer is closed.
  • Ensure system-generated encryptLevel, encryptType, and encryptKey properties override custom properties with the same names.
  • Replace the raw-pointer property map in TsFileMeta with value-based ownership.

C wrapper

  • Add length-aware binary property setter APIs.
  • Add a reader API returning explicit key/value lengths and an is_null field.
  • Add tsfile_free_tsfile_properties() for releasing reader results.
  • Add argument validation, size limits, allocation failure handling, and partial-allocation cleanup.

Python

  • Add add_tsfile_property(key: str, value: bytes) to:
    • TsFileWriter
    • TsFileTableWriter
  • Add TsFileReader.get_tsfile_properties().
  • Return properties as dict[str, bytes | None].
  • Accept exact bytes values only; strings and other bytes-like objects are not converted implicitly.
  • Keep TsFileTreeWriter unchanged.

Compatibility

The existing TsFile footer format is unchanged:

  • null value: length -1
  • non-null value: byte length followed by the original bytes

No property type tag is added. Applications are responsible for using an explicit, portable byte encoding for integers, floating-point values, and structures.

This change does not support modifying properties in an already closed TsFile.

Tests

Added coverage for:

  • binary values and embedded NUL bytes
  • null versus empty values
  • duplicate-key replacement
  • property updates after flush()
  • closed-writer rejection
  • system property precedence
  • C wrapper lengths, validation, and result cleanup
  • both Python writer APIs
  • Python type validation and bytes | None reader results

Validation completed:

  • ./mvnw spotless:check -P with-cpp
  • ./mvnw -P with-cpp clean verify
    • 738 C++ tests passed
  • ./mvnw -P with-python clean verify
    • 738 C++ tests passed
    • 172 Python tests passed

@ColinLeeo
ColinLeeo requested review from hongzhi-gao and jt2594838 and a lite review from Copilot August 5, 2026 08:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Adds file-level TsFile property support across C++, the C wrapper, and Python APIs, enabling binary property read/write with null vs empty distinction and compatibility with Java’s Map<String, byte[]>.

Changes:

  • Introduces TsFilePropertyValue / TsFileProperties, wires property storage into writer/metadata serialization and reader access.
  • Extends C wrapper with length-aware setter and reader APIs plus explicit free function.
  • Adds Python writer/reader bindings plus tests and documentation updates.

Reviewed changes

Copilot reviewed 25 out of 25 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
python/tsfile/tsfile_writer.pyx Adds Python writer API to set binary file properties via C wrapper.
python/tsfile/tsfile_table_writer.py Delegates table-writer property setting to underlying writer.
python/tsfile/tsfile_reader.pyx Adds Python reader API to retrieve properties as `dict[str, bytes
python/tsfile/tsfile_cpp.pxd Declares new C wrapper structs/enums and functions for properties.
python/tests/test_tsfile_properties.py Adds Python tests for round-trip, types, binary/NUL behavior, and closed-writer rejection.
python/README.md Documents Python file-level properties API and encoding guidance.
python/README-zh.md Chinese documentation for Python file-level properties.
cpp/test/writer/tsfile_properties_test.cc Adds C++ tests for property semantics and system property precedence.
cpp/test/writer/table_view/tsfile_writer_table_test.cc Adds table-writer delegation test for properties.
cpp/test/cwrapper/cwrapper_properties_test.cc Adds C wrapper tests for length-aware round-trips, validation, and cleanup.
cpp/test/common/tsfile_common_test.cc Updates metadata serialization test to new value-owned property model.
cpp/src/writer/tsfile_writer.h / .cc Adds C++ writer public setters for properties.
cpp/src/writer/tsfile_table_writer.h / .cc Adds C++ table-writer setters delegating to writer with closed checks.
cpp/src/reader/tsfile_reader.h / .cc Adds C++ reader API to return file-level properties.
cpp/src/file/tsfile_io_writer.h / .cc Stores properties in IO writer; serializes them into TsFileMeta, enforces system property override.
cpp/src/cwrapper/tsfile_cwrapper.h / .cc Adds C APIs for setting/reading properties plus memory-free helper and validation.
cpp/src/common/tsfile_common.h / .cc Introduces TsFilePropertyValue and updates meta (de)serialization format handling.
cpp/README.md Documents C++ file-level properties usage.
cpp/README-zh.md Chinese documentation for C++ file-level properties.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +265 to +313
if (RET_FAIL(common::SerializationUtil::read_var_int(tsfile_properties_size,
in))) {
return ret;
}
if (tsfile_properties_size < 0) {
return common::E_TSFILE_CORRUPTED;
}
for (int i = 0; i < tsfile_properties_size; i++) {
std::string key, *value;
common::SerializationUtil::read_var_str(key, in);
common::SerializationUtil::read_var_char_ptr(value, in);
tsfile_properties_.emplace(key, value);
std::string key;
int32_t key_len = 0;
int32_t value_len = 0;
if (RET_FAIL(common::SerializationUtil::read_var_int(key_len, in))) {
return ret;
} else if (key_len < 0) {
return common::E_TSFILE_CORRUPTED;
}
key.resize(static_cast<size_t>(key_len));
if (key_len > 0) {
uint32_t read_len = 0;
if (RET_FAIL(in.read_buf(reinterpret_cast<uint8_t*>(&key[0]),
static_cast<uint32_t>(key_len),
read_len))) {
return ret;
} else if (read_len != static_cast<uint32_t>(key_len)) {
return common::E_BUF_NOT_ENOUGH;
}
}
if (RET_FAIL(common::SerializationUtil::read_var_int(value_len, in))) {
return ret;
}

TsFilePropertyValue value;
if (value_len == NO_STR_TO_READ) {
value.is_null = true;
} else if (value_len < 0) {
return common::E_TSFILE_CORRUPTED;
} else {
value.is_null = false;
value.value.resize(static_cast<size_t>(value_len));
if (value_len > 0) {
uint32_t read_len = 0;
if (RET_FAIL(
in.read_buf(value.value.data(), value_len, read_len))) {
return ret;
} else if (read_len != static_cast<uint32_t>(value_len)) {
return common::E_BUF_NOT_ENOUGH;
}
}
}
Comment on lines +209 to +218
const TsFilePropertyValue& value = tsfile_property.second;
if (value.is_null) {
common::SerializationUtil::write_var_int(NO_STR_TO_READ, out);
} else {
common::SerializationUtil::write_var_int(
static_cast<int32_t>(value.value.size()), out);
if (!value.value.empty()) {
out.write_buf(value.value.data(), value.value.size());
}
}
Comment on lines +126 to +138
int TsFileIOWriter::add_tsfile_property(const std::string& key,
const std::vector<uint8_t>& value) {
if (key.size() > static_cast<size_t>(std::numeric_limits<int32_t>::max()) ||
value.size() >
static_cast<size_t>(std::numeric_limits<int32_t>::max())) {
return common::E_OUT_OF_RANGE;
}
if (file_ == nullptr || file_->get_fd() < 0) {
return common::E_FILE_WRITE_ERR;
}
tsfile_properties_[key] = TsFilePropertyValue(value);
return common::E_OK;
}
Comment on lines +263 to +272
ERRNO tsfile_writer_add_tsfile_property(TsFileWriter writer, const char* key,
uint32_t key_len, const uint8_t* value,
uint32_t value_len) {
if (writer == nullptr || key == nullptr ||
(value == nullptr && value_len > 0)) {
return common::E_INVALID_ARG;
}
if (key_len > static_cast<uint32_t>(std::numeric_limits<int32_t>::max())) {
return common::E_OUT_OF_RANGE;
}
Comment thread python/tsfile/tsfile_reader.pyx Outdated
Comment on lines +541 to +543
key = PyBytes_FromStringAndSize(
properties[i].key, properties[i].key_len
).decode('utf-8')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants