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

C API enhancements #3133

Closed
mewim opened this issue Mar 25, 2024 · 0 comments
Closed

C API enhancements #3133

mewim opened this issue Mar 25, 2024 · 0 comments
Assignees
Labels
apis usability Issues related to better usability experience, including bad error messages

Comments

@mewim
Copy link
Member

mewim commented Mar 25, 2024

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:

  1. We do not provide a way to check if the function call is successful or not (see Windows: C++ ABI stability? #2521). When these calls fail, they simply return 0/NULL/default value.
  2. Functions such as kuzu_flat_tuple* kuzu_query_result_get_next(kuzu_query_result* query_result) always creates a new kuzu_flat_tuple on each API call, which may cause additional performance overhead to create pointers, and it can be annoying for the user to call free 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 (

KUZU_C_API void kuzu_destroy_string(char* str);
), 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

// TODO: Bind utility functions for kuzu_date_t, kuzu_timestamp_t, and kuzu_interval_t
. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
apis usability Issues related to better usability experience, including bad error messages
Projects
None yet
Development

No branches or pull requests

2 participants