Skip to content

Commit 9ec0abf

Browse files
authored
Add C++ and Python TsFile properties support (#897)
* Add C++ and Python TsFile properties support * Fix TsFile properties metadata compatibility * Fix Windows TsFile binary file mode * Preserve legacy empty BloomFilter metadata
1 parent 585125b commit 9ec0abf

28 files changed

Lines changed: 1208 additions & 54 deletions

cpp/README-zh.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,3 +188,22 @@ bash build.sh
188188
```
189189

190190
即可在 `./examples/build` 目录下生成可执行文件。
191+
192+
### 文件级 Properties
193+
194+
`TsFileWriter``TsFileTableWriter` 可以在 writer 打开期间新增或覆盖二进制
195+
property。传入的数据会立即复制,调用 `flush()` 后仍可继续修改;文件关闭后不能修改。
196+
197+
```cpp
198+
std::vector<uint8_t> value = {0x01, 0x00, 0xFF};
199+
writer.add_tsfile_property("binary-property", value);
200+
201+
// nullptr 且长度为 0 表示 null;空 vector 表示非 null 的零长度值。
202+
writer.add_tsfile_property("null-property", nullptr, 0);
203+
writer.add_tsfile_property("empty-property", std::vector<uint8_t>());
204+
205+
storage::TsFileProperties properties = reader.get_tsfile_properties();
206+
```
207+
208+
Property value 本身不保存数据类型。整数、浮点数或结构体应由应用使用明确、可跨语言的
209+
字节编码进行转换。

cpp/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,23 @@ By default, parallel write is enabled when the machine has more than one CPU cor
203203
## Use TsFile
204204
205205
You can find examples on how to read and write data in `demo_read.cpp` and `demo_write.cpp` located under `./examples/cpp_examples`. There are also examples under `./examples/c_examples` on how to use a C-style API to read and write data in a C environment. The examples will be built automatically when you run the main build command.
206+
207+
### File-level properties
208+
209+
`TsFileWriter` and `TsFileTableWriter` can add or replace binary properties
210+
while the writer is open. Values are copied immediately and may still be
211+
changed after `flush()`; a closed file cannot be modified.
212+
213+
```cpp
214+
std::vector<uint8_t> value = {0x01, 0x00, 0xFF};
215+
writer.add_tsfile_property("binary-property", value);
216+
217+
// nullptr with length 0 is null; an empty vector is a non-null empty value.
218+
writer.add_tsfile_property("null-property", nullptr, 0);
219+
writer.add_tsfile_property("empty-property", std::vector<uint8_t>());
220+
221+
storage::TsFileProperties properties = reader.get_tsfile_properties();
222+
```
223+
224+
Property values do not store a data type. Applications should define their own
225+
portable byte encoding for integers, floating-point values, or structures.

cpp/src/common/tsfile_common.cc

Lines changed: 140 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "common/tsfile_common.h"
2121

2222
#include <algorithm>
23+
#include <limits>
2324
#include <map>
2425

2526
#include "common/logger/elog.h"
@@ -180,37 +181,101 @@ int TSMIterator::get_next(std::shared_ptr<IDeviceID>& ret_device_id,
180181
return ret;
181182
}
182183

183-
int TsFileMeta::serialize_to(common::ByteStream& out) {
184+
int TsFileMeta::serialize_to(common::ByteStream& out,
185+
int32_t& serialized_size) {
186+
serialized_size = 0;
187+
const size_t max_property_size =
188+
static_cast<size_t>(std::numeric_limits<int32_t>::max());
189+
if (tsfile_properties_.size() > max_property_size) {
190+
return common::E_OUT_OF_RANGE;
191+
}
192+
for (const auto& tsfile_property : tsfile_properties_) {
193+
if (tsfile_property.first.size() > max_property_size ||
194+
(!tsfile_property.second.is_null &&
195+
tsfile_property.second.value.size() > max_property_size)) {
196+
return common::E_OUT_OF_RANGE;
197+
}
198+
}
199+
200+
int ret = common::E_OK;
184201
auto start_idx = out.total_size();
185-
common::SerializationUtil::write_var_uint(
186-
table_metadata_index_node_map_.size(), out);
202+
if (RET_FAIL(common::SerializationUtil::write_var_uint(
203+
table_metadata_index_node_map_.size(), out))) {
204+
return ret;
205+
}
187206
for (auto& idx_nodes_iter : table_metadata_index_node_map_) {
188-
common::SerializationUtil::write_var_str(idx_nodes_iter.first, out);
189-
idx_nodes_iter.second->serialize_to(out);
207+
if (RET_FAIL(common::SerializationUtil::write_var_str(
208+
idx_nodes_iter.first, out))) {
209+
return ret;
210+
} else if (RET_FAIL(idx_nodes_iter.second->serialize_to(out))) {
211+
return ret;
212+
}
190213
}
191214

192-
common::SerializationUtil::write_var_uint(table_schemas_.size(), out);
215+
if (RET_FAIL(common::SerializationUtil::write_var_uint(
216+
table_schemas_.size(), out))) {
217+
return ret;
218+
}
193219
for (auto& table_schema_iter : table_schemas_) {
194-
common::SerializationUtil::write_var_str(table_schema_iter.first, out);
195-
table_schema_iter.second->serialize_to(out);
220+
if (RET_FAIL(common::SerializationUtil::write_var_str(
221+
table_schema_iter.first, out))) {
222+
return ret;
223+
} else if (RET_FAIL(table_schema_iter.second->serialize_to(out))) {
224+
return ret;
225+
}
196226
}
197227

198-
common::SerializationUtil::write_i64(meta_offset_, out);
228+
if (RET_FAIL(common::SerializationUtil::write_i64(meta_offset_, out))) {
229+
return ret;
230+
}
199231

200232
if (bloom_filter_ != nullptr) {
201-
bloom_filter_->serialize_to(out);
233+
if (RET_FAIL(bloom_filter_->serialize_to(out))) {
234+
return ret;
235+
}
202236
} else {
203-
common::SerializationUtil::write_ui8(0, out);
237+
if (RET_FAIL(common::SerializationUtil::write_ui8(0, out))) {
238+
return ret;
239+
}
204240
}
205241

206-
common::SerializationUtil::write_var_int(tsfile_properties_.size(), out);
242+
if (RET_FAIL(common::SerializationUtil::write_var_int(
243+
static_cast<int32_t>(tsfile_properties_.size()), out))) {
244+
return ret;
245+
}
207246
for (const auto& tsfile_property : tsfile_properties_) {
208-
common::SerializationUtil::write_var_str(tsfile_property.first, out);
209-
common::SerializationUtil::write_var_char_ptr(tsfile_property.second,
210-
out);
247+
if (RET_FAIL(common::SerializationUtil::write_var_str(
248+
tsfile_property.first, out))) {
249+
return ret;
250+
}
251+
const TsFilePropertyValue& value = tsfile_property.second;
252+
if (value.is_null) {
253+
if (RET_FAIL(common::SerializationUtil::write_var_int(
254+
NO_STR_TO_READ, out))) {
255+
return ret;
256+
}
257+
} else {
258+
if (RET_FAIL(common::SerializationUtil::write_var_int(
259+
static_cast<int32_t>(value.value.size()), out))) {
260+
return ret;
261+
}
262+
if (!value.value.empty()) {
263+
if (RET_FAIL(out.write_buf(
264+
value.value.data(),
265+
static_cast<uint32_t>(value.value.size())))) {
266+
return ret;
267+
}
268+
}
269+
}
211270
}
212271

213-
return out.total_size() - start_idx;
272+
const uint64_t total_size = out.total_size() - start_idx;
273+
if (total_size >
274+
static_cast<uint64_t>(std::numeric_limits<int32_t>::max())) {
275+
return common::E_OUT_OF_RANGE;
276+
}
277+
serialized_size = static_cast<int32_t>(total_size);
278+
return common::E_OK;
214279
}
215280

216281
int TsFileMeta::deserialize_from(common::ByteStream& in) {
@@ -251,15 +316,67 @@ int TsFileMeta::deserialize_from(common::ByteStream& in) {
251316

252317
common::SerializationUtil::read_i64(meta_offset_, in);
253318

254-
bloom_filter_->deserialize_from(in);
319+
if (RET_FAIL(bloom_filter_->deserialize_from(in))) {
320+
return ret;
321+
}
255322

256323
int32_t tsfile_properties_size = 0;
257-
common::SerializationUtil::read_var_int(tsfile_properties_size, in);
324+
if (RET_FAIL(common::SerializationUtil::read_var_int(tsfile_properties_size,
325+
in))) {
326+
return ret;
327+
}
328+
if (tsfile_properties_size < 0) {
329+
return common::E_TSFILE_CORRUPTED;
330+
}
258331
for (int i = 0; i < tsfile_properties_size; i++) {
259-
std::string key, *value;
260-
common::SerializationUtil::read_var_str(key, in);
261-
common::SerializationUtil::read_var_char_ptr(value, in);
262-
tsfile_properties_.emplace(key, value);
332+
std::string key;
333+
int32_t key_len = 0;
334+
int32_t value_len = 0;
335+
if (RET_FAIL(common::SerializationUtil::read_var_int(key_len, in))) {
336+
return ret;
337+
} else if (key_len < 0) {
338+
return common::E_TSFILE_CORRUPTED;
339+
}
340+
if (static_cast<uint64_t>(key_len) > in.remaining_size()) {
341+
return common::E_TSFILE_CORRUPTED;
342+
}
343+
key.resize(static_cast<size_t>(key_len));
344+
if (key_len > 0) {
345+
uint32_t read_len = 0;
346+
if (RET_FAIL(in.read_buf(reinterpret_cast<uint8_t*>(&key[0]),
347+
static_cast<uint32_t>(key_len),
348+
read_len))) {
349+
return ret;
350+
} else if (read_len != static_cast<uint32_t>(key_len)) {
351+
return common::E_BUF_NOT_ENOUGH;
352+
}
353+
}
354+
if (RET_FAIL(common::SerializationUtil::read_var_int(value_len, in))) {
355+
return ret;
356+
}
357+
358+
TsFilePropertyValue value;
359+
if (value_len == NO_STR_TO_READ) {
360+
value.is_null = true;
361+
} else if (value_len < 0) {
362+
return common::E_TSFILE_CORRUPTED;
363+
} else {
364+
if (static_cast<uint64_t>(value_len) > in.remaining_size()) {
365+
return common::E_TSFILE_CORRUPTED;
366+
}
367+
value.is_null = false;
368+
value.value.resize(static_cast<size_t>(value_len));
369+
if (value_len > 0) {
370+
uint32_t read_len = 0;
371+
if (RET_FAIL(
372+
in.read_buf(value.value.data(), value_len, read_len))) {
373+
return ret;
374+
} else if (read_len != static_cast<uint32_t>(value_len)) {
375+
return common::E_BUF_NOT_ENOUGH;
376+
}
377+
}
378+
}
379+
tsfile_properties_.emplace(key, std::move(value));
263380
}
264381
return ret;
265382
}
@@ -375,4 +492,4 @@ int MetaIndexNode::binary_search_children(const String &name,
375492
}
376493
#endif
377494

378-
} // end namespace storage
495+
} // end namespace storage

cpp/src/common/tsfile_common.h

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,13 +1126,35 @@ struct MetaIndexNode {
11261126

11271127
class TableSchema;
11281128

1129+
struct TsFilePropertyValue {
1130+
/** A default-constructed property represents a null value. */
1131+
TsFilePropertyValue() : is_null(true), value() {}
1132+
1133+
/** A vector, including an empty vector, represents a non-null value. */
1134+
explicit TsFilePropertyValue(const std::vector<uint8_t>& value)
1135+
: is_null(false), value(value) {}
1136+
1137+
/** nullptr represents null; a non-null pointer with length 0 is empty. */
1138+
TsFilePropertyValue(const uint8_t* data, uint32_t value_len)
1139+
: is_null(data == nullptr), value() {
1140+
if (data != nullptr && value_len > 0) {
1141+
value.assign(data, data + value_len);
1142+
}
1143+
}
1144+
1145+
bool is_null;
1146+
std::vector<uint8_t> value;
1147+
};
1148+
1149+
using TsFileProperties = std::unordered_map<std::string, TsFilePropertyValue>;
1150+
11291151
struct TsFileMeta {
11301152
typedef std::map<std::shared_ptr<IDeviceID>, std::shared_ptr<MetaIndexNode>,
11311153
IDeviceIDComparator>
11321154
DeviceNodeMap;
11331155
std::map<std::string, std::shared_ptr<MetaIndexNode>>
11341156
table_metadata_index_node_map_;
1135-
std::unordered_map<std::string, std::string*> tsfile_properties_;
1157+
TsFileProperties tsfile_properties_;
11361158
typedef std::unordered_map<std::string, std::shared_ptr<TableSchema>>
11371159
TableSchemasMap;
11381160
TableSchemasMap table_schemas_;
@@ -1170,18 +1192,12 @@ struct TsFileMeta {
11701192
if (bloom_filter_ != nullptr) {
11711193
bloom_filter_->destroy();
11721194
}
1173-
for (auto properties : tsfile_properties_) {
1174-
if (properties.second != nullptr) {
1175-
delete properties.second;
1176-
properties.second = nullptr;
1177-
}
1178-
}
11791195
tsfile_properties_.clear();
11801196
table_metadata_index_node_map_.clear();
11811197
table_schemas_.clear();
11821198
}
11831199

1184-
int serialize_to(common::ByteStream& out);
1200+
int serialize_to(common::ByteStream& out, int32_t& serialized_size);
11851201

11861202
int deserialize_from(common::ByteStream& in);
11871203

0 commit comments

Comments
 (0)