Skip to content

Commit

Permalink
Merge pull request #1471 from kuzudb/c-api
Browse files Browse the repository at this point in the history
Add C API bindings
  • Loading branch information
mewim committed Apr 20, 2023
2 parents a5e9793 + 1f17925 commit 6def875
Show file tree
Hide file tree
Showing 32 changed files with 3,046 additions and 54 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/linux-precompiled-bin-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@ jobs:
name: libkuzu-${{ github.event.inputs.packageVersion }}-linux-x86_64
path: |
./scripts/pre-compiled-bins/kuzu.h
./scripts/pre-compiled-bins/kuzu.hpp
./scripts/pre-compiled-bins/libkuzu.so
- uses: actions/upload-artifact@v3
with:
name: kuzu_cli-${{ github.event.inputs.packageVersion }}-linux-x86_64
path: ./scripts/pre-compiled-bins/kuzu

- name: Clean up
run: rm -rf ./scripts/pre-compiled-bins/kuzu ./scripts/pre-compiled-bins/headers ./scripts/pre-compiled-bins/libkuzu.so /scripts/pre-compiled-bins/kuzu.h
run: rm -rf ./scripts/pre-compiled-bins/kuzu ./scripts/pre-compiled-bins/headers ./scripts/pre-compiled-bins/libkuzu.so /scripts/pre-compiled-bins/kuzu.h /scripts/pre-compiled-bins/kuzu.hpp
8 changes: 5 additions & 3 deletions .github/workflows/mac-precompiled-bin-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ jobs:
name: libkuzu-${{ github.event.inputs.packageVersion }}-osx-arm64
path: |
./scripts/pre-compiled-bins/kuzu.h
./scripts/pre-compiled-bins/kuzu.hpp
./scripts/pre-compiled-bins/libkuzu.dylib
- uses: actions/upload-artifact@v3
with:
name: kuzu_cli-${{ github.event.inputs.packageVersion }}-osx-arm64
Expand All @@ -51,12 +52,13 @@ jobs:
name: libkuzu-${{ github.event.inputs.packageVersion }}-osx-x86_64
path: |
./scripts/pre-compiled-bins/kuzu.h
./scripts/pre-compiled-bins/kuzu.hpp
./scripts/pre-compiled-bins/libkuzu.dylib
- uses: actions/upload-artifact@v3
with:
name: kuzu_cli-${{ github.event.inputs.packageVersion }}-osx-x86_64
path: ./scripts/pre-compiled-bins/kuzu

- name: Clean up
run: rm -rf ./scripts/pre-compiled-bins/kuzu ./scripts/pre-compiled-bins/headers ./scripts/pre-compiled-bins/libkuzu.dylib ./scripts/pre-compiled-bins/kuzu.h
run: rm -rf ./scripts/pre-compiled-bins/kuzu ./scripts/pre-compiled-bins/headers ./scripts/pre-compiled-bins/libkuzu.dylib ./scripts/pre-compiled-bins/kuzu.h ./scripts/pre-compiled-bins/kuzu.hpp
5 changes: 3 additions & 2 deletions scripts/pre-compiled-bins/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Intro
The script in this directory builds pre-compiled library and CLI and collects header for release. It generates the following files:
- `headers`: the headers for release in a directory;
- `kuzu.h`: the merged header for release;
- `libkuzu.so` / `libkuzu.dylib`: the shared library for C++ API;
- `kuzu.hpp`: the merged C++ header for release;
- `kuzu.h`: the C header for release;
- `libkuzu.so` / `libkuzu.dylib`: the shared library for both C and C++ API;
- `kuzu`: the database shell.
# Usage:
- Install dependency: `pip3 install networkx`.
Expand Down
9 changes: 8 additions & 1 deletion scripts/pre-compiled-bins/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def cleanup():
subprocess.run(['make', 'clean-all'], cwd=workspace_root,
check=True, env=env_vars)
shutil.rmtree(os.path.join(base_dir, "headers"), ignore_errors=True)
for f in ["kuzu", "kuzu.h", "libkuzu.so", "libkuzu.dylib"]:
for f in ["kuzu", "kuzu.hpp", "kuzu.h", "libkuzu.so", "libkuzu.dylib"]:
try:
os.remove(os.path.join(base_dir, f))
except OSError:
Expand Down Expand Up @@ -91,10 +91,17 @@ def collect_and_merge_headers():

