This is an automated email from the ASF dual-hosted git repository.

hongzhigao pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/tsfile.git


The following commit(s) were added to refs/heads/develop by this push:
     new da7cd088 Feature/new cpp interface (#627)
da7cd088 is described below

commit da7cd088ea28078dcc3c3f1533fcb338305aa1ed
Author: Hongzhi Gao <[email protected]>
AuthorDate: Fri Nov 7 16:48:38 2025 +0800

    Feature/new cpp interface (#627)
    
    * rename recordIterator() to iterator()
    
    * implement new cpp interface
    
    * Marked get_device_schema interface with existing bug
    
    * resumed deleted file
    
    * Update tsfile_reader_tree_test.cc, removed unused code
    
    * This should call write_tree(record) instead of write_record(record) to 
properly handle aligned vs non-aligned records.
---
 cpp/src/common/record.h                            |   5 +
 cpp/src/reader/qds_with_timegenerator.h            |   4 +-
 cpp/src/reader/qds_without_timegenerator.h         |   4 +-
 cpp/src/reader/result_set.h                        |  70 ++++++-
 cpp/src/reader/table_result_set.h                  |   1 -
 cpp/src/reader/tsfile_reader.cc                    |  14 ++
 cpp/src/reader/tsfile_reader.h                     |   8 +
 cpp/src/reader/tsfile_tree_reader.cc               |  71 +++++++
 cpp/src/reader/tsfile_tree_reader.h                | 107 ++++++++++
 cpp/src/writer/tsfile_tree_writer.cc               |  53 +++++
 cpp/src/writer/tsfile_tree_writer.h                | 134 ++++++++++++
 cpp/src/writer/tsfile_writer.cc                    |  25 +++
 cpp/src/writer/tsfile_writer.h                     |   2 +
 .../reader/tree_view/tsfile_reader_tree_test.cc    | 226 +++++++++++++++++++++
 .../apache/tsfile/v4/TsFileTreeReaderExample.java  |   2 +-
 .../tsfile/read/query/dataset/ResultSet.java       |   2 +-
 .../tsfile/read/query/dataset/TableResultSet.java  |   2 +-
 .../tsfile/read/query/dataset/TreeResultSet.java   |   2 +-
 .../apache/tsfile/read/TsFileTreeReaderTest.java   |   2 +-
 .../apache/tsfile/read/query/ResultSetTest.java    |   2 +-
 20 files changed, 721 insertions(+), 15 deletions(-)

diff --git a/cpp/src/common/record.h b/cpp/src/common/record.h
index 370508a3..8c729f68 100644
--- a/cpp/src/common/record.h
+++ b/cpp/src/common/record.h
@@ -125,6 +125,11 @@ struct TsRecord {
         pa.init(512, common::MOD_TSFILE_READER);
     }
 
+    TsRecord(const std::string &device_name, const int64_t &timestamp)
+        : device_id_(device_name), timestamp_(timestamp) {
+        pa.init(512, common::MOD_TSFILE_READER);
+    }
+
     TsRecord(int64_t timestamp, const std::string &device_name,
              int32_t point_count_in_row = 0)
         : timestamp_(timestamp), device_id_(device_name), points_() {
diff --git a/cpp/src/reader/qds_with_timegenerator.h 
b/cpp/src/reader/qds_with_timegenerator.h
index 648a1ce8..69916868 100644
--- a/cpp/src/reader/qds_with_timegenerator.h
+++ b/cpp/src/reader/qds_with_timegenerator.h
@@ -109,8 +109,7 @@ struct Node {
 class QDSWithTimeGenerator : public ResultSet {
    public:
     QDSWithTimeGenerator()
-        : row_record_(nullptr),
-          result_set_metadata_(nullptr),
+        : result_set_metadata_(nullptr),
           io_reader_(nullptr),
           qe_(nullptr),
           tree_(nullptr),
@@ -129,7 +128,6 @@ class QDSWithTimeGenerator : public ResultSet {
     int construct_node_tree(Expression *expr, Node *&node);
 
    private:
-    RowRecord *row_record_;
     std::shared_ptr<ResultSetMetadata> result_set_metadata_;
     TsFileIOReader *io_reader_;
     QueryExpression *qe_;
diff --git a/cpp/src/reader/qds_without_timegenerator.h 
b/cpp/src/reader/qds_without_timegenerator.h
index 5c461cf4..ae1922fa 100644
--- a/cpp/src/reader/qds_without_timegenerator.h
+++ b/cpp/src/reader/qds_without_timegenerator.h
@@ -32,8 +32,7 @@ namespace storage {
 class QDSWithoutTimeGenerator : public ResultSet {
    public:
     QDSWithoutTimeGenerator()
-        : row_record_(nullptr),
-          result_set_metadata_(nullptr),
+        : result_set_metadata_(nullptr),
           io_reader_(nullptr),
           qe_(nullptr),
           ssi_vec_(),
@@ -54,7 +53,6 @@ class QDSWithoutTimeGenerator : public ResultSet {
     int get_next_tsblock(uint32_t index, bool alloc_mem);
 
    private:
-    RowRecord *row_record_;
     std::shared_ptr<ResultSetMetadata> result_set_metadata_;
     TsFileIOReader *io_reader_;
     QueryExpression *qe_;
diff --git a/cpp/src/reader/result_set.h b/cpp/src/reader/result_set.h
index b0d45b01..da9891f2 100644
--- a/cpp/src/reader/result_set.h
+++ b/cpp/src/reader/result_set.h
@@ -21,7 +21,6 @@
 #define READER_QUERY_DATA_SET_H
 
 #include <algorithm>
-#include <iostream>
 #include <string>
 #include <unordered_map>
 
@@ -84,6 +83,8 @@ class ResultSetMetadata {
     std::vector<common::TSDataType> column_types_;
 };
 
+class ResultSetIterator;
+
 /**
  * @brief ResultSet is the query result of the TsfileReader. It provides access
  * to the results.
@@ -96,7 +97,7 @@ class ResultSetMetadata {
  * it should be QDSWithTimeGenerator.
  * @note If the query uses the table model, the cast should be TableResultSet
  */
-class ResultSet {
+class ResultSet : std::enable_shared_from_this<ResultSet> {
    public:
     ResultSet() {}
     virtual ~ResultSet() {}
@@ -122,6 +123,11 @@ class ResultSet {
      */
     virtual bool is_null(uint32_t column_index) = 0;
 
+    /**
+     * @brief Simple iterator for ResultSet with smart pointers
+     */
+    virtual ResultSetIterator iterator();
+
     /**
      * @brief Get the value of the column by column name
      *
@@ -196,6 +202,7 @@ class ResultSet {
     std::unordered_map<std::string, uint32_t, CaseInsensitiveHash,
                        CaseInsensitiveEqual>
         index_lookup_;
+    RowRecord* row_record_ = nullptr;
     common::PageArena pa_;
 };
 
@@ -231,6 +238,65 @@ inline std::tm ResultSet::get_value(uint32_t column_index) 
{
     return row_record->get_field(column_index)->get_date_value();
 }
 
+/**
+ * @brief Simple iterator for ResultSet with smart pointers
+ */
+class ResultSetIterator {
+   public:
+    explicit ResultSetIterator(ResultSet* result_set)
+        : result_set_(result_set) {}
+
+    /**
+     * @brief Check if there is a next row available
+     */
+    bool hasNext() {
+        if (cached_record_ != nullptr) {
+            return true;
+        }
+        if (exhausted_) {
+            return false;
+        }
+
+        bool has_next = false;
+        if (result_set_) {
+            int ret = result_set_->next(has_next);
+            ASSERT(ret == 0);
+            if (has_next) {
+                cached_record_ = result_set_->get_row_record();
+            } else {
+                exhausted_ = true;
+            }
+        }
+        return has_next;
+    }
+
+    /**
+     * @brief Get the next row record
+     */
+    RowRecord* next() {
+        if (!hasNext()) {
+            return nullptr;
+        }
+        RowRecord* ret = cached_record_;
+        cached_record_ = nullptr;
+        return ret;
+    }
+
+    /**
+     * @brief Get the underlying ResultSet for direct access
+     */
+    ResultSet* getResultSet() const { return result_set_; }
+
+   private:
+    ResultSet* result_set_ = nullptr;
+    RowRecord* cached_record_ = nullptr;
+    bool exhausted_ = false;
+};
+
+inline ResultSetIterator ResultSet::iterator() {
+    return ResultSetIterator(this);
+}
+
 }  // namespace storage
 
 #endif  // READER_QUERY_DATA_SET_H
diff --git a/cpp/src/reader/table_result_set.h 
b/cpp/src/reader/table_result_set.h
index 71e57c47..4192f7c2 100644
--- a/cpp/src/reader/table_result_set.h
+++ b/cpp/src/reader/table_result_set.h
@@ -47,7 +47,6 @@ class TableResultSet : public ResultSet {
     std::unique_ptr<TsBlockReader> tsblock_reader_;
     common::RowIterator* row_iterator_ = nullptr;
     common::TsBlock* tsblock_ = nullptr;
-    RowRecord* row_record_ = nullptr;
     std::shared_ptr<ResultSetMetadata> result_set_metadata_;
     std::vector<std::unique_ptr<TsBlockReader>> tsblock_readers_;
     std::vector<std::string> column_names_;
diff --git a/cpp/src/reader/tsfile_reader.cc b/cpp/src/reader/tsfile_reader.cc
index 5dba982f..6da09430 100644
--- a/cpp/src/reader/tsfile_reader.cc
+++ b/cpp/src/reader/tsfile_reader.cc
@@ -127,6 +127,20 @@ std::vector<std::shared_ptr<IDeviceID>> 
TsFileReader::get_all_devices(
     return device_ids;
 }
 
+std::vector<std::shared_ptr<IDeviceID>> TsFileReader::get_all_device_ids() {
+    TsFileMeta* tsfile_meta = tsfile_executor_->get_tsfile_meta();
+    std::vector<std::shared_ptr<IDeviceID>> device_ids;
+    if (tsfile_meta != nullptr) {
+        PageArena pa;
+        pa.init(512, MOD_TSFILE_READER);
+        for (auto entry : tsfile_meta->table_metadata_index_node_map_) {
+            auto index_node = entry.second;
+            get_all_devices(device_ids, index_node, pa);
+        }
+    }
+    return device_ids;
+}
+
 int TsFileReader::get_all_devices(
     std::vector<std::shared_ptr<IDeviceID>>& device_ids,
     std::shared_ptr<MetaIndexNode> index_node, PageArena& pa) {
diff --git a/cpp/src/reader/tsfile_reader.h b/cpp/src/reader/tsfile_reader.h
index dc5105c0..eb6a7b70 100644
--- a/cpp/src/reader/tsfile_reader.h
+++ b/cpp/src/reader/tsfile_reader.h
@@ -114,6 +114,14 @@ class TsFileReader {
      */
     std::vector<std::shared_ptr<IDeviceID>> get_all_devices(
         std::string table_name);
+
+    /**
+     * @brief get all devices in the tsfile
+     *
+     * @return std::vector<std::shared_ptr<IDeviceID>> the device id list
+     */
+    std::vector<std::shared_ptr<IDeviceID>> get_all_device_ids();
+
     /**
      * @brief get the timeseries schema by the device id and measurement name
      *
diff --git a/cpp/src/reader/tsfile_tree_reader.cc 
b/cpp/src/reader/tsfile_tree_reader.cc
new file mode 100644
index 00000000..c7c9004a
--- /dev/null
+++ b/cpp/src/reader/tsfile_tree_reader.cc
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+#include "reader/tsfile_tree_reader.h"
+
+namespace storage {
+
+TsFileTreeReader::TsFileTreeReader() {
+    tsfile_reader_ = std::make_shared<TsFileReader>();
+}
+
+TsFileTreeReader::~TsFileTreeReader() = default;
+
+int TsFileTreeReader::open(const std::string &file_path) {
+    return tsfile_reader_->open(file_path);
+}
+
+int TsFileTreeReader::close() { return tsfile_reader_->close(); }
+
+int TsFileTreeReader::query(const std::vector<std::string> &device_ids,
+                            const std::vector<std::string> &measurement_names,
+                            int64_t start_time, int64_t end_time,
+                            ResultSet *&result_set) {
+    std::vector<std::string> path_list;
+    for (auto &device_id : device_ids) {
+        for (auto &measurement : measurement_names) {
+            path_list.emplace_back(device_id + PATH_SEPARATOR_CHAR +
+                                   measurement);
+        }
+    }
+    return tsfile_reader_->query(path_list, start_time, end_time, result_set);
+}
+
+void TsFileTreeReader::destroy_query_data_set(ResultSet *qds) {
+    tsfile_reader_->destroy_query_data_set(qds);
+}
+
+std::vector<MeasurementSchema> TsFileTreeReader::get_device_schema(
+    const std::string &device_id) {
+    std::vector<MeasurementSchema> schemas;
+    tsfile_reader_->get_timeseries_schema(
+        std::make_shared<StringArrayDeviceID>(device_id), schemas);
+    return schemas;
+}
+
+std::vector<std::string> TsFileTreeReader::get_all_device_ids() {
+    std::vector<std::string> ret_device_ids;
+    auto device_ids = tsfile_reader_->get_all_device_ids();
+    for (auto device_id : device_ids) {
+        ret_device_ids.emplace_back(device_id->get_device_name());
+    }
+    return ret_device_ids;
+}
+
+}  // namespace storage
\ No newline at end of file
diff --git a/cpp/src/reader/tsfile_tree_reader.h 
b/cpp/src/reader/tsfile_tree_reader.h
new file mode 100644
index 00000000..1d43c3b0
--- /dev/null
+++ b/cpp/src/reader/tsfile_tree_reader.h
@@ -0,0 +1,107 @@
+/*
+ * 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.
+ */
+
+#ifndef READER_TSFILE_TREE_READER_H
+#define READER_TSFILE_TREE_READER_H
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "tsfile_reader.h"
+
+namespace storage {
+
+class TsFileTreeReader {
+   public:
+    TsFileTreeReader();
+    ~TsFileTreeReader();
+
+    /**
+     * @brief open the tsfile
+     *
+     * @param file_path the path of the tsfile which will be opened
+     * @return Returns 0 on success, or a non-zero error code on failure.
+     */
+    int open(const std::string &file_path);
+
+    /**
+     * @brief close the tsfile, this method should be called after the
+     * query is finished
+     *
+     * @return Returns 0 on success, or a non-zero error code on failure.
+     */
+    int close();
+
+    /**
+     * @brief Query time series data for specified devices and measurements
+     * within a time range
+     *
+     * @param device_ids List of device identifiers to query
+     * @param measurement_names List of measurement names to query
+     * @param start_time Start timestamp of the query range (inclusive)
+     * @param end_time End timestamp of the query range (inclusive)
+     * @param[out] result_set Pointer to the ResultSet that will contain query
+     * results
+     * @return Returns 0 on success, or a non-zero error code on failure.
+     *         The caller is responsible for destroying the result set using
+     * destroy_query_data_set()
+     */
+    int query(const std::vector<std::string> &device_ids,
+              const std::vector<std::string> &measurement_names,
+              int64_t start_time, int64_t end_time, ResultSet *&result_set);
+
+    /**
+     * @brief Destroy and deallocate the query result set
+     *
+     * @param result_set Pointer to the ResultSet to be destroyed
+     * @note This method should be called after the result set is no longer
+     * needed to prevent memory leaks
+     */
+    void destroy_query_data_set(ResultSet *result_set);
+
+    /**
+     * @brief Get the measurement schema for a specific device
+     *
+     * @param device_id The device identifier
+     * @return Pointer to the MeasurementSchema for the device, or nullptr if
+     * not found
+     * @note The caller should not delete the returned pointer as it's managed
+     * by the reader
+     */
+    std::vector<MeasurementSchema> get_device_schema(
+        const std::string &device_id);
+
+    /**
+     * @brief Get all device identifiers in the TsFile
+     *
+     * @return Vector containing all device identifiers found in the TsFile
+     * @note The returned vector will be empty if no devices are found or file
+     * is not opened
+     */
+    std::vector<std::string> get_all_device_ids();
+
+   private:
+    std::shared_ptr<TsFileReader>
+        tsfile_reader_;  ///< Underlying TsFile reader implementation
+};
+
+}  // namespace storage
+
+#endif  // READER_TSFILE_TREE_READER_H
\ No newline at end of file
diff --git a/cpp/src/writer/tsfile_tree_writer.cc 
b/cpp/src/writer/tsfile_tree_writer.cc
new file mode 100644
index 00000000..59c11914
--- /dev/null
+++ b/cpp/src/writer/tsfile_tree_writer.cc
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+#include "writer/tsfile_tree_writer.h"
+
+namespace storage {
+
+TsFileTreeWriter::TsFileTreeWriter(storage::WriteFile* writer_file,
+                                   uint64_t memory_threshold) {
+    tsfile_writer_ = std::make_shared<TsFileWriter>();
+    tsfile_writer_->init(writer_file);
+    common::g_config_value_.chunk_group_size_threshold_ = memory_threshold;
+}
+
+int TsFileTreeWriter::register_timeseries(std::string& device_id,
+                                          MeasurementSchema* schema) {
+    return tsfile_writer_->register_timeseries(device_id, *schema);
+}
+
+int TsFileTreeWriter::register_timeseries(
+    std::string& device_id, std::vector<MeasurementSchema*> schemas) {
+    return tsfile_writer_->register_aligned_timeseries(device_id, schemas);
+}
+
+int TsFileTreeWriter::write(const Tablet& tablet) {
+    return tsfile_writer_->write_tree(tablet);
+}
+
+int TsFileTreeWriter::write(const TsRecord& record) {
+    return tsfile_writer_->write_tree(record);
+}
+
+int TsFileTreeWriter::flush() { return tsfile_writer_->flush(); }
+
+int TsFileTreeWriter::close() { return tsfile_writer_->close(); }
+
+}  // namespace storage
\ No newline at end of file
diff --git a/cpp/src/writer/tsfile_tree_writer.h 
b/cpp/src/writer/tsfile_tree_writer.h
new file mode 100644
index 00000000..90ef0d76
--- /dev/null
+++ b/cpp/src/writer/tsfile_tree_writer.h
@@ -0,0 +1,134 @@
+/*
+ * 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.
+ */
+#ifndef WRITER_TSFILE_TREE_WRITER_H
+#define WRITER_TSFILE_TREE_WRITER_H
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "tsfile_writer.h"
+
+namespace storage {
+
+/**
+ * @brief Provides an interface for writing hierarchical (tree-structured)
+ * time-series data into a TsFile.
+ *
+ * The TsFileTreeWriter class is designed for writing time-series data 
organized
+ * in a device–measurement hierarchy into a TsFile. It allows registration of
+ * single or aligned time series and supports writing data in both batch
+ * (Tablet) and row (TsRecord) formats.
+ *
+ * This writer is particularly suitable when data is naturally organized by
+ * device paths, and measurements are associated with those devices. The class
+ * also allows controlling memory usage via a configurable threshold.
+ */
+class TsFileTreeWriter {
+   public:
+    /**
+     * Constructs a TsFileTreeWriter instance to write hierarchical time-series
+     * data into the specified file, with an optional memory threshold for
+     * buffered data.
+     *
+     * @param writer_file Target file where the TsFile data will be written.
+     * Must not be null.
+     * @param memory_threshold Optional parameter specifying the memory usage
+     * threshold for buffered data before automatic flush occurs. Default is
+     * 128MB.
+     */
+    explicit TsFileTreeWriter(storage::WriteFile* writer_file,
+                              uint64_t memory_threshold = 128 * 1024 * 1024);
+
+    /**
+     * Registers a single (non-aligned) time series under the given device ID.
+     *
+     * @param device_id The ID or path of the device to which the time series
+     * belongs.
+     * @param schema The measurement schema defining the data type and encoding
+     * of the time series. Must not be null.
+     * @return Returns 0 on success, or a non-zero error code on failure.
+     */
+    int register_timeseries(std::string& device_id, MeasurementSchema* schema);
+
+    /**
+     * Registers multiple aligned time series under the same device ID.
+     *
+     * @param device_id The ID or path of the device to which the aligned time
+     * series belong.
+     * @param schemas A vector of measurement schema pointers representing the
+     * aligned measurements. Must not be empty or contain null pointers.
+     * @return Returns 0 on success, or a non-zero error code on failure.
+     */
+    int register_timeseries(std::string& device_id,
+                            std::vector<MeasurementSchema*> schemas);
+
+    /**
+     * Writes a batch of data points (tablet) for one or more time series into
+     * the TsFile.
+     *
+     * @param tablet The Tablet object containing multiple rows of data to be
+     * written. Must not be null.
+     * @return Returns 0 on success, or a non-zero error code on failure.
+     */
+    int write(const Tablet& tablet);
+
+    /**
+     * Writes a single record (row) of data for a specific device and timestamp
+     * into the TsFile.
+     *
+     * @param record The TsRecord object containing the device ID, timestamp,
+     * and measurement values. Must not be null.
+     * @return Returns 0 on success, or a non-zero error code on failure.
+     */
+    int write(const TsRecord& record);
+
+    /**
+     * @brief Flushes all buffered data to the underlying storage
+     *
+     * This method forces any buffered data to be written to the TsFile. It is
+     * useful for ensuring data persistence at specific points in the writing
+     * process, such as after writing a significant amount of data or before
+     * performing critical operations.
+     *
+     * @return Returns 0 on success, or a non-zero error code on failure.
+     * @note After a successful flush, buffered data is cleared and memory
+     *       usage is reduced. The writer continues to accept new data after
+     *       flushing.
+     */
+    int flush();
+
+    /**
+     * Closes the writer and ensures all buffered data is flushed to disk.
+     * After this method is called, no further operations should be performed 
on
+     * this writer instance.
+     *
+     * @return Returns 0 on success, or a non-zero error code on failure.
+     */
+    int close();
+
+   private:
+    // Underlying TsFile writer responsible for the actual file I/O and
+    // serialization logic.
+    std::shared_ptr<TsFileWriter> tsfile_writer_;
+};
+
+}  // namespace storage
+
+#endif  // WRITER_TSFILE_TREE_WRITER_H
\ No newline at end of file
diff --git a/cpp/src/writer/tsfile_writer.cc b/cpp/src/writer/tsfile_writer.cc
index 54c1be02..3ae5cf28 100644
--- a/cpp/src/writer/tsfile_writer.cc
+++ b/cpp/src/writer/tsfile_writer.cc
@@ -749,6 +749,31 @@ int TsFileWriter::write_tablet(const Tablet &tablet) {
     return ret;
 }
 
+int TsFileWriter::write_tree(const Tablet &tablet) {
+    auto device_id =
+        std::make_shared<StringArrayDeviceID>(tablet.insert_target_name_);
+    if (schemas_.find(device_id) != schemas_.end()) {
+        auto schema = schemas_[device_id];
+        if (schema->is_aligned_) {
+            return this->write_tablet_aligned(tablet);
+        }
+        return this->write_tablet(tablet);
+    }
+    return E_NOT_EXIST;
+}
+
+int TsFileWriter::write_tree(const TsRecord &record) {
+    auto device_id = std::make_shared<StringArrayDeviceID>(record.device_id_);
+    if (schemas_.find(device_id) != schemas_.end()) {
+        auto schema = schemas_[device_id];
+        if (schema->is_aligned_) {
+            return this->write_record_aligned(record);
+        }
+        return this->write_record(record);
+    }
+    return E_NOT_EXIST;
+}
+
 int TsFileWriter::write_table(Tablet &tablet) {
     int ret = E_OK;
     if (io_writer_->get_schema()->table_schema_map_.find(
diff --git a/cpp/src/writer/tsfile_writer.h b/cpp/src/writer/tsfile_writer.h
index bad92381..db16ae33 100644
--- a/cpp/src/writer/tsfile_writer.h
+++ b/cpp/src/writer/tsfile_writer.h
@@ -73,6 +73,8 @@ class TsFileWriter {
     int write_tablet(const Tablet &tablet);
     int write_record_aligned(const TsRecord &record);
     int write_tablet_aligned(const Tablet &tablet);
+    int write_tree(const Tablet &tablet);
+    int write_tree(const TsRecord &record);
     int write_table(Tablet &tablet);
 
     typedef std::map<std::shared_ptr<IDeviceID>, MeasurementSchemaGroup *,
diff --git a/cpp/test/reader/tree_view/tsfile_reader_tree_test.cc 
b/cpp/test/reader/tree_view/tsfile_reader_tree_test.cc
new file mode 100644
index 00000000..e7466c7b
--- /dev/null
+++ b/cpp/test/reader/tree_view/tsfile_reader_tree_test.cc
@@ -0,0 +1,226 @@
+/*
+ * 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 a
+ *
+ *     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.
+ */
+#include <gtest/gtest.h>
+
+#include <random>
+
+#include "common/record.h"
+#include "common/schema.h"
+#include "common/tablet.h"
+#include "file/write_file.h"
+#include "reader/tsfile_reader.h"
+#include "reader/tsfile_tree_reader.h"
+#include "writer/tsfile_table_writer.h"
+#include "writer/tsfile_tree_writer.h"
+
+namespace storage {
+class QDSWithoutTimeGenerator;
+}
+using namespace storage;
+using namespace common;
+
+class TsFileTreeReaderTest : public ::testing::Test {
+   protected:
+    void SetUp() override {
+        libtsfile_init();
+        file_name_ = std::string("tsfile_writer_tree_test_") +
+                     generate_random_string(10) + std::string(".tsfile");
+        remove(file_name_.c_str());
+        int flags = O_WRONLY | O_CREAT | O_TRUNC;
+#ifdef _WIN32
+        flags |= O_BINARY;
+#endif
+        mode_t mode = 0666;
+        write_file_.create(file_name_, flags, mode);
+    }
+    void TearDown() override { remove(file_name_.c_str()); }
+    std::string file_name_;
+    WriteFile write_file_;
+
+   public:
+    static std::string generate_random_string(int length) {
+        std::random_device rd;
+        std::mt19937 gen(rd());
+        std::uniform_int_distribution<> dis(0, 61);
+
+        const std::string chars =
+            "0123456789"
+            "abcdefghijklmnopqrstuvwxyz"
+            "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+        std::string random_string;
+
+        for (int i = 0; i < length; ++i) {
+            random_string += chars[dis(gen)];
+        }
+
+        return random_string;
+    }
+};
+
+TEST_F(TsFileTreeReaderTest, BasicTest) {
+    TsFileTreeWriter writer(&write_file_);
+    std::string device_id = "test_device";
+    std::string measurement_id = "test_measurement";
+    auto* measurement = new MeasurementSchema(measurement_id, INT64);
+    writer.register_timeseries(device_id, measurement);
+    TsRecord record(device_id, 0);
+    record.add_point(measurement_id, static_cast<int64_t>(1));
+    writer.write(record);
+    writer.flush();
+    writer.close();
+
+    delete measurement;
+    TsFileTreeReader reader;
+    reader.open(file_name_);
+    auto device_ids = reader.get_all_device_ids();
+    ASSERT_EQ(device_ids.size(), 1);
+    ASSERT_EQ(device_ids[0], device_id);
+
+    std::vector<std::string> measurement_ids{measurement_id};
+    ResultSet* result;
+    int ret =
+        reader.query(device_ids, measurement_ids, INT64_MIN, INT64_MAX, 
result);
+    ASSERT_EQ(ret, E_OK);
+    auto iter = result->iterator();
+    RowRecord* read_record;
+    while (iter.hasNext()) {
+        read_record = iter.next();
+        EXPECT_EQ(read_record->get_field(1)->type_, INT64);
+    }
+    reader.destroy_query_data_set(result);
+    reader.close();
+}
+
+TEST_F(TsFileTreeReaderTest, ExtendedRowsAndColumnsTest) {
+    TsFileTreeWriter writer(&write_file_);
+    std::vector<std::string> device_ids = {"device_1", "device_2", "device_3"};
+    std::vector<std::string> measurement_ids = {"temperature", "humidity",
+                                                "pressure", "voltage"};
+    std::vector<TSDataType> data_types = {INT64, DOUBLE, FLOAT, INT32};
+    std::vector<MeasurementSchema*> measurements;
+    for (size_t i = 0; i < measurement_ids.size(); ++i) {
+        auto* measurement =
+            new MeasurementSchema(measurement_ids[i], data_types[i]);
+        measurements.push_back(measurement);
+    }
+    for (auto& device_id : device_ids) {
+        for (auto measurement : measurements) {
+            writer.register_timeseries(device_id, measurement);
+        }
+    }
+
+    const int NUM_ROWS = 100;
+    for (int row = 0; row < NUM_ROWS; ++row) {
+        for (const auto& device_id : device_ids) {
+            TsRecord record(device_id, row * 1000);
+            for (size_t i = 0; i < measurement_ids.size(); ++i) {
+                switch (data_types[i]) {
+                    case INT64:
+                        record.add_point(measurement_ids[i],
+                                         static_cast<int64_t>(row + i));
+                        break;
+                    case DOUBLE:
+                        record.add_point(measurement_ids[i],
+                                         static_cast<double>(row * 1.5 + i));
+                        break;
+                    case FLOAT:
+                        record.add_point(measurement_ids[i],
+                                         static_cast<float>(row * 0.8f + i));
+                        break;
+                    case INT32:
+                        record.add_point(measurement_ids[i],
+                                         static_cast<int32_t>(row * 2 + i));
+                        break;
+                    default:
+                        break;
+                }
+            }
+            writer.write(record);
+        }
+    }
+
+    writer.flush();
+    writer.close();
+
+    TsFileTreeReader reader;
+    reader.open(file_name_);
+
+    auto read_device_ids = reader.get_all_device_ids();
+    ASSERT_EQ(read_device_ids.size(), device_ids.size());
+    for (size_t i = 0; i < device_ids.size(); ++i) {
+        EXPECT_EQ(read_device_ids[i], device_ids[i]);
+    }
+
+    ResultSet* result;
+    int ret =
+        reader.query(device_ids, measurement_ids, 0, NUM_ROWS * 1000, result);
+    ASSERT_EQ(ret, E_OK);
+
+    auto iter = result->iterator();
+    int row_count = 0;
+
+    while (iter.hasNext()) {
+        RowRecord* read_record = iter.next();
+        row_count++;
+        EXPECT_EQ(read_record->get_fields()->size(),
+                  device_ids.size() * measurement_ids.size() + 1);
+
+        // device_id1
+        for (size_t i = 0; i < measurement_ids.size(); ++i) {
+            Field* field = read_record->get_field(i + 1);
+            ASSERT_NE(field, nullptr);
+            EXPECT_EQ(field->type_, data_types[i]);
+
+            int64_t timestamp = read_record->get_timestamp();
+            int row_index = timestamp / 1000;
+
+            switch (data_types[i]) {
+                case INT64: {
+                    EXPECT_EQ(field->get_value<int64_t>(),
+                              static_cast<int64_t>(row_index + i));
+                    break;
+                }
+                case DOUBLE: {
+                    EXPECT_NEAR(field->get_value<double>(), row_index * 1.5 + 
i,
+                                0.001);
+                    break;
+                }
+                case FLOAT: {
+                    EXPECT_NEAR(field->get_value<float>(), row_index * 0.8f + 
i,
+                                0.001f);
+                    break;
+                }
+                case INT32: {
+                    EXPECT_EQ(field->get_value<int32_t>(),
+                              static_cast<int32_t>(row_index * 2 + i));
+                    break;
+                }
+                default:
+                    break;
+            }
+        }
+    }
+    EXPECT_EQ(row_count * device_ids.size(), NUM_ROWS * device_ids.size());
+    reader.destroy_query_data_set(result);
+    reader.close();
+    for (auto* measurement : measurements) {
+        delete measurement;
+    }
+}
diff --git 
a/java/examples/src/main/java/org/apache/tsfile/v4/TsFileTreeReaderExample.java 
b/java/examples/src/main/java/org/apache/tsfile/v4/TsFileTreeReaderExample.java
index a317a22e..ab59cf83 100644
--- 
a/java/examples/src/main/java/org/apache/tsfile/v4/TsFileTreeReaderExample.java
+++ 
b/java/examples/src/main/java/org/apache/tsfile/v4/TsFileTreeReaderExample.java
@@ -109,7 +109,7 @@ public class TsFileTreeReaderExample {
 
       try (ResultSet resultSet = treeReader.query(deviceIds, measurements, 
startTime, endTime)) {
         System.out.println("=== Iterator-based query results ===");
-        Iterator<TSRecord> recordIterator = resultSet.recordIterator();
+        Iterator<TSRecord> recordIterator = resultSet.iterator();
         int countVal = 0;
 
         while (recordIterator.hasNext()) {
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/read/query/dataset/ResultSet.java 
b/java/tsfile/src/main/java/org/apache/tsfile/read/query/dataset/ResultSet.java
index 2e5d0706..43fbd86a 100644
--- 
a/java/tsfile/src/main/java/org/apache/tsfile/read/query/dataset/ResultSet.java
+++ 
b/java/tsfile/src/main/java/org/apache/tsfile/read/query/dataset/ResultSet.java
@@ -92,5 +92,5 @@ public interface ResultSet extends AutoCloseable {
   void close();
 
   @TsFileApi
-  Iterator<TSRecord> recordIterator();
+  Iterator<TSRecord> iterator();
 }
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/read/query/dataset/TableResultSet.java
 
b/java/tsfile/src/main/java/org/apache/tsfile/read/query/dataset/TableResultSet.java
index bd908d7e..f3017c6c 100644
--- 
a/java/tsfile/src/main/java/org/apache/tsfile/read/query/dataset/TableResultSet.java
+++ 
b/java/tsfile/src/main/java/org/apache/tsfile/read/query/dataset/TableResultSet.java
@@ -144,7 +144,7 @@ public class TableResultSet extends AbstractResultSet {
   }
 
   @Override
-  public Iterator<TSRecord> recordIterator() {
+  public Iterator<TSRecord> iterator() {
     return new RecordIterator();
   }
 
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/read/query/dataset/TreeResultSet.java
 
b/java/tsfile/src/main/java/org/apache/tsfile/read/query/dataset/TreeResultSet.java
index 2b260167..9483e1e7 100644
--- 
a/java/tsfile/src/main/java/org/apache/tsfile/read/query/dataset/TreeResultSet.java
+++ 
b/java/tsfile/src/main/java/org/apache/tsfile/read/query/dataset/TreeResultSet.java
@@ -82,7 +82,7 @@ public class TreeResultSet extends AbstractResultSet {
   }
 
   @Override
-  public Iterator<TSRecord> recordIterator() {
+  public Iterator<TSRecord> iterator() {
     return new TreeResultSet.RecordIterator();
   }
 
diff --git 
a/java/tsfile/src/test/java/org/apache/tsfile/read/TsFileTreeReaderTest.java 
b/java/tsfile/src/test/java/org/apache/tsfile/read/TsFileTreeReaderTest.java
index 21d953dc..9df37327 100644
--- a/java/tsfile/src/test/java/org/apache/tsfile/read/TsFileTreeReaderTest.java
+++ b/java/tsfile/src/test/java/org/apache/tsfile/read/TsFileTreeReaderTest.java
@@ -95,7 +95,7 @@ public class TsFileTreeReaderTest {
     long endTime = Long.MAX_VALUE;
 
     ResultSet resultSet = treeReader.query(deviceIds, measurements, startTime, 
endTime);
-    Iterator<TSRecord> recordIterator = resultSet.recordIterator();
+    Iterator<TSRecord> recordIterator = resultSet.iterator();
 
     int countVal = 0;
     while (recordIterator.hasNext()) {
diff --git 
a/java/tsfile/src/test/java/org/apache/tsfile/read/query/ResultSetTest.java 
b/java/tsfile/src/test/java/org/apache/tsfile/read/query/ResultSetTest.java
index b24e6e6d..c453a38e 100644
--- a/java/tsfile/src/test/java/org/apache/tsfile/read/query/ResultSetTest.java
+++ b/java/tsfile/src/test/java/org/apache/tsfile/read/query/ResultSetTest.java
@@ -478,7 +478,7 @@ public class ResultSetTest {
     try (DeviceTableModelReader tsFileReader = new 
DeviceTableModelReader(tsfile);
         ResultSet resultSet =
             tsFileReader.query("T1", Arrays.asList("ID1", "ID2", "S2", "S1"), 
0, 2); ) {
-      Iterator<TSRecord> tsRecordIterator = resultSet.recordIterator();
+      Iterator<TSRecord> tsRecordIterator = resultSet.iterator();
 
       Assert.assertTrue(tsRecordIterator.hasNext());
       TSRecord tsRecord = tsRecordIterator.next();


Reply via email to