From 26e3018160c5e837f2e2a11e18b631d0a103ebff Mon Sep 17 00:00:00 2001 From: "Mahlon E. Smith" Date: Thu, 17 Jul 2025 09:57:38 -0700 Subject: [PATCH 1/2] Support chaining multiple result sets. --- src/kuzu/queries.nim | 32 ++++++++++++-- src/kuzu/types.nim | 9 ++-- .../t_can_contain_multiple_result_sets.nim | 42 +++++++++++++++++++ 3 files changed, 76 insertions(+), 7 deletions(-) create mode 100644 tests/queries/t_can_contain_multiple_result_sets.nim diff --git a/src/kuzu/queries.nim b/src/kuzu/queries.nim index 76d32a3..03fe293 100644 --- a/src/kuzu/queries.nim +++ b/src/kuzu/queries.nim @@ -147,14 +147,14 @@ func hasNext*( query: KuzuQueryResult ): bool = func getNext*( query: KuzuQueryResult ): KuzuFlatTuple = - ## Consume and return the next tuple result, or raise a KuzuIndexError - ## if at the end of the result set. + ## Consume and return the next tuple result, or raise a KuzuIterationError + ## if at the end of the result tuples. 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( KuzuIndexError, &"Query iteration past end." ) + raise newException( KuzuIterationError, &"Query iteration past end of tuples." ) func rewind*( query: KuzuQueryResult ) = @@ -162,6 +162,32 @@ func rewind*( query: KuzuQueryResult ) = kuzu_query_result_reset_iterator( addr query.handle ) +func hasNextSet*( query: KuzuQueryResult ): bool = + ## Returns +true+ if there are more result sets to be consumed. + result = kuzu_query_result_has_next_query_result( addr query.handle ) + + +# Keeping this private, because it's only safe to call from within +# an iterator -- overwriting a query variable with the next result +# goes boom. +func getNextSet( query: KuzuQueryResult ): KuzuQueryResult = + ## Consume and return the next query set result, or raise a KuzuIterationError + ## if at the end of sets. + result = new KuzuQueryResult + if kuzu_query_result_get_next_query_result( addr query.handle, addr result.handle ) == KuzuSuccess: + result.valid = true + result.getQueryMetadata() + else: + raise newException( KuzuIterationError, &"Query iteration past end of set." ) + + +iterator sets*( query: KuzuQueryResult ): KuzuQueryResult = + ## Iterate available query result sets, yielding to the block. + while query.hasNextSet: + yield query.getNextSet + # NOTE: There is no 'rewind' mechanism for result sets. + + iterator items*( query: KuzuQueryResult ): KuzuFlatTuple = ## Iterate available tuples, yielding to the block. while query.hasNext: diff --git a/src/kuzu/types.nim b/src/kuzu/types.nim index 59e5c5a..f5026eb 100644 --- a/src/kuzu/types.nim +++ b/src/kuzu/types.nim @@ -48,8 +48,9 @@ type keys*: seq[ string ] KuzuStructValue* = ref KuzuStructValueObj - KuzuException* = object of CatchableError - KuzuQueryError* = object of KuzuException - KuzuIndexError* = object of KuzuException - KuzuTypeError* = object of KuzuException + KuzuException* = object of CatchableError + KuzuQueryError* = object of KuzuException + KuzuIndexError* = object of KuzuException + KuzuIterationError* = object of KuzuException + KuzuTypeError* = object of KuzuException diff --git a/tests/queries/t_can_contain_multiple_result_sets.nim b/tests/queries/t_can_contain_multiple_result_sets.nim new file mode 100644 index 0000000..d0be2d5 --- /dev/null +++ b/tests/queries/t_can_contain_multiple_result_sets.nim @@ -0,0 +1,42 @@ +# vim: set et sta sw=4 ts=4 : + +discard """ +output: "Jenny|Lenny\nLenny\nJenny\n" +""" + +import kuzu + +let db = newKuzuDatabase() +let conn = db.connect + +var q = conn.query """ +CREATE NODE TABLE User( + id SERIAL PRIMARY KEY, + name STRING +); + +CREATE REL TABLE FOLLOWS( + From User To User +); + +MERGE (a:User {name: "Lenny"})-[f:Follows]->(b:User {name: "Jenny"}); +""" + +q = conn.query( "MATCH (u:User) RETURN *" ) + +assert typeOf( q ) is KuzuQueryResult +assert q.hasNextSet == false + +q = conn.query """ + MATCH (a:User)<-[f:Follows]-(b:User) RETURN a.name, b.name; + MATCH (u:User) RETURN u.name; +""" + +assert typeOf( q ) is KuzuQueryResult +assert q.hasNextSet == true + +echo q.getNext +for query_result in q.sets: + for row in query_result.items: + echo row + From c8da56faa8399834475d65812ac247b797c40c37 Mon Sep 17 00:00:00 2001 From: "Mahlon E. Smith" Date: Thu, 17 Jul 2025 16:04:12 -0700 Subject: [PATCH 2/2] Fixup docs, bump version. --- .gitignore | 1 + History.md | 17 +- USAGE.md | 46 +- experiments/imdb/imdb_find_actor_path.nim | 4 +- experiments/imdb/imdb_import.nim | 2 +- kuzu.nimble | 2 +- src/kuzu/constants.nim | 2 +- testresults.html | 1266 ----------------- .../t_throws_exception_fetching_at_end.nim | 2 +- 9 files changed, 65 insertions(+), 1277 deletions(-) delete mode 100644 testresults.html diff --git a/.gitignore b/.gitignore index 0e0226c..33b6351 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ kuzu nimcache/* tests/* testresults/* +testresults.html diff --git a/History.md b/History.md index 4ebac35..3628f3b 100644 --- a/History.md +++ b/History.md @@ -1,5 +1,19 @@ # Release History for nim-kuzu +--- +## v0.6.0 [2025-07-17] Mahlon E. Smith + +Enhancement: + +- Provide functions for retreiving multiple result sets from a + single primary query object. + + +Minutiae: + +- Add a KuzuInterationError, to disambiguate iteration errors from + IndexErrors. + --- ## v0.5.0 [2025-07-13] Mahlon E. Smith @@ -17,7 +31,7 @@ Bump for kuzu 0.10.0. No practical changes. Enhancement: - - Add `toBlob` for quickly fetching a sequence of opaque bytes. +- Add `toBlob` for quickly fetching a sequence of opaque bytes. --- @@ -34,4 +48,3 @@ so update version to reflect the 0.9.0 release. ## v0.1.0 [2025-03-31] Mahlon E. Smith Initial public release. - diff --git a/USAGE.md b/USAGE.md index 58ebd07..9c630b6 100644 --- a/USAGE.md +++ b/USAGE.md @@ -308,13 +308,13 @@ for pair in res.column_names.zip( res.column_types ): # ("e.activity", KUZU_TIMESTAMP) ``` -## Reading Result Sets +## Reading Results So far we've just been showing values by converting the entire `KuzuQueryResult` to a string. Convenient for quick examples and debugging, but not much else. A `KuzuQueryResult` is an iterator. You can use regular Nim functions that yield -each `KuzuFlatTuple` -- essentially, each row that was returned in the set. +each `KuzuFlatTuple` -- essentially, each row that was returned in a result set. ```nim var res = conn.query """ @@ -350,12 +350,52 @@ if res.hasNext: echo res.getNext #=> 2 echo res.getNext #=> 3 -echo res.getNext #=> KuzuIndexError exception! +echo res.getNext #=> KuzuIterationError exception! ``` Manually rewind the `KuzuQueryResult` via `rewind()`. +## Multiple Query Results + +A query can potentially return any number of separate statements. Iterate over +linked `KuzuQueryResult` objects with the `sets()` iterator. + +```nim +import kuzu + +let db = newKuzuDatabase() +let conn = db.connect + +let query = conn.query """ + UNWIND [1,2,3] as items + RETURN items; + + UNWIND [4,5,6] as items + RETURN items; + + UNWIND [7,8,9] as items + RETURN items; +""" + +for row in query: + echo row + # 1 + # 2 + # 3 + +for set in query.sets: + for row in set: + echo row + # 4 + # 5 + # 6 + # 7 + # 8 + # 9 +``` + + ## Working with Values A `KuzuFlatTuple` contains the entire row. You can index a value at its column diff --git a/experiments/imdb/imdb_find_actor_path.nim b/experiments/imdb/imdb_find_actor_path.nim index 94ed1ca..da1c13f 100644 --- a/experiments/imdb/imdb_find_actor_path.nim +++ b/experiments/imdb/imdb_find_actor_path.nim @@ -19,7 +19,7 @@ import std/strformat import kuzu -const DB = "imdb" +const DB = "imdb.kz" const DOT = "imdb-results.dot" if not DB.fileExists: @@ -37,7 +37,7 @@ var res: KuzuQueryResult var fromActor = paramStr(1) var toActor = paramStr(2) -var db = newKuzuDatabase( "imdb", kuzuConfig(read_only=true) ) +var db = newKuzuDatabase( DB, kuzuConfig(read_only=true) ) var conn = db.connect echo "Database opened: ", db.path diff --git a/experiments/imdb/imdb_import.nim b/experiments/imdb/imdb_import.nim index a55c8c5..77ccb51 100644 --- a/experiments/imdb/imdb_import.nim +++ b/experiments/imdb/imdb_import.nim @@ -20,7 +20,7 @@ import zip/gzipfiles, kuzu -const DB = "imdb" +const DB = "imdb.kz" const SOURCE = "https://datasets.imdbws.com" const FILES = @[ "name.basics", "title.basics", "title.principals" ] diff --git a/kuzu.nimble b/kuzu.nimble index 33de5ee..b124df0 100644 --- a/kuzu.nimble +++ b/kuzu.nimble @@ -1,6 +1,6 @@ # vim: set et sta sw=4 ts=4 : -version = "0.5.0" +version = "0.6.0" author = "Mahlon E. Smith" description = "Kuzu is an embedded graph database built for query speed and scalability." license = "BSD-3-Clause" diff --git a/src/kuzu/constants.nim b/src/kuzu/constants.nim index 6c528a7..705937e 100644 --- a/src/kuzu/constants.nim +++ b/src/kuzu/constants.nim @@ -1,6 +1,6 @@ # vim: set et sta sw=4 ts=4 : -const KUZU_VERSION* = "0.5.0" +const KUZU_VERSION* = "0.6.0" const KUZU_EXPECTED_LIBVERSION* = "0.11.0" const BLOB_MAXSIZE = 4096 diff --git a/testresults.html b/testresults.html deleted file mode 100644 index 101777d..0000000 --- a/testresults.html +++ /dev/null @@ -1,1266 +0,0 @@ - - - - - Testament Test Results - - - - - - - -
-

