You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tsfile.apache.org by co...@apache.org on 2026/05/21 11:19:44 UTC
(tsfile) branch develop updated: support msvc 2017. (#817)
This is an automated email from the ASF dual-hosted git repository.
ColinLeeo pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/tsfile.git
The following commit(s) were added to refs/heads/develop by this push:
new 6ccc9d7e6 support msvc 2017. (#817)
6ccc9d7e6 is described below
commit 6ccc9d7e69e816cc7babb40a9b924fc6151a6c55
Author: Colin Lee <10...@users.noreply.github.com>
AuthorDate: Thu May 21 19:19:39 2026 +0800
support msvc 2017. (#817)
---
.github/workflows/unit-test-cpp-msvc.yml | 160 ++++++++++++++++++
.github/workflows/unit-test-python-msvc.yml | 144 +++++++++++++++++
cpp/CMakeLists.txt | 97 ++++++++---
cpp/README.md | 57 ++++++-
cpp/examples/CMakeLists.txt | 9 +-
cpp/patches/antlr4-4.9.3-msvc-compat.patch | 126 +++++++++++++++
cpp/pom.xml | 87 ++++++++++
cpp/src/CMakeLists.txt | 18 ++-
cpp/src/common/allocator/alloc_base.h | 4 +-
cpp/src/common/allocator/my_string.h | 1 +
cpp/src/common/constant/tsfile_constant.h | 9 +-
cpp/src/common/container/bit_map.h | 78 ++++++++-
cpp/src/common/container/blocking_queue.cc | 57 -------
cpp/src/common/container/blocking_queue.h | 44 -----
cpp/src/common/db_common.h | 6 +-
cpp/src/common/global.cc | 1 +
cpp/src/common/global.h | 4 +-
cpp/src/common/mutex/mutex.h | 38 ++---
cpp/src/common/tablet.cc | 2 +-
cpp/src/common/tablet.h | 47 ++++++
cpp/src/common/tsfile_common.h | 14 +-
cpp/src/cwrapper/tsfile_cwrapper.cc | 6 +-
cpp/src/encoding/dictionary_encoder.h | 2 +-
cpp/src/file/read_file.cc | 12 +-
cpp/src/file/write_file.cc | 10 +-
cpp/src/reader/result_set.h | 1 +
cpp/src/utils/db_utils.h | 13 +-
cpp/src/utils/storage_utils.h | 1 +
cpp/src/utils/util_define.h | 137 +++++++++++++++-
cpp/src/writer/tsfile_writer.cc | 4 +
cpp/src/writer/tsfile_writer.h | 1 +
cpp/src/writer/value_page_writer.h | 2 +-
cpp/test/CMakeLists.txt | 14 +-
cpp/test/common/allocator/byte_stream_test.cc | 3 +-
cpp/test/cwrapper/c_release_test.cc | 56 ++-----
cpp/test/cwrapper/cwrapper_metadata_test.cc | 4 +
cpp/test/cwrapper/cwrapper_test.cc | 4 +
cpp/third_party/CMakeLists.txt | 6 +
.../antlr4-cpp-runtime-4/runtime/src/Token.h | 10 +-
.../runtime/src/Vocabulary.cpp | 2 +
.../runtime/src/atn/LL1Analyzer.h | 2 +-
pom.xml | 53 ++++++
python/setup.py | 179 +++++++++++++++------
python/tsfile/__init__.py | 24 ++-
44 files changed, 1245 insertions(+), 304 deletions(-)
diff --git a/.github/workflows/unit-test-cpp-msvc.yml b/.github/workflows/unit-test-cpp-msvc.yml
new file mode 100644
index 000000000..e7c88d4a3
--- /dev/null
+++ b/.github/workflows/unit-test-cpp-msvc.yml
@@ -0,0 +1,160 @@
+# This workflow builds and tests the C++ implementation of TsFile on Windows
+# using the MSVC toolchain (Visual Studio generator), as a complement to
+# unit-test-cpp.yml which builds the Windows target with MinGW.
+
+name: Unit-Test-Cpp-MSVC
+
+on:
+ push:
+ branches:
+ - develop
+ - iotdb
+ - rc/*
+ paths-ignore:
+ - 'docs/**'
+ - 'java/**'
+ pull_request:
+ branches:
+ - develop
+ - dev/*
+ - iotdb
+ - rc/*
+ paths-ignore:
+ - 'docs/**'
+ - 'java/**'
+ # Enable manually starting builds.
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+env:
+ MAVEN_OPTS: -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.http.retryHandler.class=standard -Dmaven.wagon.http.retryHandler.count=3
+ DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
+
+jobs:
+ unit-test-msvc:
+ strategy:
+ fail-fast: false
+ # To exercise the VS 2017 compatibility this branch targets, the non-ASan
+ # jobs build with the v141 toolset (the VS 2017 compiler, cl.exe 19.16)
+ # via a VS 2022 generator. AddressSanitizer is not supported by v141, so
+ # the ASan jobs use the default v143 toolset (VS 2022), which fully
+ # supports /fsanitize=address.
+ matrix:
+ include:
+ - build_type: Release
+ enable_asan: NoAsan
+ toolset: v141
+ - build_type: Debug
+ enable_asan: NoAsan
+ toolset: v141
+ - build_type: Release
+ enable_asan: Asan
+ toolset: default
+ - build_type: Debug
+ enable_asan: Asan
+ toolset: default
+ # Pinned to windows-2022 deliberately: it ships Visual Studio 2022, which
+ # the "Visual Studio 17 2022" CMake generator requires. The windows-latest
+ # image has since moved to a newer Visual Studio that the bundled CMake
+ # (3.30.x) does not yet recognise as a generator. Bump this (and
+ # msvc.generator) together when migrating. The v141 (VS 2017) toolset is
+ # no longer bundled with the image and is installed by a step below.
+ runs-on: windows-2022
+
+ steps:
+
+ - name: Checkout repository
+ uses: actions/checkout@v6
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v5
+ with:
+ distribution: corretto
+ java-version: 17
+
+ # Setup caching of the artifacts in the .m2 directory, so they don't have
+ # to all be downloaded again for every build.
+ - name: Cache Maven packages
+ uses: actions/cache@v5
+ with:
+ path: ~/.m2
+ key: ${{ runner.os }}-m2-msvc-${{ hashFiles('**/pom.xml') }}
+ restore-keys: ${{ runner.os }}-m2-
+
+ # Put the MSVC toolchain (and, importantly, the AddressSanitizer runtime
+ # clang_rt.asan_dynamic-*.dll) on PATH so the test executable can run
+ # during ctest discovery and execution.
+ - name: Set up MSVC developer environment
+ uses: ilammy/msvc-dev-cmd@v1
+ with:
+ arch: x64
+
+ # The windows-2022 image no longer ships the v141 (VS 2017) toolset, so
+ # install it on demand for the v141 jobs. With the component present the
+ # VS 2022 generator can build with the VS 2017 compiler via
+ # -DCMAKE_GENERATOR_TOOLSET=v141.
+ - name: Install MSVC v141 (VS 2017) toolset
+ if: matrix.toolset == 'v141'
+ shell: pwsh
+ run: |
+ $ErrorActionPreference = "Stop"
+ $installerDir = "C:\Program Files (x86)\Microsoft Visual Studio\Installer"
+ $installPath = & (Join-Path $installerDir "vswhere.exe") -latest -property installationPath
+ Write-Host "Visual Studio install path: $installPath"
+
+ # The v141 (VS 2017) toolset always installs as a 14.16.* directory.
+ function Test-V141Toolset {
+ [bool](Get-ChildItem -Path (Join-Path $installPath "VC\Tools\MSVC") `
+ -Directory -Filter "14.16.*" -ErrorAction SilentlyContinue)
+ }
+
+ if (Test-V141Toolset) {
+ Write-Host "v141 toolset already present."
+ } else {
+ # vs_installer.exe 'modify' accepts only a limited switch set;
+ # bootstrapper-only switches such as --wait / --nocache make it
+ # fail with exit code 87 (ERROR_INVALID_PARAMETER).
+ $argString = "modify --installPath `"$installPath`" " +
+ "--add Microsoft.VisualStudio.Component.VC.v141.x86.x64 " +
+ "--quiet --norestart"
+ $proc = Start-Process -FilePath (Join-Path $installerDir "vs_installer.exe") `
+ -ArgumentList $argString -Wait -PassThru
+ Write-Host "vs_installer exit code: $($proc.ExitCode)"
+ # vs_installer may delegate to a background process; wait for it.
+ Get-Process -Name "vs_installer", "setup" -ErrorAction SilentlyContinue |
+ Wait-Process -Timeout 900 -ErrorAction SilentlyContinue
+ if (-not (Test-V141Toolset)) {
+ throw "v141 toolset not found after install (vs_installer exit code $($proc.ExitCode))"
+ }
+ Write-Host "v141 toolset installed."
+ }
+
+ # Run the maven build, selecting the MSVC toolchain via -Dcpp.toolchain=msvc.
+ # spotless (clang-format) is already covered by unit-test-cpp.yml, so it is
+ # skipped here to keep this workflow focused on the MSVC build.
+ #
+ # For the v141 jobs, -Dmsvc.toolset=v141 pins the VS 2017 compiler; the
+ # ASan jobs pass no toolset and so use the generator's default (v143).
+ - name: Build and test with Maven (MSVC)
+ shell: bash
+ run: |
+ if [ "${{ matrix.enable_asan }}" = "Asan" ]; then
+ ASAN_VALUE="ON"
+ else
+ ASAN_VALUE="OFF"
+ fi
+ if [ "${{ matrix.toolset }}" = "default" ]; then
+ TOOLSET_ARG=""
+ else
+ TOOLSET_ARG="-Dmsvc.toolset=${{ matrix.toolset }}"
+ fi
+ ./mvnw.cmd -P with-cpp \
+ -Dcpp.toolchain=msvc \
+ $TOOLSET_ARG \
+ -Denable.asan=$ASAN_VALUE \
+ -Dbuild.type=${{ matrix.build_type }} \
+ -Dspotless.skip=true \
+ clean verify
diff --git a/.github/workflows/unit-test-python-msvc.yml b/.github/workflows/unit-test-python-msvc.yml
new file mode 100644
index 000000000..d86756c31
--- /dev/null
+++ b/.github/workflows/unit-test-python-msvc.yml
@@ -0,0 +1,144 @@
+# This workflow builds and tests the Python implementation of TsFile on
+# Windows using the MSVC toolchain, as a complement to unit-test-python.yml
+# which builds the Windows target with MinGW.
+#
+# The C++ shared library (libtsfile) is built with the v141 toolset (the
+# Visual Studio 2017 compiler) to match the VS 2017 support this branch
+# targets. The Cython extension links to it through the plain C wrapper ABI,
+# so it builds cleanly with whichever MSVC toolset setuptools selects.
+
+name: Unit-Test-Py-MSVC
+
+on:
+ push:
+ branches:
+ - develop
+ - iotdb
+ - rc/*
+ paths-ignore:
+ - 'docs/**'
+ - 'java/**'
+ pull_request:
+ branches:
+ - develop
+ - dev/*
+ - iotdb
+ - rc/*
+ paths-ignore:
+ - 'docs/**'
+ - 'java/**'
+ # Enable manually starting builds.
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+env:
+ MAVEN_OPTS: -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.http.retryHandler.class=standard -Dmaven.wagon.http.retryHandler.count=3
+ DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
+
+jobs:
+ unit-test-py-msvc:
+ strategy:
+ fail-fast: false
+ matrix:
+ python-version: ["3.9", "3.11", "3.14"]
+ # Pinned to windows-2022 deliberately: it ships Visual Studio 2022, which
+ # the "Visual Studio 17 2022" CMake generator requires. The v141 (VS 2017)
+ # toolset is installed by a step below. See unit-test-cpp-msvc.yml for the
+ # full rationale.
+ runs-on: windows-2022
+
+ steps:
+
+ - name: Checkout repository
+ uses: actions/checkout@v6
+
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v6
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v5
+ with:
+ distribution: corretto
+ java-version: 17
+
+ # Setup caching of the artifacts in the .m2 directory, so they don't have
+ # to all be downloaded again for every build.
+ - name: Cache Maven packages
+ uses: actions/cache@v5
+ with:
+ path: ~/.m2
+ key: ${{ runner.os }}-m2-msvc-${{ hashFiles('**/pom.xml') }}
+ restore-keys: ${{ runner.os }}-m2-
+
+ # Make the MSVC toolchain available so setup.py can compile the Cython
+ # extension with cl.exe.
+ - name: Set up MSVC developer environment
+ uses: ilammy/msvc-dev-cmd@v1
+ with:
+ arch: x64
+
+ # The windows-2022 image no longer ships the v141 (VS 2017) toolset that
+ # this build pins via -Dmsvc.toolset=v141, so install the component on
+ # demand. With it present the VS 2022 generator can build libtsfile with
+ # the VS 2017 compiler.
+ - name: Install MSVC v141 (VS 2017) toolset
+ shell: pwsh
+ run: |
+ $ErrorActionPreference = "Stop"
+ $installerDir = "C:\Program Files (x86)\Microsoft Visual Studio\Installer"
+ $installPath = & (Join-Path $installerDir "vswhere.exe") -latest -property installationPath
+ Write-Host "Visual Studio install path: $installPath"
+
+ # The v141 (VS 2017) toolset always installs as a 14.16.* directory.
+ function Test-V141Toolset {
+ [bool](Get-ChildItem -Path (Join-Path $installPath "VC\Tools\MSVC") `
+ -Directory -Filter "14.16.*" -ErrorAction SilentlyContinue)
+ }
+
+ if (Test-V141Toolset) {
+ Write-Host "v141 toolset already present."
+ } else {
+ # vs_installer.exe 'modify' accepts only a limited switch set;
+ # bootstrapper-only switches such as --wait / --nocache make it
+ # fail with exit code 87 (ERROR_INVALID_PARAMETER).
+ $argString = "modify --installPath `"$installPath`" " +
+ "--add Microsoft.VisualStudio.Component.VC.v141.x86.x64 " +
+ "--quiet --norestart"
+ $proc = Start-Process -FilePath (Join-Path $installerDir "vs_installer.exe") `
+ -ArgumentList $argString -Wait -PassThru
+ Write-Host "vs_installer exit code: $($proc.ExitCode)"
+ # vs_installer may delegate to a background process; wait for it.
+ Get-Process -Name "vs_installer", "setup" -ErrorAction SilentlyContinue |
+ Wait-Process -Timeout 900 -ErrorAction SilentlyContinue
+ if (-not (Test-V141Toolset)) {
+ throw "v141 toolset not found after install (vs_installer exit code $($proc.ExitCode))"
+ }
+ Write-Host "v141 toolset installed."
+ }
+
+ # Build C++ (MSVC, v141 toolset) and the Python extension, then run the
+ # Python test-suite. -P with-python builds the cpp module first.
+ # spotless (black/clang-format) is already covered by the non-MSVC
+ # workflows, so it is skipped here to keep this workflow focused.
+ - name: Build and test with Maven (MSVC)
+ shell: bash
+ run: |
+ ./mvnw.cmd -P with-python \
+ -Dcpp.toolchain=msvc \
+ -Dmsvc.toolset=v141 \
+ -Denable.asan=OFF \
+ -Dbuild.type=Release \
+ -Dspotless.skip=true \
+ clean verify
+
+ - name: Upload whl Artifact
+ uses: actions/upload-artifact@v7
+ with:
+ name: tsfile-msvc-${{ runner.os }}-py${{ matrix.python-version }}-whl
+ path: python/dist/tsfile-*.whl
+ retention-days: 1
diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index 3f1d8bdd3..ba2c0c921 100755
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -26,16 +26,38 @@ else()
message(STATUS "Not using ToolChain")
endif ()
-cmake_policy(SET CMP0079 NEW)
+if (POLICY CMP0079)
+ cmake_policy(SET CMP0079 NEW)
+endif ()
set(TsFile_CPP_VERSION 2.2.1.dev)
-set(CMAKE_CXX_FLAGS "$ENV{CXXFLAGS} -Wall")
+
+if (MSVC)
+ # MSVC does not provide a /std:c++11 flag; C++11 is its implicit baseline.
+ # The lowest explicitly settable standard is /std:c++14. Without this flag,
+ # the default varies by VS version (VS2017+ defaults to C++14 mode with some
+ # C++17 extensions), so we pin it explicitly for reproducibility.
+ set(CMAKE_CXX_FLAGS "$ENV{CXXFLAGS} /W3 /utf-8 /EHsc /bigobj /Zc:__cplusplus /std:c++14")
+ add_definitions(-DNOMINMAX -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_WARNINGS
+ -D_SCL_SECURE_NO_WARNINGS -D_WINSOCK_DEPRECATED_NO_WARNINGS)
+ # Export all symbols of the tsfile shared library automatically so that
+ # consumers do not need __declspec(dllexport) annotations.
+ set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
+else ()
+ set(CMAKE_CXX_FLAGS "$ENV{CXXFLAGS} -Wall")
+endif ()
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wunused -Wuninitialized -D__STDC_FORMAT_MACROS")
endif ()
message("cmake using: USE_CPP11=${USE_CPP11}")
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
+# MSVC has no /std:c++11; CMake maps this to the closest supported standard
+# (C++14 default on MSVC), which compiles the C++11 codebase fine.
+set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_CXX_STANDARD_REQUIRED OFF)
+if (NOT MSVC)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
+endif ()
if (DEFINED ENV{CXX})
set(CMAKE_CXX_COMPILER $ENV{CXX})
@@ -83,25 +105,52 @@ else ()
endif ()
message("CMAKE BUILD TYPE " ${CMAKE_BUILD_TYPE})
-if (CMAKE_BUILD_TYPE STREQUAL "Debug")
- set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g")
-elseif (CMAKE_BUILD_TYPE STREQUAL "Release")
- set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
-elseif (CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
- set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -O2 -g")
-elseif (CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
- set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} -ffunction-sections -fdata-sections -Os")
- set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections")
+# MSVC provides sensible per-configuration optimization flags by default; the
+# GCC-style flags below would be rejected by cl.exe, so skip them on MSVC.
+if (NOT MSVC)
+ if (CMAKE_BUILD_TYPE STREQUAL "Debug")
+ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g")
+ elseif (CMAKE_BUILD_TYPE STREQUAL "Release")
+ set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
+ elseif (CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
+ set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -O2 -g")
+ elseif (CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
+ set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} -ffunction-sections -fdata-sections -Os")
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections")
+ endif ()
endif ()
message("CMAKE DEBUG: CMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}")
# disable asan by default.
option(ENABLE_ASAN "Enable Address Sanitizer" OFF)
-if (NOT WIN32)
- if (ENABLE_ASAN)
- message("Address Sanitizer is enabled.")
-
+if (ENABLE_ASAN)
+ message("Address Sanitizer is enabled.")
+ if (MSVC)
+ # MSVC ships AddressSanitizer; it requires Visual Studio 2019 16.9 or
+ # newer (MSVC_VERSION >= 1928). Only the address sanitizer is available
+ # (there is no UndefinedBehaviorSanitizer for MSVC).
+ if (MSVC_VERSION LESS 1928)
+ message(FATAL_ERROR
+ "ENABLE_ASAN requires MSVC 19.28+ (Visual Studio 2019 16.9); "
+ "detected MSVC_VERSION=${MSVC_VERSION}.")
+ endif ()
+ # /fsanitize=address is incompatible with the /RTC* runtime checks that
+ # CMake injects into Debug builds, and with incremental linking. Strip
+ # /RTC* from the per-config flags and force non-incremental linking.
+ #
+ # ASan also needs debug info: /Zi (compile) + /DEBUG (link). Without it
+ # MSVC emits warning C5072 ("ASAN enabled without debug information
+ # emission"), which the bundled googletest build promotes to an error
+ # via /WX in Release builds, and ASan reports lose symbol/line info.
+ add_compile_options(/fsanitize=address /Zi)
+ foreach (flagsVar
+ CMAKE_C_FLAGS_DEBUG CMAKE_CXX_FLAGS_DEBUG
+ CMAKE_C_FLAGS_RELWITHDEBINFO CMAKE_CXX_FLAGS_RELWITHDEBINFO)
+ string(REGEX REPLACE "/RTC[1csu]+" "" ${flagsVar} "${${flagsVar}}")
+ endforeach ()
+ add_link_options(/INCREMENTAL:NO /DEBUG)
+ elseif (NOT WIN32)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,undefined -fno-omit-frame-pointer")
if (NOT APPLE)
@@ -111,8 +160,12 @@ if (NOT WIN32)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address,undefined")
endif ()
else ()
- message("Address Sanitizer is disabled.")
+ message(WARNING
+ "ENABLE_ASAN on Windows is only supported with the MSVC toolchain; "
+ "ignoring it for the current generator.")
endif ()
+else ()
+ message("Address Sanitizer is disabled.")
endif ()
option(BUILD_TEST "Build tests" ON)
@@ -178,7 +231,15 @@ set(LIBRARY_INCLUDE_DIR ${PROJECT_BINARY_DIR}/include CACHE STRING "TsFile inclu
set(THIRD_PARTY_INCLUDE ${PROJECT_BINARY_DIR}/third_party)
set(SAVED_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
-set(CMAKE_CXX_FLAGS "$ENV{CXXFLAGS} -Wall -std=c++11")
+if (MSVC)
+ # MSVC does not provide a /std:c++11 flag; C++11 is its implicit baseline.
+ # The lowest explicitly settable standard is /std:c++14. Without this flag,
+ # the default varies by VS version (VS2017+ defaults to C++14 mode with some
+ # C++17 extensions), so we pin it explicitly for reproducibility.
+ set(CMAKE_CXX_FLAGS "$ENV{CXXFLAGS} /W3 /utf-8 /EHsc /bigobj /Zc:__cplusplus /std:c++14")
+else ()
+ set(CMAKE_CXX_FLAGS "$ENV{CXXFLAGS} -Wall -std=c++11")
+endif ()
add_subdirectory(third_party)
set(CMAKE_CXX_FLAGS "${SAVED_CXX_FLAGS}")
diff --git a/cpp/README.md b/cpp/README.md
index 9f3ee7ac0..639aa38f7 100644
--- a/cpp/README.md
+++ b/cpp/README.md
@@ -36,9 +36,9 @@ The source code can be found in the `./src` directory. C/C++ examples are locate
## How to make contributions
-We use `clang-format` to ensure that our C++ code adheres to a consistent set of rules defined in `./clang-format`. This is similar to the Google style.
+We use `clang-format` to ensure that our C++ code adheres to a consistent set of rules defined in `.clang-format`. This is similar to the Google style.
-`mvn spotless` uses `clang-format v17.0.6` for C++ code formatting. Please make sure the `clang-format` in your `PATH` matches this version before submitting code.
+`mvn spotless:apply` uses `clang-format v17.0.6` for C++ code formatting. Please make sure the `clang-format` in your `PATH` matches this version before submitting code.
How to install `clang-format v17.0.6`:
@@ -70,31 +70,72 @@ mvn spotless:apply -P with-cpp
If you need to skip code formatting temporarily, you can add `-Dspotless.skip=true`, for example:
```bash
-mvn package -P with-cpp clean verify -Dspotless.skip=true
+mvn clean verify -P with-cpp -Dspotless.skip=true
```
+### Platform Support
+
+TsFile C++ now supports:
+- **Linux**: GCC/Clang
+- **macOS**: Clang
+- **Windows**: MSVC 2017+ and MinGW
+
+All code must compile without errors on all supported platforms before submission.
+
We welcome any bug reports. You can open an issue with a title starting with [CPP] to describe the bug, like: https://github.com/apache/tsfile/issues/94
## Build
### Requirements
+TsFile C++ supports three toolchains:
+
+**Linux (GCC/Clang):**
```bash
sudo apt-get update
sudo apt-get install -y cmake make g++ clang-format libuuid-dev
```
-To build tsfile, you can run: `bash build.sh`. If you have Maven tools, you can run: `mvn package -P with-cpp clean verify`. Then, you can find the shared object at `./build`.
-
-Before you submit your code to GitHub, please ensure that the `mvn` compilation is correct.
+**Windows (MSVC):**
+- Visual Studio 2017 or later
+- CMake 3.11+
+**Windows (MinGW):**
If you compile using MinGW on windows and encounter an error, you can try replacing MinGW with the following version that we have tried without problems:
-
* GCC 14.2.0 (with **POSIX** threads) + LLVM/Clang/LLD/LLDB 18.1.8 + MinGW-w64 12.0.0 UCRT - release 1
* GCC 12.2.0 + LLVM/Clang/LLD/LLDB 16.0.0 + MinGW-w64 10.0.0 (UCRT) - release 5
* GCC 12.2.0 + LLVM/Clang/LLD/LLDB 16.0.0 + MinGW-w64 10.0.0 (MSVCRT) - release 5
* GCC 11.2.0 + MinGW-w64 10.0.0 (MSVCRT) - release 1
+### Build Instructions
+
+To build tsfile, use Maven which automatically detects and uses the appropriate toolchain:
+
+```bash
+mvn clean verify -P with-cpp
+```
+
+**Toolchain Selection:**
+
+Maven will automatically select the compiler based on your platform:
+- **Linux**: GCC/Clang
+- **macOS**: Clang
+- **Windows**: MinGW (default) or MSVC
+
+To explicitly specify a toolchain on Windows:
+
+```bash
+# Use MinGW (default on Windows)
+mvn clean verify -P with-cpp -Dcpp.toolchain=mingw
+
+# Use MSVC
+mvn clean verify -P with-cpp -Dcpp.toolchain=msvc
+```
+
+Then you can find the shared library at `./cpp/target/build/lib`.
+
+Before you submit your code to GitHub, please ensure that the compilation is correct.
+
### configure the cross-compilation toolchain
Modify the Toolchain File `cmake/ToolChain.cmake`, define the following variables:
@@ -139,4 +180,4 @@ By default, parallel write is enabled when the machine has more than one CPU cor
## Use TsFile
-You can find examples on how to read and write data in `demo_read.cpp` and `demo_write.cpp` located under `./examples/cpp_examples`. There are also examples under `./examples/c_examples`on how to use a C-style API to read and write data in a C environment. You can run `bash build.sh` under `./examples` to generate an executable output under `./examples/build`.
+You can find examples on how to read and write data in `demo_read.cpp` and `demo_write.cpp` located under `./examples/cpp_examples`. There are also examples under `./examples/c_examples` on how to use a C-style API to read and write data in a C environment. The examples will be built automatically when you run the main build command.
diff --git a/cpp/examples/CMakeLists.txt b/cpp/examples/CMakeLists.txt
index ebe6c66c8..1d5072b08 100644
--- a/cpp/examples/CMakeLists.txt
+++ b/cpp/examples/CMakeLists.txt
@@ -20,7 +20,9 @@ cmake_minimum_required(VERSION 3.10)
project(examples)
message("Running in exampes directory")
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
+if (NOT MSVC)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
+endif ()
# TsFile include dir
set(SDK_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/../src/)
@@ -37,8 +39,9 @@ include_directories(${PROJECT_SOURCE_DIR}/../third_party/antlr4-cpp-runtime-4/ru
set(BUILD_TYPE "Release")
include_directories(${SDK_INCLUDE_DIR})
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g")
-set(CMAKE_CXX_FLAGS_DEBUG" ${CMAKE_CXX_FLAGS} -O0 -g")
+if (NOT MSVC)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g")
+endif ()
add_subdirectory(cpp_examples)
add_subdirectory(c_examples)
diff --git a/cpp/patches/antlr4-4.9.3-msvc-compat.patch b/cpp/patches/antlr4-4.9.3-msvc-compat.patch
new file mode 100644
index 000000000..f888b8391
--- /dev/null
+++ b/cpp/patches/antlr4-4.9.3-msvc-compat.patch
@@ -0,0 +1,126 @@
+#
+# 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.
+#
+
+# antlr4-4.9.3-msvc-compat.patch
+#
+# Purpose : Make the bundled ANTLR4 C++ runtime (v4.9.3) compile under MSVC
+# (Visual Studio 2017 / v141) while keeping the project on C++11.
+# Scope : third_party/antlr4-cpp-runtime-4 only. No version change, no
+# generated-parser change, no impact on the Java module (also 4.9.3)
+# or on embedded/C++11 builds.
+# Apply : from the cpp/ directory: git apply patches/antlr4-4.9.3-msvc-compat.patch
+# or: patch -p1 < patches/antlr4-4.9.3-msvc-compat.patch
+#
+# --- Why this is needed -------------------------------------------------------
+#
+# 1) Token.h / LL1Analyzer.h (overload-resolution ambiguity, error C2668)
+# These headers define EPSILON/EOF/HIT_PRED/etc. as either
+# `static constexpr size_t` (when __cplusplus >= 201703L) or as members of an
+# unnamed `enum : size_t` (otherwise). IntervalSet has both contains(size_t)
+# and contains(ssize_t) overloads. Per [conv.prom], converting an unnamed
+# enum with a fixed underlying type to that underlying type is an integral
+# PROMOTION (a strictly better match than a conversion to ssize_t). GCC
+# implements this correctly, so the enum branch compiles there. MSVC 2017
+# does NOT -- it ranks both overloads equally and reports an ambiguous call.
+# MSVC also reports __cplusplus as 199711L by default, so it always took the
+# enum branch. `static constexpr size_t` is fully usable in MSVC 2017's
+# C++11 mode, so we let MSVC take the constexpr branch: the constants become
+# plain size_t values and the ambiguity disappears with no call-site changes.
+# This is ODR-safe: the constants are currently enumerators (which cannot be
+# ODR-used), proving the runtime never takes their address.
+# NOTE: only the headers whose constants reach IntervalSet::contains/remove
+# are patched. The std::string_view guards in ANTLRInputStream.{h,cpp} use
+# the same `#if` and are deliberately left untouched (string_view is C++17).
+#
+# 2) Vocabulary.cpp (error C2039/C2660: isupper)
+# Vocabulary.cpp calls the two-argument std::isupper(charT, const locale&)
+# from <locale> but never includes <locale>; it relied on a transitive
+# include that libstdc++ happens to provide and MSVC's STL does not.
+# Fix: add the missing #include <locale>.
+#
+# ------------------------------------------------------------------------------
+--- a/third_party/antlr4-cpp-runtime-4/runtime/src/Token.h
++++ b/third_party/antlr4-cpp-runtime-4/runtime/src/Token.h
+@@ -14,7 +14,7 @@
+ /// we obtained this token.
+ class ANTLR4CPP_PUBLIC Token {
+ public:
+-#if __cplusplus >= 201703L
++#if __cplusplus >= 201703L || defined(_MSC_VER)
+ static constexpr size_t INVALID_TYPE = 0;
+ #else
+ enum : size_t {
+@@ -24,7 +24,7 @@
+
+ /// During lookahead operations, this "token" signifies we hit rule end ATN state
+ /// and did not follow it despite needing to.
+-#if __cplusplus >= 201703L
++#if __cplusplus >= 201703L || defined(_MSC_VER)
+ static constexpr size_t EPSILON = std::numeric_limits<size_t>::max() - 1;
+ static constexpr size_t MIN_USER_TOKEN_TYPE = 1;
+ static constexpr size_t EOF = IntStream::EOF;
+@@ -41,7 +41,7 @@
+ /// All tokens go to the parser (unless skip() is called in that rule)
+ /// on a particular "channel". The parser tunes to a particular channel
+ /// so that whitespace etc... can go to the parser on a "hidden" channel.
+-#if __cplusplus >= 201703L
++#if __cplusplus >= 201703L || defined(_MSC_VER)
+ static constexpr size_t DEFAULT_CHANNEL = 0;
+ #else
+ enum : size_t {
+@@ -51,7 +51,7 @@
+
+ /// Anything on different channel than DEFAULT_CHANNEL is not parsed
+ /// by parser.
+-#if __cplusplus >= 201703L
++#if __cplusplus >= 201703L || defined(_MSC_VER)
+ static constexpr size_t HIDDEN_CHANNEL = 1;
+ #else
+ enum : size_t {
+@@ -70,7 +70,7 @@
+ *
+ * @see Token#getChannel()
+ */
+-#if __cplusplus >= 201703L
++#if __cplusplus >= 201703L || defined(_MSC_VER)
+ static constexpr size_t MIN_USER_CHANNEL_VALUE = 2;
+ #else
+ enum : size_t {
+--- a/third_party/antlr4-cpp-runtime-4/runtime/src/atn/LL1Analyzer.h
++++ b/third_party/antlr4-cpp-runtime-4/runtime/src/atn/LL1Analyzer.h
+@@ -17,7 +17,7 @@
+ public:
+ /// Special value added to the lookahead sets to indicate that we hit
+ /// a predicate during analysis if {@code seeThruPreds==false}.
+-#if __cplusplus >= 201703L
++#if __cplusplus >= 201703L || defined(_MSC_VER)
+ static constexpr size_t HIT_PRED = Token::INVALID_TYPE;
+ #else
+ enum : size_t {
+--- a/third_party/antlr4-cpp-runtime-4/runtime/src/Vocabulary.cpp
++++ b/third_party/antlr4-cpp-runtime-4/runtime/src/Vocabulary.cpp
+@@ -3,6 +3,8 @@
+ * can be found in the LICENSE.txt file in the project root.
+ */
+
++#include <locale>
++
+ #include "Token.h"
+
+ #include "Vocabulary.h"
diff --git a/cpp/pom.xml b/cpp/pom.xml
index 13af0eefc..7061f2696 100644
--- a/cpp/pom.xml
+++ b/cpp/pom.xml
@@ -88,6 +88,16 @@
<option>-DENABLE_LZOKAY=${enable.lzokay}</option>
<option>-DENABLE_ZLIB=${enable.zlib}</option>
<option>-DENABLE_SIMDE=${enable.simde}</option>
+ <!--
+ The Visual Studio generator additionally
+ needs -DCMAKE_GENERATOR_PLATFORM (and
+ optionally -DCMAKE_GENERATOR_TOOLSET). Those
+ options are appended by the cpp-msvc /
+ cpp-msvc-toolset profiles below rather than
+ listed here: an empty <option/> element is
+ deserialized as null, which makes the cmake
+ plugin's generate goal throw an NPE.
+ -->
</options>
<sourcePath/>
<targetPath/>
@@ -100,6 +110,14 @@
<goals>
<goal>compile</goal>
</goals>
+ <configuration>
+ <!--
+ Build configuration for multi-config generators
+ (Visual Studio). Ignored by single-config
+ generators such as the Makefile generators.
+ -->
+ <config>${build.type}</config>
+ </configuration>
</execution>
<!-- Execute the tests -->
<execution>
@@ -110,6 +128,8 @@
</goals>
<configuration>
<buildDirectory>${project.build.directory}/build/test</buildDirectory>
+ <!-- Test configuration for multi-config generators (Visual Studio). -->
+ <config>${build.type}</config>
</configuration>
</execution>
</executions>
@@ -203,6 +223,73 @@
<coverage.enabled>ON</coverage.enabled>
</properties>
</profile>
+ <!--
+ MSVC (Visual Studio generator) needs the target platform passed at
+ generate time. Activated by -Dcpp.toolchain=msvc, matching the
+ root pom cpp-msvc profile that switches cmake.generator. The option
+ is appended to the cmake-generate-test-compile execution so it is
+ only present for MSVC builds (an empty <option/> would be null).
+ -->
+ <profile>
+ <id>cpp-msvc</id>
+ <activation>
+ <property>
+ <name>cpp.toolchain</name>
+ <value>msvc</value>
+ </property>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.googlecode.cmake-maven-project</groupId>
+ <artifactId>cmake-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>cmake-generate-test-compile</id>
+ <configuration>
+ <options combine.children="append">
+ <option>-DCMAKE_GENERATOR_PLATFORM=${msvc.platform}</option>
+ </options>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <!--
+ Pins the MSVC toolset when -Dmsvc.toolset is supplied (e.g.
+ -Dmsvc.toolset=v141 to build with the VS 2017 compiler). Self-
+ activating on the presence of the property, so it composes with
+ cpp-msvc regardless of how that profile was activated. Without the
+ property CMake uses the generator's default toolset.
+ -->
+ <profile>
+ <id>cpp-msvc-toolset</id>
+ <activation>
+ <property>
+ <name>msvc.toolset</name>
+ </property>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.googlecode.cmake-maven-project</groupId>
+ <artifactId>cmake-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>cmake-generate-test-compile</id>
+ <configuration>
+ <options combine.children="append">
+ <option>-DCMAKE_GENERATOR_TOOLSET=${msvc.toolset}</option>
+ </options>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
<profile>
<id>.java-9-and-above</id>
<activation>
diff --git a/cpp/src/CMakeLists.txt b/cpp/src/CMakeLists.txt
index e922836b7..93342c113 100644
--- a/cpp/src/CMakeLists.txt
+++ b/cpp/src/CMakeLists.txt
@@ -75,7 +75,10 @@ endif()
if (ENABLE_ZLIB)
list(APPEND PROJECT_INCLUDE_DIR
- ${CMAKE_SOURCE_DIR}/third_party/zlib-1.2.13
+ # zlib.h ships in the source tree; zconf.h is generated into the
+ # build tree by zlib's own CMake, so both directories are needed.
+ ${CMAKE_SOURCE_DIR}/third_party/zlib-1.3.1
+ ${THIRD_PARTY_INCLUDE}/zlib-1.3.1
)
endif()
@@ -88,6 +91,11 @@ endif()
include_directories(${PROJECT_INCLUDE_DIR})
+# Mark every translation unit that is compiled into the tsfile library so that
+# TSFILE_API (see utils/util_define.h) resolves to an export-side (empty)
+# decoration here, and to __declspec(dllimport) for external consumers.
+add_definitions(-DTSFILE_BUILDING)
+
if (ENABLE_ANTLR4)
add_subdirectory(parser)
message("Adding parser subdirectory")
@@ -157,4 +165,10 @@ set(LIBTSFILE_SO_VERSION ${TsFile_CPP_VERSION})
set_target_properties(tsfile PROPERTIES VERSION ${LIBTSFILE_PROJECT_VERSION})
set_target_properties(tsfile PROPERTIES SOVERSION ${LIBTSFILE_SO_VERSION})
-install(TARGETS tsfile LIBRARY DESTINATION ${LIBRARY_OUTPUT_PATH})
\ No newline at end of file
+# On Windows a SHARED library produces a .dll (RUNTIME) plus an import .lib
+# (ARCHIVE); on Unix it produces a .so (LIBRARY). Cover all three so the
+# install step works for every platform.
+install(TARGETS tsfile
+ RUNTIME DESTINATION ${LIBRARY_OUTPUT_PATH}
+ LIBRARY DESTINATION ${LIBRARY_OUTPUT_PATH}
+ ARCHIVE DESTINATION ${LIBRARY_OUTPUT_PATH})
\ No newline at end of file
diff --git a/cpp/src/common/allocator/alloc_base.h b/cpp/src/common/allocator/alloc_base.h
index facfd8081..c89aed077 100644
--- a/cpp/src/common/allocator/alloc_base.h
+++ b/cpp/src/common/allocator/alloc_base.h
@@ -59,7 +59,7 @@ enum AllocModID {
__MAX_MOD_ID = 127,
};
-extern const char* g_mod_names[__LAST_MOD_ID];
+extern TSFILE_API const char* g_mod_names[__LAST_MOD_ID];
/* very basic alloc/free interface in C style */
void* mem_alloc(uint32_t size, AllocModID mid);
@@ -120,7 +120,7 @@ class BaseAllocator {
void free(void* ptr) { mem_free(ptr); }
};
-extern BaseAllocator g_base_allocator;
+extern TSFILE_API BaseAllocator g_base_allocator;
} // end namespace common
diff --git a/cpp/src/common/allocator/my_string.h b/cpp/src/common/allocator/my_string.h
index 279ee798e..ce8a6b229 100644
--- a/cpp/src/common/allocator/my_string.h
+++ b/cpp/src/common/allocator/my_string.h
@@ -21,6 +21,7 @@
#include <string.h>
+#include <algorithm>
#include <iostream>
#include "common/allocator/page_arena.h"
diff --git a/cpp/src/common/constant/tsfile_constant.h b/cpp/src/common/constant/tsfile_constant.h
index 096c645ab..01ad1b2bc 100644
--- a/cpp/src/common/constant/tsfile_constant.h
+++ b/cpp/src/common/constant/tsfile_constant.h
@@ -41,9 +41,14 @@ static const std::string TIME_COLUMN_NAME = "time";
static const unsigned char VALUE_COLUMN_MASK = 0x40;
static const int NO_STR_TO_READ = -1;
-static const std::regex IDENTIFIER_PATTERN("([a-zA-Z0-9_\\u2E80-\\u9FFF]+)");
+// Identifier characters: ASCII alphanumerics / underscore plus any non-ASCII
+// (high) byte. The \x80-\xFF byte range covers UTF-8 lead/continuation bytes
+// (e.g. CJK characters) and is accepted by every std::regex implementation;
+// the \uXXXX codepoint-escape form used previously is rejected by MSVC's
+// <regex> (regex_error: error_escape).
+static const std::regex IDENTIFIER_PATTERN("([a-zA-Z0-9_\\x80-\\xFF]+)");
static const std::regex NODE_NAME_PATTERN(
- "(\\*{0,2}[a-zA-Z0-9_\\u2E80-\\u9FFF]+\\*{0,2})");
+ "(\\*{0,2}[a-zA-Z0-9_\\x80-\\xFF]+\\*{0,2})");
static const int DEFAULT_SEGMENT_NUM_FOR_TABLE_NAME = 3;
} // namespace storage
diff --git a/cpp/src/common/container/bit_map.h b/cpp/src/common/container/bit_map.h
index 7c60a1ea3..757ab1fb1 100644
--- a/cpp/src/common/container/bit_map.h
+++ b/cpp/src/common/container/bit_map.h
@@ -21,11 +21,80 @@
#include <string.h>
+#if defined(_MSC_VER)
+#include <intrin.h>
+#endif
+
#include "utils/errno_define.h"
#include "utils/util_define.h"
namespace common {
+// Cross-platform bit-twiddling helpers. GCC/Clang use their builtins; MSVC
+// uses the equivalent intrinsics from <intrin.h>; any other compiler falls
+// back to a portable loop.
+namespace bitops {
+// Population count of an 8-bit value.
+FORCE_INLINE int popcount8(uint8_t v) {
+#if defined(__GNUC__) || defined(__clang__)
+ return __builtin_popcount(v);
+#elif defined(_MSC_VER)
+ return static_cast<int>(__popcnt(static_cast<unsigned int>(v)));
+#else
+ int c = 0;
+ while (v) {
+ v = static_cast<uint8_t>(v & (v - 1));
+ ++c;
+ }
+ return c;
+#endif
+}
+// Count trailing zero bits. The argument must be non-zero.
+FORCE_INLINE int ctz_nonzero(uint32_t v) {
+#if defined(__GNUC__) || defined(__clang__)
+ return __builtin_ctz(v);
+#elif defined(_MSC_VER)
+ unsigned long idx;
+ _BitScanForward(&idx, v);
+ return static_cast<int>(idx);
+#else
+ int c = 0;
+ while (!(v & 1u)) {
+ v >>= 1;
+ ++c;
+ }
+ return c;
+#endif
+}
+// Count trailing zero bits of a 64-bit value. The argument must be non-zero.
+FORCE_INLINE int ctz64_nonzero(uint64_t v) {
+#if defined(__GNUC__) || defined(__clang__)
+ return __builtin_ctzll(v);
+#elif defined(_MSC_VER)
+ unsigned long idx;
+#if defined(_M_X64) || defined(_M_ARM64)
+ _BitScanForward64(&idx, v);
+#else
+ // 32-bit MSVC has no _BitScanForward64.
+ if (static_cast<uint32_t>(v) != 0) {
+ _BitScanForward(&idx, static_cast<uint32_t>(v));
+ } else {
+ _BitScanForward(&idx, static_cast<uint32_t>(v >> 32));
+ idx += 32;
+ }
+#endif
+ return static_cast<int>(idx);
+#else
+ int c = 0;
+ while (!(v & 1ull)) {
+ v >>= 1;
+ ++c;
+ }
+ return c;
+#endif
+}
+} // namespace bitops
+
class BitMap {
public:
BitMap() : bitmap_(nullptr), size_(0), init_as_zero_(true) {}
@@ -67,13 +136,11 @@ class BitMap {
}
// Count the number of bits set to 1 (i.e., number of null entries).
- // __builtin_popcount is supported by GCC, Clang, and MinGW on Windows.
- // TODO: add MSVC support if needed (e.g. __popcnt or manual bit count).
FORCE_INLINE uint32_t count_set_bits() const {
uint32_t count = 0;
const uint8_t* p = reinterpret_cast<const uint8_t*>(bitmap_);
for (uint32_t i = 0; i < size_; i++) {
- count += __builtin_popcount(p[i]);
+ count += bitops::popcount8(p[i]);
}
return count;
}
@@ -90,13 +157,14 @@ class BitMap {
// Check remaining bits in the first (partial) byte
uint8_t byte_val = p[byte_idx] >> (from & 7);
if (byte_val) {
- return from + __builtin_ctz(byte_val);
+ return from + bitops::ctz_nonzero(byte_val);
}
// Scan subsequent full bytes, skipping zeros
const uint32_t byte_end = (total_bits + 7) >> 3;
for (++byte_idx; byte_idx < byte_end; ++byte_idx) {
if (p[byte_idx]) {
- uint32_t pos = (byte_idx << 3) + __builtin_ctz(p[byte_idx]);
+ uint32_t pos =
+ (byte_idx << 3) + bitops::ctz_nonzero(p[byte_idx]);
return pos < total_bits ? pos : total_bits;
}
}
diff --git a/cpp/src/common/container/blocking_queue.cc b/cpp/src/common/container/blocking_queue.cc
deleted file mode 100644
index c843cd283..000000000
--- a/cpp/src/common/container/blocking_queue.cc
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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 "blocking_queue.h"
-
-namespace common {
-
-BlockingQueue::BlockingQueue() : queue_(), mutex_(), cond_() {
- pthread_mutex_init(&mutex_, NULL);
- pthread_cond_init(&cond_, NULL);
-}
-
-BlockingQueue::~BlockingQueue() {
- pthread_mutex_destroy(&mutex_);
- pthread_cond_destroy(&cond_);
-}
-
-void BlockingQueue::push(void* data) {
- pthread_mutex_lock(&mutex_);
- queue_.push(data);
- pthread_mutex_unlock(&mutex_);
- /*
- * it is safe to signal after unlock.
- * since pthread_cond_wait is guarantee to unlock and sleep atomically.
- */
- pthread_cond_signal(&cond_);
-}
-
-void* BlockingQueue::pop() {
- void* ret_data = NULL;
- pthread_mutex_lock(&mutex_);
- while (queue_.empty()) {
- pthread_cond_wait(&cond_, &mutex_);
- }
- ret_data = queue_.front();
- queue_.pop();
- pthread_mutex_unlock(&mutex_);
- return ret_data;
-}
-
-} // end namespace common
\ No newline at end of file
diff --git a/cpp/src/common/container/blocking_queue.h b/cpp/src/common/container/blocking_queue.h
deleted file mode 100644
index e03ea3a46..000000000
--- a/cpp/src/common/container/blocking_queue.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * License); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-#ifndef COMMON_CONTAINER_BLOCKING_QUEUE_H
-#define COMMON_CONTAINER_BLOCKING_QUEUE_H
-
-#include <pthread.h>
-
-#include <queue>
-
-namespace common {
-
-class BlockingQueue {
- public:
- BlockingQueue();
- ~BlockingQueue();
-
- void push(void* data);
- // if empty, blocking
- void* pop();
-
- private:
- std::queue<void*> queue_;
- pthread_mutex_t mutex_;
- pthread_cond_t cond_;
-};
-
-} // end namespace common
-#endif // COMMON_CONTAINER_BLOCKING_QUEUE_H
diff --git a/cpp/src/common/db_common.h b/cpp/src/common/db_common.h
index 8c637c3da..a2700c61c 100644
--- a/cpp/src/common/db_common.h
+++ b/cpp/src/common/db_common.h
@@ -93,9 +93,9 @@ enum CompressionType : uint8_t {
INVALID_COMPRESSION = 255
};
-extern const char* s_data_type_names[8];
-extern const char* s_encoding_names[12];
-extern const char* s_compression_names[8];
+extern TSFILE_API const char* s_data_type_names[8];
+extern TSFILE_API const char* s_encoding_names[12];
+extern TSFILE_API const char* s_compression_names[8];
} // namespace common
#if defined(__GLIBCXX__) && (__GNUC__ < 7)
diff --git a/cpp/src/common/global.cc b/cpp/src/common/global.cc
index ea4bf1289..a510dbff9 100644
--- a/cpp/src/common/global.cc
+++ b/cpp/src/common/global.cc
@@ -30,6 +30,7 @@
#include "common/thread_pool.h"
#endif
#include "utils/injection.h"
+#include "utils/util_define.h" // strncasecmp and other platform-compat shims
namespace common {
diff --git a/cpp/src/common/global.h b/cpp/src/common/global.h
index ba5f4bd40..5bee0fa60 100644
--- a/cpp/src/common/global.h
+++ b/cpp/src/common/global.h
@@ -26,8 +26,8 @@
#include "common/config/config.h"
namespace common {
-extern ConfigValue g_config_value_;
-extern ColumnSchema g_time_column_schema;
+extern TSFILE_API ConfigValue g_config_value_;
+extern TSFILE_API ColumnSchema g_time_column_schema;
FORCE_INLINE int set_global_time_data_type(uint8_t data_type) {
ASSERT(data_type >= BOOLEAN && data_type <= STRING);
diff --git a/cpp/src/common/mutex/mutex.h b/cpp/src/common/mutex/mutex.h
index 726288cd9..b35d328de 100644
--- a/cpp/src/common/mutex/mutex.h
+++ b/cpp/src/common/mutex/mutex.h
@@ -20,46 +20,28 @@
#ifndef COMMON_MUTEX_MUTEX_H
#define COMMON_MUTEX_MUTEX_H
-#include <errno.h>
-#include <pthread.h>
+#include <mutex>
#include "utils/util_define.h"
namespace common {
+// Thin wrapper over std::mutex. Implemented with the C++11 standard library
+// (instead of pthreads directly) so it builds on every platform, including
+// MSVC where pthreads is not available.
class Mutex {
public:
- Mutex() : mutex_() { pthread_mutex_init(&mutex_, NULL); }
- ~Mutex() { pthread_mutex_destroy(&mutex_); }
+ Mutex() {}
+ ~Mutex() {}
- void lock() {
- int ret = EBUSY;
- do {
- ret = pthread_mutex_lock(&mutex_);
- } while (UNLIKELY(ret == EBUSY || ret == EAGAIN));
- ASSERT(ret == 0);
- }
+ void lock() { mutex_.lock(); }
- void unlock() {
- int ret = pthread_mutex_unlock(&mutex_);
- ASSERT(ret == 0);
- (void)ret;
- }
+ void unlock() { mutex_.unlock(); }
- bool try_lock() {
- int ret = pthread_mutex_trylock(&mutex_);
- if (ret == 0) {
- return true;
- } else if (ret == EBUSY || ret == EAGAIN) {
- return false;
- } else {
- ASSERT(false);
- return false;
- }
- }
+ bool try_lock() { return mutex_.try_lock(); }
private:
- pthread_mutex_t mutex_;
+ std::mutex mutex_;
};
class MutexGuard {
diff --git a/cpp/src/common/tablet.cc b/cpp/src/common/tablet.cc
index 4088a6927..d71e48384 100644
--- a/cpp/src/common/tablet.cc
+++ b/cpp/src/common/tablet.cc
@@ -565,7 +565,7 @@ std::vector<uint32_t> Tablet::find_all_device_boundaries() const {
for (uint32_t w = 0; w < nwords; w++) {
uint64_t bits = boundary[w];
while (bits) {
- uint32_t bit = __builtin_ctzll(bits);
+ uint32_t bit = bitops::ctz64_nonzero(bits);
uint32_t idx = w * 64 + bit;
if (idx > 0 && idx < row_count) {
result.push_back(idx);
diff --git a/cpp/src/common/tablet.h b/cpp/src/common/tablet.h
index 50750d02b..799d6b7cc 100644
--- a/cpp/src/common/tablet.h
+++ b/cpp/src/common/tablet.h
@@ -22,6 +22,7 @@
#include <algorithm>
#include <memory>
+#include <utility>
#include <vector>
#include "common/config/config.h"
@@ -230,6 +231,52 @@ class Tablet {
~Tablet() { destroy(); }
+ // Tablet owns raw heap buffers (timestamps_, value_matrix_, bitmaps_) that
+ // destroy() frees. The implicitly generated copy operations would shallow-
+ // copy those pointers, causing double-free / use-after-free, so copying is
+ // disabled. Move transfers ownership and leaves the source empty (its
+ // pointers nulled) so the moved-from object destructs harmlessly.
+ Tablet(const Tablet&) = delete;
+ Tablet& operator=(const Tablet&) = delete;
+
+ Tablet(Tablet&& other) noexcept
+ : err_code_(other.err_code_),
+ max_row_num_(other.max_row_num_),
+ cur_row_size_(other.cur_row_size_),
+ insert_target_name_(std::move(other.insert_target_name_)),
+ schema_vec_(std::move(other.schema_vec_)),
+ schema_map_(std::move(other.schema_map_)),
+ timestamps_(other.timestamps_),
+ value_matrix_(other.value_matrix_),
+ bitmaps_(other.bitmaps_),
+ column_categories_(std::move(other.column_categories_)),
+ id_column_indexes_(std::move(other.id_column_indexes_)) {
+ other.timestamps_ = nullptr;
+ other.value_matrix_ = nullptr;
+ other.bitmaps_ = nullptr;
+ }
+
+ Tablet& operator=(Tablet&& other) noexcept {
+ if (this != &other) {
+ destroy();
+ err_code_ = other.err_code_;
+ max_row_num_ = other.max_row_num_;
+ cur_row_size_ = other.cur_row_size_;
+ insert_target_name_ = std::move(other.insert_target_name_);
+ schema_vec_ = std::move(other.schema_vec_);
+ schema_map_ = std::move(other.schema_map_);
+ timestamps_ = other.timestamps_;
+ value_matrix_ = other.value_matrix_;
+ bitmaps_ = other.bitmaps_;
+ column_categories_ = std::move(other.column_categories_);
+ id_column_indexes_ = std::move(other.id_column_indexes_);
+ other.timestamps_ = nullptr;
+ other.value_matrix_ = nullptr;
+ other.bitmaps_ = nullptr;
+ }
+ return *this;
+ }
+
const std::string& get_table_name() const { return insert_target_name_; }
void set_table_name(const std::string& table_name) {
insert_target_name_ = table_name;
diff --git a/cpp/src/common/tsfile_common.h b/cpp/src/common/tsfile_common.h
index 75b9a9da5..b516b608f 100644
--- a/cpp/src/common/tsfile_common.h
+++ b/cpp/src/common/tsfile_common.h
@@ -41,14 +41,14 @@
namespace storage {
-extern const char* MAGIC_STRING_TSFILE;
+extern TSFILE_API const char* MAGIC_STRING_TSFILE;
constexpr int MAGIC_STRING_TSFILE_LEN = 6;
-extern const char VERSION_NUM_BYTE;
-extern const char CHUNK_GROUP_HEADER_MARKER;
-extern const char CHUNK_HEADER_MARKER;
-extern const char ONLY_ONE_PAGE_CHUNK_HEADER_MARKER;
-extern const char SEPARATOR_MARKER;
-extern const char OPERATION_INDEX_RANGE;
+extern TSFILE_API const char VERSION_NUM_BYTE;
+extern TSFILE_API const char CHUNK_GROUP_HEADER_MARKER;
+extern TSFILE_API const char CHUNK_HEADER_MARKER;
+extern TSFILE_API const char ONLY_ONE_PAGE_CHUNK_HEADER_MARKER;
+extern TSFILE_API const char SEPARATOR_MARKER;
+extern TSFILE_API const char OPERATION_INDEX_RANGE;
// TODO review the String.len_ used
diff --git a/cpp/src/cwrapper/tsfile_cwrapper.cc b/cpp/src/cwrapper/tsfile_cwrapper.cc
index 99db61042..07b363aeb 100644
--- a/cpp/src/cwrapper/tsfile_cwrapper.cc
+++ b/cpp/src/cwrapper/tsfile_cwrapper.cc
@@ -21,8 +21,12 @@
#include <file/write_file.h>
#include <reader/qds_without_timegenerator.h>
-#include <unistd.h>
#include <writer/tsfile_table_writer.h>
+#ifdef _WIN32
+#include <io.h>
+#else
+#include <unistd.h>
+#endif
#include <cstring>
#include <set>
diff --git a/cpp/src/encoding/dictionary_encoder.h b/cpp/src/encoding/dictionary_encoder.h
index fad4ef68f..be5f78a09 100644
--- a/cpp/src/encoding/dictionary_encoder.h
+++ b/cpp/src/encoding/dictionary_encoder.h
@@ -83,7 +83,7 @@ class DictionaryEncoder : public Encoder {
if (entry_index_.count(value) == 0) {
index_entry_.push_back(value);
map_size_ = map_size_ + value.length();
- entry_index_[value] = entry_index_.size();
+ entry_index_[value] = static_cast<int>(index_entry_.size()) - 1;
}
values_encoder_.encode(entry_index_[value], out);
return common::E_OK;
diff --git a/cpp/src/file/read_file.cc b/cpp/src/file/read_file.cc
index 1807883a8..dd1c42dad 100644
--- a/cpp/src/file/read_file.cc
+++ b/cpp/src/file/read_file.cc
@@ -21,18 +21,18 @@
#include <fcntl.h>
#include <sys/stat.h>
-#include <unistd.h>
-
-#include "common/logger/elog.h"
-#include "common/tsfile_common.h"
-
#ifdef _WIN32
#include <io.h>
#include <windows.h>
-
ssize_t pread(int fd, void* buf, size_t count, uint64_t offset);
+#else
+#include <unistd.h>
#endif
+#include "common/logger/elog.h"
+#include "common/tsfile_common.h"
+#include "utils/util_define.h" // ssize_t and other platform-compat shims
+
using namespace common;
namespace storage {
diff --git a/cpp/src/file/write_file.cc b/cpp/src/file/write_file.cc
index 8ad96fab2..b6fbd6e44 100644
--- a/cpp/src/file/write_file.cc
+++ b/cpp/src/file/write_file.cc
@@ -24,18 +24,18 @@
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
+#ifdef _WIN32
+#include <io.h>
+int fsync(int);
+#else
#include <sys/time.h>
#include <unistd.h>
+#endif
#include "common/config/config.h"
#include "common/logger/elog.h"
#include "utils/errno_define.h"
-#ifdef _WIN32
-#include <io.h>
-int fsync(int);
-#endif
-
using namespace common;
namespace storage {
diff --git a/cpp/src/reader/result_set.h b/cpp/src/reader/result_set.h
index 216fced4f..1f1653603 100644
--- a/cpp/src/reader/result_set.h
+++ b/cpp/src/reader/result_set.h
@@ -21,6 +21,7 @@
#define READER_QUERY_DATA_SET_H
#include <algorithm>
+#include <cctype>
#include <string>
#include <unordered_map>
diff --git a/cpp/src/utils/db_utils.h b/cpp/src/utils/db_utils.h
index 832e5c167..4ffc4d138 100644
--- a/cpp/src/utils/db_utils.h
+++ b/cpp/src/utils/db_utils.h
@@ -23,8 +23,8 @@
#include <stdint.h>
#include <stdio.h>
#include <string.h> // memcpy
-#include <sys/time.h>
+#include <chrono>
#include <iostream>
#include <sstream>
#include <string>
@@ -195,12 +195,11 @@ struct ColumnSchema {
};
FORCE_INLINE int64_t get_cur_timestamp() {
- int64_t timestamp = 0;
- struct timeval tv;
- if (gettimeofday(&tv, NULL) >= 0) {
- timestamp = (int64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000;
- }
- return timestamp;
+ // Milliseconds since the Unix epoch. Uses the C++11 standard library so it
+ // is portable across platforms (gettimeofday is not available on MSVC).
+ return std::chrono::duration_cast<std::chrono::milliseconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
}
} // end namespace common
diff --git a/cpp/src/utils/storage_utils.h b/cpp/src/utils/storage_utils.h
index 78cc775c1..3cb06762a 100644
--- a/cpp/src/utils/storage_utils.h
+++ b/cpp/src/utils/storage_utils.h
@@ -23,6 +23,7 @@
#include <stdint.h>
#include <algorithm>
+#include <cctype>
#include "common/datatype/value.h"
#include "common/tsblock/tsblock.h"
diff --git a/cpp/src/utils/util_define.h b/cpp/src/utils/util_define.h
index 2796dfb0f..3cbe1d4c9 100644
--- a/cpp/src/utils/util_define.h
+++ b/cpp/src/utils/util_define.h
@@ -23,6 +23,67 @@
#include <stdint.h>
#include <stdlib.h>
+/* ======== platform compatibility ========
+ *
+ * MSVC does not provide several POSIX types/functions/macros used across the
+ * codebase. Provide drop-in equivalents so the same source compiles on both
+ * GCC/Clang (Linux) and MSVC (Windows) without scattering #ifdefs.
+ */
+#ifdef _WIN32
+#include <io.h>
+#include <string.h>
+
+#if defined(_MSC_VER)
+// ssize_t is a signed, pointer-sized integer; intptr_t (from <stdint.h>,
+// included above) is exactly that. We deliberately avoid <BaseTsd.h>/SSIZE_T
+// because that header also pollutes the global namespace with INT32/INT64
+// typedefs, which collide with the project's own INT32/INT64 enum values.
+typedef intptr_t ssize_t;
+typedef int mode_t;
+#endif // _MSC_VER
+
+// access() mode flags (POSIX <unistd.h>); MSVC's _access uses the same bits.
+#ifndef F_OK
+#define F_OK 0
+#endif
+#ifndef X_OK
+#define X_OK 1
+#endif
+#ifndef W_OK
+#define W_OK 2
+#endif
+#ifndef R_OK
+#define R_OK 4
+#endif
+
+#ifndef strcasecmp
+#define strcasecmp _stricmp
+#endif
+#ifndef strncasecmp
+#define strncasecmp _strnicmp
+#endif
+#endif // _WIN32
+
+/* ======== shared-library symbol visibility ========
+ *
+ * Functions are exported from tsfile.dll automatically via
+ * CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS, but global DATA symbols (plain variables,
+ * static class members) are not reliably auto-exported, and a consumer must
+ * see __declspec(dllimport) to reference them across the DLL boundary. Mark
+ * such symbols with TSFILE_API: it expands to dllexport while building the
+ * library (TSFILE_BUILDING is defined for its own translation units),
+ * dllimport for external consumers, and nothing on non-MSVC toolchains.
+ */
+#if defined(_MSC_VER)
+#if defined(TSFILE_BUILDING)
+#define TSFILE_API __declspec(dllexport)
+#else
+#define TSFILE_API __declspec(dllimport)
+#endif
+#else
+#define TSFILE_API
+#endif
+
/* ======== unsued ======== */
#define UNUSED(v) ((void)(v))
#if __cplusplus >= 201703L
@@ -34,8 +95,10 @@
#endif
/* ======== inline ======== */
-#ifdef __GNUC__
+#if defined(__GNUC__) || defined(__clang__)
#define FORCE_INLINE inline __attribute__((always_inline))
+#elif defined(_MSC_VER)
+#define FORCE_INLINE __forceinline
#else
#define FORCE_INLINE inline
#endif // __GNUC__
@@ -91,7 +154,19 @@
#define STATIC_ASSERT(cond, msg) static_assert((cond), #msg)
#endif // __cplusplus < 201103L
-/* ======== atomic operation ======== */
+/* ======== atomic operation ========
+ *
+ * The ATOMIC_* macros operate on the address of a plain (non-std::atomic)
+ * scalar, matching the semantics of the GCC/Clang __atomic builtins.
+ *
+ * - On GCC/Clang the builtins are used directly (unchanged behaviour).
+ * - On other compilers (MSVC) they are implemented on top of C++11 <atomic>
+ * via helper templates. Reinterpreting a plain scalar's address as a
+ * std::atomic<T>* is well-defined in practice for lock-free integral types
+ * (this is exactly what C++20 std::atomic_ref formalizes); all current call
+ * sites use naturally-aligned integral members.
+ */
+#if defined(__GNUC__) || defined(__clang__)
#define ATOMIC_FAA(val_addr, addv) \
__atomic_fetch_add((val_addr), (addv), __ATOMIC_SEQ_CST)
#define ATOMIC_AAF(val_addr, addv) \
@@ -112,9 +187,67 @@
#define ATOMIC_LOAD(val_addr) __atomic_load_n((val_addr), __ATOMIC_SEQ_CST)
#define ATOMIC_STORE(val_addr, val) \
__atomic_store_n((val_addr), (val), __ATOMIC_SEQ_CST)
+#elif defined(__cplusplus)
+#include <atomic>
+namespace common {
+namespace util_atomic {
+template <typename T>
+inline std::atomic<T>* as_atomic(T* p) {
+ return reinterpret_cast<std::atomic<T>*>(p);
+}
+template <typename T>
+inline const std::atomic<T>* as_atomic(const T* p) {
+ return reinterpret_cast<const std::atomic<T>*>(p);
+}
+// fetch-and-add: returns the value held *before* the addition.
+template <typename T, typename V>
+inline T faa(T* p, V v) {
+ return as_atomic(p)->fetch_add(static_cast<T>(v),
+ std::memory_order_seq_cst);
+}
+// add-and-fetch: returns the value held *after* the addition.
+template <typename T, typename V>
+inline T aaf(T* p, V v) {
+ return static_cast<T>(
+ as_atomic(p)->fetch_add(static_cast<T>(v), std::memory_order_seq_cst) +
+ static_cast<T>(v));
+}
+// compare-and-swap: returns true on success; on failure writes the current
+// value into *expected (same contract as __atomic_compare_exchange_n).
+template <typename T, typename D>
+inline bool cas(T* p, T* expected, D desired) {
+ return as_atomic(p)->compare_exchange_strong(
+ *expected, static_cast<T>(desired), std::memory_order_seq_cst);
+}
+template <typename T>
+inline T load(const T* p) {
+ return as_atomic(p)->load(std::memory_order_seq_cst);
+}
+template <typename T, typename V>
+inline void store(T* p, V v) {
+ as_atomic(p)->store(static_cast<T>(v), std::memory_order_seq_cst);
+}
+} // namespace util_atomic
+} // namespace common
+#define ATOMIC_FAA(val_addr, addv) \
+ (::common::util_atomic::faa((val_addr), (addv)))
+#define ATOMIC_AAF(val_addr, addv) \
+ (::common::util_atomic::aaf((val_addr), (addv)))
+#define ATOMIC_CAS(val_addr, expected, desired) \
+ (::common::util_atomic::cas((val_addr), (expected), (desired)))
+#define ATOMIC_LOAD(val_addr) (::common::util_atomic::load((val_addr)))
+#define ATOMIC_STORE(val_addr, val) \
+ (::common::util_atomic::store((val_addr), (val)))
+#endif // atomic operation
/* ======== align ======== */
+#if defined(__GNUC__) || defined(__clang__)
#define ALIGNED(a) __attribute__((aligned(a)))
+#elif defined(_MSC_VER)
+#define ALIGNED(a) __declspec(align(a))
+#else
+#define ALIGNED(a)
+#endif
#define ALIGNED_4 ALIGNED(4)
#define ALIGNED_8 ALIGNED(8)
diff --git a/cpp/src/writer/tsfile_writer.cc b/cpp/src/writer/tsfile_writer.cc
index 9bdbaa08d..3170a3160 100644
--- a/cpp/src/writer/tsfile_writer.cc
+++ b/cpp/src/writer/tsfile_writer.cc
@@ -19,7 +19,11 @@
#include "tsfile_writer.h"
+#ifdef _WIN32
+#include <io.h>
+#else
#include <unistd.h>
+#endif
#include "chunk_writer.h"
#include "common/config/config.h"
diff --git a/cpp/src/writer/tsfile_writer.h b/cpp/src/writer/tsfile_writer.h
index a931345af..a2c8f2842 100644
--- a/cpp/src/writer/tsfile_writer.h
+++ b/cpp/src/writer/tsfile_writer.h
@@ -33,6 +33,7 @@
#include "common/record.h"
#include "common/schema.h"
#include "common/tablet.h"
+#include "utils/util_define.h" // mode_t and other platform-compat shims
namespace storage {
class WriteFile;
diff --git a/cpp/src/writer/value_page_writer.h b/cpp/src/writer/value_page_writer.h
index ef694693b..97f8a5f0d 100644
--- a/cpp/src/writer/value_page_writer.h
+++ b/cpp/src/writer/value_page_writer.h
@@ -229,7 +229,7 @@ class ValuePageWriter {
std::vector<uint8_t> col_notnull_bitmap_;
uint32_t size_;
- static uint32_t MASK;
+ static TSFILE_API uint32_t MASK;
};
} // end namespace storage
diff --git a/cpp/test/CMakeLists.txt b/cpp/test/CMakeLists.txt
index 2be9c1b2c..f5d084f8f 100644
--- a/cpp/test/CMakeLists.txt
+++ b/cpp/test/CMakeLists.txt
@@ -108,12 +108,17 @@ file(GLOB_RECURSE TEST_SRCS
"encoding/*_test.cc"
"utils/*_test.cc"
"file/*_test.cc"
- "parser/*_test.cc"
"reader/*_test.cc"
"writer/*_test.cc"
"cwrapper/*_test.cc"
)
+# Parser tests depend on the ANTLR4 runtime; only build them when it is enabled.
+if (ENABLE_ANTLR4)
+ file(GLOB_RECURSE PARSER_TEST_SRCS "parser/*_test.cc")
+ list(APPEND TEST_SRCS ${PARSER_TEST_SRCS})
+endif()
+
if (ENABLE_SNAPPY)
file(GLOB_RECURSE SNAPPY_TEST_SRCS "compress/*snappy*_test.cc")
list(APPEND TEST_SRCS ${SNAPPY_TEST_SRCS})
@@ -171,8 +176,13 @@ include(GoogleTest)
# On Windows, delay test discovery until ctest runs (PRE_TEST) so the test exe
# runs with the correct env (e.g. PATH has MinGW, libtsfile.dll is present).
# Avoids 0xc0000139 when discovery runs at build time.
+#
+# DISCOVERY_TIMEOUT is raised well above the 5s default: the first execution of
+# the freshly-built test executable can be delayed by on-access antivirus
+# scanning (e.g. Windows Defender), which otherwise trips a spurious
+# "Process terminated due to timeout" while gtest_discover_tests enumerates it.
if(WIN32)
- gtest_discover_tests(TsFile_Test DISCOVERY_MODE PRE_TEST)
+ gtest_discover_tests(TsFile_Test DISCOVERY_MODE PRE_TEST DISCOVERY_TIMEOUT 120)
else()
gtest_discover_tests(TsFile_Test)
endif()
\ No newline at end of file
diff --git a/cpp/test/common/allocator/byte_stream_test.cc b/cpp/test/common/allocator/byte_stream_test.cc
index 6296e3a5d..b211803c3 100644
--- a/cpp/test/common/allocator/byte_stream_test.cc
+++ b/cpp/test/common/allocator/byte_stream_test.cc
@@ -87,7 +87,8 @@ TEST_F(ByteStreamTest, WriteReadLargeQuantities) {
write_to_stream(&data, 1);
}
- uint8_t read_buffer[1024 * 1024];
+ // 1 MiB buffer: keep it off the stack (MSVC's default stack is only 1 MiB).
+ static uint8_t read_buffer[1024 * 1024];
for (int i = 0; i < 1024 * 1024; i++) {
uint32_t read_len = 0;
read_from_stream(read_buffer + i, 1, read_len);
diff --git a/cpp/test/cwrapper/c_release_test.cc b/cpp/test/cwrapper/c_release_test.cc
index c0d0c35de..375c7e115 100644
--- a/cpp/test/cwrapper/c_release_test.cc
+++ b/cpp/test/cwrapper/c_release_test.cc
@@ -18,7 +18,11 @@
*/
#include <gtest/gtest.h>
+#ifdef _WIN32
+#include <io.h>
+#else
#include <unistd.h>
+#endif
#include <utils/db_utils.h>
#include <cstring>
@@ -97,13 +101,9 @@ TEST_F(CReleaseTest, TsFileWriterNew) {
table_schema.column_schemas =
static_cast<ColumnSchema*>(malloc(sizeof(ColumnSchema) * 2));
table_schema.column_schemas[0] =
- (ColumnSchema){.column_name = strdup("col1"),
- .data_type = TS_DATATYPE_STRING,
- .column_category = TAG};
+ ColumnSchema{strdup("col1"), TS_DATATYPE_STRING, TAG};
table_schema.column_schemas[1] =
- (ColumnSchema){.column_name = strdup("col2"),
- .data_type = TS_DATATYPE_INT32,
- .column_category = FIELD};
+ ColumnSchema{strdup("col2"), TS_DATATYPE_INT32, FIELD};
writer = tsfile_writer_new(file, &table_schema, &error_code);
ASSERT_EQ(RET_OK, error_code);
@@ -128,21 +128,15 @@ TEST_F(CReleaseTest, TsFileWriterWriteDataAbnormalColumn) {
abnormal_schema.column_schemas =
static_cast<ColumnSchema*>(malloc(sizeof(ColumnSchema) * 4));
abnormal_schema.column_schemas[0] =
- (ColumnSchema){.column_name = strdup("!@#$%^*()_+-="),
- .data_type = TS_DATATYPE_STRING,
- .column_category = TAG};
+ ColumnSchema{strdup("!@#$%^*()_+-="), TS_DATATYPE_STRING, TAG};
// TAG's datatype is not correct
abnormal_schema.column_schemas[1] =
- (ColumnSchema){.column_name = strdup("TAG2"),
- .data_type = TS_DATATYPE_INT32,
- .column_category = TAG};
+ ColumnSchema{strdup("TAG2"), TS_DATATYPE_INT32, TAG};
// same column name with column[0]
abnormal_schema.column_schemas[2] =
- (ColumnSchema){.column_name = strdup("!@#$%^*()_+-="),
- .data_type = TS_DATATYPE_DOUBLE,
- .column_category = FIELD};
+ ColumnSchema{strdup("!@#$%^*()_+-="), TS_DATATYPE_DOUBLE, FIELD};
// column name conflict
TsFileWriter writer =
@@ -151,9 +145,7 @@ TEST_F(CReleaseTest, TsFileWriterWriteDataAbnormalColumn) {
free(abnormal_schema.column_schemas[2].column_name);
abnormal_schema.column_schemas[2] =
- (ColumnSchema){.column_name = strdup("!@#$%^*()_+-=1"),
- .data_type = TS_DATATYPE_DOUBLE,
- .column_category = FIELD};
+ ColumnSchema{strdup("!@#$%^*()_+-=1"), TS_DATATYPE_DOUBLE, FIELD};
// datatype conflict
writer = tsfile_writer_new(file, &abnormal_schema, &error_code);
@@ -161,9 +153,7 @@ TEST_F(CReleaseTest, TsFileWriterWriteDataAbnormalColumn) {
free(abnormal_schema.column_schemas[1].column_name);
abnormal_schema.column_schemas[1] =
- (ColumnSchema){.column_name = strdup("TAG2"),
- .data_type = TS_DATATYPE_STRING,
- .column_category = TAG};
+ ColumnSchema{strdup("TAG2"), TS_DATATYPE_STRING, TAG};
writer = tsfile_writer_new(file, &abnormal_schema, &error_code);
ASSERT_EQ(RET_OK, error_code);
@@ -235,29 +225,17 @@ TEST_F(CReleaseTest, TsFileWriterMultiDataType) {
all_type_schema.column_schemas =
static_cast<ColumnSchema*>(malloc(sizeof(ColumnSchema) * 6));
all_type_schema.column_schemas[0] =
- (ColumnSchema){.column_name = strdup("TAG"),
- .data_type = TS_DATATYPE_STRING,
- .column_category = TAG};
+ ColumnSchema{strdup("TAG"), TS_DATATYPE_STRING, TAG};
all_type_schema.column_schemas[1] =
- (ColumnSchema){.column_name = strdup("INT32"),
- .data_type = TS_DATATYPE_INT32,
- .column_category = FIELD};
+ ColumnSchema{strdup("INT32"), TS_DATATYPE_INT32, FIELD};
all_type_schema.column_schemas[2] =
- (ColumnSchema){.column_name = strdup("INT64"),
- .data_type = TS_DATATYPE_INT64,
- .column_category = FIELD};
+ ColumnSchema{strdup("INT64"), TS_DATATYPE_INT64, FIELD};
all_type_schema.column_schemas[3] =
- (ColumnSchema){.column_name = strdup("FLOAT"),
- .data_type = TS_DATATYPE_FLOAT,
- .column_category = FIELD};
+ ColumnSchema{strdup("FLOAT"), TS_DATATYPE_FLOAT, FIELD};
all_type_schema.column_schemas[4] =
- (ColumnSchema){.column_name = strdup("DOUBLE"),
- .data_type = TS_DATATYPE_DOUBLE,
- .column_category = FIELD};
+ ColumnSchema{strdup("DOUBLE"), TS_DATATYPE_DOUBLE, FIELD};
all_type_schema.column_schemas[5] =
- (ColumnSchema){.column_name = strdup("BOOLEAN"),
- .data_type = TS_DATATYPE_BOOLEAN,
- .column_category = FIELD};
+ ColumnSchema{strdup("BOOLEAN"), TS_DATATYPE_BOOLEAN, FIELD};
TsFileWriter writer =
tsfile_writer_new(file, &all_type_schema, &error_code);
diff --git a/cpp/test/cwrapper/cwrapper_metadata_test.cc b/cpp/test/cwrapper/cwrapper_metadata_test.cc
index 57fca4de6..4595770a6 100644
--- a/cpp/test/cwrapper/cwrapper_metadata_test.cc
+++ b/cpp/test/cwrapper/cwrapper_metadata_test.cc
@@ -18,7 +18,11 @@
*/
#include <gtest/gtest.h>
+#ifdef _WIN32
+#include <io.h>
+#else
#include <unistd.h>
+#endif
#include <cstring>
#include <string>
diff --git a/cpp/test/cwrapper/cwrapper_test.cc b/cpp/test/cwrapper/cwrapper_test.cc
index 5998939af..9cf06d2f8 100644
--- a/cpp/test/cwrapper/cwrapper_test.cc
+++ b/cpp/test/cwrapper/cwrapper_test.cc
@@ -17,7 +17,11 @@
* under the License.
*/
#include <gtest/gtest.h>
+#ifdef _WIN32
+#include <io.h>
+#else
#include <unistd.h>
+#endif
#include <utils/db_utils.h>
#include <cstring>
diff --git a/cpp/third_party/CMakeLists.txt b/cpp/third_party/CMakeLists.txt
index b72c21638..70c5aa86c 100755
--- a/cpp/third_party/CMakeLists.txt
+++ b/cpp/third_party/CMakeLists.txt
@@ -17,6 +17,12 @@ specific language governing permissions and limitations
under the License.
]]
if (ENABLE_ANTLR4)
+ # Build the ANTLR4 runtime against the dynamic CRT (/MD) so it links the
+ # same C runtime as the rest of the project (avoiding LNK2038 mismatches).
+ # ANTLR4's own option is named WITH_STATIC_CRT and defaults to ON (=> /MT);
+ # pre-seeding it in the cache makes its option() call a no-op.
+ set(WITH_STATIC_CRT OFF CACHE BOOL
+ "Link the ANTLR4 runtime against the dynamic CRT" FORCE)
add_subdirectory(antlr4-cpp-runtime-4)
message("ANTLR4 runtime is enabled")
else()
diff --git a/cpp/third_party/antlr4-cpp-runtime-4/runtime/src/Token.h b/cpp/third_party/antlr4-cpp-runtime-4/runtime/src/Token.h
index 1878b28a1..c26ba03fd 100755
--- a/cpp/third_party/antlr4-cpp-runtime-4/runtime/src/Token.h
+++ b/cpp/third_party/antlr4-cpp-runtime-4/runtime/src/Token.h
@@ -14,7 +14,7 @@ namespace antlr4 {
/// we obtained this token.
class ANTLR4CPP_PUBLIC Token {
public:
-#if __cplusplus >= 201703L
+#if __cplusplus >= 201703L || defined(_MSC_VER)
static constexpr size_t INVALID_TYPE = 0;
#else
enum : size_t {
@@ -24,7 +24,7 @@ namespace antlr4 {
/// During lookahead operations, this "token" signifies we hit rule end ATN state
/// and did not follow it despite needing to.
-#if __cplusplus >= 201703L
+#if __cplusplus >= 201703L || defined(_MSC_VER)
static constexpr size_t EPSILON = std::numeric_limits<size_t>::max() - 1;
static constexpr size_t MIN_USER_TOKEN_TYPE = 1;
static constexpr size_t EOF = IntStream::EOF;
@@ -41,7 +41,7 @@ namespace antlr4 {
/// All tokens go to the parser (unless skip() is called in that rule)
/// on a particular "channel". The parser tunes to a particular channel
/// so that whitespace etc... can go to the parser on a "hidden" channel.
-#if __cplusplus >= 201703L
+#if __cplusplus >= 201703L || defined(_MSC_VER)
static constexpr size_t DEFAULT_CHANNEL = 0;
#else
enum : size_t {
@@ -51,7 +51,7 @@ namespace antlr4 {
/// Anything on different channel than DEFAULT_CHANNEL is not parsed
/// by parser.
-#if __cplusplus >= 201703L
+#if __cplusplus >= 201703L || defined(_MSC_VER)
static constexpr size_t HIDDEN_CHANNEL = 1;
#else
enum : size_t {
@@ -70,7 +70,7 @@ namespace antlr4 {
*
* @see Token#getChannel()
*/
-#if __cplusplus >= 201703L
+#if __cplusplus >= 201703L || defined(_MSC_VER)
static constexpr size_t MIN_USER_CHANNEL_VALUE = 2;
#else
enum : size_t {
diff --git a/cpp/third_party/antlr4-cpp-runtime-4/runtime/src/Vocabulary.cpp b/cpp/third_party/antlr4-cpp-runtime-4/runtime/src/Vocabulary.cpp
index 9bbf0b23a..f6da6e870 100755
--- a/cpp/third_party/antlr4-cpp-runtime-4/runtime/src/Vocabulary.cpp
+++ b/cpp/third_party/antlr4-cpp-runtime-4/runtime/src/Vocabulary.cpp
@@ -3,6 +3,8 @@
* can be found in the LICENSE.txt file in the project root.
*/
+#include <locale>
+
#include "Token.h"
#include "Vocabulary.h"
diff --git a/cpp/third_party/antlr4-cpp-runtime-4/runtime/src/atn/LL1Analyzer.h b/cpp/third_party/antlr4-cpp-runtime-4/runtime/src/atn/LL1Analyzer.h
index e297bc9a9..5b44be9db 100755
--- a/cpp/third_party/antlr4-cpp-runtime-4/runtime/src/atn/LL1Analyzer.h
+++ b/cpp/third_party/antlr4-cpp-runtime-4/runtime/src/atn/LL1Analyzer.h
@@ -17,7 +17,7 @@ namespace atn {
public:
/// Special value added to the lookahead sets to indicate that we hit
/// a predicate during analysis if {@code seeThruPreds==false}.
-#if __cplusplus >= 201703L
+#if __cplusplus >= 201703L || defined(_MSC_VER)
static constexpr size_t HIT_PRED = Token::INVALID_TYPE;
#else
enum : size_t {
diff --git a/pom.xml b/pom.xml
index f70b3a1b7..ff2bf8f8a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -43,6 +43,22 @@
<black.version>26.3.1</black.version>
<drill.freemarker.maven.plugin.version>1.21.1</drill.freemarker.maven.plugin.version>
<groovy.version>4.0.22</groovy.version>
+ <!--
+ C++ Windows toolchain selection. By default Windows builds use the
+ MinGW generator (see the .os-windows-* profiles). Passing
+ -Dcpp.toolchain=msvc activates the cpp-msvc profile, which switches
+ the CMake generator to Visual Studio. The msvc.generator value may
+ be overridden, e.g. -Dmsvc.generator="Visual Studio 15 2017", to
+ match the Visual Studio version installed on the build machine.
+
+ msvc.toolset optionally pins the MSVC toolset, e.g. -Dmsvc.toolset=v141
+ builds with the Visual Studio 2017 compiler while still using a
+ Visual Studio 2022 generator (useful on machines / CI runners that
+ only ship VS 2022 but need to verify VS 2017 compatibility).
+ When unset, CMake uses the generator's default toolset.
+ -->
+ <msvc.generator>Visual Studio 17 2022</msvc.generator>
+ <msvc.platform>x64</msvc.platform>
</properties>
<build>
<pluginManagement>
@@ -780,6 +796,43 @@
<python.exe.bin>python</python.exe.bin>
</properties>
</profile>
+ <!--
+ C++ Windows toolchain: MinGW (explicit form of the default).
+ Activate with -Dcpp.toolchain=mingw or -P cpp-mingw. This keeps the
+ MinGW Makefiles generator that the .os-windows-* profiles already
+ select, and exists mainly as the explicit counterpart to cpp-msvc.
+ -->
+ <profile>
+ <id>cpp-mingw</id>
+ <activation>
+ <property>
+ <name>cpp.toolchain</name>
+ <value>mingw</value>
+ </property>
+ </activation>
+ <properties>
+ <cmake.generator>MinGW Makefiles</cmake.generator>
+ </properties>
+ </profile>
+ <!--
+ C++ Windows toolchain: MSVC (Visual Studio generator).
+ Activate with -Dcpp.toolchain=msvc or -P cpp-msvc. Declared after
+ the .os-windows-* profiles so its cmake.generator value wins.
+ The Visual Studio generator is multi-config, so cpp/pom.xml passes
+ <config>${build.type}</config> to the compile and test goals.
+ -->
+ <profile>
+ <id>cpp-msvc</id>
+ <activation>
+ <property>
+ <name>cpp.toolchain</name>
+ <value>msvc</value>
+ </property>
+ </activation>
+ <properties>
+ <cmake.generator>${msvc.generator}</cmake.generator>
+ </properties>
+ </profile>
<!-- Little helper profile that will disable running the cmake tests when the maven tests are being skipped -->
<profile>
<id>.skipTests</id>
diff --git a/python/setup.py b/python/setup.py
index 63d99de03..19ada7921 100644
--- a/python/setup.py
+++ b/python/setup.py
@@ -29,20 +29,65 @@ from setuptools.command.build_ext import build_ext
ROOT = Path(__file__).parent.resolve()
PKG = ROOT / "tsfile"
-CPP_OUT = ROOT / ".." / "cpp" / "target" / "build"
-CPP_LIB = CPP_OUT / "lib"
-CPP_INC = CPP_OUT / "include"
version = "2.2.1.dev"
+
+def _find_cpp_build():
+ """Locate the C++ build output directory (one containing include/).
+
+ The Maven-driven build emits ``cpp/target/build``; a direct CMake build
+ emits ``cpp/build/<generator>``. Both layouts are supported, and an
+ explicit override may be given via the ``TSFILE_CPP_BUILD`` env var.
+ """
+ candidates = []
+ override = os.environ.get("TSFILE_CPP_BUILD")
+ if override:
+ candidates.append(Path(override))
+ candidates += [
+ ROOT / ".." / "cpp" / "target" / "build",
+ ROOT / ".." / "cpp" / "build" / "msvc",
+ ROOT / ".." / "cpp" / "build" / "Release",
+ ROOT / ".." / "cpp" / "build" / "RelWithDebInfo",
+ ROOT / ".." / "cpp" / "build" / "Debug",
+ ]
+ for cand in candidates:
+ cand = cand.resolve()
+ if (cand / "include").is_dir():
+ return cand
+ raise FileNotFoundError(
+ "Could not locate the C++ build output (a directory containing "
+ "include/). Build the C++ module first, or point TSFILE_CPP_BUILD "
+ "at the build directory."
+ )
+
+
+def _find_lib(root, patterns):
+ """Return the shortest-named file under ``root`` matching any pattern."""
+ for pattern in patterns:
+ hits = sorted(root.rglob(pattern), key=lambda p: len(p.name))
+ if hits:
+ return hits[0]
+ return None
+
+
+CPP_OUT = _find_cpp_build()
+CPP_LIB = CPP_OUT / "lib"
+CPP_INC = CPP_OUT / "include"
+
if not CPP_INC.exists():
raise FileNotFoundError(f"missing C++ headers: {CPP_INC}")
if (PKG / "include").exists():
shutil.rmtree(PKG / "include")
shutil.copytree(CPP_INC, PKG / "include")
+
+# Windows toolchain: "msvc" links against an MSVC import library (tsfile.lib),
+# "mingw" links against a MinGW import library (libtsfile.dll.a).
+win_toolchain = None
+
if sys.platform.startswith("linux"):
candidates = sorted(
- CPP_LIB.glob("libtsfile.so*"), key=lambda p: len(p.name), reverse=True
+ CPP_LIB.rglob("libtsfile.so*"), key=lambda p: len(p.name), reverse=True
)
if not candidates:
raise FileNotFoundError("missing libtsfile.so* in build output")
@@ -53,8 +98,8 @@ if sys.platform.startswith("linux"):
shutil.copy2(src, link_name)
elif sys.platform == "darwin":
- candidates = sorted(CPP_LIB.glob("libtsfile.*.dylib")) or list(
- CPP_LIB.glob("libtsfile.dylib")
+ candidates = sorted(CPP_LIB.rglob("libtsfile.*.dylib")) or list(
+ CPP_LIB.rglob("libtsfile.dylib")
)
if not candidates:
raise FileNotFoundError("missing libtsfile*.dylib in build output")
@@ -63,38 +108,62 @@ elif sys.platform == "darwin":
shutil.copy2(src, dst)
link_name = PKG / "libtsfile.dylib"
shutil.copy2(src, link_name)
+
elif sys.platform == "win32":
- for base_name in ("libtsfile",):
- dll_candidates = sorted(
- CPP_LIB.glob(f"{base_name}*.dll"), key=lambda p: len(p.name), reverse=True
- )
- dll_a_candidates = sorted(
- CPP_LIB.glob(f"{base_name}*.dll.a"), key=lambda p: len(p.name), reverse=True
+ # The shared library is named tsfile.dll (MSVC) or libtsfile.dll (MinGW).
+ dll_src = _find_lib(CPP_LIB, ["tsfile*.dll", "libtsfile*.dll"])
+ if dll_src is None:
+ raise FileNotFoundError(f"missing tsfile DLL in build output: {CPP_LIB}")
+
+ # Pick the import library and infer the toolchain from its kind.
+ mingw_imp = _find_lib(CPP_LIB, ["libtsfile*.dll.a"])
+ msvc_imp = _find_lib(CPP_LIB, ["tsfile*.lib", "libtsfile*.lib"])
+ if mingw_imp is not None and msvc_imp is None:
+ win_toolchain = "mingw"
+ imp_src = mingw_imp
+ elif msvc_imp is not None:
+ win_toolchain = "msvc"
+ imp_src = msvc_imp
+ else:
+ raise FileNotFoundError(
+ f"missing tsfile import library (*.lib or *.dll.a) in {CPP_LIB}"
)
- if not dll_candidates:
- raise FileNotFoundError(f"missing {base_name}*.dll in build output")
- if not dll_a_candidates:
- raise FileNotFoundError(f"missing {base_name}*.dll.a in build output")
-
- dll_src = dll_candidates[0]
- dll_a_src = dll_a_candidates[0]
-
- shutil.copy2(dll_src, PKG / f"{base_name}.dll")
- shutil.copy2(dll_a_src, PKG / f"{base_name}.dll.a")
-
- # Copy MinGW runtime DLLs next to libtsfile.dll so Python can find them.
- # Python 3.8+ does not search PATH for DLLs; they must be in the same
- # directory as the .pyd extensions (registered via os.add_dll_directory).
- for _mingw_dll in ("libstdc++-6.dll", "libgcc_s_seh-1.dll", "libwinpthread-1.dll"):
- for _dir in os.environ.get("PATH", "").split(os.pathsep):
- _src = Path(_dir) / _mingw_dll
- if _src.is_file():
- shutil.copy2(_src, PKG / _mingw_dll)
- print(f"setup.py: copied {_mingw_dll} from {_src}")
- break
- else:
- print(f"setup.py: WARNING - {_mingw_dll} not found on PATH")
+ # For MSVC the DLL is already named tsfile.dll. For MinGW, keep the original
+ # name (libtsfile.dll): the import library (libtsfile.dll.a) has that name
+ # baked in, so Cython .pyd files record "libtsfile.dll" in their PE import
+ # table and Windows must find it by that exact name at runtime.
+ dll_dst = PKG / dll_src.name
+ shutil.copy2(dll_src, dll_dst)
+
+ # Copy import library with a name matching the DLL.
+ if win_toolchain == "mingw":
+ imp_dst = PKG / "tsfile.dll.a"
+ else:
+ imp_dst = PKG / "tsfile.lib"
+ shutil.copy2(imp_src, imp_dst)
+ print(f"setup.py: Windows toolchain = {win_toolchain}")
+ print(
+ f"setup.py: copied {dll_src.name} -> {dll_dst.name} and {imp_src.name} -> {imp_dst.name}"
+ )
+
+ if win_toolchain == "mingw":
+ # Copy MinGW runtime DLLs next to tsfile.dll so Python can find them.
+ # Python 3.8+ does not search PATH for DLLs; they must sit in the
+ # same directory as the .pyd extensions (os.add_dll_directory).
+ for _mingw_dll in (
+ "libstdc++-6.dll",
+ "libgcc_s_seh-1.dll",
+ "libwinpthread-1.dll",
+ ):
+ for _dir in os.environ.get("PATH", "").split(os.pathsep):
+ _src = Path(_dir) / _mingw_dll
+ if _src.is_file():
+ shutil.copy2(_src, PKG / _mingw_dll)
+ print(f"setup.py: copied {_mingw_dll} from {_src}")
+ break
+ else:
+ print(f"setup.py: WARNING - {_mingw_dll} not found on PATH")
else:
raise RuntimeError(f"Unsupported platform: {sys.platform}")
@@ -104,7 +173,9 @@ class BuildExt(build_ext):
super().run()
def finalize_options(self):
- if sys.platform == "win32":
+ # MinGW must be requested explicitly; MSVC is the default Windows
+ # compiler distutils selects, which matches an MSVC-built libtsfile.
+ if sys.platform == "win32" and win_toolchain == "mingw":
self.compiler = "mingw32"
super().finalize_options()
@@ -127,15 +198,33 @@ elif sys.platform == "darwin":
extra_link_args += ["-Wl,-rpath,@loader_path", "-stdlib=libc++"]
elif sys.platform == "win32":
libraries = ["tsfile"]
- extra_compile_args += [
- "-O2",
- "-std=c++11",
- "-DSIZEOF_VOID_P=8",
- "-D__USE_MINGW_ANSI_STDIO=1",
- "-DMS_WIN64",
- "-D_WIN64",
- ]
- extra_link_args += []
+ if win_toolchain == "mingw":
+ extra_compile_args += [
+ "-O2",
+ "-std=c++11",
+ "-DSIZEOF_VOID_P=8",
+ "-D__USE_MINGW_ANSI_STDIO=1",
+ "-DMS_WIN64",
+ "-D_WIN64",
+ ]
+ else: # msvc
+ # cl.exe rejects the GCC-style flags above. Mirror the options the
+ # C++ module itself is built with (see cpp/CMakeLists.txt). C++17 is
+ # required because Cython 3 emits inline variables (error C7525); the
+ # C++11 headers compile cleanly under the newer standard.
+ extra_compile_args += [
+ "/O2",
+ "/std:c++17",
+ "/EHsc",
+ "/bigobj",
+ "/utf-8",
+ "/Zc:__cplusplus",
+ "/DNOMINMAX",
+ "/D_CRT_SECURE_NO_WARNINGS",
+ "/D_CRT_NONSTDC_NO_WARNINGS",
+ "/D_SCL_SECURE_NO_WARNINGS",
+ "/D_WINSOCK_DEPRECATED_NO_WARNINGS",
+ ]
else:
raise RuntimeError(f"Unsupported platform: {sys.platform}")
diff --git a/python/tsfile/__init__.py b/python/tsfile/__init__.py
index 32021d52e..1026ec673 100644
--- a/python/tsfile/__init__.py
+++ b/python/tsfile/__init__.py
@@ -23,12 +23,24 @@ import sys
_pkg_dir = os.path.dirname(os.path.abspath(__file__))
if sys.platform == "win32":
- os.add_dll_directory(_pkg_dir)
- # Preload libtsfile.dll with absolute path to bypass DLL search issues.
- # This ensures it's already in memory when .pyd extensions reference it.
- _tsfile_dll = os.path.join(_pkg_dir, "libtsfile.dll")
- if os.path.isfile(_tsfile_dll):
- ctypes.CDLL(_tsfile_dll)
+ # Keep the handle alive for the lifetime of this module. CPython's reference
+ # counting frees the object immediately if not stored, which calls
+ # RemoveDllDirectory and undoes the registration before any .pyd is loaded.
+ _dll_dir = os.add_dll_directory(_pkg_dir)
+ # Preload the tsfile DLL so Windows finds it by base-name when loading the
+ # Cython extensions. Store the handle to prevent the DLL from being
+ # unloaded prematurely.
+ # MSVC builds produce "tsfile.dll"; MinGW builds produce "libtsfile.dll".
+ for _dll_name in ("tsfile.dll", "libtsfile.dll"):
+ _tsfile_dll = os.path.join(_pkg_dir, _dll_name)
+ if os.path.isfile(_tsfile_dll):
+ _tsfile_cdll = ctypes.CDLL(_tsfile_dll)
+ break
+ else:
+ raise FileNotFoundError(
+ f"tsfile DLL (tsfile.dll or libtsfile.dll) not found in {_pkg_dir}. "
+ "Re-build the C++ module and reinstall the Python package."
+ )
elif sys.platform == "darwin":
_tsfile_dylib = os.path.join(_pkg_dir, "libtsfile.dylib")
if os.path.isfile(_tsfile_dylib):