diff --git a/src/c_api/CMakeLists.txt b/src/c_api/CMakeLists.txt index 597a328d35..c340744fa2 100644 --- a/src/c_api/CMakeLists.txt +++ b/src/c_api/CMakeLists.txt @@ -8,7 +8,8 @@ add_library(kuzu_c_api prepared_statement.cpp query_result.cpp query_summary.cpp - value.cpp) + value.cpp + version.cpp) set(ALL_OBJECT_FILES ${ALL_OBJECT_FILES} $ diff --git a/src/c_api/version.cpp b/src/c_api/version.cpp new file mode 100644 index 0000000000..eefda6e655 --- /dev/null +++ b/src/c_api/version.cpp @@ -0,0 +1,12 @@ +#include "main/version.h" + +#include "c_api/helpers.h" +#include "c_api/kuzu.h" + +char* kuzu_get_version() { + return convertToOwnedCString(kuzu::main::Version::getVersion()); +} + +uint64_t kuzu_get_storage_version() { + return kuzu::main::Version::getStorageVersion(); +} diff --git a/src/include/c_api/kuzu.h b/src/include/c_api/kuzu.h index 194e540422..03e78511ae 100644 --- a/src/include/c_api/kuzu.h +++ b/src/include/c_api/kuzu.h @@ -1269,4 +1269,15 @@ KUZU_C_API double kuzu_query_summary_get_compiling_time(kuzu_query_summary* quer KUZU_C_API double kuzu_query_summary_get_execution_time(kuzu_query_summary* query_summary); // TODO: Bind utility functions for kuzu_date_t, kuzu_timestamp_t, and kuzu_interval_t + +// Version +/** + * @brief Returns the version of the Kùzu library. + */ +KUZU_C_API char* kuzu_get_version(); + +/** + * @brief Returns the storage version of the Kùzu library. + */ +KUZU_C_API uint64_t kuzu_get_storage_version(); #undef KUZU_C_API diff --git a/src/include/main/kuzu.h b/src/include/main/kuzu.h index 673dc1009a..11fd568c6e 100644 --- a/src/include/main/kuzu.h +++ b/src/include/main/kuzu.h @@ -18,4 +18,5 @@ #include "main/query_result.h" // IWYU pragma: export #include "main/query_summary.h" // IWYU pragma: export #include "main/storage_driver.h" // IWYU pragma: export +#include "main/version.h" // IWYU pragma: export #include "processor/result/flat_tuple.h" // IWYU pragma: export diff --git a/src/include/main/version.h b/src/include/main/version.h new file mode 100644 index 0000000000..35dabe8eaa --- /dev/null +++ b/src/include/main/version.h @@ -0,0 +1,23 @@ +#pragma once +#include + +#include "common/api.h" +namespace kuzu { +namespace main { + +struct Version { +public: + /** + * @brief Get the version of the Kùzu library. + * @return const char* The version of the Kùzu library. + */ + KUZU_API static const char* getVersion(); + + /** + * @brief Get the storage version of the Kùzu library. + * @return uint64_t The storage version of the Kùzu library. + */ + KUZU_API static uint64_t getStorageVersion(); +}; +} // namespace main +} // namespace kuzu diff --git a/src/main/CMakeLists.txt b/src/main/CMakeLists.txt index 619a73f66f..5d9049d143 100644 --- a/src/main/CMakeLists.txt +++ b/src/main/CMakeLists.txt @@ -8,6 +8,7 @@ add_library(kuzu_main query_result.cpp query_summary.cpp storage_driver.cpp + version.cpp db_config.cpp) set(ALL_OBJECT_FILES diff --git a/src/main/version.cpp b/src/main/version.cpp new file mode 100644 index 0000000000..440a0c2db2 --- /dev/null +++ b/src/main/version.cpp @@ -0,0 +1,15 @@ +#include "main/version.h" + +#include "storage/storage_info.h" + +namespace kuzu { +namespace main { +const char* Version::getVersion() { + return KUZU_CMAKE_VERSION; +} + +uint64_t Version::getStorageVersion() { + return storage::StorageVersionInfo::getStorageVersion(); +} +} // namespace main +} // namespace kuzu diff --git a/test/c_api/CMakeLists.txt b/test/c_api/CMakeLists.txt index c19fa59742..3ae3b4779c 100644 --- a/test/c_api/CMakeLists.txt +++ b/test/c_api/CMakeLists.txt @@ -6,4 +6,5 @@ add_kuzu_api_test(c_api_test prepared_statement_test.cpp query_result_test.cpp rdf_variant_test.cpp - value_test.cpp) + value_test.cpp + version_test.cpp) diff --git a/test/c_api/version_test.cpp b/test/c_api/version_test.cpp new file mode 100644 index 0000000000..bc5803dcc5 --- /dev/null +++ b/test/c_api/version_test.cpp @@ -0,0 +1,41 @@ + +#include +#include + +#include "c_api/kuzu.h" +#include "c_api_test/c_api_test.h" +#include "gtest/gtest.h" +#include "main/version.h" + +using namespace kuzu::main; +using namespace kuzu::testing; +using namespace kuzu::common; + +class CApiVersionTest : public CApiTest { +public: + std::string getInputDir() override { + return TestHelper::appendKuzuRootPath("dataset/tinysnb/"); + } +}; + +TEST_F(CApiVersionTest, GetVersion) { + auto version = kuzu_get_version(); + ASSERT_NE(version, nullptr); + ASSERT_STREQ(version, KUZU_CMAKE_VERSION); + kuzu_destroy_string(version); +} + +TEST_F(CApiVersionTest, GetStorageVersion) { + auto storageVersion = kuzu_get_storage_version(); + auto catalog = std::filesystem::path(databasePath) / "catalog.kz"; + std::ifstream catalogFile; + catalogFile.open(catalog, std::ios::binary); + char magic[5]; + catalogFile.read(magic, 4); + magic[4] = '\0'; + ASSERT_STREQ(magic, "KUZU"); + uint64_t actualVersion; + catalogFile.read(reinterpret_cast(&actualVersion), sizeof(actualVersion)); + catalogFile.close(); + ASSERT_EQ(storageVersion, actualVersion); +} diff --git a/tools/java_api/src/jni/kuzu_java.cpp b/tools/java_api/src/jni/kuzu_java.cpp index 214915484a..40c234ec45 100644 --- a/tools/java_api/src/jni/kuzu_java.cpp +++ b/tools/java_api/src/jni/kuzu_java.cpp @@ -166,7 +166,8 @@ JNIEXPORT void JNICALL Java_com_kuzudb_KuzuNative_kuzu_1native_1reload_1library( env->ReleaseStringUTFChars(lib_path, path); if (handle == nullptr) { jclass Exception = env->FindClass("java/lang/Exception"); - auto error = dlerror(); // NOLINT(concurrency-mt-unsafe): load can only be executed in single thread. + auto error = + dlerror(); // NOLINT(concurrency-mt-unsafe): load can only be executed in single thread. env->ThrowNew(Exception, error); } #endif @@ -1255,3 +1256,11 @@ JNIEXPORT jobject JNICALL Java_com_kuzudb_KuzuNative_kuzu_1rdf_1variant_1get_1va return nullptr; } } + +JNIEXPORT jstring JNICALL Java_com_kuzudb_KuzuNative_kuzu_1get_1version(JNIEnv* env, jclass) { + return env->NewStringUTF(Version::getVersion()); +} + +JNIEXPORT jlong JNICALL Java_com_kuzudb_KuzuNative_kuzu_1get_1storage_1version(JNIEnv*, jclass) { + return static_cast(Version::getStorageVersion()); +} diff --git a/tools/java_api/src/main/java/com/kuzudb/KuzuNative.java b/tools/java_api/src/main/java/com/kuzudb/KuzuNative.java index 189ed326fe..b6d68610da 100644 --- a/tools/java_api/src/main/java/com/kuzudb/KuzuNative.java +++ b/tools/java_api/src/main/java/com/kuzudb/KuzuNative.java @@ -221,4 +221,8 @@ protected static native long kuzu_data_type_create( protected static native KuzuDataType kuzu_rdf_variant_get_data_type(KuzuValue rdf_variant); protected static native T kuzu_rdf_variant_get_value(KuzuValue rdf_variant); + + protected static native String kuzu_get_version(); + + protected static native long kuzu_get_storage_version(); } diff --git a/tools/java_api/src/main/java/com/kuzudb/KuzuVersion.java b/tools/java_api/src/main/java/com/kuzudb/KuzuVersion.java new file mode 100644 index 0000000000..b329fa74f0 --- /dev/null +++ b/tools/java_api/src/main/java/com/kuzudb/KuzuVersion.java @@ -0,0 +1,23 @@ +package com.kuzudb; + +/** + * KuzuVersion is a class to get the version of the Kùzu. + */ +public class KuzuVersion { + + /** + * Get the version of the Kùzu. + * @return The version of the Kùzu. + */ + public static String getVersion() { + return KuzuNative.kuzu_get_version(); + } + + /** + * Get the storage version of the Kùzu. + * @return The storage version of the Kùzu. + */ + public static long getStorageVersion() { + return KuzuNative.kuzu_get_storage_version(); + } +} diff --git a/tools/java_api/src/test/java/com/kuzudb/test/VersionTest.java b/tools/java_api/src/test/java/com/kuzudb/test/VersionTest.java new file mode 100644 index 0000000000..2ecdd7e137 --- /dev/null +++ b/tools/java_api/src/test/java/com/kuzudb/test/VersionTest.java @@ -0,0 +1,21 @@ +package com.kuzudb.java_test; + +import com.kuzudb.*; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class VersionTest extends TestBase { + + @Test + void GetVersion() { + String version = KuzuVersion.getVersion(); + assertTrue(!version.equals("")); + } + + @Test + void GetStorageVersion() { + long storage_version = KuzuVersion.getStorageVersion(); + assertTrue(storage_version > 0); + } +} diff --git a/tools/nodejs_api/src_cpp/include/node_database.h b/tools/nodejs_api/src_cpp/include/node_database.h index 2f161d9097..8bbb4ca79a 100644 --- a/tools/nodejs_api/src_cpp/include/node_database.h +++ b/tools/nodejs_api/src_cpp/include/node_database.h @@ -20,6 +20,8 @@ class NodeDatabase : public Napi::ObjectWrap { Napi::Value InitAsync(const Napi::CallbackInfo& info); void InitCppDatabase(); void setLoggingLevel(const Napi::CallbackInfo& info); + static Napi::Value GetVersion(const Napi::CallbackInfo& info); + static Napi::Value GetStorageVersion(const Napi::CallbackInfo& info); private: std::string databasePath; diff --git a/tools/nodejs_api/src_cpp/node_database.cpp b/tools/nodejs_api/src_cpp/node_database.cpp index 0fa032ff79..debb49eeab 100644 --- a/tools/nodejs_api/src_cpp/node_database.cpp +++ b/tools/nodejs_api/src_cpp/node_database.cpp @@ -7,6 +7,8 @@ Napi::Object NodeDatabase::Init(Napi::Env env, Napi::Object exports) { { InstanceMethod("initAsync", &NodeDatabase::InitAsync), InstanceMethod("setLoggingLevel", &NodeDatabase::setLoggingLevel), + StaticMethod("getVersion", &NodeDatabase::GetVersion), + StaticMethod("getStorageVersion", &NodeDatabase::GetStorageVersion), }); exports.Set("NodeDatabase", t); @@ -56,3 +58,15 @@ void NodeDatabase::setLoggingLevel(const Napi::CallbackInfo& info) { auto loggingLevel = info[0].As().Utf8Value(); database->setLoggingLevel(std::move(loggingLevel)); } + +Napi::Value NodeDatabase::GetVersion(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + Napi::HandleScope scope(env); + return Napi::String::New(env, Version::getVersion()); +} + +Napi::Value NodeDatabase::GetStorageVersion(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + Napi::HandleScope scope(env); + return Napi::Number::New(env, Version::getStorageVersion()); +} diff --git a/tools/nodejs_api/src_js/database.js b/tools/nodejs_api/src_js/database.js index 8ce00c9c7b..f6bbcc8609 100644 --- a/tools/nodejs_api/src_js/database.js +++ b/tools/nodejs_api/src_js/database.js @@ -47,6 +47,22 @@ class Database { this._initPromise = null; } + /** + * Get the version of the library. + * @returns {String} the version of the library. + */ + static getVersion() { + return KuzuNative.NodeDatabase.getVersion(); + } + + /** + * Get the storage version of the library. + * @returns {Number} the storage version of the library. + */ + static getStorageVersion() { + return KuzuNative.NodeDatabase.getStorageVersion(); + } + /** * Initialize the database. Calling this function is optional, as the * database is initialized automatically when the first query is executed. diff --git a/tools/nodejs_api/src_js/index.js b/tools/nodejs_api/src_js/index.js index 3743da07b8..84648ff6f7 100644 --- a/tools/nodejs_api/src_js/index.js +++ b/tools/nodejs_api/src_js/index.js @@ -12,4 +12,10 @@ module.exports = { LoggingLevel, PreparedStatement, QueryResult, + get VERSION() { + return Database.getVersion(); + }, + get STORAGE_VERSION() { + return Database.getStorageVersion(); + }, }; diff --git a/tools/nodejs_api/test/test.js b/tools/nodejs_api/test/test.js index d90281ae18..a5e46e12c6 100644 --- a/tools/nodejs_api/test/test.js +++ b/tools/nodejs_api/test/test.js @@ -17,4 +17,5 @@ describe("kuzu", () => { importTest("Extension loading", "./test_extension.js"); importTest("Query parameters", "./test_parameter.js"); importTest("Concurrent query execution", "./test_concurrency.js"); + importTest("Version", "./test_version.js"); }); diff --git a/tools/nodejs_api/test/test_version.js b/tools/nodejs_api/test/test_version.js new file mode 100644 index 0000000000..00c8090587 --- /dev/null +++ b/tools/nodejs_api/test/test_version.js @@ -0,0 +1,13 @@ +const { assert } = require("chai"); + +describe("Get version", function () { + it("should get the version of the library", function () { + assert.isString(kuzu.VERSION); + assert.notEqual(kuzu.VERSION, ""); + }); + + it("should get the storage version of the library", function () { + assert.isNumber(kuzu.STORAGE_VERSION); + assert.isAtLeast(kuzu.STORAGE_VERSION, 1); + }); +}); diff --git a/tools/python_api/src_cpp/include/py_database.h b/tools/python_api/src_cpp/include/py_database.h index 00099770bf..2e65ec9e56 100644 --- a/tools/python_api/src_cpp/include/py_database.h +++ b/tools/python_api/src_cpp/include/py_database.h @@ -16,6 +16,10 @@ class PyDatabase { static void initialize(py::handle& m); + static py::str getVersion(); + + static uint64_t getStorageVersion(); + explicit PyDatabase(const std::string& databasePath, uint64_t bufferPoolSize, uint64_t maxNumThreads, bool compression, bool readOnly, uint64_t maxDBSize); diff --git a/tools/python_api/src_cpp/py_database.cpp b/tools/python_api/src_cpp/py_database.cpp index a29b83e51e..0ed4c234fd 100644 --- a/tools/python_api/src_cpp/py_database.cpp +++ b/tools/python_api/src_cpp/py_database.cpp @@ -1,8 +1,10 @@ #include "include/py_database.h" -#include "pandas/pandas_scan.h" #include +#include "main/version.h" +#include "pandas/pandas_scan.h" + using namespace kuzu::common; void PyDatabase::initialize(py::handle& m) { @@ -26,7 +28,17 @@ void PyDatabase::initialize(py::handle& m) { .def("scan_node_table_as_float", &PyDatabase::scanNodeTable, py::arg("table_name"), py::arg("prop_name"), py::arg("indices"), py::arg("np_array"), py::arg("num_threads")) .def("scan_node_table_as_bool", &PyDatabase::scanNodeTable, py::arg("table_name"), - py::arg("prop_name"), py::arg("indices"), py::arg("np_array"), py::arg("num_threads")); + py::arg("prop_name"), py::arg("indices"), py::arg("np_array"), py::arg("num_threads")) + .def_static("get_version", &PyDatabase::getVersion) + .def_static("get_storage_version", &PyDatabase::getStorageVersion); +} + +py::str PyDatabase::getVersion() { + return py::str(Version::getVersion()); +} + +uint64_t PyDatabase::getStorageVersion() { + return Version::getStorageVersion(); } PyDatabase::PyDatabase(const std::string& databasePath, uint64_t bufferPoolSize, diff --git a/tools/python_api/src_py/__init__.py b/tools/python_api/src_py/__init__.py index 6a71049e29..899bc394bc 100644 --- a/tools/python_api/src_py/__init__.py +++ b/tools/python_api/src_py/__init__.py @@ -51,6 +51,15 @@ from .query_result import * from .types import * +def __getattr__(name): + if name == "version": + return Database.get_version() + elif name == "storage_version": + return Database.get_storage_version() + else: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + + # Restore the original dlopen flags if sys.platform == "linux": sys.setdlopenflags(original_dlopen_flags) diff --git a/tools/python_api/src_py/database.py b/tools/python_api/src_py/database.py index f12882dc7e..2b0b4470f2 100644 --- a/tools/python_api/src_py/database.py +++ b/tools/python_api/src_py/database.py @@ -50,6 +50,28 @@ def __init__(self, database_path, buffer_pool_size=0, max_num_threads=0, compres self._database = None if not lazy_init: self.init_database() + + def get_version(): + """ + Get the version of the database. + + Returns + ------- + str + The version of the database. + """ + return _kuzu.Database.get_version() + + def get_storage_version(): + """ + Get the storage version of the database. + + Returns + ------- + int + The storage version of the database. + """ + return _kuzu.Database.get_storage_version() def __getstate__(self): state = { diff --git a/tools/python_api/test/test_version.py b/tools/python_api/test/test_version.py new file mode 100644 index 0000000000..f5ed761d02 --- /dev/null +++ b/tools/python_api/test/test_version.py @@ -0,0 +1,4 @@ +def test_version(): + import kuzu + assert kuzu.version != "" + assert kuzu.storage_version > 0