From 91af0291afd391c667a0ebf30a67134645682af4 Mon Sep 17 00:00:00 2001 From: Arun Sharma Date: Mon, 26 Oct 2026 18:13:24 -0700 Subject: [PATCH 1/2] feat!: let the engine control SystemConfig defaults Stop pinning a copy of every engine default in this binding. The JS Database constructor now defaults all settings to null, and the native layer starts from SystemConfig() and only applies values that are neither null nor undefined. Unset settings follow engine-side default changes automatically. Explicitly passed values behave exactly as before. BREAKING CHANGE: Database() settings parameters changed from concrete defaults to null meaning "engine default". --- src_cpp/include/node_database.h | 19 +++--- src_cpp/node_database.cpp | 103 ++++++++++++++++++++++++-------- src_js/database.js | 69 ++++++++++++--------- src_js/lbug.d.ts | 38 ++++++------ 4 files changed, 150 insertions(+), 79 deletions(-) diff --git a/src_cpp/include/node_database.h b/src_cpp/include/node_database.h index e171961..85b67bc 100644 --- a/src_cpp/include/node_database.h +++ b/src_cpp/include/node_database.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include "main/lbug.h" #include @@ -26,15 +27,15 @@ class NodeDatabase : public Napi::ObjectWrap { private: std::string databasePath; - size_t bufferPoolSize; - bool enableCompression; - bool readOnly; - uint64_t maxDBSize; - bool autoCheckpoint; - int64_t checkpointThreshold; - bool throwOnWalReplayFailure; - bool enableChecksums; - bool enableDefaultHashIndex; + std::optional bufferPoolSize; + std::optional enableCompression; + std::optional readOnly; + std::optional maxDBSize; + std::optional autoCheckpoint; + std::optional checkpointThreshold; + std::optional throwOnWalReplayFailure; + std::optional enableChecksums; + std::optional enableDefaultHashIndex; std::shared_ptr database; }; diff --git a/src_cpp/node_database.cpp b/src_cpp/node_database.cpp index 1cb92b3..57cab92 100644 --- a/src_cpp/node_database.cpp +++ b/src_cpp/node_database.cpp @@ -16,19 +16,60 @@ Napi::Object NodeDatabase::Init(Napi::Env env, Napi::Object exports) { return exports; } +namespace { + +// Null/undefined means "use the engine default"; the setting is left unset. +bool getOptionalInt64(const Napi::CallbackInfo& info, size_t index, int64_t& out) { + if (index >= info.Length() || info[index].IsNull() || info[index].IsUndefined()) { + return false; + } + out = info[index].As().Int64Value(); + return true; +} + +bool getOptionalBool(const Napi::CallbackInfo& info, size_t index, bool& out) { + if (index >= info.Length() || info[index].IsNull() || info[index].IsUndefined()) { + return false; + } + out = info[index].As().Value(); + return true; +} + +} // namespace + NodeDatabase::NodeDatabase(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { Napi::Env env = info.Env(); Napi::HandleScope scope(env); databasePath = info[0].ToString(); - bufferPoolSize = info[1].As().Int64Value(); - enableCompression = info[2].As().Value(); - readOnly = info[3].As().Value(); - maxDBSize = info[4].As().Int64Value(); - autoCheckpoint = info[5].As().Value(); - checkpointThreshold = info[6].As().Int64Value(); - throwOnWalReplayFailure = info[7].As().Value(); - enableChecksums = info[8].As().Value(); - enableDefaultHashIndex = info[9].As().Value(); + int64_t intValue = 0; + bool boolValue = false; + if (getOptionalInt64(info, 1, intValue)) { + bufferPoolSize = intValue; + } + if (getOptionalBool(info, 2, boolValue)) { + enableCompression = boolValue; + } + if (getOptionalBool(info, 3, boolValue)) { + readOnly = boolValue; + } + if (getOptionalInt64(info, 4, intValue)) { + maxDBSize = intValue; + } + if (getOptionalBool(info, 5, boolValue)) { + autoCheckpoint = boolValue; + } + if (getOptionalInt64(info, 6, intValue)) { + checkpointThreshold = intValue; + } + if (getOptionalBool(info, 7, boolValue)) { + throwOnWalReplayFailure = boolValue; + } + if (getOptionalBool(info, 8, boolValue)) { + enableChecksums = boolValue; + } + if (getOptionalBool(info, 9, boolValue)) { + enableDefaultHashIndex = boolValue; + } } Napi::Value NodeDatabase::InitSync(const Napi::CallbackInfo& info) { @@ -54,23 +95,35 @@ Napi::Value NodeDatabase::InitAsync(const Napi::CallbackInfo& info) { } void NodeDatabase::InitCppDatabase() { + // Start from the engine defaults and only apply explicitly set settings, + // so engine-side default changes propagate automatically. auto systemConfig = SystemConfig(); - if (bufferPoolSize > 0) { - systemConfig.bufferPoolSize = bufferPoolSize; - } - if (!enableCompression) { - systemConfig.enableCompression = enableCompression; - } - systemConfig.readOnly = readOnly; - if (maxDBSize > 0) { - systemConfig.maxDBSize = maxDBSize; - } - systemConfig.autoCheckpoint = autoCheckpoint; - systemConfig.throwOnWalReplayFailure = throwOnWalReplayFailure; - systemConfig.enableChecksums = enableChecksums; - systemConfig.enableDefaultHashIndex = enableDefaultHashIndex; - if (checkpointThreshold >= 0) { - systemConfig.checkpointThreshold = checkpointThreshold; + if (bufferPoolSize && *bufferPoolSize > 0) { + systemConfig.bufferPoolSize = static_cast(*bufferPoolSize); + } + if (enableCompression) { + systemConfig.enableCompression = *enableCompression; + } + if (readOnly) { + systemConfig.readOnly = *readOnly; + } + if (maxDBSize && *maxDBSize > 0) { + systemConfig.maxDBSize = static_cast(*maxDBSize); + } + if (autoCheckpoint) { + systemConfig.autoCheckpoint = *autoCheckpoint; + } + if (throwOnWalReplayFailure) { + systemConfig.throwOnWalReplayFailure = *throwOnWalReplayFailure; + } + if (enableChecksums) { + systemConfig.enableChecksums = *enableChecksums; + } + if (enableDefaultHashIndex) { + systemConfig.enableDefaultHashIndex = *enableDefaultHashIndex; + } + if (checkpointThreshold && *checkpointThreshold >= 0) { + systemConfig.checkpointThreshold = static_cast(*checkpointThreshold); } this->database = std::make_shared(databasePath, systemConfig); } diff --git a/src_js/database.js b/src_js/database.js index efee804..ab79624 100644 --- a/src_js/database.js +++ b/src_js/database.js @@ -11,34 +11,39 @@ class Database { * * @param {String} databasePath path to the database file. If the path is not specified, or empty, or equal to `:memory:`, the database will be created in memory. - * @param {Number} bufferManagerSize size of the buffer manager in bytes. - * @param {Boolean} enableCompression whether to enable compression. - * @param {Boolean} readOnly if true, database will be opened in read-only mode. - * @param {Number} maxDBSize maximum size of the database file in bytes. Note that + * @param {Number|null} bufferManagerSize size of the buffer manager in bytes. Null (default) uses the engine default. + * @param {Boolean|null} enableCompression whether to enable compression. Null (default) uses the engine default. + * @param {Boolean|null} readOnly if true, database will be opened in read-only mode. Null (default) uses the engine default. + * @param {Number|null} maxDBSize maximum size of the database file in bytes. Note that * this is introduced temporarily for now to get around with the default 8TB mmap - * address space limit some environment. - * @param {Boolean} autoCheckpoint If true, the database will automatically checkpoint when the size of - * the WAL file exceeds the checkpoint threshold. - * @param {Number} checkpointThreshold The threshold of the WAL file size in bytes. When the size of the + * address space limit some environment. Null (default) uses the engine default. + * @param {Boolean|null} autoCheckpoint If true, the database will automatically checkpoint when the size of + * the WAL file exceeds the checkpoint threshold. Null (default) uses the engine default. + * @param {Number|null} checkpointThreshold The threshold of the WAL file size in bytes. When the size of the * WAL file exceeds this threshold, the database will checkpoint if autoCheckpoint is true. - * @param {Boolean} throwOnWalReplayFailure If true, any WAL replaying failure when loading the database + * Null (default) uses the engine default. + * @param {Boolean|null} throwOnWalReplayFailure If true, any WAL replaying failure when loading the database * will throw an error. Otherwise, Lbug will silently ignore the failure and replay up to where - * the error occured. - * @param {Boolean} enableChecksums If true, the database will use checksums to detect corruption in the - * WAL file. - * @param {Boolean} enableDefaultHashIndex If true, node tables create the default primary-key hash index. + * the error occured. Null (default) uses the engine default. + * @param {Boolean|null} enableChecksums If true, the database will use checksums to detect corruption in the + * WAL file. Null (default) uses the engine default. + * @param {Boolean|null} enableDefaultHashIndex If true, node tables create the default primary-key hash index. + * Null (default) uses the engine default. + * + * Any setting left as null/undefined is not forwarded, so the engine default applies and + * engine-side default changes propagate automatically. */ constructor( databasePath, - bufferManagerSize = 0, - enableCompression = true, - readOnly = false, - maxDBSize = 0, - autoCheckpoint = true, - checkpointThreshold = -1, - throwOnWalReplayFailure = true, - enableChecksums = true, - enableDefaultHashIndex = true, + bufferManagerSize = null, + enableCompression = null, + readOnly = null, + maxDBSize = null, + autoCheckpoint = null, + checkpointThreshold = null, + throwOnWalReplayFailure = null, + enableChecksums = null, + enableDefaultHashIndex = null, ) { if (!databasePath) { databasePath = ":memory:"; @@ -46,18 +51,26 @@ class Database { else if (typeof databasePath !== "string") { throw new Error("Database path must be a string."); } - if (typeof bufferManagerSize !== "number" || bufferManagerSize < 0) { + if (bufferManagerSize != null && (typeof bufferManagerSize !== "number" || bufferManagerSize < 0)) { throw new Error("Buffer manager size must be a positive integer."); } - if (typeof maxDBSize !== "number" || maxDBSize < 0) { + if (maxDBSize != null && (typeof maxDBSize !== "number" || maxDBSize < 0)) { throw new Error("Max DB size must be a positive integer."); } - if (typeof checkpointThreshold !== "number" || maxDBSize < -1) { + // Note: negative explicit values (e.g. the historical -1 default) are accepted here and + // ignored by the native layer, which keeps the engine default for them. + if (checkpointThreshold != null && typeof checkpointThreshold !== "number") { throw new Error("Checkpoint threshold must be a positive integer."); } - bufferManagerSize = Math.floor(bufferManagerSize); - maxDBSize = Math.floor(maxDBSize); - checkpointThreshold = Math.floor(checkpointThreshold); + if (bufferManagerSize != null) { + bufferManagerSize = Math.floor(bufferManagerSize); + } + if (maxDBSize != null) { + maxDBSize = Math.floor(maxDBSize); + } + if (checkpointThreshold != null) { + checkpointThreshold = Math.floor(checkpointThreshold); + } this._database = new LbugNative.NodeDatabase( databasePath, bufferManagerSize, diff --git a/src_js/lbug.d.ts b/src_js/lbug.d.ts index 9a2217b..8ab7e7f 100644 --- a/src_js/lbug.d.ts +++ b/src_js/lbug.d.ts @@ -104,21 +104,24 @@ export type LbugValue = /** * Configuration options for the database system. + * + * Any option left undefined uses the engine default; explicitly set options + * override the engine configuration. */ export interface SystemConfig { - /** Size of the buffer pool in bytes */ + /** Size of the buffer pool in bytes (engine default if omitted) */ bufferPoolSize?: number; - /** Whether to enable compression */ + /** Whether to enable compression (engine default if omitted) */ enableCompression?: boolean; - /** Whether to open the database in read-only mode */ + /** Whether to open the database in read-only mode (engine default if omitted) */ readOnly?: boolean; - /** Maximum size of the database in bytes */ + /** Maximum size of the database in bytes (engine default if omitted) */ maxDBSize?: number; - /** Whether to enable automatic checkpoints */ + /** Whether to enable automatic checkpoints (engine default if omitted) */ autoCheckpoint?: boolean; - /** Threshold for automatic checkpoints */ + /** Threshold for automatic checkpoints (engine default if omitted) */ checkpointThreshold?: number; - /** Whether node tables create the default primary-key hash index */ + /** Whether node tables create the default primary-key hash index (engine default if omitted) */ enableDefaultHashIndex?: boolean; } @@ -132,17 +135,18 @@ export function json(value: string | LbugValue): LbugValue; */ export class Database { /** - * Constructs a new Database instance. + * Constructs a new Database instance. Any setting left undefined/null uses + * the engine default; explicitly set settings override it. * @param databasePath Path to the database directory (defaults to ":memory:") - * @param bufferManagerSize Size of the buffer manager in bytes - * @param enableCompression Whether to enable compression - * @param readOnly Whether to open in read-only mode - * @param maxDBSize Maximum size of the database in bytes - * @param autoCheckpoint Whether to enable automatic checkpoints - * @param checkpointThreshold Threshold for automatic checkpoints - * @param throwOnWalReplayFailure Whether WAL replay failures should throw - * @param enableChecksums Whether checksum validation is enabled - * @param enableDefaultHashIndex Whether node tables create the default primary-key hash index + * @param bufferManagerSize Size of the buffer manager in bytes (engine default if omitted) + * @param enableCompression Whether to enable compression (engine default if omitted) + * @param readOnly Whether to open in read-only mode (engine default if omitted) + * @param maxDBSize Maximum size of the database in bytes (engine default if omitted) + * @param autoCheckpoint Whether to enable automatic checkpoints (engine default if omitted) + * @param checkpointThreshold Threshold for automatic checkpoints (engine default if omitted) + * @param throwOnWalReplayFailure Whether WAL replay failures should throw (engine default if omitted) + * @param enableChecksums Whether checksum validation is enabled (engine default if omitted) + * @param enableDefaultHashIndex Whether node tables create the default primary-key hash index (engine default if omitted) */ constructor( databasePath?: string, From ca7490d17b016bc86e9997be20d2c14c497878cf Mon Sep 17 00:00:00 2001 From: Arun Sharma Date: Mon, 26 Oct 2026 18:40:59 -0700 Subject: [PATCH 2/2] ci: pin OpenSSL 3 on macOS like the main repo Homebrew now also ships openssl@4, which CMake would otherwise prefer, but the top-level CMakeLists enforces OpenSSL 3 via a version guard. Mirror ci-workflow.yml: install openssl@3 and export OPENSSL_ROOT_DIR. --- .github/workflows/ci.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f43e7f3..a931756 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -145,6 +145,15 @@ jobs: ccache -s make GEN=Ninja nodejs && ccache -s + - name: Install OpenSSL 3 (macOS) + if: ${{ matrix.mac_env }} + run: | + # Pin to OpenSSL 3: Homebrew now also ships openssl@4, which CMake + # would otherwise prefer. The top-level CMakeLists enforces OpenSSL 3 + # via a version guard, so point CMake at openssl@3 explicitly. + brew list openssl@3 >/dev/null 2>&1 || brew install openssl@3 + echo "OPENSSL_ROOT_DIR=$(brew --prefix openssl@3)" >> "$GITHUB_ENV" + - name: Build Node.js native module (non-Windows) if: ${{ matrix.platform != 'win32' }} run: make GEN=Ninja nodejs