Testament Test Results Nim Tester

-
-
Hostname
-
kazak
-
Git Commit
-
not a git r
-
Branch ref.
-
fatal: not a git repository (or any parent up to mount point /home) -Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).
-
-
-
All Tests
-
- - 35 -
-
Successful Tests
-
- - 35 (100.00%) -
-
Skipped Tests
-
- - 0 (0.00%) -
-
Failed Tests
-
- - 0 (0.00%) -
-
-
- - - - - - - - - - - - - - - - - -
All Tests -
- - - - -
-
Successful Tests -
- - - - -
-
Skipped Tests -
- - - - -
-
Failed Tests -
- - - - -
-
-
-
-
- -
-
-
Name
-
tests/connection/t_can_create_new_connection.nim c
-
Category
-
connection
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/connection/t_can_interrupt_running_query.nim c
-
Category
-
connection
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/connection/t_can_set_connection_query_timeout.nim c
-
Category
-
connection
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/constants/t_has_a_default_config.nim c
-
Category
-
constants
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/constants/t_has_a_version.nim c
-
Category
-
constants
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/constants/t_knows_its_libversion.nim c
-
Category
-
constants
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/constants/t_knows_its_storage_version.nim c
-
Category
-
constants
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/database/t_can_create_in_memory.nim c
-
Category
-
database
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/database/t_can_create_with_custom_config.nim c
-
Category
-
database
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/database/t_creates_with_default_config.nim c
-
Category
-
database
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/queries/t_auto_rewinds_the_iterator.nim c
-
Category
-
queries
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/queries/t_can_bind_to_a_prepared_statement.nim c
-
Category
-
queries
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/queries/t_can_bind_various_datatypes.nim c
-
Category
-
queries
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/queries/t_can_execute_a_simple_queries.nim c
-
Category
-
queries
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/queries/t_can_iterate_tuples.nim c
-
Category
-
queries
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/queries/t_can_rewind_iteration.nim c
-
Category
-
queries
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/queries/t_can_stringify_results.nim c
-
Category
-
queries
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/queries/t_knows_if_there_are_waiting_tuples.nim c
-
Category
-
queries
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/queries/t_knows_the_column_names.nim c
-
Category
-
queries
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/queries/t_knows_the_column_types.nim c
-
Category
-
queries
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/queries/t_raises_on_bad_syntax.nim c
-
Category
-
queries
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/queries/t_raises_on_invalid_varbind.nim c
-
Category
-
queries
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/queries/t_raises_on_unknown_varbind.nim c
-
Category
-
queries
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/queries/t_raises_with_invalid_prepared_statement.nim c
-
Category
-
queries
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/tuples/t_can_be_stringified.nim c
-
Category
-
tuples
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/tuples/t_throws_exception_fetching_at_end.nim c
-
Category
-
tuples
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/tuples/t_throws_exception_invalid_index.nim c
-
Category
-
tuples
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/values/t_can_be_stringified.nim c
-
Category
-
values
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/values/t_can_convert_to_native_types.nim c
-
Category
-
values
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/values/t_can_return_a_blob_object.nim c
-
Category
-
values
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/values/t_can_return_a_list_object.nim c
-
Category
-
values
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/values/t_can_return_a_struct_object.nim c
-
Category
-
values
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/values/t_can_return_a_type.nim c
-
Category
-
values
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/values/t_raise_error_on_invalid_conversion.nim c
-
Category
-
values
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
- -
-
-
Name
-
tests/values/t_raises_error_on_struct_missing_key.nim c
-
Category
-
values
-
Timestamp
-
unknown
-
Nim Action
-
run
-
Nim Backend Target
-
c
-
Code
-
reSuccess
-
-

No output details

-
-
-
-
- -
- - diff --git a/tests/tuples/t_throws_exception_fetching_at_end.nim b/tests/tuples/t_throws_exception_fetching_at_end.nim index adb4ffd..2aa4b10 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 KuzuIndexError as err: +except KuzuIterationError as err: assert err.msg.contains( re"""Query iteration past end.""" )