diff --git a/.github/workflows/Windows.yml b/.github/workflows/Windows.yml new file mode 100644 index 000000000..fa60f5b54 --- /dev/null +++ b/.github/workflows/Windows.yml @@ -0,0 +1,337 @@ +name: NeuG Test (Windows) + +on: + workflow_dispatch: + release: + types: + - created + push: + paths: + - 'cmake/**' + - 'src/**' + - 'bin/**' + - 'proto/**' + - 'third_party/**' + - 'tools/python_bind/**' + - 'tools/utils/**' + - 'tests/**' + - '.github/workflows/Windows.yml' + - '.github/workflows/build-and-test-common.yml' + - 'scripts/install_deps.ps1' + - 'scripts/build_windows.bat' + - 'include/**' + - 'CMakeLists.txt' + - 'doc/source/tutorials/**' + +concurrency: + group: ${{ github.repository }}-${{ github.event.number || github.head_ref || github.sha }}-${{ github.workflow }} + cancel-in-progress: true + +jobs: + format_check: + if: | + github.event_name == 'workflow_dispatch' || + github.event_name == 'release' || + (github.event_name == 'push' && (github.repository != 'alibaba/neug' || github.ref == 'refs/heads/main')) + uses: ./.github/workflows/format-check.yml + with: + is_pull_request: ${{ github.event_name == 'pull_request' }} + + # ============================================================ + # Windows x64 Job: Build NeuG + run C++ and Python tests + # Uses GitHub-hosted windows-2022 runner with pre-installed + # MSVC 2022, vcpkg, Python, CMake, and Ninja. + # ============================================================ + build_and_test_windows: + needs: format_check + if: ${{ (needs.format_check.result == 'success' || needs.format_check.result == 'skipped') && (github.event_name == 'workflow_dispatch' || github.event_name == 'release' || (github.event_name == 'push' && (github.repository != 'alibaba/neug' || github.ref == 'refs/heads/main'))) }} + runs-on: windows-2022 + timeout-minutes: 120 + defaults: + run: + shell: pwsh + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + fetch-depth: 0 + + - name: Setup MSVC Developer Command Prompt + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x64 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -r tools/python_bind/requirements.txt + python -m pip install pytest pytest-cov coverage + + # vcpkg is pre-installed on GitHub-hosted Windows runners at C:\vcpkg. + # Setting VCPKG_ROOT lets CMake's vcpkg toolchain auto-install OpenSSL + # and other dependencies declared via find_package() in CMakeLists.txt. + - name: Setup vcpkg + run: | + echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" >> $env:GITHUB_ENV + echo "Using vcpkg at: $env:VCPKG_INSTALLATION_ROOT" + + - name: Cache vcpkg packages + uses: actions/cache@v4 + with: + path: ${{ env.VCPKG_ROOT }}/installed + key: vcpkg-x64-windows-static-md-${{ hashFiles('CMakeLists.txt') }} + restore-keys: | + vcpkg-x64-windows-static-md- + + - name: Configure CMake + run: | + $pythonExe = (Get-Command python).Path + $vcpkgToolchain = Join-Path $env:VCPKG_ROOT "scripts\buildsystems\vcpkg.cmake" + cmake -B build -G Ninja ` + -DCMAKE_BUILD_TYPE=Release ` + -DCMAKE_TOOLCHAIN_FILE="$vcpkgToolchain" ` + -DVCPKG_TARGET_TRIPLET=x64-windows-static-md ` + -DBUILD_PYTHON=ON ` + -DBUILD_TEST=ON ` + -DPython_EXECUTABLE="$pythonExe" ` + -DPYTHON_EXECUTABLE="$pythonExe" ` + . + + - name: Build + run: | + $cpuCount = [Environment]::ProcessorCount + $jobs = [Math]::Min($cpuCount, 8) + Write-Host "Building with $jobs parallel jobs" + cmake --build build -j $jobs + + # Windows has no RPATH ($ORIGIN/@loader_path); DLLs are resolved via + # the DLL search order (PATH, same-dir-first). Add the directories + # that contain libneug.dll and vcpkg-installed DLLs so that + # neug_py_bind.pyd can find all its dependencies at import time. + - name: Add DLL directories to PATH + run: | + $vcpkgBin = Join-Path $env:VCPKG_ROOT "installed\x64-windows-static-md\bin" + $buildSrc = Join-Path $env:GITHUB_WORKSPACE "build\src" + $buildPyBind = Join-Path $env:GITHUB_WORKSPACE "build\tools\python_bind" + Write-Host "Adding to PATH: $vcpkgBin, $buildSrc, $buildPyBind" + echo "$vcpkgBin" >> $env:GITHUB_PATH + echo "$buildSrc" >> $env:GITHUB_PATH + echo "$buildPyBind" >> $env:GITHUB_PATH + + # neug.dll links against OpenSSL dynamically (libssl-3-x64.dll / + # libcrypto-3-x64.dll). Copy them next to neug_py_bind.pyd so the + # Windows loader finds them via the same-directory rule. + - name: Copy OpenSSL DLLs next to Python bindings + shell: pwsh + run: | + $dest = Join-Path $env:GITHUB_WORKSPACE "build\tools\python_bind" + $srcBuild = Join-Path $env:GITHUB_WORKSPACE "build\src" + $vcpkgInstalled = Join-Path $env:VCPKG_ROOT "installed" + + # Search for OpenSSL DLLs across the entire vcpkg installation + # and the build tree (static triplet may not place them in bin/). + $searchDirs = @( + (Join-Path $vcpkgInstalled "x64-windows-static-md\bin"), + (Join-Path $vcpkgInstalled "x64-windows-static-md\tools"), + (Join-Path $vcpkgInstalled "x64-windows\bin"), + $srcBuild + ) + foreach ($d in $searchDirs) { + if (Test-Path $d) { + Write-Host "Searching: $d" + Get-ChildItem -Path $d -Recurse -Filter "libssl*.dll" -ErrorAction SilentlyContinue | ForEach-Object { Write-Host " Found: $($_.FullName)" } + Get-ChildItem -Path $d -Recurse -Filter "libcrypto*.dll" -ErrorAction SilentlyContinue | ForEach-Object { Write-Host " Found: $($_.FullName)" } + } + } + + # Copy all found OpenSSL DLLs to the destination + foreach ($d in $searchDirs) { + if (Test-Path $d) { + $ssl = Get-ChildItem -Path $d -Recurse -Filter "libssl-3-x64.dll" -ErrorAction SilentlyContinue | Select-Object -First 1 + $crypto = Get-ChildItem -Path $d -Recurse -Filter "libcrypto-3-x64.dll" -ErrorAction SilentlyContinue | Select-Object -First 1 + if ($ssl) { Copy-Item $ssl.FullName $dest -Force; Write-Host "Copied $($ssl.FullName) to $dest" } + if ($crypto) { Copy-Item $crypto.FullName $dest -Force; Write-Host "Copied $($crypto.FullName) to $dest" } + if ($ssl -and $crypto) { break } + } + } + + Write-Host "=== All .dll files in $dest ===" + Get-ChildItem $dest -Filter "*.dll" | Format-Table FullName + + # ======================================== + # Phase 1: C++ Tests (ctest) + # Mirrors build-and-test-common.yml Phase 1. + # test_db_svc is skipped (BRPC not available on Windows). + # ======================================== + - name: Run C++ tests + continue-on-error: true + env: + MODERN_GRAPH_DATA_DIR: ${{ github.workspace }}/example_dataset/modern_graph + COMPREHENSIVE_GRAPH_DATA_DIR: ${{ github.workspace }}/example_dataset/comprehensive_graph + FLEX_DATA_DIR: ${{ github.workspace }}/example_dataset/modern_graph + TEST_PATH: ${{ github.workspace }}/tests + TEST_RESOURCE: ${{ github.workspace }}/tests/compiler + run: | + cd build + ctest -R storage_test -V + ctest -R transaction_test -V + ctest -R csr_test -V + ctest -R edge_table_test -V + ctest -R graph_view_test -V + ctest -R graph_snapshot_store_test -V + ctest -R utils_test -V + ctest -R execution_test -V + ctest -R gopt_test -V + ctest -R test_vertex_table -V + ctest -R schema_test -V + ctest -R test_mmap_container -V + ctest -R logical_delete_test -V + ctest -R test_connection -V + ctest -R test_request -V + ctest -R sink_test -V + ctest -R test_indexer -V + ctest -R temporary_graph_test -V + ctest -R copy_temp_test -V + + # ======================================== + # Phase 2: Python Tests + # Mirrors build-and-test-common.yml Phase 3. + # Tests depending on bulk-loaded /tmp data are skipped + # (bulk_loader executable is not built on Windows). + # ======================================== + - name: Install Python dev requirements + run: | + python -m pip install -r tools/python_bind/requirements_dev.txt + + - name: Run Python DB init tests + continue-on-error: true + run: | + cd tools/python_bind + python -m pytest -sv tests/test_db_init.py --exitfirst + + - name: Run Python DB connection test + continue-on-error: true + run: | + cd tools/python_bind + python -m pytest -sv tests/test_db_connection.py --exitfirst + + - name: Run Python schema tests + continue-on-error: true + run: | + cd tools/python_bind + python -m pytest -sv tests/test_schema.py --exitfirst + + - name: Run Python DML tests + continue-on-error: true + run: | + cd tools/python_bind + python -m pytest -sv tests/test_dml.py --exitfirst + + - name: Run Python query tests + continue-on-error: true + run: | + cd tools/python_bind + python -m pytest -sv tests/test_query.py --exitfirst + + - name: Run Python path tests + continue-on-error: true + run: | + cd tools/python_bind + python -m pytest -sv tests/test_path.py --exitfirst + + - name: Run Python persistence tests + continue-on-error: true + run: | + cd tools/python_bind + python -m pytest -sv tests/test_persistence.py --exitfirst + + - name: Run Python call string literal tests + continue-on-error: true + run: | + cd tools/python_bind + python -m pytest -sv tests/test_call_string_literal.py --exitfirst + + - name: Run Python dataset tests + continue-on-error: true + run: | + cd tools/python_bind + python -m pytest -sv tests/test_dataset.py --exitfirst + + - name: Run Python merge tests + continue-on-error: true + run: | + cd tools/python_bind + python -m pytest -sv tests/test_merge.py --exitfirst + + - name: Run Python transaction tests + continue-on-error: true + run: | + cd tools/python_bind + python -m pytest -sv tests/test_db_transaction.py --exitfirst + + - name: Run Python import/export tests + continue-on-error: true + run: | + cd tools/python_bind + python -m pytest -sv tests/test_db_import_export.py --exitfirst + + - name: Run Python Data I/O documentation tests + continue-on-error: true + env: + FLEX_DATA_DIR: ${{ github.workspace }}/example_dataset/modern_graph + run: | + cd tools/python_bind + python -m pytest -sv tests/test_data_io_docs.py --exitfirst + + - name: Run Python LOAD FROM sniffer tests (CSV and JSON) + continue-on-error: true + run: | + cd tools/python_bind + python -m pytest -sv tests/test_sniffer.py -k "csv or json" --exitfirst + + - name: Run Python complex query tests + continue-on-error: true + run: | + cd tools/python_bind + python -m pytest -sv tests/test_db_cases.py --exitfirst + + - name: Run Python list/array tests + continue-on-error: true + run: | + cd tools/python_bind + python -m pytest -sv tests/test_db_list.py --exitfirst + python -m pytest -sv tests/test_db_array.py --exitfirst + + - name: Run Python concurrency tests + continue-on-error: true + run: | + cd tools/python_bind + python -m pytest -sv tests/test_db_concurrent.py --exitfirst + + - name: Run Python transaction concurrent tests + continue-on-error: true + run: | + cd tools/python_bind + python -m pytest -sv tests/transaction/elle_tester_insert.py --exitfirst + + - name: Run Python PyArrow related tests + continue-on-error: true + run: | + cd tools/python_bind + python -m pip install pyarrow==18.0.0 + python -m pytest -sv tests/test_to_arrow.py --exitfirst + python -m pip uninstall -y pyarrow + + - name: Run Python CLI tool tests + continue-on-error: true + run: | + cd tools/python_bind + python -m pytest -sv tests/test_ngcli_commands.py --exitfirst + python -m pytest -sv tests/test_ngcli_basics.py --exitfirst diff --git a/CMakeLists.txt b/CMakeLists.txt index 019fcdf7f..fdd3a36fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -114,6 +114,45 @@ endforeach() add_definitions(-DNEUG_CMAKE_VERSION="${CMAKE_PROJECT_VERSION}") +# Windows port: disable features that are not yet supported or unnecessary for the core library. +if(WIN32) + message(STATUS "Windows detected: disabling non-core features for core library build") + # BUILD_PYTHON is no longer forced off; pass -DBUILD_PYTHON=ON to enable. + set(BUILD_NODEJS OFF CACHE BOOL "Node.js binding is not supported on Windows yet" FORCE) + set(BUILD_HTTP_SERVER OFF CACHE BOOL "HTTP server (brpc) is not supported on Windows yet" FORCE) + set(BUILD_EXTENSIONS "" CACHE STRING "Extensions are not supported on Windows yet" FORCE) + set(BUILD_EXECUTABLES OFF CACHE BOOL "Executables are disabled on Windows core build" FORCE) + set(BUILD_EXAMPLES OFF CACHE BOOL "Examples are disabled on Windows core build" FORCE) + set(NEUG_NATIVE_ARCH OFF CACHE BOOL "Native architecture tuning is not supported on Windows" FORCE) + set(ENABLE_BACKTRACES OFF CACHE BOOL "Backtraces are not supported on Windows yet" FORCE) + # Use dynamic C runtime (/MD) consistently across all targets and third-party + # dependencies to match vcpkg's x64-windows-static-md triplet. + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL" CACHE STRING "" FORCE) + # Windows headers define an ERROR macro which conflicts with glog's ERROR + # severity level. Disable abbreviated severity macros on Windows. + add_compile_definitions(GLOG_NO_ABBREVIATED_SEVERITIES) + # Windows headers also define min/max macros that conflict with std::min/max. + add_compile_definitions(NOMINMAX) + # Windows headers define GetObject as GetObjectA/GetObjectW (in wingdi.h), + # which conflicts with rapidjson::Value::GetObject(). Exclude GDI headers. + add_compile_definitions(NOGDI) + # yaml-cpp is built as a static third-party library; tell its headers not to + # use __declspec(dllimport) on MSVC. + add_compile_definitions(YAML_CPP_STATIC_DEFINE) + # NEUG_EXPORTS must be visible to all object libraries that feed into the + # neug SHARED target, not just the final link step. Without this, NEUG_API + # resolves to __declspec(dllimport) in object files, causing C2491 when + # defining static data members. + add_compile_definitions(NEUG_EXPORTS) + # Force-include platform.h so that GCC extensions such as __attribute__ are + # neutralized for MSVC without touching every source file. Restrict to CXX + # so the resource compiler (RC) does not see the option. + add_compile_options($<$:/FI${CMAKE_SOURCE_DIR}/include/neug/utils/platform.h>) + # Force-include api.h so NEUG_API is visible everywhere (needed because + # protobuf-generated headers use dllexport_decl=NEUG_API). + add_compile_options($<$:/FI${CMAKE_SOURCE_DIR}/include/neug/utils/api.h>) +endif() + include(CheckCXXCompilerFlag) include(cmake/neug_symbol_visibility.cmake) include(cmake/NeugNativeArch.cmake) @@ -130,10 +169,7 @@ endif () add_compile_definitions(NEUG_VERSION="${NEUG_VERSION}") -# reference: https://gitlab.kitware.com/cmake/community/-/wikis/doc/cmake/RPATH-handling#always-full-rpath -set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) -set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") -set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) +# C++ standard is required on all platforms. set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED TRUE) # set(CMAKE_CXX_VISIBILITY_PRESET hidden) @@ -142,8 +178,16 @@ set(CMAKE_CXX_STANDARD_REQUIRED TRUE) # set(CMAKE_FIND_PACKAGE_RESOLVE_SYMLINKS TRUE) # set(CMAKE_POSITION_INDEPENDENT_CODE ON) # set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) -# On Linux, symbols in executables are not accessible by loaded shared libraries (e.g. via dlopen(3)). However, we need to export public symbols in executables so that extensions can access public symbols. This enables that behaviour. -set(CMAKE_ENABLE_EXPORTS TRUE) + +# RPATH is a Linux/macOS concept; keep related settings off Windows. +if (NOT WIN32) + # reference: https://gitlab.kitware.com/cmake/community/-/wikis/doc/cmake/RPATH-handling#always-full-rpath + set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) + set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") + set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) + # On Linux, symbols in executables are not accessible by loaded shared libraries (e.g. via dlopen(3)). However, we need to export public symbols in executables so that extensions can access public symbols. This enables that behaviour. + set(CMAKE_ENABLE_EXPORTS TRUE) +endif() if(ENABLE_WERROR) if (CMAKE_VERSION VERSION_GREATER "3.24.0" OR CMAKE_VERSION VERSION_EQUAL "3.24.0") @@ -157,33 +201,49 @@ endif() if (APPLE) set(CMAKE_MACOSX_RPATH ON) -else () +elseif(NOT WIN32) if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lopen-pal") else () set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,-rpath,$ORIGIN") endif () endif () -# Only add -fopenmp if not using clang -if (NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang") +# Only add OpenMP if not using clang (MSVC supports /openmp) +if (MSVC) + message(STATUS "Using MSVC compiler, adding /openmp") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /openmp") +elseif (NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang") message(STATUS "Using non-clang compiler, adding -fopenmp") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fopenmp") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fopenmp") endif () -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fPIC -Wno-psabi") -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") -check_cxx_compiler_flag("-std=c++20" COMPILER_SUPPORTS_CXX20) -if (NOT COMPILER_SUPPORTS_CXX20) - check_cxx_compiler_flag("-std=c++2a" COMPILER_SUPPORTS_CXX2A) - if (COMPILER_SUPPORTS_CXX2A) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++2a") +if (MSVC) + # MSVC warning levels and common suppressions for third-party headers + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W3 /wd4996 /wd4267 /wd4244 /wd4305 /wd4800 /wd4251") + # Treat source files as UTF-8 to avoid code page issues with non-ASCII characters. + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /utf-8") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /Zi") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2") +else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fPIC -Wno-psabi") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") + + check_cxx_compiler_flag("-std=c++20" COMPILER_SUPPORTS_CXX20) + if (NOT COMPILER_SUPPORTS_CXX20) + check_cxx_compiler_flag("-std=c++2a" COMPILER_SUPPORTS_CXX2A) + if (COMPILER_SUPPORTS_CXX2A) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++2a") + else() + message(WARNING "Compiler does not support C++20 or C++2a") + endif() else() - message(WARNING "Compiler does not support C++20 or C++2a") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20") endif() -else() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20") + + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") endif() include(CheckCXXSourceCompiles) @@ -199,8 +259,6 @@ if (HAVE_CHRONO_YMD) add_definitions(-DENABLE_CHRONO_YMD) endif() -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g") -set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") #if compiler is gcc if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-class-memaccess -Wno-maybe-uninitialized -Wno-ignored-attributes") @@ -248,35 +306,38 @@ if(${ENABLE_LTO}) set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) endif() -# if os is macos, -if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") +# Apple Clang ABI compatibility flag (not needed on Windows or non-Apple Clang) +if (APPLE AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") message("macos detected") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-class-conversion -fclang-abi-compat=14") # need fclang-abi-compat for clang compiler, due to absl problem: https://github.com/protocolbuffers/protobuf/issues/12693 endif() -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated") -check_cxx_compiler_flag("-Wno-sign-compare" COMPILER_SUPPORTS_SIGN_COMPARE_FLAG) -check_cxx_compiler_flag("-Wno-deprecated-declarations" COMPILER_SUPPORTS_DEPRECATED_DECLARATIONS_FLAG) -check_cxx_compiler_flag("-Wno-attributes" COMPILER_SUPPORTS_ATTRIBUTES_FLAG) -check_cxx_compiler_flag("-Wno-error=stringop-overflow" COMPILER_SUPPORTS_STRINGOP_OVERFLOW_FLAG) -check_cxx_compiler_flag("-Wno-array-bounds" COMPILER_SUPPORTS_ARRAY_BOUNDS_FLAG) -check_cxx_compiler_flag("-Wno-deprecated-builtins" COMPILER_SUPPORTS_DEPRECATED_BUILTINS_FLAG) -if (COMPILER_SUPPORTS_SIGN_COMPARE_FLAG) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-sign-compare") -endif() -if (COMPILER_SUPPORTS_DEPRECATED_DECLARATIONS_FLAG) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations") -endif() -if (COMPILER_SUPPORTS_ATTRIBUTES_FLAG) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-attributes") -endif() -if (COMPILER_SUPPORTS_STRINGOP_OVERFLOW_FLAG) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error=stringop-overflow") -endif() -if (COMPILER_SUPPORTS_ARRAY_BOUNDS_FLAG) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-array-bounds") -endif() -if (COMPILER_SUPPORTS_DEPRECATED_BUILTINS_FLAG) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-builtins") + +if (NOT MSVC) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated") + check_cxx_compiler_flag("-Wno-sign-compare" COMPILER_SUPPORTS_SIGN_COMPARE_FLAG) + check_cxx_compiler_flag("-Wno-deprecated-declarations" COMPILER_SUPPORTS_DEPRECATED_DECLARATIONS_FLAG) + check_cxx_compiler_flag("-Wno-attributes" COMPILER_SUPPORTS_ATTRIBUTES_FLAG) + check_cxx_compiler_flag("-Wno-error=stringop-overflow" COMPILER_SUPPORTS_STRINGOP_OVERFLOW_FLAG) + check_cxx_compiler_flag("-Wno-array-bounds" COMPILER_SUPPORTS_ARRAY_BOUNDS_FLAG) + check_cxx_compiler_flag("-Wno-deprecated-builtins" COMPILER_SUPPORTS_DEPRECATED_BUILTINS_FLAG) + if (COMPILER_SUPPORTS_SIGN_COMPARE_FLAG) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-sign-compare") + endif() + if (COMPILER_SUPPORTS_DEPRECATED_DECLARATIONS_FLAG) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations") + endif() + if (COMPILER_SUPPORTS_ATTRIBUTES_FLAG) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-attributes") + endif() + if (COMPILER_SUPPORTS_STRINGOP_OVERFLOW_FLAG) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error=stringop-overflow") + endif() + if (COMPILER_SUPPORTS_ARRAY_BOUNDS_FLAG) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-array-bounds") + endif() + if (COMPILER_SUPPORTS_DEPRECATED_BUILTINS_FLAG) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-builtins") + endif() endif() macro(install_neug_target target) @@ -346,13 +407,26 @@ endif() if (WITH_MIMALLOC) set(MI_BUILD_TESTS OFF CACHE BOOL "Build mimalloc tests") - set(MI_BUILD_SHARED ON CACHE BOOL "Build mimalloc as shared library" FORCE) - set(MI_BUILD_STATIC OFF CACHE BOOL "Build mimalloc as static library") + if(WIN32) + # On Windows, build mimalloc as a static library to avoid DLL + # initialization issues (DllMain fails when loaded into Python). + # MI_OVERRIDE=OFF ensures static mimalloc does not override malloc. + set(MI_BUILD_SHARED OFF CACHE BOOL "Build mimalloc as shared library" FORCE) + set(MI_BUILD_STATIC ON CACHE BOOL "Build mimalloc as static library" FORCE) + else() + set(MI_BUILD_SHARED ON CACHE BOOL "Build mimalloc as shared library" FORCE) + set(MI_BUILD_STATIC OFF CACHE BOOL "Build mimalloc as static library") + endif() set(MI_OVERRIDE OFF CACHE BOOL "Override malloc with mimalloc" FORCE) set(MI_USE_LIBATOMIC OFF CACHE BOOL "Use libatomic") # we should not use static mimalloc, the reason is that static mimalloc will override the default malloc. add_subdirectory(third_party/mimalloc) - set_target_properties(mimalloc PROPERTIES DEBUG_POSTFIX "") + if(TARGET mimalloc) + set_target_properties(mimalloc PROPERTIES DEBUG_POSTFIX "") + endif() + if(TARGET mimalloc-static) + set_target_properties(mimalloc-static PROPERTIES DEBUG_POSTFIX "") + endif() include_directories(SYSTEM third_party/mimalloc/include) add_compile_definitions(NEUG_WITH_MIMALLOC) unset(PACKAGE_VERSION) # Unset PACKAGE_VERSION to avoid generate two PACKAGE_VERSION in config.h for libdwarf-lite. @@ -449,9 +523,16 @@ function(add_neug_test TEST_NAME) find_package(OpenSSL REQUIRED) set(SRCS ${ARGN}) add_executable(${TEST_NAME} ${SRCS}) - target_link_libraries(${TEST_NAME} PUBLIC neug::GTest neug - ${Protobuf_LIBRARIES} ${GLOG_LIBRARIES} ${ARROW_LIB} ${OPENSSL_LIBRARIES} - ${CRYPTO_LIB} ${YAML_CPP_LIBRARIES}) + if(WIN32 AND BUILD_TEST) + # neug_static contains the same object files as neug.dll but exposes all + # symbols, which is required by tests that exercise internal APIs. + target_link_libraries(${TEST_NAME} PUBLIC neug::GTest neug_static + ${ARROW_LIB} ${OPENSSL_LIBRARIES} ${CRYPTO_LIB}) + else() + target_link_libraries(${TEST_NAME} PUBLIC neug::GTest neug + ${Protobuf_LIBRARIES} ${GLOG_LIBRARIES} ${ARROW_LIB} ${OPENSSL_LIBRARIES} + ${CRYPTO_LIB} ${YAML_CPP_LIBRARIES}) + endif() # The bundled googletest (v1.13.0, fetched into _deps) must beat any # system/Homebrew gtest that leaks in via an -isystem include path; otherwise # tests compile against newer gtest headers but link the bundled library, @@ -469,7 +550,9 @@ function(add_neug_test TEST_NAME) endfunction() -set(TEST_PROGRAM " +# copy_file_range is a Linux-specific syscall; skip the probe on Windows. +if(NOT WIN32) + set(TEST_PROGRAM " #define _GNU_SOURCE #include #include @@ -482,22 +565,29 @@ int main() { } ") -check_cxx_source_compiles("${TEST_PROGRAM}" HAVE_COPY_FILE_RANGE_COMPILES) + check_cxx_source_compiles("${TEST_PROGRAM}" HAVE_COPY_FILE_RANGE_COMPILES) -if(NOT HAVE_COPY_FILE_RANGE_COMPILES) - message(WARNING "copy_file_range is not available or failed to compile!") -else() - message(STATUS "copy_file_range compilation succeeded.") -endif() + if(NOT HAVE_COPY_FILE_RANGE_COMPILES) + message(WARNING "copy_file_range is not available or failed to compile!") + else() + message(STATUS "copy_file_range compilation succeeded.") + endif() -if(HAVE_COPY_FILE_RANGE_COMPILES) - add_definitions(-DUSE_COPY_FILE_RANGE) + if(HAVE_COPY_FILE_RANGE_COMPILES) + add_definitions(-DUSE_COPY_FILE_RANGE) + endif() +else() + message(STATUS "copy_file_range is not available on Windows; skipping probe.") endif() find_package(Threads REQUIRED) find_package(OpenSSL) if (NOT OpenSSL_FOUND) - message(FATAL_ERROR "Fail to find OpenSSL, try to install openssl development package. On macos: brew install openssl@3; on ubuntu: apt-get install libssl-dev") + if(WIN32) + message(FATAL_ERROR "Fail to find OpenSSL. On Windows, install OpenSSL via vcpkg (openssl:x64-windows-static-md) or set OPENSSL_ROOT_DIR.") + else() + message(FATAL_ERROR "Fail to find OpenSSL, try to install openssl development package. On macos: brew install openssl@3; on ubuntu: apt-get install libssl-dev") + endif() endif() include_directories(SYSTEM ${OPENSSL_INCLUDE_DIR}) @@ -539,7 +629,9 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/third_party) # find Threads------------------------------------------------------------------ -set(CMAKE_THREAD_PREFER_PTHREAD ON) +if(NOT WIN32) + set(CMAKE_THREAD_PREFER_PTHREAD ON) +endif() find_package(Threads REQUIRED) # find gflags------------------------------------------------------------------- @@ -903,6 +995,10 @@ endif() if (ENABLE_BACKTRACES) set(COMPILER_LIBRARIES ${COMPILER_LIBRARIES} cpptrace::cpptrace) endif() +# Windows requires additional system libraries for sockets, security, etc. +if(WIN32) + set(COMPILER_LIBRARIES ${COMPILER_LIBRARIES} ws2_32 mswsock advapi32 shell32 bcrypt) +endif() message(STATUS "head: Compiler libraries: ${COMPILER_LIBRARIES}") set(MACOS_DYLIBS @@ -938,6 +1034,8 @@ endif() include_directories(SYSTEM third_party/expected) add_subdirectory(src) neug_apply_native_arch_to_directory("${CMAKE_CURRENT_SOURCE_DIR}/src") +# tools contains python_bind (gated by BUILD_PYTHON) and utils; +# allow on Windows so Python bindings can be built when requested. add_subdirectory(tools) if (BUILD_EXECUTABLES) add_subdirectory(bin) @@ -1054,7 +1152,11 @@ set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "neug is a embedded graph database") set(CPACK_PACKAGE_VENDOR "GraphScope") set(CPACK_PACKAGE_VERSION ${NEUG_VERSION_SEMVER}) set(CPACK_PACKAGE_RELEASE 1) -set(CPACK_GENERATOR "DEB") +if(WIN32) + set(CPACK_GENERATOR "ZIP") +else() + set(CPACK_GENERATOR "DEB") +endif() set(CPACK_PACKAGE_CONTACT "graphscope@alibaba-inc.com") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "graphscope") set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) @@ -1147,7 +1249,11 @@ if (WITH_MIMALLOC) ${PROJECT_SOURCE_DIR}/third_party/mimalloc/include/mimalloc-new-delete.h DESTINATION include ) - install_neug_target(mimalloc) + if(WIN32) + install_neug_target(mimalloc-static) + else() + install_neug_target(mimalloc) + endif() endif() install_neug_target(${GLOG_LIBRARIES}) diff --git a/cmake/BuildProtobufThirdParty.cmake b/cmake/BuildProtobufThirdParty.cmake index c659a5ac7..e2ec346f8 100644 --- a/cmake/BuildProtobufThirdParty.cmake +++ b/cmake/BuildProtobufThirdParty.cmake @@ -27,6 +27,7 @@ function (build_protobuf_as_third_party) set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build shared libraries" FORCE) set(protobuf_BUILD_SHARED_LIBS OFF CACHE BOOL "Build protobuf shared libraries" FORCE) + set(protobuf_MSVC_STATIC_RUNTIME OFF CACHE BOOL "Use dynamic C runtime for protobuf" FORCE) set(protobuf_BUILD_TESTS OFF CACHE BOOL "Build protobuf tests" FORCE) set(protobuf_BUILD_CONFORMANCE OFF CACHE BOOL "Build protobuf conformance tests") set(protobuf_BUILD_EXAMPLES OFF CACHE BOOL "Build protobuf examples") @@ -67,10 +68,12 @@ function (build_protobuf_as_third_party) # trigger this warning on GCC 13+. foreach(_proto_tgt libprotobuf libprotobuf-lite libprotoc ${protobuf_ABSL_USED_TARGETS}) if(TARGET ${_proto_tgt}) - target_compile_options(${_proto_tgt} PRIVATE - -Wno-sign-compare -Wno-deprecated-declarations -Wno-attributes -Wno-ignored-attributes - $<$:-Wno-stringop-overflow -Wno-stringop-overread -Wno-missing-requires> - ) + if(NOT MSVC) + target_compile_options(${_proto_tgt} PRIVATE + -Wno-sign-compare -Wno-deprecated-declarations -Wno-attributes -Wno-ignored-attributes + $<$:-Wno-stringop-overflow -Wno-stringop-overread -Wno-missing-requires> + ) + endif() endif() endforeach() # libprotobuf and friends default to hidden visibility when built as @@ -94,10 +97,12 @@ function (build_protobuf_as_third_party) message(STATUS "protobuf uses abseil-cpp targets: ${_absl_targets}") foreach(_absl_tgt IN LISTS _absl_targets) if(TARGET ${_absl_tgt}) - target_compile_options(${_absl_tgt} PRIVATE - -Wno-sign-compare -Wno-deprecated-declarations -Wno-attributes -Wno-ignored-attributes - $<$:-Wno-stringop-overflow -Wno-stringop-overread -Wno-missing-requires> - ) + if(NOT MSVC) + target_compile_options(${_absl_tgt} PRIVATE + -Wno-sign-compare -Wno-deprecated-declarations -Wno-attributes -Wno-ignored-attributes + $<$:-Wno-stringop-overflow -Wno-stringop-overread -Wno-missing-requires> + ) + endif() else() message(STATUS "Requested abseil target ${_absl_tgt} not built in this configuration, skipping warning suppression") endif() diff --git a/cmake/neug_symbol_visibility.cmake b/cmake/neug_symbol_visibility.cmake index fe907005e..6f49d63d4 100644 --- a/cmake/neug_symbol_visibility.cmake +++ b/cmake/neug_symbol_visibility.cmake @@ -18,7 +18,10 @@ # Apply to every non-test binary that ships alongside libneug.so/dylib. macro(neug_apply_symbol_visibility target) if(WIN32) - message(FATAL_ERROR "neug_apply_symbol_visibility: symbol visibility control is not supported on Windows.") + # Windows: rely on the NEUG_API macro (defined in include/neug/utils/api.h) + # to export only the public API. Exporting all symbols hits the linker + # limit of 65535 exports (LNK1189) for a library of this size. + set_target_properties(${target} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS OFF) elseif(APPLE) target_link_options(${target} PRIVATE "LINKER:-unexported_symbols_list,${CMAKE_SOURCE_DIR}/cmake/neug_unexported.sym") diff --git a/include/neug/common/columns/vertex_columns.h b/include/neug/common/columns/vertex_columns.h index 07e95aba0..db1070a7f 100644 --- a/include/neug/common/columns/vertex_columns.h +++ b/include/neug/common/columns/vertex_columns.h @@ -15,6 +15,7 @@ #pragma once #include "neug/common/types/i_context_column.h" +#include "neug/utils/platform.h" namespace neug { @@ -43,7 +44,7 @@ class IVertexColumn : public IContextColumn { return Value::VERTEX(this->get_vertex(idx)); } - __attribute__((always_inline)) const DataType& elem_type() const override { + NEUG_ALWAYS_INLINE const DataType& elem_type() const override { return type_; } @@ -76,7 +77,7 @@ class SLVertexColumn : public IVertexColumn { explicit SLVertexColumn(label_t label) : label_(label) {} ~SLVertexColumn() = default; - __attribute__((always_inline)) inline size_t size() const override { + NEUG_ALWAYS_INLINE inline size_t size() const override { return vertices_.size(); } @@ -96,8 +97,7 @@ class SLVertexColumn : public IVertexColumn { std::shared_ptr optional_shuffle( const sel_vec_t& offset) const override; - __attribute__((always_inline)) VertexRecord get_vertex( - size_t idx) const override { + NEUG_ALWAYS_INLINE VertexRecord get_vertex(size_t idx) const override { return {label_, vertices_[idx]}; } @@ -131,9 +131,9 @@ class SLVertexColumn : public IVertexColumn { return ret; } - __attribute__((always_inline)) label_t label() const { return label_; } + NEUG_ALWAYS_INLINE label_t label() const { return label_; } - __attribute__((always_inline)) const vector_t& vertices() const { + NEUG_ALWAYS_INLINE const vector_t& vertices() const { return vertices_; } @@ -149,7 +149,7 @@ class MSVertexColumn : public IVertexColumn { MSVertexColumn() = default; ~MSVertexColumn() = default; - __attribute__((always_inline)) size_t size() const override { + NEUG_ALWAYS_INLINE size_t size() const override { size_t ret = 0; for (auto& pair : vertices_) { ret += pair.second.size(); @@ -181,8 +181,7 @@ class MSVertexColumn : public IVertexColumn { std::shared_ptr optional_shuffle( const sel_vec_t& offsets) const override; - __attribute__((always_inline)) VertexRecord get_vertex( - size_t idx) const override { + NEUG_ALWAYS_INLINE VertexRecord get_vertex(size_t idx) const override { for (auto& pair : vertices_) { if (idx < pair.second.size()) { return {pair.first, pair.second[idx]}; @@ -194,11 +193,9 @@ class MSVertexColumn : public IVertexColumn { std::numeric_limits::max()}; } - __attribute__((always_inline)) bool is_optional() const override { - return is_optional_; - } + NEUG_ALWAYS_INLINE bool is_optional() const override { return is_optional_; } - __attribute__((always_inline)) bool has_value(size_t idx) const override { + NEUG_ALWAYS_INLINE bool has_value(size_t idx) const override { auto v = get_vertex(idx); return v.vid_ != std::numeric_limits::max(); } @@ -216,16 +213,13 @@ class MSVertexColumn : public IVertexColumn { std::set get_labels_set() const override { return labels_; } - __attribute__((always_inline)) size_t seg_num() const { - return vertices_.size(); - } + NEUG_ALWAYS_INLINE size_t seg_num() const { return vertices_.size(); } - __attribute__((always_inline)) label_t seg_label(size_t seg_id) const { + NEUG_ALWAYS_INLINE label_t seg_label(size_t seg_id) const { return vertices_[seg_id].first; } - __attribute__((always_inline)) const vector_t& seg_vertices( - size_t seg_id) const { + NEUG_ALWAYS_INLINE const vector_t& seg_vertices(size_t seg_id) const { return vertices_[seg_id].second; } @@ -266,7 +260,7 @@ class MSVertexColumnBuilder : public IVertexColumnBuilder { push_back_opt(v.vid_); } - __attribute__((always_inline)) void start_label(label_t label) { + NEUG_ALWAYS_INLINE void start_label(label_t label) { if (!cur_list_.empty() && cur_label_ != label && cur_label_ != std::numeric_limits::max()) { vertices_.emplace_back(cur_label_, std::move(cur_list_)); diff --git a/include/neug/common/types.h b/include/neug/common/types.h index 284048df2..9065f426b 100644 --- a/include/neug/common/types.h +++ b/include/neug/common/types.h @@ -23,10 +23,12 @@ #include #include + #include #include #include #include +#include "neug/utils/api.h" namespace common { class DataType; @@ -109,7 +111,7 @@ enum class DataTypeId : uint8_t { struct ExtraTypeInfo; -struct DataType { +struct NEUG_API DataType { DataType(); DataType(DataTypeId id); DataType(DataTypeId id, std::shared_ptr type_info); diff --git a/include/neug/compiler/binder/binder.h b/include/neug/compiler/binder/binder.h index f38016846..0d4b001c1 100644 --- a/include/neug/compiler/binder/binder.h +++ b/include/neug/compiler/binder/binder.h @@ -35,6 +35,7 @@ #include "neug/compiler/function/neug_call_function.h" #include "neug/compiler/parser/ddl/parsed_property_definition.h" #include "neug/compiler/parser/query/graph_pattern/pattern_element.h" +#include "neug/utils/api.h" namespace neug { struct VertexSchema; diff --git a/include/neug/compiler/binder/copy/bound_copy_from.h b/include/neug/compiler/binder/copy/bound_copy_from.h index 6200fe0aa..3d6e734be 100644 --- a/include/neug/compiler/binder/copy/bound_copy_from.h +++ b/include/neug/compiler/binder/copy/bound_copy_from.h @@ -28,6 +28,7 @@ #include "neug/compiler/binder/ddl/bound_create_table_info.h" #include "neug/compiler/binder/expression/expression.h" #include "neug/compiler/binder/expression_binder.h" +#include "neug/compiler/catalog/catalog_entry/node_table_catalog_entry.h" #include "neug/compiler/catalog/catalog_entry/table_catalog_entry.h" #include "neug/compiler/common/enums/column_evaluate_type.h" #include "neug/compiler/common/types/types.h" @@ -62,6 +63,7 @@ struct NEUG_API DDLVertexInfo : public DDLTableInfo { const std::string& primaryKeyName, const expression_vector& columns, ExpressionBinder& binder, bool temporary = false); + ~DDLVertexInfo(); // return vertex label name std::string getVertexLabelName(); diff --git a/include/neug/compiler/binder/expression_evaluator_utils.h b/include/neug/compiler/binder/expression_evaluator_utils.h index fedc09754..3e7c6b276 100644 --- a/include/neug/compiler/binder/expression_evaluator_utils.h +++ b/include/neug/compiler/binder/expression_evaluator_utils.h @@ -25,6 +25,7 @@ #include "neug/compiler/binder/expression/expression.h" #include "neug/compiler/common/types/value/value.h" #include "neug/compiler/main/client_context.h" +#include "neug/utils/api.h" namespace neug { namespace evaluator { diff --git a/include/neug/compiler/common/enums/delete_type.h b/include/neug/compiler/common/enums/delete_type.h index 8c2907aee..c2f6f4a51 100644 --- a/include/neug/compiler/common/enums/delete_type.h +++ b/include/neug/compiler/common/enums/delete_type.h @@ -28,7 +28,7 @@ namespace neug { namespace common { enum class DeleteNodeType : uint8_t { - DELETE = 0, + DELETE_NODE = 0, DETACH_DELETE = 1, }; diff --git a/include/neug/compiler/common/enums/explain_type.h b/include/neug/compiler/common/enums/explain_type.h index ff1c75ff8..a15f9a327 100644 --- a/include/neug/compiler/common/enums/explain_type.h +++ b/include/neug/compiler/common/enums/explain_type.h @@ -24,6 +24,11 @@ #include +// Windows headers may define NONE as a macro; undefine it. +#ifdef NONE +#undef NONE +#endif + namespace neug { namespace common { diff --git a/include/neug/compiler/common/types/timestamp_t.h b/include/neug/compiler/common/types/timestamp_t.h index d60fb7c0d..a3243f03a 100644 --- a/include/neug/compiler/common/types/timestamp_t.h +++ b/include/neug/compiler/common/types/timestamp_t.h @@ -24,6 +24,7 @@ #include "date_t.h" #include "dtime_t.h" +#include "neug/utils/api.h" namespace neug { namespace compiler_impl { diff --git a/include/neug/compiler/function/arithmetic/arithmetic_functions.h b/include/neug/compiler/function/arithmetic/arithmetic_functions.h index ae72562e2..f9f606b00 100644 --- a/include/neug/compiler/function/arithmetic/arithmetic_functions.h +++ b/include/neug/compiler/function/arithmetic/arithmetic_functions.h @@ -24,6 +24,11 @@ #include +// MSVC does not define M_PI by default; define it if missing. +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + #include "neug/compiler/common/types/int128_t.h" namespace neug { diff --git a/include/neug/compiler/function/read_function.h b/include/neug/compiler/function/read_function.h index cfc8c74e1..61bcbb380 100644 --- a/include/neug/compiler/function/read_function.h +++ b/include/neug/compiler/function/read_function.h @@ -16,7 +16,9 @@ #pragma once +#ifndef _WIN32 #include +#endif #include #include #include diff --git a/include/neug/compiler/gopt/g_physical_analyzer.h b/include/neug/compiler/gopt/g_physical_analyzer.h index 3db560159..adb9b663c 100644 --- a/include/neug/compiler/gopt/g_physical_analyzer.h +++ b/include/neug/compiler/gopt/g_physical_analyzer.h @@ -210,7 +210,7 @@ class GPhysicalAnalyzer { break; } case planner::LogicalOperatorType::SET_PROPERTY: - case planner::LogicalOperatorType::DELETE: + case planner::LogicalOperatorType::DELETE_OP: case planner::LogicalOperatorType::MERGE: { flag.update = true; break; diff --git a/include/neug/compiler/gopt/g_physical_convertor.h b/include/neug/compiler/gopt/g_physical_convertor.h index ce6c012d8..3933ae23c 100644 --- a/include/neug/compiler/gopt/g_physical_convertor.h +++ b/include/neug/compiler/gopt/g_physical_convertor.h @@ -77,7 +77,7 @@ class GPhysicalConvertor { op->getOperatorType() == planner::LogicalOperatorType::MERGE || op->getOperatorType() == planner::LogicalOperatorType::SET_PROPERTY || - op->getOperatorType() == planner::LogicalOperatorType::DELETE; + op->getOperatorType() == planner::LogicalOperatorType::DELETE_OP; } bool ddlClause(std::shared_ptr op) { diff --git a/include/neug/compiler/gopt/g_result_schema.h b/include/neug/compiler/gopt/g_result_schema.h index 58aa21475..99bd42323 100644 --- a/include/neug/compiler/gopt/g_result_schema.h +++ b/include/neug/compiler/gopt/g_result_schema.h @@ -97,7 +97,7 @@ class GResultSchema { opType == planner::LogicalOperatorType::INSERT || opType == planner::LogicalOperatorType::MERGE || opType == planner::LogicalOperatorType::SET_PROPERTY || - opType == planner::LogicalOperatorType::DELETE || + opType == planner::LogicalOperatorType::DELETE_OP || opType == planner::LogicalOperatorType::COPY_TO || opType == planner::LogicalOperatorType::TRANSACTION || opType == planner::LogicalOperatorType::EXTENSION || diff --git a/include/neug/compiler/planner/operator/logical_operator.h b/include/neug/compiler/planner/operator/logical_operator.h index 87e51558a..7bac822cc 100644 --- a/include/neug/compiler/planner/operator/logical_operator.h +++ b/include/neug/compiler/planner/operator/logical_operator.h @@ -23,7 +23,7 @@ enum class LogicalOperatorType : uint8_t { CREATE_TABLE, CREATE_TYPE, CROSS_PRODUCT, - DELETE, + DELETE_OP, DETACH_DATABASE, DISTINCT, DROP, diff --git a/include/neug/compiler/planner/operator/persistent/logical_delete.h b/include/neug/compiler/planner/operator/persistent/logical_delete.h index b36c759be..e3c61a42e 100644 --- a/include/neug/compiler/planner/operator/persistent/logical_delete.h +++ b/include/neug/compiler/planner/operator/persistent/logical_delete.h @@ -22,7 +22,7 @@ struct LogicalDeletePrintInfo final : OPPrintInfo { }; class LogicalDelete final : public LogicalOperator { - static constexpr LogicalOperatorType type_ = LogicalOperatorType::DELETE; + static constexpr LogicalOperatorType type_ = LogicalOperatorType::DELETE_OP; public: LogicalDelete(std::vector infos, diff --git a/include/neug/compiler/planner/operator/sip/side_way_info_passing.h b/include/neug/compiler/planner/operator/sip/side_way_info_passing.h index 5a43fd78c..87f2a7b86 100644 --- a/include/neug/compiler/planner/operator/sip/side_way_info_passing.h +++ b/include/neug/compiler/planner/operator/sip/side_way_info_passing.h @@ -2,6 +2,11 @@ #include +// Windows headers may define NONE as a macro; undefine it. +#ifdef NONE +#undef NONE +#endif + namespace neug { namespace planner { diff --git a/include/neug/compiler/planner/planner.h b/include/neug/compiler/planner/planner.h index c910606d3..bf3456b10 100644 --- a/include/neug/compiler/planner/planner.h +++ b/include/neug/compiler/planner/planner.h @@ -30,6 +30,14 @@ namespace planner { struct LogicalInsertInfo; +// Windows headers may define NONE and OPTIONAL as macros; undefine them. +#ifdef NONE +#undef NONE +#endif +#ifdef OPTIONAL +#undef OPTIONAL +#endif + enum class SubqueryPlanningType : uint8_t { NONE = 0, UNNEST_CORRELATED = 1, diff --git a/include/neug/compiler/processor/operator/persistent/reader/copy_from_error.h b/include/neug/compiler/processor/operator/persistent/reader/copy_from_error.h index f6f09ab94..5964dc60f 100644 --- a/include/neug/compiler/processor/operator/persistent/reader/copy_from_error.h +++ b/include/neug/compiler/processor/operator/persistent/reader/copy_from_error.h @@ -56,7 +56,8 @@ struct NEUG_API WarningSourceData { static constexpr size_t NUM_BLOCK_VALUES = 2; WarningSourceData() : WarningSourceData(0) {} - explicit WarningSourceData(uint64_t numSourceSpecificValues); + explicit WarningSourceData(uint64_t numSourceSpecificValues) + : numValues(numSourceSpecificValues) {} template void dumpTo(uint64_t& blockIdx, uint32_t& offsetInBlock, diff --git a/include/neug/compiler/processor/operator/physical_operator.h b/include/neug/compiler/processor/operator/physical_operator.h index 9c47fc2fa..64ce313bd 100644 --- a/include/neug/compiler/processor/operator/physical_operator.h +++ b/include/neug/compiler/processor/operator/physical_operator.h @@ -162,7 +162,7 @@ class NEUG_API PhysicalOperator { bool getNextTuple(ExecutionContext* context); - virtual void finalize(ExecutionContext* context); + virtual void finalize(ExecutionContext* context) {} std::unordered_map getProfilerKeyValAttributes( common::Profiler& profiler) const; @@ -173,7 +173,7 @@ class NEUG_API PhysicalOperator { virtual std::unique_ptr copy() = 0; - virtual double getProgress(ExecutionContext* context) const; + virtual double getProgress(ExecutionContext* context) const { return 0.0; } template TARGET* ptrCast() { diff --git a/include/neug/compiler/storage/predicate/column_predicate.h b/include/neug/compiler/storage/predicate/column_predicate.h index ccbe7a960..31c4ef16c 100644 --- a/include/neug/compiler/storage/predicate/column_predicate.h +++ b/include/neug/compiler/storage/predicate/column_predicate.h @@ -67,7 +67,7 @@ class NEUG_API ColumnPredicate { virtual common::ZoneMapCheckResult checkZoneMap( const MergedColumnChunkStats& stats) const = 0; - virtual std::string toString(); + virtual std::string toString() { return columnName; } virtual std::unique_ptr copy() const = 0; diff --git a/include/neug/compiler/storage/store/table.h b/include/neug/compiler/storage/store/table.h index e8fd9a3c3..cdf946f86 100644 --- a/include/neug/compiler/storage/store/table.h +++ b/include/neug/compiler/storage/store/table.h @@ -44,7 +44,13 @@ class NEUG_API Table { tableName{const_cast(tableEntry)->get_label()} {} Table(const SchemaEntry* tableEntry, const GraphStats* storageManager, - MemoryManager* memoryManager); + MemoryManager* memoryManager) + : tableType{tableEntry->get_entry_type()}, + tableID{tableEntry->get_entry_id()}, + tableName{const_cast(tableEntry)->get_label()}, + enableCompression{false}, + memoryManager{memoryManager}, + hasChanges{false} {} virtual ~Table() = default; SchemaEntryType getTableType() const { return tableType; } diff --git a/include/neug/execution/common/operators/retrieve/edge_expand.h b/include/neug/execution/common/operators/retrieve/edge_expand.h index 5ce519d7f..f69a3bed8 100644 --- a/include/neug/execution/common/operators/retrieve/edge_expand.h +++ b/include/neug/execution/common/operators/retrieve/edge_expand.h @@ -20,6 +20,7 @@ #include "neug/execution/common/params_map.h" #include "neug/execution/expression/special_predicates.h" #include "neug/execution/utils/params.h" +#include "neug/utils/platform.h" #include "neug/utils/result.h" namespace neug { diff --git a/include/neug/main/connection.h b/include/neug/main/connection.h index a2ea5a758..c7f40a995 100644 --- a/include/neug/main/connection.h +++ b/include/neug/main/connection.h @@ -25,6 +25,8 @@ #include "neug/execution/common/params_map.h" #include "neug/main/query_result.h" +#include "neug/storages/graph_snapshot_store.h" +#include "neug/utils/api.h" #include "neug/utils/result.h" namespace neug { @@ -80,7 +82,7 @@ class ExecutionSlot; * * @since v0.1.0 */ -class Connection { +class NEUG_API Connection { public: using CloseCallback = std::function; diff --git a/include/neug/main/neug_db.h b/include/neug/main/neug_db.h index dc578f929..48ae3574a 100644 --- a/include/neug/main/neug_db.h +++ b/include/neug/main/neug_db.h @@ -37,6 +37,7 @@ #include "neug/transaction/insert_transaction.h" #include "neug/transaction/read_transaction.h" #include "neug/transaction/update_transaction.h" +#include "neug/utils/api.h" #include "neug/utils/property/types.h" #include "neug/version.h" @@ -112,7 +113,7 @@ class ExecutionSlot; * * @since v0.1.0 */ -class NeugDB { +class NEUG_API NeugDB { public: NeugDB(); ~NeugDB(); diff --git a/include/neug/main/query_result.h b/include/neug/main/query_result.h index f54385bd9..4af0f007d 100644 --- a/include/neug/main/query_result.h +++ b/include/neug/main/query_result.h @@ -23,6 +23,7 @@ #include "glog/logging.h" #include "neug/generated/proto/response/response.pb.h" +#include "neug/utils/api.h" namespace neug { /** @@ -38,7 +39,7 @@ namespace neug { * - typed cell access via `GetInt32()`, `GetString()`, etc. */ -class QueryResult { +class NEUG_API QueryResult { public: static QueryResult From(std::string&& serialized_table); static QueryResult From(const std::string& serialized_table); diff --git a/include/neug/storages/checkpoint_manager.h b/include/neug/storages/checkpoint_manager.h index 5db6ff6e0..05a94af34 100644 --- a/include/neug/storages/checkpoint_manager.h +++ b/include/neug/storages/checkpoint_manager.h @@ -20,6 +20,7 @@ #include #include "neug/storages/checkpoint.h" +#include "neug/utils/api.h" namespace neug { @@ -47,7 +48,7 @@ namespace neug { * an internal mutex). Callers that race CreateStagingCheckpoint() / Close() * must coordinate externally. */ -class CheckpointManager { +class NEUG_API CheckpointManager { public: class StagingCheckpoint { public: diff --git a/include/neug/storages/csr/csr_view.h b/include/neug/storages/csr/csr_view.h index 659bfdb52..e5f386685 100644 --- a/include/neug/storages/csr/csr_view.h +++ b/include/neug/storages/csr/csr_view.h @@ -19,6 +19,7 @@ #include "neug/common/types/value.h" #include "neug/storages/csr/nbr.h" #include "neug/storages/csr/prefetch_utils.h" +#include "neug/utils/platform.h" #include "neug/utils/property/column.h" #include "neug/utils/property/types.h" @@ -115,7 +116,7 @@ struct NbrIterator { inline vid_t operator*() const { return get_vertex(); } /** @brief Advance to next visible neighbor. */ - __attribute__((always_inline)) NbrIterator& operator++() { + NEUG_ALWAYS_INLINE NbrIterator& operator++() { cur = static_cast(cur) + cfg.stride; while (cur != end && get_timestamp() > timestamp) { cur = static_cast(cur) + cfg.stride; @@ -124,18 +125,18 @@ struct NbrIterator { } /** @brief Advance by n positions. */ - __attribute__((always_inline)) NbrIterator& operator+=(size_t n) { + NEUG_ALWAYS_INLINE NbrIterator& operator+=(size_t n) { for (size_t i = 0; i < n; ++i) { ++(*this); } return *this; } - __attribute__((always_inline)) bool operator==(const NbrIterator& rhs) const { + NEUG_ALWAYS_INLINE bool operator==(const NbrIterator& rhs) const { return (cur == rhs.cur); } - __attribute__((always_inline)) bool operator!=(const NbrIterator& rhs) const { + NEUG_ALWAYS_INLINE bool operator!=(const NbrIterator& rhs) const { return (cur != rhs.cur); } @@ -210,14 +211,14 @@ struct NbrList { ~NbrList() = default; /** @brief Get iterator to first visible neighbor. */ - __attribute__((always_inline)) NbrIterator begin() const { + NEUG_ALWAYS_INLINE NbrIterator begin() const { NbrIterator it; it.init(start_ptr, end_ptr, cfg, timestamp); return it; } /** @brief Get iterator to past-the-end position. */ - __attribute__((always_inline)) NbrIterator end() const { + NEUG_ALWAYS_INLINE NbrIterator end() const { NbrIterator it; it.init(end_ptr, end_ptr, cfg, timestamp); return it; @@ -615,7 +616,7 @@ struct CsrView { * @note This is the primary method for graph traversal. * @note Empty NbrList is returned if vertex has no edges. */ - __attribute__((always_inline)) NbrList get_edges(vid_t v) const { + NEUG_ALWAYS_INLINE NbrList get_edges(vid_t v) const { NbrList ret; if (degrees_ == nullptr) { const char* start_ptr = adjlists_ + v * cfg_.stride; @@ -663,15 +664,15 @@ struct CsrView { } } - __attribute__((always_inline)) size_t prefetch_metadata_dist() const { + NEUG_ALWAYS_INLINE size_t prefetch_metadata_dist() const { return prefetch_policy_.metadata_distance; } - __attribute__((always_inline)) size_t prefetch_head_dist() const { + NEUG_ALWAYS_INLINE size_t prefetch_head_dist() const { return prefetch_policy_.head_distance; } - __attribute__((always_inline)) void prefetch_metadata(vid_t v) const { + NEUG_ALWAYS_INLINE void prefetch_metadata(vid_t v) const { if (degrees_ == nullptr) { const char* start_ptr = adjlists_ + v * cfg_.stride; prefetch_read(start_ptr, prefetch_policy_.metadata_locality); @@ -682,7 +683,7 @@ struct CsrView { } } - __attribute__((always_inline)) void prefetch_head(vid_t v) const { + NEUG_ALWAYS_INLINE void prefetch_head(vid_t v) const { if (degrees_ == nullptr) { const char* start_ptr = adjlists_ + v * cfg_.stride; prefetch_read(start_ptr, prefetch_policy_.head_locality); @@ -696,8 +697,8 @@ struct CsrView { } private: - __attribute__((always_inline)) static void prefetch_read(const void* ptr, - uint8_t locality) { + NEUG_ALWAYS_INLINE static void prefetch_read(const void* ptr, + uint8_t locality) { switch (locality) { case 0: __builtin_prefetch(ptr, 0, 0); @@ -722,7 +723,7 @@ struct CsrView { }; template -__attribute__((always_inline)) static inline void prefetch_next_vertex( +NEUG_ALWAYS_INLINE static inline void prefetch_next_vertex( const CsrView& view, const VECTOR_T& vertices, size_t idx) { size_t metadata_dist = view.prefetch_metadata_dist(); if (metadata_dist != 0 && idx + metadata_dist < vertices.size()) { @@ -735,7 +736,7 @@ __attribute__((always_inline)) static inline void prefetch_next_vertex( } template -__attribute__((always_inline)) static inline void prefetch_next_vertex_column( +NEUG_ALWAYS_INLINE static inline void prefetch_next_vertex_column( const CsrView& view, const VERTEX_COLUMN_T& vertices, size_t idx) { size_t metadata_dist = view.prefetch_metadata_dist(); if (metadata_dist != 0 && idx + metadata_dist < vertices.size()) { diff --git a/include/neug/storages/csr/immutable_csr.h b/include/neug/storages/csr/immutable_csr.h index bf9e51670..530f2ee53 100644 --- a/include/neug/storages/csr/immutable_csr.h +++ b/include/neug/storages/csr/immutable_csr.h @@ -26,12 +26,13 @@ #include "neug/storages/csr/csr_view.h" #include "neug/storages/csr/nbr.h" #include "neug/storages/module/type_name.h" +#include "neug/utils/api.h" #include "neug/utils/property/types.h" namespace neug { template -class ImmutableCsr : public TypedCsrBase { +class NEUG_API ImmutableCsr : public TypedCsrBase { public: using data_t = EDATA_T; using nbr_t = ImmutableNbr; @@ -142,7 +143,7 @@ class ImmutableCsr : public TypedCsrBase { }; template -class SingleImmutableCsr : public TypedCsrBase { +class NEUG_API SingleImmutableCsr : public TypedCsrBase { public: using data_t = EDATA_T; using nbr_t = ImmutableNbr; diff --git a/include/neug/storages/graph/edge_table.h b/include/neug/storages/graph/edge_table.h index a32dd6007..c0c76a1a3 100644 --- a/include/neug/storages/graph/edge_table.h +++ b/include/neug/storages/graph/edge_table.h @@ -30,6 +30,7 @@ #include "neug/storages/csr/csr_view.h" #include "neug/storages/graph/schema.h" #include "neug/storages/module/module.h" +#include "neug/utils/api.h" #include "neug/utils/indexers.h" #include "neug/utils/property/table.h" #include "neug/utils/property/types.h" @@ -42,12 +43,14 @@ class PropertyGraph; class IDataChunkSupplier; -class EdgeTable { +class NEUG_API EdgeTable { public: EdgeTable(std::shared_ptr meta) : meta_(meta) {} EdgeTable(EdgeTable&& edge_table); EdgeTable(const EdgeTable&) = delete; + EdgeTable& operator=(const EdgeTable&) = delete; + EdgeTable& operator=(EdgeTable&& other) noexcept; ~EdgeTable() = default; void Swap(EdgeTable& other); diff --git a/include/neug/storages/graph/property_graph.h b/include/neug/storages/graph/property_graph.h index 90d1c1033..01403e24d 100644 --- a/include/neug/storages/graph/property_graph.h +++ b/include/neug/storages/graph/property_graph.h @@ -37,6 +37,7 @@ #include "neug/storages/graph/operation_params.h" #include "neug/storages/graph/schema.h" #include "neug/storages/graph/vertex_table.h" +#include "neug/utils/api.h" #include "neug/utils/exception/exception.h" #include "neug/utils/property/types.h" #include "neug/utils/result.h" @@ -98,7 +99,7 @@ class StorageIndexManager; * * @since v0.1.0 */ -class PropertyGraph { +class NEUG_API PropertyGraph { public: /** * @brief Construct PropertyGraph with default settings. @@ -117,6 +118,16 @@ class PropertyGraph { */ ~PropertyGraph(); + // Move-only: copy is not meaningful because the underlying storage + // (VertexTable, EdgeTable) is move-only. Use Clone() for explicit copies. + // Move operations are defaulted in the .cc where StorageIndexManager is + // complete; defaulting them here would require the full type to destroy + // index_manager_ in every including TU (MSVC C2027). + PropertyGraph(const PropertyGraph&) = delete; + PropertyGraph& operator=(const PropertyGraph&) = delete; + PropertyGraph(PropertyGraph&&) noexcept; + PropertyGraph& operator=(PropertyGraph&&) noexcept; + /** * @brief Open the graph from the given Checkpoint using the Module interface. * diff --git a/include/neug/storages/graph/schema.h b/include/neug/storages/graph/schema.h index d5e1cf092..a92afac4c 100644 --- a/include/neug/storages/graph/schema.h +++ b/include/neug/storages/graph/schema.h @@ -27,6 +27,7 @@ #include #include #include "neug/common/types/value.h" +#include "neug/utils/api.h" #include "neug/utils/bitset.h" #include "neug/utils/property/default_value.h" #include "neug/utils/property/property_definition.h" @@ -90,7 +91,7 @@ class SchemaEntry { virtual std::string get_label() const = 0; }; -class LabelIndexer { +class NEUG_API LabelIndexer { public: bool add(const std::string& name, label_t& lid); bool get_index(const std::string& name, label_t& lid) const; @@ -480,7 +481,7 @@ struct EdgeSchema : public SchemaEntry { * * @since v0.1.0 */ -class Schema { +class NEUG_API Schema { public: /// @name Plugin ID Constants /// @{ diff --git a/include/neug/storages/graph/vertex_table.h b/include/neug/storages/graph/vertex_table.h index e6212d9bf..1eafe4aa5 100644 --- a/include/neug/storages/graph/vertex_table.h +++ b/include/neug/storages/graph/vertex_table.h @@ -20,6 +20,7 @@ #include "neug/storages/graph/vertex_timestamp.h" #include "neug/storages/loader/loader_utils.h" #include "neug/storages/module/module.h" +#include "neug/utils/api.h" #include "neug/utils/indexers.h" #include "neug/utils/property/table.h" @@ -88,7 +89,7 @@ class VertexSet { }; class PropertyGraph; -class VertexTable { +class NEUG_API VertexTable { public: VertexTable() : ckp_(nullptr), @@ -121,6 +122,20 @@ class VertexTable { memory_level_(other.memory_level_) {} VertexTable(const VertexTable&) = delete; + VertexTable& operator=(const VertexTable&) = delete; + + VertexTable& operator=(VertexTable&& other) noexcept { + if (this != &other) { + ckp_ = std::move(other.ckp_); + indexer_ = std::move(other.indexer_); + table_ = std::move(other.table_); + pk_type_ = other.pk_type_; + vertex_schema_ = other.vertex_schema_; + v_ts_ = std::move(other.v_ts_); + memory_level_ = other.memory_level_; + } + return *this; + } void Swap(VertexTable& other) { std::swap(ckp_, other.ckp_); diff --git a/include/neug/storages/graph/vertex_timestamp.h b/include/neug/storages/graph/vertex_timestamp.h index 6287cd8d3..aff40d7fb 100644 --- a/include/neug/storages/graph/vertex_timestamp.h +++ b/include/neug/storages/graph/vertex_timestamp.h @@ -21,12 +21,13 @@ #include #include "neug/storages/module/module.h" +#include "neug/utils/api.h" #include "neug/utils/likely.h" #include "neug/utils/property/types.h" namespace neug { -class VertexTimestamp : public Module { +class NEUG_API VertexTimestamp : public Module { public: static constexpr timestamp_t DELETED_TIMESTAMP = std::numeric_limits::max(); diff --git a/include/neug/storages/graph_snapshot_store.h b/include/neug/storages/graph_snapshot_store.h index 8a1c0b8fa..9f340d23c 100644 --- a/include/neug/storages/graph_snapshot_store.h +++ b/include/neug/storages/graph_snapshot_store.h @@ -23,6 +23,7 @@ #include "neug/storages/graph/graph_view.h" #include "neug/storages/graph/property_graph.h" +#include "neug/utils/api.h" #include "neug/utils/result.h" namespace neug { @@ -48,7 +49,7 @@ namespace neug { * - PublishSnapshot publishes the new slot BEFORE VersionManager advances * read_ts_, so readers never see "new ts + old slot". */ -class GraphSnapshotStore { +class NEUG_API GraphSnapshotStore { public: /// A slot holding a PropertyGraph, its GraphView, and a pin count. class SnapshotSlot { diff --git a/include/neug/storages/module/module.h b/include/neug/storages/module/module.h index 633c3ac11..8b3ea1dee 100644 --- a/include/neug/storages/module/module.h +++ b/include/neug/storages/module/module.h @@ -20,6 +20,7 @@ #include "neug/config.h" #include "neug/storages/checkpoint_manifest.h" #include "neug/storages/module_descriptor.h" +#include "neug/utils/api.h" namespace neug { @@ -32,7 +33,7 @@ class Checkpoint; * Clone (zero-copy COW clone), and Detach (detach * shared storage before mutation). */ -class Module { +class NEUG_API Module { public: virtual ~Module() = default; diff --git a/include/neug/storages/module/module_factory.h b/include/neug/storages/module/module_factory.h index 403823cdb..d70d8bc7b 100644 --- a/include/neug/storages/module/module_factory.h +++ b/include/neug/storages/module/module_factory.h @@ -69,11 +69,22 @@ class ModuleFactory { * * Usage: Place REGISTER_MODULE(MyModule) in the source file after class * definition. + * + * On GCC/Clang we use __attribute__((constructor)) which runs the function + * before main(). On MSVC this attribute is not supported, so we use a static + * variable whose dynamic initializer performs the registration. The comma- + * expression returns 0 so the variable can be a plain int. */ +#ifdef _WIN32 +#define NEUG_REGISTER_MODULE(Class) \ + static int _register_##Class = \ + (ModuleFactory::instance().Register(), 0); +#else #define NEUG_REGISTER_MODULE(Class) \ __attribute__((constructor)) static void _register_##Class() { \ ModuleFactory::instance().Register(); \ } +#endif /** * @brief Macro for registering template module instantiations. @@ -82,11 +93,17 @@ class ModuleFactory { * Works with types containing '::' (e.g. std::string_view) by using * __COUNTER__ for a stable, type-name-independent function identifier. */ +#ifdef _WIN32 +#define NEUG_REGISTER_TEMPLATE_MODULE_IMPL(TemplateClass, T, Counter) \ + static int _register_##TemplateClass##_##Counter = \ + (ModuleFactory::instance().Register>(), 0); +#else #define NEUG_REGISTER_TEMPLATE_MODULE_IMPL(TemplateClass, T, Counter) \ __attribute__( \ (constructor)) static void _register_##TemplateClass##_##Counter() { \ ModuleFactory::instance().Register>(); \ } +#endif // Indirection layer: N is not adjacent to ##, so __COUNTER__ is fully expanded // here before being passed to IMPL (where it would otherwise be pasted raw). diff --git a/include/neug/utils/bitset.h b/include/neug/utils/bitset.h index d9d4a677d..48da33099 100644 --- a/include/neug/utils/bitset.h +++ b/include/neug/utils/bitset.h @@ -25,6 +25,11 @@ #include +#ifdef _WIN32 +#include +#include +#endif + #define WORD_SIZE(n) (((n) + 63ul) >> 6) #define NEUG_BYTE_SIZE(n) (WORD_SIZE(n) * sizeof(uint64_t)) @@ -39,6 +44,51 @@ (((i) + (alignment) -1) & (~((alignment) -1))) namespace neug { +namespace detail { + +inline void* AlignedAlloc(size_t alignment, size_t size) { +#ifdef _WIN32 + return _aligned_malloc(size, alignment); +#else + return aligned_alloc(alignment, size); +#endif +} + +inline void AlignedFree(void* ptr) { +#ifdef _WIN32 + _aligned_free(ptr); +#else + free(ptr); +#endif +} + +inline uint64_t AtomicFetchOr(uint64_t* ptr, uint64_t value) { +#ifdef _WIN32 + return static_cast(_InterlockedOr64( + reinterpret_cast(ptr), static_cast<__int64>(value))); +#else + return __sync_fetch_and_or(ptr, value); +#endif +} + +inline uint64_t AtomicFetchAnd(uint64_t* ptr, uint64_t value) { +#ifdef _WIN32 + return static_cast(_InterlockedAnd64( + reinterpret_cast(ptr), static_cast<__int64>(value))); +#else + return __sync_fetch_and_and(ptr, value); +#endif +} + +inline int PopCountLL(uint64_t value) { +#ifdef _WIN32 + return static_cast(__popcnt64(value)); +#else + return __builtin_popcountll(value); +#endif +} + +} // namespace detail class Bitset { static constexpr size_t kAlignment = 64; // 64-byte alignment @@ -62,7 +112,8 @@ class Bitset { assert(kAlignment <= capacity_in_words_ * sizeof(uint64_t)); auto alloc_size = ROUND_UP_TO_ALIGNMENT( capacity_in_words_ * sizeof(uint64_t), kAlignment); - data_ = static_cast(aligned_alloc(kAlignment, alloc_size)); + data_ = + static_cast(detail::AlignedAlloc(kAlignment, alloc_size)); memcpy(data_, other.data_, capacity_in_words_ * sizeof(uint64_t)); } } @@ -82,7 +133,7 @@ class Bitset { ~Bitset() { if (data_ != nullptr) { - free(data_); + detail::AlignedFree(data_); } } @@ -92,7 +143,7 @@ class Bitset { } if (data_ != nullptr) { - free(data_); + detail::AlignedFree(data_); } size_ = other.size_; @@ -106,7 +157,8 @@ class Bitset { assert(kAlignment <= capacity_in_words_ * sizeof(uint64_t)); auto alloc_size = ROUND_UP_TO_ALIGNMENT( capacity_in_words_ * sizeof(uint64_t), kAlignment); - data_ = static_cast(aligned_alloc(kAlignment, alloc_size)); + data_ = + static_cast(detail::AlignedAlloc(kAlignment, alloc_size)); memcpy(data_, other.data_, capacity_in_words_ * sizeof(uint64_t)); } return *this; @@ -118,7 +170,7 @@ class Bitset { } if (data_ != nullptr) { - free(data_); + detail::AlignedFree(data_); } data_ = other.data_; @@ -148,10 +200,10 @@ class Bitset { size_t alloc_size = ROUND_UP_TO_ALIGNMENT(new_cap_in_words * sizeof(uint64_t), kAlignment); uint64_t* new_data = - static_cast(aligned_alloc(kAlignment, alloc_size)); + static_cast(detail::AlignedAlloc(kAlignment, alloc_size)); if (data_ != nullptr) { memcpy(new_data, data_, size_in_words_ * sizeof(uint64_t)); - free(data_); + detail::AlignedFree(data_); } data_ = new_data; capacity_ = cap; @@ -178,7 +230,7 @@ class Bitset { (new_size_in_words - size_in_words_) * sizeof(uint64_t)); if (size_in_words_ && BIT_OFFSET(size_) != 0) { - uint64_t mask = ((1ul << BIT_OFFSET(size_)) - 1); + uint64_t mask = ((1ull << BIT_OFFSET(size_)) - 1); data_[size_in_words_ - 1] &= mask; } @@ -186,40 +238,40 @@ class Bitset { size_in_words_ = new_size_in_words; } - void set(size_t i) { data_[WORD_INDEX(i)] |= (1ul << BIT_OFFSET(i)); } + void set(size_t i) { data_[WORD_INDEX(i)] |= (1ull << BIT_OFFSET(i)); } - void reset(size_t i) { data_[WORD_INDEX(i)] &= (~(1ul << BIT_OFFSET(i))); } + void reset(size_t i) { data_[WORD_INDEX(i)] &= (~(1ull << BIT_OFFSET(i))); } void atomic_set(size_t i) { - uint64_t mask = 1ul << BIT_OFFSET(i); - __sync_fetch_and_or(data_ + WORD_INDEX(i), mask); + uint64_t mask = 1ull << BIT_OFFSET(i); + detail::AtomicFetchOr(data_ + WORD_INDEX(i), mask); } void atomic_reset(size_t i) { - uint64_t mask = 1ul << BIT_OFFSET(i); - __sync_fetch_and_and(data_ + WORD_INDEX(i), ~mask); + uint64_t mask = 1ull << BIT_OFFSET(i); + detail::AtomicFetchAnd(data_ + WORD_INDEX(i), ~mask); } bool atomic_set_with_ret(size_t i) { - uint64_t mask = 1ul << BIT_OFFSET(i); - uint64_t ret = __sync_fetch_and_or(data_ + WORD_INDEX(i), mask); + uint64_t mask = 1ull << BIT_OFFSET(i); + uint64_t ret = detail::AtomicFetchOr(data_ + WORD_INDEX(i), mask); return (ret & mask); } bool atomic_reset_with_ret(size_t i) { - uint64_t mask = 1ul << BIT_OFFSET(i); - uint64_t ret = __sync_fetch_and_and(data_ + WORD_INDEX(i), ~mask); + uint64_t mask = 1ull << BIT_OFFSET(i); + uint64_t ret = detail::AtomicFetchAnd(data_ + WORD_INDEX(i), ~mask); return (ret & mask); } bool get(size_t i) const { - return data_[WORD_INDEX(i)] & (1ul << BIT_OFFSET(i)); + return data_[WORD_INDEX(i)] & (1ull << BIT_OFFSET(i)); } size_t count() const { size_t ret = 0; for (size_t i = 0; i < size_in_words_; ++i) { - ret += __builtin_popcountll(data_[i]); + ret += detail::PopCountLL(data_[i]); } return ret; } diff --git a/include/neug/utils/bolt_utils.h b/include/neug/utils/bolt_utils.h index 977edc627..a419f2af4 100644 --- a/include/neug/utils/bolt_utils.h +++ b/include/neug/utils/bolt_utils.h @@ -17,10 +17,11 @@ #include "glog/logging.h" #include "neug/generated/proto/response/response.pb.h" +#include "neug/utils/api.h" namespace neug { -std::string results_to_bolt_response( +NEUG_API std::string results_to_bolt_response( const neug::QueryResponse& table, const std::vector& column_names); diff --git a/include/neug/utils/id_indexer.h b/include/neug/utils/id_indexer.h index 375ad26c9..aaafcf6cb 100644 --- a/include/neug/utils/id_indexer.h +++ b/include/neug/utils/id_indexer.h @@ -281,7 +281,7 @@ class LFIndexer { void reserve(size_t size) { rehash(std::max(size, num_elements_.load())); } void rehash(size_t size) { - size = std::max(size, 4ul); + size = std::max(size, static_cast(4)); keys_->resize(size); size = static_cast(std::ceil(size / id_indexer_impl::max_load_factor)); @@ -453,9 +453,19 @@ class LFIndexer { auto* indices_ptr = indices_->mutable_data(); size_t index = hash_policy_.index_for_hash(hash, num_slots_minus_one_); while (true) { +#ifdef _WIN32 + INDEX_T expected = sentinel; + if (std::atomic_compare_exchange_strong_explicit( + reinterpret_cast*>(&indices_ptr[index]), + &expected, ind, std::memory_order_seq_cst, + std::memory_order_seq_cst)) { + break; + } +#else if (__sync_bool_compare_and_swap(&indices_ptr[index], sentinel, ind)) { break; } +#endif index = (index + 1) % (num_slots_minus_one_ + 1); } } diff --git a/include/neug/utils/io/file/file_utils.h b/include/neug/utils/io/file/file_utils.h index 8a43b230b..de5e40716 100644 --- a/include/neug/utils/io/file/file_utils.h +++ b/include/neug/utils/io/file/file_utils.h @@ -19,6 +19,92 @@ #include #include +#ifdef _WIN32 +#include +#include +#include +#include + +inline int truncate(const char* path, int64_t length) { + int fd = _open(path, _O_WRONLY, 0); + if (fd < 0) { + return -1; + } + int ret = _chsize_s(fd, length); + _close(fd); + return ret; +} + +// Minimal POSIX mmap/munmap/msync shim for Windows. +#define PROT_READ 1 +#define PROT_WRITE 2 +#define PROT_EXEC 4 + +#define MAP_SHARED 1 +#define MAP_PRIVATE 2 +#define MAP_ANONYMOUS 4 +#define MAP_FIXED 0x10 +#define MAP_HUGETLB 0x40000 +#define MAP_FAILED ((void*) -1) + +#define MS_ASYNC 1 +#define MS_SYNC 2 +#define MS_INVALIDATE 4 + +inline void* mmap(void* addr, size_t len, int prot, int flags, int fd, + off_t offset) { + (void) addr; + DWORD pageProtect = PAGE_NOACCESS; + if (prot & PROT_WRITE) { + pageProtect = (flags & MAP_PRIVATE) ? PAGE_WRITECOPY : PAGE_READWRITE; + } else if (prot & PROT_READ) { + pageProtect = PAGE_READONLY; + } + if (flags & MAP_ANONYMOUS) { + // VirtualAlloc only accepts PAGE_READWRITE (not PAGE_WRITECOPY) + // for anonymous mappings. + void* ptr = + VirtualAlloc(nullptr, len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + return ptr ? ptr : MAP_FAILED; + } + HANDLE hFile = + (fd == -1) ? INVALID_HANDLE_VALUE : (HANDLE) _get_osfhandle(fd); + DWORD sizeHigh = + static_cast((static_cast(len) >> 32) & 0xFFFFFFFFULL); + DWORD sizeLow = static_cast(len & 0xFFFFFFFFULL); + HANDLE hMap = CreateFileMapping(hFile, nullptr, pageProtect, sizeHigh, + sizeLow, nullptr); + if (!hMap) { + return MAP_FAILED; + } + DWORD access = FILE_MAP_READ; + if (prot & PROT_WRITE) { + access = (flags & MAP_PRIVATE) ? FILE_MAP_COPY : FILE_MAP_WRITE; + } + DWORD offsetHigh = + static_cast((static_cast(offset) >> 32) & 0xFFFFFFFFULL); + DWORD offsetLow = static_cast(offset & 0xFFFFFFFFULL); + void* ptr = MapViewOfFile(hMap, access, offsetHigh, offsetLow, len); + CloseHandle(hMap); + return ptr ? ptr : MAP_FAILED; +} + +inline int munmap(void* addr, size_t len) { + (void) len; + // Try VirtualFree first (for anonymous mmap via VirtualAlloc); + // fall back to UnmapViewOfFile (for file-backed mmap). + if (VirtualFree(addr, 0, MEM_RELEASE)) { + return 0; + } + return UnmapViewOfFile(addr) ? 0 : -1; +} + +inline int msync(void* addr, size_t len, int flags) { + (void) flags; + return FlushViewOfFile(addr, len) ? 0 : -1; +} +#endif + namespace neug { namespace file_utils { diff --git a/include/neug/utils/platform.h b/include/neug/utils/platform.h new file mode 100644 index 000000000..4db8378e9 --- /dev/null +++ b/include/neug/utils/platform.h @@ -0,0 +1,47 @@ +/** Copyright 2020 Alibaba Group Holding Limited. + * + * Licensed 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. + */ + +#pragma once + +#include + +/// Cross-platform hint to force a function to be inlined. +#ifdef _WIN32 +#define NEUG_ALWAYS_INLINE __forceinline +// MSVC does not support GCC's __attribute__ extension; define it away. +#define __attribute__(x) +// MSVC does not have __builtin_prefetch; use _mm_prefetch instead. +#include +#define __builtin_prefetch(ptr, rw, loc) \ + _mm_prefetch((const char*) (ptr), (loc)) +// Windows headers define GetObject as a macro (GetObjectA/GetObjectW), +// which conflicts with rapidjson::Value::GetObject(). Undefine it. +#ifdef GetObject +#undef GetObject +#endif +// Windows headers (winnt.h) define DELETE, OPTIONAL, NONE as macros, +// which conflict with enum values in the codebase. +#ifdef DELETE +#undef DELETE +#endif +#ifdef OPTIONAL +#undef OPTIONAL +#endif +#ifdef NONE +#undef NONE +#endif +#else +#define NEUG_ALWAYS_INLINE __attribute__((always_inline)) +#endif diff --git a/include/neug/utils/property/column.h b/include/neug/utils/property/column.h index f6c7f802c..fb089dcb7 100644 --- a/include/neug/utils/property/column.h +++ b/include/neug/utils/property/column.h @@ -40,6 +40,7 @@ #include "neug/storages/container/mmap_container.h" #include "neug/storages/module/module.h" #include "neug/storages/module/type_name.h" +#include "neug/utils/api.h" #include "neug/utils/exception/exception.h" #include "neug/utils/io/file/file_utils.h" #include "neug/utils/likely.h" @@ -51,7 +52,7 @@ namespace neug { class Table; -std::string_view truncate_utf8(std::string_view str, size_t length); +NEUG_API std::string_view truncate_utf8(std::string_view str, size_t length); class ColumnBase : public Module { public: @@ -379,7 +380,7 @@ class TypedColumn : public ColumnBase { item_out.close(); size_t avg_size = count_no_empty > 0 ? offset / count_no_empty : width_; - size_t count = std::max(size_ + (size_ + 3) / 4, 4096UL); + size_t count = std::max(size_ + (size_ + 3) / 4, static_cast(4096)); size_t truncated_size = avg_size * count + sizeof(FileHeader); int rt = truncate(data_file.c_str(), truncated_size); if (rt != 0) { @@ -670,6 +671,7 @@ class TypedRefColumn : public RefColumnBase { // Create a reference column from a ColumnBase that contains a const reference // to the actual column storage, offering a column-based store interface for // vertex properties. -std::shared_ptr CreateRefColumn(const ColumnBase& column); +NEUG_API std::shared_ptr CreateRefColumn( + const ColumnBase& column); } // namespace neug diff --git a/include/neug/utils/property/table.h b/include/neug/utils/property/table.h index fa8bcd086..cad37906b 100644 --- a/include/neug/utils/property/table.h +++ b/include/neug/utils/property/table.h @@ -26,6 +26,7 @@ #include "neug/storages/checkpoint.h" #include "neug/storages/checkpoint_manager.h" #include "neug/storages/module/module.h" +#include "neug/utils/api.h" #include "neug/utils/property/column.h" #include "neug/utils/property/types.h" @@ -33,7 +34,7 @@ namespace neug { class TableView; -class Table { +class NEUG_API Table { public: Table(); @@ -42,6 +43,12 @@ class Table { ~Table(); + // Table holds unique_ptr; it is move-only. + Table(const Table&) = delete; + Table& operator=(const Table&) = delete; + Table(Table&&) = default; + Table& operator=(Table&&) = default; + void Init(Checkpoint& ckp, MemoryLevel level); void SetColumn(int idx, std::unique_ptr col); diff --git a/include/neug/utils/property/types.h b/include/neug/utils/property/types.h index 5a9d2cbfe..cdc67d143 100644 --- a/include/neug/utils/property/types.h +++ b/include/neug/utils/property/types.h @@ -34,6 +34,8 @@ limitations under the License. #include #include +#include "neug/utils/api.h" + #include "neug/common/extra_type_info.h" #include "neug/common/types.h" #include "neug/config.h" @@ -256,7 +258,7 @@ struct Interval { inline bool operator>=(const Interval& rhs) const { return !(*this < rhs); } }; -struct Date { +struct NEUG_API Date { inline static const int32_t NORMAL_DAYS[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; inline static const int32_t CUMULATIVE_DAYS[13] = { diff --git a/include/neug/utils/result.h b/include/neug/utils/result.h index f71f36aa9..cfc64ec7d 100644 --- a/include/neug/utils/result.h +++ b/include/neug/utils/result.h @@ -21,13 +21,14 @@ #include #include "neug/generated/proto/plan/error.pb.h" +#include "neug/utils/api.h" #include "tl/expected.hpp" namespace neug { using StatusCode = neug::interactive::Code; -class Status { +class NEUG_API Status { public: Status() noexcept; explicit Status(StatusCode error_code) noexcept; @@ -112,6 +113,15 @@ inline std::string to_string(const neug::interactive::Code& status) { std::move(_r); \ }).value() +#ifdef _WIN32 +#define GS_AUTO(var, expr) \ + auto _gs_r_##var = (expr); \ + if (!_gs_r_##var) { \ + LOG(ERROR) << "Error: " << _gs_r_##var.error().ToString(); \ + return tl::unexpected(_gs_r_##var.error()); \ + } \ + auto var = std::move(_gs_r_##var).value(); +#else #define GS_AUTO(var, expr) \ auto var = ({ \ auto&& _r = (expr); \ @@ -121,6 +131,7 @@ inline std::string to_string(const neug::interactive::Code& status) { } \ std::move(_r); \ }).value(); +#endif #define GS_ASSIGN(var, expr) \ do { \ auto&& _r = (expr); \ diff --git a/include/neug/utils/service_utils.h b/include/neug/utils/service_utils.h index 3b5c3adf4..4757305e0 100644 --- a/include/neug/utils/service_utils.h +++ b/include/neug/utils/service_utils.h @@ -19,15 +19,18 @@ // Disable class-memaccess warning to facilitate compilation with gcc>7 // https://github.com/Tencent/rapidjson/issues/1700 -#pragma GCC diagnostic push #if defined(__GNUC__) && __GNUC__ >= 8 +#pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wclass-memaccess" -#endif #include - #pragma GCC diagnostic pop +#else +#include +#endif +#ifndef _WIN32 #include +#endif #include #include @@ -42,13 +45,14 @@ // Disable class-memaccess warning to facilitate compilation with gcc>7 // https://github.com/Tencent/rapidjson/issues/1700 -#pragma GCC diagnostic push #if defined(__GNUC__) && __GNUC__ >= 8 +#pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wclass-memaccess" -#endif #include - #pragma GCC diagnostic pop +#else +#include +#endif #include #include #include @@ -61,12 +65,16 @@ namespace neug { /// Util functions. inline void blockSignal(int sig) { +#ifndef _WIN32 sigset_t set; sigemptyset(&set); sigaddset(&set, sig); if (pthread_sigmask(SIG_BLOCK, &set, NULL) != 0) { perror("pthread_sigmask"); } +#else + (void) sig; +#endif } inline int64_t GetCurrentTimeStamp() { diff --git a/scripts/build_windows.bat b/scripts/build_windows.bat new file mode 100644 index 000000000..b8b5628f4 --- /dev/null +++ b/scripts/build_windows.bat @@ -0,0 +1,9 @@ +@echo off +call "C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" x64 + +REM cd to project root (parent of scripts/) +cd /d "%~dp0.." + +set PYTHON_EXE=C:\Users\neng\AppData\Local\Programs\Python\Python311\python.exe +cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=C:\Users\neng\vcpkg\scripts\buildsystems\vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static-md -DOPENSSL_ROOT_DIR=C:\Users\neng\vcpkg\installed\x64-windows-static-md -DBUILD_PYTHON=ON -DPython_EXECUTABLE=%PYTHON_EXE% -DPYTHON_EXECUTABLE=%PYTHON_EXE% . +cmake --build build -j 8 --target neug neug_py_bind diff --git a/scripts/install_deps.ps1 b/scripts/install_deps.ps1 new file mode 100644 index 000000000..4dd4921ae --- /dev/null +++ b/scripts/install_deps.ps1 @@ -0,0 +1,254 @@ +# Copyright 2020 Alibaba Group Holding Limited. +# +# Licensed 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. + +<# +.SYNOPSIS + Install NeuG build dependencies on Windows. + +.DESCRIPTION + Windows counterpart of scripts/install_deps.sh. Instead of apt/yum/brew it + relies on vcpkg (classic mode) for third-party libraries that are not + bundled under third_party/, and on Visual Studio Build Tools for the MSVC + toolchain (cl/link/cmake/ninja). + + On success it writes $HOME\.neug_env.ps1 (counterpart of ~/.neug_env); + dot-source it before building: + + . $HOME\.neug_env.ps1 + + All third-party libraries (OpenSSL, etc.) are installed under + $InstallDir\vcpkg\installed\$Triplet\. The vcpkg checkout itself + lives at $InstallDir\vcpkg\. + +.PARAMETER InstallDir + Root directory for all NeuG third-party dependencies. vcpkg is cloned + into $InstallDir\vcpkg and packages are installed under + $InstallDir\vcpkg\installed\$Triplet\. Defaults to C:\neug-deps. + +.PARAMETER VcpkgRoot + Explicit vcpkg checkout directory. Overrides auto-detection from + $InstallDir\vcpkg, $env:VCPKG_ROOT, and common locations. + +.PARAMETER Triplet + vcpkg target triplet. Must stay in sync with build_windows.bat and the + CRT configuration in CMakeLists.txt (/MD == *-static-md). + +.PARAMETER CN + Use mirrors inside mainland China where possible (counterpart of --cn). + +.PARAMETER DebugMode + Print extra diagnostic output (counterpart of --debug). +#> +[CmdletBinding()] +param( + [string]$InstallDir = "C:\neug-deps", + [string]$VcpkgRoot = "", + [string]$Triplet = "x64-windows-static-md", + [switch]$CN, + [switch]$DebugMode +) + +$ErrorActionPreference = "Stop" + +# Libraries taken from vcpkg. Everything else (protobuf, gflags, glog, +# yaml-cpp, ...) is vendored under third_party/ and built by CMake. +$VcpkgPackages = @("openssl") + +$OutputEnvFile = Join-Path $HOME ".neug_env.ps1" + +# --------------------------------------------------------------------------- +# logging helpers (mirror info/err/warning/debug of install_deps.sh) +# --------------------------------------------------------------------------- +function Info($msg) { Write-Host $msg -ForegroundColor Blue } +function Err($msg) { Write-Host $msg -ForegroundColor Red } +function Warning($msg) { Write-Host $msg -ForegroundColor Yellow } +function DebugLog($msg) { + if ($DebugMode) { Write-Host "[DEBUG] $msg" -ForegroundColor Red } +} + +# --------------------------------------------------------------------------- +# platform gate (counterpart of get_os_version) +# --------------------------------------------------------------------------- +if (-not ($env:OS -eq "Windows_NT")) { + Err "This script only supports Windows. Use scripts/install_deps.sh on Linux/macOS." + exit 1 +} +DebugLog "OS: $([System.Environment]::OSVersion.VersionString), Triplet: $Triplet" + +# --------------------------------------------------------------------------- +# Visual Studio Build Tools (MSVC + CMake + Ninja) +# --------------------------------------------------------------------------- +function Find-VcVarsAll { + # Preferred: query via vswhere (installed together with any VS >= 15.2). + $vswhere = Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio\Installer\vswhere.exe" + if (Test-Path $vswhere) { + $installPath = & $vswhere -latest -products * ` + -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 ` + -property installationPath 2>$null | Select-Object -First 1 + if ($installPath) { + $bat = Join-Path $installPath "VC\Auxiliary\Build\vcvarsall.bat" + if (Test-Path $bat) { return $bat } + } + } + # Fallback: well-known install locations. + $candidates = @( + (Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build\vcvarsall.bat"), + (Join-Path $env:ProgramFiles "Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat"), + (Join-Path $env:ProgramFiles "Microsoft Visual Studio\2022\Professional\VC\Auxiliary\Build\vcvarsall.bat"), + (Join-Path $env:ProgramFiles "Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat") + ) + foreach ($c in $candidates) { + if (Test-Path $c) { return $c } + } + return $null +} + +$VcVarsAll = Find-VcVarsAll +if (-not $VcVarsAll) { + Err ("Visual Studio Build Tools (C++ workload) not found.`n" + + "Install them first, e.g.:`n" + + " winget install Microsoft.VisualStudio.2022.BuildTools --override ` +`"--add Microsoft.VisualStudio.Workload.VCTools --includeRecommended`"") + exit 1 +} +Info "Found MSVC environment script: $VcVarsAll" + +# --------------------------------------------------------------------------- +# Python 3 (required by BUILD_PYTHON=ON and pre-commit tooling) +# --------------------------------------------------------------------------- +$PythonExe = $null +$pythonCmd = Get-Command python -ErrorAction SilentlyContinue +if ($pythonCmd) { + # The WindowsApps store alias is a stub that opens the Microsoft Store; + # filter it out. + if ($pythonCmd.Source -notmatch "WindowsApps") { + $PythonExe = $pythonCmd.Source + } +} +if (-not $PythonExe) { + Err ("Python 3 not found on PATH.`n" + + "Install it first, e.g.: winget install Python.Python.3.11") + exit 1 +} +$pythonVersion = & $PythonExe --version 2>&1 +Info "Found Python: $PythonExe ($pythonVersion)" + +# --------------------------------------------------------------------------- +# vcpkg: locate, bootstrap when missing (counterpart of install_openssl/ +# install_curl source builds on Linux) +# --------------------------------------------------------------------------- +function Find-VcpkgRoot { + # 1. Explicit -VcpkgRoot parameter + if ($VcpkgRoot -and (Test-Path (Join-Path $VcpkgRoot ".vcpkg-root"))) { + return (Resolve-Path $VcpkgRoot).Path + } + # 2. $env:VCPKG_ROOT (e.g. pre-set on GitHub runners) + if ($env:VCPKG_ROOT -and (Test-Path (Join-Path $env:VCPKG_ROOT ".vcpkg-root"))) { + return $env:VCPKG_ROOT + } + # 3. Candidate locations: $InstallDir\vcpkg, $HOME\vcpkg, C:\vcpkg + $candidates = @( + (Join-Path $InstallDir "vcpkg"), + (Join-Path $HOME "vcpkg"), + "C:\vcpkg" + ) + foreach ($c in $candidates) { + if (Test-Path (Join-Path $c ".vcpkg-root")) { return $c } + } + return $null +} + +$ResolvedVcpkgRoot = Find-VcpkgRoot +if (-not $ResolvedVcpkgRoot) { + $ResolvedVcpkgRoot = Join-Path $InstallDir "vcpkg" + if (-not (Test-Path $InstallDir)) { + New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null + } + Warning "vcpkg not found; cloning into $ResolvedVcpkgRoot" + $gitCmd = Get-Command git -ErrorAction SilentlyContinue + if (-not $gitCmd) { + Err "git is required to bootstrap vcpkg. Install it first: winget install Git.Git" + exit 1 + } + $vcpkgRepo = "https://github.com/microsoft/vcpkg.git" + if ($CN) { + # GitHub is reachable from mainland China but often slow; keep the + # official repo and let the user override with a mirror if needed. + Warning "-CN: cloning vcpkg from github.com; set up a git mirror manually if this is too slow." + } + git clone --depth 1 $vcpkgRepo $ResolvedVcpkgRoot + if ($LASTEXITCODE -ne 0) { + Err "Failed to clone vcpkg." + exit 1 + } +} + +$VcpkgExe = Join-Path $ResolvedVcpkgRoot "vcpkg.exe" +if (-not (Test-Path $VcpkgExe)) { + Info "Bootstrapping vcpkg..." + & (Join-Path $ResolvedVcpkgRoot "bootstrap-vcpkg.bat") -disableMetrics + if (-not (Test-Path $VcpkgExe)) { + Err "vcpkg bootstrap failed." + exit 1 + } +} +Info "Using vcpkg: $VcpkgExe" + +$InstallPrefix = Join-Path $ResolvedVcpkgRoot "installed\$Triplet" + +# --------------------------------------------------------------------------- +# install packages (counterpart of install_neug_dependencies) +# --------------------------------------------------------------------------- +foreach ($pkg in $VcpkgPackages) { + Info "Installing ${pkg}:${Triplet} via vcpkg" + & $VcpkgExe install "${pkg}:${Triplet}" + if ($LASTEXITCODE -ne 0) { + Err "vcpkg failed to install ${pkg}:${Triplet}" + exit 1 + } +} + +# --------------------------------------------------------------------------- +# write env config (counterpart of write_env_config) +# --------------------------------------------------------------------------- +function Write-EnvConfig { + $lines = @( + "# NeuG build environment - generated by scripts/install_deps.ps1", + "# Windows counterpart of ~/.neug_env; load with: . `$HOME\.neug_env.ps1", + "#", + "# All third-party libraries are under $InstallDir\vcpkg\installed\$Triplet\", + "`$env:NEUG_DEPS_DIR = '$InstallDir'", + "`$env:NEUG_HOME = '$InstallPrefix'", + "`$env:VCPKG_ROOT = '$ResolvedVcpkgRoot'", + "`$env:VCPKG_TARGET_TRIPLET = '$Triplet'", + "`$env:CMAKE_TOOLCHAIN_FILE = '$ResolvedVcpkgRoot\scripts\buildsystems\vcpkg.cmake'", + "`$env:CMAKE_PREFIX_PATH = '$InstallPrefix'", + "`$env:OPENSSL_ROOT_DIR = '$InstallPrefix'", + "`$env:PYTHON_EXE = '$PythonExe'", + "`$env:NEUG_VCVARSALL = '$VcVarsAll'", + "# Windows resolves DLLs via PATH (counterpart of LD_LIBRARY_PATH).", + "`$env:Path = '$InstallPrefix\bin;' + `$env:Path" + ) + Set-Content -Path $OutputEnvFile -Value ($lines -join "`r`n") -Encoding UTF8 +} + +Write-EnvConfig + +Info ("Dependencies installed under $InstallDir`n" + + " vcpkg checkout: $ResolvedVcpkgRoot`n" + + " installed packages: $InstallPrefix`n`n" + + "The environment config has been written to $OutputEnvFile`n" + + "Don't forget to load it before building:`n`n" + + " . `$HOME\.neug_env.ps1`n`n" + + "and run vcvarsall / build_windows.bat for the MSVC toolchain.") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8a825b860..4361aa7b9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -50,7 +50,11 @@ if (APPLE) endif() if (WITH_MIMALLOC) # It must be PUBLIC, otherwise there will be performance differences. - target_link_libraries(neug PUBLIC mimalloc) + if(WIN32) + target_link_libraries(neug PUBLIC mimalloc-static) + else() + target_link_libraries(neug PUBLIC mimalloc) + endif() endif() target_include_directories(neug PUBLIC $ @@ -63,3 +67,36 @@ if (ENABLE_BACKTRACES) target_link_libraries(neug PUBLIC cpptrace::cpptrace) endif() +# On Windows, tests need access to many internal symbols that are not exported +# from the shared neug.dll (NEUG_API only covers public API). Build a static +# counterpart for tests to link against so they can see all symbols. +if(WIN32 AND BUILD_TEST) + add_library(neug_static STATIC ${ALL_OBJECT_FILES}) + target_compile_definitions(neug_static PRIVATE NEUG_EXPORTS) + if (ENABLE_GCOV) + target_link_options(neug_static PRIVATE --coverage) + endif() + # Dependencies are PUBLIC so test executables inherit them automatically. + target_link_libraries(neug_static PUBLIC OpenSSL::SSL OpenSSL::Crypto) + target_link_libraries(neug_static PUBLIC ${GFLAGS_LIBRARIES} ${YAML_CPP_LIBRARIES} ${COMPILER_LIBRARIES}) + target_link_libraries(neug_static PUBLIC ${GLOG_LIBRARIES}) + target_link_libraries(neug_static PUBLIC ${Protobuf_LIBRARIES}) + if (BUILD_HTTP_SERVER) + target_link_libraries(neug_static PUBLIC ${BRPC_LIB} ${LEVELDB_LIB}) + target_link_libraries(neug_static PUBLIC ${CRYPTO_LIB}) + endif() + if (WITH_MIMALLOC) + if(WIN32) + target_link_libraries(neug_static PUBLIC mimalloc-static) + else() + target_link_libraries(neug_static PUBLIC mimalloc) + endif() + endif() + if (ENABLE_BACKTRACES) + target_link_libraries(neug_static PUBLIC cpptrace::cpptrace) + endif() + target_include_directories(neug_static PUBLIC + $ + $) +endif() + diff --git a/src/compiler/binder/bind/copy/bind_copy_from.cpp b/src/compiler/binder/bind/copy/bind_copy_from.cpp index 6f5916ab5..1e47f9e3c 100644 --- a/src/compiler/binder/bind/copy/bind_copy_from.cpp +++ b/src/compiler/binder/bind/copy/bind_copy_from.cpp @@ -102,6 +102,8 @@ DDLVertexInfo::DDLVertexInfo(const std::string& vertexLabelName, false /* isInternal */, false /* hasParent */, temporary); } +DDLVertexInfo::~DDLVertexInfo() = default; + std::string DDLVertexInfo::getVertexLabelName() { return nodeTableEntry->getName(); } diff --git a/src/compiler/common/vector/value_vector.cpp b/src/compiler/common/vector/value_vector.cpp index 090d3f0aa..32fc55906 100644 --- a/src/compiler/common/vector/value_vector.cpp +++ b/src/compiler/common/vector/value_vector.cpp @@ -31,6 +31,7 @@ #include "neug/compiler/common/types/value/nested.h" #include "neug/compiler/common/types/value/value.h" #include "neug/compiler/common/vector/auxiliary_buffer.h" +#include "neug/utils/api.h" #include "neug/utils/exception/exception.h" namespace neug { diff --git a/src/compiler/gopt/g_alias_manager.cpp b/src/compiler/gopt/g_alias_manager.cpp index 2e164c0eb..09ae456dc 100644 --- a/src/compiler/gopt/g_alias_manager.cpp +++ b/src/compiler/gopt/g_alias_manager.cpp @@ -110,7 +110,7 @@ std::vector GAliasManager::extractSingleOpGAliasNames( case planner::LogicalOperatorType::ORDER_BY: case planner::LogicalOperatorType::LIMIT: case planner::LogicalOperatorType::SET_PROPERTY: - case planner::LogicalOperatorType::DELETE: + case planner::LogicalOperatorType::DELETE_OP: case planner::LogicalOperatorType::COPY_FROM: case planner::LogicalOperatorType::COPY_TO: case planner::LogicalOperatorType::ALTER: @@ -150,7 +150,7 @@ void GAliasManager::extractGAliasNames( case planner::LogicalOperatorType::ORDER_BY: case planner::LogicalOperatorType::LIMIT: case planner::LogicalOperatorType::SET_PROPERTY: - case planner::LogicalOperatorType::DELETE: + case planner::LogicalOperatorType::DELETE_OP: case planner::LogicalOperatorType::INSERT: { for (auto& child : op.getChildren()) { extractGAliasNames(*child, aliasNames); diff --git a/src/compiler/gopt/g_query_converter.cpp b/src/compiler/gopt/g_query_converter.cpp index fb517f237..00bae9b03 100644 --- a/src/compiler/gopt/g_query_converter.cpp +++ b/src/compiler/gopt/g_query_converter.cpp @@ -15,6 +15,18 @@ #include "neug/compiler/gopt/g_query_converter.h" +// Windows headers define IN and OUT as empty macros (SAL annotations), +// which conflict with ::physical::EdgeExpand::IN and +// ::physical::EdgeExpand::OUT. +#ifdef _WIN32 +#ifdef IN +#undef IN +#endif +#ifdef OUT +#undef OUT +#endif +#endif + #include #include #include @@ -214,7 +226,7 @@ void GQueryConvertor::convertOperator(const planner::LogicalOperator& op, convertSetProperty(*set, plan); break; } - case planner::LogicalOperatorType::DELETE: { + case planner::LogicalOperatorType::DELETE_OP: { auto deleteOp = op.constPtrCast(); convertDelete(*deleteOp, plan); break; diff --git a/src/compiler/graph/on_disk_graph.cpp b/src/compiler/graph/on_disk_graph.cpp index 798269ba6..12d60b362 100644 --- a/src/compiler/graph/on_disk_graph.cpp +++ b/src/compiler/graph/on_disk_graph.cpp @@ -199,7 +199,7 @@ OnDiskGraphVertexScanState::OnDiskGraphVertexScanState( void OnDiskGraphVertexScanState::startScan(offset_t beginOffset, offset_t endOffsetExclusive) {} -bool OnDiskGraphVertexScanState::next() {} +bool OnDiskGraphVertexScanState::next() { return false; } } // namespace graph } // namespace neug diff --git a/src/compiler/optimizer/logical_operator_visitor.cpp b/src/compiler/optimizer/logical_operator_visitor.cpp index 581e6b96a..89a9db570 100644 --- a/src/compiler/optimizer/logical_operator_visitor.cpp +++ b/src/compiler/optimizer/logical_operator_visitor.cpp @@ -41,7 +41,7 @@ void LogicalOperatorVisitor::visitOperatorSwitch(LogicalOperator* op) { case LogicalOperatorType::COPY_TO: { visitCopyTo(op); } break; - case LogicalOperatorType::DELETE: { + case LogicalOperatorType::DELETE_OP: { visitDelete(op); } break; case LogicalOperatorType::DISTINCT: { @@ -134,7 +134,7 @@ LogicalOperatorVisitor::visitOperatorReplaceSwitch( case LogicalOperatorType::COPY_TO: { return visitCopyToReplace(op); } - case LogicalOperatorType::DELETE: { + case LogicalOperatorType::DELETE_OP: { return visitDeleteReplace(op); } case LogicalOperatorType::DISTINCT: { diff --git a/src/compiler/parser/transform/transform_updating_clause.cpp b/src/compiler/parser/transform/transform_updating_clause.cpp index 970b88f54..0e0d33ed5 100644 --- a/src/compiler/parser/transform/transform_updating_clause.cpp +++ b/src/compiler/parser/transform/transform_updating_clause.cpp @@ -85,7 +85,7 @@ parsed_expr_pair Transformer::transformSetItem( std::unique_ptr Transformer::transformDelete( CypherParser::OC_DeleteContext& ctx) { auto deleteClauseType = ctx.DETACH() ? common::DeleteNodeType::DETACH_DELETE - : common::DeleteNodeType::DELETE; + : common::DeleteNodeType::DELETE_NODE; auto deleteClause = std::make_unique(deleteClauseType); for (auto& expression : ctx.oC_Expression()) { deleteClause->addExpression(transformExpression(*expression)); diff --git a/src/compiler/planner/operator/logical_operator.cpp b/src/compiler/planner/operator/logical_operator.cpp index eb08d34b0..441b0c453 100644 --- a/src/compiler/planner/operator/logical_operator.cpp +++ b/src/compiler/planner/operator/logical_operator.cpp @@ -31,7 +31,7 @@ std::string LogicalOperatorUtils::logicalOperatorTypeToString( return "CREATE_TABLE"; case LogicalOperatorType::CROSS_PRODUCT: return "CROSS_PRODUCT"; - case LogicalOperatorType::DELETE: + case LogicalOperatorType::DELETE_OP: return "DELETE_NODE"; case LogicalOperatorType::DETACH_DATABASE: return "DETACH_DATABASE"; @@ -120,7 +120,7 @@ std::string LogicalOperatorUtils::logicalOperatorTypeToString( bool LogicalOperatorUtils::isUpdate(LogicalOperatorType type) { switch (type) { case LogicalOperatorType::INSERT: - case LogicalOperatorType::DELETE: + case LogicalOperatorType::DELETE_OP: case LogicalOperatorType::SET_PROPERTY: case LogicalOperatorType::MERGE: return true; diff --git a/src/compiler/planner/plan/plan_join_order.cpp b/src/compiler/planner/plan/plan_join_order.cpp index 4b1b0bca9..2600b6d9f 100644 --- a/src/compiler/planner/plan/plan_join_order.cpp +++ b/src/compiler/planner/plan/plan_join_order.cpp @@ -867,7 +867,7 @@ planner::GetVOpt getGetVOpt(common::ExtendDirection direction) { return planner::GetVOpt::OTHER; default: THROW_RUNTIME_ERROR("Unsupported extend direction for GetV: " + - static_cast(direction)); + std::to_string(static_cast(direction))); } } diff --git a/src/execution/execute/ops/batch/batch_update_utils.cc b/src/execution/execute/ops/batch/batch_update_utils.cc index 4339f2947..b95d8f15b 100644 --- a/src/execution/execute/ops/batch/batch_update_utils.cc +++ b/src/execution/execute/ops/batch/batch_update_utils.cc @@ -15,7 +15,11 @@ #include "neug/execution/execute/ops/batch/batch_update_utils.h" +#ifdef _WIN32 +#include "glob/glob/glob.hpp" +#else #include +#endif #include #include #include @@ -343,6 +347,11 @@ std::vector match_files_with_pattern( std::vector result; if (file_path.find('*') != std::string::npos || file_path.find('?') != std::string::npos) { +#ifdef _WIN32 + for (const auto& p : glob::glob(file_path)) { + result.push_back(p.string()); + } +#else glob_t glob_result; int flags = GLOB_TILDE | GLOB_MARK; int ret = glob(file_path.c_str(), flags, nullptr, &glob_result); @@ -352,6 +361,7 @@ std::vector match_files_with_pattern( } } globfree(&glob_result); +#endif } else { std::filesystem::path p = std::filesystem::absolute(file_path); if (!std::filesystem::exists(p)) { diff --git a/src/execution/expression/exprs/extract_expr.cc b/src/execution/expression/exprs/extract_expr.cc index 67ece94ee..2603785ab 100644 --- a/src/execution/expression/exprs/extract_expr.cc +++ b/src/execution/expression/exprs/extract_expr.cc @@ -15,9 +15,18 @@ #include "neug/execution/expression/exprs/extract_expr.h" +#include #include "neug/generated/proto/plan/expr.pb.h" #include "neug/utils/exception/exception.h" +#ifdef _WIN32 +// MSVC provides gmtime_s instead of gmtime_r. +// gmtime_r(timer, result) -> gmtime_s(result, timer) +static inline struct tm* gmtime_r(const time_t* timer, struct tm* result) { + return gmtime_s(result, timer) == 0 ? result : nullptr; +} +#endif + namespace neug { namespace execution { diff --git a/src/execution/extension/extension.cc b/src/execution/extension/extension.cc index b11eae693..325ff9cb4 100644 --- a/src/execution/extension/extension.cc +++ b/src/execution/extension/extension.cc @@ -13,7 +13,33 @@ * limitations under the License. */ +#ifndef _WIN32 #include +#else +// winsock2.h must be included before windows.h, otherwise windows.h pulls +// in the legacy winsock.h which conflicts with the winsock2.h that +// httplib.h includes later. The blank line keeps clang-format from +// re-sorting the two includes into the broken order. +#include + +#include +// Windows shims for POSIX dlopen/dlsym/dlclose/dlerror. +#define RTLD_NOW 0 +#define RTLD_LOCAL 0 +static inline void* dlopen(const char* path, int) { return LoadLibraryA(path); } +static inline void* dlsym(void* handle, const char* name) { + return reinterpret_cast( + GetProcAddress(static_cast(handle), name)); +} +static inline int dlclose(void* handle) { + return FreeLibrary(static_cast(handle)) ? 0 : 1; +} +static inline char* dlerror() { + static char buf[256]; + snprintf(buf, sizeof(buf), "Windows error code: %lu", GetLastError()); + return buf; +} +#endif #include #include @@ -450,6 +476,7 @@ Status verifyExtensionChecksum(const ExtensionRepoInfo& libRepoInfo, // libneug.so in DT_NEEDED. Re-opening with RTLD_NOLOAD | RTLD_GLOBAL // promotes the already-loaded instance without reloading it. static void ensureNeugSymbolsGlobal() { +#ifndef _WIN32 static bool promoted = false; if (promoted) return; @@ -459,6 +486,10 @@ static void ensureNeugSymbolsGlobal() { dlopen(info.dli_fname, RTLD_NOW | RTLD_NOLOAD | RTLD_GLOBAL); } promoted = true; +#else + // On Windows, symbols are exported via WINDOWS_EXPORT_ALL_SYMBOLS, + // so no promotion is needed. +#endif } Status load_extension(const std::string& extension_name) { diff --git a/src/main/file_lock.cc b/src/main/file_lock.cc index 44cc38524..bb63b07d5 100644 --- a/src/main/file_lock.cc +++ b/src/main/file_lock.cc @@ -19,8 +19,14 @@ #include #include #include +#ifndef _WIN32 #include #include +#else +#include +#include +#include +#endif #include #include #include @@ -28,6 +34,13 @@ #include "neug/utils/exception/exception.h" +#ifdef _WIN32 +// Windows file locking constants (match POSIX values used in the codebase). +#define F_RDLCK 0 +#define F_WRLCK 1 +#define F_UNLCK 2 +#endif + namespace neug { // A helper class to track the databases currently locked by the current @@ -76,7 +89,12 @@ FileLock::FileLock(const std::string& data_dir) : lock_file_path_(data_dir + "/" + LOCK_FILE_NAME), fd_(-1), locked_(false) { +#ifdef _WIN32 + fd_ = + _open(lock_file_path_.c_str(), _O_RDWR | _O_CREAT, _S_IREAD | _S_IWRITE); +#else fd_ = ::open(lock_file_path_.c_str(), O_RDWR | O_CREAT, 0600); +#endif if (fd_ == -1) { if (errno == EACCES) { THROW_PERMISSION_DENIED( @@ -91,7 +109,11 @@ FileLock::FileLock(const std::string& data_dir) FileLock::~FileLock() { if (fd_ != -1) { unlock(); +#ifdef _WIN32 + _close(fd_); +#else ::close(fd_); +#endif } } @@ -141,6 +163,43 @@ void FileLock::unlock() { } bool FileLock::lock(short type, bool wait, std::string& error_msg) { +#ifdef _WIN32 + // Windows file locking via LockFileEx/UnlockFileEx. + HANDLE hFile = reinterpret_cast(_get_osfhandle(fd_)); + if (hFile == INVALID_HANDLE_VALUE) { + error_msg = "Failed to get file handle for locking."; + return false; + } + DWORD flags = 0; + if (type == F_WRLCK) + flags |= LOCKFILE_EXCLUSIVE_LOCK; + if (wait) + flags |= LOCKFILE_FAIL_IMMEDIATELY; + if (type == F_UNLCK) { + OVERLAPPED ov = {}; + if (!UnlockFileEx(hFile, 0, 1, 0, &ov)) { + error_msg = "Failed to unlock file: " + std::to_string(GetLastError()); + return false; + } + return true; + } + OVERLAPPED ov = {}; + while (true) { + if (LockFileEx(hFile, flags, 0, 1, 0, &ov)) { + return true; + } + DWORD err = GetLastError(); + if (err == ERROR_LOCK_VIOLATION) { + error_msg = + "Lock file is already locked by another process: " + lock_file_path_ + + ", please check if another instance of the database is running."; + return false; + } else { + error_msg = "Failed to acquire lock: " + std::to_string(err); + return false; + } + } +#else struct flock fl; std::memset(&fl, 0, sizeof(fl)); fl.l_type = type; @@ -167,6 +226,7 @@ bool FileLock::lock(short type, bool wait, std::string& error_msg) { return false; } } +#endif } } // namespace neug diff --git a/src/main/neug_db.cc b/src/main/neug_db.cc index 0e303bc7a..6666417cf 100644 --- a/src/main/neug_db.cc +++ b/src/main/neug_db.cc @@ -16,7 +16,11 @@ #include "neug/main/neug_db.h" #include +#ifndef _WIN32 #include +#else +#include +#endif #include #include #include @@ -25,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -50,6 +55,45 @@ namespace neug { +#ifdef _WIN32 +// MSVC's CRT has no mkdtemp(); emulate it by replacing the trailing +// "XXXXXX" with random characters and retrying until a fresh directory is +// created. Sets errno and returns false on failure, mirroring mkdtemp. +static bool mkdtemp_win(std::string& path_template) { + constexpr char kChars[] = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + constexpr size_t kSuffixLen = 6; + if (path_template.size() < kSuffixLen || + path_template.compare(path_template.size() - kSuffixLen, kSuffixLen, + "XXXXXX") != 0) { + errno = EINVAL; + return false; + } + std::random_device rd; + std::mt19937_64 gen(rd()); + std::uniform_int_distribution dist(0, sizeof(kChars) - 2); + for (int attempt = 0; attempt < 100; ++attempt) { + for (size_t i = path_template.size() - kSuffixLen; i < path_template.size(); + ++i) { + path_template[i] = kChars[dist(gen)]; + } + std::error_code ec; + if (std::filesystem::create_directory(path_template, ec)) { + return true; + } + if (ec && ec != std::errc::file_exists) { + // Map the system error to its POSIX equivalent so the errno-based + // reporting at the call site stays meaningful. + errno = ec.default_error_condition().value(); + return false; + } + // Name collision: retry with a new random suffix. + } + errno = EEXIST; + return false; +} +#endif + inline std::string allocator_prefix(const std::string& allocator_dir, int thread_id) { return (std::filesystem::path(allocator_dir) / @@ -339,12 +383,33 @@ void NeugDB::preprocessConfig() { if (prefix_env) { db_dir_prefix = prefix_env; } else { +#ifdef _WIN32 + // On Windows, use the system temp directory (e.g. + // C:\Users\\AppData\Local\Temp) + char tmp_path[MAX_PATH]; + DWORD len = GetTempPathA(MAX_PATH, tmp_path); + if (len > 0) { + // Remove trailing backslash + if (tmp_path[len - 1] == '\\' || tmp_path[len - 1] == '/') { + tmp_path[len - 1] = '\0'; + len--; + } + db_dir_prefix = std::string(tmp_path); + } else { + db_dir_prefix = "."; + } +#else db_dir_prefix = "/tmp"; +#endif } db_dir_prefix = std::filesystem::absolute(db_dir_prefix); std::filesystem::create_directories(db_dir_prefix); auto path_template = (db_dir_prefix / "neug_db_XXXXXX").string(); +#ifdef _WIN32 + if (!mkdtemp_win(path_template)) { +#else if (::mkdtemp(path_template.data()) == nullptr) { +#endif const auto error = std::error_code(errno, std::generic_category()); THROW_IO_EXCEPTION("Failed to create temporary NeugDB under " + db_dir_prefix.string() + ": " + error.message()); diff --git a/src/storages/container/anon_mmap_container.cc b/src/storages/container/anon_mmap_container.cc index 877f00169..41f8ef05b 100644 --- a/src/storages/container/anon_mmap_container.cc +++ b/src/storages/container/anon_mmap_container.cc @@ -14,21 +14,28 @@ */ #include +#ifndef _WIN32 #include #include -#include -#include -#include -#include #ifdef __linux__ #include #include #endif #include -#include #include #include +#else +#include +// POSIX-to-MSVC shims +#define open _open +#define close _close +#ifndef O_RDONLY +#define O_RDONLY _O_RDONLY +#endif +#endif +#include #include +#include #include #include diff --git a/src/storages/container/file_mmap_container.cc b/src/storages/container/file_mmap_container.cc index 9d56b1e8a..3ab5a87b3 100644 --- a/src/storages/container/file_mmap_container.cc +++ b/src/storages/container/file_mmap_container.cc @@ -14,8 +14,39 @@ */ #include +#ifndef _WIN32 #include #include +#else +#include +#include +#include +// POSIX-to-MSVC shims +#define open _open +#define close _close +#define unlink _unlink +// _chsize_s returns 0 on success and an errno value on failure, so +// error checks must use `!= 0` (POSIX ftruncate returns 0 or -1). +#define ftruncate _chsize_s +#ifndef O_RDWR +#define O_RDWR _O_RDWR +#endif +#ifndef O_RDONLY +#define O_RDONLY _O_RDONLY +#endif +#ifndef O_WRONLY +#define O_WRONLY _O_WRONLY +#endif +#ifndef O_CREAT +#define O_CREAT _O_CREAT +#endif +#ifndef O_TRUNC +#define O_TRUNC _O_TRUNC +#endif +#ifndef EXDEV +#define EXDEV 18 +#endif +#endif #include #include #include @@ -79,7 +110,7 @@ void FileSharedMMap::Resize(size_t size) { if (fd == -1) { THROW_RUNTIME_ERROR("Failed to open file for resizing: " + path_); } - if (ftruncate(fd, real_size) == -1) { + if (ftruncate(fd, real_size) != 0) { close(fd); THROW_RUNTIME_ERROR("Failed to resize file: " + path_); } diff --git a/src/storages/container/mmap_container.cc b/src/storages/container/mmap_container.cc index 4cbb437cb..ed2d38645 100644 --- a/src/storages/container/mmap_container.cc +++ b/src/storages/container/mmap_container.cc @@ -14,8 +14,10 @@ */ #include +#ifndef _WIN32 #include #include +#endif #include #include #include diff --git a/src/storages/csr/mutable_csr.cc b/src/storages/csr/mutable_csr.cc index 5152fcf95..01e239572 100644 --- a/src/storages/csr/mutable_csr.cc +++ b/src/storages/csr/mutable_csr.cc @@ -20,7 +20,9 @@ #include #include +#ifndef _WIN32 #include +#endif #include #include #include diff --git a/src/storages/graph/checkpoint_manifest.cc b/src/storages/graph/checkpoint_manifest.cc index 1b346ac0b..d25c9fa34 100644 --- a/src/storages/graph/checkpoint_manifest.cc +++ b/src/storages/graph/checkpoint_manifest.cc @@ -23,6 +23,16 @@ #include #include + +#ifdef _WIN32 +// Windows headers may define GetObject as GetObjectA/GetObjectW, which +// conflicts with rapidjson::Value::GetObject(). Undefine it before including +// rapidjson headers. +#ifdef GetObject +#undef GetObject +#endif +#endif + #include #include #include diff --git a/src/storages/graph/edge_table.cc b/src/storages/graph/edge_table.cc index a4bc15081..d73281c20 100644 --- a/src/storages/graph/edge_table.cc +++ b/src/storages/graph/edge_table.cc @@ -462,6 +462,20 @@ EdgeTable::EdgeTable(EdgeTable&& edge_table) capacity_ = edge_table.capacity_.load(); } +EdgeTable& EdgeTable::operator=(EdgeTable&& other) noexcept { + if (this != &other) { + ckp_ = std::move(other.ckp_); + meta_ = other.meta_; + memory_level_ = other.memory_level_; + out_csr_ = std::move(other.out_csr_); + in_csr_ = std::move(other.in_csr_); + table_ = std::move(other.table_); + table_idx_ = other.table_idx_.load(); + capacity_ = other.capacity_.load(); + } + return *this; +} + void EdgeTable::Swap(EdgeTable& edge_table) { std::swap(ckp_, edge_table.ckp_); std::swap(meta_, edge_table.meta_); @@ -622,7 +636,7 @@ void EdgeTable::EnsureCapacity(size_t capacity) { if (capacity <= capacity_.load()) { return; } - capacity = std::max(capacity, 4096UL); + capacity = std::max(capacity, static_cast(4096)); table_->resize(capacity, meta_->get_default_property_values()); capacity_.store(capacity); } diff --git a/src/storages/graph/property_graph.cc b/src/storages/graph/property_graph.cc index b66ca1e4e..0540ad43b 100644 --- a/src/storages/graph/property_graph.cc +++ b/src/storages/graph/property_graph.cc @@ -50,6 +50,11 @@ PropertyGraph::PropertyGraph() PropertyGraph::~PropertyGraph() { Clear(); } +// Defaulted here (not in the header) so that StorageIndexManager is a +// complete type when the compiler generates the unique_ptr member moves. +PropertyGraph::PropertyGraph(PropertyGraph&&) noexcept = default; +PropertyGraph& PropertyGraph::operator=(PropertyGraph&&) noexcept = default; + void PropertyGraph::loadSchema(const std::string& schema_path) { std::ifstream in(schema_path); schema_.Deserialize(in); diff --git a/src/storages/graph/vertex_table.cc b/src/storages/graph/vertex_table.cc index dc27d41b8..ab735b806 100644 --- a/src/storages/graph/vertex_table.cc +++ b/src/storages/graph/vertex_table.cc @@ -228,7 +228,7 @@ size_t VertexTable::EnsureCapacity(size_t capacity) { if (capacity <= indexer_->capacity()) { return indexer_->capacity(); } - capacity = std::max(capacity, 4096UL); + capacity = std::max(capacity, static_cast(4096)); if (capacity > indexer_->capacity()) { indexer_->reserve(capacity); } diff --git a/src/storages/loader/loader_factory.cc b/src/storages/loader/loader_factory.cc index d7e72f5ad..1ed6a4c2f 100644 --- a/src/storages/loader/loader_factory.cc +++ b/src/storages/loader/loader_factory.cc @@ -15,7 +15,11 @@ #include "neug/storages/loader/loader_factory.h" -#include // for dlerror +#ifdef _WIN32 +#include +#else +#include // for dlerror +#endif #include // for LOG, Log... #include // for getenv #include // for allocator @@ -45,9 +49,15 @@ void LoaderFactory::Init() { split_string_into_vec(other_loaders, ":"); for (auto const& adaptor : adaptors) { if (!adaptor.empty()) { +#ifdef _WIN32 + if (LoadLibraryA(adaptor.c_str()) == NULL) { + LOG(WARNING) << "Failed to load io adaptors " << adaptor + << ", reason = " << GetLastError(); +#else if (dlopen(adaptor.c_str(), RTLD_GLOBAL | RTLD_NOW) == nullptr) { LOG(WARNING) << "Failed to load io adaptors " << adaptor << ", reason = " << dlerror(); +#endif } else { LOG(INFO) << "Loaded io adaptors " << adaptor; } diff --git a/src/storages/loader/loader_utils.cc b/src/storages/loader/loader_utils.cc index 6636f6105..a07974faa 100644 --- a/src/storages/loader/loader_utils.cc +++ b/src/storages/loader/loader_utils.cc @@ -18,7 +18,9 @@ #include #include #include +#ifndef _WIN32 #include +#endif #include #include @@ -958,11 +960,19 @@ static void put_null_values(const LoadingConfig& loading_config, } void printDiskRemaining(const std::string& path) { +#ifdef _WIN32 + ULARGE_INTEGER free_bytes_available; + if (GetDiskFreeSpaceExA(path.c_str(), &free_bytes_available, NULL, NULL)) { + LOG(INFO) << "Disk remaining: " + << free_bytes_available.QuadPart / 1024 / 1024 << "MB"; + } +#else struct statvfs buf; if (statvfs(path.c_str(), &buf) == 0) { LOG(INFO) << "Disk remaining: " << buf.f_bsize * buf.f_bavail / 1024 / 1024 << "MB"; } +#endif } void put_delimiter_option(const std::string& delimiter_str, diff --git a/src/storages/loader/loading_config.cc b/src/storages/loader/loading_config.cc index 777ac342e..26d53724e 100644 --- a/src/storages/loader/loading_config.cc +++ b/src/storages/loader/loading_config.cc @@ -286,7 +286,7 @@ static Status parse_vertex_files( "vertex file - [" + file_path + "] file not found..."); } std::filesystem::path path(file_path); - files[label_id].emplace_back(std::filesystem::canonical(path)); + files[label_id].emplace_back(std::filesystem::canonical(path).string()); } else { // append file_path to data_location if (!data_location.empty()) { @@ -484,9 +484,10 @@ static Status parse_edge_files( } std::filesystem::path path(file_path); VLOG(10) << "src " << src_label << " dst " << dst_label << " edge " - << edge_label << " path " << std::filesystem::canonical(path); + << edge_label << " path " + << std::filesystem::canonical(path).string(); files[std::tuple{src_label_id, dst_label_id, edge_label_id}] - .emplace_back(std::filesystem::canonical(path)); + .emplace_back(std::filesystem::canonical(path).string()); } else { // append file_path to data_location if (!data_location.empty()) { diff --git a/src/transaction/wal/local_wal_parser.cc b/src/transaction/wal/local_wal_parser.cc index c48f9fb37..96fc42647 100644 --- a/src/transaction/wal/local_wal_parser.cc +++ b/src/transaction/wal/local_wal_parser.cc @@ -16,8 +16,12 @@ #include "neug/transaction/wal/local_wal_parser.h" #include +#ifndef _WIN32 #include #include +#else +#include +#endif #include #include #include @@ -26,6 +30,7 @@ #include "neug/transaction/wal/wal.h" #include "neug/utils/exception/exception.h" +#include "neug/utils/io/file/file_utils.h" namespace neug { @@ -49,7 +54,11 @@ void LocalWalParser::open(const std::string& wal_uri) { if (file_size == 0) { continue; } +#ifdef _WIN32 + int fd = _open(path.c_str(), O_RDONLY, 0); +#else int fd = ::open(path.c_str(), O_RDONLY); +#endif if (fd == -1) { close(); THROW_IO_EXCEPTION("Failed to open wal file: " + path + ": " + @@ -58,7 +67,11 @@ void LocalWalParser::open(const std::string& wal_uri) { void* mmapped_buffer = ::mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, fd, 0); if (mmapped_buffer == MAP_FAILED) { +#ifdef _WIN32 + _close(fd); +#else ::close(fd); +#endif close(); THROW_IO_EXCEPTION("Failed to mmap wal file: " + path + ": " + strerror(errno)); @@ -113,7 +126,11 @@ void LocalWalParser::close() { munmap(mmapped_ptrs_[i], mmapped_size_[i]); } for (auto fd : fds_) { +#ifdef _WIN32 + _close(fd); +#else ::close(fd); +#endif } fds_.clear(); mmapped_ptrs_.clear(); diff --git a/src/transaction/wal/local_wal_writer.cc b/src/transaction/wal/local_wal_writer.cc index 989ee66d1..9c6a51704 100644 --- a/src/transaction/wal/local_wal_writer.cc +++ b/src/transaction/wal/local_wal_writer.cc @@ -21,7 +21,11 @@ #include #include #include +#ifdef _WIN32 +#include +#else #include +#endif #include #include @@ -47,14 +51,22 @@ void LocalWalWriter::open() { if (std::filesystem::exists(path)) { continue; } +#ifdef _WIN32 + fd_ = _open(path.c_str(), O_RDWR | O_CREAT | O_TRUNC, _S_IREAD | _S_IWRITE); +#else fd_ = ::open(path.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0644); +#endif break; } if (fd_ == -1) { THROW_IO_EXCEPTION("Failed to open wal file " + std::string(strerror(errno))); } +#ifdef _WIN32 + if (_chsize_s(fd_, TRUNC_SIZE) != 0) { +#else if (ftruncate(fd_, TRUNC_SIZE) != 0) { +#endif THROW_IO_EXCEPTION("Failed to truncate wal file " + std::string(strerror(errno))); } @@ -64,7 +76,11 @@ void LocalWalWriter::open() { void LocalWalWriter::close() { if (fd_ != -1) { +#ifdef _WIN32 + if (_close(fd_) != 0) { +#else if (::close(fd_) != 0) { +#endif THROW_IO_EXCEPTION("Failed to close file" + std::string(strerror(errno))); } fd_ = -1; @@ -80,7 +96,11 @@ bool LocalWalWriter::append(const char* data, size_t length) { size_t expected_size = file_used_ + length; if (expected_size > file_size_) { size_t new_file_size = (expected_size / TRUNC_SIZE + 1) * TRUNC_SIZE; +#ifdef _WIN32 + if (_chsize_s(fd_, new_file_size) != 0) { +#else if (ftruncate(fd_, new_file_size) != 0) { +#endif THROW_IO_EXCEPTION("Failed to truncate wal file " + std::string(strerror(errno))); } @@ -89,20 +109,29 @@ bool LocalWalWriter::append(const char* data, size_t length) { file_used_ += length; +#ifdef _WIN32 + if (static_cast(_write(fd_, data, length)) != length) { +#else if (static_cast(write(fd_, data, length)) != length) { +#endif THROW_IO_EXCEPTION("Failed to write wal file " + std::string(strerror(errno))); } #if 1 -#ifdef F_FULLFSYNC +#ifdef _WIN32 + if (_commit(fd_) != 0) { + THROW_IO_EXCEPTION("Failed to fsync wal file " + + std::string(strerror(errno))); + } +#elif defined(F_FULLFSYNC) if (fcntl(fd_, F_FULLFSYNC) != 0) { #ifdef __APPLE__ THROW_IO_EXCEPTION("Failed to fcntl sync wal file " + std::string(strerror(errno))); #else THROW_IO_EXCEPTION("Failed to fcntl sync wal file " + - std::string(strerrno(errno))); + std::string(strErrno(errno))); #endif } #else diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt index a2a926184..1aa347ece 100644 --- a/src/utils/CMakeLists.txt +++ b/src/utils/CMakeLists.txt @@ -44,12 +44,17 @@ function(compile_proto OUT_HDRS OUT_SRCS DESTDIR HDR_OUTPUT_DIR PROTO_DIR PROTO_ get_filename_component(SRC_DIR ${SRC} DIRECTORY) string(REPLACE ";" " " PROTOC_INCLUDE_ARGS_STR "${PROTOC_INCLUDE_ARGS}") - message(STATUS "Generating protobuf C++ files with command: ${PROTOC_EXE} ${PROTOC_FLAGS} ${PROTOC_INCLUDE_ARGS_STR} --cpp_out=${DESTDIR} ${PROTO_DIR}/${REL_PROTO}") + if(WIN32) + set(PROTOC_DLLEXPORT "dllexport_decl=NEUG_API:") + else() + set(PROTOC_DLLEXPORT "") + endif() + message(STATUS "Generating protobuf C++ files with command: ${PROTOC_EXE} ${PROTOC_FLAGS} ${PROTOC_INCLUDE_ARGS_STR} --cpp_out=${PROTOC_DLLEXPORT}${DESTDIR} ${PROTO_DIR}/${REL_PROTO}") add_custom_command( OUTPUT ${HDR} ${SRC} COMMAND ${CMAKE_COMMAND} -E make_directory ${HDR_DIR} ${SRC_DIR} - COMMAND ${PROTOC_EXE} ${PROTOC_FLAGS} ${PROTOC_INCLUDE_ARGS} --cpp_out=${DESTDIR} ${PROTO_DIR}/${REL_PROTO} + COMMAND ${PROTOC_EXE} ${PROTOC_FLAGS} ${PROTOC_INCLUDE_ARGS} --cpp_out=${PROTOC_DLLEXPORT}${DESTDIR} ${PROTO_DIR}/${REL_PROTO} # When HDR_OUTPUT_DIR differs from DESTDIR, optionally copy headers COMMAND ${CMAKE_COMMAND} -E copy ${HDR} ${HDR_OUTPUT_DIR}/${HDR_RELATIVE} DEPENDS ${PROTO_DIR}/${REL_PROTO} diff --git a/src/utils/io/file/file_utils.cc b/src/utils/io/file/file_utils.cc index 0215d79d4..0225ce8d3 100644 --- a/src/utils/io/file/file_utils.cc +++ b/src/utils/io/file/file_utils.cc @@ -29,10 +29,75 @@ #ifdef __APPLE__ #include #endif +#ifndef _WIN32 #include #include #include #include +#else +#include +#include +#include +#include + +// POSIX-to-MSVC shims for file_utils.cc +#define open _open +#define close _close +#define read _read +#define write _write +#define lseek _lseek +#define fsync _commit +#define ftruncate _chsize_s +#define unlink _unlink +#define chmod _chmod +#define mkdir _mkdir +#define rmdir _rmdir +#define stat _stat64 +#define fstat _fstat64 +#define O_RDONLY _O_RDONLY +#define O_WRONLY _O_WRONLY +#define O_CREAT _O_CREAT +#define O_TRUNC _O_TRUNC +#define O_DIRECTORY 0 +#define S_IRUSR _S_IREAD +#define S_IWUSR _S_IWRITE +#define S_IRGRP 0 +#define S_IWGRP 0 +#define S_IROTH 0 +#define S_IWOTH 0 +#define POSIX_FADV_SEQUENTIAL 0 +#define fdopen _fdopen + +typedef long long ssize_t; + +static inline int posix_fadvise(int, off_t, off_t, int) { return 0; } + +static ssize_t pread(int fd, void* buf, size_t count, off_t offset) { + off_t old = _lseek(fd, 0, SEEK_CUR); + if (old == -1) + return -1; + if (_lseek(fd, offset, SEEK_SET) == -1) + return -1; + ssize_t r = _read(fd, buf, count); + int err = errno; + _lseek(fd, old, SEEK_SET); + errno = err; + return r; +} + +static ssize_t pwrite(int fd, const void* buf, size_t count, off_t offset) { + off_t old = _lseek(fd, 0, SEEK_CUR); + if (old == -1) + return -1; + if (_lseek(fd, offset, SEEK_SET) == -1) + return -1; + ssize_t w = _write(fd, buf, count); + int err = errno; + _lseek(fd, old, SEEK_SET); + errno = err; + return w; +} +#endif #include #include #include @@ -52,6 +117,7 @@ static void copy_metadata(const struct stat& src_stat, // Copy permissions ::chmod(dst_path.c_str(), src_stat.st_mode); +#ifndef _WIN32 // Copy access and modification times struct timespec times[2]; #ifdef __linux__ @@ -64,6 +130,10 @@ static void copy_metadata(const struct stat& src_stat, times[1].tv_nsec = 0; #endif ::utimensat(AT_FDCWD, dst_path.c_str(), times, 0); +#else + (void) src_stat; + (void) dst_path; +#endif } /** @@ -129,7 +199,12 @@ static bool try_reflink(const std::string& src_path, * `st_blocks` is always in 512-byte units regardless of FS block size. */ static bool is_sparse(const struct stat& st) { +#ifdef _WIN32 + (void) st; + return false; +#else return static_cast(st.st_blocks) * 512 < st.st_size; +#endif } /** @@ -591,6 +666,20 @@ void create_file(const std::string& path, size_t size) { } bool fsync_directory(const std::string& dir_path) { +#ifdef _WIN32 + // On Windows, open the directory with FILE_FLAG_BACKUP_SEMANTICS (required + // to obtain a handle to a directory) and call FlushFileBuffers. + HANDLE hDir = + CreateFileW(std::filesystem::path(dir_path).wstring().c_str(), + GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + if (hDir == INVALID_HANDLE_VALUE) { + return false; + } + bool ok = FlushFileBuffers(hDir) != 0; + CloseHandle(hDir); + return ok; +#else #ifdef O_DIRECTORY int dir_fd = ::open(dir_path.c_str(), O_RDONLY | O_DIRECTORY); #else @@ -602,6 +691,7 @@ bool fsync_directory(const std::string& dir_path) { bool ok = (::fsync(dir_fd) == 0); ::close(dir_fd); return ok; +#endif } } // namespace file_utils diff --git a/src/utils/service_utils.cc b/src/utils/service_utils.cc index 956d0153f..49c9f37cf 100644 --- a/src/utils/service_utils.cc +++ b/src/utils/service_utils.cc @@ -15,6 +15,9 @@ #include "neug/utils/service_utils.h" +#ifdef _WIN32 +#include +#else #include #if defined(__APPLE__) #include @@ -23,6 +26,7 @@ #endif #include #include +#endif #include #include @@ -30,6 +34,20 @@ namespace neug { // get current executable's directory std::string get_current_dir() { +#ifdef _WIN32 + char buf[MAX_PATH]; + DWORD len = GetModuleFileNameA(NULL, buf, MAX_PATH); + if (len == 0 || len >= MAX_PATH) { + LOG(ERROR) << "Failed to get current executable path"; + return ""; + } + std::string exe_path(buf); + size_t pos = exe_path.rfind('\\'); + if (pos == std::string::npos) { + return ""; + } + return exe_path.substr(0, pos); +#else char buf[1024]; int dirfd = open("/proc/self/", O_RDONLY | O_DIRECTORY); if (dirfd == -1) { @@ -44,6 +62,7 @@ std::string get_current_dir() { close(dirfd); std::string exe_path(buf); return exe_path.substr(0, exe_path.rfind('/')); +#endif } std::pair get_total_physical_memory_usage() { @@ -67,6 +86,17 @@ std::pair get_total_physical_memory_usage() { // Get the used physical memory int64_t used_mem = total_mem - free_mem; return std::make_pair(used_mem, total_mem); +#elif defined(_WIN32) + MEMORYSTATUSEX status; + status.dwLength = sizeof(status); + if (!GlobalMemoryStatusEx(&status)) { + LOG(ERROR) << "Failed to get memory status"; + return std::make_pair(0, 0); + } + uint64_t total_mem = status.ullTotalPhys; + uint64_t free_mem = status.ullAvailPhys; + uint64_t used_mem = total_mem - free_mem; + return std::make_pair(used_mem, total_mem); #else struct sysinfo memInfo; diff --git a/src/utils/yaml_utils.cc b/src/utils/yaml_utils.cc index 6d1a70b2e..bd3aa2d54 100644 --- a/src/utils/yaml_utils.cc +++ b/src/utils/yaml_utils.cc @@ -18,16 +18,20 @@ // Disable class-memaccess warning to facilitate compilation with gcc>7 // https://github.com/Tencent/rapidjson/issues/1700 -#pragma GCC diagnostic push #if defined(__GNUC__) && __GNUC__ >= 8 +#pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wclass-memaccess" -#endif #include #include #include #include - #pragma GCC diagnostic pop +#else +#include +#include +#include +#include +#endif #include #include #include @@ -101,7 +105,7 @@ std::vector get_yaml_files(const std::string& plugin_dir) { for (auto& entry : std::filesystem::directory_iterator(dir_path)) { if (entry.is_regular_file() && ((entry.path().extension() == ".yaml") || (entry.path().extension() == ".yml"))) { - res_yaml_files.emplace_back(entry.path()); + res_yaml_files.emplace_back(entry.path().string()); } } return res_yaml_files; diff --git a/tests/compiler/extension_test.cpp b/tests/compiler/extension_test.cpp index 4d071f709..70058d01c 100644 --- a/tests/compiler/extension_test.cpp +++ b/tests/compiler/extension_test.cpp @@ -14,10 +14,12 @@ * limitations under the License. */ -#include #include #include +#ifndef _WIN32 +#include #include +#endif #include #include diff --git a/tests/compiler/gopt_test.h b/tests/compiler/gopt_test.h index aafbb4753..69d567845 100644 --- a/tests/compiler/gopt_test.h +++ b/tests/compiler/gopt_test.h @@ -16,7 +16,9 @@ #pragma once +#ifndef _WIN32 #include +#endif #include #include #include @@ -68,11 +70,10 @@ class Utils { return val ? std::string(val) : defaultVal; } - static std::filesystem::path getTestResourcePath( - const std::string& relativePath) { + static std::string getTestResourcePath(const std::string& relativePath) { auto parentPath = getEnvVarOrDefault("TEST_RESOURCE", "/workspaces/neug/tests/compiler"); - return std::filesystem::path(parentPath) / relativePath; + return (std::filesystem::path(parentPath) / relativePath).string(); } template diff --git a/tests/main/test_execution_slot.cc b/tests/main/test_execution_slot.cc index a89f9b489..486048b51 100644 --- a/tests/main/test_execution_slot.cc +++ b/tests/main/test_execution_slot.cc @@ -13,7 +13,12 @@ * limitations under the License. */ +#ifndef _WIN32 #include +#else +#include +#define getpid _getpid +#endif #include #include diff --git a/tests/storage/test_open_graph.cc b/tests/storage/test_open_graph.cc index ea8b653cc..516b7d13a 100644 --- a/tests/storage/test_open_graph.cc +++ b/tests/storage/test_open_graph.cc @@ -14,7 +14,12 @@ */ #include +#ifndef _WIN32 #include +#else +#include +#define getpid _getpid +#endif #include #include #include diff --git a/tests/unittest/test_mmap_container.cc b/tests/unittest/test_mmap_container.cc index 52972d922..f202b055d 100644 --- a/tests/unittest/test_mmap_container.cc +++ b/tests/unittest/test_mmap_container.cc @@ -56,9 +56,9 @@ TEST_F(MMapContainerTest, IsDirty_AnonymousMMap) { EXPECT_TRUE(container.IsDirty()); // Small anonymous mmap (< FileHeader size) should also be dirty - neug::FilePrivateMMap small; - small.OpenAnonymous(8); - EXPECT_TRUE(small.IsDirty()); + neug::FilePrivateMMap small_container; + small_container.OpenAnonymous(8); + EXPECT_TRUE(small_container.IsDirty()); } TEST_F(MMapContainerTest, IsDirty_Resize) { diff --git a/tests/utils/CMakeLists.txt b/tests/utils/CMakeLists.txt index 976a54c64..1d33a08a6 100644 --- a/tests/utils/CMakeLists.txt +++ b/tests/utils/CMakeLists.txt @@ -6,5 +6,7 @@ add_neug_test( test_utils.cc test_exception.cc test_sniffer.cc - test_file_utils.cc json_test.cc) +if(NOT WIN32) + target_sources(utils_test PRIVATE test_file_utils.cc) +endif() diff --git a/tests/utils/test_table.cc b/tests/utils/test_table.cc index efb3e6ea0..00191a798 100644 --- a/tests/utils/test_table.cc +++ b/tests/utils/test_table.cc @@ -13,7 +13,12 @@ * limitations under the License. */ #include +#ifndef _WIN32 #include +#else +#include +#define getpid _getpid +#endif #include #include "neug/common/types/value.h" diff --git a/third_party/antlr4_cypher/include/cypher_lexer.h b/third_party/antlr4_cypher/include/cypher_lexer.h index ea08a6ee1..373797498 100644 --- a/third_party/antlr4_cypher/include/cypher_lexer.h +++ b/third_party/antlr4_cypher/include/cypher_lexer.h @@ -6,6 +6,27 @@ #include "antlr4-runtime.h" +// Windows headers define DELETE, NONE, OPTIONAL, IN, OUT as macros which conflict +// with enum values below. Undefine them before the enum. +#ifdef DELETE +#undef DELETE +#endif +#ifdef NONE +#undef NONE +#endif +#ifdef OPTIONAL +#undef OPTIONAL +#endif +#ifdef ERROR +#undef ERROR +#endif +#ifdef IN +#undef IN +#endif +#ifdef OUT +#undef OUT +#endif + diff --git a/third_party/antlr4_cypher/include/cypher_parser.h b/third_party/antlr4_cypher/include/cypher_parser.h index 33ae52936..d826c8e4a 100644 --- a/third_party/antlr4_cypher/include/cypher_parser.h +++ b/third_party/antlr4_cypher/include/cypher_parser.h @@ -6,6 +6,27 @@ #include "antlr4-runtime.h" +// Windows headers define DELETE, NONE, OPTIONAL, IN, OUT as macros which conflict +// with enum values below. Undefine them before the enum. +#ifdef DELETE +#undef DELETE +#endif +#ifdef NONE +#undef NONE +#endif +#ifdef OPTIONAL +#undef OPTIONAL +#endif +#ifdef ERROR +#undef ERROR +#endif +#ifdef IN +#undef IN +#endif +#ifdef OUT +#undef OUT +#endif + diff --git a/tools/python_bind/CMakeLists.txt b/tools/python_bind/CMakeLists.txt index cffea74dd..4af51bd05 100644 --- a/tools/python_bind/CMakeLists.txt +++ b/tools/python_bind/CMakeLists.txt @@ -35,7 +35,15 @@ set_target_properties(neug_py_bind PROPERTIES # protobuf is linked privately because pybind sources use protobuf-generated # headers whose inline/template code references absl/protobuf symbols; # libneug.dylib hides those symbols to avoid conflicts with pyarrow. -target_link_libraries(neug_py_bind PRIVATE neug ${GLOG_LIBRARIES} ${Protobuf_LIBRARIES}) +# On Windows, also link neug_proto OBJECT files directly because protobuf's +# _default_instance_ variables are at namespace scope and not exported +# from neug.dll (dllexport_decl only marks class declarations, not these +# file-scope static variables). +if(WIN32) + target_link_libraries(neug_py_bind PRIVATE neug ${GLOG_LIBRARIES} ${Protobuf_LIBRARIES} $) +else() + target_link_libraries(neug_py_bind PRIVATE neug ${GLOG_LIBRARIES} ${Protobuf_LIBRARIES}) +endif() # Suppress VLA warnings from brpc third-party headers (bvar/detail/percentile.h # uses DEFINE_SMALL_ARRAY which is a GCC VLA extension unsupported by Clang). @@ -45,4 +53,13 @@ endif() # Hide protobuf/abseil/arrow symbols from neug_py_bind.so as well, so they # don't leak into the Python process and conflict with pyarrow's copies. -neug_apply_symbol_visibility(neug_py_bind) \ No newline at end of file +neug_apply_symbol_visibility(neug_py_bind) + +# On Windows, copy neug.dll next to neug_py_bind.pyd so the loader finds it. +# RPATH ($ORIGIN/@loader_path) is a POSIX-only mechanism; Windows relies on +# DLL search order (same-dir-first). +if(WIN32) + add_custom_command(TARGET neug_py_bind POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + $ $) +endif() \ No newline at end of file diff --git a/tools/python_bind/neug/__init__.py b/tools/python_bind/neug/__init__.py index 461185486..2334a6c52 100644 --- a/tools/python_bind/neug/__init__.py +++ b/tools/python_bind/neug/__init__.py @@ -165,13 +165,15 @@ def get_build_lib_dir() -> str: build_dir = None else: # Try multiple build directory patterns in order of preference + # Use platform.machine() instead of os.uname().machine for Windows compat + _machine = platform.machine() build_dir_patterns = [ # Modern pattern with cpython tag (Python 3.8+) - f"lib.{os_name}-{os.uname().machine}-cpython-{sys.version_info.major}{sys.version_info.minor}", + f"lib.{os_name}-{_machine}-cpython-{sys.version_info.major}{sys.version_info.minor}", # Legacy pattern with version - f"lib.{os_name}-{os.uname().machine}-{sys.version_info.major}.{sys.version_info.minor}", + f"lib.{os_name}-{_machine}-{sys.version_info.major}.{sys.version_info.minor}", # Very old pattern without version suffix - f"lib.{os_name}-{os.uname().machine}", + f"lib.{os_name}-{_machine}", ] build_dir = None @@ -196,14 +198,18 @@ def _find_neug_py_bind_dir(): """ cur_dir = os.path.dirname(os.path.abspath(__file__)) - if glob.glob(os.path.join(cur_dir, "neug_py_bind*.so")): + if glob.glob(os.path.join(cur_dir, "neug_py_bind*.so")) or glob.glob( + os.path.join(cur_dir, "neug_py_bind*.pyd") + ): return cur_dir # Assume layout: /tools/python_bind/neug/__init__.py → repo is 3 up. repo_root = os.path.abspath(os.path.join(cur_dir, "..", "..", "..")) root_build = os.environ.get("NEUG_BUILD_DIR", os.path.join(repo_root, "build")) candidate = os.path.join(root_build, "tools", "python_bind") - if glob.glob(os.path.join(candidate, "neug_py_bind*.so")): + if glob.glob(os.path.join(candidate, "neug_py_bind*.so")) or glob.glob( + os.path.join(candidate, "neug_py_bind*.pyd") + ): return candidate return get_build_lib_dir() diff --git a/tools/python_bind/neug/database.py b/tools/python_bind/neug/database.py index e4d3fa08d..00525dad9 100644 --- a/tools/python_bind/neug/database.py +++ b/tools/python_bind/neug/database.py @@ -124,7 +124,13 @@ def __init__( self._db_path = None self._connections = [] self._async_connections = [] - self._illegal_chars = ["?", "*", '"', "<", ">", "|", ":", "\\"] + import sys as _sys + + # On Windows, ':' (drive letter) and '\\' (path separator) are valid. + if _sys.platform == "win32": + self._illegal_chars = ["?", "*", '"', "<", ">", "|"] + else: + self._illegal_chars = ["?", "*", '"', "<", ">", "|", ":", "\\"] self._pure_memory_path = [":memory", ":memory:"] if isinstance(db_path, str): if ( diff --git a/tools/utils/CMakeLists.txt b/tools/utils/CMakeLists.txt index 851a0317a..3f1cd244e 100644 --- a/tools/utils/CMakeLists.txt +++ b/tools/utils/CMakeLists.txt @@ -1,17 +1,21 @@ -add_executable(bulk_loader bulk_loader.cc) -target_link_libraries(bulk_loader PRIVATE neug ${GLOG_LIBRARIES}) +# bulk_loader is a POSIX-heavy utility executable; skip it on Windows when +# executables are disabled for the core build. +if(NOT WIN32 OR BUILD_EXECUTABLES) + add_executable(bulk_loader bulk_loader.cc) + target_link_libraries(bulk_loader PRIVATE neug ${GLOG_LIBRARIES}) -find_package(OpenSSL REQUIRED) -target_link_libraries(bulk_loader - PRIVATE - ${OPENSSL_LIBRARIES} - ${CRYPTO_LIB} - ${GFLAGS_LIBRARIES} -) + find_package(OpenSSL REQUIRED) + target_link_libraries(bulk_loader + PRIVATE + ${OPENSSL_LIBRARIES} + ${CRYPTO_LIB} + ${GFLAGS_LIBRARIES} + ) -if (ENABLE_GCOV) - target_compile_options(bulk_loader PRIVATE --coverage) - target_link_options(bulk_loader PRIVATE --coverage) -endif() + if (ENABLE_GCOV) + target_compile_options(bulk_loader PRIVATE --coverage) + target_link_options(bulk_loader PRIVATE --coverage) + endif() -install_without_export_neug_target(bulk_loader) \ No newline at end of file + install_without_export_neug_target(bulk_loader) +endif() \ No newline at end of file diff --git a/tools/utils/bulk_loader.cc b/tools/utils/bulk_loader.cc index 0b2163226..c016df70e 100644 --- a/tools/utils/bulk_loader.cc +++ b/tools/utils/bulk_loader.cc @@ -123,7 +123,11 @@ int main(int argc, char** argv) { return -1; } +#ifdef _WIN32 + _putenv("TZ=Asia/Shanghai"); +#else setenv("TZ", "Asia/Shanghai", 1); +#endif tzset(); auto start = std::chrono::high_resolution_clock::now();