Multiple changes.

- Minor README updates.
- Create LICENSE and History files.
- Use 'func' instead of 'proc' where applicable.
- Add some destructor debug.
- Rename primary exceptions to 'X-error'.
- Bind to proper object types in prepared statement parameters.
- Retain the found 'type' in the KuzuValue object.

FossilOrigin-Name: db59c0b901b1715170e0d269fc2bf00477ac48af4d10a747eb5a749adbf6268e
This commit is contained in:
mahlon 2025-03-29 23:17:10 +00:00
parent 421cb87e57
commit db85c36d70
22 changed files with 284 additions and 162 deletions

View file

@ -1,6 +1,6 @@
# vim: set et sta sw=4 ts=4 :
proc kuzuConfig*(
func kuzuConfig*(
buffer_pool_size = KUZU_DEFAULT_CONFIG.buffer_pool_size,
max_num_threads = KUZU_DEFAULT_CONFIG.max_num_threads,
enable_compression = KUZU_DEFAULT_CONFIG.enable_compression,

View file

@ -3,10 +3,11 @@
proc `=destroy`*( conn: KuzuConnectionObj ) =
## Graceful cleanup for open connection handles.
if conn.valid:
when defined( debug ): echo &"Destroying connection: {conn}"
kuzu_connection_destroy( addr conn.handle )
proc connect*( db: KuzuDatabase ): KuzuConnection =
func connect*( db: KuzuDatabase ): KuzuConnection =
## Connect to a database.
result = new KuzuConnection
if kuzu_connection_init( addr db.handle, addr result.handle ) == KuzuSuccess:
@ -15,12 +16,12 @@ proc connect*( db: KuzuDatabase ): KuzuConnection =
raise newException( KuzuException, "Unable to connect to the database." )
proc queryTimeout*( conn: KuzuConnection, timeout: uint64 ) =
func queryTimeout*( conn: KuzuConnection, timeout: uint64 ) =
## Set a maximum time limit (in milliseconds) for query runtime.
discard kuzu_connection_set_query_timeout( addr conn.handle, timeout )
proc queryInterrupt*( conn: KuzuConnection ) =
func queryInterrupt*( conn: KuzuConnection ) =
## Cancel any running queries.
kuzu_connection_interrupt( addr conn.handle )

View file

@ -3,10 +3,11 @@
proc `=destroy`*( db: KuzuDatabaseObj ) =
## Graceful cleanup for an open DB handle when it goes out of scope.
if db.valid:
when defined( debug ): echo &"Destroying database: {db}"
kuzu_database_destroy( addr db.handle )
proc newKuzuDatabase*( path="", config=kuzuConfig() ): KuzuDatabase =
func newKuzuDatabase*( path="", config=kuzuConfig() ): KuzuDatabase =
## Create a new Kuzu database handle. Creates an in-memory
## database by default, but writes to disk if a +path+ is supplied.

View file

@ -3,10 +3,11 @@
proc `=destroy`*( query: KuzuQueryResultObj ) =
## Graceful cleanup for out of scope query objects.
if query.valid:
when defined( debug ): echo &"Destroying query: {query}"
kuzu_query_result_destroy( addr query.handle )
proc getQueryMetadata( query: KuzuQueryResult ) =
func getQueryMetadata( query: KuzuQueryResult ) =
## Find and retain additional data for the query.
query.num_columns = kuzu_query_result_get_num_columns( addr query.handle )
query.num_tuples = kuzu_query_result_get_num_tuples( addr query.handle )
@ -21,6 +22,7 @@ proc getQueryMetadata( query: KuzuQueryResult ) =
# Column information.
query.column_types = @[]
query.column_names = @[]
if query.num_columns == 0: return
for idx in ( 0 .. query.num_columns-1 ):
# types
@ -46,7 +48,7 @@ proc getQueryMetadata( query: KuzuQueryResult ) =
kuzu_destroy_string( name )
proc query*( conn: KuzuConnection, query: string ): KuzuQueryResult =
func query*( conn: KuzuConnection, query: string ): KuzuQueryResult =
## Perform a database +query+ and return the result.
result = new KuzuQueryResult
@ -55,16 +57,17 @@ proc query*( conn: KuzuConnection, query: string ): KuzuQueryResult =
result.getQueryMetadata()
else:
var err = kuzu_query_result_get_error_message( addr result.handle )
raise newException( KuzuQueryException, &"Error running query: {err}" )
raise newException( KuzuQueryError, &"Error running query: {err}" )
proc `=destroy`*( prepared: KuzuPreparedStatementObj ) =
## Graceful cleanup for out of scope prepared objects.
if prepared.valid:
when defined( debug ): echo &"Destroying prepared statement: {prepared}"
kuzu_prepared_statement_destroy( addr prepared.handle )
proc prepare*( conn: KuzuConnection, query: string ): KuzuPreparedStatement =
func prepare*( conn: KuzuConnection, query: string ): KuzuPreparedStatement =
## Return a prepared statement that can avoid planning for repeat calls,
## with optional variable binding via #execute.
result = new KuzuPreparedStatement
@ -73,7 +76,40 @@ proc prepare*( conn: KuzuConnection, query: string ): KuzuPreparedStatement =
result.valid = true
else:
var err = kuzu_prepared_statement_get_error_message( addr result.handle )
raise newException( KuzuQueryException, &"Error preparing statement: {err}" )
raise newException( KuzuQueryError, &"Error preparing statement: {err}" )
func bindValue[T](
stmtHandle: kuzu_prepared_statement,
key: cstring,
val: T
) =
## Bind a key/value to a prepared statement handle.
when typeOf( val ) is bool:
assert( kuzu_prepared_statement_bind_bool( addr stmtHandle, key, val ) == KuzuSuccess )
elif typeOf( val ) is int8:
assert( kuzu_prepared_statement_bind_int8( addr stmtHandle, key, val ) == KuzuSuccess )
elif typeOf( val ) is int16:
assert( kuzu_prepared_statement_bind_int16( addr stmtHandle, key, val ) == KuzuSuccess )
elif typeOf( val ) is int64:
assert( kuzu_prepared_statement_bind_int64( addr stmtHandle, key, val ) == KuzuSuccess )
elif typeOf( val ) is int or typeOf( val ) is int32:
assert( kuzu_prepared_statement_bind_int32( addr stmtHandle, key, val.int32 ) == KuzuSuccess )
elif typeOf( val ) is uint8:
assert( kuzu_prepared_statement_bind_uint8( addr stmtHandle, key, val ) == KuzuSuccess )
elif typeOf( val ) is uint16:
assert( kuzu_prepared_statement_bind_uint16( addr stmtHandle, key, val ) == KuzuSuccess )
elif typeOf( val ) is uint64:
assert( kuzu_prepared_statement_bind_uint64( addr stmtHandle, key, val ) == KuzuSuccess )
elif typeOf( val ) is uint or typeOf( val ) is uint32:
assert( kuzu_prepared_statement_bind_uint32( addr stmtHandle, key, val.uint32 ) == KuzuSuccess )
elif typeOf( val ) is float:
assert( kuzu_prepared_statement_bind_double( addr stmtHandle, key, val ) == KuzuSuccess )
elif typeOf( val ) is string:
# Fallback to string. For custom types, just cast in the cypher query.
assert( kuzu_prepared_statement_bind_string( addr stmtHandle, key, val.cstring ) == KuzuSuccess )
else:
raise newException( KuzuTypeError, &"""Unsupported type {$typeOf(val)} for prepared statement.""" )
proc execute*(
@ -86,111 +122,42 @@ proc execute*(
result = new KuzuQueryResult
for key, val in params.fieldPairs:
#
# FIXME: type checks and conversions for all bound variables
# from nim types to supported Kuzu types.
#
discard kuzu_prepared_statement_bind_string( addr prepared.handle, key.cstring, val.cstring )
#[
KUZU_C_API kuzu_state kuzu_prepared_statement_bind_bool (kuzu_prepared_statement *prepared_statement, const char *param_name, bool value)
Binds the given boolean value to the given parameter name in the prepared statement.
KUZU_C_API kuzu_state kuzu_prepared_statement_bind_int64 (kuzu_prepared_statement *prepared_statement, const char *param_name, int64_t value)
Binds the given int64_t value to the given parameter name in the prepared statement.
KUZU_C_API kuzu_state kuzu_prepared_statement_bind_int32 (kuzu_prepared_statement *prepared_statement, const char *param_name, int32_t value)
Binds the given int32_t value to the given parameter name in the prepared statement.
KUZU_C_API kuzu_state kuzu_prepared_statement_bind_int16 (kuzu_prepared_statement *prepared_statement, const char *param_name, int16_t value)
Binds the given int16_t value to the given parameter name in the prepared statement.
KUZU_C_API kuzu_state kuzu_prepared_statement_bind_int8 (kuzu_prepared_statement *prepared_statement, const char *param_name, int8_t value)
Binds the given int8_t value to the given parameter name in the prepared statement.
KUZU_C_API kuzu_state kuzu_prepared_statement_bind_uint64 (kuzu_prepared_statement *prepared_statement, const char *param_name, uint64_t value)
Binds the given uint64_t value to the given parameter name in the prepared statement.
KUZU_C_API kuzu_state kuzu_prepared_statement_bind_uint32 (kuzu_prepared_statement *prepared_statement, const char *param_name, uint32_t value)
Binds the given uint32_t value to the given parameter name in the prepared statement.
KUZU_C_API kuzu_state kuzu_prepared_statement_bind_uint16 (kuzu_prepared_statement *prepared_statement, const char *param_name, uint16_t value)
Binds the given uint16_t value to the given parameter name in the prepared statement.
KUZU_C_API kuzu_state kuzu_prepared_statement_bind_uint8 (kuzu_prepared_statement *prepared_statement, const char *param_name, uint8_t value)
Binds the given int8_t value to the given parameter name in the prepared statement.
KUZU_C_API kuzu_state kuzu_prepared_statement_bind_double (kuzu_prepared_statement *prepared_statement, const char *param_name, double value)
Binds the given double value to the given parameter name in the prepared statement.
KUZU_C_API kuzu_state kuzu_prepared_statement_bind_float (kuzu_prepared_statement *prepared_statement, const char *param_name, float value)
Binds the given float value to the given parameter name in the prepared statement.
KUZU_C_API kuzu_state kuzu_prepared_statement_bind_date (kuzu_prepared_statement *prepared_statement, const char *param_name, kuzu_date_t value)
Binds the given date value to the given parameter name in the prepared statement.
KUZU_C_API kuzu_state kuzu_prepared_statement_bind_timestamp_ns (kuzu_prepared_statement *prepared_statement, const char *param_name, kuzu_timestamp_ns_t value)
Binds the given timestamp_ns value to the given parameter name in the prepared statement.
KUZU_C_API kuzu_state kuzu_prepared_statement_bind_timestamp_sec (kuzu_prepared_statement *prepared_statement, const char *param_name, kuzu_timestamp_sec_t value)
Binds the given timestamp_sec value to the given parameter name in the prepared statement.
KUZU_C_API kuzu_state kuzu_prepared_statement_bind_timestamp_tz (kuzu_prepared_statement *prepared_statement, const char *param_name, kuzu_timestamp_tz_t value)
Binds the given timestamp_tz value to the given parameter name in the prepared statement.
KUZU_C_API kuzu_state kuzu_prepared_statement_bind_timestamp_ms (kuzu_prepared_statement *prepared_statement, const char *param_name, kuzu_timestamp_ms_t value)
Binds the given timestamp_ms value to the given parameter name in the prepared statement.
KUZU_C_API kuzu_state kuzu_prepared_statement_bind_timestamp (kuzu_prepared_statement *prepared_statement, const char *param_name, kuzu_timestamp_t value)
Binds the given timestamp value to the given parameter name in the prepared statement.
KUZU_C_API kuzu_state kuzu_prepared_statement_bind_interval (kuzu_prepared_statement *prepared_statement, const char *param_name, kuzu_interval_t value)
Binds the given interval value to the given parameter name in the prepared statement.
KUZU_C_API kuzu_state kuzu_prepared_statement_bind_string (kuzu_prepared_statement *prepared_statement, const char *param_name, const char *value)
Binds the given string value to the given parameter name in the prepared statement.
KUZU_C_API kuzu_state kuzu_prepared_statement_bind_value (kuzu_prepared_statement *prepared_statement, const char *param_name, kuzu_value *value)
]#
prepared.handle.bindValue( key, val )
if kuzu_connection_execute(
addr prepared.conn.handle,
addr prepared.handle,
addr result.handle
) == KuzuSuccess:
discard kuzu_query_result_get_query_summary( addr result.handle, addr result.summary )
result.num_columns = kuzu_query_result_get_num_columns( addr result.handle )
result.num_tuples = kuzu_query_result_get_num_tuples( addr result.handle )
result.compile_time = kuzu_query_summary_get_compiling_time( addr result.summary )
result.execution_time = kuzu_query_summary_get_execution_time( addr result.summary )
result.valid = true
result.valid = false
result.getQueryMetadata()
else:
var err = kuzu_query_result_get_error_message( addr result.handle )
raise newException( KuzuQueryException, &"Error executing prepared statement: {err}" )
raise newException( KuzuQueryError, &"Error executing prepared statement: {err}" )
proc `$`*( query: KuzuQueryResult ): string =
func `$`*( query: KuzuQueryResult ): string =
## Return the entire result set as a string.
result = $kuzu_query_result_to_string( addr query.handle )
proc hasNext*( query: KuzuQueryResult ): bool =
func hasNext*( query: KuzuQueryResult ): bool =
## Returns +true+ if there are more tuples to be consumed.
result = kuzu_query_result_has_next( addr query.handle )
proc getNext*( query: KuzuQueryResult ): KuzuFlatTuple =
## Consume and return the next tuple result, or raise a KuzuIndexException
func getNext*( query: KuzuQueryResult ): KuzuFlatTuple =
## Consume and return the next tuple result, or raise a KuzuIndexError
## if at the end of the result set.
result = new KuzuFlatTuple
if kuzu_query_result_get_next( addr query.handle, addr result.handle ) == KuzuSuccess:
result.valid = true
result.num_columns = query.num_columns
else:
raise newException( KuzuIndexException, &"Query iteration past end." )
raise newException( KuzuIndexError, &"Query iteration past end." )
proc rewind*( query: KuzuQueryResult ) =
func rewind*( query: KuzuQueryResult ) =
## Reset query iteration back to the beginning.
kuzu_query_result_reset_iterator( addr query.handle )

View file

@ -3,22 +3,25 @@
proc `=destroy`*( tpl: KuzuFlatTupleObj ) =
## Graceful cleanup for out of scope tuples.
if tpl.valid:
when defined( debug ): echo &"Destroying tuple: {tpl}"
kuzu_flat_tuple_destroy( addr tpl.handle )
proc `$`*( tpl: KuzuFlatTuple ): string =
func `$`*( tpl: KuzuFlatTuple ): string =
## Stringify a tuple.
result = $kuzu_flat_tuple_to_string( addr tpl.handle )
result.removeSuffix( "\n" )
proc `[]`*( tpl: KuzuFlatTuple, idx: int ): KuzuValue =
## Returns a KuzuValue at the given +idx+.
func `[]`*( tpl: KuzuFlatTuple, idx: int ): KuzuValue =
## Returns a KuzuValue at the given *idx*.
result = new KuzuValue
if kuzu_flat_tuple_get_value( addr tpl.handle, idx.uint64, addr result.handle ) == KuzuSuccess:
result.valid = true
result.getType()
else:
raise newException( KuzuIndexException,
raise newException( KuzuIndexError,
&"Unable to fetch tuple value at idx {idx}. ({tpl.num_columns} column(s).)" )

View file

@ -39,9 +39,11 @@ type
KuzuValueObj = object
handle: kuzu_value
valid = false
kind*: kuzu_data_type_id
KuzuValue* = ref KuzuValueObj
KuzuException* = object of CatchableError
KuzuQueryException* = object of KuzuException
KuzuIndexException* = object of KuzuException
KuzuException* = object of CatchableError
KuzuQueryError* = object of KuzuException
KuzuIndexError* = object of KuzuException
KuzuTypeError* = object of KuzuException

View file

@ -3,41 +3,28 @@
proc `=destroy`*( value: KuzuValueObj ) =
## Graceful cleanup for out of scope values.
if value.valid:
when defined( debug ): echo &"Destroying value: {value}"
kuzu_value_destroy( addr value.handle )
proc `$`*( value: KuzuValue ): string =
func `$`*( value: KuzuValue ): string =
## Stringify a value.
result = $kuzu_value_to_string( addr value.handle )
proc kind*( value: KuzuValue ): kuzu_data_type_id =
## Find and return the native Kuzu type of this value.
func getType( value: KuzuValue ) =
## Find and set the native Kuzu type of this value.
var logical_type: kuzu_logical_type
kuzu_value_get_data_type( addr value.handle, addr logical_type )
result = kuzu_data_type_get_id( addr logical_type )
# var num: uint64
# discard kuzu_data_type_get_num_elements_in_array( addr logical_type, addr num )
# echo "HMMM ", $num
value.kind = kuzu_data_type_get_id( addr logical_type )
kuzu_data_type_destroy( addr logical_type )
# enum_kuzu_data_type_id_570425857* {.size: sizeof(cuint).} = enum
# KUZU_ANY = 0, KUZU_NODE = 10, KUZU_REL = 11, KUZU_RECURSIVE_REL = 12,
# KUZU_SERIAL = 13, KUZU_BOOL = 22, KUZU_INT64 = 23, KUZU_INT32 = 24,
# KUZU_INT16 = 25, KUZU_INT8 = 26, KUZU_UINT64 = 27, KUZU_UINT32 = 28,
# KUZU_UINT16 = 29, KUZU_UINT8 = 30, KUZU_INT128 = 31, KUZU_DOUBLE = 32,
# KUZU_FLOAT = 33, KUZU_DATE = 34, KUZU_TIMESTAMP = 35,
# KUZU_TIMESTAMP_SEC = 36, KUZU_TIMESTAMP_MS = 37, KUZU_TIMESTAMP_NS = 38,
# KUZU_TIMESTAMP_TZ = 39, KUZU_INTERVAL = 40, KUZU_DECIMAL = 41,
# KUZU_INTERNAL_ID = 42, KUZU_STRING = 50, KUZU_BLOB = 51, KUZU_LIST = 52,
# KUZU_ARRAY = 53, KUZU_STRUCT = 54, KUZU_MAP = 55, KUZU_UNION = 56,
# KUZU_POINTER = 58, KUZU_UUID = 59
# proc getValue*( value: KuzuValue ):
#
# FIXME: type checks and conversions from supported kuzu
# types to supported Nim types.
#
# Currently the value can only be stringified via `$`.
#
func toInt8*( value: KuzuValue ): int8 =
if value.kind != KUZU_INT8:
raise newException( KuzuTypeError, &"Mismatched types: {value.kind} != int8" )
assert(
kuzu_value_get_int8( addr value.handle, addr result ) ==
KuzuSuccess
)