Skip to content

Commit 7b7da54

Browse files
authored
Add C, Python, Node.js, and Java API bindings for read-only mode (#2229)
* C, Node.js and Python done * Add Java bindings * Fix format * Skip tests on Windows * Typo Fix * Throw Python query errors as `RuntimeError`
1 parent f728f6b commit 7b7da54

6 files changed

Lines changed: 91 additions & 4 deletions

File tree

src_cpp/include/node_database.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class NodeDatabase : public Napi::ObjectWrap<NodeDatabase> {
2525
std::string databasePath;
2626
size_t bufferPoolSize;
2727
bool enableCompression;
28+
AccessMode accessMode;
2829
std::shared_ptr<Database> database;
2930
};
3031

src_cpp/node_database.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
#include "include/node_database.h"
22

3-
43
Napi::Object NodeDatabase::Init(Napi::Env env, Napi::Object exports) {
54
Napi::HandleScope scope(env);
65

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

2525
Napi::Value NodeDatabase::InitAsync(const Napi::CallbackInfo& info) {
@@ -41,7 +41,8 @@ void NodeDatabase::InitCppDatabase() {
4141
if (!enableCompression) {
4242
systemConfig.enableCompression = enableCompression;
4343
}
44-
this->database = make_shared<Database>(databasePath, systemConfig);
44+
systemConfig.accessMode = accessMode;
45+
this->database = std::make_shared<Database>(databasePath, systemConfig);
4546
}
4647

4748
void NodeDatabase::setLoggingLevel(const Napi::CallbackInfo& info) {

src_js/access_mode.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"use strict";
2+
3+
/*
4+
* Access modes for Database initialization.
5+
*/
6+
7+
const ACCESS_MODE = {
8+
READ_ONLY: 0,
9+
READ_WRITE: 1,
10+
};
11+
12+
module.exports = ACCESS_MODE;

src_js/database.js

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

33
const KuzuNative = require("./kuzujs.node");
44
const LoggingLevel = require("./logging_level.js");
5+
const AccessMode = require("./access_mode.js");
56

67
class Database {
78
/**
@@ -12,16 +13,28 @@ class Database {
1213
*
1314
* @param {String} databasePath path to the database file.
1415
* @param {Number} bufferManagerSize size of the buffer manager in bytes.
16+
* @param {Boolean} enableCompression whether to enable compression.
17+
* @param {AccessMode} accessMode access mode for the database.
1518
*/
16-
constructor(databasePath, bufferManagerSize = 0, enableCompression = true) {
19+
constructor(
20+
databasePath,
21+
bufferManagerSize = 0,
22+
enableCompression = true,
23+
accessMode = AccessMode.READ_WRITE
24+
) {
1725
if (typeof databasePath !== "string") {
1826
throw new Error("Database path must be a string.");
1927
}
2028
if (typeof bufferManagerSize !== "number" || bufferManagerSize < 0) {
2129
throw new Error("Buffer manager size must be a positive integer.");
2230
}
2331
bufferManagerSize = Math.floor(bufferManagerSize);
24-
this._database = new KuzuNative.NodeDatabase(databasePath, bufferManagerSize, enableCompression);
32+
this._database = new KuzuNative.NodeDatabase(
33+
databasePath,
34+
bufferManagerSize,
35+
enableCompression,
36+
accessMode
37+
);
2538
this._isInitialized = false;
2639
this._initPromise = null;
2740
}

src_js/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
"use strict";
22

3+
const AccessMode = require("./access_mode.js");
34
const Connection = require("./connection.js");
45
const Database = require("./database.js");
56
const LoggingLevel = require("./logging_level.js");
67
const PreparedStatement = require("./prepared_statement.js");
78
const QueryResult = require("./query_result.js");
89

910
module.exports = {
11+
AccessMode,
1012
Connection,
1113
Database,
1214
LoggingLevel,

test/test_database.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const { assert } = require("chai");
22
const tmp = require("tmp");
3+
const process = require("process");
34

45
describe("Database constructor", function () {
56
it("should create a database with a valid path and buffer size", async function () {
@@ -38,6 +39,63 @@ describe("Database constructor", function () {
3839
assert.notExists(testDb._initPromise);
3940
});
4041

42+
it("should create a database in read-only mode", async function () {
43+
// TODO: Enable this test on Windows when the read-only mode is implemented.
44+
if (process.platform === "win32") {
45+
this._runnable.title += " (skipped: not implemented on Windows)";
46+
this.skip();
47+
}
48+
49+
const tmpDbPath = await new Promise((resolve, reject) => {
50+
tmp.dir({ unsafeCleanup: true }, (err, path, _) => {
51+
if (err) {
52+
return reject(err);
53+
}
54+
return resolve(path);
55+
});
56+
});
57+
const testDb = new kuzu.Database(tmpDbPath, 1 << 28 /* 256MB */);
58+
assert.exists(testDb);
59+
assert.equal(testDb.constructor.name, "Database");
60+
await testDb.init();
61+
assert.exists(testDb._database);
62+
assert.isTrue(testDb._isInitialized);
63+
assert.notExists(testDb._initPromise);
64+
delete testDb;
65+
const testDbReadOnly = new kuzu.Database(
66+
tmpDbPath,
67+
1 << 28 /* 256MB */,
68+
true,
69+
kuzu.AccessMode.READ_ONLY
70+
);
71+
assert.exists(testDbReadOnly);
72+
assert.equal(testDbReadOnly.constructor.name, "Database");
73+
await testDbReadOnly.init();
74+
assert.exists(testDbReadOnly._database);
75+
assert.isTrue(testDbReadOnly._isInitialized);
76+
assert.notExists(testDbReadOnly._initPromise);
77+
const connection = new kuzu.Connection(testDbReadOnly);
78+
assert.exists(connection);
79+
assert.equal(connection.constructor.name, "Connection");
80+
await connection.init();
81+
assert.exists(connection._connection);
82+
assert.isTrue(connection._isInitialized);
83+
assert.notExists(connection._initPromise);
84+
try {
85+
const _ = await connection.query(
86+
"CREATE NODE TABLE test (id INT64, PRIMARY KEY(id))"
87+
);
88+
assert.fail(
89+
"No error thrown when executing CREATE TABLE in read-only mode."
90+
);
91+
} catch (e) {
92+
assert.equal(
93+
e.message,
94+
"Cannot execute write operations in a read-only access mode database!"
95+
);
96+
}
97+
});
98+
4199
it("should throw error if the path is invalid", async function () {
42100
try {
43101
const _ = new kuzu.Database({}, 1 << 28 /* 256MB */);

0 commit comments

Comments
 (0)