Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add C, Python, Node.js, and Java API bindings for read-only mode #2229

Merged
merged 6 commits into from
Oct 19, 2023
Merged
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
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