| /** | |
| * 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 "catch.hpp" | |
| #include "SessionC.h" | |
| #include <cmath> | |
| #include <cstring> | |
| #include <iostream> | |
| #include <string> | |
| #include <vector> | |
| extern CSession* g_session; | |
| static int global_test_id = 0; | |
| class CaseReporter { | |
| public: | |
| CaseReporter(const char* caseNameArg) : caseName(caseNameArg) { | |
| test_id = global_test_id++; | |
| std::cout << "C-API Test " << test_id << ": " << caseName << std::endl; | |
| } | |
| ~CaseReporter() { | |
| std::cout << "C-API Test " << test_id << ": " << caseName << " Done" << std::endl << std::endl; | |
| } | |
| private: | |
| const char* caseName; | |
| int test_id; | |
| }; | |
| static const char* testTimeseries[] = {"root.ctest.d1.s1", "root.ctest.d1.s2", "root.ctest.d1.s3"}; | |
| static const int testTimeseriesCount = 3; | |
| static void dropTimeseriesIfExists(CSession* session, const char* path) { | |
| bool exists = false; | |
| REQUIRE(ts_session_check_timeseries_exists(session, path, &exists) == TS_OK); | |
| if (exists) { | |
| REQUIRE(ts_session_delete_timeseries(session, path) == TS_OK); | |
| } | |
| } | |
| static void ensureTimeseries(CSession* session, const char* path, TSDataType_C type, | |
| TSEncoding_C encoding, TSCompressionType_C compression) { | |
| dropTimeseriesIfExists(session, path); | |
| REQUIRE(ts_session_create_timeseries(session, path, type, encoding, compression) == TS_OK); | |
| } | |
| static int queryRowCount(CSession* session, const char* sql, int fetchSize = 1024) { | |
| CSessionDataSet* dataSet = nullptr; | |
| REQUIRE(ts_session_execute_query(session, sql, &dataSet) == TS_OK); | |
| REQUIRE(dataSet != nullptr); | |
| ts_dataset_set_fetch_size(dataSet, fetchSize); | |
| int count = 0; | |
| while (ts_dataset_has_next(dataSet)) { | |
| CRowRecord* record = ts_dataset_next(dataSet); | |
| REQUIRE(record != nullptr); | |
| ++count; | |
| ts_row_record_destroy(record); | |
| } | |
| ts_dataset_destroy(dataSet); | |
| return count; | |
| } | |
| static void dropDatabaseIfExists(CSession* session, const char* database) { | |
| TsStatus status = ts_session_delete_database(session, database); | |
| (void)status; | |
| } | |
| static void prepareTimeseries() { | |
| for (int i = 0; i < testTimeseriesCount; i++) { | |
| ensureTimeseries(g_session, testTimeseries[i], TS_TYPE_INT64, TS_ENCODING_RLE, | |
| TS_COMPRESSION_SNAPPY); | |
| } | |
| } | |
| /* ============================================================ | |
| * Timeseries CRUD | |
| * ============================================================ */ | |
| TEST_CASE("C API - Create timeseries", "[c_createTimeseries]") { | |
| CaseReporter cr("c_createTimeseries"); | |
| const char* path = "root.ctest.d1.s1"; | |
| ensureTimeseries(g_session, path, TS_TYPE_INT64, TS_ENCODING_RLE, TS_COMPRESSION_SNAPPY); | |
| bool exists = false; | |
| REQUIRE(ts_session_check_timeseries_exists(g_session, path, &exists) == TS_OK); | |
| REQUIRE(exists); | |
| REQUIRE(ts_session_delete_timeseries(g_session, path) == TS_OK); | |
| } | |
| TEST_CASE("C API - Delete timeseries", "[c_deleteTimeseries]") { | |
| CaseReporter cr("c_deleteTimeseries"); | |
| const char* path = "root.ctest.d1.s1"; | |
| ensureTimeseries(g_session, path, TS_TYPE_INT64, TS_ENCODING_RLE, TS_COMPRESSION_SNAPPY); | |
| REQUIRE(ts_session_delete_timeseries(g_session, path) == TS_OK); | |
| bool exists = true; | |
| REQUIRE(ts_session_check_timeseries_exists(g_session, path, &exists) == TS_OK); | |
| REQUIRE_FALSE(exists); | |
| } | |
| TEST_CASE("C API - Login failure", "[c_Authentication]") { | |
| CaseReporter cr("c_LoginTest"); | |
| CSession* badSession = ts_session_new("127.0.0.1", 6667, "root", "wrong-password"); | |
| REQUIRE(badSession != nullptr); | |
| TsStatus status = ts_session_open(badSession); | |
| REQUIRE(status != TS_OK); | |
| const char* err = ts_get_last_error(); | |
| REQUIRE(std::string(err).find("801") != std::string::npos); | |
| ts_session_destroy(badSession); | |
| } | |
| /* ============================================================ | |
| * Insert Record (string values) | |
| * ============================================================ */ | |
| TEST_CASE("C API - Insert record by string", "[c_insertRecordStr]") { | |
| CaseReporter cr("c_insertRecordStr"); | |
| prepareTimeseries(); | |
| const char* deviceId = "root.ctest.d1"; | |
| const char* measurements[] = {"s1", "s2", "s3"}; | |
| for (int64_t time = 0; time < 100; time++) { | |
| const char* values[] = {"1", "2", "3"}; | |
| TsStatus status = | |
| ts_session_insert_record_str(g_session, deviceId, time, 3, measurements, values); | |
| REQUIRE(status == TS_OK); | |
| } | |
| CSessionDataSet* dataSet = nullptr; | |
| TsStatus status = | |
| ts_session_execute_query(g_session, "select s1,s2,s3 from root.ctest.d1", &dataSet); | |
| REQUIRE(status == TS_OK); | |
| REQUIRE(dataSet != nullptr); | |
| ts_dataset_set_fetch_size(dataSet, 1024); | |
| int count = 0; | |
| while (ts_dataset_has_next(dataSet)) { | |
| CRowRecord* record = ts_dataset_next(dataSet); | |
| REQUIRE(record != nullptr); | |
| REQUIRE(ts_row_record_get_int64(record, 0) == 1); | |
| REQUIRE(ts_row_record_get_int64(record, 1) == 2); | |
| REQUIRE(ts_row_record_get_int64(record, 2) == 3); | |
| ++count; | |
| ts_row_record_destroy(record); | |
| } | |
| REQUIRE(count == 100); | |
| ts_dataset_destroy(dataSet); | |
| } | |
| /* ============================================================ | |
| * Insert Record (typed values) | |
| * ============================================================ */ | |
| TEST_CASE("C API - Insert record with types", "[c_insertRecordTyped]") { | |
| CaseReporter cr("c_insertRecordTyped"); | |
| const char* timeseries[] = {"root.ctest.d1.s1", "root.ctest.d1.s2", "root.ctest.d1.s3"}; | |
| TSDataType_C types[] = {TS_TYPE_INT32, TS_TYPE_DOUBLE, TS_TYPE_INT64}; | |
| TSEncoding_C encodings[] = {TS_ENCODING_RLE, TS_ENCODING_RLE, TS_ENCODING_RLE}; | |
| TSCompressionType_C compressions[] = {TS_COMPRESSION_SNAPPY, TS_COMPRESSION_SNAPPY, | |
| TS_COMPRESSION_SNAPPY}; | |
| for (int i = 0; i < 3; i++) { | |
| ensureTimeseries(g_session, timeseries[i], types[i], encodings[i], compressions[i]); | |
| } | |
| const char* deviceId = "root.ctest.d1"; | |
| const char* measurements[] = {"s1", "s2", "s3"}; | |
| for (int64_t time = 0; time < 100; time++) { | |
| int32_t v1 = 1; | |
| double v2 = 2.2; | |
| int64_t v3 = 3; | |
| const void* values[] = {&v1, &v2, &v3}; | |
| TsStatus status = | |
| ts_session_insert_record(g_session, deviceId, time, 3, measurements, types, values); | |
| REQUIRE(status == TS_OK); | |
| } | |
| REQUIRE(queryRowCount(g_session, "select s1,s2,s3 from root.ctest.d1") == 100); | |
| } | |
| /* ============================================================ | |
| * Insert Records (batch, string values) | |
| * ============================================================ */ | |
| TEST_CASE("C API - Insert records batch", "[c_insertRecordsBatch]") { | |
| CaseReporter cr("c_insertRecordsBatch"); | |
| prepareTimeseries(); | |
| const int BATCH = 100; | |
| const char* deviceId = "root.ctest.d1"; | |
| const char* measurements[] = {"s1", "s2", "s3"}; | |
| const char* deviceIds[BATCH]; | |
| int64_t times[BATCH]; | |
| int measurementCounts[BATCH]; | |
| const char* const* measurementsList[BATCH]; | |
| const char* values[] = {"1", "2", "3"}; | |
| const char* const* valuesList[BATCH]; | |
| for (int i = 0; i < BATCH; i++) { | |
| deviceIds[i] = deviceId; | |
| times[i] = i; | |
| measurementCounts[i] = 3; | |
| measurementsList[i] = measurements; | |
| valuesList[i] = values; | |
| } | |
| TsStatus status = ts_session_insert_records_str(g_session, BATCH, deviceIds, times, | |
| measurementCounts, measurementsList, valuesList); | |
| REQUIRE(status == TS_OK); | |
| REQUIRE(queryRowCount(g_session, "select s1,s2,s3 from root.ctest.d1") == BATCH); | |
| } | |
| /* ============================================================ | |
| * Insert Tablet | |
| * ============================================================ */ | |
| TEST_CASE("C API - Insert tablet", "[c_insertTablet]") { | |
| CaseReporter cr("c_insertTablet"); | |
| prepareTimeseries(); | |
| const char* columnNames[] = {"s1", "s2", "s3"}; | |
| TSDataType_C dataTypes[] = {TS_TYPE_INT64, TS_TYPE_INT64, TS_TYPE_INT64}; | |
| CTablet* tablet = ts_tablet_new("root.ctest.d1", 3, columnNames, dataTypes, 100); | |
| REQUIRE(tablet != nullptr); | |
| for (int64_t time = 0; time < 100; time++) { | |
| ts_tablet_add_timestamp(tablet, (int)time, time); | |
| for (int col = 0; col < 3; col++) { | |
| int64_t val = col; | |
| ts_tablet_add_value_int64(tablet, col, (int)time, val); | |
| } | |
| } | |
| ts_tablet_set_row_count(tablet, 100); | |
| TsStatus status = ts_session_insert_tablet(g_session, tablet, false); | |
| REQUIRE(status == TS_OK); | |
| CSessionDataSet* dataSet = nullptr; | |
| REQUIRE(ts_session_execute_query(g_session, "select s1,s2,s3 from root.ctest.d1", &dataSet) == | |
| TS_OK); | |
| REQUIRE(dataSet != nullptr); | |
| ts_dataset_set_fetch_size(dataSet, 1024); | |
| int count = 0; | |
| while (ts_dataset_has_next(dataSet)) { | |
| CRowRecord* record = ts_dataset_next(dataSet); | |
| REQUIRE(ts_row_record_get_int64(record, 0) == 0); | |
| REQUIRE(ts_row_record_get_int64(record, 1) == 1); | |
| REQUIRE(ts_row_record_get_int64(record, 2) == 2); | |
| ++count; | |
| ts_row_record_destroy(record); | |
| } | |
| REQUIRE(count == 100); | |
| ts_dataset_destroy(dataSet); | |
| ts_tablet_destroy(tablet); | |
| } | |
| /* ============================================================ | |
| * Execute SQL directly | |
| * ============================================================ */ | |
| TEST_CASE("C API - Execute non-query SQL", "[c_executeNonQuery]") { | |
| CaseReporter cr("c_executeNonQuery"); | |
| prepareTimeseries(); | |
| TsStatus status = ts_session_execute_non_query( | |
| g_session, "insert into root.ctest.d1(timestamp,s1,s2,s3) values(200,10,20,30)"); | |
| REQUIRE(status == TS_OK); | |
| CSessionDataSet* dataSet = nullptr; | |
| ts_session_execute_query(g_session, "select s1 from root.ctest.d1 where time=200", &dataSet); | |
| REQUIRE(ts_dataset_has_next(dataSet)); | |
| CRowRecord* record = ts_dataset_next(dataSet); | |
| REQUIRE(ts_row_record_get_int64(record, 0) == 10); | |
| ts_row_record_destroy(record); | |
| ts_dataset_destroy(dataSet); | |
| } | |
| /* ============================================================ | |
| * Raw data query | |
| * ============================================================ */ | |
| TEST_CASE("C API - Execute raw data query", "[c_executeRawDataQuery]") { | |
| CaseReporter cr("c_executeRawDataQuery"); | |
| prepareTimeseries(); | |
| const char* deviceId = "root.ctest.d1"; | |
| const char* measurements[] = {"s1", "s2", "s3"}; | |
| for (int64_t time = 0; time < 50; time++) { | |
| const char* values[] = {"1", "2", "3"}; | |
| ts_session_insert_record_str(g_session, deviceId, time, 3, measurements, values); | |
| } | |
| const char* paths[] = {"root.ctest.d1.s1", "root.ctest.d1.s2", "root.ctest.d1.s3"}; | |
| CSessionDataSet* dataSet = nullptr; | |
| TsStatus status = ts_session_execute_raw_data_query(g_session, 3, paths, 0, 50, &dataSet); | |
| REQUIRE(status == TS_OK); | |
| int count = 0; | |
| while (ts_dataset_has_next(dataSet)) { | |
| CRowRecord* record = ts_dataset_next(dataSet); | |
| count++; | |
| ts_row_record_destroy(record); | |
| } | |
| REQUIRE(count == 50); | |
| ts_dataset_destroy(dataSet); | |
| } | |
| /* ============================================================ | |
| * Data deletion | |
| * ============================================================ */ | |
| TEST_CASE("C API - Delete data", "[c_deleteData]") { | |
| CaseReporter cr("c_deleteData"); | |
| prepareTimeseries(); | |
| const char* deviceId = "root.ctest.d1"; | |
| const char* measurements[] = {"s1", "s2", "s3"}; | |
| for (int64_t time = 0; time < 100; time++) { | |
| const char* values[] = {"1", "2", "3"}; | |
| ts_session_insert_record_str(g_session, deviceId, time, 3, measurements, values); | |
| } | |
| const char* paths[] = {"root.ctest.d1.s1", "root.ctest.d1.s2", "root.ctest.d1.s3"}; | |
| TsStatus status = ts_session_delete_data_batch(g_session, 3, paths, 49); | |
| REQUIRE(status == TS_OK); | |
| REQUIRE(queryRowCount(g_session, "select s1,s2,s3 from root.ctest.d1") == 50); | |
| } | |
| /* ============================================================ | |
| * Timezone | |
| * ============================================================ */ | |
| TEST_CASE("C API - Timezone", "[c_timezone]") { | |
| CaseReporter cr("c_timezone"); | |
| char buf[64] = {0}; | |
| TsStatus status = ts_session_get_timezone(g_session, buf, sizeof(buf)); | |
| REQUIRE(status == TS_OK); | |
| REQUIRE(strlen(buf) > 0); | |
| status = ts_session_set_timezone(g_session, "Asia/Shanghai"); | |
| REQUIRE(status == TS_OK); | |
| memset(buf, 0, sizeof(buf)); | |
| ts_session_get_timezone(g_session, buf, sizeof(buf)); | |
| REQUIRE(std::string(buf) == "Asia/Shanghai"); | |
| } | |
| /* ============================================================ | |
| * Multi-node constructor | |
| * ============================================================ */ | |
| TEST_CASE("C API - Multi-node session", "[c_multiNode]") { | |
| CaseReporter cr("c_multiNode"); | |
| const char* urls[] = {"127.0.0.1:6667"}; | |
| CSession* localSession = ts_session_new_multi_node(urls, 1, "root", "root"); | |
| REQUIRE(localSession != nullptr); | |
| TsStatus status = ts_session_open(localSession); | |
| REQUIRE(status == TS_OK); | |
| const char* path = "root.ctest.d1.s1"; | |
| ensureTimeseries(localSession, path, TS_TYPE_INT64, TS_ENCODING_RLE, TS_COMPRESSION_SNAPPY); | |
| bool exists = false; | |
| REQUIRE(ts_session_check_timeseries_exists(localSession, path, &exists) == TS_OK); | |
| REQUIRE(exists); | |
| REQUIRE(ts_session_delete_timeseries(localSession, path) == TS_OK); | |
| ts_session_close(localSession); | |
| ts_session_destroy(localSession); | |
| } | |
| /* ============================================================ | |
| * Dataset column info | |
| * ============================================================ */ | |
| TEST_CASE("C API - Dataset column info", "[c_datasetColumns]") { | |
| CaseReporter cr("c_datasetColumns"); | |
| prepareTimeseries(); | |
| const char* deviceId = "root.ctest.d1"; | |
| const char* measurements[] = {"s1", "s2", "s3"}; | |
| const char* values[] = {"1", "2", "3"}; | |
| ts_session_insert_record_str(g_session, deviceId, 0, 3, measurements, values); | |
| CSessionDataSet* dataSet = nullptr; | |
| ts_session_execute_query(g_session, "select s1,s2,s3 from root.ctest.d1", &dataSet); | |
| REQUIRE(dataSet != nullptr); | |
| int colCount = ts_dataset_get_column_count(dataSet); | |
| REQUIRE(colCount == 4); // Time + s1 + s2 + s3 | |
| const char* col0 = ts_dataset_get_column_name(dataSet, 0); | |
| REQUIRE(std::string(col0) == "Time"); | |
| int n = ts_dataset_get_column_count(dataSet); | |
| for (int i = 0; i < n; i++) { | |
| const char* ct = ts_dataset_get_column_type(dataSet, i); | |
| REQUIRE(ct != nullptr); | |
| REQUIRE(strlen(ct) > 0); | |
| } | |
| ts_dataset_destroy(dataSet); | |
| } | |
| /* ============================================================ | |
| * SessionC.h API coverage (tree model) — additional smoke tests | |
| * ============================================================ */ | |
| TEST_CASE("C API - Session lifecycle variants", "[c_sessionLifecycle]") { | |
| CaseReporter cr("c_sessionLifecycle"); | |
| CSession* s1 = ts_session_new_with_zone("127.0.0.1", 6667, "root", "root", "Asia/Shanghai", 1024); | |
| REQUIRE(s1 != nullptr); | |
| REQUIRE(ts_session_open(s1) == TS_OK); | |
| ts_session_close(s1); | |
| ts_session_destroy(s1); | |
| CSession* s2 = ts_session_new("127.0.0.1", 6667, "root", "root"); | |
| REQUIRE(s2 != nullptr); | |
| REQUIRE(ts_session_open_with_compression(s2, true) == TS_OK); | |
| ts_session_close(s2); | |
| ts_session_destroy(s2); | |
| } | |
| TEST_CASE("C API - Database and extended timeseries APIs", "[c_dbTimeseries]") { | |
| CaseReporter cr("c_dbTimeseries"); | |
| const char* sg1 = "root.cov_sg_a"; | |
| const char* sg2 = "root.cov_sg_b"; | |
| dropDatabaseIfExists(g_session, sg1); | |
| dropDatabaseIfExists(g_session, sg2); | |
| REQUIRE(ts_session_create_database(g_session, sg1) == TS_OK); | |
| REQUIRE(ts_session_create_database(g_session, sg2) == TS_OK); | |
| const char* dbs[] = {sg1, sg2}; | |
| REQUIRE(ts_session_delete_databases(g_session, dbs, 2) == TS_OK); | |
| const char* sg3 = "root.cov_sg_c"; | |
| dropDatabaseIfExists(g_session, sg3); | |
| REQUIRE(ts_session_create_database(g_session, sg3) == TS_OK); | |
| REQUIRE(ts_session_delete_database(g_session, sg3) == TS_OK); | |
| const char* sgEx = "root.cov_sg_ex"; | |
| dropDatabaseIfExists(g_session, sgEx); | |
| REQUIRE(ts_session_create_database(g_session, sgEx) == TS_OK); | |
| const char* pathEx = "root.cov_sg_ex.d1.s_ex"; | |
| dropTimeseriesIfExists(g_session, pathEx); | |
| REQUIRE(ts_session_create_timeseries_ex(g_session, pathEx, TS_TYPE_INT64, TS_ENCODING_RLE, | |
| TS_COMPRESSION_SNAPPY, 0, nullptr, nullptr, 0, nullptr, | |
| nullptr, 0, nullptr, nullptr, nullptr) == TS_OK); | |
| const char* pathsM[] = {"root.cov_sg_ex.d1.s_m1", "root.cov_sg_ex.d1.s_m2"}; | |
| TSDataType_C tsM[] = {TS_TYPE_INT64, TS_TYPE_DOUBLE}; | |
| TSEncoding_C encM[] = {TS_ENCODING_RLE, TS_ENCODING_RLE}; | |
| TSCompressionType_C compM[] = {TS_COMPRESSION_SNAPPY, TS_COMPRESSION_SNAPPY}; | |
| for (int i = 0; i < 2; i++) { | |
| dropTimeseriesIfExists(g_session, pathsM[i]); | |
| } | |
| REQUIRE(ts_session_create_multi_timeseries(g_session, 2, pathsM, tsM, encM, compM) == TS_OK); | |
| REQUIRE(ts_session_delete_timeseries_batch(g_session, pathsM, 2) == TS_OK); | |
| REQUIRE(ts_session_delete_timeseries(g_session, pathEx) == TS_OK); | |
| REQUIRE(ts_session_delete_database(g_session, sgEx) == TS_OK); | |
| } | |
| TEST_CASE("C API - Tablet row count and reset", "[c_tabletReset]") { | |
| CaseReporter cr("c_tabletReset"); | |
| const char* colNames[] = {"s1"}; | |
| TSDataType_C dts[] = {TS_TYPE_INT64}; | |
| CTablet* tablet = ts_tablet_new("root.ctest.d1", 1, colNames, dts, 10); | |
| REQUIRE(tablet != nullptr); | |
| REQUIRE(ts_tablet_get_row_count(tablet) == 0); | |
| REQUIRE(ts_tablet_set_row_count(tablet, 1) == TS_OK); | |
| REQUIRE(ts_tablet_get_row_count(tablet) == 1); | |
| ts_tablet_reset(tablet); | |
| REQUIRE(ts_tablet_get_row_count(tablet) == 0); | |
| ts_tablet_destroy(tablet); | |
| } | |
| TEST_CASE("C API - Tablet index bounds", "[c_tabletBounds]") { | |
| CaseReporter cr("c_tabletBounds"); | |
| const char* colNames[] = {"s1"}; | |
| TSDataType_C dts[] = {TS_TYPE_INT64}; | |
| CTablet* tablet = ts_tablet_new("root.ctest.d1", 1, colNames, dts, 10); | |
| REQUIRE(tablet != nullptr); | |
| REQUIRE(ts_tablet_add_timestamp(tablet, 0, 1) == TS_OK); | |
| REQUIRE(ts_tablet_add_value_int64(tablet, 0, 0, 100) == TS_OK); | |
| REQUIRE(ts_tablet_add_timestamp(tablet, 100, 2) != TS_OK); | |
| REQUIRE(ts_tablet_add_value_int64(tablet, 1, 0, 100) != TS_OK); | |
| REQUIRE(ts_tablet_add_value_int64(tablet, 0, 100, 100) != TS_OK); | |
| REQUIRE(ts_tablet_set_row_count(tablet, -1) != TS_OK); | |
| REQUIRE(ts_tablet_set_row_count(tablet, 11) != TS_OK); | |
| REQUIRE(ts_tablet_set_row_count(tablet, 1000000) != TS_OK); | |
| REQUIRE(ts_tablet_set_row_count(tablet, 5) == TS_OK); | |
| REQUIRE(ts_tablet_get_row_count(tablet) == 5); | |
| ts_tablet_destroy(tablet); | |
| } | |
| TEST_CASE("C API - Aligned timeseries and aligned writes", "[c_aligned]") { | |
| CaseReporter cr("c_aligned"); | |
| const char* sg = "root.cov_al"; | |
| dropDatabaseIfExists(g_session, sg); | |
| REQUIRE(ts_session_create_database(g_session, sg) == TS_OK); | |
| const char* alDev = "root.cov_al.dev"; | |
| const char* meas[] = {"m1", "m2"}; | |
| TSDataType_C adt[] = {TS_TYPE_INT64, TS_TYPE_INT64}; | |
| TSEncoding_C aenc[] = {TS_ENCODING_RLE, TS_ENCODING_RLE}; | |
| TSCompressionType_C acomp[] = {TS_COMPRESSION_SNAPPY, TS_COMPRESSION_SNAPPY}; | |
| REQUIRE(ts_session_create_aligned_timeseries(g_session, alDev, 2, meas, adt, aenc, acomp) == | |
| TS_OK); | |
| const char* mstr[] = {"m1", "m2"}; | |
| const char* vstr[] = {"1", "2"}; | |
| REQUIRE(ts_session_insert_aligned_record_str(g_session, alDev, 100LL, 2, mstr, vstr) == TS_OK); | |
| int64_t v1 = 3; | |
| int64_t v2 = 4; | |
| const void* vals[] = {&v1, &v2}; | |
| REQUIRE(ts_session_insert_aligned_record(g_session, alDev, 101LL, 2, mstr, adt, vals) == TS_OK); | |
| const char* devs1[] = {alDev}; | |
| int64_t times1[] = {102LL}; | |
| int mc1[] = {2}; | |
| const char* const* mlist1[] = {mstr}; | |
| const char* const* vlist1[] = {vstr}; | |
| REQUIRE(ts_session_insert_aligned_records_str(g_session, 1, devs1, times1, mc1, mlist1, vlist1) == | |
| TS_OK); | |
| const TSDataType_C* trows[] = {adt}; | |
| const void* const* vrows[] = {vals}; | |
| REQUIRE(ts_session_insert_aligned_records(g_session, 1, devs1, times1, mc1, mlist1, trows, | |
| vrows) == TS_OK); | |
| int64_t tRows[] = {104LL, 105LL}; | |
| int mcRows[] = {2, 2}; | |
| const char* const* mRows[] = {mstr, mstr}; | |
| const TSDataType_C* tRowsList[] = {adt, adt}; | |
| int64_t v1a = 5, v1b = 6; | |
| int64_t v2a = 7, v2b = 8; | |
| const void* row0[] = {&v1a, &v2a}; | |
| const void* row1[] = {&v1b, &v2b}; | |
| const void* const* vRowsList[] = {row0, row1}; | |
| REQUIRE(ts_session_insert_aligned_records_of_one_device(g_session, alDev, 2, tRows, mcRows, mRows, | |
| tRowsList, vRowsList, true) == TS_OK); | |
| const char* alDev2 = "root.cov_al.dev2"; | |
| REQUIRE(ts_session_create_aligned_timeseries(g_session, alDev2, 2, meas, adt, aenc, acomp) == | |
| TS_OK); | |
| CTablet* tab = ts_tablet_new(alDev, 2, meas, adt, 10); | |
| CTablet* tab2 = ts_tablet_new(alDev2, 2, meas, adt, 5); | |
| REQUIRE(tab != nullptr); | |
| REQUIRE(tab2 != nullptr); | |
| ts_tablet_add_timestamp(tab, 0, 106LL); | |
| ts_tablet_add_value_int64(tab, 0, 0, 9); | |
| ts_tablet_add_value_int64(tab, 1, 0, 10); | |
| ts_tablet_set_row_count(tab, 1); | |
| ts_tablet_add_timestamp(tab2, 0, 107LL); | |
| ts_tablet_add_value_int64(tab2, 0, 0, 11); | |
| ts_tablet_add_value_int64(tab2, 1, 0, 12); | |
| ts_tablet_set_row_count(tab2, 1); | |
| const char* devIds[] = {alDev, alDev2}; | |
| CTablet* tabs[] = {tab, tab2}; | |
| REQUIRE(ts_session_insert_aligned_tablets(g_session, 2, devIds, tabs, false) == TS_OK); | |
| ts_tablet_reset(tab); | |
| ts_tablet_add_timestamp(tab, 0, 200LL); | |
| ts_tablet_add_value_int64(tab, 0, 0, 13); | |
| ts_tablet_add_value_int64(tab, 1, 0, 14); | |
| ts_tablet_set_row_count(tab, 1); | |
| REQUIRE(ts_session_insert_aligned_tablet(g_session, tab, false) == TS_OK); | |
| ts_tablet_destroy(tab2); | |
| ts_tablet_destroy(tab); | |
| REQUIRE(ts_session_delete_database(g_session, sg) == TS_OK); | |
| } | |
| TEST_CASE("C API - Typed batch inserts and insert_tablets", "[c_batchTablet]") { | |
| CaseReporter cr("c_batchTablet"); | |
| const char* sg = "root.cov_batch"; | |
| dropDatabaseIfExists(g_session, sg); | |
| REQUIRE(ts_session_create_database(g_session, sg) == TS_OK); | |
| const char* p1 = "root.cov_batch.da.s1"; | |
| const char* p2 = "root.cov_batch.db.s1"; | |
| ensureTimeseries(g_session, p1, TS_TYPE_INT64, TS_ENCODING_RLE, TS_COMPRESSION_SNAPPY); | |
| ensureTimeseries(g_session, p2, TS_TYPE_INT64, TS_ENCODING_RLE, TS_COMPRESSION_SNAPPY); | |
| const char* devIds[] = {"root.cov_batch.da", "root.cov_batch.db"}; | |
| int64_t tt[] = {1LL, 2LL}; | |
| int mmc[] = {1, 1}; | |
| const char* mda[] = {"s1"}; | |
| const char* mdb[] = {"s1"}; | |
| const char* const* mlist[] = {mda, mdb}; | |
| int64_t va = 11; | |
| int64_t vb = 22; | |
| const void* vva[] = {&va}; | |
| const void* vvb[] = {&vb}; | |
| const void* const* vlist[] = {vva, vvb}; | |
| TSDataType_C ta[] = {TS_TYPE_INT64}; | |
| TSDataType_C tb[] = {TS_TYPE_INT64}; | |
| const TSDataType_C* tlist[] = {ta, tb}; | |
| REQUIRE(ts_session_insert_records(g_session, 2, devIds, tt, mmc, mlist, tlist, vlist) == TS_OK); | |
| const char* dc = "root.cov_batch.dc"; | |
| const char* p3 = "root.cov_batch.dc.s1"; | |
| ensureTimeseries(g_session, p3, TS_TYPE_INT64, TS_ENCODING_RLE, TS_COMPRESSION_SNAPPY); | |
| int64_t tdc[] = {3LL, 4LL}; | |
| int mcdc[] = {1, 1}; | |
| const char* const* mdcList[] = {mda, mda}; | |
| const TSDataType_C* tdcList[] = {ta, ta}; | |
| int64_t vc = 30, vd = 40; | |
| const void* rv0[] = {&vc}; | |
| const void* rv1[] = {&vd}; | |
| const void* const* vdcList[] = {rv0, rv1}; | |
| REQUIRE(ts_session_insert_records_of_one_device(g_session, dc, 2, tdc, mcdc, mdcList, tdcList, | |
| vdcList, true) == TS_OK); | |
| const char* col1[] = {"s1"}; | |
| TSDataType_C dt1[] = {TS_TYPE_INT64}; | |
| CTablet* tb1 = ts_tablet_new("root.cov_batch.ta", 1, col1, dt1, 5); | |
| CTablet* tb2 = ts_tablet_new("root.cov_batch.tb", 1, col1, dt1, 5); | |
| REQUIRE(tb1 != nullptr); | |
| REQUIRE(tb2 != nullptr); | |
| const char* pta = "root.cov_batch.ta.s1"; | |
| const char* ptb = "root.cov_batch.tb.s1"; | |
| ensureTimeseries(g_session, pta, TS_TYPE_INT64, TS_ENCODING_RLE, TS_COMPRESSION_SNAPPY); | |
| ensureTimeseries(g_session, ptb, TS_TYPE_INT64, TS_ENCODING_RLE, TS_COMPRESSION_SNAPPY); | |
| ts_tablet_add_timestamp(tb1, 0, 1LL); | |
| ts_tablet_add_value_int64(tb1, 0, 0, 100); | |
| ts_tablet_set_row_count(tb1, 1); | |
| ts_tablet_add_timestamp(tb2, 0, 2LL); | |
| ts_tablet_add_value_int64(tb2, 0, 0, 200); | |
| ts_tablet_set_row_count(tb2, 1); | |
| const char* tabDevs[] = {"root.cov_batch.ta", "root.cov_batch.tb"}; | |
| CTablet* tbs[] = {tb1, tb2}; | |
| REQUIRE(ts_session_insert_tablets(g_session, 2, tabDevs, tbs, false) == TS_OK); | |
| ts_tablet_destroy(tb2); | |
| ts_tablet_destroy(tb1); | |
| REQUIRE(ts_session_delete_database(g_session, sg) == TS_OK); | |
| } | |
| TEST_CASE("C API - Query timeout and last data queries", "[c_queryLast]") { | |
| CaseReporter cr("c_queryLast"); | |
| prepareTimeseries(); | |
| const char* deviceId = "root.ctest.d1"; | |
| const char* measurements[] = {"s1", "s2", "s3"}; | |
| for (int64_t time = 300; time < 310; time++) { | |
| const char* values[] = {"7", "8", "9"}; | |
| REQUIRE(ts_session_insert_record_str(g_session, deviceId, time, 3, measurements, values) == | |
| TS_OK); | |
| } | |
| const char* paths[] = {"root.ctest.d1.s1", "root.ctest.d1.s2"}; | |
| CSessionDataSet* ds = nullptr; | |
| REQUIRE(ts_session_execute_query_with_timeout( | |
| g_session, "select s1 from root.ctest.d1 where time>=300", 60000, &ds) == TS_OK); | |
| REQUIRE(ds != nullptr); | |
| ts_dataset_destroy(ds); | |
| ds = nullptr; | |
| REQUIRE(ts_session_execute_last_data_query(g_session, 2, paths, &ds) == TS_OK); | |
| REQUIRE(ds != nullptr); | |
| ts_dataset_destroy(ds); | |
| ds = nullptr; | |
| REQUIRE(ts_session_execute_last_data_query_with_time(g_session, 2, paths, 305LL, &ds) == TS_OK); | |
| REQUIRE(ds != nullptr); | |
| ts_dataset_destroy(ds); | |
| } | |
| TEST_CASE("C API - RowRecord and delete data APIs", "[c_rowDelete]") { | |
| CaseReporter cr("c_rowDelete"); | |
| const char* sg = "root.cov_types"; | |
| dropDatabaseIfExists(g_session, sg); | |
| REQUIRE(ts_session_create_database(g_session, sg) == TS_OK); | |
| const char* pb = "root.cov_types.d1.sb"; | |
| const char* pi = "root.cov_types.d1.si"; | |
| const char* pf = "root.cov_types.d1.sf"; | |
| const char* pd = "root.cov_types.d1.sd"; | |
| const char* pt = "root.cov_types.d1.st"; | |
| const char* pstr = "root.cov_types.d1.sstr"; | |
| const char* pdate = "root.cov_types.d1.sdate"; | |
| const char* pblob = "root.cov_types.d1.sblob"; | |
| const char* tpaths[] = {pb, pi, pf, pd, pt, pstr, pdate, pblob}; | |
| for (const char* tp : tpaths) { | |
| dropTimeseriesIfExists(g_session, tp); | |
| } | |
| REQUIRE(ts_session_create_timeseries(g_session, pb, TS_TYPE_BOOLEAN, TS_ENCODING_RLE, | |
| TS_COMPRESSION_SNAPPY) == TS_OK); | |
| REQUIRE(ts_session_create_timeseries(g_session, pi, TS_TYPE_INT32, TS_ENCODING_RLE, | |
| TS_COMPRESSION_SNAPPY) == TS_OK); | |
| REQUIRE(ts_session_create_timeseries(g_session, pf, TS_TYPE_FLOAT, TS_ENCODING_RLE, | |
| TS_COMPRESSION_SNAPPY) == TS_OK); | |
| REQUIRE(ts_session_create_timeseries(g_session, pd, TS_TYPE_DOUBLE, TS_ENCODING_RLE, | |
| TS_COMPRESSION_SNAPPY) == TS_OK); | |
| REQUIRE(ts_session_create_timeseries(g_session, pt, TS_TYPE_TEXT, TS_ENCODING_PLAIN, | |
| TS_COMPRESSION_SNAPPY) == TS_OK); | |
| REQUIRE(ts_session_create_timeseries(g_session, pstr, TS_TYPE_STRING, TS_ENCODING_PLAIN, | |
| TS_COMPRESSION_SNAPPY) == TS_OK); | |
| REQUIRE(ts_session_create_timeseries(g_session, pdate, TS_TYPE_DATE, TS_ENCODING_PLAIN, | |
| TS_COMPRESSION_SNAPPY) == TS_OK); | |
| REQUIRE(ts_session_create_timeseries(g_session, pblob, TS_TYPE_BLOB, TS_ENCODING_PLAIN, | |
| TS_COMPRESSION_SNAPPY) == TS_OK); | |
| const char* dev = "root.cov_types.d1"; | |
| const char* names[] = {"sb", "si", "sf", "sd", "st", "sstr", "sdate", "sblob"}; | |
| TSDataType_C types[] = {TS_TYPE_BOOLEAN, TS_TYPE_INT32, TS_TYPE_FLOAT, TS_TYPE_DOUBLE, | |
| TS_TYPE_TEXT, TS_TYPE_STRING, TS_TYPE_DATE, TS_TYPE_BLOB}; | |
| bool bv = true; | |
| int32_t iv = 42; | |
| float fv = 2.5f; | |
| double dv = 3.25; | |
| const char* tv = "hi"; | |
| const char* strVal = "hello"; | |
| TSDate_C dateVal = {2025, 5, 7}; | |
| const char* blobVal = "blobXY"; | |
| const void* vals[] = {&bv, &iv, &fv, &dv, tv, strVal, &dateVal, blobVal}; | |
| REQUIRE(ts_session_insert_record(g_session, dev, 500LL, 8, names, types, vals) == TS_OK); | |
| CSessionDataSet* dataSet = nullptr; | |
| REQUIRE(ts_session_execute_query(g_session, | |
| "select sb,si,sf,sd,st,sstr,sdate,sblob from root.cov_types.d1 " | |
| "where time=500", | |
| &dataSet) == TS_OK); | |
| REQUIRE(dataSet != nullptr); | |
| REQUIRE(ts_dataset_has_next(dataSet)); | |
| CRowRecord* record = ts_dataset_next(dataSet); | |
| REQUIRE(record != nullptr); | |
| REQUIRE(ts_row_record_get_timestamp(record) == 500LL); | |
| REQUIRE(ts_row_record_get_field_count(record) == 8); | |
| REQUIRE_FALSE(ts_row_record_is_null(record, 0)); | |
| REQUIRE(ts_row_record_get_bool(record, 0) == true); | |
| REQUIRE(ts_row_record_get_int32(record, 1) == 42); | |
| REQUIRE(std::fabs(ts_row_record_get_float(record, 2) - 2.5f) < 1e-4f); | |
| REQUIRE(std::fabs(ts_row_record_get_double(record, 3) - 3.25) < 1e-9); | |
| REQUIRE(ts_row_record_get_data_type(record, 4) == TS_TYPE_TEXT); | |
| REQUIRE(std::string(ts_row_record_get_string(record, 4)) == "hi"); | |
| REQUIRE(ts_row_record_get_string_byte_length(record, 4) == 2); | |
| REQUIRE(ts_row_record_get_data_type(record, 5) == TS_TYPE_STRING); | |
| REQUIRE(std::string(ts_row_record_get_string(record, 5)) == "hello"); | |
| REQUIRE(ts_row_record_get_string_byte_length(record, 5) == 5); | |
| REQUIRE(ts_row_record_get_date_int32(record, 6) == 20250507); | |
| REQUIRE(std::string(ts_row_record_get_string(record, 7)) == "blobXY"); | |
| REQUIRE(ts_row_record_get_string_byte_length(record, 7) == 6); | |
| for (int i = 0; i < 8; i++) { | |
| REQUIRE(ts_row_record_get_data_type(record, i) == types[i]); | |
| } | |
| ts_row_record_destroy(record); | |
| ts_dataset_destroy(dataSet); | |
| REQUIRE(ts_session_delete_data(g_session, pb, 500LL) == TS_OK); | |
| const char* delPaths[] = {pi, pf}; | |
| REQUIRE(ts_session_delete_data_range(g_session, 2, delPaths, 400LL, 600LL) == TS_OK); | |
| REQUIRE(ts_session_delete_timeseries(g_session, pb) == TS_OK); | |
| REQUIRE(ts_session_delete_timeseries(g_session, pi) == TS_OK); | |
| REQUIRE(ts_session_delete_timeseries(g_session, pf) == TS_OK); | |
| REQUIRE(ts_session_delete_timeseries(g_session, pd) == TS_OK); | |
| REQUIRE(ts_session_delete_timeseries(g_session, pt) == TS_OK); | |
| REQUIRE(ts_session_delete_timeseries(g_session, pstr) == TS_OK); | |
| REQUIRE(ts_session_delete_timeseries(g_session, pdate) == TS_OK); | |
| REQUIRE(ts_session_delete_timeseries(g_session, pblob) == TS_OK); | |
| REQUIRE(ts_session_delete_database(g_session, sg) == TS_OK); | |
| } | |
| TEST_CASE("C API - Query DATE 1000-01-01", "[c_dateMinYear]") { | |
| CaseReporter cr("c_dateMinYear"); | |
| const char* path = "root.ctest.d1.s_date_min"; | |
| ensureTimeseries(g_session, path, TS_TYPE_DATE, TS_ENCODING_PLAIN, TS_COMPRESSION_SNAPPY); | |
| const char* deviceId = "root.ctest.d1"; | |
| const char* measurements[] = {"s_date_min"}; | |
| TSDataType_C types[] = {TS_TYPE_DATE}; | |
| TSDate_C dateVal = {1000, 1, 1}; | |
| const void* vals[] = {&dateVal}; | |
| REQUIRE(ts_session_insert_record(g_session, deviceId, 1000LL, 1, measurements, types, vals) == | |
| TS_OK); | |
| CSessionDataSet* dataSet = nullptr; | |
| REQUIRE(ts_session_execute_query(g_session, | |
| "select s_date_min from root.ctest.d1 where time=1000", | |
| &dataSet) == TS_OK); | |
| REQUIRE(dataSet != nullptr); | |
| REQUIRE(ts_dataset_has_next(dataSet)); | |
| CRowRecord* record = ts_dataset_next(dataSet); | |
| REQUIRE(record != nullptr); | |
| REQUIRE_FALSE(ts_row_record_is_null(record, 0)); | |
| REQUIRE(ts_row_record_get_date_int32(record, 0) == 10000101); | |
| ts_row_record_destroy(record); | |
| ts_dataset_destroy(dataSet); | |
| REQUIRE(ts_session_delete_timeseries(g_session, path) == TS_OK); | |
| } |