def collect_binaries():
logging.info("Collecting binaries...")
c_header_path = os.path.join(workspace_root, "src", "include", "c_api",
"kuzu.h")
so_path = os.path.join(workspace_root, "build",
"release", "src", "libkuzu.so")
dylib_path = os.path.join(workspace_root, "build",
"release", "src", "libkuzu.dylib")
if not os.path.exists(c_header_path):
logging.error("No C header file found")
sys.exit(1)
shutil.copy(c_header_path, os.path.join(base_dir, "kuzu.h"))
logging.info("Copied kuzu.h")
so_exists = os.path.exists(so_path)
dylib_exists = os.path.exists(dylib_path)
if not so_exists and not dylib_exists:
Expand Down
2 changes: 1 addition & 1 deletion scripts/pre-compiled-bins/merge_headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
os.path.join(os.path.dirname(__file__), 'headers')
)
OUTPUT_PATH = os.path.realpath(
os.path.join(os.path.dirname(__file__), 'kuzu.h')
os.path.join(os.path.dirname(__file__), 'kuzu.hpp')
)

logging.debug('HEADER_PATH: %s', HEADER_PATH)
Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
add_subdirectory(binder)
add_subdirectory(c_api)
add_subdirectory(catalog)
add_subdirectory(common)
add_subdirectory(expression_evaluator)
Expand Down
14 changes: 14 additions & 0 deletions src/c_api/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
add_library(kuzu_c_api
OBJECT
connection.cpp
database.cpp
data_type.cpp
flat_tuple.cpp
prepared_statement.cpp
query_result.cpp
query_summary.cpp
value.cpp)

set(ALL_OBJECT_FILES
${ALL_OBJECT_FILES} $<TARGET_OBJECTS:kuzu_c_api>
PARENT_SCOPE)
137 changes: 137 additions & 0 deletions src/c_api/connection.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
#include "binder/bound_statement_result.h"
#include "c_api/kuzu.h"
#include "common/exception.h"
#include "common/types/value.h"
#include "main/kuzu.h"
#include "planner/logical_plan/logical_plan.h"

using namespace kuzu::common;
using namespace kuzu::main;

kuzu_connection* kuzu_connection_init(kuzu_database* database) {
if (database == nullptr || database->_database == nullptr) {
return nullptr;
}
auto connection = (kuzu_connection*)malloc(sizeof(kuzu_connection));
try {
auto* connection_ptr = new Connection(static_cast<Database*>(database->_database));
connection->_connection = connection_ptr;
} catch (Exception& e) {
free(connection);
return nullptr;
}
return connection;
}

void kuzu_connection_destroy(kuzu_connection* connection) {
if (connection == nullptr) {
return;
}
if (connection->_connection != nullptr) {
delete static_cast<Connection*>(connection->_connection);
}
free(connection);
}

void kuzu_connection_begin_read_only_transaction(kuzu_connection* connection) {
static_cast<Connection*>(connection->_connection)->beginReadOnlyTransaction();
}

void kuzu_connection_begin_write_transaction(kuzu_connection* connection) {
static_cast<Connection*>(connection->_connection)->beginWriteTransaction();
}

void kuzu_connection_commit(kuzu_connection* connection) {
static_cast<Connection*>(connection->_connection)->commit();
}

void kuzu_connection_rollback(kuzu_connection* connection) {
static_cast<Connection*>(connection->_connection)->rollback();
}

