diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 304f059..b3c8acb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-latest] + os: [ubuntu-latest, macos-latest, windows-latest] build_type: [Debug, Release] steps: @@ -38,6 +38,14 @@ jobs: brew update brew install cmake ninja + - name: Install build tools (Windows) + if: runner.os == 'Windows' + uses: ilammy/msvc-dev-cmd@v1 + + - name: Install Ninja (Windows) + if: runner.os == 'Windows' + run: choco install ninja --no-progress -y + - name: Report toolchain versions run: | cmake --version @@ -58,28 +66,54 @@ jobs: - name: Build run: cmake --build build --config ${{ matrix.build_type }} --parallel - - name: List built targets + - name: List built targets (POSIX) + if: runner.os != 'Windows' run: ls -la build + - name: List built targets (Windows) + if: runner.os == 'Windows' + run: dir build + - name: Run tests (CTest) working-directory: build run: ctest --output-on-failure --build-config ${{ matrix.build_type }} - - name: Run tests (verbose) + - name: Run tests (verbose, POSIX) + if: runner.os != 'Windows' working-directory: build run: ./logosdb-test --test-suite-exclude=stress - - name: List test suites + - name: Run tests (verbose, Windows) + if: runner.os == 'Windows' + working-directory: build + run: .\logosdb-test.exe --test-suite-exclude=stress + + - name: List test suites (POSIX) + if: runner.os != 'Windows' working-directory: build run: ./logosdb-test --list-test-suites - - name: Smoke-test CLI + - name: List test suites (Windows) + if: runner.os == 'Windows' + working-directory: build + run: .\logosdb-test.exe --list-test-suites + + - name: Smoke-test CLI (POSIX) + if: runner.os != 'Windows' working-directory: build run: | rm -rf /tmp/ci-smoke-db ./logosdb-cli info /tmp/ci-smoke-db --dim 16 - # libFuzzer smoke (short run) + - name: Smoke-test CLI (Windows) + if: runner.os == 'Windows' + working-directory: build + run: | + $smoke = "$env:RUNNER_TEMP\ci-smoke-db" + if (Test-Path $smoke) { Remove-Item -Recurse -Force $smoke } + .\logosdb-cli.exe info $smoke --dim 16 + + # libFuzzer smoke (short run) — Linux + Clang only fuzz-smoke-jsonl: name: Fuzz smoke (JSONL parse harness) runs-on: ubuntu-latest diff --git a/CMakeLists.txt b/CMakeLists.txt index 538c815..96b8ccd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,21 @@ cmake_minimum_required(VERSION 3.15) project(logosdb VERSION 0.9.0 LANGUAGES CXX) -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) +# MSVC-specific compile flags +if(MSVC) + add_compile_options( + /utf-8 # Source and execution charset: UTF-8 + /EHsc # C++ exception handling + /W3 # Warning level 3 + /wd4068 # Suppress unknown pragma warnings (for clang pragmas in vendored headers) + ) + # Suppress deprecation warnings for POSIX-named functions (_open, _write, etc.) + add_compile_definitions(_CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_DEPRECATE) +endif() + option(LOGOSDB_BUILD_TOOLS "Build CLI and benchmark tools" ON) option(LOGOSDB_BUILD_TESTS "Build unit tests" ON) option(LOGOSDB_BUILD_PYTHON "Build Python bindings (pybind11)" OFF) @@ -43,6 +55,11 @@ endif() if(LOGOSDB_BUILD_TOOLS) add_executable(logosdb-cli tools/logosdb-cli.cpp) target_link_libraries(logosdb-cli PRIVATE logosdb) + # MSVC's format-string checker cannot parse "%" PRIu64 macro concatenation + # and emits false-positive C4474/C4477. Suppress only for this target. + if(MSVC) + target_compile_options(logosdb-cli PRIVATE /wd4474 /wd4477) + endif() add_executable(logosdb-bench tools/logosdb-bench.cpp) target_link_libraries(logosdb-bench PRIVATE logosdb) @@ -68,6 +85,11 @@ if(LOGOSDB_BUILD_FUZZ) WARNING "LOGOSDB_BUILD_FUZZ skipped: Apple Clang does not ship libFuzzer here. Use Linux + Clang (see CI)." ) + elseif(WIN32) + message( + WARNING + "LOGOSDB_BUILD_FUZZ skipped: libFuzzer is not available on Windows. Use Linux + Clang (see CI)." + ) else() add_executable(logosdb-fuzz-jsonl tools/fuzz_jsonl_metadata.cpp) target_link_libraries(logosdb-fuzz-jsonl PRIVATE nlohmann_json) diff --git a/README.md b/README.md index f025813..f16fc66 100644 --- a/README.md +++ b/README.md @@ -112,15 +112,31 @@ cd logosdb # Building -This project supports [CMake](https://cmake.org/) out of the box. +This project supports [CMake](https://cmake.org/) on Linux, macOS, and Windows. -Quick start: +**Linux / macOS:** ```bash mkdir -p build && cd build cmake -DCMAKE_BUILD_TYPE=Release .. && cmake --build . ``` +**Windows (MSVC, Visual Studio 2019+):** + +Open a *Developer Command Prompt for VS* (or use the `ilammy/msvc-dev-cmd` action in CI), then: + +```cmd +cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release +cmake --build build --parallel +``` + +Alternatively, use the Visual Studio generator (binaries land in `build\Release\`): + +```cmd +cmake -S . -B build +cmake --build build --config Release +``` + This builds: | Target | Description | diff --git a/src/hnsw_index.cpp b/src/hnsw_index.cpp index 1202549..8372d99 100644 --- a/src/hnsw_index.cpp +++ b/src/hnsw_index.cpp @@ -1,6 +1,14 @@ #include "hnsw_index.h" +// Suppress MSVC narrowing/conversion warnings from vendored hnswlib headers +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4244 4267) +#endif #include +#ifdef _MSC_VER +#pragma warning(pop) +#endif #include #include diff --git a/src/metadata.cpp b/src/metadata.cpp index f9b61fb..8132808 100644 --- a/src/metadata.cpp +++ b/src/metadata.cpp @@ -10,6 +10,7 @@ #ifdef _WIN32 #include +#include #else #include #endif @@ -32,11 +33,15 @@ bool MetadataStore::open(const std::string& path, std::string& err) path_ = path; #ifdef _WIN32 - int flags = O_RDWR | O_CREAT | O_APPEND | O_BINARY; + { + int fd = -1; + _sopen_s(&fd, path.c_str(), O_RDWR | O_CREAT | O_APPEND | O_BINARY, _SH_DENYNO, + _S_IREAD | _S_IWRITE); + fd_ = fd; + } #else - int flags = O_RDWR | O_CREAT | O_APPEND; + fd_ = ::open(path.c_str(), O_RDWR | O_CREAT | O_APPEND, 0644); #endif - fd_ = ::open(path.c_str(), flags, 0644); if (fd_ < 0) { err = std::string("open meta: ") + strerror(errno); @@ -58,7 +63,7 @@ bool MetadataStore::open(const std::string& path, std::string& err) { j = json::parse(line); } - catch (const json::exception& e) + catch (const json::exception&) { // Invalid JSON line - skip but don't fail continue; diff --git a/src/platform.cpp b/src/platform.cpp index 4e2f043..fc4452d 100644 --- a/src/platform.cpp +++ b/src/platform.cpp @@ -32,67 +32,82 @@ bool file_exists(const std::string& path) } #ifdef _WIN32 -// Windows memory mapping implementation - -bool mmap_open(const std::string& path, size_t& out_size, MappedFile& out_map, std::string& err) +// Windows memory mapping implementation. +// +// Design notes: +// - CreateFileMapping returns NULL on failure (not INVALID_HANDLE_VALUE). +// - PAGE_READONLY mappings cannot exceed the current file size, so the Linux +// MAP_NORESERVE over-reservation trick is not available. Instead we simply +// map the current file size and re-create the mapping object whenever the +// file grows (mmap_commit). The file_handle is kept open across commits so +// that GetFileSizeEx always returns the live size written by the POSIX fd. + +// Helper: create a mapping + view for the current size of an already-open file. +// Returns true on success; map_handle / data / size are filled in out_map. +// Caller must pre-set out_map.file_handle before calling. +static bool win_map_current(MappedFile& out_map, std::string& err) { - out_map = {}; // Clear - - HANDLE file_handle = CreateFileA(path.c_str(), - GENERIC_READ, - FILE_SHARE_READ, - nullptr, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - nullptr); - - if (file_handle == INVALID_HANDLE_VALUE) - { - err = "CreateFileA failed: " + std::to_string(GetLastError()); - return false; - } - - LARGE_INTEGER file_size; - if (!GetFileSizeEx(file_handle, &file_size)) + LARGE_INTEGER fs; + if (!GetFileSizeEx(out_map.file_handle, &fs)) { err = "GetFileSizeEx failed: " + std::to_string(GetLastError()); - CloseHandle(file_handle); return false; } - if (file_size.QuadPart == 0) + if (fs.QuadPart == 0) { - // Empty file - no mapping needed - out_map.file_handle = file_handle; + out_map.map_handle = INVALID_HANDLE_VALUE; out_map.data = nullptr; out_map.size = 0; - out_size = 0; return true; } - HANDLE map_handle = CreateFileMapping(file_handle, nullptr, PAGE_READONLY, 0, 0, nullptr); - - if (map_handle == INVALID_HANDLE_VALUE) + HANDLE mh = CreateFileMapping(out_map.file_handle, nullptr, PAGE_READONLY, 0, 0, nullptr); + if (mh == NULL) { err = "CreateFileMapping failed: " + std::to_string(GetLastError()); - CloseHandle(file_handle); return false; } - void* data = MapViewOfFile(map_handle, FILE_MAP_READ, 0, 0, 0); - + void* data = MapViewOfFile(mh, FILE_MAP_READ, 0, 0, 0); if (!data) { err = "MapViewOfFile failed: " + std::to_string(GetLastError()); - CloseHandle(map_handle); - CloseHandle(file_handle); + CloseHandle(mh); return false; } - out_map.file_handle = file_handle; - out_map.map_handle = map_handle; + out_map.map_handle = mh; out_map.data = static_cast(data); - out_map.size = static_cast(file_size.QuadPart); + out_map.size = static_cast(fs.QuadPart); + return true; +} + +bool mmap_open(const std::string& path, size_t& out_size, MappedFile& out_map, std::string& err) +{ + out_map = {}; + + HANDLE fh = CreateFileA(path.c_str(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_DELETE, + nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + nullptr); + if (fh == INVALID_HANDLE_VALUE) + { + err = "CreateFileA failed: " + std::to_string(GetLastError()); + return false; + } + + out_map.file_handle = fh; + if (!win_map_current(out_map, err)) + { + CloseHandle(fh); + out_map = {}; + return false; + } + out_size = out_map.size; return true; } @@ -119,117 +134,90 @@ void mmap_close(MappedFile& map) bool mmap_resize(MappedFile& map, size_t new_size, std::string& err) { - // On Windows, we need to close and reopen the mapping - // This is used when the file grows + // On Windows, close and reopen the whole mapping. + // (mmap_commit is the preferred path for growth; this is a fallback.) + (void)new_size; mmap_close(map); - - // For simplicity on Windows, we don't remap here - the caller - // should use mmap_open again after closing the file - err = "mmap_resize not implemented for Windows - use close/reopen pattern"; + err = "mmap_resize: use mmap_commit for in-place growth on Windows"; return false; } +// mmap_reserve: open the file keeping it share-writable so that the POSIX fd +// owned by VectorStorage can append data while the mapping stays open. +// True VA over-reservation is not supported for read-only mappings on Windows; +// we simply map the current file contents and re-map on each commit. bool mmap_reserve(const std::string& path, size_t reserve_size, MappedFile& out_map, std::string& err) { - out_map = {}; // Clear - - // First, get the current file size - HANDLE file_handle = - CreateFileA(path.c_str(), - GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE, // Allow others to write (file growth) - nullptr, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - nullptr); - - if (file_handle == INVALID_HANDLE_VALUE) + out_map = {}; + (void)reserve_size; // cannot over-reserve with PAGE_READONLY on Windows + + HANDLE fh = CreateFileA(path.c_str(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + nullptr); + if (fh == INVALID_HANDLE_VALUE) { err = "CreateFileA failed: " + std::to_string(GetLastError()); return false; } - LARGE_INTEGER file_size; - if (!GetFileSizeEx(file_handle, &file_size)) - { - err = "GetFileSizeEx failed: " + std::to_string(GetLastError()); - CloseHandle(file_handle); - return false; - } - - // Create a file mapping that reserves virtual address space - // We use a large maximum size for reservation, but only commit what's needed - HANDLE map_handle = - CreateFileMapping(file_handle, - nullptr, - PAGE_READONLY, - 0, - static_cast(reserve_size), // Maximum size for reservation - nullptr); - - if (map_handle == INVALID_HANDLE_VALUE) + out_map.file_handle = fh; + if (!win_map_current(out_map, err)) { - err = "CreateFileMapping failed: " + std::to_string(GetLastError()); - CloseHandle(file_handle); + CloseHandle(fh); + out_map = {}; return false; } - - // Map only the current file size initially - void* data = MapViewOfFile(map_handle, - FILE_MAP_READ, - 0, - 0, - static_cast(file_size.QuadPart) // Initial view size - ); - - if (!data) - { - err = "MapViewOfFile failed: " + std::to_string(GetLastError()); - CloseHandle(map_handle); - CloseHandle(file_handle); - return false; - } - - out_map.file_handle = file_handle; - out_map.map_handle = map_handle; - out_map.data = static_cast(data); - out_map.size = static_cast(file_size.QuadPart); return true; } +// mmap_commit: re-create the mapping object so it covers the new (larger) file +// size. The file_handle stays open; only map_handle and the view are recycled. size_t mmap_commit(MappedFile& map, size_t file_size) { -#ifdef _WIN32 - // On Windows with file mapping, the view automatically extends - // when the file grows (as long as we're within the mapping's max size) - // We just need to unmap and remap to the new size + if (map.file_handle == INVALID_HANDLE_VALUE) + return 0; + + // Release the old view and mapping object. if (map.data) { UnmapViewOfFile(map.data); + map.data = nullptr; + } + if (map.map_handle != INVALID_HANDLE_VALUE) + { + CloseHandle(map.map_handle); + map.map_handle = INVALID_HANDLE_VALUE; + } + + if (file_size == 0) + { + map.size = 0; + return 0; } - void* data = MapViewOfFile(map.map_handle, - FILE_MAP_READ, - 0, - 0, - file_size // New view size - ); + // Create a fresh mapping covering the current (grown) file. + HANDLE mh = CreateFileMapping(map.file_handle, nullptr, PAGE_READONLY, 0, 0, nullptr); + if (mh == NULL) + return map.size; + void* data = MapViewOfFile(mh, FILE_MAP_READ, 0, 0, file_size); if (!data) { - return map.size; // Failed, keep old size + CloseHandle(mh); + return map.size; } + map.map_handle = mh; map.data = static_cast(data); map.size = file_size; - return map.size; -#else - (void)map; return file_size; -#endif } #else diff --git a/src/storage.cpp b/src/storage.cpp index 686fca5..6ed72f9 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -12,6 +12,7 @@ #ifdef _WIN32 #include +#include #else #include #endif @@ -237,11 +238,14 @@ bool VectorStorage::open(const std::string& path, int dim, StorageDtype dtype, s path_ = path; #ifdef _WIN32 - int flags = O_RDWR | O_CREAT | O_BINARY; + { + int fd = -1; + _sopen_s(&fd, path.c_str(), O_RDWR | O_CREAT | O_BINARY, _SH_DENYNO, _S_IREAD | _S_IWRITE); + fd_ = fd; + } #else - int flags = O_RDWR | O_CREAT; + fd_ = ::open(path.c_str(), O_RDWR | O_CREAT, 0644); #endif - fd_ = ::open(path.c_str(), flags, 0644); if (fd_ < 0) { err = std::string("open: ") + strerror(errno); diff --git a/src/wal.cpp b/src/wal.cpp index 274f5ab..f35d78c 100644 --- a/src/wal.cpp +++ b/src/wal.cpp @@ -16,6 +16,7 @@ #ifdef _WIN32 #include +#include #endif namespace logosdb @@ -37,11 +38,14 @@ bool WriteAheadLog::open(const std::string& path, std::string& err) path_ = path; #ifdef _WIN32 - int flags = O_RDWR | O_CREAT | O_BINARY; + { + int fd = -1; + _sopen_s(&fd, path.c_str(), O_RDWR | O_CREAT | O_BINARY, _SH_DENYNO, _S_IREAD | _S_IWRITE); + fd_ = fd; + } #else - int flags = O_RDWR | O_CREAT; + fd_ = ::open(path.c_str(), O_RDWR | O_CREAT, 0644); #endif - fd_ = ::open(path.c_str(), flags, 0644); if (fd_ < 0) { err = std::string("wal open: ") + strerror(errno); diff --git a/tests/test_doctest.cpp b/tests/test_doctest.cpp index 3bdf231..ad5b7eb 100644 --- a/tests/test_doctest.cpp +++ b/tests/test_doctest.cpp @@ -3,9 +3,15 @@ #include +#ifdef _WIN32 +#include +#include +#include +#else #include #include #include +#endif #include #include @@ -22,6 +28,13 @@ using namespace std::string_literals; // Test Helpers // ============================================================================ +// Returns a platform-specific temp directory prefix, e.g. "/tmp/" on POSIX +// and the system temp path (with trailing separator) on Windows. +static std::string tmp_path(const std::string& name) +{ + return (std::filesystem::temp_directory_path() / name).string(); +} + static std::vector unit_vec(int dim, int seed) { std::mt19937 rng(seed); @@ -41,8 +54,13 @@ static std::vector unit_vec(int dim, int seed) static int run_cli(const std::string& args, std::string& output) { +#ifdef _WIN32 + std::string cmd = "logosdb-cli.exe " + args + " 2>&1"; + FILE* pipe = _popen(cmd.c_str(), "r"); +#else std::string cmd = "./logosdb-cli " + args + " 2>&1"; FILE* pipe = popen(cmd.c_str(), "r"); +#endif if (!pipe) return -1; char buffer[4096]; @@ -51,7 +69,11 @@ static int run_cli(const std::string& args, std::string& output) { output += buffer; } +#ifdef _WIN32 + return _pclose(pipe); +#else return pclose(pipe); +#endif } // ============================================================================ @@ -62,7 +84,7 @@ TEST_SUITE("core") { TEST_CASE("core: open and close") { - std::string path = "/tmp/logosdb_test_oc"; + std::string path = tmp_path("logosdb_test_oc"); std::filesystem::remove_all(path); char* err = nullptr; @@ -82,7 +104,7 @@ TEST_SUITE("core") TEST_CASE("core: logosdb_sync") { - std::string path = "/tmp/logosdb_test_sync_api"; + std::string path = tmp_path("logosdb_test_sync_api"); std::filesystem::remove_all(path); char* err = nullptr; @@ -125,7 +147,7 @@ TEST_SUITE("core") TEST_CASE("core: put and search") { - std::string path = "/tmp/logosdb_test_ps"; + std::string path = tmp_path("logosdb_test_ps"); std::filesystem::remove_all(path); logosdb::DB db(path, {.dim = 64}); @@ -155,7 +177,7 @@ TEST_SUITE("core") TEST_CASE("core: persistence across reopen") { - std::string path = "/tmp/logosdb_test_persist"; + std::string path = tmp_path("logosdb_test_persist"); std::filesystem::remove_all(path); auto v0 = unit_vec(64, 400); @@ -184,7 +206,7 @@ TEST_SUITE("core") TEST_CASE("core: raw vectors bulk access") { - std::string path = "/tmp/logosdb_test_raw"; + std::string path = tmp_path("logosdb_test_raw"); std::filesystem::remove_all(path); int dim = 32; @@ -217,7 +239,7 @@ TEST_SUITE("core") TEST_CASE("core: many vectors self-retrieval") { - std::string path = "/tmp/logosdb_test_many"; + std::string path = tmp_path("logosdb_test_many"); std::filesystem::remove_all(path); int dim = 128; @@ -246,7 +268,7 @@ TEST_SUITE("core") TEST_CASE("core: search ordering") { - std::string path = "/tmp/logosdb_test_order"; + std::string path = tmp_path("logosdb_test_order"); std::filesystem::remove_all(path); int dim = 64; @@ -280,7 +302,7 @@ TEST_SUITE("core") TEST_CASE("core: top_k limit and clamping") { - std::string path = "/tmp/logosdb_test_topk"; + std::string path = tmp_path("logosdb_test_topk"); std::filesystem::remove_all(path); int dim = 32; @@ -303,7 +325,7 @@ TEST_SUITE("core") TEST_CASE("core: empty database search") { - std::string path = "/tmp/logosdb_test_empty_search"; + std::string path = tmp_path("logosdb_test_empty_search"); std::filesystem::remove_all(path); logosdb::DB db(path, {.dim = 32}); @@ -315,7 +337,7 @@ TEST_SUITE("core") TEST_CASE("core: put without metadata") { - std::string path = "/tmp/logosdb_test_nometa"; + std::string path = tmp_path("logosdb_test_nometa"); std::filesystem::remove_all(path); logosdb::DB db(path, {.dim = 32}); @@ -333,7 +355,7 @@ TEST_SUITE("core") TEST_CASE("core: persistence append after reopen") { - std::string path = "/tmp/logosdb_test_append_reopen"; + std::string path = tmp_path("logosdb_test_append_reopen"); std::filesystem::remove_all(path); int dim = 32; @@ -368,7 +390,7 @@ TEST_SUITE("core") TEST_CASE("core: large dimension (2048)") { - std::string path = "/tmp/logosdb_test_largedim"; + std::string path = tmp_path("logosdb_test_largedim"); std::filesystem::remove_all(path); int dim = 2048; @@ -399,7 +421,7 @@ TEST_SUITE("metadata") { TEST_CASE("metadata: special characters round-trip") { - std::string path = "/tmp/logosdb_test_special"; + std::string path = tmp_path("logosdb_test_special"); std::filesystem::remove_all(path); logosdb::DB db(path, {.dim = 32}); @@ -420,7 +442,7 @@ TEST_SUITE("metadata") TEST_CASE("metadata: unicode and escapes") { - std::string path = "/tmp/logosdb_test_unicode"; + std::string path = tmp_path("logosdb_test_unicode"); std::filesystem::remove_all(path); logosdb::DB db(path, {.dim = 32}); @@ -456,7 +478,7 @@ TEST_SUITE("metadata") TEST_CASE("metadata: JSON edge cases") { - std::string path = "/tmp/logosdb_test_jsonedge"; + std::string path = tmp_path("logosdb_test_jsonedge"); std::filesystem::remove_all(path); { @@ -508,7 +530,7 @@ TEST_SUITE("errors") logosdb_options_t* opts = logosdb_options_create(); logosdb_options_set_dim(opts, 0); - logosdb_t* db = logosdb_open("/tmp/logosdb_test_err", opts, &err); + logosdb_t* db = logosdb_open(tmp_path("logosdb_test_err").c_str(), opts, &err); CHECK(db == nullptr); CHECK(err != nullptr); free(err); @@ -528,7 +550,7 @@ TEST_SUITE("errors") bool caught = false; try { - logosdb::DB db("/tmp/logosdb_test_exc", {.dim = 0}); + logosdb::DB db(tmp_path("logosdb_test_exc"), {.dim = 0}); } catch (const std::runtime_error& e) { @@ -540,7 +562,7 @@ TEST_SUITE("errors") TEST_CASE("errors: dimension mismatch on put") { - std::string path = "/tmp/logosdb_test_dim_put"; + std::string path = tmp_path("logosdb_test_dim_put"); std::filesystem::remove_all(path); char* err = nullptr; @@ -564,7 +586,7 @@ TEST_SUITE("errors") TEST_CASE("errors: dimension mismatch on search") { - std::string path = "/tmp/logosdb_test_dim_search"; + std::string path = tmp_path("logosdb_test_dim_search"); std::filesystem::remove_all(path); char* err = nullptr; @@ -588,7 +610,7 @@ TEST_SUITE("errors") TEST_CASE("errors: result accessor bounds") { - std::string path = "/tmp/logosdb_test_bounds"; + std::string path = tmp_path("logosdb_test_bounds"); std::filesystem::remove_all(path); char* err = nullptr; @@ -628,7 +650,7 @@ TEST_SUITE("delete_update") { TEST_CASE("delete_update: basic delete") { - std::string path = "/tmp/logosdb_test_delete_basic"; + std::string path = tmp_path("logosdb_test_delete_basic"); std::filesystem::remove_all(path); int dim = 64; @@ -664,7 +686,7 @@ TEST_SUITE("delete_update") TEST_CASE("delete_update: delete errors") { - std::string path = "/tmp/logosdb_test_delete_err"; + std::string path = tmp_path("logosdb_test_delete_err"); std::filesystem::remove_all(path); logosdb::DB db(path, {.dim = 32}); @@ -692,7 +714,7 @@ TEST_SUITE("delete_update") TEST_CASE("delete_update: delete persistence") { - std::string path = "/tmp/logosdb_test_delete_persist"; + std::string path = tmp_path("logosdb_test_delete_persist"); std::filesystem::remove_all(path); int dim = 32; @@ -730,7 +752,7 @@ TEST_SUITE("delete_update") TEST_CASE("delete_update: tombstone replay on index rebuild") { - std::string path = "/tmp/logosdb_test_delete_rebuild"; + std::string path = tmp_path("logosdb_test_delete_rebuild"); std::filesystem::remove_all(path); int dim = 32; @@ -765,7 +787,7 @@ TEST_SUITE("delete_update") TEST_CASE("delete_update: basic update") { - std::string path = "/tmp/logosdb_test_update"; + std::string path = tmp_path("logosdb_test_update"); std::filesystem::remove_all(path); int dim = 64; @@ -802,7 +824,7 @@ TEST_SUITE("delete_update") TEST_CASE("delete_update: update errors") { - std::string path = "/tmp/logosdb_test_update_err"; + std::string path = tmp_path("logosdb_test_update_err"); std::filesystem::remove_all(path); logosdb::DB db(path, {.dim = 32}); @@ -837,7 +859,7 @@ TEST_SUITE("delete_update") TEST_CASE("delete_update: delete and reput independence") { - std::string path = "/tmp/logosdb_test_delete_reput"; + std::string path = tmp_path("logosdb_test_delete_reput"); std::filesystem::remove_all(path); int dim = 32; @@ -871,7 +893,7 @@ TEST_SUITE("wal") { TEST_CASE("wal: crash recovery replay") { - std::string path = "/tmp/logosdb_test_wal"; + std::string path = tmp_path("logosdb_test_wal"); std::filesystem::remove_all(path); { @@ -884,37 +906,59 @@ TEST_SUITE("wal") } { +#ifdef _WIN32 + int fd = _open((path + "/wal.log").c_str(), O_RDWR | O_BINARY); +#else int fd = ::open((path + "/wal.log").c_str(), O_RDWR); +#endif CHECK(fd >= 0); +#ifdef _WIN32 + _lseeki64(fd, 0, SEEK_END); +#else ::lseek(fd, 0, SEEK_END); +#endif + + auto raw_write = [&](const void* buf, unsigned int len) + { +#ifdef _WIN32 + return _write(fd, buf, len); +#else + return static_cast(::write(fd, buf, len)); +#endif + }; uint8_t state = 0; - ::write(fd, &state, 1); + raw_write(&state, 1); uint32_t dim = 32; - ::write(fd, &dim, 4); + raw_write(&dim, 4); auto v2 = unit_vec(32, 7200); uint32_t vec_bytes = 32 * sizeof(float); - ::write(fd, &vec_bytes, 4); - ::write(fd, v2.data(), vec_bytes); + raw_write(&vec_bytes, 4); + raw_write(v2.data(), vec_bytes); std::string text = "third (from wal)"; - uint32_t text_len = text.size(); - ::write(fd, &text_len, 4); - ::write(fd, text.data(), text_len); + uint32_t text_len = static_cast(text.size()); + raw_write(&text_len, 4); + raw_write(text.data(), text_len); std::string ts = "2025-03-01T00:00:00Z"; - uint32_t ts_len = ts.size(); - ::write(fd, &ts_len, 4); - ::write(fd, ts.data(), ts_len); + uint32_t ts_len = static_cast(ts.size()); + raw_write(&ts_len, 4); + raw_write(ts.data(), ts_len); uint64_t expected_id = 2; - ::write(fd, &expected_id, 8); + raw_write(&expected_id, 8); +#ifdef _WIN32 + _commit(fd); + _close(fd); +#else ::fsync(fd); ::close(fd); +#endif } { @@ -951,7 +995,7 @@ TEST_SUITE("batch") { TEST_CASE("batch: basic put_batch") { - std::string path = "/tmp/logosdb_test_batch"; + std::string path = tmp_path("logosdb_test_batch"); std::filesystem::remove_all(path); { @@ -1006,7 +1050,7 @@ TEST_SUITE("batch") TEST_CASE("batch: empty and edge cases") { - std::string path = "/tmp/logosdb_test_batch_empty"; + std::string path = tmp_path("logosdb_test_batch_empty"); std::filesystem::remove_all(path); { @@ -1044,10 +1088,14 @@ TEST_SUITE("batch") TEST_CASE("batch: chunked WAL-aware put_batch crosses chunk boundary") { - std::string path = "/tmp/logosdb_test_batch_chunked"; + std::string path = tmp_path("logosdb_test_batch_chunked"); std::filesystem::remove_all(path); // Force several small chunks to exercise the chunking path. +#ifdef _WIN32 + _putenv_s("LOGOSDB_BATCH_CHUNK_SIZE", "64"); +#else setenv("LOGOSDB_BATCH_CHUNK_SIZE", "64", 1); +#endif { logosdb::DB db(path, {.dim = 16}); const int n = 257; // > 4 chunks of 64 @@ -1080,7 +1128,11 @@ TEST_SUITE("batch") CHECK(hits[0].id == 200u); CHECK(hits[0].text == "row_200"); } +#ifdef _WIN32 + _putenv_s("LOGOSDB_BATCH_CHUNK_SIZE", ""); +#else unsetenv("LOGOSDB_BATCH_CHUNK_SIZE"); +#endif std::filesystem::remove_all(path); } @@ -1094,9 +1146,9 @@ TEST_SUITE("streaming") { TEST_CASE("streaming: ndjson export then import round-trip") { - std::string src = "/tmp/logosdb_test_stream_src"; - std::string dst = "/tmp/logosdb_test_stream_dst"; - std::string ndjson = "/tmp/logosdb_test_stream.ndjson"; + std::string src = tmp_path("logosdb_test_stream_src"); + std::string dst = tmp_path("logosdb_test_stream_dst"); + std::string ndjson = tmp_path("logosdb_test_stream.ndjson"); std::filesystem::remove_all(src); std::filesystem::remove_all(dst); std::filesystem::remove(ndjson); @@ -1141,10 +1193,10 @@ TEST_SUITE("streaming") TEST_CASE("streaming: import resume from checkpoint after partial run") { - std::string src = "/tmp/logosdb_test_stream_src2"; - std::string dst = "/tmp/logosdb_test_stream_dst2"; - std::string ndjson = "/tmp/logosdb_test_stream2.ndjson"; - std::string checkpoint = "/tmp/logosdb_test_stream2.checkpoint"; + std::string src = tmp_path("logosdb_test_stream_src2"); + std::string dst = tmp_path("logosdb_test_stream_dst2"); + std::string ndjson = tmp_path("logosdb_test_stream2.ndjson"); + std::string checkpoint = tmp_path("logosdb_test_stream2.checkpoint"); std::filesystem::remove_all(src); std::filesystem::remove_all(dst); std::filesystem::remove(ndjson); @@ -1217,7 +1269,7 @@ TEST_SUITE("ts_range") { TEST_CASE("ts_range: basic functionality") { - std::string path = "/tmp/logosdb_test_ts_range"; + std::string path = tmp_path("logosdb_test_ts_range"); std::filesystem::remove_all(path); int dim = 64; @@ -1258,7 +1310,7 @@ TEST_SUITE("ts_range") TEST_CASE("ts_range: edge cases and C API") { - std::string path = "/tmp/logosdb_test_ts_range_edge"; + std::string path = tmp_path("logosdb_test_ts_range_edge"); std::filesystem::remove_all(path); int dim = 32; @@ -1310,7 +1362,7 @@ TEST_SUITE("ts_range") TEST_CASE("ts_range: recall with alternating timestamps") { - std::string path = "/tmp/logosdb_test_ts_range_recall"; + std::string path = tmp_path("logosdb_test_ts_range_recall"); std::filesystem::remove_all(path); int dim = 64; @@ -1368,7 +1420,7 @@ TEST_SUITE("distance") { TEST_CASE("distance: cosine auto-normalization") { - std::string path = "/tmp/logosdb_test_cosine"; + std::string path = tmp_path("logosdb_test_cosine"); std::filesystem::remove_all(path); int dim = 64; @@ -1402,7 +1454,7 @@ TEST_SUITE("distance") TEST_CASE("distance: L2 (Euclidean)") { - std::string path = "/tmp/logosdb_test_l2"; + std::string path = tmp_path("logosdb_test_l2"); std::filesystem::remove_all(path); int dim = 64; @@ -1425,7 +1477,7 @@ TEST_SUITE("distance") TEST_CASE("distance: persistence across reopen") { - std::string path = "/tmp/logosdb_test_dist_persist"; + std::string path = tmp_path("logosdb_test_dist_persist"); std::filesystem::remove_all(path); int dim = 32; @@ -1449,7 +1501,7 @@ TEST_SUITE("distance") TEST_CASE("distance: mismatch error") { - std::string path = "/tmp/logosdb_test_dist_mismatch"; + std::string path = tmp_path("logosdb_test_dist_mismatch"); std::filesystem::remove_all(path); int dim = 32; @@ -1493,7 +1545,7 @@ TEST_SUITE("cli") { TEST_CASE("cli: info reads dim from header") { - std::string path = "/tmp/logosdb_test_cli_info"; + std::string path = tmp_path("logosdb_test_cli_info"); std::filesystem::remove_all(path); logosdb::DB db(path, {.dim = 64}); @@ -1517,9 +1569,9 @@ TEST_SUITE("cli") TEST_CASE("cli: export/import roundtrip") { - std::string path = "/tmp/logosdb_test_cli_export"; - std::string import_path = "/tmp/logosdb_test_cli_import"; - std::string export_file = "/tmp/test_export.jsonl"; + std::string path = tmp_path("logosdb_test_cli_export"); + std::string import_path = tmp_path("logosdb_test_cli_import"); + std::string export_file = tmp_path("test_export.jsonl"); std::filesystem::remove_all(path); std::filesystem::remove_all(import_path); std::remove(export_file.c_str()); @@ -1553,7 +1605,7 @@ TEST_SUITE("cli") TEST_CASE("cli: search with timestamp range") { - std::string path = "/tmp/logosdb_test_cli_ts"; + std::string path = tmp_path("logosdb_test_cli_ts"); std::filesystem::remove_all(path); logosdb::DB db(path, {.dim = 32}); @@ -1563,13 +1615,14 @@ TEST_SUITE("cli") db.put(v1, "late", "2025-01-15T18:00:00Z"); { - std::ofstream qf("/tmp/query_vec.bin", std::ios::binary); + std::ofstream qf(tmp_path("query_vec.bin"), std::ios::binary); qf.write(reinterpret_cast(v0.data()), 32 * sizeof(float)); qf.close(); } std::string output; - int rc = run_cli("search " + path + " --query-file /tmp/query_vec.bin --top-k 10 --json", + int rc = run_cli("search " + path + " --query-file " + tmp_path("query_vec.bin") + + " --top-k 10 --json", output); CHECK(rc == 0); int results = 0; @@ -1582,8 +1635,8 @@ TEST_SUITE("cli") CHECK(results == 2); output.clear(); - rc = run_cli("search " + path + - " --query-file /tmp/query_vec.bin --ts-from 2025-01-01T00:00:00Z --ts-to " + rc = run_cli("search " + path + " --query-file " + tmp_path("query_vec.bin") + + " --ts-from 2025-01-01T00:00:00Z --ts-to " "2025-01-12T00:00:00Z --top-k 10 --json", output); CHECK(rc == 0); @@ -1591,12 +1644,12 @@ TEST_SUITE("cli") CHECK(output.find("late") == std::string::npos); std::filesystem::remove_all(path); - std::remove("/tmp/query_vec.bin"); + std::remove(tmp_path("query_vec.bin").c_str()); } TEST_CASE("cli: doctor reports healthy database") { - std::string path = "/tmp/logosdb_test_cli_doctor"; + std::string path = tmp_path("logosdb_test_cli_doctor"); std::filesystem::remove_all(path); logosdb::DB db(path, {.dim = 32}); @@ -1620,7 +1673,7 @@ TEST_SUITE("cli") TEST_CASE("cli: upgrade dry-run and apply on legacy vectors header") { - std::string path = "/tmp/logosdb_test_cli_upgrade_hdr"; + std::string path = tmp_path("logosdb_test_cli_upgrade_hdr"); std::filesystem::remove_all(path); std::filesystem::create_directories(path); const std::string vec = path + "/vectors.bin"; @@ -1660,9 +1713,9 @@ TEST_SUITE("cli") TEST_CASE("cli: snapshot and restore roundtrip") { - std::string src = "/tmp/logosdb_test_snap_src"; - std::string snap = "/tmp/logosdb_test_snap_out"; - std::string dst = "/tmp/logosdb_test_snap_dst"; + std::string src = tmp_path("logosdb_test_snap_src"); + std::string snap = tmp_path("logosdb_test_snap_out"); + std::string dst = tmp_path("logosdb_test_snap_dst"); std::filesystem::remove_all(src); std::filesystem::remove_all(snap); std::filesystem::remove_all(dst); @@ -1697,8 +1750,8 @@ TEST_SUITE("cli") TEST_CASE("cli: restore rejects missing manifest") { - std::string snap = "/tmp/logosdb_test_restore_bad"; - std::string dst = "/tmp/logosdb_test_restore_dst"; + std::string snap = tmp_path("logosdb_test_restore_bad"); + std::string dst = tmp_path("logosdb_test_restore_dst"); std::filesystem::remove_all(snap); std::filesystem::remove_all(dst); std::filesystem::create_directories(snap); @@ -1709,8 +1762,8 @@ TEST_SUITE("cli") TEST_CASE("cli: stats and compact") { - std::string src = "/tmp/logosdb_test_cli_stats_src"; - std::string dst = "/tmp/logosdb_test_cli_stats_dst"; + std::string src = tmp_path("logosdb_test_cli_stats_src"); + std::string dst = tmp_path("logosdb_test_cli_stats_dst"); std::filesystem::remove_all(src); std::filesystem::remove_all(dst); @@ -1755,7 +1808,7 @@ TEST_SUITE("storage") { TEST_CASE("storage: float16 basic") { - std::string path = "/tmp/logosdb_test_float16"; + std::string path = tmp_path("logosdb_test_float16"); std::filesystem::remove_all(path); int dim = 64; @@ -1790,7 +1843,7 @@ TEST_SUITE("storage") TEST_CASE("storage: int8 basic") { - std::string path = "/tmp/logosdb_test_int8"; + std::string path = tmp_path("logosdb_test_int8"); std::filesystem::remove_all(path); int dim = 64; @@ -1825,7 +1878,7 @@ TEST_SUITE("storage") TEST_CASE("storage: dtype persistence") { - std::string path = "/tmp/logosdb_test_dtype_persist"; + std::string path = tmp_path("logosdb_test_dtype_persist"); std::filesystem::remove_all(path); int dim = 32; @@ -1850,7 +1903,7 @@ TEST_SUITE("storage") TEST_CASE("storage: pointers stable across appends") { - std::string path = "/tmp/logosdb_test_stable_pointers"; + std::string path = tmp_path("logosdb_test_stable_pointers"); std::filesystem::remove_all(path); int dim = 32; @@ -2035,7 +2088,7 @@ TEST_SUITE("scoring") { TEST_CASE("scoring: self-similarity is ~1.0") { - std::string path = "/tmp/logosdb_test_selfscore"; + std::string path = tmp_path("logosdb_test_selfscore"); std::filesystem::remove_all(path); int dim = 64; diff --git a/tests/test_stress.cpp b/tests/test_stress.cpp index 96a495a..02c502e 100644 --- a/tests/test_stress.cpp +++ b/tests/test_stress.cpp @@ -16,6 +16,11 @@ #include #include +static std::string tmp_path(const std::string& name) +{ + return (std::filesystem::temp_directory_path() / name).string(); +} + namespace { @@ -42,7 +47,7 @@ TEST_SUITE("stress") { TEST_CASE("stress: many sequential puts") { - const std::string path = "/tmp/logosdb_stress_puts"; + const std::string path = tmp_path("logosdb_stress_puts"); std::filesystem::remove_all(path); char* err = nullptr; @@ -73,7 +78,7 @@ TEST_SUITE("stress") TEST_CASE("stress: search under load") { - const std::string path = "/tmp/logosdb_stress_search"; + const std::string path = tmp_path("logosdb_stress_search"); std::filesystem::remove_all(path); char* err = nullptr; @@ -109,7 +114,7 @@ TEST_SUITE("stress") TEST_CASE("stress: large put_batch") { - const std::string path = "/tmp/logosdb_stress_batch"; + const std::string path = tmp_path("logosdb_stress_batch"); std::filesystem::remove_all(path); char* err = nullptr; @@ -145,7 +150,7 @@ TEST_SUITE("stress") TEST_CASE("stress: interleaved put search sync") { - const std::string path = "/tmp/logosdb_stress_mixed"; + const std::string path = tmp_path("logosdb_stress_mixed"); std::filesystem::remove_all(path); char* err = nullptr; @@ -180,7 +185,7 @@ TEST_SUITE("stress") TEST_CASE("stress: concurrent puts (shared handle)") { - const std::string path = "/tmp/logosdb_stress_par_puts"; + const std::string path = tmp_path("logosdb_stress_par_puts"); std::filesystem::remove_all(path); char* err = nullptr; @@ -200,7 +205,7 @@ TEST_SUITE("stress") for (int t = 0; t < n_threads; ++t) { workers.emplace_back( - [db, t, &fail_count]() + [db, t, &fail_count, per_thread]() { for (int i = 0; i < per_thread; ++i) { @@ -232,7 +237,7 @@ TEST_SUITE("stress") TEST_CASE("stress: concurrent searches (shared handle)") { - const std::string path = "/tmp/logosdb_stress_par_search"; + const std::string path = tmp_path("logosdb_stress_par_search"); std::filesystem::remove_all(path); char* err = nullptr; @@ -259,7 +264,7 @@ TEST_SUITE("stress") for (int t = 0; t < n_threads; ++t) { workers.emplace_back( - [db, t, &fail_count]() + [db, t, &fail_count, queries_per_thread]() { for (int q = 0; q < queries_per_thread; ++q) { @@ -291,7 +296,7 @@ TEST_SUITE("stress") TEST_CASE("stress: concurrent mixed put and search") { - const std::string path = "/tmp/logosdb_stress_par_mixed"; + const std::string path = tmp_path("logosdb_stress_par_mixed"); std::filesystem::remove_all(path); char* err = nullptr; @@ -320,7 +325,7 @@ TEST_SUITE("stress") for (int t = 0; t < n_putters; ++t) { workers.emplace_back( - [db, t, &fail_count]() + [db, t, &fail_count, put_iters]() { for (int i = 0; i < put_iters; ++i) { @@ -339,7 +344,7 @@ TEST_SUITE("stress") for (int t = 0; t < n_searchers; ++t) { workers.emplace_back( - [db, t, &fail_count]() + [db, t, &fail_count, search_iters]() { for (int q = 0; q < search_iters; ++q) { diff --git a/tools/logosdb-cli.cpp b/tools/logosdb-cli.cpp index 11c8a52..f0f16b8 100644 --- a/tools/logosdb-cli.cpp +++ b/tools/logosdb-cli.cpp @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -392,17 +393,17 @@ static int run_upgrade_cmd(const std::string& db_path, bool apply) if (inferred != raw.n_rows) { fprintf(stderr, - "error: legacy n_rows mismatch (header=%llu size implies %llu)\n", - (unsigned long long)raw.n_rows, - (unsigned long long)inferred); + "error: legacy n_rows mismatch (header=%" PRIu64 " size implies %" PRIu64 ")\n", + raw.n_rows, + inferred); return 1; } - printf("Planned: rewrite vectors.bin header from v%u to v2 (dim=%u n_rows=%llu, float32 " + printf("Planned: rewrite vectors.bin header from v%u to v2 (dim=%u n_rows=%" PRIu64 ", float32 " "rows).\n", raw.version, raw.dim, - (unsigned long long)raw.n_rows); + raw.n_rows); if (!apply) { printf("This was a dry run. Re-run with --apply or --yes to write the new header.\n"); @@ -579,7 +580,7 @@ static int run_doctor(const std::string& db_path, bool json, int distance_overri { printf(" \"on_disk_version\": %u,\n", disk_vec_version); printf(" \"dim\": %u,\n", disk_raw.dim); - printf(" \"n_rows\": %llu,\n", (unsigned long long)disk_raw.n_rows); + printf(" \"n_rows\": %" PRIu64 ",\n", disk_raw.n_rows); } else { @@ -669,10 +670,10 @@ static int run_doctor(const std::string& db_path, bool json, int distance_overri printf("wal.log : %s (%zu bytes)\n", e_wal ? "yes" : "no", s_wal); if (have_disk_header) { - printf("vectors on-disk hdr : version=%u dim=%u n_rows=%llu\n", + printf("vectors on-disk hdr : version=%u dim=%u n_rows=%" PRIu64 "\n", disk_vec_version, disk_raw.dim, - (unsigned long long)disk_raw.n_rows); + disk_raw.n_rows); } if (vec_ok) { @@ -896,57 +897,57 @@ static int cmd_stats(const std::string& db_path, bool json) if (json) { printf("{\n"); - printf(" \"rows_total\": %llu,\n", (unsigned long long)s.rows_total); - printf(" \"rows_live\": %llu,\n", (unsigned long long)s.rows_live); - printf(" \"tombstones\": %llu,\n", (unsigned long long)s.tombstones); - printf(" \"index_elements\": %llu,\n", (unsigned long long)s.index_elements); - printf(" \"wal_pending\": %llu,\n", (unsigned long long)s.wal_pending); + printf(" \"rows_total\": %" PRIu64 ",\n", s.rows_total); + printf(" \"rows_live\": %" PRIu64 ",\n", s.rows_live); + printf(" \"tombstones\": %" PRIu64 ",\n", s.tombstones); + printf(" \"index_elements\": %" PRIu64 ",\n", s.index_elements); + printf(" \"wal_pending\": %" PRIu64 ",\n", s.wal_pending); printf(" \"distance_metric\": %d,\n", s.distance_metric); printf(" \"storage_dtype\": %d,\n", s.storage_dtype); - printf(" \"put_success\": %llu,\n", (unsigned long long)s.put_success); - printf(" \"put_failed\": %llu,\n", (unsigned long long)s.put_failed); - printf(" \"put_batch_success\": %llu,\n", (unsigned long long)s.put_batch_success); - printf(" \"put_batch_failed\": %llu,\n", (unsigned long long)s.put_batch_failed); - printf(" \"search_success\": %llu,\n", (unsigned long long)s.search_success); - printf(" \"search_failed\": %llu,\n", (unsigned long long)s.search_failed); - printf(" \"search_ts_success\": %llu,\n", (unsigned long long)s.search_ts_success); - printf(" \"search_ts_failed\": %llu,\n", (unsigned long long)s.search_ts_failed); - printf(" \"delete_success\": %llu,\n", (unsigned long long)s.delete_success); - printf(" \"delete_failed\": %llu,\n", (unsigned long long)s.delete_failed); - printf(" \"update_success\": %llu,\n", (unsigned long long)s.update_success); - printf(" \"update_failed\": %llu,\n", (unsigned long long)s.update_failed); - printf(" \"sync_calls\": %llu\n", (unsigned long long)s.sync_calls); + printf(" \"put_success\": %" PRIu64 ",\n", s.put_success); + printf(" \"put_failed\": %" PRIu64 ",\n", s.put_failed); + printf(" \"put_batch_success\": %" PRIu64 ",\n", s.put_batch_success); + printf(" \"put_batch_failed\": %" PRIu64 ",\n", s.put_batch_failed); + printf(" \"search_success\": %" PRIu64 ",\n", s.search_success); + printf(" \"search_failed\": %" PRIu64 ",\n", s.search_failed); + printf(" \"search_ts_success\": %" PRIu64 ",\n", s.search_ts_success); + printf(" \"search_ts_failed\": %" PRIu64 ",\n", s.search_ts_failed); + printf(" \"delete_success\": %" PRIu64 ",\n", s.delete_success); + printf(" \"delete_failed\": %" PRIu64 ",\n", s.delete_failed); + printf(" \"update_success\": %" PRIu64 ",\n", s.update_success); + printf(" \"update_failed\": %" PRIu64 ",\n", s.update_failed); + printf(" \"sync_calls\": %" PRIu64 "\n", s.sync_calls); printf("}\n"); return 0; } printf("stats — %s\n", db_path.c_str()); - printf("rows_total / live / tombstones : %llu / %llu / %llu\n", - (unsigned long long)s.rows_total, - (unsigned long long)s.rows_live, - (unsigned long long)s.tombstones); - printf("index_elements : %llu\n", (unsigned long long)s.index_elements); - printf("wal_pending : %llu\n", (unsigned long long)s.wal_pending); + printf("rows_total / live / tombstones : %" PRIu64 " / %" PRIu64 " / %" PRIu64 "\n", + s.rows_total, + s.rows_live, + s.tombstones); + printf("index_elements : %" PRIu64 "\n", s.index_elements); + printf("wal_pending : %" PRIu64 "\n", s.wal_pending); printf("distance / storage_dtype : %d / %d\n", s.distance_metric, s.storage_dtype); - printf("put ok / fail : %llu / %llu\n", - (unsigned long long)s.put_success, - (unsigned long long)s.put_failed); - printf("put_batch ok / fail : %llu / %llu\n", - (unsigned long long)s.put_batch_success, - (unsigned long long)s.put_batch_failed); - printf("search ok / fail : %llu / %llu\n", - (unsigned long long)s.search_success, - (unsigned long long)s.search_failed); - printf("search_ts ok / fail : %llu / %llu\n", - (unsigned long long)s.search_ts_success, - (unsigned long long)s.search_ts_failed); - printf("delete ok / fail : %llu / %llu\n", - (unsigned long long)s.delete_success, - (unsigned long long)s.delete_failed); - printf("update ok / fail : %llu / %llu\n", - (unsigned long long)s.update_success, - (unsigned long long)s.update_failed); - printf("sync_calls : %llu\n", (unsigned long long)s.sync_calls); + printf("put ok / fail : %" PRIu64 " / %" PRIu64 "\n", + s.put_success, + s.put_failed); + printf("put_batch ok / fail : %" PRIu64 " / %" PRIu64 "\n", + s.put_batch_success, + s.put_batch_failed); + printf("search ok / fail : %" PRIu64 " / %" PRIu64 "\n", + s.search_success, + s.search_failed); + printf("search_ts ok / fail : %" PRIu64 " / %" PRIu64 "\n", + s.search_ts_success, + s.search_ts_failed); + printf("delete ok / fail : %" PRIu64 " / %" PRIu64 "\n", + s.delete_success, + s.delete_failed); + printf("update ok / fail : %" PRIu64 " / %" PRIu64 "\n", + s.update_success, + s.update_failed); + printf("sync_calls : %" PRIu64 "\n", s.sync_calls); return 0; } @@ -1444,11 +1445,11 @@ int main(int argc, char** argv) { if (args.json) { - printf("{\"id\": %llu}\n", (unsigned long long)id); + printf("{\"id\": %" PRIu64 "}\n", id); } else { - printf("put id=%llu\n", (unsigned long long)id); + printf("put id=%" PRIu64 "\n", id); } } } @@ -1473,8 +1474,7 @@ int main(int argc, char** argv) const float* raw = logosdb_raw_vectors(db, nullptr, nullptr); if (!raw || args.query_id >= logosdb_count(db)) { - fprintf( - stderr, "error: invalid query-id %llu\n", (unsigned long long)args.query_id); + fprintf(stderr, "error: invalid query-id %" PRIu64 "\n", args.query_id); rc = 1; goto done; } @@ -1524,7 +1524,7 @@ int main(int argc, char** argv) { printf(" {\n"); printf(" \"rank\": %d,\n", i); - printf(" \"id\": %llu,\n", (unsigned long long)logosdb_result_id(res, i)); + printf(" \"id\": %" PRIu64 ",\n", logosdb_result_id(res, i)); printf(" \"score\": %.6f", logosdb_result_score(res, i)); const char* t = logosdb_result_text(res, i); const char* tts = logosdb_result_timestamp(res, i); @@ -1549,9 +1549,9 @@ int main(int argc, char** argv) printf("results: %d\n", n); for (int i = 0; i < n; ++i) { - printf(" #%d id=%llu score=%.6f", + printf(" #%d id=%" PRIu64 " score=%.6f", i, - (unsigned long long)logosdb_result_id(res, i), + logosdb_result_id(res, i), logosdb_result_score(res, i)); const char* t = logosdb_result_text(res, i); if (t) @@ -1584,10 +1584,10 @@ int main(int argc, char** argv) goto done; } free(err); - printf("Exported rows to %s (start_id=%llu end_id=%llu)\n", + printf("Exported rows to %s (start_id=%" PRIu64 " end_id=%" PRIu64 ")\n", args.output_file, - (unsigned long long)args.export_start_id, - (unsigned long long)args.export_end_id); + args.export_start_id, + args.export_end_id); } else if (args.cmd == "import") {