diff --git a/.github/scripts/native-linux-env.sh b/.github/scripts/native-linux-env.sh
new file mode 100644
index 0000000000000..74493cd92d4a1
--- /dev/null
+++ b/.github/scripts/native-linux-env.sh
@@ -0,0 +1,20 @@
+# 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.
+# Shared Linux release baseline for the C++ SDK and ODBC driver.
+NATIVE_GLIBC_BASELINE=2.28
+NATIVE_GCC_MIN_MAJOR=14
+NATIVE_LINUX_IMAGE="quay.io/pypa/manylinux_2_28_$(uname -m)"
diff --git a/.github/scripts/package-client-cpp-manylinux228.sh b/.github/scripts/package-client-cpp-manylinux228.sh
index 6bfef0aa415b6..a5506854f7ac6 100644
--- a/.github/scripts/package-client-cpp-manylinux228.sh
+++ b/.github/scripts/package-client-cpp-manylinux228.sh
@@ -16,6 +16,7 @@
#
# Build client-cpp in manylinux_2_28 and verify max required GLIBC symbol <= 2.28.
set -euxo pipefail
+source "$(dirname "${BASH_SOURCE[0]}")/native-linux-env.sh"
MACHINE=$(uname -m)
case "${MACHINE}" in
@@ -64,7 +65,7 @@ export JAVA_HOME
gcc --version
c++ --version
gcc_major=$(gcc -dumpversion | cut -d. -f1)
-if (( gcc_major < 14 )); then
+if (( gcc_major < NATIVE_GCC_MIN_MAJOR )); then
echo "ERROR: GCC >= 14 is required; got $(gcc -dumpversion)"
exit 1
fi
@@ -78,9 +79,9 @@ java -version
# modules (IPC::Cmd, Data::Dumper) that are not on the minimal image - install
# them even when perl itself is already present.
if command -v dnf >/dev/null 2>&1; then
- dnf install -y perl perl-IPC-Cmd perl-Data-Dumper
+ dnf install -y perl perl-IPC-Cmd perl-Data-Dumper perl-Time-Piece
else
- yum install -y perl perl-IPC-Cmd perl-Data-Dumper
+ yum install -y perl perl-IPC-Cmd perl-Data-Dumper perl-Time-Piece
fi
cd "${GITHUB_WORKSPACE:?GITHUB_WORKSPACE is not set}"
@@ -106,8 +107,9 @@ if [[ -z "${max_glibc}" ]]; then
exit 1
fi
-if awk -v max="${max_glibc}" "BEGIN { exit !(max > 2.28) }"; then
- echo "ERROR: libiotdb_session.so requires glibc > 2.28 (max=${max_glibc})"
+highest_glibc=$(printf '%s\n' "${max_glibc}" "${NATIVE_GLIBC_BASELINE}" | sort -V | tail -1)
+if [[ "${highest_glibc}" != "${NATIVE_GLIBC_BASELINE}" ]]; then
+ echo "ERROR: libiotdb_session.so requires glibc > ${NATIVE_GLIBC_BASELINE} (max=${max_glibc})"
exit 1
fi
diff --git a/.github/scripts/package-odbc-manylinux228.sh b/.github/scripts/package-odbc-manylinux228.sh
new file mode 100644
index 0000000000000..3a458f1a9407c
--- /dev/null
+++ b/.github/scripts/package-odbc-manylinux228.sh
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+# 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.
+set -euo pipefail
+source "$(dirname "${BASH_SOURCE[0]}")/native-linux-env.sh"
+if (( $(gcc -dumpversion | cut -d. -f1) < NATIVE_GCC_MIN_MAJOR )); then
+ echo "GCC ${NATIVE_GCC_MIN_MAJOR}+ is required" >&2
+ exit 1
+fi
+dnf install -y unixODBC-devel perl-IPC-Cmd perl-Data-Dumper perl-Time-Piece
+odbc_build="${ODBC_BUILD_DIR:-iotdb-client/odbc/target/build}"
+cmake -S iotdb-client/odbc -B "${odbc_build}" \
+ -DCMAKE_BUILD_TYPE=Release -DCMAKE_SKIP_RPATH=ON \
+ -DODBC_PACKAGE_VERSION="${ODBC_PACKAGE_VERSION:-$(sed -n 's:.*\([^<]*\).*:\1:p' iotdb-client/pom.xml | head -1)}" \
+ -DODBC_PACKAGE_CLASSIFIER="linux-x86_64-glibc${NATIVE_GLIBC_BASELINE}"
+cmake --build "${odbc_build}" --parallel "${CMAKE_BUILD_PARALLEL_LEVEL:-4}"
+ctest --test-dir "${odbc_build}" --output-on-failure
+cmake -DDRIVER="${odbc_build}/libapache_iotdb_odbc.so" \
+ -DGLIBC_BASELINE="${NATIVE_GLIBC_BASELINE}" -P iotdb-client/odbc/cmake/CheckLinuxPackage.cmake
+(cd "${odbc_build}" && cpack)
diff --git a/.github/workflows/client-cpp-package.yml b/.github/workflows/client-cpp-package.yml
index 70f2bffdddda6..c59665f2b1e26 100644
--- a/.github/workflows/client-cpp-package.yml
+++ b/.github/workflows/client-cpp-package.yml
@@ -64,7 +64,7 @@ jobs:
cpp=false
while IFS= read -r file; do
case "$file" in
- iotdb-client/client-cpp/*|iotdb-client/pom.xml|iotdb-protocol/thrift-datanode/src/main/thrift/client.thrift|iotdb-protocol/thrift-commons/src/main/thrift/common.thrift|.github/workflows/client-cpp-package.yml|.github/scripts/package-client-cpp-*.sh)
+ iotdb-client/client-cpp/*|iotdb-client/pom.xml|iotdb-protocol/thrift-datanode/src/main/thrift/client.thrift|iotdb-protocol/thrift-commons/src/main/thrift/common.thrift|.github/workflows/client-cpp-package.yml|.github/scripts/package-client-cpp-*.sh|.github/scripts/native-linux-env.sh)
cpp=true
break
;;
@@ -140,7 +140,7 @@ jobs:
echo "run_windows=${run_windows}" >> "$GITHUB_OUTPUT"
# Compact JSON (no leading whitespace); required for GITHUB_OUTPUT + fromJSON().
- WINDOWS_MATRIX='[{"name":"windows-vs2026","runs-on":"windows-2025-vs2026","boost_choco":"boost-msvc-14.3","boost_choco_version":"","cmake_generator":"Visual Studio 18 2026","package_classifier":"windows-x86_64-msvc14.4","vs_choco":"","vs_choco_params":""},{"name":"windows-vs2022","runs-on":"windows-2022","boost_choco":"boost-msvc-14.3","boost_choco_version":"","cmake_generator":"","package_classifier":"windows-x86_64-msvc14.3","vs_choco":"","vs_choco_params":""},{"name":"windows-vs2019","runs-on":"windows-2022","boost_choco":"boost-msvc-14.2","boost_choco_version":"","cmake_generator":"Visual Studio 16 2019","package_classifier":"windows-x86_64-msvc14.2","vs_choco":"visualstudio2019buildtools","vs_choco_params":"--add Microsoft.VisualStudio.Workload.VCTools --includeRecommended"},{"name":"windows-vs2017","runs-on":"windows-2022","boost_choco":"boost-msvc-14.1","boost_choco_version":"1.74.0","cmake_generator":"Visual Studio 15 2017","package_classifier":"windows-x86_64-msvc14.1","vs_choco":"visualstudio2017buildtools","vs_choco_params":"--add Microsoft.VisualStudio.Workload.VCTools --includeRecommended"}]'
+ WINDOWS_MATRIX='[{"name":"windows-vs2026","runs-on":"windows-2025-vs2026","cmake_generator":"Visual Studio 18 2026","package_classifier":"windows-x86_64-msvc14.4","vs_choco":"","vs_choco_params":""},{"name":"windows-vs2022","runs-on":"windows-2022","cmake_generator":"","package_classifier":"windows-x86_64-msvc14.3","vs_choco":"","vs_choco_params":""},{"name":"windows-vs2019","runs-on":"windows-2022","cmake_generator":"Visual Studio 16 2019","package_classifier":"windows-x86_64-msvc14.2","vs_choco":"visualstudio2019buildtools","vs_choco_params":"--add Microsoft.VisualStudio.Workload.VCTools --includeRecommended"},{"name":"windows-vs2017","runs-on":"windows-2022","cmake_generator":"Visual Studio 15 2017","package_classifier":"windows-x86_64-msvc14.1","vs_choco":"visualstudio2017buildtools","vs_choco_params":"--add Microsoft.VisualStudio.Workload.VCTools --includeRecommended"}]'
write_windows_matrix_output() {
local matrix_json="$1"
@@ -189,12 +189,13 @@ jobs:
run: |
set -euxo pipefail
chmod +x .github/scripts/package-client-cpp-manylinux228.sh
+ source .github/scripts/native-linux-env.sh
docker run --rm \
-v "${{ github.workspace }}:/workspace" \
-v "${HOME}/.m2:/root/.m2" \
-w /workspace \
-e GITHUB_WORKSPACE=/workspace \
- quay.io/pypa/manylinux_2_28_x86_64 \
+ "${NATIVE_LINUX_IMAGE}" \
bash .github/scripts/package-client-cpp-manylinux228.sh
- name: Restore workspace ownership after container build
if: always()
@@ -247,12 +248,13 @@ jobs:
run: |
set -euxo pipefail
chmod +x .github/scripts/package-client-cpp-manylinux228.sh
+ source .github/scripts/native-linux-env.sh
docker run --rm \
-v "${{ github.workspace }}:/workspace" \
-v "${HOME}/.m2:/root/.m2" \
-w /workspace \
-e GITHUB_WORKSPACE=/workspace \
- quay.io/pypa/manylinux_2_28_aarch64 \
+ "${NATIVE_LINUX_IMAGE}" \
bash .github/scripts/package-client-cpp-manylinux228.sh
- name: Restore workspace ownership after container build
if: always()
@@ -432,20 +434,11 @@ jobs:
shell: pwsh
run: |
choco install winflexbison3 strawberryperl -y --no-progress
+ if ($LASTEXITCODE) { exit $LASTEXITCODE }
echo 'C:\Strawberry\perl\bin' >> $env:GITHUB_PATH
- $boostArgs = @('install', '${{ matrix.boost_choco }}', '-y', '--no-progress')
- if ('${{ matrix.boost_choco_version }}' -ne '') {
- $boostArgs += @("--version=${{ matrix.boost_choco_version }}")
- }
- & choco @boostArgs
- if (-not (Test-Path 'C:\local')) {
- New-Item -ItemType Directory -Path 'C:\local' -Force | Out-Null
- }
- $boostDir = Get-ChildItem -Path 'C:\local\' -Filter 'boost_*' -ErrorAction SilentlyContinue | Select-Object -First 1
- if (-not $boostDir) {
- throw "Boost not found under C:\local after installing ${{ matrix.boost_choco }}"
- }
- echo $boostDir.FullName >> $env:GITHUB_PATH
+ # Thrift only needs Boost headers. FetchBoost.cmake provides the same
+ # source headers for every toolchain; precompiled Chocolatey Boost
+ # installers add an unnecessary download and can fail checksum checks.
- name: Cache Maven packages
uses: actions/cache@v5
with:
diff --git a/.github/workflows/multi-language-client.yml b/.github/workflows/multi-language-client.yml
index b49b63b2e5c77..4b0695c1cf878 100644
--- a/.github/workflows/multi-language-client.yml
+++ b/.github/workflows/multi-language-client.yml
@@ -15,6 +15,7 @@ on:
- '.github/workflows/multi-language-client.yml'
- '.github/workflows/client-cpp-package.yml'
- '.github/scripts/package-client-cpp-*.sh'
+ - '.github/scripts/native-linux-env.sh'
pull_request:
branches:
- master
@@ -30,6 +31,7 @@ on:
- '.github/workflows/multi-language-client.yml'
- '.github/workflows/client-cpp-package.yml'
- '.github/scripts/package-client-cpp-*.sh'
+ - '.github/scripts/native-linux-env.sh'
# allow manually run the action:
workflow_dispatch:
@@ -82,7 +84,7 @@ jobs:
go=false
while IFS= read -r file; do
case "$file" in
- pom.xml|iotdb-client/pom.xml|iotdb-client/client-cpp/*|iotdb-protocol/thrift-datanode/src/main/thrift/client.thrift|iotdb-protocol/thrift-commons/src/main/thrift/common.thrift|.github/workflows/multi-language-client.yml|.github/workflows/client-cpp-package.yml|.github/scripts/package-client-cpp-*.sh)
+ pom.xml|iotdb-client/pom.xml|iotdb-client/client-cpp/*|iotdb-protocol/thrift-datanode/src/main/thrift/client.thrift|iotdb-protocol/thrift-commons/src/main/thrift/common.thrift|.github/workflows/multi-language-client.yml|.github/workflows/client-cpp-package.yml|.github/scripts/package-client-cpp-*.sh|.github/scripts/native-linux-env.sh)
cpp=true
;;
esac
diff --git a/.github/workflows/odbc-package.yml b/.github/workflows/odbc-package.yml
new file mode 100644
index 0000000000000..1f11d32c4c80e
--- /dev/null
+++ b/.github/workflows/odbc-package.yml
@@ -0,0 +1,180 @@
+# 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.
+name: ODBC package
+on:
+ workflow_dispatch:
+ push:
+ paths:
+ - 'iotdb-client/odbc/**'
+ - 'iotdb-client/client-cpp/**'
+ - 'iotdb-protocol/**/thrift/**'
+ - '.github/scripts/*odbc*'
+ - '.github/scripts/native-linux-env.sh'
+ - '.github/workflows/odbc-package.yml'
+ pull_request:
+ paths:
+ - 'iotdb-client/odbc/**'
+ - 'iotdb-client/client-cpp/**'
+ - 'iotdb-protocol/**/thrift/**'
+ - '.github/scripts/*odbc*'
+ - '.github/scripts/native-linux-env.sh'
+ - '.github/workflows/odbc-package.yml'
+jobs:
+ linux:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v7
+ - name: Build and check glibc baseline
+ run: |
+ source .github/scripts/native-linux-env.sh
+ docker run --rm -v "$PWD:/workspace" -w /workspace "${NATIVE_LINUX_IMAGE}" \
+ bash .github/scripts/package-odbc-manylinux228.sh
+ - name: Restore ownership
+ if: always()
+ run: sudo chown -R "$(id -u):$(id -g)" iotdb-client/odbc/target iotdb-client/client-cpp/third-party
+ - uses: actions/upload-artifact@v7
+ with:
+ name: odbc-linux-x86_64-glibc2.28
+ path: iotdb-client/odbc/target/build/iotdb-odbc-*.zip*
+ if-no-files-found: error
+ windows:
+ runs-on: windows-2022
+ steps:
+ - uses: actions/checkout@v7
+ - name: Install build prerequisites
+ shell: pwsh
+ run: |
+ choco install winflexbison3 strawberryperl -y --no-progress
+ 'C:\Strawberry\perl\bin' >> $env:GITHUB_PATH
+ - name: Build, test and package
+ shell: pwsh
+ run: |
+ $version = ([xml](Get-Content iotdb-client/pom.xml)).project.parent.version
+ cmake -S iotdb-client/odbc -B iotdb-client/odbc/target/build -A x64 "-DODBC_PACKAGE_VERSION=$version" -DODBC_PACKAGE_CLASSIFIER=windows-x86_64
+ if ($LASTEXITCODE) { exit $LASTEXITCODE }
+ cmake --build iotdb-client/odbc/target/build --config Release --parallel 4
+ if ($LASTEXITCODE) { exit $LASTEXITCODE }
+ ctest --test-dir iotdb-client/odbc/target/build -C Release --output-on-failure
+ if ($LASTEXITCODE) { exit $LASTEXITCODE }
+ & iotdb-client/odbc/test/check-windows-dependencies.ps1 iotdb-client/odbc/target/build/Release/apache_iotdb_odbc.dll
+ Push-Location iotdb-client/odbc/target/build
+ cpack -C Release
+ if ($LASTEXITCODE) { exit $LASTEXITCODE }
+ Pop-Location
+ - uses: actions/upload-artifact@v7
+ with:
+ name: odbc-windows-x86_64
+ path: iotdb-client/odbc/target/build/iotdb-odbc-*.zip*
+ if-no-files-found: error
+ linux-runtime:
+ needs: linux
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ image: ['almalinux:8', 'ubuntu:22.04', 'ubuntu:24.04']
+ steps:
+ - uses: actions/download-artifact@v8
+ with:
+ name: odbc-linux-x86_64-glibc2.28
+ path: artifacts
+ - name: Extract release
+ run: unzip -q artifacts/*.zip -d package
+ - name: Load with system ODBC manager in a clean runtime
+ run: |
+ docker run --rm -v "$PWD/package:/package:ro" '${{ matrix.image }}' sh -ec '
+ if command -v dnf >/dev/null; then
+ dnf install -y unixODBC
+ else
+ apt-get update && apt-get install -y --no-install-recommends unixodbc
+ fi
+ pkg=$(find /package -mindepth 1 -maxdepth 1 -type d)
+ "$pkg/test/odbc_manager" "$pkg/lib/libapache_iotdb_odbc.so"
+ '
+ server:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v7
+ - uses: actions/setup-java@v5
+ with:
+ distribution: corretto
+ java-version: 17
+ cache: maven
+ - name: Check ODBC source formatting
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y clang-format-17
+ sudo update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-17 100
+ sudo update-alternatives --set clang-format /usr/bin/clang-format-17
+ ./mvnw -B -P with-odbc -pl iotdb-client/odbc spotless:check
+ - name: Build disposable test server
+ run: ./mvnw -B clean package -pl distribution -am -DskipTests
+ - uses: actions/upload-artifact@v7
+ with:
+ name: odbc-test-server
+ path: distribution/target/apache-iotdb-*-all-bin.zip
+ if-no-files-found: error
+ server-runtime:
+ needs: [linux, windows, server]
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - os: ubuntu-latest
+ artifact: odbc-linux-x86_64-glibc2.28
+ - os: windows-2022
+ artifact: odbc-windows-x86_64
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v7
+ - uses: actions/setup-java@v5
+ with:
+ distribution: corretto
+ java-version: 17
+ - uses: actions/setup-python@v6
+ with:
+ python-version: '3.13'
+ - uses: actions/download-artifact@v8
+ with:
+ name: ${{ matrix.artifact }}
+ path: artifacts/driver
+ - uses: actions/download-artifact@v8
+ with:
+ name: odbc-test-server
+ path: artifacts/server
+ - name: Install runtime ODBC manager
+ if: runner.os == 'Linux'
+ run: sudo apt-get update && sudo apt-get install -y unixodbc lsof
+ - name: Extract packages
+ shell: pwsh
+ run: |
+ Expand-Archive (Get-ChildItem artifacts/driver/*.zip).FullName package/driver
+ Expand-Archive (Get-ChildItem artifacts/server/*.zip).FullName package/server
+ - name: Test real server and application consumers
+ shell: pwsh
+ env:
+ IOTDB_TEST_PASSWORD: root
+ run: |
+ $package = (Get-ChildItem package/driver -Directory).FullName
+ $server = (Get-ChildItem package/server -Directory).FullName
+ if ($IsWindows) {
+ python iotdb-client/odbc/test/run_e2e.py "$package/test/odbc_manager.exe" "$package/bin/apache_iotdb_odbc.dll" $server --windows-consumers
+ } else {
+ chmod +x "$package/test/odbc_manager"
+ Get-ChildItem "$server/sbin/*.sh" | ForEach-Object { chmod +x $_.FullName }
+ python iotdb-client/odbc/test/run_e2e.py "$package/test/odbc_manager" "$package/lib/libapache_iotdb_odbc.so" $server
+ }
+ if ($LASTEXITCODE) { exit $LASTEXITCODE }
diff --git a/iotdb-client/client-cpp/CMakeLists.txt b/iotdb-client/client-cpp/CMakeLists.txt
index f6178d183d896..431aa5b98800b 100644
--- a/iotdb-client/client-cpp/CMakeLists.txt
+++ b/iotdb-client/client-cpp/CMakeLists.txt
@@ -22,6 +22,10 @@
cmake_minimum_required(VERSION 3.16)
project(iotdb_session CXX C)
+option(IOTDB_SESSION_STATIC "Build the session client as a static library" OFF)
+option(IOTDB_STATIC_CRT "Use the static MSVC runtime" OFF)
+option(IOTDB_SESSION_INSTALL "Install the standalone session SDK" ON)
+
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
@@ -46,7 +50,11 @@ endif()
if(MSVC)
# /MD: matches default Visual Studio projects; CRT lives in the VC redistributable.
- set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL")
+ if(IOTDB_STATIC_CRT)
+ set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>")
+ else()
+ set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL")
+ endif()
endif()
if(NOT IOTDB_USE_CXX11_ABI STREQUAL "" AND NOT IOTDB_USE_CXX11_ABI MATCHES "^[01]$")
@@ -59,7 +67,7 @@ if(NOT MSVC)
else()
set(_iotdb_cxx11_abi_stamp_value "default")
endif()
- set(_iotdb_cxx11_abi_stamp "${CMAKE_BINARY_DIR}/.iotdb-cxx11-abi")
+ set(_iotdb_cxx11_abi_stamp "${CMAKE_CURRENT_BINARY_DIR}/.iotdb-cxx11-abi")
if(EXISTS "${_iotdb_cxx11_abi_stamp}")
file(READ "${_iotdb_cxx11_abi_stamp}" _iotdb_previous_cxx11_abi)
else()
@@ -69,8 +77,8 @@ if(NOT MSVC)
message(STATUS
"IOTDB_USE_CXX11_ABI changed from '${_iotdb_previous_cxx11_abi}' "
"to '${_iotdb_cxx11_abi_stamp_value}'; removing cached iotdb_session objects")
- file(REMOVE_RECURSE "${CMAKE_BINARY_DIR}/CMakeFiles/iotdb_session.dir")
- file(GLOB _iotdb_stale_session_libs "${CMAKE_BINARY_DIR}/libiotdb_session.so*")
+ file(REMOVE_RECURSE "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/iotdb_session.dir")
+ file(GLOB _iotdb_stale_session_libs "${CMAKE_CURRENT_BINARY_DIR}/libiotdb_session.so*")
if(_iotdb_stale_session_libs)
file(REMOVE ${_iotdb_stale_session_libs})
endif()
@@ -110,7 +118,7 @@ endif()
file(MAKE_DIRECTORY "${IOTDB_OS_DEPS_DIR}")
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
- set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install"
+ set(CMAKE_INSTALL_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/install"
CACHE PATH "Install prefix" FORCE)
endif()
@@ -130,12 +138,17 @@ file(GLOB SESSION_PUBLIC_SRCS CONFIGURE_DEPENDS
file(GLOB SESSION_RPC_SRCS CONFIGURE_DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/src/rpc/*.cpp")
-add_library(iotdb_session SHARED
+if(IOTDB_SESSION_STATIC)
+ set(_iotdb_library_type STATIC)
+else()
+ set(_iotdb_library_type SHARED)
+endif()
+add_library(iotdb_session ${_iotdb_library_type}
${SESSION_PUBLIC_SRCS}
${SESSION_RPC_SRCS}
${THRIFT_GENERATED_SRCS})
-if(WIN32)
+if(WIN32 AND NOT IOTDB_SESSION_STATIC)
set_target_properties(iotdb_session PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
endif()
@@ -163,7 +176,12 @@ endif()
add_dependencies(iotdb_session iotdb_thrift_external iotdb_thrift_codegen)
-target_compile_definitions(iotdb_session PRIVATE THRIFT_STATIC_DEFINE IOTDB_BUILDING_SHARED)
+target_compile_definitions(iotdb_session PRIVATE THRIFT_STATIC_DEFINE)
+if(IOTDB_SESSION_STATIC)
+ target_compile_definitions(iotdb_session PUBLIC IOTDB_SESSION_STATIC)
+else()
+ target_compile_definitions(iotdb_session PRIVATE IOTDB_BUILDING_SHARED)
+endif()
if(NOT IOTDB_USE_CXX11_ABI STREQUAL "" AND NOT MSVC)
target_compile_definitions(iotdb_session PUBLIC
_GLIBCXX_USE_CXX11_ABI=${IOTDB_USE_CXX11_ABI})
@@ -179,7 +197,9 @@ target_include_directories(iotdb_session
${THRIFT_INCLUDE_DIR}
$<$:${BOOST_INCLUDE_DIR}>)
-if(APPLE)
+if(IOTDB_SESSION_STATIC)
+ target_link_libraries(iotdb_session PRIVATE iotdb_thrift_static)
+elseif(APPLE)
target_link_libraries(iotdb_session PRIVATE "-Wl,-force_load,${THRIFT_STATIC_LIB_PATH}")
elseif(UNIX AND NOT MSVC)
# whole-archive pulls all Thrift objects into libiotdb_session.so; allow-multiple-definition
@@ -214,6 +234,8 @@ endif()
include(GNUInstallDirs)
+if(IOTDB_SESSION_INSTALL)
+
set(IOTDB_PUBLIC_HEADERS
Export.h
SessionConfig.h
@@ -265,11 +287,11 @@ if(NOT IOTDB_USE_CXX11_ABI STREQUAL "" AND NOT MSVC)
endif()
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/iotdb-session-config.cmake.in"
- "${CMAKE_BINARY_DIR}/package-metadata/cmake/iotdb-session-config.cmake"
+ "${CMAKE_CURRENT_BINARY_DIR}/package-metadata/cmake/iotdb-session-config.cmake"
@ONLY)
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/iotdb-session.pc.in"
- "${CMAKE_BINARY_DIR}/package-metadata/pkgconfig/iotdb-session.pc"
+ "${CMAKE_CURRENT_BINARY_DIR}/package-metadata/pkgconfig/iotdb-session.pc"
@ONLY)
execute_process(
COMMAND git rev-parse HEAD
@@ -296,9 +318,9 @@ else()
set(IOTDB_SESSION_OPENSSL_VERSION "disabled")
set(IOTDB_SESSION_OPENSSL_PROVIDER "disabled")
endif()
-file(WRITE "${CMAKE_BINARY_DIR}/package-metadata/VERSION"
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/package-metadata/VERSION"
"${IOTDB_SESSION_VERSION}\n")
-file(WRITE "${CMAKE_BINARY_DIR}/package-metadata/BUILD-INFO.txt"
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/package-metadata/BUILD-INFO.txt"
"Apache IoTDB C++ session client build information\n\n"
"version=${IOTDB_SESSION_VERSION}\n"
"commit=${IOTDB_SESSION_GIT_COMMIT}\n"
@@ -313,14 +335,15 @@ file(WRITE "${CMAKE_BINARY_DIR}/package-metadata/BUILD-INFO.txt"
"iotdb.offline=${IOTDB_OFFLINE}\n"
"iotdb.use.cxx11.abi=${IOTDB_USE_CXX11_ABI}\n"
"iotdb.extra.cxx.flags=${IOTDB_EXTRA_CXX_FLAGS}\n")
-install(FILES "${CMAKE_BINARY_DIR}/package-metadata/cmake/iotdb-session-config.cmake"
+install(FILES "${CMAKE_CURRENT_BINARY_DIR}/package-metadata/cmake/iotdb-session-config.cmake"
DESTINATION cmake)
-install(FILES "${CMAKE_BINARY_DIR}/package-metadata/pkgconfig/iotdb-session.pc"
+install(FILES "${CMAKE_CURRENT_BINARY_DIR}/package-metadata/pkgconfig/iotdb-session.pc"
DESTINATION pkgconfig)
install(FILES
- "${CMAKE_BINARY_DIR}/package-metadata/VERSION"
- "${CMAKE_BINARY_DIR}/package-metadata/BUILD-INFO.txt"
+ "${CMAKE_CURRENT_BINARY_DIR}/package-metadata/VERSION"
+ "${CMAKE_CURRENT_BINARY_DIR}/package-metadata/BUILD-INFO.txt"
DESTINATION .)
+endif()
if(BUILD_TESTING)
enable_testing()
diff --git a/iotdb-client/client-cpp/cmake/FetchBoost.cmake b/iotdb-client/client-cpp/cmake/FetchBoost.cmake
index d1ab9c18ae1b9..75eec3fa4462e 100644
--- a/iotdb-client/client-cpp/cmake/FetchBoost.cmake
+++ b/iotdb-client/client-cpp/cmake/FetchBoost.cmake
@@ -103,9 +103,9 @@ if(NOT _boost_tarball)
endif()
# ---------------------------------------------------------------------------
-# Extract headers-only into ${CMAKE_BINARY_DIR}/_deps/boost
+# Extract headers-only into ${CMAKE_CURRENT_BINARY_DIR}/_deps/boost
# ---------------------------------------------------------------------------
-set(_boost_extract_dir "${CMAKE_BINARY_DIR}/_deps/boost")
+set(_boost_extract_dir "${CMAKE_CURRENT_BINARY_DIR}/_deps/boost")
set(_boost_marker "${_boost_extract_dir}/.extracted-${BOOST_VERSION}")
if(NOT EXISTS "${_boost_marker}")
diff --git a/iotdb-client/client-cpp/cmake/FetchBuildTools.cmake b/iotdb-client/client-cpp/cmake/FetchBuildTools.cmake
index 5860d68ac195f..f24a78bd4726e 100644
--- a/iotdb-client/client-cpp/cmake/FetchBuildTools.cmake
+++ b/iotdb-client/client-cpp/cmake/FetchBuildTools.cmake
@@ -23,7 +23,7 @@
# Otherwise we provision them locally:
#
# * Linux / macOS - configure-make-install each tool from a tarball into
-# ${CMAKE_BINARY_DIR}/tools (no sudo required).
+# ${CMAKE_CURRENT_BINARY_DIR}/tools (no sudo required).
# * Windows - extract the winflexbison zip and copy
# win_flex.exe -> flex.exe, win_bison.exe -> bison.exe.
#
@@ -33,7 +33,7 @@
# 3. FATAL_ERROR otherwise
# =============================================================================
-set(_tools_prefix "${CMAKE_BINARY_DIR}/tools")
+set(_tools_prefix "${CMAKE_CURRENT_BINARY_DIR}/tools")
set(_tools_bin "${_tools_prefix}/bin")
file(MAKE_DIRECTORY "${_tools_bin}")
@@ -109,7 +109,7 @@ endfunction()
# Configure-make-install from into ${_tools_prefix}.
function(_iotdb_build_autotools NAME TARBALL EXTRACTED_DIRNAME)
- set(_src_root "${CMAKE_BINARY_DIR}/_deps/${NAME}")
+ set(_src_root "${CMAKE_CURRENT_BINARY_DIR}/_deps/${NAME}")
set(_marker "${_tools_prefix}/.${NAME}-installed")
if(EXISTS "${_marker}")
return()
diff --git a/iotdb-client/client-cpp/cmake/FetchOpenSSL.cmake b/iotdb-client/client-cpp/cmake/FetchOpenSSL.cmake
index 165736dd1602c..9ab01108e7529 100644
--- a/iotdb-client/client-cpp/cmake/FetchOpenSSL.cmake
+++ b/iotdb-client/client-cpp/cmake/FetchOpenSSL.cmake
@@ -21,7 +21,7 @@
# Apache Thrift 0.24 (bundled by this client) builds against OpenSSL 3.x.
#
# By default, a fixed OpenSSL source release is downloaded, checksum-verified,
-# built as shared libraries, and installed under ${CMAKE_BINARY_DIR}/_deps.
+# built as shared libraries, and installed under ${CMAKE_CURRENT_BINARY_DIR}/_deps.
# Set IOTDB_OPENSSL_FROM_SOURCE=OFF to opt into a compatible system OpenSSL.
#
# Side effects:
@@ -41,6 +41,9 @@ set(OPENSSL_FALLBACK_SHA256
# packages independent of the build host's OpenSSL installation.
option(IOTDB_OPENSSL_FROM_SOURCE
"Ignore any system OpenSSL and build OpenSSL ${OPENSSL_FALLBACK_VERSION} from source" ON)
+option(IOTDB_OPENSSL_STATIC "Build OpenSSL as static PIC libraries" OFF)
+set(OPENSSL_USE_STATIC_LIBS ${IOTDB_OPENSSL_STATIC})
+set(OPENSSL_MSVC_STATIC_RT ${IOTDB_STATIC_CRT})
if(NOT IOTDB_OPENSSL_FROM_SOURCE)
find_package(OpenSSL QUIET)
@@ -88,7 +91,24 @@ if(_ossl_download_required)
endif()
endif()
-set(_ossl_root "${CMAKE_BINARY_DIR}/_deps/openssl")
+set(_ossl_variant "shared")
+set(_ossl_options shared no-tests)
+if(IOTDB_OPENSSL_STATIC)
+ set(_ossl_variant "static-${CMAKE_SYSTEM_PROCESSOR}-${CMAKE_C_COMPILER_ID}-${CMAKE_C_COMPILER_VERSION}-${CMAKE_BUILD_TYPE}-crt${IOTDB_STATIC_CRT}")
+ set(_ossl_options no-shared no-module no-tests)
+ if(NOT WIN32)
+ list(APPEND _ossl_options -fPIC)
+ elseif(NOT IOTDB_STATIC_CRT)
+ list(APPEND _ossl_options /MD)
+ endif()
+ if(CMAKE_BUILD_TYPE STREQUAL "Debug")
+ list(APPEND _ossl_options --debug)
+ endif()
+endif()
+set(_ossl_root "${CMAKE_CURRENT_BINARY_DIR}/_deps/openssl")
+if(IOTDB_OPENSSL_STATIC)
+ string(APPEND _ossl_root "-${_ossl_variant}")
+endif()
set(_ossl_src "${_ossl_root}/src/openssl-${OPENSSL_FALLBACK_VERSION}")
set(_ossl_inst "${_ossl_root}/install")
set(_ossl_stamp "${_ossl_root}/.built-${OPENSSL_FALLBACK_VERSION}")
@@ -134,16 +154,20 @@ if(NOT EXISTS "${_ossl_stamp}")
file(TO_NATIVE_PATH "${_ossl_src}" _ossl_src_native)
file(TO_NATIVE_PATH "${_ossl_perl}" _ossl_perl_native)
set(_ossl_build_script "${_ossl_root}/build-openssl.cmd")
+ string(REPLACE ";" " " _ossl_windows_options "${_ossl_options}")
+ # file(WRITE) translates LF to CRLF on Windows; explicit CRLF produces
+ # CRCRLF and cmd.exe can skip commands. Also catch negative process exit
+ # codes (for example a missing Perl runtime DLL), not only positive ones.
file(WRITE "${_ossl_build_script}"
- "@echo on\r\n"
- "call \"${_vcvars}\"\r\n"
- "if errorlevel 1 exit /b %errorlevel%\r\n"
- "cd /d \"${_ossl_src_native}\"\r\n"
- "\"${_ossl_perl_native}\" Configure VC-WIN64A --prefix=\"${_ossl_inst_native}\" --openssldir=\"${_ossl_inst_native}\\ssl\" shared no-tests no-asm\r\n"
- "if errorlevel 1 exit /b %errorlevel%\r\n"
- "nmake\r\n"
- "if errorlevel 1 exit /b %errorlevel%\r\n"
- "nmake install_sw\r\n")
+ "@echo on\n"
+ "call \"${_vcvars}\"\n"
+ "if not \"%errorlevel%\"==\"0\" exit /b %errorlevel%\n"
+ "cd /d \"${_ossl_src_native}\"\n"
+ "\"${_ossl_perl_native}\" Configure VC-WIN64A --prefix=\"${_ossl_inst_native}\" --openssldir=\"${_ossl_inst_native}\\ssl\" ${_ossl_windows_options} no-asm\n"
+ "if not \"%errorlevel%\"==\"0\" exit /b %errorlevel%\n"
+ "nmake\n"
+ "if not \"%errorlevel%\"==\"0\" exit /b %errorlevel%\n"
+ "nmake install_sw\n")
execute_process(COMMAND cmd /d /c "${_ossl_build_script}" RESULT_VARIABLE _rc)
if(NOT _rc EQUAL 0)
message(FATAL_ERROR "[OpenSSL] Windows source build failed (rc=${_rc})")
@@ -155,7 +179,8 @@ if(NOT EXISTS "${_ossl_stamp}")
set(_jobs 1)
endif()
execute_process(
- COMMAND ./config --prefix=${_ossl_inst} --openssldir=${_ossl_inst}/ssl shared no-tests
+ COMMAND ${CMAKE_COMMAND} -E env "CC=${CMAKE_C_COMPILER}"
+ ./config --prefix=${_ossl_inst} --openssldir=${_ossl_inst}/ssl ${_ossl_options}
WORKING_DIRECTORY "${_ossl_src}"
RESULT_VARIABLE _rc)
if(NOT _rc EQUAL 0)
@@ -180,7 +205,7 @@ if(NOT EXISTS "${_ossl_stamp}")
file(TOUCH "${_ossl_stamp}")
endif()
-if(APPLE)
+if(APPLE AND NOT IOTDB_OPENSSL_STATIC)
# OpenSSL's Darwin build records its absolute installation prefix in each dylib. Rewrite the
# IDs before iotdb_session links against them so both the client and libssl resolve the bundled
# libraries relative to the package's lib/ directory.
@@ -233,6 +258,11 @@ if(APPLE)
endif()
set(OPENSSL_ROOT_DIR "${_ossl_inst}" CACHE PATH "OpenSSL root" FORCE)
-set(OPENSSL_USE_STATIC_LIBS OFF)
+# Do not reuse a previous shared-library discovery when switching variants.
+unset(OPENSSL_SSL_LIBRARY CACHE)
+unset(OPENSSL_CRYPTO_LIBRARY CACHE)
+unset(OPENSSL_INCLUDE_DIR CACHE)
find_package(OpenSSL REQUIRED)
-message(STATUS "[OpenSSL] built locally (shared) at ${OPENSSL_ROOT_DIR}")
+# The ODBC parent also consumes these targets (FindOpenSSL creates local imports).
+set_property(TARGET OpenSSL::SSL OpenSSL::Crypto PROPERTY IMPORTED_GLOBAL TRUE)
+message(STATUS "[OpenSSL] built locally (${_ossl_variant}) at ${OPENSSL_ROOT_DIR}")
diff --git a/iotdb-client/client-cpp/cmake/FetchThrift.cmake b/iotdb-client/client-cpp/cmake/FetchThrift.cmake
index d69b2a47ad9e5..a5b614ad281cf 100644
--- a/iotdb-client/client-cpp/cmake/FetchThrift.cmake
+++ b/iotdb-client/client-cpp/cmake/FetchThrift.cmake
@@ -67,9 +67,12 @@ if(NOT EXISTS "${_thrift_tarball}")
endif()
# ---------------------------------------------------------------------------
-# Extract once into ${CMAKE_BINARY_DIR}/_deps/thrift/src
+# Extract once into ${CMAKE_CURRENT_BINARY_DIR}/_deps/thrift/src
# ---------------------------------------------------------------------------
-set(_thrift_root "${CMAKE_BINARY_DIR}/_deps/thrift")
+set(_thrift_root "${CMAKE_CURRENT_BINARY_DIR}/_deps/thrift")
+if(IOTDB_SESSION_STATIC)
+ string(APPEND _thrift_root "-static-${CMAKE_SYSTEM_PROCESSOR}-${CMAKE_CXX_COMPILER_ID}-${CMAKE_CXX_COMPILER_VERSION}-${CMAKE_BUILD_TYPE}-crt${IOTDB_STATIC_CRT}-ssl${WITH_SSL}-abi${IOTDB_USE_CXX11_ABI}")
+endif()
set(_thrift_src "${_thrift_root}/src/${_thrift_dirname}")
set(_thrift_build "${_thrift_root}/build")
set(_thrift_install "${_thrift_root}/install")
@@ -125,19 +128,28 @@ endif()
if(MSVC)
list(APPEND _thrift_cmake_args
- "-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded$<$:Debug>DLL")
+ "-DWITH_MT=${IOTDB_STATIC_CRT}"
+ "-DCMAKE_MSVC_RUNTIME_LIBRARY=${CMAKE_MSVC_RUNTIME_LIBRARY}")
else()
set(_thrift_cxxflags "-fPIC")
- if(IOTDB_USE_CXX11_ABI)
+ if(NOT IOTDB_USE_CXX11_ABI STREQUAL "")
set(_thrift_cxxflags "${_thrift_cxxflags} -D_GLIBCXX_USE_CXX11_ABI=${IOTDB_USE_CXX11_ABI}")
endif()
list(APPEND _thrift_cmake_args
+ "-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}"
+ "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
"-DCMAKE_C_FLAGS=-fPIC"
"-DCMAKE_CXX_FLAGS=${_thrift_cxxflags}")
endif()
if(WITH_SSL)
list(APPEND _thrift_cmake_args "-DWITH_OPENSSL=ON")
+ list(APPEND _thrift_cmake_args
+ "-DOPENSSL_USE_STATIC_LIBS=${IOTDB_OPENSSL_STATIC}"
+ "-DOPENSSL_MSVC_STATIC_RT=${IOTDB_STATIC_CRT}"
+ "-DOPENSSL_SSL_LIBRARY=${OPENSSL_SSL_LIBRARY}"
+ "-DOPENSSL_CRYPTO_LIBRARY=${OPENSSL_CRYPTO_LIBRARY}"
+ "-DOPENSSL_INCLUDE_DIR=${OPENSSL_INCLUDE_DIR}")
# Build Thrift's TSSLSocket against the same OpenSSL that iotdb_session links
# and bundles, so the runtime libraries match. find_package does not set
# OPENSSL_ROOT_DIR itself, so derive it from the resolved include dir.
diff --git a/iotdb-client/client-cpp/cmake/GenerateThriftSources.cmake b/iotdb-client/client-cpp/cmake/GenerateThriftSources.cmake
index 6a3685af45847..ba1c931fd5e17 100644
--- a/iotdb-client/client-cpp/cmake/GenerateThriftSources.cmake
+++ b/iotdb-client/client-cpp/cmake/GenerateThriftSources.cmake
@@ -51,12 +51,12 @@ endforeach()
# Mirror the bash reference script: stage both .thrift files into a flat
# working directory so `include "common.thrift"` resolves without -I args.
-set(_thrift_proto_dir "${CMAKE_BINARY_DIR}/thrift-protocols")
+set(_thrift_proto_dir "${CMAKE_CURRENT_BINARY_DIR}/thrift-protocols")
file(MAKE_DIRECTORY "${_thrift_proto_dir}")
configure_file("${_common_thrift}" "${_thrift_proto_dir}/common.thrift" COPYONLY)
configure_file("${_client_thrift}" "${_thrift_proto_dir}/client.thrift" COPYONLY)
-set(THRIFT_GEN_CPP_DIR "${CMAKE_BINARY_DIR}/gen-cpp" CACHE PATH "" FORCE)
+set(THRIFT_GEN_CPP_DIR "${CMAKE_CURRENT_BINARY_DIR}/gen-cpp" CACHE PATH "" FORCE)
file(MAKE_DIRECTORY "${THRIFT_GEN_CPP_DIR}")
# A stamp file lets us re-run thrift only when one of the inputs changes
diff --git a/iotdb-client/client-cpp/src/include/Export.h b/iotdb-client/client-cpp/src/include/Export.h
index 71262d0cffb77..d4f46d3ed8732 100644
--- a/iotdb-client/client-cpp/src/include/Export.h
+++ b/iotdb-client/client-cpp/src/include/Export.h
@@ -19,7 +19,9 @@
#ifndef IOTDB_EXPORT_H
#define IOTDB_EXPORT_H
-#if defined(_WIN32) && defined(IOTDB_BUILDING_SHARED)
+#if defined(IOTDB_SESSION_STATIC)
+#define IOTDB_SESSION_API
+#elif defined(_WIN32) && defined(IOTDB_BUILDING_SHARED)
#define IOTDB_SESSION_API __declspec(dllexport)
#elif defined(_WIN32)
#define IOTDB_SESSION_API __declspec(dllimport)
diff --git a/iotdb-client/odbc/.gitignore b/iotdb-client/odbc/.gitignore
new file mode 100644
index 0000000000000..c49c7033d8e37
--- /dev/null
+++ b/iotdb-client/odbc/.gitignore
@@ -0,0 +1,26 @@
+# 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.
+# Root rules ignore *.txt and *.cmake; these are source inputs here.
+!/CMakeLists.txt
+!/cmake/*.cmake
+!/test/CMakeLists.txt
+!/licenses/*.txt
+!/examples/python-example/requirements.txt
+/target/
+/build/
+__pycache__/
+*.log
diff --git a/iotdb-client/odbc/CMakeLists.txt b/iotdb-client/odbc/CMakeLists.txt
new file mode 100644
index 0000000000000..5a72d7ab110be
--- /dev/null
+++ b/iotdb-client/odbc/CMakeLists.txt
@@ -0,0 +1,149 @@
+# 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.
+cmake_minimum_required(VERSION 3.20)
+cmake_policy(SET CMP0091 NEW)
+project(apache_iotdb_odbc VERSION 1.0.0 LANGUAGES C CXX)
+
+set(CMAKE_CXX_STANDARD 14)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+if(NOT CMAKE_SIZEOF_VOID_P EQUAL 8 OR
+ NOT CMAKE_SYSTEM_NAME MATCHES "^(Windows|Linux)$" OR
+ NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|amd64|x86_64|X86_64)$" OR
+ CMAKE_GENERATOR_PLATFORM MATCHES "[Aa][Rr][Mm]")
+ message(FATAL_ERROR "ODBC release builds support Windows/Linux x64 only")
+endif()
+set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+if(NOT CMAKE_BUILD_TYPE)
+ set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
+endif()
+if(MSVC)
+ # OpenSSL and Thrift are configured once per build directory. Avoid linking
+ # a Debug driver against dependencies built with the Release CRT.
+ set(CMAKE_CONFIGURATION_TYPES "${CMAKE_BUILD_TYPE}" CACHE STRING "Configurations" FORCE)
+endif()
+set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>")
+option(ODBC_BUILD_TESTS "Build ODBC tests" ON)
+option(ODBC_ENABLE_LOGGING "Compile file logging support into the ODBC driver" OFF)
+file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/../pom.xml" _version_line REGEX "")
+list(GET _version_line 0 _version_line)
+string(REGEX REPLACE ".*([^<]+).*" "\\1" _repository_version "${_version_line}")
+set(ODBC_PACKAGE_VERSION "${_repository_version}" CACHE STRING "Package version")
+set(ODBC_PACKAGE_CLASSIFIER "${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}" CACHE STRING "Release platform")
+
+# Scope SDK configuration to this build; its standalone defaults remain unchanged.
+set(IOTDB_SESSION_STATIC ON CACHE BOOL "" FORCE)
+set(IOTDB_SESSION_INSTALL OFF CACHE BOOL "" FORCE)
+set(IOTDB_STATIC_CRT ON CACHE BOOL "" FORCE)
+set(IOTDB_OPENSSL_STATIC ON CACHE BOOL "" FORCE)
+set(IOTDB_OPENSSL_FROM_SOURCE ON CACHE BOOL "" FORCE)
+set(WITH_SSL ON CACHE BOOL "" FORCE)
+set(IOTDB_SESSION_VERSION "${ODBC_PACKAGE_VERSION}" CACHE STRING "" FORCE)
+set(BUILD_TESTING OFF CACHE BOOL "" FORCE)
+add_subdirectory(../client-cpp client-cpp EXCLUDE_FROM_ALL)
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
+include(FetchDependencies)
+
+set(DRIVER_VERSION "${ODBC_PACKAGE_VERSION}")
+set(DEBUG_LOG_OUTPUT_PATH "apache_iotdb_odbc.log")
+configure_file(src/DriverConfig.h.in generated/DriverConfig.h @ONLY)
+set(ODBC_SOURCES
+ src/Catalog.cpp
+ src/ConnectionHandle.cpp
+ src/CopyField.cpp
+ src/DiagnosticManager.cpp
+ src/EnvironmentHandle.cpp
+ src/Log.cpp
+ src/ODBCField.cpp
+ src/ODBCHandle.cpp
+ src/ODBCRestResultSet.cpp
+ src/ODBCResultSet.cpp
+ src/ODBCSessionResultSet.cpp
+ src/StatementHandle.cpp
+ src/StatementUtils.cpp
+ src/Unicode.cpp
+ src/driver.cpp
+ src/rest_api_client.cpp
+ src/session_api_client.cpp)
+add_library(odbc_objects OBJECT ${ODBC_SOURCES})
+target_include_directories(odbc_objects PUBLIC src "${CMAKE_CURRENT_BINARY_DIR}/generated")
+target_link_libraries(odbc_objects PUBLIC iotdb_session CURL::libcurl nlohmann_json::nlohmann_json)
+target_compile_definitions(odbc_objects PUBLIC CURL_STATICLIB)
+add_library(apache_iotdb_odbc SHARED $)
+target_link_libraries(apache_iotdb_odbc PRIVATE odbc_objects)
+if(WIN32)
+ enable_language(RC)
+ target_compile_definitions(odbc_objects PUBLIC NOMINMAX WIN32_LEAN_AND_MEAN)
+ target_compile_options(odbc_objects PUBLIC /utf-8)
+ target_sources(odbc_objects PRIVATE src/setup/setup_dialog.cpp)
+ target_sources(apache_iotdb_odbc PRIVATE src/setup/setup.rc src/driver.def)
+ target_link_libraries(odbc_objects PUBLIC odbccp32 legacy_stdio_definitions)
+ add_executable(install_driver src/install_driver.cpp)
+ target_compile_definitions(install_driver PRIVATE NOMINMAX WIN32_LEAN_AND_MEAN)
+ target_link_libraries(install_driver PRIVATE odbccp32 legacy_stdio_definitions)
+ install(TARGETS install_driver RUNTIME DESTINATION bin)
+ install(FILES scripts/windows/install.bat scripts/windows/uninstall.bat DESTINATION .)
+else()
+ find_path(ODBC_INCLUDE_DIR sql.h REQUIRED)
+ find_library(ODBCINST_LIBRARY odbcinst REQUIRED)
+ target_include_directories(odbc_objects PUBLIC "${ODBC_INCLUDE_DIR}")
+ target_link_libraries(odbc_objects PUBLIC "${ODBCINST_LIBRARY}")
+ target_link_options(apache_iotdb_odbc PRIVATE -static-libstdc++ -static-libgcc
+ "LINKER:--version-script=${CMAKE_CURRENT_SOURCE_DIR}/cmake/exports.map"
+ "LINKER:--exclude-libs,ALL" "LINKER:-z,defs" "LINKER:-Bsymbolic-functions")
+ set_property(TARGET apache_iotdb_odbc APPEND PROPERTY LINK_DEPENDS
+ "${CMAKE_CURRENT_SOURCE_DIR}/cmake/exports.map")
+endif()
+install(TARGETS apache_iotdb_odbc RUNTIME DESTINATION bin LIBRARY DESTINATION lib)
+install(FILES README.md DESTINATION .)
+install(DIRECTORY conf/ DESTINATION conf)
+install(FILES "${odbc_curl_SOURCE_DIR}/COPYING" DESTINATION licenses RENAME curl.txt)
+install(FILES "${odbc_json_SOURCE_DIR}/LICENSE.MIT" DESTINATION licenses RENAME json.txt)
+install(DIRECTORY ../client-cpp/src/assembly/package-metadata/third_party/ DESTINATION licenses/sdk)
+install(FILES ../../LICENSE ../../NOTICE DESTINATION .)
+if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+ # GCC runtime notices belong to the combined GNU/Linux binary, not the
+ # Apache source tree or the MSVC package.
+ configure_file(cmake/PackageGccRuntime.cmake.in generated/PackageGccRuntime.cmake @ONLY)
+ install(SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/generated/PackageGccRuntime.cmake")
+endif()
+get_filename_component(ODBC_REPOSITORY_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../.." REALPATH)
+# Container builds read the host checkout as root. Trust only this configured
+# source repository for the provenance query, without changing global Git policy.
+execute_process(COMMAND git -c "safe.directory=${ODBC_REPOSITORY_ROOT}" rev-parse HEAD
+ WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
+ OUTPUT_VARIABLE ODBC_COMMIT OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET)
+if(NOT ODBC_COMMIT)
+ set(ODBC_COMMIT "unknown")
+endif()
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/BUILD-INFO.txt"
+ "version=${ODBC_PACKAGE_VERSION}\ncommit=${ODBC_COMMIT}\n"
+ "platform=${ODBC_PACKAGE_CLASSIFIER}\ncompiler=${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}\n"
+ "thrift=${THRIFT_VERSION}\nopenssl=${OPENSSL_FALLBACK_VERSION}\n"
+ "curl=${ODBC_CURL_VERSION}\njson=${ODBC_JSON_VERSION}\n"
+ "session.linkage=static\nopenssl.linkage=static\ncurl.linkage=static\n")
+install(FILES "${CMAKE_CURRENT_BINARY_DIR}/BUILD-INFO.txt" DESTINATION .)
+
+if(ODBC_BUILD_TESTS)
+ enable_testing()
+ add_subdirectory(test)
+endif()
+
+set(CPACK_GENERATOR ZIP)
+set(CPACK_PACKAGE_FILE_NAME "iotdb-odbc-${ODBC_PACKAGE_VERSION}-${ODBC_PACKAGE_CLASSIFIER}")
+set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY ON)
+set(CPACK_PACKAGE_CHECKSUM SHA512)
+include(CPack)
diff --git a/iotdb-client/odbc/README.md b/iotdb-client/odbc/README.md
new file mode 100644
index 0000000000000..a78c9d744a53a
--- /dev/null
+++ b/iotdb-client/odbc/README.md
@@ -0,0 +1,203 @@
+
+# IoTDB ODBC driver
+
+The driver builds directly against `../client-cpp`. Each release contains one
+driver library with the Session client, Thrift, OpenSSL and curl linked statically.
+It does not require a separately installed C++ SDK, OpenSSL or VC redistributable.
+The OS libraries and ODBC manager/installer remain system prerequisites.
+
+## Build
+
+Use CMake 3.20+, a C++14 compiler and the C++ SDK build prerequisites. Windows
+requires Visual Studio 2022 x64, Strawberry Perl and winflexbison3. Linux requires
+unixODBC development headers. CMake downloads curl 8.4.0 and nlohmann/json 3.11.3
+into the build directory and verifies their SHA-256 checksums. The C++ SDK keeps
+its dependency archives under `../client-cpp/third-party`; `-DIOTDB_OFFLINE=ON`
+requires that SDK cache to be populated.
+
+```sh
+cmake -S iotdb-client/odbc -B iotdb-client/odbc/target/build -DCMAKE_BUILD_TYPE=Release
+cmake --build iotdb-client/odbc/target/build --config Release --parallel 4
+ctest --test-dir iotdb-client/odbc/target/build -C Release --output-on-failure
+cd iotdb-client/odbc/target/build
+cpack -C Release
+```
+
+On Windows pass `-A x64` to configure. Maven also supports
+`./mvnw package -P with-odbc -pl iotdb-client/odbc`; CMake and CPack must be on PATH.
+`-DskipTests` skips test execution. Builds do not register the driver on the host.
+
+For Linux release packages use the same manylinux 2.28 baseline as the C++ SDK:
+
+```sh
+source .github/scripts/native-linux-env.sh
+docker run --rm -v "$PWD:/workspace" -w /workspace "$NATIVE_LINUX_IMAGE" \
+ bash .github/scripts/package-odbc-manylinux228.sh
+```
+
+The release script builds all dependencies in this environment, runs tests and
+rejects unexpected shared-library dependencies or GLIBC requirements above 2.28.
+A build on a newer host is a development build, not evidence of glibc 2.28 compatibility.
+Release scope is Linux x86_64 and Windows x64 (Windows 10/11, Server 2016+).
+Packages and SHA-512 checksums are written into the CMake build directory.
+The dependency URLs, versions and checksums are exposed as `ODBC_CURL_*` and
+`ODBC_JSON_*` CMake cache variables for controlled mirrors or version updates.
+File logging is excluded from release builds by default; enable it explicitly
+with `-DODBC_ENABLE_LOGGING=ON` when troubleshooting.
+
+GNU/Linux binary packages combine GCC's libstdc++ and libgcc runtime object code
+under GPL-3.0-or-later WITH GCC-exception-3.1. The
+[ASF GCC Runtime Library exception policy](https://www.apache.org/legal/resolved.html#gcc-runtime-library)
+addresses this binary combination; the project PMC must review the inclusion.
+No GCC runtime source or GPL license text is vendored in the IoTDB source tree.
+At install/package time, GNU builds download the two license texts from the GCC
+14.2.0 upstream release, verify pinned SHA-256 hashes and include them under
+`licenses/gcc`, with a disclosure in the binary's `LICENSE`. See the
+[GCC runtime licensing documentation](https://gcc.gnu.org/onlinedocs/libstdc++/manual/license.html).
+Windows/MSVC packages do not include GCC runtime notices. Offline GNU packaging
+requires prepopulating `/runtime-licenses/GPL-3.0.txt` and
+`GCC-Runtime-Exception.txt` with those verified upstream files.
+
+## ODBC conformance
+
+The implementation reference is Microsoft's official
+[ODBC Programmer's Reference](https://learn.microsoft.com/en-us/sql/odbc/reference/odbc-programmer-s-reference),
+especially the [Core Interface Conformance](https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/core-interface-conformance)
+function requirements, API reference, state-transition
+tables and diagnostic-record rules linked from that page. The driver uses the
+ODBC 3.8 ABI and targets core interface conformance.
+
+Capability discovery is treated as a compatibility contract: `SQLGetFunctions`
+reports only callable implementations, and `SQLGetInfo` reports only behavior
+provided by the driver and IoTDB. Unsupported optional behavior returns a
+standard SQLSTATE instead of being silently accepted. Core behavior includes
+explicit and implicit descriptors, input parameter arrays (including
+data-at-execution parameters),
+rowset fetching, and the required catalog result schemas. The current intentional
+optional limits are forward-only, read-only cursors; no true transactions, output
+parameters, stored procedures, asynchronous execution or bookmarks. Applications
+must use `SQLGetFunctions` and `SQLGetInfo` rather than infer optional support from
+exported symbols.
+
+`SQL_C_NUMERIC` conversion supports INT32, INT64 and epoch TIMESTAMP values,
+including INT64_MIN, with a default precision of 38 and scale of zero.
+Bound numeric columns and `SQLGetData(SQL_ARD_TYPE)` honor the application row
+descriptor's precision (1–38) and scale (-38–38). Fractional truncation returns
+`SQL_SUCCESS_WITH_INFO`/`01S07`; magnitude overflow returns `22003`.
+FLOAT/DOUBLE to `SQL_C_NUMERIC` is currently unsupported and returns `07006`
+without modifying the output. Retrieve approximate values as `SQL_C_FLOAT` or
+`SQL_C_DOUBLE` instead. `SQLDisconnect` drops all connection-owned statement and
+descriptor handles; applications must allocate new ones after reconnecting.
+
+## Install
+
+Windows: extract the release, then run `install.bat "C:\Program Files\Apache IoTDB ODBC Driver"`
+from an administrator command prompt. Use the **64-bit** ODBC administrator to
+configure a DSN. `uninstall.bat` unregisters the driver, preserving DSNs and files.
+
+Linux: install unixODBC, extract the release, update `Driver` in `conf/odbcinst.ini`
+to the absolute library path, then register with `odbcinst -i -d -f conf/odbcinst.ini`.
+Copy the example DSN into your user `odbc.ini` and provide your credentials.
+
+## Connection and encryption
+
+Existing `SERVER`, `PORT`, `UID`, `PWD`, `DATABASE`, `ISTABLEMODEL`, `LOGLEVEL`,
+`SESSIONTIMEOUTMS` and `BATCHSIZE` parameters remain available. `RESTFUL=1` selects
+the retained HTTP REST implementation; specify its HTTP port explicitly.
+The default is Session RPC on port 6667. Explicit connection attributes override
+DSN values. Values containing semicolons can be braced, with `}}` escaping `}`.
+
+```text
+DRIVER={Apache IoTDB ODBC Driver};SERVER=localhost;PORT=6667;UID=root;PWD={your password};DATABASE=information_schema;SSL=1;SSLCA={C:\certs\ca.pem};
+```
+
+| Attribute | Default | Meaning |
+| --- | --- | --- |
+| SSL | 0 | Enable Session TLS; accepts 0/1, false/true, no/yes, off/on |
+| SSLCA | empty | Trusted CA file in PEM format; required for TLS |
+| SSLCERT | empty | PEM client certificate for mutual TLS |
+| SSLKEY | empty | PEM client private key; required together with SSLCERT |
+
+These attributes are available in connection strings, DSNs and the Windows setup
+dialog. Certificates are external files readable by the application account;
+they are not compiled into the driver. The server must enable the corresponding
+RPC TLS mode. The client verifies trust and server identity and never falls back
+to plaintext. Connection testing and queries use the same Session construction.
+Certificate attributes with SSL disabled are rejected. REST combined with these
+TLS options is rejected: HTTPS is outside this migration's scope.
+
+## Validation
+
+CTest covers configuration, type conversions, diagnostics and loading through the
+system ODBC manager. The manager test deliberately rejects an invalid TLS/REST
+combination without contacting a server. Windows tests use a temporary user DSN
+and do not install or modify a machine-wide ODBC driver registration.
+To test a running server:
+
+```sh
+export IOTDB_ODBC_CONNECTION='SERVER=localhost;PORT=6667;UID=root;PWD=your-password;ISTABLEMODEL=0'
+target/build/test/odbc_manager /absolute/path/libapache_iotdb_odbc.so
+```
+
+Set `IOTDB_ODBC_QUERY` to exercise a query and `IOTDB_ODBC_EXPECT_FAILURE=1` for
+negative connection tests. For plain/TLS/mTLS server phases, run
+`python test/run_e2e.py --help`; use a disposable server distribution and test data.
+The runner requires Java 17+, `keytool`, Python 3 and (on Linux) `lsof` or `netstat`.
+Set `IOTDB_TEST_PASSWORD` to the test server password. It copies the supplied
+distribution, refuses occupied test ports and removes the temporary copy afterward.
+
+The package workflow also builds an IoTDB server and tests the **extracted release
+packages** in plain, TLS and mutual-TLS modes on Linux and Windows. Certificate
+rejection tests cover missing trust material, wrong trust, missing/mismatched
+client credentials and a trusted server certificate with the wrong identity.
+`--windows-consumers` adds real `System.Data.Odbc` and MSDASQL/ADO tests, including
+tree/table catalog browsing, parameterized inserts, Unicode, long text, NULL,
+empty strings, DATE, BOOLEAN and Session batch boundaries. It creates temporary
+user DSNs and disposable databases, then removes them. CTest additionally runs
+public-ABI regressions against a local HTTP fixture for catalog schemas,
+enumeration, patterns, Unicode lengths and REST type conversion.
+
+## Excel and other Unicode applications
+
+Use **64-bit Microsoft Excel** with the Windows x64 package and a 64-bit user or
+system DSN. In Excel choose **Data > Get Data > From Other Sources > From ODBC**,
+select the DSN and select a table in Navigator. For an explicit query use the
+advanced SQL option, for example `SELECT time,device,val,txt,day FROM samples` in
+the DSN's table-model database. SQL must follow IoTDB's dialect; optional ODBC SQL
+escapes and transactions are not implemented. Refresh uses a forward-only,
+read-only cursor.
+
+The driver exports Unicode `W` entry points and describes STRING/TEXT as
+`SQL_WVARCHAR`/`SQL_WLONGVARCHAR`. Unicode applications therefore pass UTF-16 SQL
+and retrieve UTF-16 data without the system ANSI code page corrupting Chinese or
+other characters. Native applications should use `SQLDriverConnectW`,
+`SQLExecDirectW` and `SQLPrepareW` through the Driver Manager. The driver's narrow
+entry points accept UTF-8 directly; an ANSI application going through Windows'
+Driver Manager is subject to its ANSI code-page conversion. TIMESTAMP query
+columns retain epoch-integer semantics; DATE columns use `SQL_TYPE_DATE`.
+
+The Windows application tests exercise .NET and Office's MSDASQL/ADO data path.
+GitHub-hosted runners do not include Excel; those tests are not an Excel or Power
+Query UI test. Validate Navigator, loading into a worksheet and refresh on the
+target Excel installation before claiming that particular Office version has
+been tested. WPS registering `Excel.Application` does not establish Microsoft
+Excel compatibility.
diff --git a/iotdb-client/odbc/cmake/CheckLinuxPackage.cmake b/iotdb-client/odbc/cmake/CheckLinuxPackage.cmake
new file mode 100644
index 0000000000000..cbd06c990e530
--- /dev/null
+++ b/iotdb-client/odbc/cmake/CheckLinuxPackage.cmake
@@ -0,0 +1,53 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+if(NOT EXISTS "${DRIVER}")
+ message(FATAL_ERROR "Missing driver: ${DRIVER}")
+endif()
+if(NOT DEFINED GLIBC_BASELINE)
+ message(FATAL_ERROR "GLIBC_BASELINE must come from the shared native Linux release configuration")
+endif()
+find_program(READELF readelf REQUIRED)
+execute_process(COMMAND "${READELF}" --version-info "${DRIVER}"
+ OUTPUT_VARIABLE versions RESULT_VARIABLE rc)
+if(NOT rc EQUAL 0)
+ message(FATAL_ERROR "Cannot inspect ELF versions")
+endif()
+string(REGEX MATCHALL "GLIBC_[0-9]+\\.[0-9]+(\\.[0-9]+)?" requirements "${versions}")
+if(NOT requirements)
+ message(FATAL_ERROR "Cannot determine GLIBC requirements")
+endif()
+foreach(requirement IN LISTS requirements)
+ string(REPLACE "GLIBC_" "" version "${requirement}")
+ if(version VERSION_GREATER GLIBC_BASELINE)
+ message(FATAL_ERROR "${DRIVER} requires ${requirement}, baseline is ${GLIBC_BASELINE}")
+ endif()
+endforeach()
+execute_process(COMMAND "${READELF}" -d "${DRIVER}" OUTPUT_VARIABLE dynamic RESULT_VARIABLE rc)
+if(NOT rc EQUAL 0)
+ message(FATAL_ERROR "Cannot inspect ELF dependencies")
+endif()
+string(REGEX MATCHALL "Shared library: \\[[^]]+\\]" dependencies "${dynamic}")
+foreach(dependency IN LISTS dependencies)
+ string(REGEX REPLACE "Shared library: \\[([^]]+)\\]" "\\1" name "${dependency}")
+ if(NOT name MATCHES "^(lib(c|m|dl|pthread|rt)\\.so\\.[0-9]+|libodbcinst\\.so\\.2|ld-linux-x86-64\\.so\\.2)$")
+ message(FATAL_ERROR "Unexpected runtime dependency: ${name}")
+ endif()
+endforeach()
+if(dynamic MATCHES "(RPATH|RUNPATH)")
+ message(FATAL_ERROR "Release driver must not contain a build-time library search path")
+endif()
+message(STATUS "Single-library dependency check and GLIBC <= ${GLIBC_BASELINE} passed")
diff --git a/iotdb-client/odbc/cmake/FetchDependencies.cmake b/iotdb-client/odbc/cmake/FetchDependencies.cmake
new file mode 100644
index 0000000000000..1515fde81c1d7
--- /dev/null
+++ b/iotdb-client/odbc/cmake/FetchDependencies.cmake
@@ -0,0 +1,91 @@
+# 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(FetchContent)
+
+set(ODBC_CURL_VERSION "8.4.0" CACHE STRING "curl version used by the ODBC driver")
+string(REPLACE "." "_" _odbc_curl_tag_version "${ODBC_CURL_VERSION}")
+set(ODBC_CURL_URL
+ "https://github.com/curl/curl/releases/download/curl-${_odbc_curl_tag_version}/curl-${ODBC_CURL_VERSION}.tar.xz"
+ CACHE STRING "curl source archive")
+set(ODBC_CURL_SHA256
+ "16c62a9c4af0f703d28bda6d7bbf37ba47055ad3414d70dec63e2e6336f2a82d"
+ CACHE STRING "curl source archive SHA-256")
+set(ODBC_JSON_VERSION "3.11.3" CACHE STRING "nlohmann/json version used by the ODBC driver")
+set(ODBC_JSON_URL
+ "https://github.com/nlohmann/json/releases/download/v${ODBC_JSON_VERSION}/json.tar.xz"
+ CACHE STRING "nlohmann/json source archive")
+set(ODBC_JSON_SHA256
+ "d6c65aca6b1ed68e7a182f4757257b107ae403032760ed6ef121c9d55e81757d"
+ CACHE STRING "nlohmann/json source archive SHA-256")
+
+# The REST transport only needs HTTP. Session TLS is provided by client-cpp.
+set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
+set(BUILD_CURL_EXE OFF CACHE BOOL "" FORCE)
+set(BUILD_TESTING OFF CACHE BOOL "" FORCE)
+set(BUILD_LIBCURL_DOCS OFF CACHE BOOL "" FORCE)
+set(ENABLE_MANUAL OFF CACHE BOOL "" FORCE)
+set(HTTP_ONLY ON CACHE BOOL "" FORCE)
+set(CURL_USE_OPENSSL OFF CACHE BOOL "" FORCE)
+set(CURL_USE_SCHANNEL OFF CACHE BOOL "" FORCE)
+set(CURL_ENABLE_SSL OFF CACHE BOOL "" FORCE)
+set(CURL_ZLIB OFF CACHE BOOL "" FORCE)
+set(CURL_BROTLI OFF CACHE BOOL "" FORCE)
+set(CURL_ZSTD OFF CACHE BOOL "" FORCE)
+set(CURL_USE_LIBPSL OFF CACHE BOOL "" FORCE)
+set(CURL_USE_LIBSSH2 OFF CACHE BOOL "" FORCE)
+set(USE_NGHTTP2 OFF CACHE BOOL "" FORCE)
+set(USE_LIBIDN2 OFF CACHE BOOL "" FORCE)
+set(ENABLE_LDAP OFF CACHE BOOL "" FORCE)
+set(CURL_DISABLE_LDAP ON CACHE BOOL "" FORCE)
+set(CURL_DISABLE_LDAPS ON CACHE BOOL "" FORCE)
+set(CURL_STATIC_CRT ${MSVC} CACHE BOOL "" FORCE)
+set(JSON_BuildTests OFF CACHE BOOL "" FORCE)
+set(JSON_Install OFF CACHE BOOL "" FORCE)
+
+FetchContent_Declare(
+ odbc_curl
+ URL "${ODBC_CURL_URL}"
+ URL_HASH "SHA256=${ODBC_CURL_SHA256}"
+ DOWNLOAD_EXTRACT_TIMESTAMP TRUE)
+FetchContent_Declare(
+ odbc_json
+ URL "${ODBC_JSON_URL}"
+ URL_HASH "SHA256=${ODBC_JSON_SHA256}"
+ DOWNLOAD_EXTRACT_TIMESTAMP TRUE)
+
+# CMake 3.20 does not yet support FetchContent's EXCLUDE_FROM_ALL argument.
+# Populate explicitly so dependency targets remain buildable while their
+# install rules do not leak headers, archives, or tools into the ODBC package.
+if(POLICY CMP0169)
+ cmake_policy(SET CMP0169 OLD)
+endif()
+
+function(odbc_make_dependency_available dependency)
+ FetchContent_GetProperties(${dependency})
+ if(NOT ${dependency}_POPULATED)
+ FetchContent_Populate(${dependency})
+ add_subdirectory(
+ "${${dependency}_SOURCE_DIR}"
+ "${${dependency}_BINARY_DIR}"
+ EXCLUDE_FROM_ALL)
+ endif()
+ set(${dependency}_SOURCE_DIR "${${dependency}_SOURCE_DIR}" PARENT_SCOPE)
+endfunction()
+
+odbc_make_dependency_available(odbc_curl)
+odbc_make_dependency_available(odbc_json)
diff --git a/iotdb-client/odbc/cmake/PackageGccRuntime.cmake.in b/iotdb-client/odbc/cmake/PackageGccRuntime.cmake.in
new file mode 100644
index 0000000000000..5ffee10235832
--- /dev/null
+++ b/iotdb-client/odbc/cmake/PackageGccRuntime.cmake.in
@@ -0,0 +1,66 @@
+# 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.
+
+# Only the combined GNU/Linux binary incorporates GCC runtime object code.
+# Fetch notices at install/package time; never vendor runtime sources or license
+# texts in the Apache source release. See the GCC Runtime Library exception at
+# https://www.apache.org/legal/resolved.html#gcc-runtime-library
+set(runtime_license_cache "@CMAKE_CURRENT_BINARY_DIR@/runtime-licenses")
+file(MAKE_DIRECTORY "${runtime_license_cache}")
+
+function(package_gcc_license upstream_name package_name sha256)
+ set(cached "${runtime_license_cache}/${package_name}")
+ if(NOT EXISTS "${cached}")
+ if("@IOTDB_OFFLINE@")
+ message(FATAL_ERROR "Offline packaging requires ${cached}")
+ endif()
+ file(DOWNLOAD
+ "https://raw.githubusercontent.com/gcc-mirror/gcc/releases/gcc-14.2.0/${upstream_name}"
+ "${cached}.download" TLS_VERIFY ON STATUS download_status)
+ list(GET download_status 0 download_code)
+ if(NOT download_code EQUAL 0)
+ message(FATAL_ERROR "Cannot obtain GCC runtime notice ${upstream_name}: ${download_status}")
+ endif()
+ file(SHA256 "${cached}.download" actual_sha256)
+ if(NOT actual_sha256 STREQUAL sha256)
+ message(FATAL_ERROR "GCC runtime notice checksum mismatch: ${upstream_name}")
+ endif()
+ file(RENAME "${cached}.download" "${cached}")
+ endif()
+ file(SHA256 "${cached}" actual_sha256)
+ if(NOT actual_sha256 STREQUAL sha256)
+ message(FATAL_ERROR "Cached GCC runtime notice checksum mismatch: ${cached}")
+ endif()
+ file(INSTALL "${cached}" DESTINATION "${CMAKE_INSTALL_PREFIX}/licenses/gcc")
+endfunction()
+
+package_gcc_license(COPYING3 GPL-3.0.txt
+ 8ceb4b9ee5adedde47b31e975c1d90c73ad27b6b165a1dcd80c7c545eb65b903)
+package_gcc_license(COPYING.RUNTIME GCC-Runtime-Exception.txt
+ 9d6b43ce4d8de0c878bf16b54d8e7a10d9bd42b75178153e3af6a815bdc90f74)
+
+file(APPEND "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/LICENSE" "\n
+GNU/Linux GCC runtime inclusion
+-------------------------------
+This binary contains libstdc++ and libgcc runtime object code combined with the
+Apache IoTDB driver and its test executables by GCC. These runtime portions are
+covered by GPL-3.0-or-later WITH GCC-exception-3.1; the IoTDB source remains under
+the Apache License 2.0. See licenses/gcc/GPL-3.0.txt and
+licenses/gcc/GCC-Runtime-Exception.txt for the corresponding license texts.
+GCC homepage and upstream sources: https://gcc.gnu.org/
+Runtime license: https://gcc.gnu.org/onlinedocs/libstdc++/manual/license.html
+")
diff --git a/iotdb-client/odbc/cmake/exports.map b/iotdb-client/odbc/cmake/exports.map
new file mode 100644
index 0000000000000..6f598e38c0c54
--- /dev/null
+++ b/iotdb-client/odbc/cmake/exports.map
@@ -0,0 +1,22 @@
+/**
+ * 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.
+ */
+{
+ global: SQL*; ConfigDSN;
+ local: *;
+};
diff --git a/iotdb-client/odbc/conf/odbc.ini b/iotdb-client/odbc/conf/odbc.ini
new file mode 100644
index 0000000000000..dd7a3c6fcfffd
--- /dev/null
+++ b/iotdb-client/odbc/conf/odbc.ini
@@ -0,0 +1,29 @@
+; 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.
+[IoTDB]
+Driver=Apache IoTDB ODBC Driver
+SERVER=127.0.0.1
+PORT=6667
+UID=root
+DATABASE=information_schema
+ISTABLEMODEL=1
+SSL=0
+; For TLS set SSL=1 and SSLCA to a trusted PEM CA file.
+; SSLCA=/etc/iotdb/ca.pem
+; For mutual TLS also set both of these PEM file paths:
+; SSLCERT=/etc/iotdb/client.pem
+; SSLKEY=/etc/iotdb/client.key
diff --git a/iotdb-client/odbc/conf/odbcinst.ini b/iotdb-client/odbc/conf/odbcinst.ini
new file mode 100644
index 0000000000000..a447be86d7ae3
--- /dev/null
+++ b/iotdb-client/odbc/conf/odbcinst.ini
@@ -0,0 +1,20 @@
+; 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.
+[Apache IoTDB ODBC Driver]
+Description=Apache IoTDB ODBC Driver
+Driver=/opt/iotdb-odbc/lib/libapache_iotdb_odbc.so
+Threading=2
diff --git a/iotdb-client/odbc/examples/cpp-example/README.md b/iotdb-client/odbc/examples/cpp-example/README.md
new file mode 100644
index 0000000000000..72748a8de89ec
--- /dev/null
+++ b/iotdb-client/odbc/examples/cpp-example/README.md
@@ -0,0 +1,103 @@
+
+# C++ ODBC Example
+
+This directory contains C++ example programs that connect to IoTDB using the Apache IoTDB ODBC driver.
+
+## Example Files
+
+| File | Description |
+|------|-------------|
+| **TableTest.cpp** | Table Model example. Uses table-style SQL such as `CREATE TABLE`, `INSERT INTO table`, suitable for IoTDB Table Model. |
+| **TreeTest.cpp** | Tree Model example. Uses timeseries-style SQL such as `CREATE DATABASE`, `CREATE TIMESERIES`, `INSERT INTO root.xxx.xxx`, suitable for IoTDB native Tree Model. |
+
+### Table Model vs Tree Model
+
+- **Table Model**: Connection string defaults to or explicitly sets `istablemodel=1`. Uses `use database`, `CREATE TABLE`, `INSERT INTO tablename`; data is stored in tables.
+- **Tree Model**: Connection string requires `istablemodel=0`. Uses full paths like `root.xxx.xxx`, creates timeseries via `CREATE TIMESERIES`; data is organized by device/measurement.
+
+## Prerequisites
+
+1. **Build Environment**: Visual Studio 2022 (or x64 Native Tools Command Prompt)
+2. **ODBC Driver**: Apache IoTDB ODBC driver installed and configured, with DSN name `Apache IoTDB DSN`
+3. **IoTDB Service**: IoTDB is running, default `127.0.0.1:6667`
+4. **Dependencies**: `odbc32.lib` (Windows ODBC). When installing Visual Studio with the Windows SDK and C++ development tools options selected, `odbc32.lib` is automatically included.
+
+## Build and Run
+
+### 1. Open Command Prompt
+
+Open **x64 Native Tools Command Prompt for VS 2022** (or the corresponding version of VS Command Prompt).
+
+### 2. Navigate to Example Directory
+
+```bash
+cd examples\cpp-example
+```
+
+### 3. Build
+
+**Table Model example:**
+```bash
+cl /EHsc /W4 /O2 TableTest.cpp /link odbc32.lib
+```
+
+**Tree Model example:**
+```bash
+cl /EHsc /W4 /O2 TreeTest.cpp /link odbc32.lib
+```
+
+### 4. Run
+
+**Table Model:**
+```bash
+TableTest
+```
+
+**Tree Model:**
+```bash
+TreeTest
+```
+
+## Connection String Parameters
+
+| Parameter | Description | Example |
+|-----------|-------------|---------|
+| DSN | Data Source Name | `Apache IoTDB DSN` |
+| Server | IoTDB server address | `127.0.0.1` |
+| UID | Username | `root` |
+| PWD | Password | `root` |
+| Database | Database name (Table Model) | `test` |
+| loglevel | Log level 0–4 | `4` |
+| istablemodel | 0=Tree Model, 1=Table Model | `0` or `1` |
+
+## Encoding Notes
+
+STRING and TEXT columns use UTF-8 encoding. Direct output in the Windows console may show garbled characters; you can redirect output to a file to view correctly:
+
+```bash
+TreeTest > output.txt
+```
+
+## FAQ
+
+1. **Connection failed**: Check whether IoTDB is running, the DSN is configured correctly, and the Server/port are correct.
+2. **Garbled characters**: If SQL statements contain non-ASCII characters, make sure the source file is saved with code page encoding. Alternatively, use English test data to avoid encoding issues.
diff --git a/iotdb-client/odbc/examples/cpp-example/TableTest.cpp b/iotdb-client/odbc/examples/cpp-example/TableTest.cpp
new file mode 100644
index 0000000000000..a31f4534777bc
--- /dev/null
+++ b/iotdb-client/odbc/examples/cpp-example/TableTest.cpp
@@ -0,0 +1,534 @@
+/**
+ * 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 WIN32_LEAN_AND_MEAN
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifndef SQL_DIAG_COLUMN_SIZE
+#define SQL_DIAG_COLUMN_SIZE 33L
+#endif
+
+// Error handling function
+void CheckOdbcError(SQLRETURN retCode, SQLSMALLINT handleType, SQLHANDLE handle,
+ const char* functionName) {
+ if (retCode == SQL_SUCCESS || retCode == SQL_SUCCESS_WITH_INFO) {
+ return;
+ }
+
+ SQLCHAR sqlState[6];
+ SQLCHAR message[SQL_MAX_MESSAGE_LENGTH];
+ SQLINTEGER nativeError;
+ SQLSMALLINT textLength;
+ SQLRETURN errRet;
+ errRet = SQLGetDiagRec(handleType, handle, 1, sqlState, &nativeError, message, sizeof(message),
+ &textLength);
+
+ std::cerr << "ODBC Error in " << functionName << ":\n";
+ std::cerr << " SQL State: " << sqlState << "\n";
+ std::cerr << " Native Error: " << nativeError << "\n";
+ std::cerr << " Message: " << message << "\n";
+ std::cerr << " SQLGetDiagRec Return: " << errRet << "\n";
+
+ if (retCode == SQL_ERROR || retCode == SQL_INVALID_HANDLE) {
+ exit(1);
+ }
+}
+
+// Simplified table output - display basic data only
+void PrintSimpleTable(const std::vector& headers,
+ const std::vector>& rows) {
+ // Print header
+ for (size_t i = 0; i < headers.size(); i++) {
+ std::cout << headers[i];
+ if (i < headers.size() - 1)
+ std::cout << "\t";
+ }
+ std::cout << std::endl;
+
+ // Print separator
+ for (size_t i = 0; i < headers.size(); i++) {
+ std::cout << "----------------";
+ if (i < headers.size() - 1)
+ std::cout << "\t";
+ }
+ std::cout << std::endl;
+
+ // Print data rows
+ for (const auto& row : rows) {
+ for (size_t i = 0; i < row.size(); i++) {
+ std::cout << row[i];
+ if (i < row.size() - 1)
+ std::cout << "\t";
+ }
+ std::cout << std::endl;
+ }
+ std::cout << std::endl;
+}
+
+/// Execute a SELECT query and display fulltable results in table format
+void Query(SQLHDBC hDbc) {
+ SQLHSTMT hStmt = SQL_NULL_HSTMT;
+ SQLRETURN ret = SQL_SUCCESS;
+
+ try {
+ // Allocate statement handle
+ ret = SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt);
+ if (!SQL_SUCCEEDED(ret)) {
+ CheckOdbcError(ret, SQL_HANDLE_DBC, hDbc, "SQLAllocHandle(SQL_HANDLE_STMT)");
+ return;
+ }
+
+ // Execute query
+ const std::string sqlQuery = "select * from fulltable";
+ std::cout << "Execute query: " << sqlQuery << std::endl;
+
+ ret = SQLExecDirect(hStmt, reinterpret_cast(const_cast(sqlQuery.c_str())),
+ SQL_NTS);
+ if (!SQL_SUCCEEDED(ret)) {
+ if (ret != SQL_NO_DATA) {
+ CheckOdbcError(ret, SQL_HANDLE_STMT, hStmt, "SQLExecDirect(SELECT)");
+ }
+ SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+ return;
+ }
+
+ // Get column count
+ SQLSMALLINT colCount = 0;
+ ret = SQLNumResultCols(hStmt, &colCount);
+ if (!SQL_SUCCEEDED(ret)) {
+ CheckOdbcError(ret, SQL_HANDLE_STMT, hStmt, "SQLNumResultCols");
+ SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+ return;
+ }
+
+ std::cout << "Column count = " << colCount << std::endl;
+
+ // Return immediately if no columns
+ if (colCount <= 0) {
+ SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+ return;
+ }
+
+ // Get column names and type information
+ std::vector columnNames;
+ std::vector columnTypes(colCount);
+ std::vector columnSizes(colCount);
+ std::vector decimalDigits(colCount);
+ std::vector nullable(colCount);
+
+ // Get basic column information
+ for (SQLSMALLINT i = 1; i <= colCount; i++) {
+ SQLSMALLINT nameLength = 0;
+ ret = SQLDescribeCol(hStmt, i, NULL, 0, &nameLength, NULL, NULL, NULL, NULL);
+ if (!SQL_SUCCEEDED(ret)) {
+ CheckOdbcError(ret, SQL_HANDLE_STMT, hStmt, "SQLDescribeCol (get length)");
+ SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+ return;
+ }
+
+ std::vector colNameBuffer(nameLength + 1);
+ SQLSMALLINT actualNameLength = 0;
+
+ ret = SQLDescribeCol(hStmt, i, colNameBuffer.data(), nameLength + 1, &actualNameLength, NULL,
+ NULL, NULL, NULL);
+ if (!SQL_SUCCEEDED(ret)) {
+ CheckOdbcError(ret, SQL_HANDLE_STMT, hStmt, "SQLDescribeCol (get name)");
+ SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+ return;
+ }
+
+ std::string fullName(reinterpret_cast(colNameBuffer.data()));
+
+ size_t pos = fullName.find_last_of('.');
+ if (pos != std::string::npos) {
+ columnNames.push_back(fullName.substr(pos + 1));
+ } else {
+ columnNames.push_back(fullName);
+ }
+
+ ret = SQLDescribeCol(hStmt, i, NULL, 0, NULL, &columnTypes[i - 1], &columnSizes[i - 1],
+ &decimalDigits[i - 1], &nullable[i - 1]);
+ if (!SQL_SUCCEEDED(ret)) {
+ CheckOdbcError(ret, SQL_HANDLE_STMT, hStmt, "SQLDescribeCol (get type info)");
+ SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+ return;
+ }
+ }
+
+ std::vector> tableRows;
+
+ int rowCount = 0;
+ // Get data front every row
+ while (true) {
+ ret = SQLFetch(hStmt);
+ if (ret == SQL_NO_DATA) {
+ break;
+ }
+
+ if (!SQL_SUCCEEDED(ret)) {
+ CheckOdbcError(ret, SQL_HANDLE_STMT, hStmt, "SQLFetch");
+ break;
+ }
+
+ std::vector row;
+
+ for (SQLSMALLINT i = 1; i <= colCount; i++) {
+ SQLLEN indicator = 0;
+ std::string valueStr;
+
+ SQLSMALLINT cType;
+ size_t bufferSize;
+ bool isCharacterType = false;
+ const int maxBufferSize = 32768;
+
+ switch (columnTypes[i - 1]) {
+ case SQL_CHAR:
+ case SQL_VARCHAR:
+ case SQL_LONGVARCHAR:
+ case SQL_WCHAR:
+ case SQL_WVARCHAR:
+ case SQL_WLONGVARCHAR:
+ cType = SQL_C_CHAR;
+ if (columnSizes[i - 1] > 0) {
+ bufferSize = min(maxBufferSize, static_cast(columnSizes[i - 1]) * 4 + 1);
+ } else {
+ bufferSize = maxBufferSize;
+ }
+ isCharacterType = true;
+ break;
+
+ case SQL_DECIMAL:
+ case SQL_NUMERIC:
+ cType = SQL_C_CHAR;
+ if (columnSizes[i - 1] > 0) {
+ bufferSize = min(maxBufferSize, static_cast(columnSizes[i - 1]) * 4 + 1);
+ } else {
+ bufferSize = maxBufferSize;
+ }
+ isCharacterType = true;
+ break;
+
+ case SQL_INTEGER:
+ case SQL_SMALLINT:
+ case SQL_TINYINT:
+ case SQL_BIGINT:
+ cType = SQL_C_SBIGINT;
+ bufferSize = sizeof(SQLBIGINT);
+ break;
+
+ case SQL_REAL:
+ case SQL_FLOAT:
+ case SQL_DOUBLE:
+ cType = SQL_C_DOUBLE;
+ bufferSize = sizeof(double);
+ break;
+
+ case SQL_BIT:
+ cType = SQL_C_BIT;
+ bufferSize = sizeof(SQLCHAR);
+ break;
+
+ case SQL_DATE:
+ case SQL_TYPE_DATE:
+ cType = SQL_C_DATE;
+ bufferSize = sizeof(SQL_DATE_STRUCT);
+ break;
+
+ case SQL_TIME:
+ case SQL_TYPE_TIME:
+ cType = SQL_C_TIME;
+ bufferSize = sizeof(SQL_TIME_STRUCT);
+ break;
+
+ case SQL_TIMESTAMP:
+ case SQL_TYPE_TIMESTAMP:
+ cType = SQL_C_TIMESTAMP;
+ bufferSize = sizeof(SQL_TIMESTAMP_STRUCT);
+ break;
+
+ default:
+ cType = SQL_C_CHAR;
+ bufferSize = 256;
+ isCharacterType = true;
+ break;
+ }
+
+ std::vector buffer(bufferSize);
+
+ ret = SQLGetData(hStmt, i, cType, buffer.data(), bufferSize, &indicator);
+
+ if (indicator == SQL_NULL_DATA) {
+ valueStr = "NULL";
+ } else if (ret != SQL_SUCCESS) {
+ valueStr = "ERR_CONV";
+ } else {
+ if (cType == SQL_C_CHAR) {
+ valueStr = reinterpret_cast(buffer.data());
+ } else if (cType == SQL_C_SBIGINT) {
+ SQLBIGINT intVal = *reinterpret_cast(buffer.data());
+ valueStr = std::to_string(intVal);
+ } else if (cType == SQL_C_DOUBLE) {
+ double doubleVal = *reinterpret_cast(buffer.data());
+ valueStr = std::to_string(doubleVal);
+ } else if (cType == SQL_C_BIT) {
+ valueStr = (*buffer.data() != 0) ? "TRUE" : "FALSE";
+ } else if (cType == SQL_C_DATE) {
+ SQL_DATE_STRUCT* date = reinterpret_cast(buffer.data());
+ char dateStr[20];
+ snprintf(dateStr, sizeof(dateStr), "%04d-%02d-%02d", date->year, date->month,
+ date->day);
+ valueStr = dateStr;
+ } else if (cType == SQL_C_TIME) {
+ SQL_TIME_STRUCT* time = reinterpret_cast(buffer.data());
+ char timeStr[15];
+ snprintf(timeStr, sizeof(timeStr), "%02d:%02d:%02d", time->hour, time->minute,
+ time->second);
+ valueStr = timeStr;
+ } else if (cType == SQL_C_TIMESTAMP) {
+ SQL_TIMESTAMP_STRUCT* ts = reinterpret_cast(buffer.data());
+ char tsStr[30];
+ snprintf(tsStr, sizeof(tsStr), "%04d-%02d-%02d %02d:%02d:%02d.%06d", ts->year,
+ ts->month, ts->day, ts->hour, ts->minute, ts->second, ts->fraction / 1000);
+ valueStr = tsStr;
+ } else {
+ valueStr = "UNKNOWN_TYPE";
+ }
+
+ if (isCharacterType && ret == SQL_SUCCESS_WITH_INFO) {
+ SQLLEN actualSize = 0;
+ SQLGetDiagField(SQL_HANDLE_STMT, hStmt, 0, SQL_DIAG_COLUMN_SIZE, &actualSize,
+ SQL_IS_INTEGER, NULL);
+
+ if (indicator > 0 && static_cast(indicator) > bufferSize - 1) {
+ valueStr += "...";
+ }
+ }
+ }
+
+ row.push_back(valueStr);
+ }
+
+ tableRows.push_back(row);
+ }
+
+ if (!tableRows.empty()) {
+ PrintSimpleTable(columnNames, tableRows);
+ }
+
+ SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+ } catch (const std::exception& ex) {
+ std::cerr << "Exception: " << ex.what() << std::endl;
+ if (hStmt != SQL_NULL_HSTMT) {
+ SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+ }
+ throw;
+ } catch (...) {
+ std::cerr << "Unknown exception occurred" << std::endl;
+ if (hStmt != SQL_NULL_HSTMT) {
+ SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+ }
+ throw;
+ }
+}
+
+/// Execute a non-query SQL statement (e.g. CREATE DATABASE, CREATE TABLE, INSERT)
+void Execute(SQLHDBC hDbc, const std::string& command) {
+ SQLHSTMT hStmt = SQL_NULL_HSTMT;
+ SQLRETURN ret;
+
+ try {
+ // Allocate statement handle
+ ret = SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt);
+ CheckOdbcError(ret, SQL_HANDLE_DBC, hDbc, "SQLAllocHandle(SQL_HANDLE_STMT)");
+
+ // Execute command
+ ret = SQLExecDirect(hStmt, (SQLCHAR*)command.c_str(), SQL_NTS);
+ if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
+ CheckOdbcError(ret, SQL_HANDLE_STMT, hStmt, "SQLExecDirect");
+ }
+
+ // Free statement handle
+ SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+ } catch (...) {
+ if (hStmt != SQL_NULL_HSTMT) {
+ SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+ }
+ throw;
+ }
+}
+
+int main() {
+ SQLHENV hEnv = SQL_NULL_HENV;
+ SQLHDBC hDbc = SQL_NULL_HDBC;
+ SQLRETURN ret;
+
+ try {
+ std::cout << "Start" << std::endl;
+
+ // 1. Initialize ODBC environment
+ ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv);
+ CheckOdbcError(ret, SQL_HANDLE_ENV, hEnv, "SQLAllocHandle(SQL_HANDLE_ENV)");
+
+ ret = SQLSetEnvAttr(hEnv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0);
+ CheckOdbcError(ret, SQL_HANDLE_ENV, hEnv, "SQLSetEnvAttr");
+
+ // 2. Establish connection
+ ret = SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hDbc);
+ CheckOdbcError(ret, SQL_HANDLE_ENV, hEnv, "SQLAllocHandle(SQL_HANDLE_DBC)");
+
+ // Connection string
+ std::string dsn = "Apache IoTDB DSN";
+ std::string user = "root";
+ std::string password = "root";
+ std::string server = "127.0.0.1";
+ std::string database = "test";
+
+ std::string connectionString = "DSN=" + dsn + ";Server=" + server + ";UID=" + user +
+ ";PWD=" + password + ";Database=" + database + ";loglevel=4";
+ std::cout << "Using connection string: " << connectionString << std::endl;
+
+ SQLCHAR outConnStr[1024];
+ SQLSMALLINT outConnStrLen;
+
+ ret = SQLDriverConnect(hDbc, NULL, (SQLCHAR*)connectionString.c_str(), SQL_NTS, outConnStr,
+ sizeof(outConnStr), &outConnStrLen, SQL_DRIVER_COMPLETE);
+
+ if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
+ std::cerr << "Login failed" << std::endl;
+ CheckOdbcError(ret, SQL_HANDLE_DBC, hDbc, "SQLDriverConnect");
+ return 1;
+ }
+
+ // Get driver name
+ SQLCHAR driverName[256];
+ SQLSMALLINT nameLength;
+ ret = SQLGetInfo(hDbc, SQL_DRIVER_NAME, driverName, sizeof(driverName), &nameLength);
+ CheckOdbcError(ret, SQL_HANDLE_DBC, hDbc, "SQLGetInfo");
+
+ std::cout << "Successfully opened connection. database name = " << driverName << std::endl;
+
+ // 3. Execute operations
+ Execute(hDbc, "CREATE DATABASE IF NOT EXISTS test");
+ Execute(hDbc, "use test");
+ std::cout << "use test Execute complete. Begin to setup fulltable." << std::endl;
+
+ // Create fulltable and insert test data
+ Execute(hDbc,
+ "CREATE TABLE IF NOT EXISTS fullTable (time TIMESTAMP TIME, bool_col BOOLEAN FIELD, "
+ "int32_col INT32 FIELD, int64_col INT64 FIELD, float_col FLOAT FIELD, double_col "
+ "DOUBLE FIELD, text_col TEXT FIELD, string_col STRING FIELD, blob_col BLOB FIELD, "
+ "timestamp_col TIMESTAMP FIELD, date_col DATE FIELD) WITH (TTL=315360000000)");
+ const char* insertStatements[] = {
+ "INSERT INTO fulltable VALUES (1735689600000, true, 100, 10000000000, 36.5, 128.689, "
+ "'Device running normally', 'DeviceA-Room1', '0x506C616E7444617461', 1735689600000, "
+ "'2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735689660000, false, 101, 10000000001, 36.6, 128.789, "
+ "'Device running normally', 'DeviceA-Room1', '0x506C616E7444617461', 1735689660000, "
+ "'2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735689720000, true, 102, 10000000002, 36.7, 128.889, "
+ "'Device running normally', 'DeviceA-Room1', '0x506C616E7444617461', 1735689720000, "
+ "'2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735689780000, false, 103, 10000000003, 36.8, 128.989, "
+ "'Device high temperature alert', 'DeviceA-Room1', '0x506C616E7444617462', 1735689780000, "
+ "'2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735689840000, true, 104, 10000000004, 36.9, 129.089, "
+ "'Device status restored', 'DeviceA-Room1', '0x506C616E7444617461', 1735689840000, "
+ "'2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735689900000, false, 105, 10000000005, 37.0, 129.189, "
+ "'Device running normally', 'DeviceB-Room2', '0x506C616E7444617463', 1735689900000, "
+ "'2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735689960000, true, 106, 10000000006, 37.1, 129.289, "
+ "'Device running normally', 'DeviceB-Room2', '0x506C616E7444617463', 1735689960000, "
+ "'2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735690020000, false, 107, 10000000007, 37.2, 129.389, "
+ "'Device low humidity alert', 'DeviceB-Room2', '0x506C616E7444617464', 1735690020000, "
+ "'2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735690080000, true, 108, 10000000008, 37.3, 129.489, "
+ "'Device status restored', 'DeviceB-Room2', '0x506C616E7444617463', 1735690080000, "
+ "'2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735690140000, false, 109, 10000000009, 37.4, 129.589, "
+ "'Device running normally', 'DeviceC-Room3', '0x506C616E7444617465', 1735690140000, "
+ "'2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735690200000, true, 110, 10000000010, 37.5, 129.689, "
+ "'Device running normally', 'DeviceC-Room3', '0x506C616E7444617465', 1735690200000, "
+ "'2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735690260000, false, 111, 10000000011, 37.6, 129.789, "
+ "'Device unstable voltage alert', 'DeviceC-Room3', '0x506C616E7444617466', 1735690260000, "
+ "'2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735690320000, true, 112, 10000000012, 37.7, 129.889, "
+ "'Device status restored', 'DeviceC-Room3', '0x506C616E7444617465', 1735690320000, "
+ "'2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735690380000, false, 113, 10000000013, 37.8, 129.989, "
+ "'Device running normally', 'DeviceD-Room4', '0x506C616E7444617467', 1735690380000, "
+ "'2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735690440000, true, 114, 10000000014, 37.9, 130.089, "
+ "'Device running normally', 'DeviceD-Room4', '0x506C616E7444617467', 1735690440000, "
+ "'2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735690500000, false, 115, 10000000015, 38.0, 130.189, "
+ "'Device running normally', 'DeviceD-Room4', '0x506C616E7444617467', 1735690500000, "
+ "'2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735690560000, true, 116, 10000000016, 38.1, 130.289, "
+ "'Device signal interrupted alert', 'DeviceD-Room4', '0x506C616E7444617468', "
+ "1735690560000, '2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735690620000, false, 117, 10000000017, 38.2, 130.389, "
+ "'Device running normally', 'DeviceE-Room5', '0x506C616E7444617469', 1735690620000, "
+ "'2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735690680000, true, 118, 10000000018, 38.3, 130.489, "
+ "'Device running normally', 'DeviceE-Room5', '0x506C616E7444617469', 1735690680000, "
+ "'2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735690740000, false, 119, 10000000019, 38.4, 130.589, "
+ "'Device running normally', 'DeviceE-Room5', '0x506C616E7444617469', 1735690740000, "
+ "'2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735690790000, false, 119, 10000000019, 38.4, 130.589, "
+ "'Device running normally', 'DeviceE-Room5', '0x506C616E7444617469', 1735690740000, "
+ "'2026-01-04')"};
+ for (const char* sql : insertStatements) {
+ Execute(hDbc, sql);
+ }
+ std::cout << "fulltable setup complete. Begin to query." << std::endl;
+ Query(hDbc);
+ std::cout << "Query ok" << std::endl;
+
+ // 4. Clean up resources
+ SQLDisconnect(hDbc);
+ SQLFreeHandle(SQL_HANDLE_DBC, hDbc);
+ SQLFreeHandle(SQL_HANDLE_ENV, hEnv);
+
+ return 0;
+ } catch (...) {
+ // Exception cleanup
+ if (hDbc != SQL_NULL_HDBC) {
+ SQLDisconnect(hDbc);
+ SQLFreeHandle(SQL_HANDLE_DBC, hDbc);
+ }
+ if (hEnv != SQL_NULL_HENV) {
+ SQLFreeHandle(SQL_HANDLE_ENV, hEnv);
+ }
+
+ std::cerr << "Unexpected error!" << std::endl;
+ return 1;
+ }
+}
diff --git a/iotdb-client/odbc/examples/cpp-example/TreeTest.cpp b/iotdb-client/odbc/examples/cpp-example/TreeTest.cpp
new file mode 100644
index 0000000000000..644d1daca48d5
--- /dev/null
+++ b/iotdb-client/odbc/examples/cpp-example/TreeTest.cpp
@@ -0,0 +1,525 @@
+/**
+ * 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 WIN32_LEAN_AND_MEAN
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifndef SQL_DIAG_COLUMN_SIZE
+#define SQL_DIAG_COLUMN_SIZE 33L
+#endif
+
+void CheckOdbcError(SQLRETURN retCode, SQLSMALLINT handleType, SQLHANDLE handle,
+ const char* functionName) {
+ if (retCode == SQL_SUCCESS || retCode == SQL_SUCCESS_WITH_INFO) {
+ return;
+ }
+
+ SQLCHAR sqlState[6];
+ SQLCHAR message[SQL_MAX_MESSAGE_LENGTH];
+ SQLINTEGER nativeError;
+ SQLSMALLINT textLength;
+ SQLRETURN errRet;
+ errRet = SQLGetDiagRec(handleType, handle, 1, sqlState, &nativeError, message, sizeof(message),
+ &textLength);
+
+ std::cerr << "ODBC Error in " << functionName << ":\n";
+ std::cerr << " SQL State: " << sqlState << "\n";
+ std::cerr << " Native Error: " << nativeError << "\n";
+ std::cerr << " Message: " << message << "\n";
+ std::cerr << " SQLGetDiagRec Return: " << errRet << "\n";
+
+ if (retCode == SQL_ERROR || retCode == SQL_INVALID_HANDLE) {
+ exit(1);
+ }
+}
+
+void PrintSimpleTable(const std::vector& headers,
+ const std::vector>& rows) {
+ for (size_t i = 0; i < headers.size(); i++) {
+ std::cout << headers[i];
+ if (i < headers.size() - 1)
+ std::cout << "\t";
+ }
+ std::cout << std::endl;
+
+ for (size_t i = 0; i < headers.size(); i++) {
+ std::cout << "----------------";
+ if (i < headers.size() - 1)
+ std::cout << "\t";
+ }
+ std::cout << std::endl;
+
+ for (const auto& row : rows) {
+ for (size_t i = 0; i < row.size(); i++) {
+ std::cout << row[i];
+ if (i < row.size() - 1)
+ std::cout << "\t";
+ }
+ std::cout << std::endl;
+ }
+ std::cout << std::endl;
+}
+
+/// Execute a SELECT query and display results of root.full.fulldevice in table format
+void Query(SQLHDBC hDbc) {
+ SQLHSTMT hStmt = SQL_NULL_HSTMT;
+ SQLRETURN ret = SQL_SUCCESS;
+
+ try {
+ ret = SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt);
+ if (!SQL_SUCCEEDED(ret)) {
+ CheckOdbcError(ret, SQL_HANDLE_DBC, hDbc, "SQLAllocHandle(SQL_HANDLE_STMT)");
+ return;
+ }
+
+ const std::string sqlQuery =
+ "SELECT * FROM root.full.fulldevice WHERE time >= 1735689600000 AND time <= 1735690790000";
+ std::cout << "Execute query: " << sqlQuery << std::endl;
+
+ ret = SQLExecDirect(hStmt, reinterpret_cast(const_cast(sqlQuery.c_str())),
+ SQL_NTS);
+ if (!SQL_SUCCEEDED(ret)) {
+ if (ret != SQL_NO_DATA) {
+ CheckOdbcError(ret, SQL_HANDLE_STMT, hStmt, "SQLExecDirect(SELECT)");
+ }
+ SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+ return;
+ }
+
+ SQLSMALLINT colCount = 0;
+ ret = SQLNumResultCols(hStmt, &colCount);
+ if (!SQL_SUCCEEDED(ret)) {
+ CheckOdbcError(ret, SQL_HANDLE_STMT, hStmt, "SQLNumResultCols");
+ SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+ return;
+ }
+
+ std::cout << "Column count = " << colCount << std::endl;
+
+ if (colCount <= 0) {
+ SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+ return;
+ }
+
+ std::vector columnNames;
+ std::vector columnTypes(colCount);
+ std::vector columnSizes(colCount);
+ std::vector decimalDigits(colCount);
+ std::vector nullable(colCount);
+
+ // Get basic column information
+ for (SQLSMALLINT i = 1; i <= colCount; i++) {
+ SQLSMALLINT nameLength = 0;
+ ret = SQLDescribeCol(hStmt, i, NULL, 0, &nameLength, NULL, NULL, NULL, NULL);
+ if (!SQL_SUCCEEDED(ret)) {
+ CheckOdbcError(ret, SQL_HANDLE_STMT, hStmt, "SQLDescribeCol (get length)");
+ SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+ return;
+ }
+
+ std::vector colNameBuffer(nameLength + 1);
+ SQLSMALLINT actualNameLength = 0;
+
+ ret = SQLDescribeCol(hStmt, i, colNameBuffer.data(), nameLength + 1, &actualNameLength, NULL,
+ NULL, NULL, NULL);
+ if (!SQL_SUCCEEDED(ret)) {
+ CheckOdbcError(ret, SQL_HANDLE_STMT, hStmt, "SQLDescribeCol (get name)");
+ SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+ return;
+ }
+
+ std::string fullName(reinterpret_cast(colNameBuffer.data()));
+
+ size_t pos = fullName.find_last_of('.');
+ if (pos != std::string::npos) {
+ columnNames.push_back(fullName.substr(pos + 1));
+ } else {
+ columnNames.push_back(fullName);
+ }
+
+ ret = SQLDescribeCol(hStmt, i, NULL, 0, NULL, &columnTypes[i - 1], &columnSizes[i - 1],
+ &decimalDigits[i - 1], &nullable[i - 1]);
+ if (!SQL_SUCCEEDED(ret)) {
+ CheckOdbcError(ret, SQL_HANDLE_STMT, hStmt, "SQLDescribeCol (get type info)");
+ SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+ return;
+ }
+ }
+
+ std::vector> tableRows;
+
+ int rowCount = 0;
+ // Get data front every row
+ while (true) {
+ ret = SQLFetch(hStmt);
+ if (ret == SQL_NO_DATA) {
+ break;
+ }
+
+ if (!SQL_SUCCEEDED(ret)) {
+ CheckOdbcError(ret, SQL_HANDLE_STMT, hStmt, "SQLFetch");
+ break;
+ }
+
+ std::vector row;
+
+ for (SQLSMALLINT i = 1; i <= colCount; i++) {
+ SQLLEN indicator = 0;
+ std::string valueStr;
+
+ SQLSMALLINT cType;
+ size_t bufferSize;
+ bool isCharacterType = false;
+ const int maxBufferSize = 32768;
+
+ switch (columnTypes[i - 1]) {
+ case SQL_CHAR:
+ case SQL_VARCHAR:
+ case SQL_LONGVARCHAR:
+ case SQL_WCHAR:
+ case SQL_WVARCHAR:
+ case SQL_WLONGVARCHAR:
+ cType = SQL_C_CHAR;
+ if (columnSizes[i - 1] > 0) {
+ bufferSize = min(maxBufferSize, static_cast(columnSizes[i - 1]) * 4 + 1);
+ } else {
+ bufferSize = maxBufferSize;
+ }
+ isCharacterType = true;
+ break;
+
+ case SQL_DECIMAL:
+ case SQL_NUMERIC:
+ cType = SQL_C_CHAR;
+ if (columnSizes[i - 1] > 0) {
+ bufferSize = min(maxBufferSize, static_cast(columnSizes[i - 1]) * 4 + 1);
+ } else {
+ bufferSize = maxBufferSize;
+ }
+ isCharacterType = true;
+ break;
+
+ case SQL_INTEGER:
+ case SQL_SMALLINT:
+ case SQL_TINYINT:
+ case SQL_BIGINT:
+ cType = SQL_C_SBIGINT;
+ bufferSize = sizeof(SQLBIGINT);
+ break;
+
+ case SQL_REAL:
+ case SQL_FLOAT:
+ case SQL_DOUBLE:
+ cType = SQL_C_DOUBLE;
+ bufferSize = sizeof(double);
+ break;
+
+ case SQL_BIT:
+ cType = SQL_C_BIT;
+ bufferSize = sizeof(SQLCHAR);
+ break;
+
+ case SQL_DATE:
+ case SQL_TYPE_DATE:
+ cType = SQL_C_DATE;
+ bufferSize = sizeof(SQL_DATE_STRUCT);
+ break;
+
+ case SQL_TIME:
+ case SQL_TYPE_TIME:
+ cType = SQL_C_TIME;
+ bufferSize = sizeof(SQL_TIME_STRUCT);
+ break;
+
+ case SQL_TIMESTAMP:
+ case SQL_TYPE_TIMESTAMP:
+ cType = SQL_C_TIMESTAMP;
+ bufferSize = sizeof(SQL_TIMESTAMP_STRUCT);
+ break;
+
+ default:
+ cType = SQL_C_CHAR;
+ bufferSize = 256;
+ isCharacterType = true;
+ break;
+ }
+
+ std::vector buffer(bufferSize);
+
+ ret = SQLGetData(hStmt, i, cType, buffer.data(), bufferSize, &indicator);
+
+ if (indicator == SQL_NULL_DATA) {
+ valueStr = "NULL";
+ } else if (ret != SQL_SUCCESS) {
+ valueStr = "ERR_CONV";
+ } else {
+ if (cType == SQL_C_CHAR) {
+ valueStr = reinterpret_cast(buffer.data());
+ } else if (cType == SQL_C_SBIGINT) {
+ SQLBIGINT intVal = *reinterpret_cast(buffer.data());
+ valueStr = std::to_string(intVal);
+ } else if (cType == SQL_C_DOUBLE) {
+ double doubleVal = *reinterpret_cast(buffer.data());
+ valueStr = std::to_string(doubleVal);
+ } else if (cType == SQL_C_BIT) {
+ valueStr = (*buffer.data() != 0) ? "TRUE" : "FALSE";
+ } else if (cType == SQL_C_DATE) {
+ SQL_DATE_STRUCT* date = reinterpret_cast(buffer.data());
+ char dateStr[20];
+ snprintf(dateStr, sizeof(dateStr), "%04d-%02d-%02d", date->year, date->month,
+ date->day);
+ valueStr = dateStr;
+ } else if (cType == SQL_C_TIME) {
+ SQL_TIME_STRUCT* time = reinterpret_cast(buffer.data());
+ char timeStr[15];
+ snprintf(timeStr, sizeof(timeStr), "%02d:%02d:%02d", time->hour, time->minute,
+ time->second);
+ valueStr = timeStr;
+ } else if (cType == SQL_C_TIMESTAMP) {
+ SQL_TIMESTAMP_STRUCT* ts = reinterpret_cast(buffer.data());
+ char tsStr[30];
+ snprintf(tsStr, sizeof(tsStr), "%04d-%02d-%02d %02d:%02d:%02d.%06d", ts->year,
+ ts->month, ts->day, ts->hour, ts->minute, ts->second, ts->fraction / 1000);
+ valueStr = tsStr;
+ } else {
+ valueStr = "UNKNOWN_TYPE";
+ }
+
+ if (isCharacterType && ret == SQL_SUCCESS_WITH_INFO) {
+ SQLLEN actualSize = 0;
+ SQLGetDiagField(SQL_HANDLE_STMT, hStmt, 0, SQL_DIAG_COLUMN_SIZE, &actualSize,
+ SQL_IS_INTEGER, NULL);
+
+ if (indicator > 0 && static_cast(indicator) > bufferSize - 1) {
+ valueStr += "...";
+ }
+ }
+ }
+
+ row.push_back(valueStr);
+ }
+
+ tableRows.push_back(row);
+ }
+
+ if (!tableRows.empty()) {
+ PrintSimpleTable(columnNames, tableRows);
+ }
+
+ SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+ } catch (const std::exception& ex) {
+ std::cerr << "Exception: " << ex.what() << std::endl;
+ if (hStmt != SQL_NULL_HSTMT) {
+ SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+ }
+ throw;
+ } catch (...) {
+ std::cerr << "Unknown exception occurred" << std::endl;
+ if (hStmt != SQL_NULL_HSTMT) {
+ SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+ }
+ throw;
+ }
+}
+
+/// Execute a non-query SQL statement (e.g. INSERT; tree model INSERT auto-creates timeseries)
+void Execute(SQLHDBC hDbc, const std::string& command) {
+ SQLHSTMT hStmt = SQL_NULL_HSTMT;
+ SQLRETURN ret;
+
+ try {
+ ret = SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt);
+ CheckOdbcError(ret, SQL_HANDLE_DBC, hDbc, "SQLAllocHandle(SQL_HANDLE_STMT)");
+
+ ret = SQLExecDirect(hStmt, (SQLCHAR*)command.c_str(), SQL_NTS);
+ if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
+ CheckOdbcError(ret, SQL_HANDLE_STMT, hStmt, "SQLExecDirect");
+ }
+
+ SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+ } catch (...) {
+ if (hStmt != SQL_NULL_HSTMT) {
+ SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+ }
+ throw;
+ }
+}
+
+int main() {
+ SQLHENV hEnv = SQL_NULL_HENV;
+ SQLHDBC hDbc = SQL_NULL_HDBC;
+ SQLRETURN ret;
+
+ try {
+ std::cout << "Start" << std::endl;
+
+ ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv);
+ CheckOdbcError(ret, SQL_HANDLE_ENV, hEnv, "SQLAllocHandle(SQL_HANDLE_ENV)");
+
+ ret = SQLSetEnvAttr(hEnv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0);
+ CheckOdbcError(ret, SQL_HANDLE_ENV, hEnv, "SQLSetEnvAttr");
+
+ ret = SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hDbc);
+ CheckOdbcError(ret, SQL_HANDLE_ENV, hEnv, "SQLAllocHandle(SQL_HANDLE_DBC)");
+
+ std::string dsn = "Apache IoTDB DSN";
+ std::string user = "root";
+ std::string password = "root";
+ std::string server = "127.0.0.1";
+
+ std::string connectionString = "DSN=" + dsn + ";Server=" + server + ";UID=" + user +
+ ";PWD=" + password + ";loglevel=4;istablemodel=0";
+ std::cout << "Using connection string: " << connectionString << std::endl;
+
+ SQLCHAR outConnStr[1024];
+ SQLSMALLINT outConnStrLen;
+
+ ret = SQLDriverConnect(hDbc, NULL, (SQLCHAR*)connectionString.c_str(), SQL_NTS, outConnStr,
+ sizeof(outConnStr), &outConnStrLen, SQL_DRIVER_COMPLETE);
+
+ if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
+ std::cerr << "Login failed" << std::endl;
+ CheckOdbcError(ret, SQL_HANDLE_DBC, hDbc, "SQLDriverConnect");
+ return 1;
+ }
+
+ SQLCHAR driverName[256];
+ SQLSMALLINT nameLength;
+ ret = SQLGetInfo(hDbc, SQL_DRIVER_NAME, driverName, sizeof(driverName), &nameLength);
+ CheckOdbcError(ret, SQL_HANDLE_DBC, hDbc, "SQLGetInfo");
+
+ std::cout << "Successfully opened connection. database name = " << driverName << std::endl;
+
+ const char* insertStatements[] = {
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, "
+ "double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735689600000, true, "
+ "100, 10000000000, 36.5, 128.689, 'Device running normally', 'DeviceA-Room1', "
+ "1735689600000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, "
+ "double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735689660000, false, "
+ "101, 10000000001, 36.6, 128.789, 'Device running normally', 'DeviceA-Room1', "
+ "1735689660000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, "
+ "double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735689720000, true, "
+ "102, 10000000002, 36.7, 128.889, 'Device running normally', 'DeviceA-Room1', "
+ "1735689720000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, "
+ "double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735689780000, false, "
+ "103, 10000000003, 36.8, 128.989, 'Device high temperature alert', 'DeviceA-Room1', "
+ "1735689780000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, "
+ "double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735689840000, true, "
+ "104, 10000000004, 36.9, 129.089, 'Device status restored', 'DeviceA-Room1', "
+ "1735689840000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, "
+ "double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735689900000, false, "
+ "105, 10000000005, 37.0, 129.189, 'Device running normally', 'DeviceB-Room2', "
+ "1735689900000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, "
+ "double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735689960000, true, "
+ "106, 10000000006, 37.1, 129.289, 'Device running normally', 'DeviceB-Room2', "
+ "1735689960000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, "
+ "double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735690020000, false, "
+ "107, 10000000007, 37.2, 129.389, 'Device low humidity alert', 'DeviceB-Room2', "
+ "1735690020000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, "
+ "double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735690080000, true, "
+ "108, 10000000008, 37.3, 129.489, 'Device status restored', 'DeviceB-Room2', "
+ "1735690080000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, "
+ "double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735690140000, false, "
+ "109, 10000000009, 37.4, 129.589, 'Device running normally', 'DeviceC-Room3', "
+ "1735690140000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, "
+ "double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735690200000, true, "
+ "110, 10000000010, 37.5, 129.689, 'Device running normally', 'DeviceC-Room3', "
+ "1735690200000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, "
+ "double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735690260000, false, "
+ "111, 10000000011, 37.6, 129.789, 'Device unstable voltage alert', 'DeviceC-Room3', "
+ "1735690260000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, "
+ "double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735690320000, true, "
+ "112, 10000000012, 37.7, 129.889, 'Device status restored', 'DeviceC-Room3', "
+ "1735690320000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, "
+ "double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735690380000, false, "
+ "113, 10000000013, 37.8, 129.989, 'Device running normally', 'DeviceD-Room4', "
+ "1735690380000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, "
+ "double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735690440000, true, "
+ "114, 10000000014, 37.9, 130.089, 'Device running normally', 'DeviceD-Room4', "
+ "1735690440000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, "
+ "double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735690500000, false, "
+ "115, 10000000015, 38.0, 130.189, 'Device running normally', 'DeviceD-Room4', "
+ "1735690500000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, "
+ "double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735690560000, true, "
+ "116, 10000000016, 38.1, 130.289, 'Device signal interrupted alert', 'DeviceD-Room4', "
+ "1735690560000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, "
+ "double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735690620000, false, "
+ "117, 10000000017, 38.2, 130.389, 'Device running normally', 'DeviceE-Room5', "
+ "1735690620000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, "
+ "double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735690680000, true, "
+ "118, 10000000018, 38.3, 130.489, 'Device running normally', 'DeviceE-Room5', "
+ "1735690680000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, "
+ "double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735690740000, false, "
+ "119, 10000000019, 38.4, 130.589, 'Device running normally', 'DeviceE-Room5', "
+ "1735690740000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, "
+ "double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735690790000, false, "
+ "119, 10000000019, 38.4, 130.589, 'Device running normally', 'DeviceE-Room5', "
+ "1735690740000, '2026-01-04')"};
+ for (const char* sql : insertStatements) {
+ Execute(hDbc, sql);
+ }
+ std::cout << "[DEBUG] Inserted 20 rows. Begin to query." << std::endl;
+ Query(hDbc);
+ std::cout << "Query ok" << std::endl;
+
+ SQLDisconnect(hDbc);
+ SQLFreeHandle(SQL_HANDLE_DBC, hDbc);
+ SQLFreeHandle(SQL_HANDLE_ENV, hEnv);
+
+ return 0;
+ } catch (...) {
+ if (hDbc != SQL_NULL_HDBC) {
+ SQLDisconnect(hDbc);
+ SQLFreeHandle(SQL_HANDLE_DBC, hDbc);
+ }
+ if (hEnv != SQL_NULL_HENV) {
+ SQLFreeHandle(SQL_HANDLE_ENV, hEnv);
+ }
+
+ std::cerr << "Unexpected error!" << std::endl;
+ return 1;
+ }
+}
diff --git a/iotdb-client/odbc/examples/python-example/README.md b/iotdb-client/odbc/examples/python-example/README.md
new file mode 100644
index 0000000000000..0a90b6fea96ec
--- /dev/null
+++ b/iotdb-client/odbc/examples/python-example/README.md
@@ -0,0 +1,84 @@
+
+# Python ODBC Example
+
+Python example programs that connect to the Apache IoTDB ODBC driver using pyodbc. For reference, see `examples/cpp-example` and `examples/BasicTest`.
+
+## Example Files
+
+| File | Description |
+|------|-------------|
+| **TableTest.py** | Table Model example. Uses table-style SQL such as `CREATE TABLE`, `INSERT INTO table`, suitable for IoTDB Table Model. |
+| **TreeTest.py** | Tree Model example. Uses timeseries-style SQL such as `CREATE DATABASE`, `CREATE TIMESERIES`, `INSERT INTO root.xxx.xxx`, suitable for IoTDB native Tree Model. |
+
+### Table Model vs Tree Model
+
+- **Table Model**: Connection string defaults to or explicitly sets `istablemodel=1`. Uses `use database`, `CREATE TABLE`, `INSERT INTO tablename`; data is stored in tables.
+- **Tree Model**: Connection string requires `istablemodel=0`. Uses full paths like `root.xxx.xxx`, creates timeseries via `CREATE TIMESERIES`; data is organized by device/measurement.
+
+## Install Dependencies
+
+```bash
+pip install -r requirements.txt
+```
+
+## Prerequisites
+
+1. **ODBC Driver**: Apache IoTDB ODBC driver installed and configured, with DSN name `Apache IoTDB DSN`
+2. **IoTDB Service**: IoTDB is running, default `127.0.0.1:6667`
+
+## Run
+
+### Table Model Example
+
+```bash
+python TableTest.py
+```
+
+### Tree Model Example
+
+```bash
+python TreeTest.py
+```
+
+## Function Descriptions
+
+- **execute**: Execute non-query SQL (e.g. `CREATE`, `INSERT`, `DELETE`)
+- **query**: Execute a SELECT query and display results in table format
+- **TableTest**: Demonstrates creation, insertion, and query of fulltable
+- **TreeTest**: Demonstrates creation, insertion, and query of root.full.fulldevice
+
+## Connection Parameters
+
+| Parameter | Description | Example |
+|-----------|-------------|---------|
+| DSN | Data Source Name | `Apache IoTDB DSN` |
+| Server | IoTDB server address | `127.0.0.1` |
+| UID | Username | `root` |
+| PWD | Password | `root` |
+| Database | Database name (Table Model) | `test` |
+| loglevel | Log level 0–4 | `4` |
+| istablemodel | 0=Tree Model, 1=Table Model | `0` or `1` |
+
+## FAQ
+
+1. **Connection failed**: Check whether IoTDB is running, the DSN is configured correctly, and the Server/port are correct.
+2. **Garbled characters**: If SQL statements contain non-ASCII characters, make sure the source file is saved with code page encoding. Alternatively, use English test data to avoid encoding issues.
diff --git a/iotdb-client/odbc/examples/python-example/TableTest.py b/iotdb-client/odbc/examples/python-example/TableTest.py
new file mode 100644
index 0000000000000..519f11a540365
--- /dev/null
+++ b/iotdb-client/odbc/examples/python-example/TableTest.py
@@ -0,0 +1,165 @@
+#!/usr/bin/env python3
+# 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.
+# -*- coding: utf-8 -*-
+"""
+Apache IoTDB ODBC Python example
+Use pyodbc to connect to the IoTDB ODBC driver and perform operations such as query and insert.
+For reference, see examples/cpp-example/test.cpp and examples/BasicTest/BasicTest/Program.cs
+"""
+
+import pyodbc
+
+
+def execute(conn: pyodbc.Connection, command: str) -> None:
+ """Execute a non-query SQL statement (e.g. USE, CREATE, INSERT, DELETE)"""
+ try:
+ with conn.cursor() as cursor:
+ cursor.execute(command)
+ # INSERT/UPDATE/DELETE require commit; session commands such as USE do not.
+ cmd_upper = command.strip().upper()
+ if cmd_upper.startswith(("INSERT", "UPDATE", "DELETE")):
+ conn.commit()
+ print(f"Execute command: {command}")
+ except pyodbc.Error as ex:
+ print(f"CommandText error: {ex}")
+
+
+def query(conn: pyodbc.Connection, sql: str) -> None:
+ """Execute a SELECT query and display results in table format"""
+ try:
+ with conn.cursor() as cursor:
+ cursor.execute(sql)
+ col_count = len(cursor.description)
+ print(f"fCount = {col_count}")
+
+ if col_count <= 0:
+ return
+
+ # Get column names (if the name contains '.', take the last segment, consistent with C++/C# samples).
+ columns = []
+ for i in range(col_count):
+ col_name = cursor.description[i][0] or f"Column{i}"
+ if "." in str(col_name):
+ col_name = str(col_name).split(".")[-1]
+ columns.append(str(col_name))
+
+ # Fetch data rows
+ rows = cursor.fetchall()
+
+ # Simple table output
+ col_widths = [max(len(str(col)), 4) for col in columns]
+ for i, row in enumerate(rows):
+ for j, val in enumerate(row):
+ if j < len(col_widths):
+ col_widths[j] = max(col_widths[j], len(str(val) if val is not None else "NULL"))
+
+ # Print header
+ header = " | ".join(str(c).ljust(col_widths[i]) for i, c in enumerate(columns))
+ print(header)
+ print("-" * len(header))
+
+ # Print data rows
+ for row in rows:
+ values = []
+ for i, val in enumerate(row):
+ if val is None:
+ cell = "NULL"
+ else:
+ cell = str(val)
+ values.append(cell.ljust(col_widths[i]) if i < len(col_widths) else cell)
+ print(" | ".join(values))
+
+ print()
+
+ except pyodbc.Error as ex:
+ print(f"Query error: {ex}")
+
+
+def main() -> None:
+ dsn = "Apache IoTDB DSN"
+ user = "root"
+ password = "root"
+ server = "127.0.0.1"
+ database = "test"
+ connection_string = (
+ f"DSN={dsn};Server={server};UID={user};PWD={password};"
+ f"Database={database};loglevel=4"
+ )
+
+ print("Start")
+
+ try:
+ conn = pyodbc.connect(connection_string)
+ except pyodbc.Error as ex:
+ print(f"Login failed: {ex}")
+ return
+
+ try:
+ driver_name = conn.getinfo(6) # SQL_DRIVER_NAME
+ print(f"Successfully opened connection. driver = {driver_name}")
+ except Exception:
+ print("Successfully opened connection.")
+
+ try:
+ execute(conn, "CREATE DATABASE IF NOT EXISTS test")
+ execute(conn, "use test")
+ print("use test Execute complete. Begin to setup fulltable.")
+
+ # Create the fulltable table and insert test data
+ execute(
+ conn,
+ "CREATE TABLE IF NOT EXISTS fullTable (time TIMESTAMP TIME, bool_col BOOLEAN FIELD, "
+ "int32_col INT32 FIELD, int64_col INT64 FIELD, float_col FLOAT FIELD, "
+ "double_col DOUBLE FIELD, text_col TEXT FIELD, string_col STRING FIELD, "
+ "blob_col BLOB FIELD, timestamp_col TIMESTAMP FIELD, date_col DATE FIELD) "
+ "WITH (TTL=315360000000)",
+ )
+ insert_statements = [
+ "INSERT INTO fulltable VALUES (1735689600000, true, 100, 10000000000, 36.5, 128.689, 'Device running normally', 'DeviceA-Room1', '0x506C616E7444617461', 1735689600000, '2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735689660000, false, 101, 10000000001, 36.6, 128.789, 'Device running normally', 'DeviceA-Room1', '0x506C616E7444617461', 1735689660000, '2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735689720000, true, 102, 10000000002, 36.7, 128.889, 'Device running normally', 'DeviceA-Room1', '0x506C616E7444617461', 1735689720000, '2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735689780000, false, 103, 10000000003, 36.8, 128.989, 'Device high temperature alert', 'DeviceA-Room1', '0x506C616E7444617462', 1735689780000, '2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735689840000, true, 104, 10000000004, 36.9, 129.089, 'Device status restored', 'DeviceA-Room1', '0x506C616E7444617461', 1735689840000, '2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735689900000, false, 105, 10000000005, 37.0, 129.189, 'Device running normally', 'DeviceB-Room2', '0x506C616E7444617463', 1735689900000, '2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735689960000, true, 106, 10000000006, 37.1, 129.289, 'Device running normally', 'DeviceB-Room2', '0x506C616E7444617463', 1735689960000, '2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735690020000, false, 107, 10000000007, 37.2, 129.389, 'Device low humidity alert', 'DeviceB-Room2', '0x506C616E7444617464', 1735690020000, '2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735690080000, true, 108, 10000000008, 37.3, 129.489, 'Device status restored', 'DeviceB-Room2', '0x506C616E7444617463', 1735690080000, '2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735690140000, false, 109, 10000000009, 37.4, 129.589, 'Device running normally', 'DeviceC-Room3', '0x506C616E7444617465', 1735690140000, '2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735690200000, true, 110, 10000000010, 37.5, 129.689, 'Device running normally', 'DeviceC-Room3', '0x506C616E7444617465', 1735690200000, '2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735690260000, false, 111, 10000000011, 37.6, 129.789, 'Device unstable voltage alert', 'DeviceC-Room3', '0x506C616E7444617466', 1735690260000, '2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735690320000, true, 112, 10000000012, 37.7, 129.889, 'Device status restored', 'DeviceC-Room3', '0x506C616E7444617465', 1735690320000, '2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735690380000, false, 113, 10000000013, 37.8, 129.989, 'Device running normally', 'DeviceD-Room4', '0x506C616E7444617467', 1735690380000, '2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735690440000, true, 114, 10000000014, 37.9, 130.089, 'Device running normally', 'DeviceD-Room4', '0x506C616E7444617467', 1735690440000, '2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735690500000, false, 115, 10000000015, 38.0, 130.189, 'Device running normally', 'DeviceD-Room4', '0x506C616E7444617467', 1735690500000, '2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735690560000, true, 116, 10000000016, 38.1, 130.289, 'Device signal interrupted alert', 'DeviceD-Room4', '0x506C616E7444617468', 1735690560000, '2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735690620000, false, 117, 10000000017, 38.2, 130.389, 'Device running normally', 'DeviceE-Room5', '0x506C616E7444617469', 1735690620000, '2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735690680000, true, 118, 10000000018, 38.3, 130.489, 'Device running normally', 'DeviceE-Room5', '0x506C616E7444617469', 1735690680000, '2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735690740000, false, 119, 10000000019, 38.4, 130.589, 'Device running normally', 'DeviceE-Room5', '0x506C616E7444617469', 1735690740000, '2026-01-04')",
+ "INSERT INTO fulltable VALUES (1735690790000, false, 119, 10000000019, 38.4, 130.589, 'Device running normally', 'DeviceE-Room5', '0x506C616E7444617469', 1735690740000, '2026-01-04')",
+ ]
+ for insert_sql in insert_statements:
+ execute(conn, insert_sql)
+ print("fulltable setup complete. Begin to query.")
+ query(conn, "select * from fulltable")
+ print("Query ok")
+ finally:
+ conn.close()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/iotdb-client/odbc/examples/python-example/TreeTest.py b/iotdb-client/odbc/examples/python-example/TreeTest.py
new file mode 100644
index 0000000000000..e1da4303ae7a0
--- /dev/null
+++ b/iotdb-client/odbc/examples/python-example/TreeTest.py
@@ -0,0 +1,147 @@
+#!/usr/bin/env python3
+# 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.
+# -*- coding: utf-8 -*-
+"""
+Apache IoTDB ODBC Python example - Tree Model
+Uses pyodbc to connect to the IoTDB ODBC driver with istablemodel=0 for tree model.
+Reference: examples/BasicTest/TreeTest/TreeTest.cs and examples/cpp-example/TreeTest.cpp.
+"""
+
+import pyodbc
+
+
+def execute(conn: pyodbc.Connection, command: str) -> None:
+ """Execute a non-query SQL statement (e.g. INSERT; tree model INSERT auto-creates timeseries)"""
+ try:
+ with conn.cursor() as cursor:
+ cursor.execute(command)
+ cmd_upper = command.strip().upper()
+ if cmd_upper.startswith(("INSERT", "UPDATE", "DELETE")):
+ conn.commit()
+ print(f"Execute command: {command}")
+ except pyodbc.Error as ex:
+ print(f"CommandText error: {ex}")
+
+
+def query(conn: pyodbc.Connection, sql: str) -> None:
+ """Execute a SELECT query and display results of root.full.fulldevice in table format"""
+ try:
+ with conn.cursor() as cursor:
+ cursor.execute(sql)
+ col_count = len(cursor.description)
+ print(f"fCount = {col_count}")
+
+ if col_count <= 0:
+ return
+
+ columns = []
+ for i in range(col_count):
+ col_name = cursor.description[i][0] or f"Column{i}"
+ if "." in str(col_name):
+ col_name = str(col_name).split(".")[-1]
+ columns.append(str(col_name))
+
+ rows = cursor.fetchall()
+
+ col_widths = [max(len(str(col)), 4) for col in columns]
+ for row in rows:
+ for j, val in enumerate(row):
+ if j < len(col_widths):
+ col_widths[j] = max(col_widths[j], len(str(val) if val is not None else "NULL"))
+
+ header = " | ".join(str(c).ljust(col_widths[i]) for i, c in enumerate(columns))
+ print(header)
+ print("-" * len(header))
+
+ for row in rows:
+ values = []
+ for i, val in enumerate(row):
+ if val is None:
+ cell = "NULL"
+ else:
+ cell = str(val)
+ values.append(cell.ljust(col_widths[i]) if i < len(col_widths) else cell)
+ print(" | ".join(values))
+
+ print()
+
+ except pyodbc.Error as ex:
+ print(f"Query error: {ex}")
+
+
+def main() -> None:
+ dsn = "Apache IoTDB DSN"
+ user = "root"
+ password = "root"
+ server = "127.0.0.1"
+ connection_string = (
+ f"DSN={dsn};Server={server};UID={user};PWD={password};"
+ f"loglevel=4;istablemodel=0"
+ )
+
+ print("Start")
+
+ try:
+ conn = pyodbc.connect(connection_string)
+ except pyodbc.Error as ex:
+ print(f"Login failed: {ex}")
+ return
+
+ try:
+ driver_name = conn.getinfo(6) # SQL_DRIVER_NAME
+ print(f"Successfully opened connection. driver = {driver_name}")
+ except Exception:
+ print("Successfully opened connection.")
+
+ try:
+ insert_statements = [
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735689600001, true, 100, 10000000000, 36.5, 128.689, 'Device running normally', 'DeviceA-Room1', 1735689600000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735689660000, false, 101, 10000000001, 36.6, 128.789, 'Device running normally', 'DeviceA-Room1', 1735689660000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735689720000, true, 102, 10000000002, 36.7, 128.889, 'Device running normally', 'DeviceA-Room1', 1735689720000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735689780000, false, 103, 10000000003, 36.8, 128.989, 'Device high temperature alert', 'DeviceA-Room1', 1735689780000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735689840000, true, 104, 10000000004, 36.9, 129.089, 'Device status restored', 'DeviceA-Room1', 1735689840000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735689900000, false, 105, 10000000005, 37.0, 129.189, 'Device running normally', 'DeviceB-Room2', 1735689900000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735689960000, true, 106, 10000000006, 37.1, 129.289, 'Device running normally', 'DeviceB-Room2', 1735689960000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735690020000, false, 107, 10000000007, 37.2, 129.389, 'Device low humidity alert', 'DeviceB-Room2', 1735690020000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735690080000, true, 108, 10000000008, 37.3, 129.489, 'Device status restored', 'DeviceB-Room2', 1735690080000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735690140000, false, 109, 10000000009, 37.4, 129.589, 'Device running normally', 'DeviceC-Room3', 1735690140000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735690200000, true, 110, 10000000010, 37.5, 129.689, 'Device running normally', 'DeviceC-Room3', 1735690200000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735690260000, false, 111, 10000000011, 37.6, 129.789, 'Device unstable voltage alert', 'DeviceC-Room3', 1735690260000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735690320000, true, 112, 10000000012, 37.7, 129.889, 'Device status restored', 'DeviceC-Room3', 1735690320000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735690380000, false, 113, 10000000013, 37.8, 129.989, 'Device running normally', 'DeviceD-Room4', 1735690380000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735690440000, true, 114, 10000000014, 37.9, 130.089, 'Device running normally', 'DeviceD-Room4', 1735690440000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735690500000, false, 115, 10000000015, 38.0, 130.189, 'Device running normally', 'DeviceD-Room4', 1735690500000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735690560000, true, 116, 10000000016, 38.1, 130.289, 'Device signal interrupted alert', 'DeviceD-Room4', 1735690560000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735690620000, false, 117, 10000000017, 38.2, 130.389, 'Device running normally', 'DeviceE-Room5', 1735690620000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735690680000, true, 118, 10000000018, 38.3, 130.489, 'Device running normally', 'DeviceE-Room5', 1735690680000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735690740000, false, 119, 10000000019, 38.4, 130.589, 'Device running normally', 'DeviceE-Room5', 1735690740000, '2026-01-04')",
+ "INSERT INTO root.full.fulldevice(timestamp, bool_col, int32_col, int64_col, float_col, double_col, text_col, string_col, timestamp_col, date_col) VALUES (1735690790000, false, 119, 10000000019, 38.4, 130.589, 'Device running normally', 'DeviceE-Room5', 1735690740000, '2026-01-04')",
+ ]
+ for insert_sql in insert_statements:
+ execute(conn, insert_sql)
+ print(f"[DEBUG] Inserted {len(insert_statements)} rows. Begin to query.")
+
+ query_sql = "SELECT * FROM root.full.fulldevice WHERE time >= 1735689600000 AND time <= 1735690790000"
+ query(conn, query_sql)
+ print("Query ok")
+ finally:
+ conn.close()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/iotdb-client/odbc/examples/python-example/requirements.txt b/iotdb-client/odbc/examples/python-example/requirements.txt
new file mode 100644
index 0000000000000..35a46b8500847
--- /dev/null
+++ b/iotdb-client/odbc/examples/python-example/requirements.txt
@@ -0,0 +1,17 @@
+# 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.
+pyodbc>=4.0.0
diff --git a/iotdb-client/odbc/pom.xml b/iotdb-client/odbc/pom.xml
new file mode 100644
index 0000000000000..7e3f42ac0fbd2
--- /dev/null
+++ b/iotdb-client/odbc/pom.xml
@@ -0,0 +1,158 @@
+
+
+
+ 4.0.0
+
+ org.apache.iotdb
+ iotdb-client
+ 2.0.11-SNAPSHOT
+
+ odbc
+ pom
+ IoTDB: Client: ODBC
+
+ ${project.build.directory}/build
+ Release
+ ${os.classifier}
+ ON
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+
+
+ configure-odbc
+ generate-resources
+
+ exec
+
+
+ cmake
+
+ -S
+ ${project.basedir}
+ -B
+ ${odbc.build.dir}
+ -DCMAKE_BUILD_TYPE=${odbc.build.type}
+ -DODBC_PACKAGE_VERSION=${project.version}
+ -DODBC_PACKAGE_CLASSIFIER=${odbc.package.classifier}
+ -DODBC_BUILD_TESTS=${odbc.build.tests}
+
+
+
+
+ build-odbc
+ compile
+
+ exec
+
+
+ cmake
+
+ --build
+ ${odbc.build.dir}
+ --config
+ ${odbc.build.type}
+
+
+
+
+ test-odbc
+ test
+
+ exec
+
+
+ ${skipTests}
+ ctest
+
+ --test-dir
+ ${odbc.build.dir}
+ -C
+ ${odbc.build.type}
+ --output-on-failure
+
+
+
+
+ package-odbc
+ package
+
+ exec
+
+
+ cpack
+ ${odbc.build.dir}
+
+ -C
+ ${odbc.build.type}
+
+
+
+
+
+
+
+
+
+
+ spotless-cpp
+
+ [17,)
+
+
+
+
+ com.diffplug.spotless
+ spotless-maven-plugin
+ 2.44.5
+
+
+
+ src/**/*.h
+ src/**/*.hpp
+ src/**/*.c
+ src/**/*.cc
+ src/**/*.cpp
+ test/**/*.h
+ test/**/*.hpp
+ test/**/*.c
+ test/**/*.cc
+ test/**/*.cpp
+ examples/**/*.c
+ examples/**/*.cc
+ examples/**/*.cpp
+
+
+ ${clang.format.version}
+
+
+
+
+
+
+
+
+
+
diff --git a/iotdb-client/odbc/scripts/windows/install.bat b/iotdb-client/odbc/scripts/windows/install.bat
new file mode 100644
index 0000000000000..abb62643cb596
--- /dev/null
+++ b/iotdb-client/odbc/scripts/windows/install.bat
@@ -0,0 +1,28 @@
+rem Licensed to the Apache Software Foundation (ASF) under one
+rem or more contributor license agreements. See the NOTICE file
+rem distributed with this work for additional information
+rem regarding copyright ownership. The ASF licenses this file
+rem to you under the Apache License, Version 2.0 (the
+rem "License"); you may not use this file except in compliance
+rem with the License. You may obtain a copy of the License at
+rem
+rem http://www.apache.org/licenses/LICENSE-2.0
+rem
+rem Unless required by applicable law or agreed to in writing,
+rem software distributed under the License is distributed on an
+rem "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+rem KIND, either express or implied. See the License for the
+rem specific language governing permissions and limitations
+rem under the License.
+@echo off
+setlocal
+if "%~1"=="" (
+ echo Usage: install.bat "C:\Program Files\Apache IoTDB ODBC Driver"
+ exit /b 1
+)
+if not exist "%~1" mkdir "%~1"
+if errorlevel 1 exit /b 1
+copy /Y "%~dp0bin\apache_iotdb_odbc.dll" "%~1\apache_iotdb_odbc.dll" >nul
+if errorlevel 1 exit /b 1
+"%~dp0bin\install_driver.exe" install "%~f1\apache_iotdb_odbc.dll"
+exit /b %errorlevel%
diff --git a/iotdb-client/odbc/scripts/windows/uninstall.bat b/iotdb-client/odbc/scripts/windows/uninstall.bat
new file mode 100644
index 0000000000000..720f06d60711d
--- /dev/null
+++ b/iotdb-client/odbc/scripts/windows/uninstall.bat
@@ -0,0 +1,19 @@
+rem Licensed to the Apache Software Foundation (ASF) under one
+rem or more contributor license agreements. See the NOTICE file
+rem distributed with this work for additional information
+rem regarding copyright ownership. The ASF licenses this file
+rem to you under the Apache License, Version 2.0 (the
+rem "License"); you may not use this file except in compliance
+rem with the License. You may obtain a copy of the License at
+rem
+rem http://www.apache.org/licenses/LICENSE-2.0
+rem
+rem Unless required by applicable law or agreed to in writing,
+rem software distributed under the License is distributed on an
+rem "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+rem KIND, either express or implied. See the License for the
+rem specific language governing permissions and limitations
+rem under the License.
+@echo off
+"%~dp0bin\install_driver.exe" uninstall
+exit /b %errorlevel%
diff --git a/iotdb-client/odbc/src/Catalog.cpp b/iotdb-client/odbc/src/Catalog.cpp
new file mode 100644
index 0000000000000..86cd60da99491
--- /dev/null
+++ b/iotdb-client/odbc/src/Catalog.cpp
@@ -0,0 +1,346 @@
+/*
+ * 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 "driver.h"
+#include
+#include