diff --git a/example/client-cpp-example/src/CMakeLists.txt b/example/client-cpp-example/src/CMakeLists.txt index 6d87631ab576b..1ee249bfb803e 100644 --- a/example/client-cpp-example/src/CMakeLists.txt +++ b/example/client-cpp-example/src/CMakeLists.txt @@ -28,12 +28,23 @@ INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/thrift/include) # Add cpp-client include directory INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/client/include) -FIND_PACKAGE(OpenSSL REQUIRED) -IF(OpenSSL_FOUND) - MESSAGE(STATUS "OpenSSL found: ${OPENSSL_VERSION}") - INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR}) +# ========================= +# SSL option (default OFF) +# ========================= +option(WITH_SSL "Build with SSL support" OFF) + +IF(WITH_SSL) + FIND_PACKAGE(OpenSSL REQUIRED) + IF(OpenSSL_FOUND) + MESSAGE(STATUS "OpenSSL found: ${OPENSSL_VERSION}") + INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR}) + ADD_DEFINITIONS(-DWITH_SSL=1) + ELSE() + MESSAGE(FATAL_ERROR "OpenSSL not found, but WITH_SSL is enabled") + ENDIF() ELSE() - MESSAGE(FATAL_ERROR "OpenSSL not found") + MESSAGE(STATUS "Building without SSL support") + ADD_DEFINITIONS(-DWITH_SSL=0) ENDIF() FIND_PACKAGE(Boost REQUIRED) @@ -50,53 +61,91 @@ ADD_EXECUTABLE(TableModelSessionExample TableModelSessionExample.cpp) ADD_EXECUTABLE(MultiSvrNodeClient MultiSvrNodeClient.cpp) IF(MSVC) - TARGET_LINK_LIBRARIES(SessionExample - iotdb_session - "${CMAKE_SOURCE_DIR}/thrift/lib/Release/thriftmd.lib" - OpenSSL::SSL - OpenSSL::Crypto - ) - TARGET_LINK_LIBRARIES(AlignedTimeseriesSessionExample - iotdb_session - "${CMAKE_SOURCE_DIR}/thrift/lib/Release/thriftmd.lib" - OpenSSL::SSL - OpenSSL::Crypto - ) - TARGET_LINK_LIBRARIES(TableModelSessionExample - iotdb_session - "${CMAKE_SOURCE_DIR}/thrift/lib/Release/thriftmd.lib" - OpenSSL::SSL - OpenSSL::Crypto - ) - TARGET_LINK_LIBRARIES(MultiSvrNodeClient - iotdb_session - "${CMAKE_SOURCE_DIR}/thrift/lib/Release/thriftmd.lib" - OpenSSL::SSL - OpenSSL::Crypto - ) + IF(WITH_SSL) + TARGET_LINK_LIBRARIES(SessionExample + iotdb_session + "${CMAKE_SOURCE_DIR}/thrift/lib/Release/thriftmd.lib" + OpenSSL::SSL + OpenSSL::Crypto + ) + TARGET_LINK_LIBRARIES(AlignedTimeseriesSessionExample + iotdb_session + "${CMAKE_SOURCE_DIR}/thrift/lib/Release/thriftmd.lib" + OpenSSL::SSL + OpenSSL::Crypto + ) + TARGET_LINK_LIBRARIES(TableModelSessionExample + iotdb_session + "${CMAKE_SOURCE_DIR}/thrift/lib/Release/thriftmd.lib" + OpenSSL::SSL + OpenSSL::Crypto + ) + TARGET_LINK_LIBRARIES(MultiSvrNodeClient + iotdb_session + "${CMAKE_SOURCE_DIR}/thrift/lib/Release/thriftmd.lib" + OpenSSL::SSL + OpenSSL::Crypto + ) + ELSE() + TARGET_LINK_LIBRARIES(SessionExample + iotdb_session + "${CMAKE_SOURCE_DIR}/thrift/lib/Release/thriftmd.lib" + ) + TARGET_LINK_LIBRARIES(AlignedTimeseriesSessionExample + iotdb_session + "${CMAKE_SOURCE_DIR}/thrift/lib/Release/thriftmd.lib" + ) + TARGET_LINK_LIBRARIES(TableModelSessionExample + iotdb_session + "${CMAKE_SOURCE_DIR}/thrift/lib/Release/thriftmd.lib" + ) + TARGET_LINK_LIBRARIES(MultiSvrNodeClient + iotdb_session + "${CMAKE_SOURCE_DIR}/thrift/lib/Release/thriftmd.lib" + ) + ENDIF() ELSE() - TARGET_LINK_LIBRARIES(SessionExample - iotdb_session - pthread - OpenSSL::SSL - OpenSSL::Crypto - ) - TARGET_LINK_LIBRARIES(AlignedTimeseriesSessionExample - iotdb_session - pthread - OpenSSL::SSL - OpenSSL::Crypto - ) - TARGET_LINK_LIBRARIES(TableModelSessionExample - iotdb_session - pthread - OpenSSL::SSL - OpenSSL::Crypto - ) - TARGET_LINK_LIBRARIES(MultiSvrNodeClient - iotdb_session - pthread - OpenSSL::SSL - OpenSSL::Crypto - ) -ENDIF() \ No newline at end of file + IF(WITH_SSL) + TARGET_LINK_LIBRARIES(SessionExample + iotdb_session + pthread + OpenSSL::SSL + OpenSSL::Crypto + ) + TARGET_LINK_LIBRARIES(AlignedTimeseriesSessionExample + iotdb_session + pthread + OpenSSL::SSL + OpenSSL::Crypto + ) + TARGET_LINK_LIBRARIES(TableModelSessionExample + iotdb_session + pthread + OpenSSL::SSL + OpenSSL::Crypto + ) + TARGET_LINK_LIBRARIES(MultiSvrNodeClient + iotdb_session + pthread + OpenSSL::SSL + OpenSSL::Crypto + ) + ELSE() + TARGET_LINK_LIBRARIES(SessionExample + iotdb_session + pthread + ) + TARGET_LINK_LIBRARIES(AlignedTimeseriesSessionExample + iotdb_session + pthread + ) + TARGET_LINK_LIBRARIES(TableModelSessionExample + iotdb_session + pthread + ) + TARGET_LINK_LIBRARIES(MultiSvrNodeClient + iotdb_session + pthread + ) + ENDIF() +ENDIF() diff --git a/iotdb-client/client-cpp/pom.xml b/iotdb-client/client-cpp/pom.xml index c307c651fba8b..bf0f92204e41e 100644 --- a/iotdb-client/client-cpp/pom.xml +++ b/iotdb-client/client-cpp/pom.xml @@ -38,6 +38,7 @@ ${project.build.directory}/dependency/cmake/ ${project.build.directory}/thrift/bin/${thrift.executable} ${ctest.skip.tests} + false @@ -217,6 +218,7 @@ ${project.build.directory}/build/main + @@ -233,6 +235,7 @@ ${project.build.directory}/build/test + diff --git a/iotdb-client/client-cpp/src/main/CMakeLists.txt b/iotdb-client/client-cpp/src/main/CMakeLists.txt index 7945cd887d37f..8119c3830f4b7 100644 --- a/iotdb-client/client-cpp/src/main/CMakeLists.txt +++ b/iotdb-client/client-cpp/src/main/CMakeLists.txt @@ -26,13 +26,23 @@ SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -g -O2 ") # Add Thrift include directory INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/../../thrift/include) -# Find OpenSSL Library -FIND_PACKAGE(OpenSSL REQUIRED) -IF(OpenSSL_FOUND) - MESSAGE(STATUS "OpenSSL found: ${OPENSSL_VERSION}") - INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR}) +# ========================= +# SSL option (default OFF) +# ========================= +option(WITH_SSL "Build with SSL support" OFF) + +IF(WITH_SSL) + FIND_PACKAGE(OpenSSL REQUIRED) + IF(OpenSSL_FOUND) + MESSAGE(STATUS "OpenSSL found: ${OPENSSL_VERSION}") + INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR}) + ADD_DEFINITIONS(-DWITH_SSL=1) + ELSE() + MESSAGE(FATAL_ERROR "OpenSSL not found, but WITH_SSL is enabled") + ENDIF() ELSE() - MESSAGE(FATAL_ERROR "OpenSSL not found") + MESSAGE(STATUS "Building without SSL support") + ADD_DEFINITIONS(-DWITH_SSL=0) ENDIF() # Add Boost include path for MacOS @@ -51,8 +61,8 @@ ELSE() ENDIF() IF(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang" AND NOT MSVC) - add_compile_options(-fsanitize=address -fno-omit-frame-pointer) - add_link_options(-fsanitize=address) + #add_compile_options(-fsanitize=address -fno-omit-frame-pointer) + #add_link_options(-fsanitize=address) ENDIF() # Add the generated source files to the sources for the library. @@ -63,10 +73,20 @@ ELSE() ADD_LIBRARY(iotdb_session SHARED ${SESSION_SRCS}) ENDIF() -# Link with Thrift static library -target_link_libraries(iotdb_session - PUBLIC - OpenSSL::SSL - OpenSSL::Crypto - ${THRIFT_STATIC_LIB} -) +# ========================= +# Link libraries (SSL optional) +# ========================= +IF(WITH_SSL) + target_link_libraries(iotdb_session + PUBLIC + OpenSSL::SSL + OpenSSL::Crypto + ${THRIFT_STATIC_LIB} + ) +ELSE() + target_link_libraries(iotdb_session + PUBLIC + ${THRIFT_STATIC_LIB} + ) +ENDIF() + 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..1c502b0fac93f --- /dev/null +++ b/iotdb-client/client-cpp/src/main/SessionC.cpp @@ -0,0 +1,1417 @@ +/** + * 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; + std::shared_ptr innerSession; // kept alive for lifetime +}; + +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) { + const std::string msg = e.what(); + // Try to classify exception type + 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); + } + return setError(TS_ERR_UNKNOWN, e); +} + +extern "C" { + +const char* ts_get_last_error(void) { + return g_lastError.c_str(); +} + +/* ============================================================ + * 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 + * ============================================================ */ + +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/main/SessionConnection.cpp b/iotdb-client/client-cpp/src/main/SessionConnection.cpp index 0ab22cc79a600..7ed903d24fb41 100644 --- a/iotdb-client/client-cpp/src/main/SessionConnection.cpp +++ b/iotdb-client/client-cpp/src/main/SessionConnection.cpp @@ -100,11 +100,17 @@ SessionConnection::~SessionConnection() { void SessionConnection::init(const TEndPoint& endpoint, bool useSSL, const std::string& trustCertFilePath) { if (useSSL) { +#if WITH_SSL socketFactory_->loadTrustedCertificates(trustCertFilePath.c_str()); socketFactory_->authenticate(false); auto sslSocket = socketFactory_->createSocket(endPoint.ip, endPoint.port); sslSocket->setConnTimeout(connectionTimeoutInMs); transport = std::make_shared(sslSocket); +#else + throw IoTDBException("SSL/TLS support is not enabled in this build. " + "Please rebuild with -DWITH_SSL=ON flag " + "or use non-SSL connection."); +#endif } else { auto socket = std::make_shared(endPoint.ip, endPoint.port); socket->setConnTimeout(connectionTimeoutInMs); diff --git a/iotdb-client/client-cpp/src/main/SessionConnection.h b/iotdb-client/client-cpp/src/main/SessionConnection.h index 297898ca9a233..a4ef7a7d64c48 100644 --- a/iotdb-client/client-cpp/src/main/SessionConnection.h +++ b/iotdb-client/client-cpp/src/main/SessionConnection.h @@ -23,7 +23,10 @@ #include #include #include +#if WITH_SSL #include +#endif + #include "IClientRPCService.h" #include "common_types.h" #include "NodesSupplier.h" @@ -179,9 +182,10 @@ class SessionConnection : public std::enable_shared_from_this TSStatus insertTabletsInternal(TSInsertTabletsReq request); TSStatus deleteDataInternal(TSDeleteDataReq request); - +#if WITH_SSL std::shared_ptr socketFactory_ = std::make_shared(); +#endif std::shared_ptr transport; std::shared_ptr client; Session* session; diff --git a/iotdb-client/client-cpp/src/main/ThriftConnection.cpp b/iotdb-client/client-cpp/src/main/ThriftConnection.cpp index 2cb52bed6075b..3a7989a5e7654 100644 --- a/iotdb-client/client-cpp/src/main/ThriftConnection.cpp +++ b/iotdb-client/client-cpp/src/main/ThriftConnection.cpp @@ -69,11 +69,17 @@ void ThriftConnection::init(const std::string& username, const std::string& zoneId, const std::string& version) { if (useSSL) { +#if WITH_SSL socketFactory_->loadTrustedCertificates(trustCertFilePath.c_str()); socketFactory_->authenticate(false); auto sslSocket = socketFactory_->createSocket(endPoint_.ip, endPoint_.port); sslSocket->setConnTimeout(connectionTimeoutInMs_); transport_ = std::make_shared(sslSocket); +#else + throw IoTDBException("SSL/TLS support is not enabled in this build. " + "Please rebuild with -DWITH_SSL=ON flag " + "or use non-SSL connection."); +#endif } else { auto socket = std::make_shared(endPoint_.ip, endPoint_.port); socket->setConnTimeout(connectionTimeoutInMs_); diff --git a/iotdb-client/client-cpp/src/main/ThriftConnection.h b/iotdb-client/client-cpp/src/main/ThriftConnection.h index a308c95e3a202..6dae68357fdbc 100644 --- a/iotdb-client/client-cpp/src/main/ThriftConnection.h +++ b/iotdb-client/client-cpp/src/main/ThriftConnection.h @@ -20,7 +20,9 @@ #define IOTDB_THRIFTCONNECTION_H #include +#if WITH_SSL #include +#endif #include "IClientRPCService.h" class SessionDataSet; @@ -60,8 +62,10 @@ class ThriftConnection { int connectionTimeoutInMs_; int fetchSize_; +#if WITH_SSL std::shared_ptr socketFactory_ = std::make_shared(); +#endif std::shared_ptr transport_; std::shared_ptr client_; int64_t sessionId_{}; diff --git a/iotdb-client/client-cpp/src/test/CMakeLists.txt b/iotdb-client/client-cpp/src/test/CMakeLists.txt index ab225f8efb9f5..241cb12bb75c9 100644 --- a/iotdb-client/client-cpp/src/test/CMakeLists.txt +++ b/iotdb-client/client-cpp/src/test/CMakeLists.txt @@ -25,13 +25,23 @@ SET(TARGET_NAME_RELATIONAL session_relational_tests) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -g -O2") ENABLE_TESTING() -# OpenSSL -FIND_PACKAGE(OpenSSL REQUIRED) -IF(OpenSSL_FOUND) - MESSAGE(STATUS "OpenSSL found: ${OPENSSL_VERSION}") - INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR}) +# ========================= +# SSL option (default OFF) +# ========================= +option(WITH_SSL "Build with SSL support" OFF) + +IF(WITH_SSL) + FIND_PACKAGE(OpenSSL REQUIRED) + IF(OpenSSL_FOUND) + MESSAGE(STATUS "OpenSSL found: ${OPENSSL_VERSION}") + INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR}) + ADD_DEFINITIONS(-DWITH_SSL=1) + ELSE() + MESSAGE(FATAL_ERROR "OpenSSL not found, but WITH_SSL is enabled") + ENDIF() ELSE() - MESSAGE(FATAL_ERROR "OpenSSL not found") + MESSAGE(STATUS "Building without SSL support") + ADD_DEFINITIONS(-DWITH_SSL=0) ENDIF() # Add Boost include path for MacOS @@ -61,52 +71,76 @@ 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) - 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 - ) + IF(WITH_SSL) + FOREACH(T ${ALL_TEST_TARGETS}) + TARGET_LINK_LIBRARIES(${T} + iotdb_session + ${THRIFT_STATIC_LIB} + OpenSSL::SSL + OpenSSL::Crypto + ) + ENDFOREACH() + ELSE() + FOREACH(T ${ALL_TEST_TARGETS}) + TARGET_LINK_LIBRARIES(${T} + iotdb_session + ${THRIFT_STATIC_LIB} + ) + ENDFOREACH() + ENDIF() ELSE() - 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 - ) + IF(WITH_SSL) + FOREACH(T ${ALL_TEST_TARGETS}) + TARGET_LINK_LIBRARIES(${T} + iotdb_session + pthread + OpenSSL::SSL + OpenSSL::Crypto + ) + ENDFOREACH() + ELSE() + 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() 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..13a847355eb42 --- /dev/null +++ b/iotdb-client/client-cpp/src/test/cpp/sessionCIT.cpp @@ -0,0 +1,429 @@ +/** + * 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 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 prepareTimeseries() { + for (int i = 0; i < testTimeseriesCount; i++) { + bool exists = false; + ts_session_check_timeseries_exists(g_session, testTimeseries[i], &exists); + if (exists) { + ts_session_delete_timeseries(g_session, testTimeseries[i]); + } + ts_session_create_timeseries(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"; + bool exists = false; + ts_session_check_timeseries_exists(g_session, path, &exists); + if (!exists) { + TsStatus status = ts_session_create_timeseries(g_session, path, + TS_TYPE_INT64, TS_ENCODING_RLE, TS_COMPRESSION_SNAPPY); + REQUIRE(status == TS_OK); + } + ts_session_check_timeseries_exists(g_session, path, &exists); + REQUIRE(exists == true); + ts_session_delete_timeseries(g_session, path); +} + +TEST_CASE("C API - Delete timeseries", "[c_deleteTimeseries]") { + CaseReporter cr("c_deleteTimeseries"); + const char* path = "root.ctest.d1.s1"; + + bool exists = false; + ts_session_check_timeseries_exists(g_session, path, &exists); + if (!exists) { + ts_session_create_timeseries(g_session, path, TS_TYPE_INT64, TS_ENCODING_RLE, TS_COMPRESSION_SNAPPY); + } + ts_session_check_timeseries_exists(g_session, path, &exists); + REQUIRE(exists == true); + + TsStatus status = ts_session_delete_timeseries(g_session, path); + REQUIRE(status == TS_OK); + ts_session_check_timeseries_exists(g_session, path, &exists); + REQUIRE(exists == false); +} + +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++) { + bool exists = false; + ts_session_check_timeseries_exists(g_session, timeseries[i], &exists); + if (exists) ts_session_delete_timeseries(g_session, timeseries[i]); + ts_session_create_timeseries(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); + } + + CSessionDataSet* dataSet = nullptr; + ts_session_execute_query(g_session, "select s1,s2,s3 from root.ctest.d1", &dataSet); + 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 == 100); + ts_dataset_destroy(dataSet); +} + +/* ============================================================ + * 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); + + CSessionDataSet* dataSet = nullptr; + ts_session_execute_query(g_session, "select s1,s2,s3 from root.ctest.d1", &dataSet); + 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 == BATCH); + ts_dataset_destroy(dataSet); +} + +/* ============================================================ + * 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; + ts_session_execute_query(g_session, "select s1,s2,s3 from root.ctest.d1", &dataSet); + 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); + + CSessionDataSet* dataSet = nullptr; + ts_session_execute_query(g_session, "select s1,s2,s3 from root.ctest.d1", &dataSet); + 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); +} + +/* ============================================================ + * 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"; + bool exists = false; + ts_session_check_timeseries_exists(localSession, path, &exists); + if (!exists) { + ts_session_create_timeseries(localSession, path, TS_TYPE_INT64, TS_ENCODING_RLE, TS_COMPRESSION_SNAPPY); + } + ts_session_check_timeseries_exists(localSession, path, &exists); + REQUIRE(exists == true); + ts_session_delete_timeseries(localSession, path); + + 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"); + + ts_dataset_destroy(dataSet); +} 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..3957e9fe03bb9 --- /dev/null +++ b/iotdb-client/client-cpp/src/test/cpp/sessionCRelationalIT.cpp @@ -0,0 +1,291 @@ +/** + * 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]") { + 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]") { + 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]") { + 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]") { + 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]") { + 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]") { + 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 + + ts_dataset_destroy(dataSet); +} 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..fa3f9cb88125e --- /dev/null +++ b/iotdb-client/client-cpp/src/test/main_c.cpp @@ -0,0 +1,45 @@ +/** + * 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"); + ts_session_open(g_session); + } + + 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..22d033ada68c6 --- /dev/null +++ b/iotdb-client/client-cpp/src/test/main_c_Relational.cpp @@ -0,0 +1,44 @@ +/** + * 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", ""); + } + + 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)