Skip to content

Commit 81c9fea

Browse files
committed
feat!: let the engine control SystemConfig defaults
Stop pinning a copy of every engine default in this binding. All engine settings now default to None, and only explicitly set values are forwarded to either backend (capi starts from lbug_default_system_config(), pybind from SystemConfig()). Unset settings now follow engine-side default changes automatically. BREAKING CHANGE: Database() settings parameters changed from concrete defaults (e.g. enable_multi_writes=False) to None meaning "engine default"; explicitly passed values behave as before.
1 parent 2e1adc0 commit 81c9fea

4 files changed

Lines changed: 153 additions & 93 deletions

File tree

‎src_cpp/include/py_database.h‎

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22

33
#include <memory>
4+
#include <optional>
45

56
#include "main/lbug.h"
67
#include "main/storage_driver.h"
@@ -19,10 +20,16 @@ class PyDatabase {
1920

2021
static uint64_t getStorageVersion();
2122

22-
explicit PyDatabase(const std::string& databasePath, uint64_t bufferPoolSize,
23-
uint64_t maxNumThreads, bool compression, bool readOnly, uint64_t maxDBSize,
24-
bool autoCheckpoint, int64_t checkpointThreshold, bool throwOnWalReplayFailure = true,
25-
bool enableChecksums = true, bool enableMultiWrites = false);
23+
explicit PyDatabase(const std::string& databasePath,
24+
std::optional<uint64_t> bufferPoolSize = std::nullopt,
25+
std::optional<uint64_t> maxNumThreads = std::nullopt,
26+
std::optional<bool> compression = std::nullopt, std::optional<bool> readOnly = std::nullopt,
27+
std::optional<uint64_t> maxDBSize = std::nullopt,
28+
std::optional<bool> autoCheckpoint = std::nullopt,
29+
std::optional<int64_t> checkpointThreshold = std::nullopt,
30+
std::optional<bool> throwOnWalReplayFailure = std::nullopt,
31+
std::optional<bool> enableChecksums = std::nullopt,
32+
std::optional<bool> enableMultiWrites = std::nullopt);
2633

2734
~PyDatabase();
2835

‎src_cpp/py_database.cpp‎

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <memory>
44
#include <mutex>
5+
#include <optional>
56

67
#include "common/exception/runtime.h"
78
#include "extension/extension.h"
@@ -13,14 +14,17 @@ using namespace lbug::common;
1314

1415
void PyDatabase::initialize(py::handle& m) {
1516
py::class_<PyDatabase>(m, "Database")
16-
.def(py::init<const std::string&, uint64_t, uint64_t, bool, bool, uint64_t, bool, int64_t,
17-
bool, bool, bool>(),
18-
py::arg("database_path"), py::arg("buffer_pool_size") = 0,
19-
py::arg("max_num_threads") = 0, py::arg("compression") = true,
20-
py::arg("read_only") = false, py::arg("max_db_size") = -1u,
21-
py::arg("auto_checkpoint") = true, py::arg("checkpoint_threshold") = -1,
22-
py::arg("throw_on_wal_replay_failure") = true, py::arg("enable_checksums") = true,
23-
py::arg("enable_multi_writes") = false)
17+
.def(py::init<const std::string&, std::optional<uint64_t>, std::optional<uint64_t>,
18+
std::optional<bool>, std::optional<bool>, std::optional<uint64_t>,
19+
std::optional<bool>, std::optional<int64_t>, std::optional<bool>,
20+
std::optional<bool>, std::optional<bool>>(),
21+
py::arg("database_path"), py::arg("buffer_pool_size") = py::none(),
22+
py::arg("max_num_threads") = py::none(), py::arg("compression") = py::none(),
23+
py::arg("read_only") = py::none(), py::arg("max_db_size") = py::none(),
24+
py::arg("auto_checkpoint") = py::none(), py::arg("checkpoint_threshold") = py::none(),
25+
py::arg("throw_on_wal_replay_failure") = py::none(),
26+
py::arg("enable_checksums") = py::none(),
27+
py::arg("enable_multi_writes") = py::none())
2428
.def("scan_node_table_as_int64", &PyDatabase::scanNodeTable<std::int64_t>,
2529
py::arg("table_name"), py::arg("prop_name"), py::arg("indices"), py::arg("np_array"),
2630
py::arg("num_threads"))
@@ -49,18 +53,46 @@ uint64_t PyDatabase::getStorageVersion() {
4953
return Version::getStorageVersion();
5054
}
5155

52-
PyDatabase::PyDatabase(const std::string& databasePath, uint64_t bufferPoolSize,
53-
uint64_t maxNumThreads, bool compression, bool readOnly, uint64_t maxDBSize,
54-
bool autoCheckpoint, int64_t checkpointThreshold, bool throwOnWalReplayFailure,
55-
bool enableChecksums, bool enableMultiWrites) {
56-
auto systemConfig = SystemConfig(bufferPoolSize, maxNumThreads, compression, readOnly,
57-
maxDBSize, autoCheckpoint);
58-
if (checkpointThreshold >= 0) {
59-
systemConfig.checkpointThreshold = static_cast<uint64_t>(checkpointThreshold);
56+
PyDatabase::PyDatabase(const std::string& databasePath, std::optional<uint64_t> bufferPoolSize,
57+
std::optional<uint64_t> maxNumThreads, std::optional<bool> compression,
58+
std::optional<bool> readOnly, std::optional<uint64_t> maxDBSize,
59+
std::optional<bool> autoCheckpoint, std::optional<int64_t> checkpointThreshold,
60+
std::optional<bool> throwOnWalReplayFailure, std::optional<bool> enableChecksums,
61+
std::optional<bool> enableMultiWrites) {
62+
// Start from the engine defaults and only apply explicitly set settings,
63+
// so engine-side default changes propagate automatically.
64+
auto systemConfig = SystemConfig();
65+
if (bufferPoolSize.has_value()) {
66+
systemConfig.bufferPoolSize = bufferPoolSize.value();
67+
}
68+
if (maxNumThreads.has_value()) {
69+
systemConfig.maxNumThreads = maxNumThreads.value();
70+
}
71+
if (compression.has_value()) {
72+
systemConfig.enableCompression = compression.value();
73+
}
74+
if (readOnly.has_value()) {
75+
systemConfig.readOnly = readOnly.value();
76+
}
77+
if (maxDBSize.has_value()) {
78+
systemConfig.maxDBSize = maxDBSize.value();
79+
}
80+
if (autoCheckpoint.has_value()) {
81+
systemConfig.autoCheckpoint = autoCheckpoint.value();
82+
}
83+
if (checkpointThreshold.has_value() && checkpointThreshold.value() >= 0) {
84+
systemConfig.checkpointThreshold =
85+
static_cast<uint64_t>(checkpointThreshold.value());
86+
}
87+
if (throwOnWalReplayFailure.has_value()) {
88+
systemConfig.throwOnWalReplayFailure = throwOnWalReplayFailure.value();
89+
}
90+
if (enableChecksums.has_value()) {
91+
systemConfig.enableChecksums = enableChecksums.value();
92+
}
93+
if (enableMultiWrites.has_value()) {
94+
systemConfig.enableMultiWrites = enableMultiWrites.value();
6095
}
61-
systemConfig.throwOnWalReplayFailure = throwOnWalReplayFailure;
62-
systemConfig.enableChecksums = enableChecksums;
63-
systemConfig.enableMultiWrites = enableMultiWrites;
6496
state = std::make_shared<PyDatabaseState>();
6597
state->database = std::make_unique<Database>(databasePath, systemConfig);
6698
lbug::extension::ExtensionUtils::addTableFunc<lbug::PandasScanFunction>(*state->database);

‎src_py/_lbug_capi.py‎

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1197,33 +1197,43 @@ class Database:
11971197
def __init__(
11981198
self,
11991199
database_path: str,
1200-
buffer_pool_size: int = 0,
1201-
max_num_threads: int = 0,
1202-
compression: bool = True,
1203-
read_only: bool = False,
1200+
buffer_pool_size: int | None = None,
1201+
max_num_threads: int | None = None,
1202+
compression: bool | None = None,
1203+
read_only: bool | None = None,
12041204
max_db_size: int | None = None,
1205-
auto_checkpoint: bool = True,
1206-
checkpoint_threshold: int = -1,
1207-
throw_on_wal_replay_failure: bool = True,
1208-
enable_checksums: bool = True,
1209-
enable_multi_writes: bool = False,
1205+
auto_checkpoint: bool | None = None,
1206+
checkpoint_threshold: int | None = None,
1207+
throw_on_wal_replay_failure: bool | None = None,
1208+
enable_checksums: bool | None = None,
1209+
enable_multi_writes: bool | None = None,
12101210
):
12111211
self._database = _LbugDatabase()
1212+
# Start from the engine defaults and only apply explicitly set
1213+
# settings, so engine-side default changes propagate automatically.
12121214
config = _LIB.lbug_default_system_config()
1213-
config.buffer_pool_size = buffer_pool_size
1214-
config.max_num_threads = max_num_threads
1215-
config.enable_compression = compression
1216-
config.read_only = read_only
1215+
if buffer_pool_size is not None:
1216+
config.buffer_pool_size = buffer_pool_size
1217+
if max_num_threads is not None:
1218+
config.max_num_threads = max_num_threads
1219+
if compression is not None:
1220+
config.enable_compression = compression
1221+
if read_only is not None:
1222+
config.read_only = read_only
12171223

12181224
if max_db_size is not None:
12191225
config.max_db_size = max_db_size
12201226

1221-
config.auto_checkpoint = auto_checkpoint
1222-
if checkpoint_threshold >= 0:
1227+
if auto_checkpoint is not None:
1228+
config.auto_checkpoint = auto_checkpoint
1229+
if checkpoint_threshold is not None and checkpoint_threshold >= 0:
12231230
config.checkpoint_threshold = checkpoint_threshold
1224-
config.throw_on_wal_replay_failure = throw_on_wal_replay_failure
1225-
config.enable_checksums = enable_checksums
1226-
config.enable_multi_writes = enable_multi_writes
1231+
if throw_on_wal_replay_failure is not None:
1232+
config.throw_on_wal_replay_failure = throw_on_wal_replay_failure
1233+
if enable_checksums is not None:
1234+
config.enable_checksums = enable_checksums
1235+
if enable_multi_writes is not None:
1236+
config.enable_multi_writes = enable_multi_writes
12271237

12281238
state = _LIB.lbug_database_init(
12291239
database_path.encode("utf-8"), config, ctypes.byref(self._database)

‎src_py/database.py‎

Lines changed: 63 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,17 @@ def __init__(
3434
self,
3535
database_path: str | Path | None = None,
3636
*,
37-
buffer_pool_size: int = 0,
38-
max_num_threads: int = 0,
39-
compression: bool = True,
37+
buffer_pool_size: int | None = None,
38+
max_num_threads: int | None = None,
39+
compression: bool | None = None,
4040
lazy_init: bool = False,
41-
read_only: bool = False,
41+
read_only: bool | None = None,
4242
max_db_size: int | None = None,
43-
auto_checkpoint: bool = True,
44-
checkpoint_threshold: int = -1,
45-
throw_on_wal_replay_failure: bool = True,
46-
enable_checksums: bool = True,
47-
enable_multi_writes: bool = False,
43+
auto_checkpoint: bool | None = None,
44+
checkpoint_threshold: int | None = None,
45+
throw_on_wal_replay_failure: bool | None = None,
46+
enable_checksums: bool | None = None,
47+
enable_multi_writes: bool | None = None,
4848
backend: str = "auto",
4949
):
5050
"""
@@ -54,54 +54,60 @@ def __init__(
5454
The path to database files. If the path is not specified, or empty, or equal to `:memory:`, the database
5555
will be created in memory.
5656
57-
buffer_pool_size : int
58-
The maximum size of buffer pool in bytes. Defaults to ~80% of system memory.
57+
buffer_pool_size : int, optional
58+
The maximum size of buffer pool in bytes. If None (default), the
59+
engine default (~80% of system memory) is used.
5960
60-
max_num_threads : int
61-
The maximum number of threads to use for executing queries.
61+
max_num_threads : int, optional
62+
The maximum number of threads to use for executing queries. If None
63+
(default), the engine default is used.
6264
63-
compression : bool
64-
Enable database compression.
65+
compression : bool, optional
66+
Enable database compression. If None (default), the engine default
67+
is used.
6568
6669
lazy_init : bool
6770
If True, the database will not be initialized until the first query.
6871
This is useful when the database is not used in the main thread or
6972
when the main process is forked.
7073
Default to False.
7174
72-
read_only : bool
75+
read_only : bool, optional
7376
If true, the database is opened read-only. No write transactions is
7477
allowed on the `Database` object. Multiple read-only `Database`
7578
objects can be created with the same database path. However, there
7679
cannot be multiple `Database` objects created with the same
7780
database path.
78-
Default to False.
81+
If None (default), the engine default is used.
7982
8083
max_db_size : int, optional
8184
The maximum size of the database in bytes. Note that this is introduced
8285
temporarily for now to get around with the default 8TB mmap address
8386
space limit some environment. This will be removed once we implement
8487
a better solution later. If not specified, the backend's default is used.
8588
86-
auto_checkpoint: bool
89+
auto_checkpoint: bool, optional
8790
If true, the database will automatically checkpoint when the size of
88-
the WAL file exceeds the checkpoint threshold.
91+
the WAL file exceeds the checkpoint threshold. If None (default),
92+
the engine default is used.
8993
90-
checkpoint_threshold: int
94+
checkpoint_threshold: int, optional
9195
The threshold of the WAL file size in bytes. When the size of the
9296
WAL file exceeds this threshold, the database will checkpoint if autoCheckpoint is true.
97+
If None (default), the engine default is used.
9398
94-
throw_on_wal_replay_failure: bool
99+
throw_on_wal_replay_failure: bool, optional
95100
If true, any WAL replaying failure when loading the database will throw an error.
96101
Otherwise, Lbug will silently ignore the failure and replay up to where the error
97-
occured.
102+
occured. If None (default), the engine default is used.
98103
99-
enable_checksums: bool
104+
enable_checksums: bool, optional
100105
If true, the database will use checksums to detect corruption in the
101-
WAL file.
106+
WAL file. If None (default), the engine default is used.
102107
103-
enable_multi_writes: bool
104-
If true, multiple concurrent write transactions are allowed. Default to False.
108+
enable_multi_writes: bool, optional
109+
If true, multiple concurrent write transactions are allowed. If None
110+
(default), the engine default is used.
105111
106112
backend : {"auto", "capi", "pybind"}
107113
Backend to use for database/query execution.
@@ -213,6 +219,35 @@ def __getstate__(self) -> dict[str, Any]:
213219
}
214220
return state
215221

222+
def _engine_kwargs(self) -> dict[str, Any]:
223+
"""Return only the explicitly set engine settings.
224+
225+
Unset (None) settings are omitted so the engine default applies,
226+
instead of this binding pinning its own copy of the default.
227+
"""
228+
kwargs: dict[str, Any] = {}
229+
if self.buffer_pool_size is not None:
230+
kwargs["buffer_pool_size"] = self.buffer_pool_size
231+
if self.max_num_threads is not None:
232+
kwargs["max_num_threads"] = self.max_num_threads
233+
if self.compression is not None:
234+
kwargs["compression"] = self.compression
235+
if self.read_only is not None:
236+
kwargs["read_only"] = self.read_only
237+
if self.max_db_size is not None:
238+
kwargs["max_db_size"] = self.max_db_size
239+
if self.auto_checkpoint is not None:
240+
kwargs["auto_checkpoint"] = self.auto_checkpoint
241+
if self.checkpoint_threshold is not None:
242+
kwargs["checkpoint_threshold"] = self.checkpoint_threshold
243+
if self.throw_on_wal_replay_failure is not None:
244+
kwargs["throw_on_wal_replay_failure"] = self.throw_on_wal_replay_failure
245+
if self.enable_checksums is not None:
246+
kwargs["enable_checksums"] = self.enable_checksums
247+
if self.enable_multi_writes is not None:
248+
kwargs["enable_multi_writes"] = self.enable_multi_writes
249+
return kwargs
250+
216251
def init_database(self) -> None:
217252
"""Initialize the database."""
218253
self.check_for_database_close()
@@ -221,17 +256,7 @@ def init_database(self) -> None:
221256
self._database = self.init_pybind_database()
222257
else:
223258
self._database = get_capi_module().Database(
224-
self.database_path,
225-
self.buffer_pool_size,
226-
self.max_num_threads,
227-
self.compression,
228-
self.read_only,
229-
self.max_db_size,
230-
self.auto_checkpoint,
231-
self.checkpoint_threshold,
232-
self.throw_on_wal_replay_failure,
233-
self.enable_checksums,
234-
self.enable_multi_writes,
259+
self.database_path, **self._engine_kwargs()
235260
)
236261

237262
def init_pybind_database(self) -> Any | None:
@@ -241,21 +266,7 @@ def init_pybind_database(self) -> Any | None:
241266
if pybind_module is None:
242267
return None
243268
if self._pybind_database is None:
244-
kwargs = {
245-
"database_path": self.database_path,
246-
"buffer_pool_size": self.buffer_pool_size,
247-
"max_num_threads": self.max_num_threads,
248-
"compression": self.compression,
249-
"read_only": self.read_only,
250-
"auto_checkpoint": self.auto_checkpoint,
251-
"checkpoint_threshold": self.checkpoint_threshold,
252-
"throw_on_wal_replay_failure": self.throw_on_wal_replay_failure,
253-
"enable_checksums": self.enable_checksums,
254-
"enable_multi_writes": self.enable_multi_writes,
255-
}
256-
257-
if self.max_db_size is not None:
258-
kwargs["max_db_size"] = self.max_db_size
269+
kwargs = {"database_path": self.database_path, **self._engine_kwargs()}
259270

260271
self._pybind_database = pybind_module.Database(**kwargs)
261272

0 commit comments

Comments
 (0)