void kuzu_connection_set_max_num_thread_for_exec(
kuzu_connection* connection, uint64_t num_threads) {
static_cast<Connection*>(connection->_connection)->setMaxNumThreadForExec(num_threads);
}

uint64_t kuzu_connection_get_max_num_thread_for_exec(kuzu_connection* connection) {
return static_cast<Connection*>(connection->_connection)->getMaxNumThreadForExec();
}

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;
}

kuzu_prepared_statement* kuzu_connection_prepare(kuzu_connection* connection, const char* query) {
auto connection_ptr = static_cast<Connection*>(connection->_connection);
auto prepared_statement = connection_ptr->prepare(query).release();
if (prepared_statement == nullptr) {
return nullptr;
}
auto* c_prepared_statement = new kuzu_prepared_statement;
c_prepared_statement->_prepared_statement = prepared_statement;
c_prepared_statement->_bound_values =
new std::unordered_map<std::string, std::shared_ptr<Value>>;
return c_prepared_statement;
}

kuzu_query_result* kuzu_connection_execute(
kuzu_connection* connection, kuzu_prepared_statement* prepared_statement) {
auto prepared_statement_ptr =
static_cast<PreparedStatement*>(prepared_statement->_prepared_statement);
auto bound_values = static_cast<std::unordered_map<std::string, std::shared_ptr<Value>>*>(
prepared_statement->_bound_values);
auto query_result = static_cast<Connection*>(connection->_connection)
->executeWithParams(prepared_statement_ptr, *bound_values)
.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;
}

char* kuzu_connection_get_node_table_names(kuzu_connection* connection) {
auto node_table_names = static_cast<Connection*>(connection->_connection)->getNodeTableNames();
char* node_table_names_c = (char*)malloc(node_table_names.size() + 1);
strcpy(node_table_names_c, node_table_names.c_str());
return node_table_names_c;
}

char* kuzu_connection_get_rel_table_names(kuzu_connection* connection) {
auto rel_table_names = static_cast<Connection*>(connection->_connection)->getRelTableNames();
char* rel_table_names_c = (char*)malloc(rel_table_names.size() + 1);
strcpy(rel_table_names_c, rel_table_names.c_str());
return rel_table_names_c;
}

char* kuzu_connection_get_node_property_names(kuzu_connection* connection, const char* table_name) {
auto node_property_names =
static_cast<Connection*>(connection->_connection)->getNodePropertyNames(table_name);
char* node_property_names_c = (char*)malloc(node_property_names.size() + 1);
strcpy(node_property_names_c, node_property_names.c_str());
return node_property_names_c;
}

char* kuzu_connection_get_rel_property_names(kuzu_connection* connection, const char* table_name) {
auto rel_property_names =
static_cast<Connection*>(connection->_connection)->getRelPropertyNames(table_name);
char* rel_property_names_c = (char*)malloc(rel_property_names.size() + 1);
strcpy(rel_property_names_c, rel_property_names.c_str());
return rel_property_names_c;
}

void kuzu_connection_interrupt(kuzu_connection* connection) {
static_cast<Connection*>(connection->_connection)->interrupt();
}

void kuzu_connection_set_query_timeout(kuzu_connection* connection, uint64_t timeout_in_ms) {
static_cast<Connection*>(connection->_connection)->setQueryTimeOut(timeout_in_ms);
}
81 changes: 81 additions & 0 deletions src/c_api/data_type.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#include "c_api/kuzu.h"
#include "common/types/types.h"
#include "main/kuzu.h"

using namespace kuzu::main;
using namespace kuzu::common;

