Skip to content

Commit

Permalink
Add C, Python, Node.js, and Java API bindings for read-only mode (#2229)
Browse files Browse the repository at this point in the history
* C, Node.js and Python done

* Add Java bindings

* Fix format

* Skip tests on Windows

* Typo Fix

* Throw Python query errors as `RuntimeError`
  • Loading branch information
mewim committed Oct 19, 2023
1 parent 76074a5 commit 938f995
Show file tree
Hide file tree
Showing 27 changed files with 224 additions and 69 deletions.
17 changes: 10 additions & 7 deletions src/c_api/connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,16 @@ uint64_t kuzu_connection_get_max_num_thread_for_exec(kuzu_connection* connection
}

kuzu_query_result* kuzu_connection_query(kuzu_connection* connection, const char* query) {
auto query_result = static_cast<Connection*>(connection->_connection)->query(query).release();
if (query_result == nullptr) {
return nullptr;
}
auto* c_query_result = new kuzu_query_result;
c_query_result->_query_result = query_result;
return c_query_result;
try {
auto query_result =
static_cast<Connection*>(connection->_connection)->query(query).release();
if (query_result == nullptr) {
return nullptr;
}
auto* c_query_result = new kuzu_query_result;
c_query_result->_query_result = query_result;
return c_query_result;
} catch (Exception& e) { return nullptr; }
}

kuzu_prepared_statement* kuzu_connection_prepare(kuzu_connection* connection, const char* query) {
Expand Down
9 changes: 5 additions & 4 deletions src/c_api/database.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ kuzu_database* kuzu_database_init(const char* database_path, kuzu_system_config
auto database = (kuzu_database*)malloc(sizeof(kuzu_database));
std::string database_path_str = database_path;
try {
database->_database =
new Database(database_path_str, SystemConfig(config.buffer_pool_size,
config.max_num_threads, config.enable_compression));
database->_database = new Database(database_path_str,
SystemConfig(config.buffer_pool_size, config.max_num_threads, config.enable_compression,
static_cast<AccessMode>(config.access_mode)));
} catch (Exception& e) {
free(database);
return nullptr;
Expand All @@ -34,5 +34,6 @@ void kuzu_database_set_logging_level(const char* logging_level) {
}

kuzu_system_config kuzu_default_system_config() {
return {0 /*bufferPoolSize*/, 0 /*maxNumThreads*/, true /*enableCompression*/};
return {0 /*bufferPoolSize*/, 0 /*maxNumThreads*/, true /*enableCompression*/,
kuzu_access_mode::READ_WRITE /*accessMode*/};
}
7 changes: 7 additions & 0 deletions src/include/c_api/kuzu.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ typedef struct {
uint64_t max_num_threads;
// Whether or not to compress data on-disk for supported types
bool enable_compression;
// Access mode to the database (READ_ONLY or READ_WRITE)
uint8_t access_mode;
} kuzu_system_config;

/**
Expand Down Expand Up @@ -252,6 +254,11 @@ typedef enum {
KUZU_UNION = 55,
} kuzu_data_type_id;

typedef enum {
READ_ONLY = 0,
READ_WRITE = 1,
} kuzu_access_mode;

// Database
/**
* @brief Allocates memory and creates a kuzu database instance at database_path with
Expand Down
32 changes: 31 additions & 1 deletion test/c_api/database_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ class CApiDatabaseTest : public APIEmptyDBTest {
TEST_F(CApiDatabaseTest, CreationAndDestroy) {
auto databasePathCStr = databasePath.c_str();
auto systemConfig = kuzu_default_system_config();
systemConfig.buffer_pool_size = 512 * 1024;
auto database = kuzu_database_init(databasePathCStr, systemConfig);
ASSERT_NE(database, nullptr);
ASSERT_NE(database->_database, nullptr);
Expand All @@ -17,6 +16,37 @@ TEST_F(CApiDatabaseTest, CreationAndDestroy) {
kuzu_database_destroy(database);
}

TEST_F(CApiDatabaseTest, CreationReadOnly) {
// TODO: Enable this test on Windows when the read-only mode is implemented.
#ifdef _WIN32
return;
#endif
auto databasePathCStr = databasePath.c_str();
auto systemConfig = kuzu_default_system_config();
// First, create a database with read-write access mode.
auto database = kuzu_database_init(databasePathCStr, systemConfig);
ASSERT_NE(database, nullptr);
ASSERT_NE(database->_database, nullptr);
auto databaseCpp = static_cast<Database*>(database->_database);
ASSERT_NE(databaseCpp, nullptr);
kuzu_database_destroy(database);
// Now, access the same database with read-only access mode.
systemConfig.access_mode = kuzu_access_mode::READ_ONLY;
database = kuzu_database_init(databasePathCStr, systemConfig);
ASSERT_NE(database, nullptr);
ASSERT_NE(database->_database, nullptr);
databaseCpp = static_cast<Database*>(database->_database);
ASSERT_NE(databaseCpp, nullptr);
// Try to write to the database.
auto connection = kuzu_connection_init(database);
ASSERT_NE(connection, nullptr);
auto queryResult = kuzu_connection_query(connection,
"CREATE NODE TABLE User(name STRING, age INT64, reg_date DATE, PRIMARY KEY (name))");
ASSERT_EQ(queryResult, nullptr);
kuzu_connection_destroy(connection);
kuzu_database_destroy(database);
}

TEST_F(CApiDatabaseTest, CreationInvalidPath) {
auto databasePathCStr = (char*)"";
auto database = kuzu_database_init(databasePathCStr, kuzu_default_system_config());
Expand Down
3 changes: 2 additions & 1 deletion tools/java_api/src/jni/kuzu_java.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -153,13 +153,14 @@ void javaMapToCPPMap(
*/

JNIEXPORT jlong JNICALL Java_com_kuzudb_KuzuNative_kuzu_1database_1init(JNIEnv* env, jclass,
jstring database_path, jlong buffer_pool_size, jboolean enable_compression) {
jstring database_path, jlong buffer_pool_size, jboolean enable_compression, jint access_mode) {

const char* path = env->GetStringUTFChars(database_path, JNI_FALSE);
uint64_t buffer = static_cast<uint64_t>(buffer_pool_size);
SystemConfig systemConfig;
systemConfig.bufferPoolSize = buffer == 0 ? -1u : buffer;
systemConfig.enableCompression = enable_compression;
systemConfig.accessMode = static_cast<AccessMode>(access_mode);
try {
Database* db = new Database(path, systemConfig);
uint64_t address = reinterpret_cast<uint64_t>(db);
Expand Down
34 changes: 20 additions & 14 deletions tools/java_api/src/main/java/com/kuzudb/KuzuDatabase.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,27 @@
* The KuzuDatabase class is the main class of KuzuDB. It manages all database components.
*/
public class KuzuDatabase {
public enum AccessMode {
READ_ONLY(0),
READ_WRITE(1);

private final int value;

AccessMode(int value) {
this.value = value;
}

public int getValue() {
return value;
}
}

long db_ref;
String db_path;
long buffer_size;
boolean enableCompression = true;
boolean destroyed = false;
AccessMode accessMode = AccessMode.READ_WRITE;

/**
* Creates a database object.
Expand All @@ -18,18 +33,7 @@ public class KuzuDatabase {
public KuzuDatabase(String databasePath) {
this.db_path = databasePath;
this.buffer_size = 0;
db_ref = KuzuNative.kuzu_database_init(databasePath, 0, true);
}

/**
* Creates a database object.
* @param databasePath: Database path. If the database does not already exist, it will be created.
* @param bufferPoolSize: Max size of the buffer pool in bytes.
*/
public KuzuDatabase(String databasePath, long bufferPoolSize) {
this.db_path = databasePath;
this.buffer_size = bufferPoolSize;
db_ref = KuzuNative.kuzu_database_init(databasePath, bufferPoolSize, true);
db_ref = KuzuNative.kuzu_database_init(databasePath, 0, true, AccessMode.READ_WRITE.getValue());
}

/**
Expand All @@ -38,11 +42,13 @@ public KuzuDatabase(String databasePath, long bufferPoolSize) {
* @param bufferPoolSize: Max size of the buffer pool in bytes.
* @param enableCompression: Enable compression in storage.
*/
public KuzuDatabase(String databasePath, long bufferPoolSize, boolean enableCompression) {
public KuzuDatabase(String databasePath, long bufferPoolSize, boolean enableCompression, AccessMode accessMode) {
this.db_path = databasePath;
this.buffer_size = bufferPoolSize;
this.enableCompression = enableCompression;
db_ref = KuzuNative.kuzu_database_init(databasePath, bufferPoolSize, enableCompression);
this.accessMode = accessMode;
int accessModeValue = accessMode.getValue();
db_ref = KuzuNative.kuzu_database_init(databasePath, bufferPoolSize, enableCompression, accessModeValue);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion tools/java_api/src/main/java/com/kuzudb/KuzuNative.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public class KuzuNative {
}

// Database
protected static native long kuzu_database_init(String database_path, long buffer_pool_size, boolean enable_compression);
protected static native long kuzu_database_init(String database_path, long buffer_pool_size, boolean enable_compression, int access_mode);

protected static native void kuzu_database_destroy(KuzuDatabase db);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class DatabaseTest extends TestBase {
void DBCreationAndDestroy() {
try {
String dbPath = tempDir.toFile().getAbsolutePath();
KuzuDatabase database = new KuzuDatabase(dbPath, 1024 * 1024 * 1024);
KuzuDatabase database = new KuzuDatabase(dbPath, 1024 * 1024 * 1024, true, KuzuDatabase.AccessMode.READ_WRITE);
database.destroy();
database = new KuzuDatabase(dbPath);
database.destroy();
Expand All @@ -30,7 +30,7 @@ void DBCreationAndDestroy() {
@Test
void DBInvalidPath() {
try {
KuzuDatabase database = new KuzuDatabase("", 0);
KuzuDatabase database = new KuzuDatabase("");
database.destroy();
fail("DBInvalidPath did not throw exception as expected.");
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public static KuzuConnection getConnection() {

public static void loadData(String dbPath) throws IOException, KuzuObjectRefDestroyedException {
BufferedReader reader;
db = new KuzuDatabase(dbPath, 0);
db = new KuzuDatabase(dbPath);
conn = new KuzuConnection(db);
KuzuQueryResult result;

Expand Down
2 changes: 1 addition & 1 deletion tools/java_api/test.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public static void main(String[] args) throws KuzuObjectRefDestroyedException {
String db_path = "./test_db";
deleteFolder(new File(db_path));
BufferedReader reader;
KuzuDatabase db = new KuzuDatabase(db_path, 0);
KuzuDatabase db = new KuzuDatabase(db_path);
KuzuConnection conn = new KuzuConnection(db);
try {
reader = new BufferedReader(new FileReader("../../dataset/tinysnb/schema.cypher"));
Expand Down
1 change: 1 addition & 0 deletions tools/nodejs_api/src_cpp/include/node_database.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class NodeDatabase : public Napi::ObjectWrap<NodeDatabase> {
std::string databasePath;
size_t bufferPoolSize;
bool enableCompression;
AccessMode accessMode;
std::shared_ptr<Database> database;
};

Expand Down
5 changes: 3 additions & 2 deletions tools/nodejs_api/src_cpp/node_database.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#include "include/node_database.h"


Napi::Object NodeDatabase::Init(Napi::Env env, Napi::Object exports) {
Napi::HandleScope scope(env);

Expand All @@ -20,6 +19,7 @@ NodeDatabase::NodeDatabase(const Napi::CallbackInfo& info) : Napi::ObjectWrap<No
databasePath = info[0].ToString();
bufferPoolSize = info[1].As<Napi::Number>().Int64Value();
enableCompression = info[2].As<Napi::Boolean>().Value();
accessMode = static_cast<AccessMode>(info[3].As<Napi::Number>().Int32Value());
}

Napi::Value NodeDatabase::InitAsync(const Napi::CallbackInfo& info) {
Expand All @@ -41,7 +41,8 @@ void NodeDatabase::InitCppDatabase() {
if (!enableCompression) {
systemConfig.enableCompression = enableCompression;
}
this->database = make_shared<Database>(databasePath, systemConfig);
systemConfig.accessMode = accessMode;
this->database = std::make_shared<Database>(databasePath, systemConfig);
}

void NodeDatabase::setLoggingLevel(const Napi::CallbackInfo& info) {
Expand Down
12 changes: 12 additions & 0 deletions tools/nodejs_api/src_js/access_mode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"use strict";

/*
* Access modes for Database initialization.
*/

const ACCESS_MODE = {
READ_ONLY: 0,
READ_WRITE: 1,
};

module.exports = ACCESS_MODE;
17 changes: 15 additions & 2 deletions tools/nodejs_api/src_js/database.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const KuzuNative = require("./kuzujs.node");
const LoggingLevel = require("./logging_level.js");
const AccessMode = require("./access_mode.js");

class Database {
/**
Expand All @@ -12,16 +13,28 @@ class Database {
*
* @param {String} databasePath path to the database file.
* @param {Number} bufferManagerSize size of the buffer manager in bytes.
* @param {Boolean} enableCompression whether to enable compression.
* @param {AccessMode} accessMode access mode for the database.
*/
constructor(databasePath, bufferManagerSize = 0, enableCompression = true) {
constructor(
databasePath,
bufferManagerSize = 0,
enableCompression = true,
accessMode = AccessMode.READ_WRITE
) {
if (typeof databasePath !== "string") {
throw new Error("Database path must be a string.");
}
if (typeof bufferManagerSize !== "number" || bufferManagerSize < 0) {
throw new Error("Buffer manager size must be a positive integer.");
}
bufferManagerSize = Math.floor(bufferManagerSize);
this._database = new KuzuNative.NodeDatabase(databasePath, bufferManagerSize, enableCompression);
this._database = new KuzuNative.NodeDatabase(
databasePath,
bufferManagerSize,
enableCompression,
accessMode
);
this._isInitialized = false;
this._initPromise = null;
}
Expand Down
2 changes: 2 additions & 0 deletions tools/nodejs_api/src_js/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
"use strict";

const AccessMode = require("./access_mode.js");
const Connection = require("./connection.js");
const Database = require("./database.js");
const LoggingLevel = require("./logging_level.js");
const PreparedStatement = require("./prepared_statement.js");
const QueryResult = require("./query_result.js");

module.exports = {
AccessMode,
Connection,
Database,
LoggingLevel,
Expand Down
Loading

0 comments on commit 938f995

Please sign in to comment.