kuzu
kuzu copied to clipboard
C API enhancements
Error handling and memory reuse
Our current C API uses the signatures similar to the following examples:
kuzu_database* kuzu_database_init(const char* database_path, kuzu_system_config config);
kuzu_flat_tuple* kuzu_query_result_get_next(kuzu_query_result* query_result)
This creates several problems:
- We do not provide a way to check if the function call is successful or not (see #2521). When these calls fail, they simply return 0/NULL/default value.
- Functions such as
kuzu_flat_tuple* kuzu_query_result_get_next(kuzu_query_result* query_result)
always creates a newkuzu_flat_tuple
on each API call, which may cause additional performance overhead to create pointers, and it can be annoying for the user to callfree
for each iteration.
To address this, we should introduction error codes and also change the function signatures to return by out parameters instead of directly return the results.
Examples
Instead of:
kuzu_database* kuzu_database_init(const char* database_path, kuzu_system_config config);
we should use:
kuzu_state kuzu_database_init(const char* database_path, kuzu_system_config config, kuzu_database* out_database);
Instead of:
kuzu_flat_tuple* kuzu_query_result_get_next(kuzu_query_result* query_result);
we should use:
kuzu_state kuzu_query_result_get_next(kuzu_query_result* query_result, kuzu_flat_tuple* out_flat_tuple);
Reuse memory
After the function call, the underlying pointer of the out
parameters will be set to point to the return object if the call successes. This makes it easier for the user to reuse the object.
Instead of:
while (kuzu_query_result_has_next(result)) {
kuzu_flat_tuple* tuple = kuzu_query_result_get_next(result);
// Do something with the tuple
...
kuzu_flat_tuple_destroy(tuple);
}
The user can now do:
kuzu_flat_tuple tuple;
kuzu_state state;
while (kuzu_query_result_has_next(result)) {
state = kuzu_query_result_get_next(result, &tuple);
// Do something with the tuple
...
}
kuzu_flat_tuple_destroy(&tuple);
We should make it clear in the documentation regarding which objects can be reused (criteria: backed by smart pointers and not copied) and which ones cannot be reused, so that the user understands better when to call the destructors.
Error handle
The function will also return a kuzu_state
which is a enum/int for error code to facilitate error handling. The return code will be set to 0 if the call is successful, otherwise it should be set to an error code. This improves the error handling.
In https://github.com/duckdb/duckdb/blob/8ef134fa0e855ad0cd9d03af0f8907a6adab8718/src/include/duckdb.h#L140, the DuckDB state code is simply defined as a enum of 0 and 1, but we should consider follow SQLite's approach to have a more extensive list of error codes and also provide a function const char *kuzu_errstr(kuzu_state)
function to decode the error code and return meaningful string.
Additionally, we should double check to make sure that all the exceptions thrown by C++ is caught in C API implementation and converted to error code. In C, there is no support for exception handling. Any uncaught exception will cause the process to terminate.
Provide destructors
In our C API, we mostly follow the opaque pointer approach, which returns a pointer to an object we allocated. For most of the objects we return, we provide a destructor function. However, for some objects such as string, we relied on the user to call the libc free()
function, which may cause error (#2527) on platforms such as Windows MSVC. The destructor for strings were added (https://github.com/kuzudb/kuzu/blob/3813eed8fbf99f88b6cb5bf315924595197869e7/src/include/c_api/kuzu.h#L1251), but we should double check if there are other types that requires a destructor.
Features and ergonomics
The bindings we provide for several object types, such as kuzu_date_t
, kuzu_timestamp_t
, and kuzu_interval_t
are very basic. For example, the user can only create a kuzu_date_t
by passing in the days since 1970-01-01 00:00:00 UTC
. We left a TODO at https://github.com/kuzudb/kuzu/blob/3813eed8fbf99f88b6cb5bf315924595197869e7/src/include/c_api/kuzu.h#L1270. This should be addressed by binding the utility functions for these types. For example, the user should be able to get the year, month, and day from kuzu_date_t
and also create it from year, month, and day.