kuzu_data_type* kuzu_data_type_create(
kuzu_data_type_id id, kuzu_data_type* child_type, uint64_t fixed_num_elements_in_list) {
auto* c_data_type = (kuzu_data_type*)malloc(sizeof(kuzu_data_type));
uint8_t data_type_id_u8 = id;
DataType* data_type;
if (child_type == nullptr) {
data_type = new DataType(static_cast<DataTypeID>(data_type_id_u8));
} else {
auto child_type_pty =
std::make_unique<DataType>(*static_cast<DataType*>(child_type->_data_type));
data_type =
fixed_num_elements_in_list > 0 ?
new DataType(static_cast<DataTypeID>(data_type_id_u8), std::move(child_type_pty),
fixed_num_elements_in_list) :
new DataType(static_cast<DataTypeID>(data_type_id_u8), std::move(child_type_pty));
}
c_data_type->_data_type = data_type;
return c_data_type;
}

kuzu_data_type* kuzu_data_type_clone(kuzu_data_type* data_type) {
auto* c_data_type = (kuzu_data_type*)malloc(sizeof(kuzu_data_type));
c_data_type->_data_type = new DataType(*static_cast<DataType*>(data_type->_data_type));
return c_data_type;
}

void kuzu_data_type_destroy(kuzu_data_type* data_type) {
if (data_type == nullptr) {
return;
}
if (data_type->_data_type != nullptr) {
delete static_cast<DataType*>(data_type->_data_type);
}
free(data_type);
}

bool kuzu_data_type_equals(kuzu_data_type* data_type1, kuzu_data_type* data_type2) {
return *static_cast<DataType*>(data_type1->_data_type) ==
*static_cast<DataType*>(data_type2->_data_type);
}

kuzu_data_type_id kuzu_data_type_get_id(kuzu_data_type* data_type) {
auto data_type_id_u8 =
static_cast<uint8_t>(static_cast<DataType*>(data_type->_data_type)->getTypeID());
return static_cast<kuzu_data_type_id>(data_type_id_u8);
}

kuzu_data_type* kuzu_data_type_get_child_type(kuzu_data_type* data_type) {
auto parent_type = static_cast<DataType*>(data_type->_data_type);
if (parent_type->getTypeID() != DataTypeID::FIXED_LIST &&
parent_type->getTypeID() != DataTypeID::VAR_LIST) {
return nullptr;
}
auto child_type = static_cast<DataType*>(data_type->_data_type)->getChildType();
if (child_type == nullptr) {
return nullptr;
}
auto* child_type_c = (kuzu_data_type*)malloc(sizeof(kuzu_data_type));
child_type_c->_data_type = new DataType(*child_type);
return child_type_c;
}

uint64_t kuzu_data_type_get_fixed_num_elements_in_list(kuzu_data_type* data_type) {
auto parent_type = static_cast<DataType*>(data_type->_data_type);
if (parent_type->getTypeID() != DataTypeID::FIXED_LIST) {
return 0;
}
auto extra_info = static_cast<DataType*>(data_type->_data_type)->getExtraTypeInfo();
if (extra_info == nullptr) {
return 0;
}
auto fixed_list_info = dynamic_cast<FixedListTypeInfo*>(extra_info);
return fixed_list_info->getFixedNumElementsInList();
}
34 changes: 34 additions & 0 deletions src/c_api/database.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#include "c_api/kuzu.h"
#include "common/exception.h"
#include "main/kuzu.h"

using namespace kuzu::main;
using namespace kuzu::common;

kuzu_database* kuzu_database_init(const char* database_path, uint64_t buffer_pool_size) {
auto database = (kuzu_database*)malloc(sizeof(kuzu_database));
std::string database_path_str = database_path;
try {
database->_database = buffer_pool_size == 0 ?
new Database(database_path_str) :
new Database(database_path_str, SystemConfig(buffer_pool_size));
} catch (Exception& e) {
free(database);
return nullptr;
}
return database;
}

void kuzu_database_destroy(kuzu_database* database) {
if (database == nullptr) {
return;
}
if (database->_database != nullptr) {
delete static_cast<Database*>(database->_database);
}
free(database);
}

void kuzu_database_set_logging_level(const char* logging_level) {
Database::setLoggingLevel(logging_level);
}
Loading

0 comments on commit 6def875

Please sign in to comment.