diff --git a/src/main/database.cpp b/src/main/database.cpp index 5f79e67b0a..283efc3c47 100644 --- a/src/main/database.cpp +++ b/src/main/database.cpp @@ -49,7 +49,6 @@ static void getLockFileFlagsAndType( AccessMode accessMode, bool createNew, int& flags, FileLockType& lock) { flags = accessMode == AccessMode::READ_ONLY ? O_RDONLY : O_RDWR; if (createNew) { - assert(flags == O_RDWR); flags |= O_CREAT; } lock = accessMode == AccessMode::READ_ONLY ? FileLockType::READ_LOCK : FileLockType::WRITE_LOCK; diff --git a/tools/rust_api/include/kuzu_rs.h b/tools/rust_api/include/kuzu_rs.h index c37a90c802..2e9bf4f269 100644 --- a/tools/rust_api/include/kuzu_rs.h +++ b/tools/rust_api/include/kuzu_rs.h @@ -60,7 +60,8 @@ std::unique_ptr> logical_type_get_struct_ /* Database */ std::unique_ptr new_database(const std::string& databasePath, - uint64_t bufferPoolSize, uint64_t maxNumThreads, bool enableCompression); + uint64_t bufferPoolSize, uint64_t maxNumThreads, bool enableCompression, + kuzu::main::AccessMode accessMode); void database_set_logging_level(kuzu::main::Database& database, const std::string& level); diff --git a/tools/rust_api/src/database.rs b/tools/rust_api/src/database.rs index 2a86bc16dd..0ddb116287 100644 --- a/tools/rust_api/src/database.rs +++ b/tools/rust_api/src/database.rs @@ -20,6 +20,21 @@ pub enum LoggingLevel { Error, } +#[derive(Clone, Debug)] +pub enum AccessMode { + ReadWrite, + ReadOnly, +} + +impl From for ffi::AccessMode { + fn from(other: AccessMode) -> Self { + match other { + AccessMode::ReadWrite => ffi::AccessMode::READ_WRITE, + AccessMode::ReadOnly => ffi::AccessMode::READ_ONLY, + } + } +} + #[derive(Clone, Debug)] /// Configuration options for the database. pub struct SystemConfig { @@ -36,6 +51,7 @@ pub struct SystemConfig { /// When true, new columns will be compressed if possible /// Defaults to true enable_compression: bool, + access_mode: AccessMode, } impl Default for SystemConfig { @@ -44,6 +60,7 @@ impl Default for SystemConfig { buffer_pool_size: 0, max_num_threads: 0, enable_compression: true, + access_mode: AccessMode::ReadWrite, } } } @@ -61,6 +78,10 @@ impl SystemConfig { self.enable_compression = enable_compression; self } + pub fn access_mode(mut self, access_mode: AccessMode) -> Self { + self.access_mode = access_mode; + self + } } impl Database { @@ -77,6 +98,7 @@ impl Database { config.buffer_pool_size, config.max_num_threads, config.enable_compression, + config.access_mode.into(), )?), }) } @@ -107,7 +129,8 @@ impl fmt::Debug for Database { #[cfg(test)] mod tests { - use crate::database::{Database, LoggingLevel, SystemConfig}; + use crate::connection::Connection; + use crate::database::{AccessMode, Database, LoggingLevel, SystemConfig}; use anyhow::{Error, Result}; // Note: Cargo runs tests in parallel by default, however kuzu does not support // working with multiple databases in parallel. @@ -156,4 +179,28 @@ mod tests { .starts_with("Failed to create directory due to")); } } + + #[test] + fn test_database_read_only() -> Result<()> { + let temp_dir = tempfile::tempdir()?; + // Create database first so that it can be opened read-only + { + Database::new(temp_dir.path(), SystemConfig::default())?; + } + let db = Database::new( + temp_dir.path(), + SystemConfig::default().access_mode(AccessMode::ReadOnly), + )?; + let conn = Connection::new(&db)?; + let result: Error = conn + .query("CREATE NODE TABLE Person(name STRING, age INT64, PRIMARY KEY(name));") + .expect_err("Invalid syntax in query should produce an error") + .into(); + + assert_eq!( + result.to_string(), + "Cannot execute write operations in a read-only access mode database!" + ); + Ok(()) + } } diff --git a/tools/rust_api/src/ffi.rs b/tools/rust_api/src/ffi.rs index 521282118b..a2d4d5ec54 100644 --- a/tools/rust_api/src/ffi.rs +++ b/tools/rust_api/src/ffi.rs @@ -46,11 +46,24 @@ pub(crate) mod ffi { MAP = 54, UNION = 55, } + #[namespace = "kuzu::common"] unsafe extern "C++" { type LogicalTypeID; } + #[namespace = "kuzu::main"] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + enum AccessMode { + READ_ONLY = 0, + READ_WRITE = 1, + } + + #[namespace = "kuzu::main"] + unsafe extern "C++" { + type AccessMode; + } + #[namespace = "kuzu::main"] unsafe extern "C++" { include!("kuzu/include/kuzu_rs.h"); @@ -84,6 +97,7 @@ pub(crate) mod ffi { bufferPoolSize: u64, maxNumThreads: u64, enableCompression: bool, + access_mode: AccessMode, ) -> Result>; fn database_set_logging_level(database: Pin<&mut Database>, level: &CxxString); diff --git a/tools/rust_api/src/kuzu_rs.cpp b/tools/rust_api/src/kuzu_rs.cpp index ed8d71ab0d..8b3b52dbbf 100644 --- a/tools/rust_api/src/kuzu_rs.cpp +++ b/tools/rust_api/src/kuzu_rs.cpp @@ -78,7 +78,7 @@ std::unique_ptr> logical_type_get_struct_ } std::unique_ptr new_database(const std::string& databasePath, uint64_t bufferPoolSize, - uint64_t maxNumThreads, bool enableCompression) { + uint64_t maxNumThreads, bool enableCompression, kuzu::main::AccessMode accessMode) { auto systemConfig = SystemConfig(); if (bufferPoolSize > 0) { systemConfig.bufferPoolSize = bufferPoolSize; @@ -86,6 +86,7 @@ std::unique_ptr new_database(const std::string& databasePath, uint64_t if (maxNumThreads > 0) { systemConfig.maxNumThreads = maxNumThreads; } + systemConfig.accessMode = accessMode; systemConfig.enableCompression = enableCompression; return std::make_unique(databasePath, systemConfig); } diff --git a/tools/rust_api/src/lib.rs b/tools/rust_api/src/lib.rs index ee40a45299..26729b8569 100644 --- a/tools/rust_api/src/lib.rs +++ b/tools/rust_api/src/lib.rs @@ -47,7 +47,7 @@ mod query_result; mod value; pub use connection::{Connection, PreparedStatement}; -pub use database::{Database, LoggingLevel, SystemConfig}; +pub use database::{AccessMode, Database, LoggingLevel, SystemConfig}; pub use error::Error; pub use logical_type::LogicalType; pub use query_result::{CSVOptions, QueryResult};