Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions include/lbug_rs.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,11 @@ inline lbug::common::LogicalTypeID logical_type_get_logical_type_id(
}

/* Database */
// Boolean settings use a tristate: negative keeps the engine default.
std::unique_ptr<lbug::main::Database> new_database(std::string_view databasePath,
uint64_t bufferPoolSize, uint64_t maxNumThreads, bool enableCompression, bool readOnly,
uint64_t maxDBSize, bool autoCheckpoint, int64_t checkpointThreshold,
bool throwOnWalReplayFailure, bool enableChecksums, bool enableMultiWrites);
uint64_t bufferPoolSize, uint64_t maxNumThreads, int64_t enableCompression, int64_t readOnly,
uint64_t maxDBSize, int64_t autoCheckpoint, int64_t checkpointThreshold,
int64_t throwOnWalReplayFailure, int64_t enableChecksums, int64_t enableMultiWrites);

void database_set_logging_level(lbug::main::Database& database, const std::string& level);

Expand Down
135 changes: 68 additions & 67 deletions src/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,113 +14,109 @@ pub struct Database {
unsafe impl Send for Database {}
unsafe impl Sync for Database {}

#[derive(Clone, Debug)]
#[derive(Clone, Debug, Default)]
/// Configuration options for the database.
///
/// Every field defaults to `None`, which means "use the engine default".
/// Only explicitly set fields override the engine configuration, so
/// engine-side default changes apply automatically without a binding update.
pub struct SystemConfig {
/// Max size of the buffer pool in bytes
///
/// The larger the buffer pool, the more data from the database files is kept in memory,
/// reducing the amount of File I/O.
///
/// Defaults to an auto-detected size
buffer_pool_size: u64,
/// The maximum number of threads to use when executing queries
/// Defaults to auto-detect from the number of processors
max_num_threads: u64,
/// When true, new columns will be compressed if possible
/// Defaults to true
enable_compression: bool,
/// Whether or not the database can be written to from this instance
/// Defaults to not read-only
read_only: bool,
/// The maximum size of the database. Defaults to 8TB
max_db_size: u64,
/// If None (default), the engine default (auto-detected size) is used.
buffer_pool_size: Option<u64>,
/// The maximum number of threads to use when executing queries.
/// If None (default), the engine default (auto-detected) is used.
max_num_threads: Option<u64>,
/// When true, new columns will be compressed if possible.
/// If None (default), the engine default is used.
enable_compression: Option<bool>,
/// Whether or not the database can be written to from this instance.
/// If None (default), the engine default (writable) is used.
read_only: Option<bool>,
/// The maximum size of the database.
/// If None (default), the engine default is used.
max_db_size: Option<u64>,
/// If true, the database will automatically checkpoint when the size of the WAL file exceeds the checkpoint threshold.
auto_checkpoint: bool,
/// If None (default), the engine default is used.
auto_checkpoint: Option<bool>,
/// 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.
checkpoint_threshold: i64,
/// If None (default), the engine default is used.
checkpoint_threshold: Option<i64>,
/// 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.
throw_on_wal_replay_failure: bool,
/// If None (default), the engine default is used.
throw_on_wal_replay_failure: Option<bool>,
/// If true, the database will use checksums to detect corruption in the WAL file.
enable_checksums: bool,
/// If None (default), the engine default is used.
enable_checksums: Option<bool>,
/// If true, multiple concurrent write transactions are allowed.
/// Defaults to false
enable_multi_writes: bool,
/// If None (default), the engine default is used.
enable_multi_writes: Option<bool>,
}

#[cfg(test)]
pub(crate) const SYSTEM_CONFIG_FOR_TESTS: SystemConfig = SystemConfig {
buffer_pool_size: 0,
max_num_threads: 0,
enable_compression: true,
read_only: false,
buffer_pool_size: None,
max_num_threads: None,
enable_compression: None,
read_only: None,
// Use a smaller max DB size since these tests don't need a large DB and it limits the number of
// databases which can be open in a single process
max_db_size: 16 * 1024 * 1024 * 1024,
auto_checkpoint: true,
checkpoint_threshold: -1_i64,
throw_on_wal_replay_failure: true,
enable_checksums: true,
enable_multi_writes: false,
max_db_size: Some(16 * 1024 * 1024 * 1024),
auto_checkpoint: None,
checkpoint_threshold: None,
throw_on_wal_replay_failure: None,
enable_checksums: None,
enable_multi_writes: None,
};

impl Default for SystemConfig {
fn default() -> SystemConfig {
SystemConfig {
buffer_pool_size: 0,
max_num_threads: 0,
enable_compression: true,
read_only: false,
// This is a little weird, but it's a temporary interface
max_db_size: u64::from(u32::MAX),
auto_checkpoint: true,
checkpoint_threshold: -1_i64,
throw_on_wal_replay_failure: true,
enable_checksums: true,
enable_multi_writes: false,
}
}
/// Maps an optional boolean to the FFI tristate: -1 keeps the engine default.
fn bool_or_default(value: Option<bool>) -> i64 {
value.map_or(-1, i64::from)
}

impl SystemConfig {
pub fn buffer_pool_size(mut self, buffer_pool_size: u64) -> Self {
self.buffer_pool_size = buffer_pool_size;
self.buffer_pool_size = Some(buffer_pool_size);
self
}
pub fn max_num_threads(mut self, max_num_threads: u64) -> Self {
self.max_num_threads = max_num_threads;
self.max_num_threads = Some(max_num_threads);
self
}
pub fn enable_compression(mut self, enable_compression: bool) -> Self {
self.enable_compression = enable_compression;
self.enable_compression = Some(enable_compression);
self
}
pub fn read_only(mut self, read_only: bool) -> Self {
self.read_only = read_only;
self.read_only = Some(read_only);
self
}
pub fn max_db_size(mut self, max_db_size: u64) -> Self {
self.max_db_size = max_db_size;
self.max_db_size = Some(max_db_size);
self
}
pub fn auto_checkpoint(mut self, auto_checkpoint: bool) -> Self {
self.auto_checkpoint = auto_checkpoint;
self.auto_checkpoint = Some(auto_checkpoint);
self
}
pub fn checkpoint_threshold(mut self, checkpoint_threshold: i64) -> Self {
self.checkpoint_threshold = checkpoint_threshold;
self.checkpoint_threshold = Some(checkpoint_threshold);
self
}
pub fn throw_on_wal_replay_failure(mut self, throw_on_wal_replay_failure: bool) -> Self {
self.throw_on_wal_replay_failure = throw_on_wal_replay_failure;
self.throw_on_wal_replay_failure = Some(throw_on_wal_replay_failure);
self
}
pub fn enable_checksums(mut self, enable_checksums: bool) -> Self {
self.enable_checksums = enable_checksums;
self.enable_checksums = Some(enable_checksums);
self
}
pub fn enable_multi_writes(mut self, enable_multi_writes: bool) -> Self {
self.enable_multi_writes = enable_multi_writes;
self.enable_multi_writes = Some(enable_multi_writes);
self
}
}
Expand All @@ -138,16 +134,16 @@ impl Database {
Ok(Database {
db: UnsafeCell::new(ffi::new_database(
ffi::StringView::new(&path.as_ref().display().to_string()),
config.buffer_pool_size,
config.max_num_threads,
config.enable_compression,
config.read_only,
config.max_db_size,
config.auto_checkpoint,
config.checkpoint_threshold,
config.throw_on_wal_replay_failure,
config.enable_checksums,
config.enable_multi_writes,
config.buffer_pool_size.unwrap_or(0),
config.max_num_threads.unwrap_or(0),
bool_or_default(config.enable_compression),
bool_or_default(config.read_only),
config.max_db_size.unwrap_or(u64::MAX),
bool_or_default(config.auto_checkpoint),
config.checkpoint_threshold.unwrap_or(-1),
bool_or_default(config.throw_on_wal_replay_failure),
bool_or_default(config.enable_checksums),
bool_or_default(config.enable_multi_writes),
)?),
})
}
Expand Down Expand Up @@ -314,9 +310,14 @@ mod tests {
conn.query("create node table testtest1(id int64 primary key)")?;
}
{
// WAL replay failures must be configured to throw: the engine
// default (which this binding now follows) is to silently ignore
// them.
Database::new(
db_path.clone(),
SYSTEM_CONFIG_FOR_TESTS.enable_checksums(false),
SYSTEM_CONFIG_FOR_TESTS
.enable_checksums(false)
.throw_on_wal_replay_failure(true),
)
.expect_err("An error should be thrown if the enable_checksums config doesn't match the one used to produce a WAL file");
}
Expand Down
17 changes: 10 additions & 7 deletions src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,19 +157,22 @@ pub(crate) mod ffi {
#[namespace = "lbug::main"]
type Database;

#[allow(clippy::fn_params_excessive_bools)]
// Boolean settings use a tristate: -1 keeps the engine default,
// 0/1 set it explicitly. Unset numerics use the existing sentinels
// (0 for sizes resolved by the engine, u64::MAX for maxDBSize,
// -1 for checkpoint_threshold).
fn new_database(
databasePath: StringView,
bufferPoolSize: u64,
maxNumThreads: u64,
enableCompression: bool,
readOnly: bool,
enableCompression: i64,
readOnly: i64,
maxDBSize: u64,
auto_checkpoint: bool,
auto_checkpoint: i64,
checkpoint_threshold: i64,
throw_on_wal_replay_failure: bool,
enable_checksums: bool,
enableMultiWrites: bool,
throw_on_wal_replay_failure: i64,
enable_checksums: i64,
enableMultiWrites: i64,
) -> Result<UniquePtr<Database>>;

}
Expand Down
38 changes: 28 additions & 10 deletions src/lbug_rs.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "lbug_rs.h"

#include <limits>
#include <stdexcept>

using lbug::common::ArrayTypeInfo;
Expand Down Expand Up @@ -72,28 +73,45 @@ std::unique_ptr<std::vector<lbug::common::LogicalType>> logical_type_get_struct_
}

std::unique_ptr<Database> new_database(std::string_view databasePath, uint64_t bufferPoolSize,
uint64_t maxNumThreads, bool enableCompression, bool readOnly, uint64_t maxDBSize,
bool autoCheckpoint, int64_t checkpointThreshold, bool throwOnWalReplayFailure,
bool enableChecksums, bool enableMultiWrites) {
uint64_t maxNumThreads, int64_t enableCompression, int64_t readOnly, uint64_t maxDBSize,
int64_t autoCheckpoint, int64_t checkpointThreshold, int64_t throwOnWalReplayFailure,
int64_t enableChecksums, int64_t enableMultiWrites) {
// Boolean settings use a tristate: negative keeps the engine default.
auto systemConfig = SystemConfig();
if (bufferPoolSize > 0) {
systemConfig.bufferPoolSize = bufferPoolSize;
}
if (maxNumThreads > 0) {
systemConfig.maxNumThreads = maxNumThreads;
}
systemConfig.readOnly = readOnly;
systemConfig.enableCompression = enableCompression;
if (maxDBSize != -1u) {
if (enableCompression >= 0) {
systemConfig.enableCompression = enableCompression != 0;
}
if (readOnly >= 0) {
systemConfig.readOnly = readOnly != 0;
}
// NB: compare against the 64-bit max. A bare `-1u` is a 32-bit unsigned
// int, so `maxDBSize != -1u` would wrongly treat UINT64_MAX (the "keep
// engine default" sentinel the Rust binding sends for None) as an
// explicit value.
if (maxDBSize != std::numeric_limits<uint64_t>::max()) {
systemConfig.maxDBSize = maxDBSize;
}
systemConfig.autoCheckpoint = autoCheckpoint;
if (autoCheckpoint >= 0) {
systemConfig.autoCheckpoint = autoCheckpoint != 0;
}
if (checkpointThreshold >= 0) {
systemConfig.checkpointThreshold = checkpointThreshold;
}
systemConfig.throwOnWalReplayFailure = throwOnWalReplayFailure;
systemConfig.enableChecksums = enableChecksums;
systemConfig.enableMultiWrites = enableMultiWrites;
if (throwOnWalReplayFailure >= 0) {
systemConfig.throwOnWalReplayFailure = throwOnWalReplayFailure != 0;
}
if (enableChecksums >= 0) {
systemConfig.enableChecksums = enableChecksums != 0;
}
if (enableMultiWrites >= 0) {
systemConfig.enableMultiWrites = enableMultiWrites != 0;
}
return std::make_unique<Database>(databasePath, systemConfig);
}

Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
//!
//! ## Memory
//!
//! [`SystemConfig::default`] leaves `buffer_pool_size` at 0, which the engine resolves from the
//! [`SystemConfig::default`] leaves `buffer_pool_size` unset, which the engine resolves from the
//! host's physical memory, so an embedding process reserves a large share of RAM regardless of
//! the database's size (about 6 GB resident on a 15 GiB host for a 200,000-edge graph). Set
//! [`SystemConfig::buffer_pool_size`] explicitly when the process shares the machine.
Expand Down
Loading