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 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,