diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a18ad53 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true + +# Space indentation +[**.nim] +indent_style = space +indent_size = 4 + diff --git a/History.md b/History.md new file mode 100644 index 0000000..5e63199 --- /dev/null +++ b/History.md @@ -0,0 +1,7 @@ +# Release History for nim-kuzu + +--- +## v0.1.0 [2025-??-??] Mahlon E. Smith + +Initial public release. + diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..ded6a6b --- /dev/null +++ b/LICENCE @@ -0,0 +1,29 @@ + +Copyright (c) 2025 Mahlon E. Smith +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the author/s, nor the names of the project's + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/README.md b/README.md index 7802664..f10a122 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ github_mirror ## Description -This is a Nim binding for the Kuzu graph database library. +This is a Nim binding for the [Kuzu](https://kuzudb.com) graph database library. Kuzu is an embedded graph database built for query speed and scalability. It is optimized for handling complex join-heavy analytical workloads on very large @@ -32,7 +32,7 @@ For more information about Kuzu itself, see its ## Prerequisites * A functioning Nim >= 2 installation -- [KuzuDB](https://kuzudb.com) +- [KuzuDB](https://kuzudb.com) to be locally installed! ## Installation @@ -76,34 +76,3 @@ development. A note of thanks to @mantielero on Github, who has a Kuzu binding for an early KuzuDB (0.4.x) that I found after starting this project. - -## License - -Copyright (c) 2025 Mahlon E. Smith -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the author/s, nor the names of the project's - contributors may be used to endorse or promote products derived from this - software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - diff --git a/src/kuzu/config.nim b/src/kuzu/config.nim index 82c86b1..c8bc4b5 100644 --- a/src/kuzu/config.nim +++ b/src/kuzu/config.nim @@ -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, diff --git a/src/kuzu/connection.nim b/src/kuzu/connection.nim index 42a16e6..0907a98 100644 --- a/src/kuzu/connection.nim +++ b/src/kuzu/connection.nim @@ -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 ) diff --git a/src/kuzu/database.nim b/src/kuzu/database.nim index 4cfa97d..830f552 100644 --- a/src/kuzu/database.nim +++ b/src/kuzu/database.nim @@ -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. diff --git a/src/kuzu/queries.nim b/src/kuzu/queries.nim index 6c4ee60..2aac566 100644 --- a/src/kuzu/queries.nim +++ b/src/kuzu/queries.nim @@ -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 ) diff --git a/src/kuzu/tuple.nim b/src/kuzu/tuple.nim index b584279..b16917a 100644 --- a/src/kuzu/tuple.nim +++ b/src/kuzu/tuple.nim @@ -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).)" ) diff --git a/src/kuzu/types.nim b/src/kuzu/types.nim index 0767417..aec9eec 100644 --- a/src/kuzu/types.nim +++ b/src/kuzu/types.nim @@ -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 diff --git a/src/kuzu/value.nim b/src/kuzu/value.nim index 14d5a01..1e32514 100644 --- a/src/kuzu/value.nim +++ b/src/kuzu/value.nim @@ -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 + ) diff --git a/tests/queries/t_can_prepare_a_statement.nim b/tests/queries/t_can_bind_to_a_prepared_statement.nim similarity index 100% rename from tests/queries/t_can_prepare_a_statement.nim rename to tests/queries/t_can_bind_to_a_prepared_statement.nim diff --git a/tests/queries/t_can_bind_various_datatypes.nim b/tests/queries/t_can_bind_various_datatypes.nim new file mode 100644 index 0000000..9a3780c --- /dev/null +++ b/tests/queries/t_can_bind_various_datatypes.nim @@ -0,0 +1,55 @@ +# vim: set et sta sw=4 ts=4 : + +discard """ +output: "0|-222222|128|True|Stuff!|3.344903|239.299923|a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11|2025-03-29" +""" + +import kuzu + +let db = newKuzuDatabase() +let conn = db.connect + +var q = conn.query( """CREATE NODE TABLE Doop ( + id SERIAL, + num INT, + unum UINT8, + woo BOOL, + thing STRING, + float FLOAT, + double DOUBLE, + uuid UUID, + date DATE, + PRIMARY KEY(id) +)""" ) +assert typeOf( q ) is KuzuQueryResult + + +var stmt = conn.prepare( """CREATE (d:Doop { + woo: $woo, + thing: $thing, + num: $num, + unum: $unum, + float: $float, + double: $double, + uuid: UUID($uuid), + date: DATE($date) +})""" ) +assert typeOf( stmt ) is KuzuPreparedStatement + + +q = stmt.execute(( + woo: true, + thing: "Stuff!", + num: -222222, + unum: 128, + float: 3.34490345039450345, + double: 239.299922883992, + uuid: "A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11", + date: "2025-03-29" +)) +assert typeOf( q ) is KuzuQueryResult + + +q = conn.query( "MATCH (d:Doop) RETURN d.*" ) +echo $q.getNext + diff --git a/tests/queries/t_knows_the_column_names.nim b/tests/queries/t_knows_the_column_names.nim new file mode 100644 index 0000000..ab41d60 --- /dev/null +++ b/tests/queries/t_knows_the_column_names.nim @@ -0,0 +1,17 @@ +# vim: set et sta sw=4 ts=4 : + +import kuzu + +let db = newKuzuDatabase() +let conn = db.connect + +var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, thing STRING, PRIMARY KEY(id) )" ) +assert typeOf( q ) is KuzuQueryResult + +q = conn.query( "CREATE (d:Doop {thing: 'okay!'})" ) +q = conn.query( "MATCH (d:Doop) RETURN d.id AS IDENTIFIER, d.thing AS THING" ) + +assert q.column_names.len == 2 +assert q.column_names[0] == "IDENTIFIER" +assert q.column_names[1] == "THING" + diff --git a/tests/queries/t_knows_the_column_types.nim b/tests/queries/t_knows_the_column_types.nim new file mode 100644 index 0000000..c972f28 --- /dev/null +++ b/tests/queries/t_knows_the_column_types.nim @@ -0,0 +1,17 @@ +# vim: set et sta sw=4 ts=4 : + +import kuzu + +let db = newKuzuDatabase() +let conn = db.connect + +var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, thing STRING, PRIMARY KEY(id) )" ) +assert typeOf( q ) is KuzuQueryResult + +q = conn.query( "CREATE (d:Doop {thing: 'okay!'})" ) +q = conn.query( "MATCH (d:Doop) RETURN d.id AS IDENTIFIER, d.thing AS THING" ) + +assert q.column_types.len == 2 +assert $q.column_types[0] == "KUZU_SERIAL" +assert $q.column_types[1] == "KUZU_STRING" + diff --git a/tests/queries/t_raises_on_bad_syntax.nim b/tests/queries/t_raises_on_bad_syntax.nim index 8ad0ef2..5b8b15a 100644 --- a/tests/queries/t_raises_on_bad_syntax.nim +++ b/tests/queries/t_raises_on_bad_syntax.nim @@ -9,6 +9,6 @@ let conn = db.connect try: discard conn.query( "NOPE NOPE NOPE" ) -except KuzuQueryException as err: - assert err.msg.contains( re"""Error running query:.*extraneous input 'NOPE'""" ) +except KuzuQueryError as err: + assert err.msg.contains( re"""Parser exception: extraneous input 'NOPE'""" ) diff --git a/tests/queries/t_raises_on_invalid_varbind.nim b/tests/queries/t_raises_on_invalid_varbind.nim new file mode 100644 index 0000000..682308a --- /dev/null +++ b/tests/queries/t_raises_on_invalid_varbind.nim @@ -0,0 +1,31 @@ +# vim: set et sta sw=4 ts=4 : + +import + std/re +import kuzu + +let db = newKuzuDatabase() +let conn = db.connect + +var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, created DATE, PRIMARY KEY(id) )" ) +assert typeOf( q ) is KuzuQueryResult + +var p = conn.prepare( "CREATE (d:Doop {created: $created})" ) +assert typeOf( p ) is KuzuPreparedStatement + +# Typecast binding failure +# +try: + discard p.execute( (created: "1111-1111") ) +except KuzuQueryError as err: + assert err.msg.contains( re"""Expression \$created has data type STRING but expected DATE.""" ) + +# Invalid value for typecast +# +p = conn.prepare( "CREATE (d:Doop {created: DATE($created)})" ) +try: + discard p.execute( (created: "1111-1111") ) +except KuzuQueryError as err: + assert err.msg.contains( re"""Given: "1111-1111". Expected format: \(YYYY-MM-DD\)""" ) + + diff --git a/tests/queries/t_raises_on_unknown_varbind.nim b/tests/queries/t_raises_on_unknown_varbind.nim index cc23724..bebe094 100644 --- a/tests/queries/t_raises_on_unknown_varbind.nim +++ b/tests/queries/t_raises_on_unknown_varbind.nim @@ -14,7 +14,7 @@ assert typeOf( p ) is KuzuPreparedStatement try: discard p.execute( (nope: "undefined var in statement!") ) -except KuzuQueryException as err: +except KuzuQueryError as err: assert err.msg.contains( re"""Parameter nope not found.""" ) diff --git a/tests/queries/t_raises_with_invalid_prepared_statement.nim b/tests/queries/t_raises_with_invalid_prepared_statement.nim index 64b7ebd..8a241ac 100644 --- a/tests/queries/t_raises_with_invalid_prepared_statement.nim +++ b/tests/queries/t_raises_with_invalid_prepared_statement.nim @@ -14,7 +14,7 @@ assert typeOf( p ) is KuzuPreparedStatement try: discard p.execute -except KuzuQueryException as err: - assert err.msg.contains( re""".*Error executing prepared statement:.*CREAET""" ) +except KuzuQueryError as err: + assert err.msg.contains( re"""Parser exception: extraneous input 'CREAET'""" ) diff --git a/tests/tuples/t_throws_exception_fetching_at_end.nim b/tests/tuples/t_throws_exception_fetching_at_end.nim index ca4cc55..adb4ffd 100644 --- a/tests/tuples/t_throws_exception_fetching_at_end.nim +++ b/tests/tuples/t_throws_exception_fetching_at_end.nim @@ -12,6 +12,6 @@ q = conn.query( "MATCH (d:Doop) RETURN d.thing" ) try: discard q.getNext -except KuzuIndexException as err: +except KuzuIndexError as err: assert err.msg.contains( re"""Query iteration past end.""" ) diff --git a/tests/tuples/t_throws_exception_invalid_index.nim b/tests/tuples/t_throws_exception_invalid_index.nim index 78fe6d8..6038f0c 100644 --- a/tests/tuples/t_throws_exception_invalid_index.nim +++ b/tests/tuples/t_throws_exception_invalid_index.nim @@ -15,6 +15,6 @@ let tup = q.getNext try: echo tup[22] -except KuzuIndexException as err: +except KuzuIndexError as err: assert err.msg.contains( re"""Unable to fetch tuple value at idx 22.""" ) diff --git a/tests/values/t_can_return_a_type.nim b/tests/values/t_can_return_a_type.nim new file mode 100644 index 0000000..3e8a788 --- /dev/null +++ b/tests/values/t_can_return_a_type.nim @@ -0,0 +1,21 @@ +# vim: set et sta sw=4 ts=4 : + +import kuzu + +let db = newKuzuDatabase() +let conn = db.connect + +var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, thing STRING, PRIMARY KEY(id) )" ) + +q = conn.query( "CREATE (d:Doop {thing: 'okay!'})" ) +q = conn.query( "MATCH (d:Doop) RETURN d.id, d.thing, d" ) + +var row = q.getNext +var id = row[0] +var thing = row[1] +var node = row[2] + +assert id.kind == KUZU_INT64 +assert thing.kind == KUZU_STRING +assert node.kind == KUZU_NODE +