diff --git a/iotdb-client/client-cpp/pom.xml b/iotdb-client/client-cpp/pom.xml index 7f43fd77440e0..88b70cebdf108 100644 --- a/iotdb-client/client-cpp/pom.xml +++ b/iotdb-client/client-cpp/pom.xml @@ -273,6 +273,7 @@ integration-test + ${cmake.build.type} ${project.build.directory}/build/test ${maven.test.skip} @@ -314,8 +315,8 @@ ${ctest.skip.tests} iotdb-server false - - 15 + + 45 ${project.build.directory}/build/test/test.log diff --git a/iotdb-client/client-cpp/src/main/CMakeLists.txt b/iotdb-client/client-cpp/src/main/CMakeLists.txt index 2a6173514d7fe..8cfed4e7db912 100644 --- a/iotdb-client/client-cpp/src/main/CMakeLists.txt +++ b/iotdb-client/client-cpp/src/main/CMakeLists.txt @@ -21,7 +21,10 @@ PROJECT(iotdb_session CXX) SET(CMAKE_CXX_STANDARD 11) SET(CMAKE_CXX_STANDARD_REQUIRED ON) SET(CMAKE_POSITION_INDEPENDENT_CODE ON) -SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -g -O2 ") +IF(NOT MSVC) + # Keep GCC/Clang style flags off on MSVC to avoid invalid-option build errors. + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -g -O2") +ENDIF() # Add Thrift include directory INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/../../thrift/include) diff --git a/iotdb-client/client-cpp/src/main/SessionC.cpp b/iotdb-client/client-cpp/src/main/SessionC.cpp new file mode 100644 index 0000000000000..1a26718354455 --- /dev/null +++ b/iotdb-client/client-cpp/src/main/SessionC.cpp @@ -0,0 +1,1421 @@ +/** + * 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 "SessionC.h" +#include "Session.h" +#include "TableSession.h" +#include "TableSessionBuilder.h" +#include "SessionBuilder.h" +#include "SessionDataSet.h" + +#include +#include +#include +#include +#include +#include + +/* ============================================================ + * Internal wrapper structs — the opaque handles point to these + * ============================================================ */ + +struct CSession_ { + std::shared_ptr cpp; +}; + +struct CTableSession_ { + std::shared_ptr cpp; +}; + +struct CTablet_ { + Tablet cpp; +}; + +struct CSessionDataSet_ { + std::unique_ptr cpp; +}; + +struct CRowRecord_ { + std::shared_ptr cpp; +}; + +/* ============================================================ + * Thread-local error message buffer + * ============================================================ */ + +static thread_local std::string g_lastError; + +static void clearError() { + g_lastError.clear(); +} + +static TsStatus setError(TsStatus code, const std::string& msg) { + g_lastError = msg; + return code; +} + +static TsStatus setError(TsStatus code, const std::exception& e) { + g_lastError = e.what(); + return code; +} + +static TsStatus handleException(const std::exception& e) { +#if defined(_CPPRTTI) || defined(__GXX_RTTI) + // Try to classify exception type (requires RTTI enabled). + if (dynamic_cast(&e)) { + return setError(TS_ERR_CONNECTION, e); + } + if (dynamic_cast(&e) || + dynamic_cast(&e) || + dynamic_cast(&e)) { + return setError(TS_ERR_EXECUTION, e); + } +#endif + return setError(TS_ERR_UNKNOWN, e); +} + +extern "C" { + +const char* ts_get_last_error(void) { + return g_lastError.c_str(); +} + +} /* extern "C" */ + +/* ============================================================ + * Helpers — convert C arrays to C++ vectors + * ============================================================ */ + +static std::vector toStringVec(const char* const* arr, int count) { + std::vector v; + v.reserve(count); + for (int i = 0; i < count; i++) { + v.emplace_back(arr[i]); + } + return v; +} + +static std::vector toTypeVec(const TSDataType_C* arr, int count) { + std::vector v; + v.reserve(count); + for (int i = 0; i < count; i++) { + v.push_back(static_cast(arr[i])); + } + return v; +} + +static std::vector toEncodingVec(const TSEncoding_C* arr, int count) { + std::vector v; + v.reserve(count); + for (int i = 0; i < count; i++) { + v.push_back(static_cast(arr[i])); + } + return v; +} + +static std::vector toCompressionVec(const TSCompressionType_C* arr, int count) { + std::vector v; + v.reserve(count); + for (int i = 0; i < count; i++) { + v.push_back(static_cast(arr[i])); + } + return v; +} + +static std::map toStringMap(int count, const char* const* keys, const char* const* values) { + std::map m; + for (int i = 0; i < count; i++) { + m[keys[i]] = values[i]; + } + return m; +} + +/** + * Convert C typed values (void* const* values, TSDataType_C* types, int count) + * to C++ vector that Session expects. + * The caller must free the returned char* pointers using freeCharPtrVec(). + */ +static std::vector toCharPtrVec(const TSDataType_C* types, const void* const* values, int count) { + std::vector result(count); + for (int i = 0; i < count; i++) { + switch (types[i]) { + case TS_TYPE_BOOLEAN: { + bool* p = new bool(*static_cast(values[i])); + result[i] = reinterpret_cast(p); + break; + } + case TS_TYPE_INT32: { + int32_t* p = new int32_t(*static_cast(values[i])); + result[i] = reinterpret_cast(p); + break; + } + case TS_TYPE_INT64: + case TS_TYPE_TIMESTAMP: { + int64_t* p = new int64_t(*static_cast(values[i])); + result[i] = reinterpret_cast(p); + break; + } + case TS_TYPE_FLOAT: { + float* p = new float(*static_cast(values[i])); + result[i] = reinterpret_cast(p); + break; + } + case TS_TYPE_DOUBLE: { + double* p = new double(*static_cast(values[i])); + result[i] = reinterpret_cast(p); + break; + } + case TS_TYPE_TEXT: + case TS_TYPE_STRING: + case TS_TYPE_BLOB: + default: { + const char* src = static_cast(values[i]); + size_t len = strlen(src) + 1; + char* p = new char[len]; + memcpy(p, src, len); + result[i] = p; + break; + } + } + } + return result; +} + +static void freeCharPtrVec(std::vector& vec, const TSDataType_C* types, int count) { + for (int i = 0; i < count; i++) { + switch (types[i]) { + case TS_TYPE_BOOLEAN: delete reinterpret_cast(vec[i]); break; + case TS_TYPE_INT32: delete reinterpret_cast(vec[i]); break; + case TS_TYPE_INT64: + case TS_TYPE_TIMESTAMP: delete reinterpret_cast(vec[i]); break; + case TS_TYPE_FLOAT: delete reinterpret_cast(vec[i]); break; + case TS_TYPE_DOUBLE: delete reinterpret_cast(vec[i]); break; + default: delete[] vec[i]; break; + } + } +} + +/* ============================================================ + * Session Lifecycle — Tree Model + * ============================================================ */ + +extern "C" { + +CSession* ts_session_new(const char* host, int rpcPort, + const char* username, const char* password) { + clearError(); + try { + auto* cs = new CSession_(); + cs->cpp = std::make_shared( + std::string(host), rpcPort, + std::string(username), std::string(password)); + return cs; + } catch (const std::exception& e) { + handleException(e); + return nullptr; + } +} + +CSession* ts_session_new_with_zone(const char* host, int rpcPort, + const char* username, const char* password, + const char* zoneId, int fetchSize) { + clearError(); + try { + auto* cs = new CSession_(); + cs->cpp = std::make_shared( + std::string(host), rpcPort, + std::string(username), std::string(password), + std::string(zoneId), fetchSize); + return cs; + } catch (const std::exception& e) { + handleException(e); + return nullptr; + } +} + +CSession* ts_session_new_multi_node(const char* const* nodeUrls, int urlCount, + const char* username, const char* password) { + clearError(); + try { + auto urls = toStringVec(nodeUrls, urlCount); + auto* cs = new CSession_(); + cs->cpp = std::make_shared(urls, std::string(username), std::string(password)); + return cs; + } catch (const std::exception& e) { + handleException(e); + return nullptr; + } +} + +void ts_session_destroy(CSession* session) { + delete session; +} + +TsStatus ts_session_open(CSession* session) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + session->cpp->open(); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +TsStatus ts_session_open_with_compression(CSession* session, bool enableRPCCompression) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + session->cpp->open(enableRPCCompression); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +TsStatus ts_session_close(CSession* session) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + session->cpp->close(); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +/* ============================================================ + * Session Lifecycle — Table Model + * ============================================================ */ + +CTableSession* ts_table_session_new(const char* host, int rpcPort, + const char* username, const char* password, + const char* database) { + clearError(); + try { + std::unique_ptr builder(new TableSessionBuilder()); + auto tableSession = builder->host(std::string(host)) + ->rpcPort(rpcPort) + ->username(std::string(username)) + ->password(std::string(password)) + ->database(std::string(database ? database : "")) + ->build(); + auto* cts = new CTableSession_(); + cts->cpp = tableSession; + return cts; + } catch (const std::exception& e) { + handleException(e); + return nullptr; + } +} + +CTableSession* ts_table_session_new_multi_node(const char* const* nodeUrls, int urlCount, + const char* username, const char* password, + const char* database) { + clearError(); + try { + auto urls = toStringVec(nodeUrls, urlCount); + std::unique_ptr builder(new TableSessionBuilder()); + auto tableSession = builder->nodeUrls(urls) + ->username(std::string(username)) + ->password(std::string(password)) + ->database(std::string(database ? database : "")) + ->build(); + auto* cts = new CTableSession_(); + cts->cpp = tableSession; + return cts; + } catch (const std::exception& e) { + handleException(e); + return nullptr; + } +} + +void ts_table_session_destroy(CTableSession* session) { + delete session; +} + +TsStatus ts_table_session_open(CTableSession* session) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + session->cpp->open(); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +TsStatus ts_table_session_close(CTableSession* session) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + session->cpp->close(); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +/* ============================================================ + * Timezone + * ============================================================ */ + +TsStatus ts_session_set_timezone(CSession* session, const char* zoneId) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + session->cpp->setTimeZone(std::string(zoneId)); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +TsStatus ts_session_get_timezone(CSession* session, char* buf, int bufLen) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + if (!buf || bufLen <= 0) return setError(TS_ERR_INVALID_PARAM, "invalid buffer"); + try { + std::string tz = session->cpp->getTimeZone(); + if ((int)tz.size() >= bufLen) { + return setError(TS_ERR_INVALID_PARAM, "buffer too small"); + } + strncpy(buf, tz.c_str(), bufLen); + buf[bufLen - 1] = '\0'; + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +/* ============================================================ + * Database Management (Tree Model) + * ============================================================ */ + +TsStatus ts_session_create_database(CSession* session, const char* database) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + session->cpp->createDatabase(std::string(database)); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +TsStatus ts_session_delete_database(CSession* session, const char* database) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + session->cpp->deleteDatabase(std::string(database)); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +TsStatus ts_session_delete_databases(CSession* session, const char* const* databases, int count) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + auto dbs = toStringVec(databases, count); + session->cpp->deleteDatabases(dbs); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +/* ============================================================ + * Timeseries Management (Tree Model) + * ============================================================ */ + +TsStatus ts_session_create_timeseries(CSession* session, const char* path, + TSDataType_C dataType, TSEncoding_C encoding, + TSCompressionType_C compressor) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + session->cpp->createTimeseries( + std::string(path), + static_cast(dataType), + static_cast(encoding), + static_cast(compressor)); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +TsStatus ts_session_create_timeseries_ex(CSession* session, const char* path, + TSDataType_C dataType, TSEncoding_C encoding, + TSCompressionType_C compressor, + int propsCount, + const char* const* propKeys, + const char* const* propValues, + int tagsCount, + const char* const* tagKeys, + const char* const* tagValues, + int attrsCount, + const char* const* attrKeys, + const char* const* attrValues, + const char* measurementAlias) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + std::map props = propsCount > 0 ? toStringMap(propsCount, propKeys, propValues) : std::map(); + std::map tags = tagsCount > 0 ? toStringMap(tagsCount, tagKeys, tagValues) : std::map(); + std::map attrs = attrsCount > 0 ? toStringMap(attrsCount, attrKeys, attrValues) : std::map(); + session->cpp->createTimeseries( + std::string(path), + static_cast(dataType), + static_cast(encoding), + static_cast(compressor), + &props, &tags, &attrs, + std::string(measurementAlias ? measurementAlias : "")); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +TsStatus ts_session_create_multi_timeseries(CSession* session, int count, + const char* const* paths, + const TSDataType_C* dataTypes, + const TSEncoding_C* encodings, + const TSCompressionType_C* compressors) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + auto pathsVec = toStringVec(paths, count); + auto typesVec = toTypeVec(dataTypes, count); + auto encVec = toEncodingVec(encodings, count); + auto compVec = toCompressionVec(compressors, count); + session->cpp->createMultiTimeseries( + pathsVec, typesVec, encVec, compVec, + nullptr, nullptr, nullptr, nullptr); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +TsStatus ts_session_create_aligned_timeseries(CSession* session, const char* deviceId, + int count, + const char* const* measurements, + const TSDataType_C* dataTypes, + const TSEncoding_C* encodings, + const TSCompressionType_C* compressors) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + auto measurementsVec = toStringVec(measurements, count); + auto typesVec = toTypeVec(dataTypes, count); + auto encVec = toEncodingVec(encodings, count); + auto compVec = toCompressionVec(compressors, count); + session->cpp->createAlignedTimeseries( + std::string(deviceId), measurementsVec, typesVec, encVec, compVec); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +TsStatus ts_session_check_timeseries_exists(CSession* session, const char* path, bool* exists) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + if (!exists) return setError(TS_ERR_INVALID_PARAM, "exists pointer is null"); + try { + *exists = session->cpp->checkTimeseriesExists(std::string(path)); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +TsStatus ts_session_delete_timeseries(CSession* session, const char* path) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + session->cpp->deleteTimeseries(std::string(path)); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +TsStatus ts_session_delete_timeseries_batch(CSession* session, const char* const* paths, int count) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + auto pathsVec = toStringVec(paths, count); + session->cpp->deleteTimeseries(pathsVec); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +/* ============================================================ + * Tablet Operations + * ============================================================ */ + +CTablet* ts_tablet_new(const char* deviceId, int columnCount, + const char* const* columnNames, + const TSDataType_C* dataTypes, + int maxRowNumber) { + try { + std::vector> schemas; + schemas.reserve(columnCount); + for (int i = 0; i < columnCount; i++) { + schemas.emplace_back(std::string(columnNames[i]), + static_cast(dataTypes[i])); + } + auto* ct = new CTablet_(); + ct->cpp = Tablet(std::string(deviceId), schemas, maxRowNumber); + return ct; + } catch (const std::exception& e) { + handleException(e); + return nullptr; + } +} + +CTablet* ts_tablet_new_with_category(const char* deviceId, int columnCount, + const char* const* columnNames, + const TSDataType_C* dataTypes, + const TSColumnCategory_C* columnCategories, + int maxRowNumber) { + try { + std::vector> schemas; + std::vector colTypes; + schemas.reserve(columnCount); + colTypes.reserve(columnCount); + for (int i = 0; i < columnCount; i++) { + schemas.emplace_back(std::string(columnNames[i]), + static_cast(dataTypes[i])); + colTypes.push_back(static_cast(columnCategories[i])); + } + auto* ct = new CTablet_(); + ct->cpp = Tablet(std::string(deviceId), schemas, colTypes, maxRowNumber); + return ct; + } catch (const std::exception& e) { + handleException(e); + return nullptr; + } +} + +void ts_tablet_destroy(CTablet* tablet) { + delete tablet; +} + +void ts_tablet_reset(CTablet* tablet) { + if (tablet) { + tablet->cpp.reset(); + } +} + +int ts_tablet_get_row_count(CTablet* tablet) { + if (!tablet) return 0; + return static_cast(tablet->cpp.rowSize); +} + +TsStatus ts_tablet_set_row_count(CTablet* tablet, int rowCount) { + clearError(); + if (!tablet) return setError(TS_ERR_NULL_PTR, "tablet is null"); + tablet->cpp.rowSize = rowCount; + return TS_OK; +} + +TsStatus ts_tablet_add_timestamp(CTablet* tablet, int rowIndex, int64_t timestamp) { + clearError(); + if (!tablet) return setError(TS_ERR_NULL_PTR, "tablet is null"); + try { + tablet->cpp.addTimestamp(static_cast(rowIndex), timestamp); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +TsStatus ts_tablet_add_value_bool(CTablet* tablet, int colIndex, int rowIndex, bool value) { + clearError(); + if (!tablet) return setError(TS_ERR_NULL_PTR, "tablet is null"); + try { + tablet->cpp.addValue(static_cast(colIndex), static_cast(rowIndex), value); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +TsStatus ts_tablet_add_value_int32(CTablet* tablet, int colIndex, int rowIndex, int32_t value) { + clearError(); + if (!tablet) return setError(TS_ERR_NULL_PTR, "tablet is null"); + try { + tablet->cpp.addValue(static_cast(colIndex), static_cast(rowIndex), value); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +TsStatus ts_tablet_add_value_int64(CTablet* tablet, int colIndex, int rowIndex, int64_t value) { + clearError(); + if (!tablet) return setError(TS_ERR_NULL_PTR, "tablet is null"); + try { + tablet->cpp.addValue(static_cast(colIndex), static_cast(rowIndex), value); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +TsStatus ts_tablet_add_value_float(CTablet* tablet, int colIndex, int rowIndex, float value) { + clearError(); + if (!tablet) return setError(TS_ERR_NULL_PTR, "tablet is null"); + try { + tablet->cpp.addValue(static_cast(colIndex), static_cast(rowIndex), value); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +TsStatus ts_tablet_add_value_double(CTablet* tablet, int colIndex, int rowIndex, double value) { + clearError(); + if (!tablet) return setError(TS_ERR_NULL_PTR, "tablet is null"); + try { + tablet->cpp.addValue(static_cast(colIndex), static_cast(rowIndex), value); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +TsStatus ts_tablet_add_value_string(CTablet* tablet, int colIndex, int rowIndex, const char* value) { + clearError(); + if (!tablet) return setError(TS_ERR_NULL_PTR, "tablet is null"); + try { + std::string str(value); + tablet->cpp.addValue(static_cast(colIndex), static_cast(rowIndex), str); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +/* ============================================================ + * Data Insertion — Tree Model (Record, string values) + * ============================================================ */ + +TsStatus ts_session_insert_record_str(CSession* session, const char* deviceId, + int64_t time, int count, + const char* const* measurements, + const char* const* values) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + auto measurementsVec = toStringVec(measurements, count); + auto valuesVec = toStringVec(values, count); + session->cpp->insertRecord(std::string(deviceId), time, measurementsVec, valuesVec); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +TsStatus ts_session_insert_record(CSession* session, const char* deviceId, + int64_t time, int count, + const char* const* measurements, + const TSDataType_C* types, + const void* const* values) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + auto measurementsVec = toStringVec(measurements, count); + auto typesVec = toTypeVec(types, count); + auto charVec = toCharPtrVec(types, values, count); + session->cpp->insertRecord(std::string(deviceId), time, measurementsVec, typesVec, charVec); + freeCharPtrVec(charVec, types, count); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +TsStatus ts_session_insert_aligned_record_str(CSession* session, const char* deviceId, + int64_t time, int count, + const char* const* measurements, + const char* const* values) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + auto measurementsVec = toStringVec(measurements, count); + auto valuesVec = toStringVec(values, count); + session->cpp->insertAlignedRecord(std::string(deviceId), time, measurementsVec, valuesVec); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +TsStatus ts_session_insert_aligned_record(CSession* session, const char* deviceId, + int64_t time, int count, + const char* const* measurements, + const TSDataType_C* types, + const void* const* values) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + auto measurementsVec = toStringVec(measurements, count); + auto typesVec = toTypeVec(types, count); + auto charVec = toCharPtrVec(types, values, count); + session->cpp->insertAlignedRecord(std::string(deviceId), time, measurementsVec, typesVec, charVec); + freeCharPtrVec(charVec, types, count); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +/* ============================================================ + * Data Insertion — Batch, multiple devices (string values) + * ============================================================ */ + +TsStatus ts_session_insert_records_str(CSession* session, int deviceCount, + const char* const* deviceIds, + const int64_t* times, + const int* measurementCounts, + const char* const* const* measurementsList, + const char* const* const* valuesList) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + auto devVec = toStringVec(deviceIds, deviceCount); + std::vector timesVec(times, times + deviceCount); + std::vector> mList, vList; + mList.reserve(deviceCount); + vList.reserve(deviceCount); + for (int i = 0; i < deviceCount; i++) { + mList.push_back(toStringVec(measurementsList[i], measurementCounts[i])); + vList.push_back(toStringVec(valuesList[i], measurementCounts[i])); + } + session->cpp->insertRecords(devVec, timesVec, mList, vList); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +TsStatus ts_session_insert_aligned_records_str(CSession* session, int deviceCount, + const char* const* deviceIds, + const int64_t* times, + const int* measurementCounts, + const char* const* const* measurementsList, + const char* const* const* valuesList) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + auto devVec = toStringVec(deviceIds, deviceCount); + std::vector timesVec(times, times + deviceCount); + std::vector> mList, vList; + mList.reserve(deviceCount); + vList.reserve(deviceCount); + for (int i = 0; i < deviceCount; i++) { + mList.push_back(toStringVec(measurementsList[i], measurementCounts[i])); + vList.push_back(toStringVec(valuesList[i], measurementCounts[i])); + } + session->cpp->insertAlignedRecords(devVec, timesVec, mList, vList); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +/* ============================================================ + * Data Insertion — Batch, multiple devices (typed values) + * ============================================================ */ + +TsStatus ts_session_insert_records(CSession* session, int deviceCount, + const char* const* deviceIds, + const int64_t* times, + const int* measurementCounts, + const char* const* const* measurementsList, + const TSDataType_C* const* typesList, + const void* const* const* valuesList) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + auto devVec = toStringVec(deviceIds, deviceCount); + std::vector timesVec(times, times + deviceCount); + std::vector> mList; + std::vector> tList; + std::vector> vList; + mList.reserve(deviceCount); + tList.reserve(deviceCount); + vList.reserve(deviceCount); + for (int i = 0; i < deviceCount; i++) { + mList.push_back(toStringVec(measurementsList[i], measurementCounts[i])); + tList.push_back(toTypeVec(typesList[i], measurementCounts[i])); + vList.push_back(toCharPtrVec(typesList[i], valuesList[i], measurementCounts[i])); + } + session->cpp->insertRecords(devVec, timesVec, mList, tList, vList); + for (int i = 0; i < deviceCount; i++) { + freeCharPtrVec(vList[i], typesList[i], measurementCounts[i]); + } + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +TsStatus ts_session_insert_aligned_records(CSession* session, int deviceCount, + const char* const* deviceIds, + const int64_t* times, + const int* measurementCounts, + const char* const* const* measurementsList, + const TSDataType_C* const* typesList, + const void* const* const* valuesList) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + auto devVec = toStringVec(deviceIds, deviceCount); + std::vector timesVec(times, times + deviceCount); + std::vector> mList; + std::vector> tList; + std::vector> vList; + mList.reserve(deviceCount); + tList.reserve(deviceCount); + vList.reserve(deviceCount); + for (int i = 0; i < deviceCount; i++) { + mList.push_back(toStringVec(measurementsList[i], measurementCounts[i])); + tList.push_back(toTypeVec(typesList[i], measurementCounts[i])); + vList.push_back(toCharPtrVec(typesList[i], valuesList[i], measurementCounts[i])); + } + session->cpp->insertAlignedRecords(devVec, timesVec, mList, tList, vList); + for (int i = 0; i < deviceCount; i++) { + freeCharPtrVec(vList[i], typesList[i], measurementCounts[i]); + } + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +/* ============================================================ + * Data Insertion — Batch, single device (typed values) + * ============================================================ */ + +TsStatus ts_session_insert_records_of_one_device(CSession* session, const char* deviceId, + int rowCount, const int64_t* times, + const int* measurementCounts, + const char* const* const* measurementsList, + const TSDataType_C* const* typesList, + const void* const* const* valuesList, + bool sorted) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + std::vector timesVec(times, times + rowCount); + std::vector> mList; + std::vector> tList; + std::vector> vList; + mList.reserve(rowCount); + tList.reserve(rowCount); + vList.reserve(rowCount); + for (int i = 0; i < rowCount; i++) { + mList.push_back(toStringVec(measurementsList[i], measurementCounts[i])); + tList.push_back(toTypeVec(typesList[i], measurementCounts[i])); + vList.push_back(toCharPtrVec(typesList[i], valuesList[i], measurementCounts[i])); + } + session->cpp->insertRecordsOfOneDevice(std::string(deviceId), timesVec, mList, tList, vList, sorted); + for (int i = 0; i < rowCount; i++) { + freeCharPtrVec(vList[i], typesList[i], measurementCounts[i]); + } + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +TsStatus ts_session_insert_aligned_records_of_one_device(CSession* session, const char* deviceId, + int rowCount, const int64_t* times, + const int* measurementCounts, + const char* const* const* measurementsList, + const TSDataType_C* const* typesList, + const void* const* const* valuesList, + bool sorted) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + std::vector timesVec(times, times + rowCount); + std::vector> mList; + std::vector> tList; + std::vector> vList; + mList.reserve(rowCount); + tList.reserve(rowCount); + vList.reserve(rowCount); + for (int i = 0; i < rowCount; i++) { + mList.push_back(toStringVec(measurementsList[i], measurementCounts[i])); + tList.push_back(toTypeVec(typesList[i], measurementCounts[i])); + vList.push_back(toCharPtrVec(typesList[i], valuesList[i], measurementCounts[i])); + } + session->cpp->insertAlignedRecordsOfOneDevice(std::string(deviceId), timesVec, mList, tList, vList, sorted); + for (int i = 0; i < rowCount; i++) { + freeCharPtrVec(vList[i], typesList[i], measurementCounts[i]); + } + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +/* ============================================================ + * Data Insertion — Tree Model (Tablet) + * ============================================================ */ + +TsStatus ts_session_insert_tablet(CSession* session, CTablet* tablet, bool sorted) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + if (!tablet) return setError(TS_ERR_NULL_PTR, "tablet is null"); + try { + session->cpp->insertTablet(tablet->cpp, sorted); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +TsStatus ts_session_insert_aligned_tablet(CSession* session, CTablet* tablet, bool sorted) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + if (!tablet) return setError(TS_ERR_NULL_PTR, "tablet is null"); + try { + session->cpp->insertAlignedTablet(tablet->cpp, sorted); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +TsStatus ts_session_insert_tablets(CSession* session, int tabletCount, + const char* const* deviceIds, + CTablet** tablets, bool sorted) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + std::unordered_map tabletMap; + for (int i = 0; i < tabletCount; i++) { + tabletMap[std::string(deviceIds[i])] = &(tablets[i]->cpp); + } + session->cpp->insertTablets(tabletMap, sorted); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +TsStatus ts_session_insert_aligned_tablets(CSession* session, int tabletCount, + const char* const* deviceIds, + CTablet** tablets, bool sorted) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + std::unordered_map tabletMap; + for (int i = 0; i < tabletCount; i++) { + tabletMap[std::string(deviceIds[i])] = &(tablets[i]->cpp); + } + session->cpp->insertAlignedTablets(tabletMap, sorted); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +/* ============================================================ + * Data Insertion — Table Model (Tablet) + * ============================================================ */ + +TsStatus ts_table_session_insert(CTableSession* session, CTablet* tablet) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + if (!tablet) return setError(TS_ERR_NULL_PTR, "tablet is null"); + try { + session->cpp->insert(tablet->cpp); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +/* ============================================================ + * Query — Tree Model + * ============================================================ */ + +TsStatus ts_session_execute_query(CSession* session, const char* sql, + CSessionDataSet** dataSet) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + if (!dataSet) return setError(TS_ERR_INVALID_PARAM, "dataSet pointer is null"); + try { + auto ds = session->cpp->executeQueryStatement(std::string(sql)); + auto* cds = new CSessionDataSet_(); + cds->cpp = std::move(ds); + *dataSet = cds; + return TS_OK; + } catch (const std::exception& e) { + *dataSet = nullptr; + return handleException(e); + } +} + +TsStatus ts_session_execute_query_with_timeout(CSession* session, const char* sql, + int64_t timeoutInMs, + CSessionDataSet** dataSet) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + if (!dataSet) return setError(TS_ERR_INVALID_PARAM, "dataSet pointer is null"); + try { + auto ds = session->cpp->executeQueryStatement(std::string(sql), timeoutInMs); + auto* cds = new CSessionDataSet_(); + cds->cpp = std::move(ds); + *dataSet = cds; + return TS_OK; + } catch (const std::exception& e) { + *dataSet = nullptr; + return handleException(e); + } +} + +TsStatus ts_session_execute_non_query(CSession* session, const char* sql) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + session->cpp->executeNonQueryStatement(std::string(sql)); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +TsStatus ts_session_execute_raw_data_query(CSession* session, + int pathCount, const char* const* paths, + int64_t startTime, int64_t endTime, + CSessionDataSet** dataSet) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + if (!dataSet) return setError(TS_ERR_INVALID_PARAM, "dataSet pointer is null"); + try { + auto pathsVec = toStringVec(paths, pathCount); + auto ds = session->cpp->executeRawDataQuery(pathsVec, startTime, endTime); + auto* cds = new CSessionDataSet_(); + cds->cpp = std::move(ds); + *dataSet = cds; + return TS_OK; + } catch (const std::exception& e) { + *dataSet = nullptr; + return handleException(e); + } +} + +TsStatus ts_session_execute_last_data_query(CSession* session, + int pathCount, const char* const* paths, + CSessionDataSet** dataSet) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + if (!dataSet) return setError(TS_ERR_INVALID_PARAM, "dataSet pointer is null"); + try { + auto pathsVec = toStringVec(paths, pathCount); + auto ds = session->cpp->executeLastDataQuery(pathsVec); + auto* cds = new CSessionDataSet_(); + cds->cpp = std::move(ds); + *dataSet = cds; + return TS_OK; + } catch (const std::exception& e) { + *dataSet = nullptr; + return handleException(e); + } +} + +TsStatus ts_session_execute_last_data_query_with_time(CSession* session, + int pathCount, const char* const* paths, + int64_t lastTime, + CSessionDataSet** dataSet) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + if (!dataSet) return setError(TS_ERR_INVALID_PARAM, "dataSet pointer is null"); + try { + auto pathsVec = toStringVec(paths, pathCount); + auto ds = session->cpp->executeLastDataQuery(pathsVec, lastTime); + auto* cds = new CSessionDataSet_(); + cds->cpp = std::move(ds); + *dataSet = cds; + return TS_OK; + } catch (const std::exception& e) { + *dataSet = nullptr; + return handleException(e); + } +} + +/* ============================================================ + * Query — Table Model + * ============================================================ */ + +TsStatus ts_table_session_execute_query(CTableSession* session, const char* sql, + CSessionDataSet** dataSet) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + if (!dataSet) return setError(TS_ERR_INVALID_PARAM, "dataSet pointer is null"); + try { + auto ds = session->cpp->executeQueryStatement(std::string(sql)); + auto* cds = new CSessionDataSet_(); + cds->cpp = std::move(ds); + *dataSet = cds; + return TS_OK; + } catch (const std::exception& e) { + *dataSet = nullptr; + return handleException(e); + } +} + +TsStatus ts_table_session_execute_query_with_timeout(CTableSession* session, const char* sql, + int64_t timeoutInMs, + CSessionDataSet** dataSet) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + if (!dataSet) return setError(TS_ERR_INVALID_PARAM, "dataSet pointer is null"); + try { + auto ds = session->cpp->executeQueryStatement(std::string(sql), timeoutInMs); + auto* cds = new CSessionDataSet_(); + cds->cpp = std::move(ds); + *dataSet = cds; + return TS_OK; + } catch (const std::exception& e) { + *dataSet = nullptr; + return handleException(e); + } +} + +TsStatus ts_table_session_execute_non_query(CTableSession* session, const char* sql) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + session->cpp->executeNonQueryStatement(std::string(sql)); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +/* ============================================================ + * SessionDataSet & RowRecord — Result Iteration + * ============================================================ */ + +void ts_dataset_destroy(CSessionDataSet* dataSet) { + if (dataSet) { + if (dataSet->cpp) { + dataSet->cpp->closeOperationHandle(); + } + delete dataSet; + } +} + +bool ts_dataset_has_next(CSessionDataSet* dataSet) { + if (!dataSet || !dataSet->cpp) return false; + try { + return dataSet->cpp->hasNext(); + } catch (...) { + return false; + } +} + +CRowRecord* ts_dataset_next(CSessionDataSet* dataSet) { + if (!dataSet || !dataSet->cpp) return nullptr; + try { + auto row = dataSet->cpp->next(); + if (!row) return nullptr; + auto* crr = new CRowRecord_(); + crr->cpp = row; + return crr; + } catch (...) { + return nullptr; + } +} + +int ts_dataset_get_column_count(CSessionDataSet* dataSet) { + if (!dataSet || !dataSet->cpp) return 0; + return static_cast(dataSet->cpp->getColumnNames().size()); +} + +static thread_local std::string g_colNameBuf; + +const char* ts_dataset_get_column_name(CSessionDataSet* dataSet, int index) { + if (!dataSet || !dataSet->cpp) return ""; + const auto& names = dataSet->cpp->getColumnNames(); + if (index < 0 || index >= (int)names.size()) return ""; + g_colNameBuf = names[index]; + return g_colNameBuf.c_str(); +} + +static thread_local std::string g_colTypeBuf; + +const char* ts_dataset_get_column_type(CSessionDataSet* dataSet, int index) { + if (!dataSet || !dataSet->cpp) return ""; + const auto& types = dataSet->cpp->getColumnTypeList(); + if (index < 0 || index >= (int)types.size()) return ""; + g_colTypeBuf = types[index]; + return g_colTypeBuf.c_str(); +} + +void ts_dataset_set_fetch_size(CSessionDataSet* dataSet, int fetchSize) { + if (dataSet && dataSet->cpp) { + dataSet->cpp->setFetchSize(fetchSize); + } +} + +void ts_row_record_destroy(CRowRecord* record) { + delete record; +} + +int64_t ts_row_record_get_timestamp(CRowRecord* record) { + if (!record || !record->cpp) return -1; + return record->cpp->timestamp; +} + +int ts_row_record_get_field_count(CRowRecord* record) { + if (!record || !record->cpp) return 0; + return static_cast(record->cpp->fields.size()); +} + +bool ts_row_record_is_null(CRowRecord* record, int index) { + if (!record || !record->cpp) return true; + if (index < 0 || index >= (int)record->cpp->fields.size()) return true; + const Field& f = record->cpp->fields[index]; + switch (f.dataType) { + case TSDataType::BOOLEAN: return !f.boolV.is_initialized(); + case TSDataType::INT32: return !f.intV.is_initialized(); + case TSDataType::INT64: + case TSDataType::TIMESTAMP: return !f.longV.is_initialized(); + case TSDataType::FLOAT: return !f.floatV.is_initialized(); + case TSDataType::DOUBLE: return !f.doubleV.is_initialized(); + case TSDataType::TEXT: + case TSDataType::STRING: + case TSDataType::BLOB: return !f.stringV.is_initialized(); + case TSDataType::DATE: return !f.dateV.is_initialized(); + default: return true; + } +} + +bool ts_row_record_get_bool(CRowRecord* record, int index) { + if (!record || !record->cpp) return false; + if (index < 0 || index >= (int)record->cpp->fields.size()) return false; + const Field& f = record->cpp->fields[index]; + return f.boolV.is_initialized() ? f.boolV.value() : false; +} + +int32_t ts_row_record_get_int32(CRowRecord* record, int index) { + if (!record || !record->cpp) return 0; + if (index < 0 || index >= (int)record->cpp->fields.size()) return 0; + const Field& f = record->cpp->fields[index]; + return f.intV.is_initialized() ? f.intV.value() : 0; +} + +int64_t ts_row_record_get_int64(CRowRecord* record, int index) { + if (!record || !record->cpp) return 0; + if (index < 0 || index >= (int)record->cpp->fields.size()) return 0; + const Field& f = record->cpp->fields[index]; + return f.longV.is_initialized() ? f.longV.value() : 0; +} + +float ts_row_record_get_float(CRowRecord* record, int index) { + if (!record || !record->cpp) return 0.0f; + if (index < 0 || index >= (int)record->cpp->fields.size()) return 0.0f; + const Field& f = record->cpp->fields[index]; + return f.floatV.is_initialized() ? f.floatV.value() : 0.0f; +} + +double ts_row_record_get_double(CRowRecord* record, int index) { + if (!record || !record->cpp) return 0.0; + if (index < 0 || index >= (int)record->cpp->fields.size()) return 0.0; + const Field& f = record->cpp->fields[index]; + return f.doubleV.is_initialized() ? f.doubleV.value() : 0.0; +} + +static thread_local std::string g_stringBuf; + +const char* ts_row_record_get_string(CRowRecord* record, int index) { + if (!record || !record->cpp) return ""; + if (index < 0 || index >= (int)record->cpp->fields.size()) return ""; + const Field& f = record->cpp->fields[index]; + if (f.stringV.is_initialized()) { + g_stringBuf = f.stringV.value(); + return g_stringBuf.c_str(); + } + return ""; +} + +TSDataType_C ts_row_record_get_data_type(CRowRecord* record, int index) { + if (!record || !record->cpp) return TS_TYPE_TEXT; + if (index < 0 || index >= (int)record->cpp->fields.size()) return TS_TYPE_TEXT; + return static_cast(record->cpp->fields[index].dataType); +} + +/* ============================================================ + * Data Deletion (Tree Model) + * ============================================================ */ + +TsStatus ts_session_delete_data(CSession* session, const char* path, int64_t endTime) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + session->cpp->deleteData(std::string(path), endTime); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +TsStatus ts_session_delete_data_batch(CSession* session, int pathCount, + const char* const* paths, int64_t endTime) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + auto pathsVec = toStringVec(paths, pathCount); + session->cpp->deleteData(pathsVec, endTime); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +TsStatus ts_session_delete_data_range(CSession* session, int pathCount, + const char* const* paths, + int64_t startTime, int64_t endTime) { + clearError(); + if (!session) return setError(TS_ERR_NULL_PTR, "session is null"); + try { + auto pathsVec = toStringVec(paths, pathCount); + session->cpp->deleteData(pathsVec, startTime, endTime); + return TS_OK; + } catch (const std::exception& e) { + return handleException(e); + } +} + +} /* extern "C" */ diff --git a/iotdb-client/client-cpp/src/main/SessionC.h b/iotdb-client/client-cpp/src/main/SessionC.h new file mode 100644 index 0000000000000..1de5574aa60a7 --- /dev/null +++ b/iotdb-client/client-cpp/src/main/SessionC.h @@ -0,0 +1,440 @@ +/** + * 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 IOTDB_SESSION_C_H +#define IOTDB_SESSION_C_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ============================================================ + * Error Handling + * ============================================================ */ + +typedef int64_t TsStatus; + +#define TS_OK 0 +#define TS_ERR_CONNECTION -1 +#define TS_ERR_EXECUTION -2 +#define TS_ERR_INVALID_PARAM -3 +#define TS_ERR_NULL_PTR -4 +#define TS_ERR_UNKNOWN -99 + +/** + * Returns the error message from the last failed C API call on the current thread. + * The returned pointer is valid until the next C API call on the same thread. + */ +const char* ts_get_last_error(void); + +/* ============================================================ + * Opaque Handle Types + * ============================================================ */ + +typedef struct CSession_ CSession; +typedef struct CTableSession_ CTableSession; +typedef struct CTablet_ CTablet; +typedef struct CSessionDataSet_ CSessionDataSet; +typedef struct CRowRecord_ CRowRecord; + +/* ============================================================ + * Enums (values match C++ TSDataType / TSEncoding / CompressionType) + * ============================================================ */ + +typedef enum { + TS_TYPE_BOOLEAN = 0, + TS_TYPE_INT32 = 1, + TS_TYPE_INT64 = 2, + TS_TYPE_FLOAT = 3, + TS_TYPE_DOUBLE = 4, + TS_TYPE_TEXT = 5, + TS_TYPE_TIMESTAMP = 8, + TS_TYPE_DATE = 9, + TS_TYPE_BLOB = 10, + TS_TYPE_STRING = 11 +} TSDataType_C; + +typedef enum { + TS_ENCODING_PLAIN = 0, + TS_ENCODING_DICTIONARY = 1, + TS_ENCODING_RLE = 2, + TS_ENCODING_DIFF = 3, + TS_ENCODING_TS_2DIFF = 4, + TS_ENCODING_BITMAP = 5, + TS_ENCODING_GORILLA_V1 = 6, + TS_ENCODING_REGULAR = 7, + TS_ENCODING_GORILLA = 8, + TS_ENCODING_ZIGZAG = 9, + TS_ENCODING_FREQ = 10 +} TSEncoding_C; + +typedef enum { + TS_COMPRESSION_UNCOMPRESSED = 0, + TS_COMPRESSION_SNAPPY = 1, + TS_COMPRESSION_GZIP = 2, + TS_COMPRESSION_LZO = 3, + TS_COMPRESSION_SDT = 4, + TS_COMPRESSION_PAA = 5, + TS_COMPRESSION_PLA = 6, + TS_COMPRESSION_LZ4 = 7, + TS_COMPRESSION_ZSTD = 8, + TS_COMPRESSION_LZMA2 = 9 +} TSCompressionType_C; + +typedef enum { + TS_COL_TAG = 0, + TS_COL_FIELD = 1, + TS_COL_ATTRIBUTE = 2 +} TSColumnCategory_C; + +/* ============================================================ + * Session Lifecycle — Tree Model + * ============================================================ */ + +CSession* ts_session_new(const char* host, int rpcPort, + const char* username, const char* password); + +CSession* ts_session_new_with_zone(const char* host, int rpcPort, + const char* username, const char* password, + const char* zoneId, int fetchSize); + +CSession* ts_session_new_multi_node(const char* const* nodeUrls, int urlCount, + const char* username, const char* password); + +void ts_session_destroy(CSession* session); + +TsStatus ts_session_open(CSession* session); + +TsStatus ts_session_open_with_compression(CSession* session, bool enableRPCCompression); + +TsStatus ts_session_close(CSession* session); + +/* ============================================================ + * Session Lifecycle — Table Model + * ============================================================ */ + +CTableSession* ts_table_session_new(const char* host, int rpcPort, + const char* username, const char* password, + const char* database); + +CTableSession* ts_table_session_new_multi_node(const char* const* nodeUrls, int urlCount, + const char* username, const char* password, + const char* database); + +void ts_table_session_destroy(CTableSession* session); + +TsStatus ts_table_session_open(CTableSession* session); + +TsStatus ts_table_session_close(CTableSession* session); + +/* ============================================================ + * Timezone + * ============================================================ */ + +TsStatus ts_session_set_timezone(CSession* session, const char* zoneId); + +TsStatus ts_session_get_timezone(CSession* session, char* buf, int bufLen); + +/* ============================================================ + * Database Management (Tree Model) + * ============================================================ */ + +TsStatus ts_session_create_database(CSession* session, const char* database); + +TsStatus ts_session_delete_database(CSession* session, const char* database); + +TsStatus ts_session_delete_databases(CSession* session, const char* const* databases, int count); + +/* ============================================================ + * Timeseries Management (Tree Model) + * ============================================================ */ + +TsStatus ts_session_create_timeseries(CSession* session, const char* path, + TSDataType_C dataType, TSEncoding_C encoding, + TSCompressionType_C compressor); + +TsStatus ts_session_create_timeseries_ex(CSession* session, const char* path, + TSDataType_C dataType, TSEncoding_C encoding, + TSCompressionType_C compressor, + int propsCount, + const char* const* propKeys, + const char* const* propValues, + int tagsCount, + const char* const* tagKeys, + const char* const* tagValues, + int attrsCount, + const char* const* attrKeys, + const char* const* attrValues, + const char* measurementAlias); + +TsStatus ts_session_create_multi_timeseries(CSession* session, int count, + const char* const* paths, + const TSDataType_C* dataTypes, + const TSEncoding_C* encodings, + const TSCompressionType_C* compressors); + +TsStatus ts_session_create_aligned_timeseries(CSession* session, const char* deviceId, + int count, + const char* const* measurements, + const TSDataType_C* dataTypes, + const TSEncoding_C* encodings, + const TSCompressionType_C* compressors); + +TsStatus ts_session_check_timeseries_exists(CSession* session, const char* path, bool* exists); + +TsStatus ts_session_delete_timeseries(CSession* session, const char* path); + +TsStatus ts_session_delete_timeseries_batch(CSession* session, const char* const* paths, int count); + +/* ============================================================ + * Tablet Operations + * ============================================================ */ + +CTablet* ts_tablet_new(const char* deviceId, int columnCount, + const char* const* columnNames, + const TSDataType_C* dataTypes, + int maxRowNumber); + +CTablet* ts_tablet_new_with_category(const char* deviceId, int columnCount, + const char* const* columnNames, + const TSDataType_C* dataTypes, + const TSColumnCategory_C* columnCategories, + int maxRowNumber); + +void ts_tablet_destroy(CTablet* tablet); + +void ts_tablet_reset(CTablet* tablet); + +int ts_tablet_get_row_count(CTablet* tablet); + +TsStatus ts_tablet_set_row_count(CTablet* tablet, int rowCount); + +TsStatus ts_tablet_add_timestamp(CTablet* tablet, int rowIndex, int64_t timestamp); + +TsStatus ts_tablet_add_value_bool(CTablet* tablet, int colIndex, int rowIndex, bool value); + +TsStatus ts_tablet_add_value_int32(CTablet* tablet, int colIndex, int rowIndex, int32_t value); + +TsStatus ts_tablet_add_value_int64(CTablet* tablet, int colIndex, int rowIndex, int64_t value); + +TsStatus ts_tablet_add_value_float(CTablet* tablet, int colIndex, int rowIndex, float value); + +TsStatus ts_tablet_add_value_double(CTablet* tablet, int colIndex, int rowIndex, double value); + +TsStatus ts_tablet_add_value_string(CTablet* tablet, int colIndex, int rowIndex, const char* value); + +/* ============================================================ + * Data Insertion — Tree Model (Record) + * ============================================================ */ + +TsStatus ts_session_insert_record_str(CSession* session, const char* deviceId, + int64_t time, int count, + const char* const* measurements, + const char* const* values); + +TsStatus ts_session_insert_record(CSession* session, const char* deviceId, + int64_t time, int count, + const char* const* measurements, + const TSDataType_C* types, + const void* const* values); + +TsStatus ts_session_insert_aligned_record_str(CSession* session, const char* deviceId, + int64_t time, int count, + const char* const* measurements, + const char* const* values); + +TsStatus ts_session_insert_aligned_record(CSession* session, const char* deviceId, + int64_t time, int count, + const char* const* measurements, + const TSDataType_C* types, + const void* const* values); + +/* Batch insert — multiple devices (string values) */ +TsStatus ts_session_insert_records_str(CSession* session, int deviceCount, + const char* const* deviceIds, + const int64_t* times, + const int* measurementCounts, + const char* const* const* measurementsList, + const char* const* const* valuesList); + +TsStatus ts_session_insert_aligned_records_str(CSession* session, int deviceCount, + const char* const* deviceIds, + const int64_t* times, + const int* measurementCounts, + const char* const* const* measurementsList, + const char* const* const* valuesList); + +/* Batch insert — multiple devices (typed values) */ +TsStatus ts_session_insert_records(CSession* session, int deviceCount, + const char* const* deviceIds, + const int64_t* times, + const int* measurementCounts, + const char* const* const* measurementsList, + const TSDataType_C* const* typesList, + const void* const* const* valuesList); + +TsStatus ts_session_insert_aligned_records(CSession* session, int deviceCount, + const char* const* deviceIds, + const int64_t* times, + const int* measurementCounts, + const char* const* const* measurementsList, + const TSDataType_C* const* typesList, + const void* const* const* valuesList); + +/* Batch insert — single device (typed values) */ +TsStatus ts_session_insert_records_of_one_device(CSession* session, const char* deviceId, + int rowCount, const int64_t* times, + const int* measurementCounts, + const char* const* const* measurementsList, + const TSDataType_C* const* typesList, + const void* const* const* valuesList, + bool sorted); + +TsStatus ts_session_insert_aligned_records_of_one_device(CSession* session, const char* deviceId, + int rowCount, const int64_t* times, + const int* measurementCounts, + const char* const* const* measurementsList, + const TSDataType_C* const* typesList, + const void* const* const* valuesList, + bool sorted); + +/* ============================================================ + * Data Insertion — Tree Model (Tablet) + * ============================================================ */ + +TsStatus ts_session_insert_tablet(CSession* session, CTablet* tablet, bool sorted); + +TsStatus ts_session_insert_aligned_tablet(CSession* session, CTablet* tablet, bool sorted); + +TsStatus ts_session_insert_tablets(CSession* session, int tabletCount, + const char* const* deviceIds, + CTablet** tablets, bool sorted); + +TsStatus ts_session_insert_aligned_tablets(CSession* session, int tabletCount, + const char* const* deviceIds, + CTablet** tablets, bool sorted); + +/* ============================================================ + * Data Insertion — Table Model (Tablet) + * ============================================================ */ + +TsStatus ts_table_session_insert(CTableSession* session, CTablet* tablet); + +/* ============================================================ + * Query — Tree Model + * ============================================================ */ + +TsStatus ts_session_execute_query(CSession* session, const char* sql, + CSessionDataSet** dataSet); + +TsStatus ts_session_execute_query_with_timeout(CSession* session, const char* sql, + int64_t timeoutInMs, + CSessionDataSet** dataSet); + +TsStatus ts_session_execute_non_query(CSession* session, const char* sql); + +TsStatus ts_session_execute_raw_data_query(CSession* session, + int pathCount, const char* const* paths, + int64_t startTime, int64_t endTime, + CSessionDataSet** dataSet); + +TsStatus ts_session_execute_last_data_query(CSession* session, + int pathCount, const char* const* paths, + CSessionDataSet** dataSet); + +TsStatus ts_session_execute_last_data_query_with_time(CSession* session, + int pathCount, const char* const* paths, + int64_t lastTime, + CSessionDataSet** dataSet); + +/* ============================================================ + * Query — Table Model + * ============================================================ */ + +TsStatus ts_table_session_execute_query(CTableSession* session, const char* sql, + CSessionDataSet** dataSet); + +TsStatus ts_table_session_execute_query_with_timeout(CTableSession* session, const char* sql, + int64_t timeoutInMs, + CSessionDataSet** dataSet); + +TsStatus ts_table_session_execute_non_query(CTableSession* session, const char* sql); + +/* ============================================================ + * SessionDataSet & RowRecord — Result Iteration + * ============================================================ */ + +void ts_dataset_destroy(CSessionDataSet* dataSet); + +bool ts_dataset_has_next(CSessionDataSet* dataSet); + +CRowRecord* ts_dataset_next(CSessionDataSet* dataSet); + +int ts_dataset_get_column_count(CSessionDataSet* dataSet); + +const char* ts_dataset_get_column_name(CSessionDataSet* dataSet, int index); + +const char* ts_dataset_get_column_type(CSessionDataSet* dataSet, int index); + +void ts_dataset_set_fetch_size(CSessionDataSet* dataSet, int fetchSize); + +void ts_row_record_destroy(CRowRecord* record); + +int64_t ts_row_record_get_timestamp(CRowRecord* record); + +int ts_row_record_get_field_count(CRowRecord* record); + +bool ts_row_record_is_null(CRowRecord* record, int index); + +bool ts_row_record_get_bool(CRowRecord* record, int index); + +int32_t ts_row_record_get_int32(CRowRecord* record, int index); + +int64_t ts_row_record_get_int64(CRowRecord* record, int index); + +float ts_row_record_get_float(CRowRecord* record, int index); + +double ts_row_record_get_double(CRowRecord* record, int index); + +const char* ts_row_record_get_string(CRowRecord* record, int index); + +TSDataType_C ts_row_record_get_data_type(CRowRecord* record, int index); + +/* ============================================================ + * Data Deletion (Tree Model) + * ============================================================ */ + +TsStatus ts_session_delete_data(CSession* session, const char* path, int64_t endTime); + +TsStatus ts_session_delete_data_batch(CSession* session, int pathCount, + const char* const* paths, int64_t endTime); + +TsStatus ts_session_delete_data_range(CSession* session, int pathCount, + const char* const* paths, + int64_t startTime, int64_t endTime); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* IOTDB_SESSION_C_H */ diff --git a/iotdb-client/client-cpp/src/test/CMakeLists.txt b/iotdb-client/client-cpp/src/test/CMakeLists.txt index 0a830b05fcd5b..5a234b4709408 100644 --- a/iotdb-client/client-cpp/src/test/CMakeLists.txt +++ b/iotdb-client/client-cpp/src/test/CMakeLists.txt @@ -22,7 +22,10 @@ SET(CMAKE_CXX_STANDARD 11) SET(CMAKE_CXX_STANDARD_REQUIRED ON) SET(TARGET_NAME session_tests) SET(TARGET_NAME_RELATIONAL session_relational_tests) -SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -g -O2") +IF(NOT MSVC) + # Keep GCC/Clang style flags off on MSVC to avoid invalid-option build errors. + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -g -O2") +ENDIF() ENABLE_TESTING() # ========================= @@ -71,74 +74,79 @@ IF(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang" AND NOT MSVC) add_link_options(-fsanitize=address) ENDIF() +SET(TARGET_NAME_C session_c_tests) +SET(TARGET_NAME_C_RELATIONAL session_c_relational_tests) + ADD_EXECUTABLE(${TARGET_NAME} main.cpp cpp/sessionIT.cpp) ADD_EXECUTABLE(${TARGET_NAME_RELATIONAL} main_Relational.cpp cpp/sessionRelationalIT.cpp) +ADD_EXECUTABLE(${TARGET_NAME_C} main_c.cpp cpp/sessionCIT.cpp) +ADD_EXECUTABLE(${TARGET_NAME_C_RELATIONAL} main_c_Relational.cpp cpp/sessionCRelationalIT.cpp) # Link with shared library iotdb_session and pthread +SET(ALL_TEST_TARGETS ${TARGET_NAME} ${TARGET_NAME_RELATIONAL} ${TARGET_NAME_C} ${TARGET_NAME_C_RELATIONAL}) + IF(MSVC) IF(WITH_SSL) - TARGET_LINK_LIBRARIES(${TARGET_NAME} - iotdb_session - ${THRIFT_STATIC_LIB} - OpenSSL::SSL - OpenSSL::Crypto - ) - TARGET_LINK_LIBRARIES(${TARGET_NAME_RELATIONAL} - iotdb_session - ${THRIFT_STATIC_LIB} - OpenSSL::SSL - OpenSSL::Crypto - ) + FOREACH(T ${ALL_TEST_TARGETS}) + TARGET_LINK_LIBRARIES(${T} + iotdb_session + ${THRIFT_STATIC_LIB} + OpenSSL::SSL + OpenSSL::Crypto + ) + ENDFOREACH() ELSE() - TARGET_LINK_LIBRARIES(${TARGET_NAME} - iotdb_session - ${THRIFT_STATIC_LIB} - ) - TARGET_LINK_LIBRARIES(${TARGET_NAME_RELATIONAL} - iotdb_session - ${THRIFT_STATIC_LIB} - ) + FOREACH(T ${ALL_TEST_TARGETS}) + TARGET_LINK_LIBRARIES(${T} + iotdb_session + ${THRIFT_STATIC_LIB} + ) + ENDFOREACH() ENDIF() ELSE() IF(WITH_SSL) - TARGET_LINK_LIBRARIES(${TARGET_NAME} - iotdb_session - pthread - OpenSSL::SSL - OpenSSL::Crypto - ) - TARGET_LINK_LIBRARIES(${TARGET_NAME_RELATIONAL} - iotdb_session - pthread - OpenSSL::SSL - OpenSSL::Crypto - ) + FOREACH(T ${ALL_TEST_TARGETS}) + TARGET_LINK_LIBRARIES(${T} + iotdb_session + pthread + OpenSSL::SSL + OpenSSL::Crypto + ) + ENDFOREACH() ELSE() - TARGET_LINK_LIBRARIES(${TARGET_NAME} - iotdb_session - pthread - ) - TARGET_LINK_LIBRARIES(${TARGET_NAME_RELATIONAL} - iotdb_session - pthread - ) + FOREACH(T ${ALL_TEST_TARGETS}) + TARGET_LINK_LIBRARIES(${T} + iotdb_session + pthread + ) + ENDFOREACH() ENDIF() ENDIF() # Add Catch2 include directory -TARGET_INCLUDE_DIRECTORIES(${TARGET_NAME} PUBLIC ./catch2/) -TARGET_INCLUDE_DIRECTORIES(${TARGET_NAME_RELATIONAL} PUBLIC ./catch2/) +FOREACH(T ${ALL_TEST_TARGETS}) + TARGET_INCLUDE_DIRECTORIES(${T} PUBLIC ./catch2/) +ENDFOREACH() -# Add 'sessionIT' to the project to be run by ctest +# Add tests to ctest IF(MSVC) ADD_TEST(NAME sessionIT CONFIGURATIONS Release COMMAND ${TARGET_NAME}) ADD_TEST(NAME sessionRelationalIT CONFIGURATIONS Release COMMAND ${TARGET_NAME_RELATIONAL}) + ADD_TEST(NAME sessionCIT CONFIGURATIONS Release COMMAND ${TARGET_NAME_C}) + ADD_TEST(NAME sessionCRelationalIT CONFIGURATIONS Release COMMAND ${TARGET_NAME_C_RELATIONAL}) ELSE() ADD_TEST(NAME sessionIT COMMAND ${TARGET_NAME}) ADD_TEST(NAME sessionRelationalIT COMMAND ${TARGET_NAME_RELATIONAL}) + ADD_TEST(NAME sessionCIT COMMAND ${TARGET_NAME_C}) + ADD_TEST(NAME sessionCRelationalIT COMMAND ${TARGET_NAME_C_RELATIONAL}) ENDIF() +# One process at a time: parallel ctest overloads a single local IoTDB on 127.0.0.1:6667. +SET_TESTS_PROPERTIES(sessionIT sessionRelationalIT sessionCIT sessionCRelationalIT PROPERTIES RUN_SERIAL TRUE) + if(UNIX AND NOT APPLE) target_link_options(session_tests PRIVATE -Wl,--no-as-needed) target_link_options(session_relational_tests PRIVATE -Wl,--no-as-needed) + target_link_options(session_c_tests PRIVATE -Wl,--no-as-needed) + target_link_options(session_c_relational_tests PRIVATE -Wl,--no-as-needed) endif() diff --git a/iotdb-client/client-cpp/src/test/cpp/sessionCIT.cpp b/iotdb-client/client-cpp/src/test/cpp/sessionCIT.cpp new file mode 100644 index 0000000000000..bfe823a84fba3 --- /dev/null +++ b/iotdb-client/client-cpp/src/test/cpp/sessionCIT.cpp @@ -0,0 +1,733 @@ +/** + * 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 +#include +#include +#include +#include + +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 - 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* tpaths[] = {pb, pi, pf, pd, pt}; + 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); + + const char* dev = "root.cov_types.d1"; + const char* names[] = {"sb", "si", "sf", "sd", "st"}; + TSDataType_C types[] = {TS_TYPE_BOOLEAN, TS_TYPE_INT32, TS_TYPE_FLOAT, TS_TYPE_DOUBLE, TS_TYPE_TEXT}; + bool bv = true; + int32_t iv = 42; + float fv = 2.5f; + double dv = 3.25; + const char* tv = "hi"; + const void* vals[] = {&bv, &iv, &fv, &dv, tv}; + REQUIRE(ts_session_insert_record(g_session, dev, 500LL, 5, names, types, vals) == TS_OK); + + CSessionDataSet* dataSet = nullptr; + REQUIRE(ts_session_execute_query( + g_session, "select sb,si,sf,sd,st 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) == 5); + 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(std::string(ts_row_record_get_string(record, 4)) == "hi"); + REQUIRE(ts_row_record_get_data_type(record, 0) == TS_TYPE_BOOLEAN); + 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_database(g_session, sg) == TS_OK); +} diff --git a/iotdb-client/client-cpp/src/test/cpp/sessionCRelationalIT.cpp b/iotdb-client/client-cpp/src/test/cpp/sessionCRelationalIT.cpp new file mode 100644 index 0000000000000..4e0a559e706f5 --- /dev/null +++ b/iotdb-client/client-cpp/src/test/cpp/sessionCRelationalIT.cpp @@ -0,0 +1,307 @@ +/** + * 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 +#include +#include +#include + +extern CTableSession* g_table_session; + +static int global_test_tag = 0; + +class CaseReporter { +public: + CaseReporter(const char* caseNameArg) : caseName(caseNameArg) { + test_tag = global_test_tag++; + std::cout << "C-API Table Test " << test_tag << ": " << caseName << std::endl; + } + ~CaseReporter() { + std::cout << "C-API Table Test " << test_tag << ": " << caseName << " Done" << std::endl << std::endl; + } +private: + const char* caseName; + int test_tag; +}; + +/* ============================================================ + * DDL via SQL — create database & table + * ============================================================ */ + +TEST_CASE("C API Table - Create table", "[c_table_createTable][c_table_ddl]") { + CaseReporter cr("c_table_createTable"); + + ts_table_session_execute_non_query(g_table_session, "DROP DATABASE IF EXISTS c_db1"); + TsStatus status = ts_table_session_execute_non_query(g_table_session, "CREATE DATABASE c_db1"); + REQUIRE(status == TS_OK); + + ts_table_session_execute_non_query(g_table_session, "USE \"c_db1\""); + status = ts_table_session_execute_non_query(g_table_session, + "CREATE TABLE c_table0 (" + "tag1 string tag," + "attr1 string attribute," + "m1 double field)"); + REQUIRE(status == TS_OK); + + CSessionDataSet* dataSet = nullptr; + status = ts_table_session_execute_query(g_table_session, "SHOW TABLES", &dataSet); + REQUIRE(status == TS_OK); + REQUIRE(dataSet != nullptr); + + ts_dataset_set_fetch_size(dataSet, 1024); + bool tableExist = false; + while (ts_dataset_has_next(dataSet)) { + CRowRecord* record = ts_dataset_next(dataSet); + const char* tableName = ts_row_record_get_string(record, 0); + if (std::string(tableName) == "c_table0") { + tableExist = true; + } + ts_row_record_destroy(record); + if (tableExist) break; + } + REQUIRE(tableExist == true); + ts_dataset_destroy(dataSet); +} + +/* ============================================================ + * Insert Tablet (table model, with TAG/FIELD/ATTRIBUTE columns) + * ============================================================ */ + +TEST_CASE("C API Table - Insert tablet", "[c_table_insertTablet][c_table_write]") { + CaseReporter cr("c_table_insertTablet"); + + ts_table_session_execute_non_query(g_table_session, "DROP DATABASE IF EXISTS c_db2"); + ts_table_session_execute_non_query(g_table_session, "CREATE DATABASE c_db2"); + ts_table_session_execute_non_query(g_table_session, "USE \"c_db2\""); + ts_table_session_execute_non_query(g_table_session, + "CREATE TABLE c_table1 (" + "tag1 string tag," + "attr1 string attribute," + "m1 double field)"); + + const char* columnNames[] = {"tag1", "attr1", "m1"}; + TSDataType_C dataTypes[] = {TS_TYPE_STRING, TS_TYPE_STRING, TS_TYPE_DOUBLE}; + TSColumnCategory_C colCategories[] = {TS_COL_TAG, TS_COL_ATTRIBUTE, TS_COL_FIELD}; + + CTablet* tablet = ts_tablet_new_with_category("c_table1", 3, columnNames, dataTypes, + colCategories, 100); + REQUIRE(tablet != nullptr); + + for (int i = 0; i < 50; i++) { + ts_tablet_add_timestamp(tablet, i, (int64_t)i); + ts_tablet_add_value_string(tablet, 0, i, "device_A"); + ts_tablet_add_value_string(tablet, 1, i, "attr_val"); + ts_tablet_add_value_double(tablet, 2, i, i * 1.5); + } + ts_tablet_set_row_count(tablet, 50); + + TsStatus status = ts_table_session_insert(g_table_session, tablet); + REQUIRE(status == TS_OK); + + CSessionDataSet* dataSet = nullptr; + status = ts_table_session_execute_query(g_table_session, "SELECT * FROM c_table1", &dataSet); + REQUIRE(status == TS_OK); + + ts_dataset_set_fetch_size(dataSet, 1024); + 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); + ts_tablet_destroy(tablet); +} + +/* ============================================================ + * Query with timeout + * ============================================================ */ + +TEST_CASE("C API Table - Query with timeout", "[c_table_queryTimeout][c_table_query]") { + CaseReporter cr("c_table_queryTimeout"); + + ts_table_session_execute_non_query(g_table_session, "DROP DATABASE IF EXISTS c_db3"); + ts_table_session_execute_non_query(g_table_session, "CREATE DATABASE c_db3"); + ts_table_session_execute_non_query(g_table_session, "USE \"c_db3\""); + ts_table_session_execute_non_query(g_table_session, + "CREATE TABLE c_table2 (tag1 string tag, m1 int32 field)"); + + const char* columnNames[] = {"tag1", "m1"}; + TSDataType_C dataTypes[] = {TS_TYPE_STRING, TS_TYPE_INT32}; + TSColumnCategory_C colCategories[] = {TS_COL_TAG, TS_COL_FIELD}; + + CTablet* tablet = ts_tablet_new_with_category("c_table2", 2, columnNames, dataTypes, + colCategories, 10); + for (int i = 0; i < 10; i++) { + ts_tablet_add_timestamp(tablet, i, (int64_t)i); + ts_tablet_add_value_string(tablet, 0, i, "dev1"); + ts_tablet_add_value_int32(tablet, 1, i, i * 10); + } + ts_tablet_set_row_count(tablet, 10); + ts_table_session_insert(g_table_session, tablet); + ts_tablet_destroy(tablet); + + CSessionDataSet* dataSet = nullptr; + TsStatus status = ts_table_session_execute_query_with_timeout(g_table_session, + "SELECT * FROM c_table2", 60000, &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 == 10); + ts_dataset_destroy(dataSet); +} + +/* ============================================================ + * Multi-type tablet insert + * ============================================================ */ + +TEST_CASE("C API Table - Multi-type tablet", "[c_table_multiType][c_table_write]") { + CaseReporter cr("c_table_multiType"); + + ts_table_session_execute_non_query(g_table_session, "DROP DATABASE IF EXISTS c_db4"); + ts_table_session_execute_non_query(g_table_session, "CREATE DATABASE c_db4"); + ts_table_session_execute_non_query(g_table_session, "USE \"c_db4\""); + ts_table_session_execute_non_query(g_table_session, + "CREATE TABLE c_table3 (" + "tag1 string tag," + "m_bool boolean field," + "m_int32 int32 field," + "m_int64 int64 field," + "m_float float field," + "m_double double field," + "m_text text field)"); + + const char* columnNames[] = {"tag1", "m_bool", "m_int32", "m_int64", "m_float", "m_double", "m_text"}; + TSDataType_C dataTypes[] = {TS_TYPE_STRING, TS_TYPE_BOOLEAN, TS_TYPE_INT32, TS_TYPE_INT64, + TS_TYPE_FLOAT, TS_TYPE_DOUBLE, TS_TYPE_TEXT}; + TSColumnCategory_C colCategories[] = {TS_COL_TAG, TS_COL_FIELD, TS_COL_FIELD, TS_COL_FIELD, + TS_COL_FIELD, TS_COL_FIELD, TS_COL_FIELD}; + + CTablet* tablet = ts_tablet_new_with_category("c_table3", 7, columnNames, dataTypes, + colCategories, 20); + for (int i = 0; i < 20; i++) { + ts_tablet_add_timestamp(tablet, i, (int64_t)(i + 1000)); + ts_tablet_add_value_string(tablet, 0, i, "dev1"); + ts_tablet_add_value_bool(tablet, 1, i, (i % 2 == 0)); + ts_tablet_add_value_int32(tablet, 2, i, i * 10); + ts_tablet_add_value_int64(tablet, 3, i, (int64_t)i * 100); + ts_tablet_add_value_float(tablet, 4, i, i * 1.1f); + ts_tablet_add_value_double(tablet, 5, i, i * 2.2); + ts_tablet_add_value_string(tablet, 6, i, "hello"); + } + ts_tablet_set_row_count(tablet, 20); + + TsStatus status = ts_table_session_insert(g_table_session, tablet); + REQUIRE(status == TS_OK); + + CSessionDataSet* dataSet = nullptr; + status = ts_table_session_execute_query(g_table_session, "SELECT * FROM c_table3", &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 == 20); + ts_dataset_destroy(dataSet); + ts_tablet_destroy(tablet); +} + +/* ============================================================ + * Multi-node table session + * ============================================================ */ + +TEST_CASE("C API Table - Multi-node table session", "[c_table_multiNode][c_table_lifecycle]") { + CaseReporter cr("c_table_multiNode"); + + const char* urls[] = {"127.0.0.1:6667"}; + CTableSession* localSession = ts_table_session_new_multi_node(urls, 1, "root", "root", ""); + REQUIRE(localSession != nullptr); + + TsStatus status = ts_table_session_execute_non_query(localSession, "DROP DATABASE IF EXISTS c_db5"); + REQUIRE(status == TS_OK); + ts_table_session_execute_non_query(localSession, "CREATE DATABASE c_db5"); + + ts_table_session_close(localSession); + ts_table_session_destroy(localSession); +} + +/* ============================================================ + * Dataset column info (table model) + * ============================================================ */ + +TEST_CASE("C API Table - Dataset column info", "[c_table_datasetColumns][c_table_query]") { + CaseReporter cr("c_table_datasetColumns"); + + ts_table_session_execute_non_query(g_table_session, "DROP DATABASE IF EXISTS c_db6"); + ts_table_session_execute_non_query(g_table_session, "CREATE DATABASE c_db6"); + ts_table_session_execute_non_query(g_table_session, "USE \"c_db6\""); + ts_table_session_execute_non_query(g_table_session, + "CREATE TABLE c_table6 (tag1 string tag, m1 int64 field)"); + + const char* columnNames[] = {"tag1", "m1"}; + TSDataType_C dataTypes[] = {TS_TYPE_STRING, TS_TYPE_INT64}; + TSColumnCategory_C colCategories[] = {TS_COL_TAG, TS_COL_FIELD}; + + CTablet* tablet = ts_tablet_new_with_category("c_table6", 2, columnNames, dataTypes, + colCategories, 5); + for (int i = 0; i < 5; i++) { + ts_tablet_add_timestamp(tablet, i, (int64_t)i); + ts_tablet_add_value_string(tablet, 0, i, "dev1"); + ts_tablet_add_value_int64(tablet, 1, i, (int64_t)(i * 100)); + } + ts_tablet_set_row_count(tablet, 5); + ts_table_session_insert(g_table_session, tablet); + ts_tablet_destroy(tablet); + + CSessionDataSet* dataSet = nullptr; + ts_table_session_execute_query(g_table_session, "SELECT * FROM c_table6", &dataSet); + REQUIRE(dataSet != nullptr); + + int colCount = ts_dataset_get_column_count(dataSet); + REQUIRE(colCount >= 2); // at least time + tag1 + m1 + + for (int i = 0; i < colCount; i++) { + const char* colType = ts_dataset_get_column_type(dataSet, i); + REQUIRE(colType != nullptr); + REQUIRE(strlen(colType) > 0); + } + + if (ts_dataset_has_next(dataSet)) { + CRowRecord* record = ts_dataset_next(dataSet); + REQUIRE(record != nullptr); + REQUIRE(ts_row_record_get_field_count(record) >= 1); + (void)ts_row_record_get_timestamp(record); + (void)ts_row_record_get_data_type(record, 0); + (void)ts_row_record_is_null(record, 0); + ts_row_record_destroy(record); + } + + ts_dataset_destroy(dataSet); +} diff --git a/iotdb-client/client-cpp/src/test/main.cpp b/iotdb-client/client-cpp/src/test/main.cpp index c7cc1f692f021..44a4aec135855 100644 --- a/iotdb-client/client-cpp/src/test/main.cpp +++ b/iotdb-client/client-cpp/src/test/main.cpp @@ -23,30 +23,35 @@ #include "Session.h" #include "SessionBuilder.h" -auto builder = std::unique_ptr(new SessionBuilder()); -std::shared_ptr session = - std::shared_ptr( - builder - ->host("127.0.0.1") - ->rpcPort(6667) - ->username("root") - ->password("root") - ->useSSL(false) - ->build() - ); +std::shared_ptr session; struct SessionListener : Catch::TestEventListenerBase { using TestEventListenerBase::TestEventListenerBase; void testCaseStarting(Catch::TestCaseInfo const &testInfo) override { - // Perform some setup before a test case is run - session->open(false); + if (!session) { + SessionBuilder builder; + session = builder.host("127.0.0.1") + ->rpcPort(6667) + ->username("root") + ->password("root") + ->useSSL(false) + ->build(); + } else { + session->open(false); + } } void testCaseEnded(Catch::TestCaseStats const &testCaseStats) override { - // Tear-down after a test case is run - session->close(); + if (session) { + session->close(); + } + } + + void testRunEnded(Catch::TestRunStats const &testRunStats) override { + // Release session before static/global teardown on Windows. + session.reset(); } }; diff --git a/iotdb-client/client-cpp/src/test/main_Relational.cpp b/iotdb-client/client-cpp/src/test/main_Relational.cpp index ab028f0306af6..e45e7a38644d3 100644 --- a/iotdb-client/client-cpp/src/test/main_Relational.cpp +++ b/iotdb-client/client-cpp/src/test/main_Relational.cpp @@ -1,5 +1,5 @@ /** -* Licensed to the Apache Software Foundation (ASF) under one + * 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 @@ -22,28 +22,33 @@ #include #include "TableSessionBuilder.h" -auto builder = std::unique_ptr(new TableSessionBuilder()); -std::shared_ptr session = - std::shared_ptr( - builder - ->host("127.0.0.1") - ->rpcPort(6667) - ->username("root") - ->password("root") - ->build() - ); +std::shared_ptr session; struct SessionListener : Catch::TestEventListenerBase { using TestEventListenerBase::TestEventListenerBase; void testCaseStarting(Catch::TestCaseInfo const& testInfo) override { - // Perform some setup before a test case is run - session->open(); + if (!session) { + TableSessionBuilder builder; + session = builder.host("127.0.0.1") + ->rpcPort(6667) + ->username("root") + ->password("root") + ->build(); + } else { + session->open(); + } } void testCaseEnded(Catch::TestCaseStats const& testCaseStats) override { - // Tear-down after a test case is run - session->close(); + if (session) { + session->close(); + } + } + + void testRunEnded(Catch::TestRunStats const& testRunStats) override { + // Release session before static/global teardown on Windows. + session.reset(); } }; diff --git a/iotdb-client/client-cpp/src/test/main_c.cpp b/iotdb-client/client-cpp/src/test/main_c.cpp new file mode 100644 index 0000000000000..5a712d0c83034 --- /dev/null +++ b/iotdb-client/client-cpp/src/test/main_c.cpp @@ -0,0 +1,51 @@ +/** + * 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. + */ + +#define CATCH_CONFIG_MAIN + +#include +#include "SessionC.h" + +// Global session handle used by the C API tree-model tests +CSession* g_session = nullptr; + +struct CSessionListener : Catch::TestEventListenerBase { + using TestEventListenerBase::TestEventListenerBase; + + void testCaseStarting(Catch::TestCaseInfo const& testInfo) override { + g_session = ts_session_new("127.0.0.1", 6667, "root", "root"); + REQUIRE(g_session != nullptr); + TsStatus st = ts_session_open(g_session); + if (st != TS_OK) { + ts_session_destroy(g_session); + g_session = nullptr; + FAIL("ts_session_open failed; ensure distribution is built and IoTDB listens on 127.0.0.1:6667"); + } + } + + void testCaseEnded(Catch::TestCaseStats const& testCaseStats) override { + if (g_session) { + ts_session_close(g_session); + ts_session_destroy(g_session); + g_session = nullptr; + } + } +}; + +CATCH_REGISTER_LISTENER(CSessionListener) diff --git a/iotdb-client/client-cpp/src/test/main_c_Relational.cpp b/iotdb-client/client-cpp/src/test/main_c_Relational.cpp new file mode 100644 index 0000000000000..80067663bbbb9 --- /dev/null +++ b/iotdb-client/client-cpp/src/test/main_c_Relational.cpp @@ -0,0 +1,51 @@ +/** + * 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. + */ + +#define CATCH_CONFIG_MAIN + +#include +#include "SessionC.h" + +// Global table session handle used by the C API table-model tests +CTableSession* g_table_session = nullptr; + +struct CTableSessionListener : Catch::TestEventListenerBase { + using TestEventListenerBase::TestEventListenerBase; + + void testCaseStarting(Catch::TestCaseInfo const& testInfo) override { + g_table_session = ts_table_session_new("127.0.0.1", 6667, "root", "root", ""); + REQUIRE(g_table_session != nullptr); + TsStatus st = ts_table_session_open(g_table_session); + if (st != TS_OK) { + ts_table_session_destroy(g_table_session); + g_table_session = nullptr; + FAIL("ts_table_session_open failed; ensure distribution is built and IoTDB listens on 127.0.0.1:6667"); + } + } + + void testCaseEnded(Catch::TestCaseStats const& testCaseStats) override { + if (g_table_session) { + ts_table_session_close(g_table_session); + ts_table_session_destroy(g_table_session); + g_table_session = nullptr; + } + } +}; + +CATCH_REGISTER_LISTENER(CTableSessionListener)