From a74b151bd0b99d67cc61df6d0c94b2546f7cfa26 Mon Sep 17 00:00:00 2001 From: matt james Date: Tue, 21 Jul 2026 09:52:04 +0100 Subject: [PATCH 1/9] feat: extend BSD support --- CMakeLists.txt | 11 +++++++---- src/linux/mgcommon.h | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5fa37c2..f46937e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,13 +43,16 @@ elseif(UNIX AND NOT APPLE) message(STATUS "ON LINUX WASM BUILD") elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") message(STATUS "ON LINUX BUILD") - elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") - message(STATUS "ON FREEBSD BUILD") + elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "NetBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "OpenBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "DragonFly") + message(STATUS "ON BSD BUILD (${CMAKE_SYSTEM_NAME})") else() message(FATAL_ERROR "Unsupported operating system. Please create issue or contribute!") endif() - # Linux and FreeBSD share the same POSIX socket path in src/linux/, - # selected by MGCLIENT_ON_POSIX. + # Linux and the BSDs (FreeBSD, NetBSD, OpenBSD, DragonFly) share the same + # POSIX socket path in src/linux/, selected by MGCLIENT_ON_POSIX. set(MGCLIENT_ON_POSIX TRUE) add_definitions(-DMGCLIENT_ON_POSIX) set(MGCLIENT_FIND_LIBRARY_PREFIXES "${CMAKE_FIND_LIBRARY_PREFIXES}") diff --git a/src/linux/mgcommon.h b/src/linux/mgcommon.h index 0b8f07f..f674dc6 100644 --- a/src/linux/mgcommon.h +++ b/src/linux/mgcommon.h @@ -15,7 +15,7 @@ #ifndef MGCLIENT_LINUX_MGCOMMON_H #define MGCLIENT_LINUX_MGCOMMON_H -#if defined(__FreeBSD__) +#if !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && !defined(__DragonFly__) #include #else #include From 44305019d3410380034df46b637488e43748ac0c Mon Sep 17 00:00:00 2001 From: matt james Date: Tue, 21 Jul 2026 10:15:36 +0100 Subject: [PATCH 2/9] fix libstdc++ for clang --- tests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3477ccf..f9943a0 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -49,7 +49,7 @@ elseif(MGCLIENT_ON_POSIX) target_link_libraries(client -Wl,--wrap=mg_secure_transport_init) endif() add_gtest(transport transport.cpp) -if(MGCLIENT_ON_APPLE) +if(MGCLIENT_ON_APPLE OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") target_link_libraries(transport c++) else() target_link_libraries(transport stdc++fs) From e3ef3fde497fd653e6a44b6d9e4bbcdbd618e750 Mon Sep 17 00:00:00 2001 From: matt james Date: Tue, 21 Jul 2026 10:56:07 +0100 Subject: [PATCH 3/9] allow example host and port to be overridden with ctest --- examples/advanced.cpp | 28 ++++++++++++++++++++-------- examples/basic.c | 28 +++++++++++++++++++++++----- examples/basic.cpp | 26 +++++++++++++++++++++----- tests/CMakeLists.txt | 10 +++++++--- 4 files changed, 71 insertions(+), 21 deletions(-) diff --git a/examples/advanced.cpp b/examples/advanced.cpp index 1f7b85d..cef408b 100644 --- a/examples/advanced.cpp +++ b/examples/advanced.cpp @@ -1,8 +1,25 @@ +#include #include #include +#include +#include #include "mgclient.hpp" +// Reads the environment variable `value_name`, falling back to `default_value` +// when it is unset. Matches the helper used by the integration tests so the +// examples honor the same MEMGRAPH_HOST / MEMGRAPH_PORT overrides, e.g. +// MEMGRAPH_HOST= MEMGRAPH_PORT= ./example_advanced_cpp +template +T GetEnvOrDefault(const std::string &value_name, const T &default_value) { + const char *char_value = std::getenv(value_name.c_str()); + if (!char_value) return default_value; + T value; + std::stringstream env_value_stream(char_value); + env_value_stream >> value; + return value; +} + void ClearDatabaseData(mg::Client *client) { if (!client->Execute("MATCH (n) DETACH DELETE n;")) { std::cerr << "Failed to delete all data from the database." << std::endl; @@ -46,18 +63,13 @@ std::string MgValueToString(const mg::ConstValue &value) { return value_str; } -int main(int argc, char *argv[]) { - if (argc != 3) { - std::cerr << "Usage: " << argv[0] << " [host] [port]\n"; - std::exit(1); - } - +int main() { mg::Client::Init(); { mg::Client::Params params; - params.host = argv[1]; - params.port = static_cast(atoi(argv[2])); + params.host = GetEnvOrDefault("MEMGRAPH_HOST", "127.0.0.1"); + params.port = GetEnvOrDefault("MEMGRAPH_PORT", 7687); auto client = mg::Client::Connect(params); if (!client) { std::cerr << "Failed to connect." << std::endl; diff --git a/examples/basic.c b/examples/basic.c index 3606c41..ab46794 100644 --- a/examples/basic.c +++ b/examples/basic.c @@ -3,9 +3,25 @@ #include +// Reads the environment variable `name`, falling back to `default_value` when +// it is unset. The C counterpart of the GetEnvOrDefault helper used by the +// integration tests, so the examples honor the same MEMGRAPH_HOST / +// MEMGRAPH_PORT overrides, e.g. +// MEMGRAPH_HOST= MEMGRAPH_PORT= ./example_basic_c "RETURN 1" +static const char *get_env_or_default(const char *name, + const char *default_value) { + const char *value = getenv(name); + return value ? value : default_value; +} + +static int get_env_int_or_default(const char *name, int default_value) { + const char *value = getenv(name); + return value ? atoi(value) : default_value; +} + int main(int argc, char *argv[]) { - if (argc != 4) { - fprintf(stderr, "Usage: %s [host] [port] [query]\n", argv[0]); + if (argc != 2) { + fprintf(stderr, "Usage: %s [query]\n", argv[0]); exit(1); } @@ -17,8 +33,10 @@ int main(int argc, char *argv[]) { fprintf(stderr, "failed to allocate session parameters\n"); exit(1); } - mg_session_params_set_host(params, argv[1]); - mg_session_params_set_port(params, (uint16_t)atoi(argv[2])); + const char *host = get_env_or_default("MEMGRAPH_HOST", "127.0.0.1"); + int port = get_env_int_or_default("MEMGRAPH_PORT", 7687); + mg_session_params_set_host(params, host); + mg_session_params_set_port(params, (uint16_t)port); mg_session_params_set_sslmode(params, MG_SSLMODE_DISABLE); mg_session *session = NULL; @@ -30,7 +48,7 @@ int main(int argc, char *argv[]) { return 1; } - if (mg_session_run(session, argv[3], NULL, NULL, NULL, NULL) < 0) { + if (mg_session_run(session, argv[1], NULL, NULL, NULL, NULL) < 0) { printf("failed to execute query: %s\n", mg_session_error(session)); mg_session_destroy(session); return 1; diff --git a/examples/basic.cpp b/examples/basic.cpp index 4c6c3b3..c4017f6 100644 --- a/examples/basic.cpp +++ b/examples/basic.cpp @@ -1,11 +1,27 @@ #include #include +#include +#include #include +// Reads the environment variable `value_name`, falling back to `default_value` +// when it is unset. Matches the helper used by the integration tests so the +// examples honor the same MEMGRAPH_HOST / MEMGRAPH_PORT overrides, e.g. +// MEMGRAPH_HOST= MEMGRAPH_PORT= ./example_basic_cpp "RETURN 1" +template +T GetEnvOrDefault(const std::string &value_name, const T &default_value) { + const char *char_value = std::getenv(value_name.c_str()); + if (!char_value) return default_value; + T value; + std::stringstream env_value_stream(char_value); + env_value_stream >> value; + return value; +} + int main(int argc, char *argv[]) { - if (argc != 4) { - std::cerr << "Usage: " << argv[0] << " [host] [port] [query]\n"; + if (argc != 2) { + std::cerr << "Usage: " << argv[0] << " [query]\n"; exit(1); } @@ -13,8 +29,8 @@ int main(int argc, char *argv[]) { std::cout << "mgclient version: " << mg::Client::Version() << std::endl; mg::Client::Params params; - params.host = argv[1]; - params.port = static_cast(atoi(argv[2])); + params.host = GetEnvOrDefault("MEMGRAPH_HOST", "127.0.0.1"); + params.port = GetEnvOrDefault("MEMGRAPH_PORT", 7687); params.use_ssl = false; auto client = mg::Client::Connect(params); @@ -23,7 +39,7 @@ int main(int argc, char *argv[]) { return 1; } - if (!client->Execute(argv[3])) { + if (!client->Execute(argv[1])) { std::cerr << "Failed to execute query!"; return 1; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f9943a0..4678e73 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -65,17 +65,21 @@ endif() # Build examples and add them to tests set(EXAMPLE_DIR ${PROJECT_SOURCE_DIR}/examples) +# The examples read the host/port from the MEMGRAPH_HOST/MEMGRAPH_PORT +# environment variables (like the integration tests), falling back to +# 127.0.0.1:7687. Override the whole suite at once, e.g. +# MEMGRAPH_HOST= MEMGRAPH_PORT= ctest --test-dir build add_executable(example_basic_c ${EXAMPLE_DIR}/basic.c) target_link_libraries(example_basic_c mgclient-static project_c_warnings) -add_test(example_basic_c example_basic_c 127.0.0.1 7687 "RETURN 1") +add_test(example_basic_c example_basic_c "RETURN 1") add_executable(example_basic_cpp ${EXAMPLE_DIR}/basic.cpp) target_link_libraries(example_basic_cpp mgclient-static mgclient_cpp project_cpp_warnings) -add_test(example_basic_cpp example_basic_cpp 127.0.0.1 7687 "RETURN 1") +add_test(example_basic_cpp example_basic_cpp "RETURN 1") add_executable(example_advanced_cpp ${EXAMPLE_DIR}/advanced.cpp) target_link_libraries(example_advanced_cpp mgclient-static mgclient_cpp project_cpp_warnings) -add_test(example_advanced_cpp example_advanced_cpp 127.0.0.1 7687) +add_test(example_advanced_cpp example_advanced_cpp) # Client-side routing example. Built (so it stays compilable) but not run as a # test: it needs a high-availability cluster coordinator, which CI does not From bae70230fbc94f296f38a33528f5bca32422cd3b Mon Sep 17 00:00:00 2001 From: matt james Date: Tue, 21 Jul 2026 11:03:10 +0100 Subject: [PATCH 4/9] handle undefined symbols in mgsocket --- src/linux/mgsocket.c | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/linux/mgsocket.c b/src/linux/mgsocket.c index 2bca678..f3f27f5 100644 --- a/src/linux/mgsocket.c +++ b/src/linux/mgsocket.c @@ -101,16 +101,28 @@ int mg_socket_options(int sock, mg_session *session) { int level; int optname; int optval; - } socket_options[] = {// disable Nagle algorithm for performance reasons - {IPPROTO_TCP, TCP_NODELAY, 1}, - // turn keep-alive on - {SOL_SOCKET, SO_KEEPALIVE, 1}, - // wait 20s before sending keep-alive packets - {IPPROTO_TCP, TCP_KEEPIDLE, 20}, - // 4 keep-alive packets must fail to close - {IPPROTO_TCP, TCP_KEEPCNT, 4}, - // send keep-alive packets every 15s - {IPPROTO_TCP, TCP_KEEPINTVL, 15}}; + } socket_options[] = { + // disable Nagle algorithm for performance reasons + {IPPROTO_TCP, TCP_NODELAY, 1}, + // turn keep-alive on + {SOL_SOCKET, SO_KEEPALIVE, 1}, +// The per-socket keep-alive tuning options below are not portable: OpenBSD, +// for one, only supports SO_KEEPALIVE and tunes the timers system-wide via +// sysctl (net.inet.tcp.keep*). Guard each on its macro so those platforms fall +// back to plain keep-alive instead of failing to build. +#ifdef TCP_KEEPIDLE + // wait 20s before sending keep-alive packets + {IPPROTO_TCP, TCP_KEEPIDLE, 20}, +#endif +#ifdef TCP_KEEPCNT + // 4 keep-alive packets must fail to close + {IPPROTO_TCP, TCP_KEEPCNT, 4}, +#endif +#ifdef TCP_KEEPINTVL + // send keep-alive packets every 15s + {IPPROTO_TCP, TCP_KEEPINTVL, 15}, +#endif + }; const size_t OPTCNT = sizeof(socket_options) / sizeof(socket_options[0]); for (size_t i = 0; i < OPTCNT; ++i) { From cc5651dce39248cc792e34a815ffbb8bb0105247 Mon Sep 17 00:00:00 2001 From: matt james Date: Tue, 21 Jul 2026 11:17:55 +0100 Subject: [PATCH 5/9] add instructions for BSD to the readme --- README.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/README.md b/README.md index fd1a553..ff0f6e0 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,59 @@ cmake -DBUILD_TESTING=ON -DBUILD_TESTING_INTEGRATION=ON .. ctest ``` +## Building and installing on BSD + +mgclient builds on FreeBSD, NetBSD, OpenBSD, and DragonFly BSD, which share the +same POSIX socket implementation as Linux. + +To build and install mgclient from source you will need: + - CMake version >= 3.8 + - OpenSSL version >= 1.0.2 + - a C11 compiler (the base system Clang is sufficient) + +``` +# FreeBSD / DragonFly BSD +pkg install cmake git gcc openssl + +# OpenBSD +pkg_add cmake git gcc openssl + +# NetBSD +pkgin install cmake git gcc openssl +``` + +Once everything is in place, configure and build the project from the source +directory: + +``` +cmake -B build . +cmake --build build +``` + +This will build two `mgclient` library flavours: a static library (usually +named `libmgclient.a`) and a shared library (usually named `libmgclient.so`). + +To install the libraries and corresponding header files run: + +``` +cmake --install build +``` + +This will install to system default installation directory. If you want to +change this location, use the `-DCMAKE_INSTALL_PREFIX` option when configuring. + +If you want to build and run tests, configure with: + +``` +cmake -B build -DBUILD_TESTING=ON -DBUILD_TESTING_INTEGRATION=ON . +cmake --build build +ctest --test-dir build +``` + +NOTE: FreeBSD defaults to Clang with libc++. If you instead configure the build +with GCC (e.g. from `lang/gcc`), point CMake at it explicitly with +`-DCMAKE_C_COMPILER=` / `-DCMAKE_CXX_COMPILER=`. + ## Building and installing on Windows To build and install mgclient from source on Windows you will need: From 38851789d8b69ef172bd044cae18a037d9178455 Mon Sep 17 00:00:00 2001 From: matt james Date: Tue, 21 Jul 2026 11:20:38 +0100 Subject: [PATCH 6/9] fix formatting --- src/linux/mgcommon.h | 3 ++- src/linux/mgsocket.c | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/linux/mgcommon.h b/src/linux/mgcommon.h index f674dc6..51c5823 100644 --- a/src/linux/mgcommon.h +++ b/src/linux/mgcommon.h @@ -15,7 +15,8 @@ #ifndef MGCLIENT_LINUX_MGCOMMON_H #define MGCLIENT_LINUX_MGCOMMON_H -#if !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && !defined(__DragonFly__) +#if !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && \ + !defined(__DragonFly__) #include #else #include diff --git a/src/linux/mgsocket.c b/src/linux/mgsocket.c index f3f27f5..faaae29 100644 --- a/src/linux/mgsocket.c +++ b/src/linux/mgsocket.c @@ -102,25 +102,25 @@ int mg_socket_options(int sock, mg_session *session) { int optname; int optval; } socket_options[] = { - // disable Nagle algorithm for performance reasons - {IPPROTO_TCP, TCP_NODELAY, 1}, - // turn keep-alive on - {SOL_SOCKET, SO_KEEPALIVE, 1}, + // disable Nagle algorithm for performance reasons + {IPPROTO_TCP, TCP_NODELAY, 1}, + // turn keep-alive on + {SOL_SOCKET, SO_KEEPALIVE, 1}, // The per-socket keep-alive tuning options below are not portable: OpenBSD, // for one, only supports SO_KEEPALIVE and tunes the timers system-wide via // sysctl (net.inet.tcp.keep*). Guard each on its macro so those platforms fall // back to plain keep-alive instead of failing to build. #ifdef TCP_KEEPIDLE - // wait 20s before sending keep-alive packets - {IPPROTO_TCP, TCP_KEEPIDLE, 20}, + // wait 20s before sending keep-alive packets + {IPPROTO_TCP, TCP_KEEPIDLE, 20}, #endif #ifdef TCP_KEEPCNT - // 4 keep-alive packets must fail to close - {IPPROTO_TCP, TCP_KEEPCNT, 4}, + // 4 keep-alive packets must fail to close + {IPPROTO_TCP, TCP_KEEPCNT, 4}, #endif #ifdef TCP_KEEPINTVL - // send keep-alive packets every 15s - {IPPROTO_TCP, TCP_KEEPINTVL, 15}, + // send keep-alive packets every 15s + {IPPROTO_TCP, TCP_KEEPINTVL, 15}, #endif }; const size_t OPTCNT = sizeof(socket_options) / sizeof(socket_options[0]); From 426aade5d2063657bf1717afcbe51defb72c498a Mon Sep 17 00:00:00 2001 From: matt james Date: Tue, 21 Jul 2026 11:27:03 +0100 Subject: [PATCH 7/9] fix inverted logic --- src/linux/mgcommon.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/linux/mgcommon.h b/src/linux/mgcommon.h index 51c5823..f29f37c 100644 --- a/src/linux/mgcommon.h +++ b/src/linux/mgcommon.h @@ -15,8 +15,8 @@ #ifndef MGCLIENT_LINUX_MGCOMMON_H #define MGCLIENT_LINUX_MGCOMMON_H -#if !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && \ - !defined(__DragonFly__) +#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \ + defined(__DragonFly__) #include #else #include From 156b41399bb4a493225d7058063b1f025dd6a4a0 Mon Sep 17 00:00:00 2001 From: matt james Date: Tue, 21 Jul 2026 11:32:58 +0100 Subject: [PATCH 8/9] fix libstdc++ linking --- tests/CMakeLists.txt | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4678e73..717122d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -49,7 +49,20 @@ elseif(MGCLIENT_ON_POSIX) target_link_libraries(client -Wl,--wrap=mg_secure_transport_init) endif() add_gtest(transport transport.cpp) -if(MGCLIENT_ON_APPLE OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") +# transport.cpp uses std::filesystem, whose runtime support library depends on +# the C++ standard library in use rather than the compiler or OS: libc++ +# (Clang's default on Apple and the BSDs) bundles it into libc++, while +# libstdc++ (GCC, and Clang on Linux) splits it into a separate libstdc++fs for +# versions older than GCC 9. Detect libc++ via its version macro and link the +# matching library. +include(CheckCXXSourceCompiles) +check_cxx_source_compiles(" +#include +#ifndef _LIBCPP_VERSION +#error not libc++ +#endif +int main() { return 0; }" MGCLIENT_USING_LIBCXX) +if(MGCLIENT_USING_LIBCXX) target_link_libraries(transport c++) else() target_link_libraries(transport stdc++fs) From 7cc99dd87e90d98b60b4270494a396c14bc6f3b7 Mon Sep 17 00:00:00 2001 From: matt james Date: Tue, 21 Jul 2026 11:47:40 +0100 Subject: [PATCH 9/9] bump cmake minimum --- .github/workflows/ci.yml | 2 +- CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 45adeef..7bfcf26 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -323,7 +323,7 @@ jobs: run: | mkdir build cd build - cmake .. -G "MinGW Makefiles" -DBUILD_TESTING=ON -DBUILD_TESTING_INTEGRATION=ON -DC_WARNINGS_AS_ERRORS=ON -DCPP_WARNINGS_AS_ERRORS=ON -DCMAKE_POLICY_VERSION_MINIMUM=3.5 + cmake .. -G "MinGW Makefiles" -DBUILD_TESTING=ON -DBUILD_TESTING_INTEGRATION=ON -DC_WARNINGS_AS_ERRORS=ON -DCPP_WARNINGS_AS_ERRORS=ON cmake --build . --parallel - name: Verify Memgraph is running under WSL diff --git a/CMakeLists.txt b/CMakeLists.txt index f46937e..29514c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -cmake_minimum_required(VERSION 3.8) +cmake_minimum_required(VERSION 3.10) if(WASM) execute_process(COMMAND ${CMAKE_SOURCE_DIR}/wasm/install_deps.sh)