This is an automated email from the ASF dual-hosted git repository.
hongzhigao pushed a commit to branch feat/c-python-timeseries-metadata
in repository https://gitbox.apache.org/repos/asf/tsfile.git
The following commit(s) were added to
refs/heads/feat/c-python-timeseries-metadata by this push:
new 9fc3e828 python c metadata interface
9fc3e828 is described below
commit 9fc3e828b41568b1a0c79cb9d8929404929930ea
Author: 761417898 <[email protected]>
AuthorDate: Fri Apr 3 19:55:09 2026 +0800
python c metadata interface
---
cpp/src/cwrapper/tsfile_cwrapper.cc | 157 ++++++++++++++++++++++++++--
cpp/src/cwrapper/tsfile_cwrapper.h | 29 +++++
cpp/test/cwrapper/cwrapper_metadata_test.cc | 137 ++++++++++++++++++++++++
python/tests/test_reader_metadata.py | 86 +++++++++++++++
python/tsfile/schema.py | 20 +++-
python/tsfile/tsfile_cpp.pxd | 18 ++++
python/tsfile/tsfile_py_cpp.pyx | 23 ++++
7 files changed, 460 insertions(+), 10 deletions(-)
diff --git a/cpp/src/cwrapper/tsfile_cwrapper.cc
b/cpp/src/cwrapper/tsfile_cwrapper.cc
index 1f1010c8..3582f5f6 100644
--- a/cpp/src/cwrapper/tsfile_cwrapper.cc
+++ b/cpp/src/cwrapper/tsfile_cwrapper.cc
@@ -703,15 +703,45 @@ const DeviceID tsfile_c_metadata_empty_device_list_marker
= {nullptr};
namespace {
+char* dup_common_string_to_cstr(const common::String& s) {
+ if (s.buf_ == nullptr || s.len_ == 0) {
+ return strdup("");
+ }
+ char* p = static_cast<char*>(malloc(static_cast<size_t>(s.len_) + 1U));
+ if (p == nullptr) {
+ return nullptr;
+ }
+ memcpy(p, s.buf_, static_cast<size_t>(s.len_));
+ p[s.len_] = '\0';
+ return p;
+}
+
+void free_timeseries_statistic_heap(TimeseriesStatistic* s) {
+ if (s == nullptr) {
+ return;
+ }
+ free(s->str_min);
+ s->str_min = nullptr;
+ free(s->str_max);
+ s->str_max = nullptr;
+ free(s->str_first);
+ s->str_first = nullptr;
+ free(s->str_last);
+ s->str_last = nullptr;
+}
+
void clear_timeseries_statistic(TimeseriesStatistic* s) {
memset(s, 0, sizeof(*s));
}
-void fill_timeseries_statistic(storage::Statistic* st,
- TimeseriesStatistic* out) {
+/**
+ * Fills @p out from C++ Statistic. On allocation failure returns E_OOM and
+ * clears/frees any partial string fields in @p out.
+ */
+int fill_timeseries_statistic(storage::Statistic* st, TimeseriesStatistic*
out) {
clear_timeseries_statistic(out);
if (st == nullptr) {
- return;
+ return common::E_OK;
}
out->has_statistic = true;
out->row_count = st->get_count();
@@ -725,6 +755,9 @@ void fill_timeseries_statistic(storage::Statistic* st,
auto* bs = static_cast<storage::BooleanStatistic*>(st);
out->sum_valid = true;
out->sum = static_cast<double>(bs->sum_value_);
+ out->bool_ext_valid = true;
+ out->first_bool = bs->first_value_;
+ out->last_bool = bs->last_value_;
break;
}
case common::INT32:
@@ -732,6 +765,13 @@ void fill_timeseries_statistic(storage::Statistic* st,
auto* is = static_cast<storage::Int32Statistic*>(st);
out->sum_valid = true;
out->sum = static_cast<double>(is->sum_value_);
+ if (out->row_count > 0) {
+ out->int_range_valid = true;
+ out->min_int64 = static_cast<int64_t>(is->min_value_);
+ out->max_int64 = static_cast<int64_t>(is->max_value_);
+ out->first_int64 = static_cast<int64_t>(is->first_value_);
+ out->last_int64 = static_cast<int64_t>(is->last_value_);
+ }
break;
}
case common::INT64:
@@ -739,23 +779,98 @@ void fill_timeseries_statistic(storage::Statistic* st,
auto* ls = static_cast<storage::Int64Statistic*>(st);
out->sum_valid = true;
out->sum = ls->sum_value_;
+ if (out->row_count > 0) {
+ out->int_range_valid = true;
+ out->min_int64 = ls->min_value_;
+ out->max_int64 = ls->max_value_;
+ out->first_int64 = ls->first_value_;
+ out->last_int64 = ls->last_value_;
+ }
break;
}
case common::FLOAT: {
auto* fs = static_cast<storage::FloatStatistic*>(st);
out->sum_valid = true;
out->sum = static_cast<double>(fs->sum_value_);
+ if (out->row_count > 0) {
+ out->float_range_valid = true;
+ out->min_float64 = static_cast<double>(fs->min_value_);
+ out->max_float64 = static_cast<double>(fs->max_value_);
+ out->first_float64 = static_cast<double>(fs->first_value_);
+ out->last_float64 = static_cast<double>(fs->last_value_);
+ }
break;
}
case common::DOUBLE: {
auto* ds = static_cast<storage::DoubleStatistic*>(st);
out->sum_valid = true;
out->sum = ds->sum_value_;
+ if (out->row_count > 0) {
+ out->float_range_valid = true;
+ out->min_float64 = ds->min_value_;
+ out->max_float64 = ds->max_value_;
+ out->first_float64 = ds->first_value_;
+ out->last_float64 = ds->last_value_;
+ }
+ break;
+ }
+ case common::STRING: {
+ auto* ss = static_cast<storage::StringStatistic*>(st);
+ out->str_ext_valid = true;
+ out->str_min = dup_common_string_to_cstr(ss->min_value_);
+ if (out->str_min == nullptr) {
+ free_timeseries_statistic_heap(out);
+ clear_timeseries_statistic(out);
+ return common::E_OOM;
+ }
+ out->str_max = dup_common_string_to_cstr(ss->max_value_);
+ if (out->str_max == nullptr) {
+ free_timeseries_statistic_heap(out);
+ clear_timeseries_statistic(out);
+ return common::E_OOM;
+ }
+ out->str_first = dup_common_string_to_cstr(ss->first_value_);
+ if (out->str_first == nullptr) {
+ free_timeseries_statistic_heap(out);
+ clear_timeseries_statistic(out);
+ return common::E_OOM;
+ }
+ out->str_last = dup_common_string_to_cstr(ss->last_value_);
+ if (out->str_last == nullptr) {
+ free_timeseries_statistic_heap(out);
+ clear_timeseries_statistic(out);
+ return common::E_OOM;
+ }
+ break;
+ }
+ case common::TEXT: {
+ auto* ts = static_cast<storage::TextStatistic*>(st);
+ out->str_ext_valid = true;
+ out->str_min = strdup("");
+ out->str_max = strdup("");
+ if (out->str_min == nullptr || out->str_max == nullptr) {
+ free_timeseries_statistic_heap(out);
+ clear_timeseries_statistic(out);
+ return common::E_OOM;
+ }
+ out->str_first = dup_common_string_to_cstr(ts->first_value_);
+ if (out->str_first == nullptr) {
+ free_timeseries_statistic_heap(out);
+ clear_timeseries_statistic(out);
+ return common::E_OOM;
+ }
+ out->str_last = dup_common_string_to_cstr(ts->last_value_);
+ if (out->str_last == nullptr) {
+ free_timeseries_statistic_heap(out);
+ clear_timeseries_statistic(out);
+ return common::E_OOM;
+ }
break;
}
default:
break;
}
+ return common::E_OK;
}
void free_device_timeseries_metadata_entries_partial(
@@ -768,6 +883,8 @@ void free_device_timeseries_metadata_entries_partial(
entries[i].device.path = nullptr;
if (entries[i].timeseries != nullptr) {
for (uint32_t j = 0; j < entries[i].timeseries_count; j++) {
+ free_timeseries_statistic_heap(
+ &entries[i].timeseries[j].statistic);
free(entries[i].timeseries[j].measurement_name);
}
free(entries[i].timeseries);
@@ -868,7 +985,13 @@ ERRNO tsfile_reader_get_timeseries_metadata(
return common::E_OOM;
}
const auto& vec = kv.second;
- e.timeseries_count = static_cast<uint32_t>(vec.size());
+ uint32_t n_ts = 0;
+ for (const auto& idx_nz : vec) {
+ if (idx_nz != nullptr) {
+ n_ts++;
+ }
+ }
+ e.timeseries_count = n_ts;
if (e.timeseries_count == 0) {
e.timeseries = nullptr;
di++;
@@ -884,16 +1007,17 @@ ERRNO tsfile_reader_get_timeseries_metadata(
}
memset(e.timeseries, 0,
sizeof(TimeseriesMetadata) * e.timeseries_count);
- for (uint32_t ti = 0; ti < e.timeseries_count; ti++) {
- const auto& idx = vec[ti];
- TimeseriesMetadata& m = e.timeseries[ti];
+ uint32_t slot = 0;
+ for (const auto& idx : vec) {
if (idx == nullptr) {
continue;
}
+ TimeseriesMetadata& m = e.timeseries[slot];
common::String mn = idx->get_measurement_name();
m.measurement_name = strdup(mn.to_std_string().c_str());
if (m.measurement_name == nullptr) {
- for (uint32_t u = 0; u <= ti; u++) {
+ for (uint32_t u = 0; u < slot; u++) {
+ free_timeseries_statistic_heap(&e.timeseries[u].statistic);
free(e.timeseries[u].measurement_name);
}
free(e.timeseries);
@@ -911,7 +1035,22 @@ ERRNO tsfile_reader_get_timeseries_metadata(
chunk_cnt = static_cast<int32_t>(cl->size());
}
m.chunk_meta_count = chunk_cnt;
- fill_timeseries_statistic(st, &m.statistic);
+ const int st_rc = fill_timeseries_statistic(st, &m.statistic);
+ if (st_rc != common::E_OK) {
+ for (uint32_t u = 0; u < slot; u++) {
+ free_timeseries_statistic_heap(&e.timeseries[u].statistic);
+ free(e.timeseries[u].measurement_name);
+ }
+ free_timeseries_statistic_heap(&m.statistic);
+ free(m.measurement_name);
+ free(e.timeseries);
+ e.timeseries = nullptr;
+ free(e.device.path);
+ e.device.path = nullptr;
+ free_device_timeseries_metadata_entries_partial(entries, di);
+ return st_rc;
+ }
+ slot++;
}
di++;
}
diff --git a/cpp/src/cwrapper/tsfile_cwrapper.h
b/cpp/src/cwrapper/tsfile_cwrapper.h
index 1cad8c47..bfa2430a 100644
--- a/cpp/src/cwrapper/tsfile_cwrapper.h
+++ b/cpp/src/cwrapper/tsfile_cwrapper.h
@@ -113,6 +113,9 @@ typedef struct DeviceID {
/**
* @brief Aggregated statistic for one timeseries (subset of C++ Statistic).
+ *
+ * String pointers str_* are allocated with malloc; freed by
+ * tsfile_free_device_timeseries_metadata_map (do not free individually).
*/
typedef struct TimeseriesStatistic {
bool has_statistic;
@@ -123,6 +126,32 @@ typedef struct TimeseriesStatistic {
bool sum_valid;
/** Sum when sum_valid; boolean uses sum of true as int-like aggregate. */
double sum;
+
+ /** INT32, DATE, INT64, TIMESTAMP: min/max/first/last in int64_t form. */
+ bool int_range_valid;
+ int64_t min_int64;
+ int64_t max_int64;
+ int64_t first_int64;
+ int64_t last_int64;
+
+ /** FLOAT, DOUBLE: min/max/first/last. */
+ bool float_range_valid;
+ double min_float64;
+ double max_float64;
+ double first_float64;
+ double last_float64;
+
+ /** BOOLEAN: first/last sample values. */
+ bool bool_ext_valid;
+ bool first_bool;
+ bool last_bool;
+
+ /** STRING: min/max lexicographic; TEXT: first/last only (min/max unused).
*/
+ bool str_ext_valid;
+ char* str_min;
+ char* str_max;
+ char* str_first;
+ char* str_last;
} TimeseriesStatistic;
/**
diff --git a/cpp/test/cwrapper/cwrapper_metadata_test.cc
b/cpp/test/cwrapper/cwrapper_metadata_test.cc
index 16b29df5..897b838c 100644
--- a/cpp/test/cwrapper/cwrapper_metadata_test.cc
+++ b/cpp/test/cwrapper/cwrapper_metadata_test.cc
@@ -90,6 +90,11 @@ TEST_F(CWrapperMetadataTest,
GetAllDevicesAndMetadataWithStatistic) {
EXPECT_EQ(3, tm.statistic.end_time);
ASSERT_TRUE(tm.statistic.sum_valid);
EXPECT_DOUBLE_EQ(60.0, tm.statistic.sum);
+ ASSERT_TRUE(tm.statistic.int_range_valid);
+ EXPECT_EQ(10, tm.statistic.min_int64);
+ EXPECT_EQ(30, tm.statistic.max_int64);
+ EXPECT_EQ(10, tm.statistic.first_int64);
+ EXPECT_EQ(30, tm.statistic.last_int64);
tsfile_free_device_timeseries_metadata_map(&map);
@@ -113,6 +118,138 @@ TEST_F(CWrapperMetadataTest,
GetAllDevicesAndMetadataWithStatistic) {
remove(filename);
}
+TEST_F(CWrapperMetadataTest, GetTimeseriesMetadataBooleanStatistic) {
+ ERRNO code = RET_OK;
+ const char* filename = "cwrapper_metadata_bool.tsfile";
+ remove(filename);
+
+ const char* device = "root.sg.bool";
+ char* m_b = strdup("s_bool");
+ timeseries_schema sch{};
+ sch.timeseries_name = m_b;
+ sch.data_type = TS_DATATYPE_BOOLEAN;
+ sch.encoding = TS_ENCODING_PLAIN;
+ sch.compression = TS_COMPRESSION_UNCOMPRESSED;
+
+ auto* writer = static_cast<void*>(
+ _tsfile_writer_new(filename, 128 * 1024 * 1024, &code));
+ ASSERT_EQ(RET_OK, code);
+ ASSERT_EQ(RET_OK, _tsfile_writer_register_timeseries(writer, device,
&sch));
+
+ const bool vals[] = {true, false, true};
+ for (int row = 0; row < 3; row++) {
+ auto* record = static_cast<TsRecord>(
+ _ts_record_new(device, static_cast<int64_t>(row + 1), 1));
+ ASSERT_EQ(RET_OK, _insert_data_into_ts_record_by_name_bool(record, m_b,
+ vals[row]));
+ ASSERT_EQ(RET_OK, _tsfile_writer_write_ts_record(writer, record));
+ _free_tsfile_ts_record(reinterpret_cast<TsRecord*>(&record));
+ }
+ ASSERT_EQ(RET_OK, _tsfile_writer_close(writer));
+
+ TsFileReader reader = tsfile_reader_new(filename, &code);
+ ASSERT_EQ(RET_OK, code);
+
+ DeviceTimeseriesMetadataMap map{};
+ ASSERT_EQ(RET_OK,
+ tsfile_reader_get_timeseries_metadata(reader, nullptr, 0, &map));
+ TimeseriesMetadata& tm = map.entries[0].timeseries[0];
+ ASSERT_STREQ(m_b, tm.measurement_name);
+ ASSERT_EQ(TS_DATATYPE_BOOLEAN, tm.data_type);
+ ASSERT_TRUE(tm.statistic.has_statistic);
+ ASSERT_TRUE(tm.statistic.sum_valid);
+ EXPECT_DOUBLE_EQ(2.0, tm.statistic.sum);
+ ASSERT_TRUE(tm.statistic.bool_ext_valid);
+ EXPECT_TRUE(tm.statistic.first_bool);
+ EXPECT_TRUE(tm.statistic.last_bool);
+
+ tsfile_free_device_timeseries_metadata_map(&map);
+ ASSERT_EQ(RET_OK, tsfile_reader_close(reader));
+ free(m_b);
+ remove(filename);
+}
+
+TEST_F(CWrapperMetadataTest, GetTimeseriesMetadataStringStatistic) {
+ ERRNO code = RET_OK;
+ const char* filename = "cwrapper_metadata_str.tsfile";
+ remove(filename);
+
+ const char* device = "root.sg.str";
+ char* m_str = strdup("s_str");
+ timeseries_schema sch{};
+ sch.timeseries_name = m_str;
+ sch.data_type = TS_DATATYPE_STRING;
+ sch.encoding = TS_ENCODING_PLAIN;
+ sch.compression = TS_COMPRESSION_UNCOMPRESSED;
+
+ auto* writer = static_cast<void*>(
+ _tsfile_writer_new(filename, 128 * 1024 * 1024, &code));
+ ASSERT_EQ(RET_OK, code);
+ ASSERT_EQ(RET_OK, _tsfile_writer_register_timeseries(writer, device,
&sch));
+
+ const char* vals[] = {"aa", "cc", "bb"};
+ for (int row = 0; row < 3; row++) {
+ auto* record = static_cast<TsRecord>(
+ _ts_record_new(device, static_cast<int64_t>(row + 1), 1));
+ ASSERT_EQ(RET_OK,
+ _insert_data_into_ts_record_by_name_string_with_len(
+ record, m_str, vals[row],
+ static_cast<int>(std::strlen(vals[row]))));
+ ASSERT_EQ(RET_OK, _tsfile_writer_write_ts_record(writer, record));
+ _free_tsfile_ts_record(reinterpret_cast<TsRecord*>(&record));
+ }
+ ASSERT_EQ(RET_OK, _tsfile_writer_close(writer));
+
+ TsFileReader reader = tsfile_reader_new(filename, &code);
+ ASSERT_EQ(RET_OK, code);
+
+ DeviceTimeseriesMetadataMap map{};
+ ASSERT_EQ(RET_OK,
+ tsfile_reader_get_timeseries_metadata(reader, nullptr, 0, &map));
+ ASSERT_EQ(1u, map.device_count);
+ TimeseriesMetadata& tm = map.entries[0].timeseries[0];
+ ASSERT_STREQ(m_str, tm.measurement_name);
+ ASSERT_EQ(TS_DATATYPE_STRING, tm.data_type);
+ ASSERT_TRUE(tm.statistic.has_statistic);
+ ASSERT_TRUE(tm.statistic.str_ext_valid);
+ ASSERT_NE(nullptr, tm.statistic.str_min);
+ ASSERT_NE(nullptr, tm.statistic.str_max);
+ ASSERT_NE(nullptr, tm.statistic.str_first);
+ ASSERT_NE(nullptr, tm.statistic.str_last);
+ EXPECT_STREQ("aa", tm.statistic.str_min);
+ EXPECT_STREQ("cc", tm.statistic.str_max);
+ EXPECT_STREQ("aa", tm.statistic.str_first);
+ EXPECT_STREQ("bb", tm.statistic.str_last);
+
+ tsfile_free_device_timeseries_metadata_map(&map);
+ ASSERT_EQ(RET_OK, tsfile_reader_close(reader));
+ free(m_str);
+ remove(filename);
+}
+
+TEST_F(CWrapperMetadataTest, GetTimeseriesMetadataNullDevicePath) {
+ ERRNO code = RET_OK;
+ const char* filename = "cwrapper_metadata_null_path.tsfile";
+ remove(filename);
+
+ auto* writer = static_cast<void*>(
+ _tsfile_writer_new(filename, 128 * 1024 * 1024, &code));
+ ASSERT_EQ(RET_OK, code);
+ ASSERT_EQ(RET_OK, _tsfile_writer_close(writer));
+
+ TsFileReader reader = tsfile_reader_new(filename, &code);
+ ASSERT_EQ(RET_OK, code);
+
+ DeviceID bad{};
+ bad.path = nullptr;
+ DeviceTimeseriesMetadataMap map{};
+ EXPECT_EQ(RET_INVALID_ARG,
+ tsfile_reader_get_timeseries_metadata(reader, &bad, 1, &map));
+
+ ASSERT_EQ(RET_OK, tsfile_reader_close(reader));
+ remove(filename);
+}
+
TEST_F(CWrapperMetadataTest, GetTimeseriesMetadataInvalidArgs) {
ERRNO code = RET_OK;
const char* filename = "cwrapper_metadata_empty.tsfile";
diff --git a/python/tests/test_reader_metadata.py
b/python/tests/test_reader_metadata.py
index f58c0f67..fc10c40a 100644
--- a/python/tests/test_reader_metadata.py
+++ b/python/tests/test_reader_metadata.py
@@ -67,6 +67,11 @@ def test_get_all_devices_and_timeseries_metadata_statistic():
assert st.end_time == 3
assert st.sum_valid
assert st.sum == pytest.approx(60.0)
+ assert st.int_range_valid
+ assert st.min_int64 == 10
+ assert st.max_int64 == 30
+ assert st.first_int64 == 10
+ assert st.last_int64 == 30
assert reader.get_timeseries_metadata([]) == {}
@@ -82,3 +87,84 @@ def test_get_all_devices_and_timeseries_metadata_statistic():
os.unlink(path)
except OSError:
pass
+
+
+def test_get_timeseries_metadata_boolean_statistic():
+ path = os.path.join(tempfile.gettempdir(),
"py_reader_metadata_bool.tsfile")
+ try:
+ os.unlink(path)
+ except OSError:
+ pass
+
+ device = "root.sg.py_bool"
+ writer = TsFileWriter(path)
+ writer.register_timeseries(
+ device, TimeseriesSchema("m_b", TSDataType.BOOLEAN))
+ for row, b in enumerate([True, False, True]):
+ writer.write_row_record(
+ RowRecord(
+ device,
+ row + 1,
+ [Field("m_b", b, TSDataType.BOOLEAN)],
+ )
+ )
+ writer.close()
+
+ reader = TsFileReader(path)
+ try:
+ meta_all = reader.get_timeseries_metadata(None)
+ st = meta_all[device][0].statistic
+ assert st.has_statistic
+ assert st.sum_valid
+ assert st.sum == pytest.approx(2.0)
+ assert st.bool_ext_valid
+ assert st.first_bool is True
+ assert st.last_bool is True
+ finally:
+ reader.close()
+ try:
+ os.unlink(path)
+ except OSError:
+ pass
+
+
+def test_get_timeseries_metadata_string_statistic():
+ path = os.path.join(tempfile.gettempdir(), "py_reader_metadata_str.tsfile")
+ try:
+ os.unlink(path)
+ except OSError:
+ pass
+
+ device = "root.sg.py_str"
+ writer = TsFileWriter(path)
+ writer.register_timeseries(
+ device, TimeseriesSchema("m_str", TSDataType.STRING))
+ for row, s in enumerate(["aa", "cc", "bb"]):
+ writer.write_row_record(
+ RowRecord(
+ device,
+ row + 1,
+ [Field("m_str", s, TSDataType.STRING)],
+ )
+ )
+ writer.close()
+
+ reader = TsFileReader(path)
+ try:
+ meta_all = reader.get_timeseries_metadata(None)
+ m = meta_all[device][0]
+ assert m.measurement_name == "m_str"
+ assert m.data_type == TSDataType.STRING
+ st = m.statistic
+ assert st.has_statistic
+ assert st.str_ext_valid
+ assert st.str_min == "aa"
+ assert st.str_max == "cc"
+ assert st.str_first == "aa"
+ assert st.str_last == "bb"
+ finally:
+ reader.close()
+ try:
+ os.unlink(path)
+ except OSError:
+ pass
diff --git a/python/tsfile/schema.py b/python/tsfile/schema.py
index 955253ea..10b2412a 100644
--- a/python/tsfile/schema.py
+++ b/python/tsfile/schema.py
@@ -16,7 +16,7 @@
# under the License.
#
from dataclasses import dataclass
-from typing import List
+from typing import List, Optional
from .exceptions import TypeMismatchError
from .constants import TSDataType, ColumnCategory, TSEncoding, Compressor
@@ -42,6 +42,24 @@ class TimeseriesStatistic:
end_time: int
sum_valid: bool
sum: float
+ int_range_valid: bool = False
+ min_int64: int = 0
+ max_int64: int = 0
+ first_int64: int = 0
+ last_int64: int = 0
+ float_range_valid: bool = False
+ min_float64: float = 0.0
+ max_float64: float = 0.0
+ first_float64: float = 0.0
+ last_float64: float = 0.0
+ bool_ext_valid: bool = False
+ first_bool: bool = False
+ last_bool: bool = False
+ str_ext_valid: bool = False
+ str_min: Optional[str] = None
+ str_max: Optional[str] = None
+ str_first: Optional[str] = None
+ str_last: Optional[str] = None
@dataclass(frozen=True)
diff --git a/python/tsfile/tsfile_cpp.pxd b/python/tsfile/tsfile_cpp.pxd
index 22f32459..10ba05a6 100644
--- a/python/tsfile/tsfile_cpp.pxd
+++ b/python/tsfile/tsfile_cpp.pxd
@@ -113,6 +113,24 @@ cdef extern from "cwrapper/tsfile_cwrapper.h":
int64_t end_time
bint sum_valid
double sum
+ bint int_range_valid
+ int64_t min_int64
+ int64_t max_int64
+ int64_t first_int64
+ int64_t last_int64
+ bint float_range_valid
+ double min_float64
+ double max_float64
+ double first_float64
+ double last_float64
+ bint bool_ext_valid
+ bint first_bool
+ bint last_bool
+ bint str_ext_valid
+ char* str_min
+ char* str_max
+ char* str_first
+ char* str_last
ctypedef struct TimeseriesMetadata:
char * measurement_name
diff --git a/python/tsfile/tsfile_py_cpp.pyx b/python/tsfile/tsfile_py_cpp.pyx
index d913b0c4..91910175 100644
--- a/python/tsfile/tsfile_py_cpp.pyx
+++ b/python/tsfile/tsfile_py_cpp.pyx
@@ -927,6 +927,11 @@ cdef object get_all_timeseries_schema(TsFileReader reader):
free(schemas)
return device_schemas
+cdef object _c_str_to_py_utf8_or_none(char* p):
+ if p == NULL:
+ return None
+ return p.decode('utf-8')
+
cdef object timeseries_metadata_c_to_py(TimeseriesMetadata* m):
cdef str name_py
if m == NULL or m.measurement_name == NULL:
@@ -940,6 +945,24 @@ cdef object
timeseries_metadata_c_to_py(TimeseriesMetadata* m):
int(m.statistic.end_time),
bool(m.statistic.sum_valid),
float(m.statistic.sum),
+ bool(m.statistic.int_range_valid),
+ int(m.statistic.min_int64),
+ int(m.statistic.max_int64),
+ int(m.statistic.first_int64),
+ int(m.statistic.last_int64),
+ bool(m.statistic.float_range_valid),
+ float(m.statistic.min_float64),
+ float(m.statistic.max_float64),
+ float(m.statistic.first_float64),
+ float(m.statistic.last_float64),
+ bool(m.statistic.bool_ext_valid),
+ bool(m.statistic.first_bool),
+ bool(m.statistic.last_bool),
+ bool(m.statistic.str_ext_valid),
+ _c_str_to_py_utf8_or_none(m.statistic.str_min),
+ _c_str_to_py_utf8_or_none(m.statistic.str_max),
+ _c_str_to_py_utf8_or_none(m.statistic.str_first),
+ _c_str_to_py_utf8_or_none(m.statistic.str_last),
)
return TimeseriesMetadataPy(
name_py,