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,

Reply via email to