Add basic tuple and value fetching from queries.

Add safeties for =destroy hooks.

FossilOrigin-Name: 2fae5297a0d0598cc3580777688b4f4307de008d4f379d2fb224c8a74cb9b708
This commit is contained in:
mahlon 2025-03-22 22:28:22 +00:00
parent 1ed442a68a
commit 7850a79372
14 changed files with 237 additions and 17 deletions

View file

@ -13,14 +13,18 @@ else:
include "kuzu/0.8.2.nim" include "kuzu/0.8.2.nim"
import import
std/strformat std/strformat,
std/strutils
# Order very much matters here pre Nim 3.0 multi-pass compiling.
include include
"kuzu/constants.nim", "kuzu/constants.nim",
"kuzu/types.nim", "kuzu/types.nim",
"kuzu/config.nim", "kuzu/config.nim",
"kuzu/database.nim", "kuzu/database.nim",
"kuzu/connection.nim", "kuzu/connection.nim",
"kuzu/value.nim",
"kuzu/tuple.nim",
"kuzu/queries.nim" "kuzu/queries.nim"

View file

@ -2,14 +2,16 @@
proc `=destroy`*( conn: KuzuConnectionObj ) = proc `=destroy`*( conn: KuzuConnectionObj ) =
## Graceful cleanup for open connection handles. ## Graceful cleanup for open connection handles.
if conn.valid:
kuzu_connection_destroy( addr conn.handle ) kuzu_connection_destroy( addr conn.handle )
proc connect*( db: KuzuDB ): KuzuConnection = proc connect*( db: KuzuDatabase ): KuzuConnection =
## Connect to a database. ## Connect to a database.
result = new KuzuConnection result = new KuzuConnection
var rv = kuzu_connection_init( addr db.handle, addr result.handle ) if kuzu_connection_init( addr db.handle, addr result.handle ) == KuzuSuccess:
if rv != KuzuSuccess: result.valid = true
else:
raise newException( KuzuException, "Unable to connect to the database." ) raise newException( KuzuException, "Unable to connect to the database." )

View file

@ -1,21 +1,23 @@
# vim: set et sta sw=4 ts=4 : # vim: set et sta sw=4 ts=4 :
proc `=destroy`*( db: KuzuDBObj ) = proc `=destroy`*( db: KuzuDatabaseObj ) =
## Graceful cleanup for an open DB handle when it goes out of scope. ## Graceful cleanup for an open DB handle when it goes out of scope.
if db.valid:
kuzu_database_destroy( addr db.handle ) kuzu_database_destroy( addr db.handle )
proc newKuzuDatabase*( path="", config=kuzuConfig() ): KuzuDB = proc newKuzuDatabase*( path="", config=kuzuConfig() ): KuzuDatabase =
## Create a new Kuzu database handle. Creates an in-memory ## Create a new Kuzu database handle. Creates an in-memory
## database by default, but writes to disk if a +path+ is supplied. ## database by default, but writes to disk if a +path+ is supplied.
result = new KuzuDB result = new KuzuDatabase
result.config = config result.config = config
result.path = if path != "" and path != ":memory:": path else: "(in-memory)" result.path = if path != "" and path != ":memory:": path else: "(in-memory)"
result.handle = kuzu_database() result.handle = kuzu_database()
var rv = kuzu_database_init( path, config, addr result.handle ) if kuzu_database_init( path, config, addr result.handle ) == KuzuSuccess:
if rv != KuzuSuccess: result.valid = true
else:
raise newException( KuzuException, "Unable to open database." ) raise newException( KuzuException, "Unable to open database." )

View file

@ -2,6 +2,7 @@
proc `=destroy`*( query: KuzuQueryResultObj ) = proc `=destroy`*( query: KuzuQueryResultObj ) =
## Graceful cleanup for out of scope query objects. ## Graceful cleanup for out of scope query objects.
if query.valid:
kuzu_query_result_destroy( addr query.handle ) kuzu_query_result_destroy( addr query.handle )
kuzu_query_summary_destroy( addr query.summary ) kuzu_query_summary_destroy( addr query.summary )
@ -9,15 +10,39 @@ proc `=destroy`*( query: KuzuQueryResultObj ) =
proc query*( conn: KuzuConnection, query: string ): KuzuQueryResult = proc query*( conn: KuzuConnection, query: string ): KuzuQueryResult =
## Perform a database +query+ and return the result. ## Perform a database +query+ and return the result.
result = new KuzuQueryResult result = new KuzuQueryResult
var rv = kuzu_connection_query( addr conn.handle, query, addr result.handle ) if kuzu_connection_query( addr conn.handle, query, addr result.handle ) == KuzuSuccess:
if rv == KuzuSuccess:
discard kuzu_query_result_get_query_summary( addr result.handle, addr result.summary ) 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_columns = kuzu_query_result_get_num_columns( addr result.handle )
result.num_tuples = kuzu_query_result_get_num_tuples( 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.compile_time = kuzu_query_summary_get_compiling_time( addr result.summary )
result.execution_time = kuzu_query_summary_get_execution_time( addr result.summary ) result.execution_time = kuzu_query_summary_get_execution_time( addr result.summary )
result.valid = true
else: else:
var err = kuzu_query_result_get_error_message( addr result.handle ) var err = kuzu_query_result_get_error_message( addr result.handle )
raise newException( KuzuQueryException, &"Error running query: {err}" ) raise newException( KuzuQueryException, &"Error running query: {err}" )
proc `$`*( 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 =
## Returns +true+ if there are more tuples to be consumed.
result = kuzu_query_result_has_next( addr query.handle )
proc getNext*( query: KuzuQueryResult ): KuzuFlatTuple =
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( KuzuQueryException, &"Unable to fetch next tuple." )
iterator items*( query: KuzuQueryResult ): KuzuFlatTuple =
## Iterate available tuples, yielding to the block.
while query.hasNext:
yield query.getNext

23
src/kuzu/tuple.nim Normal file
View file

@ -0,0 +1,23 @@
# vim: set et sta sw=4 ts=4 :
proc `=destroy`*( tpl: KuzuFlatTupleObj ) =
## Graceful cleanup for out of scope tuples.
if tpl.valid:
kuzu_flat_tuple_destroy( addr tpl.handle )
proc `$`*( 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+.
result = new KuzuValue
if kuzu_flat_tuple_get_value( addr tpl.handle, idx.uint64, addr result.handle ) == KuzuSuccess:
result.valid = true
else:
raise newException( KuzuIndexException,
&"Unable to fetch tuple value at idx {idx}. ({tpl.num_columns} column(s).)" )

View file

@ -1,14 +1,16 @@
# vim: set et sta sw=4 ts=4 : # vim: set et sta sw=4 ts=4 :
type type
KuzuDBObj = object KuzuDatabaseObj = object
handle*: kuzu_database handle*: kuzu_database
path*: string path*: string
config*: kuzu_system_config config*: kuzu_system_config
KuzuDB* = ref KuzuDBObj valid = false
KuzuDatabase* = ref KuzuDatabaseObj
KuzuConnectionObj = object KuzuConnectionObj = object
handle*: kuzu_connection handle*: kuzu_connection
valid = false
KuzuConnection* = ref KuzuConnectionObj KuzuConnection* = ref KuzuConnectionObj
KuzuQueryResultObj = object KuzuQueryResultObj = object
@ -18,8 +20,21 @@ type
num_tuples*: uint64 = 0 num_tuples*: uint64 = 0
compile_time*: cdouble = 0 compile_time*: cdouble = 0
execution_time*: cdouble = 0 execution_time*: cdouble = 0
valid = false
KuzuQueryResult* = ref KuzuQueryResultObj KuzuQueryResult* = ref KuzuQueryResultObj
KuzuFlatTupleObj = object
handle*: kuzu_flat_tuple
num_columns: uint64 = 0
valid = false
KuzuFlatTuple* = ref KuzuFlatTupleObj
KuzuValueObj = object
handle*: kuzu_value
valid = false
KuzuValue* = ref KuzuValueObj
KuzuException* = object of CatchableError KuzuException* = object of CatchableError
KuzuQueryException* = object of KuzuException KuzuQueryException* = object of KuzuException
KuzuIndexException* = object of KuzuException

13
src/kuzu/value.nim Normal file
View file

@ -0,0 +1,13 @@
# vim: set et sta sw=4 ts=4 :
proc `=destroy`*( value: KuzuValueObj ) =
## Graceful cleanup for out of scope values.
if value.valid:
kuzu_value_destroy( addr value.handle )
proc `$`*( value: KuzuValue ): string =
## Stringify a value.
result = $kuzu_value_to_string( addr value.handle )

View file

@ -0,0 +1,19 @@
# vim: set et sta sw=4 ts=4 :
discard """
output: "Camel\nLampshade\nDelicious Cake\n"
"""
import kuzu
let db = newKuzuDatabase()
let conn = db.connect
var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, thing STRING, PRIMARY KEY(id) )" )
for thing in @[ "Camel", "Lampshade", "Delicious Cake" ]:
q = conn.query( "CREATE (d:Doop {thing: '" & thing & "'})" )
for tpl in conn.query( "MATCH (d:Doop) RETURN d.thing" ):
echo $tpl

View file

@ -0,0 +1,17 @@
# vim: set et sta sw=4 ts=4 :
discard """
output: "d.thing\nokay!\n\n"
"""
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.thing" )
echo $q

View file

@ -0,0 +1,24 @@
# 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( "MATCH (d:Doop) RETURN d.thing" )
assert q.num_tuples == 0
assert q.hasNext == false
q = conn.query( "CREATE (d:Doop {thing: 'okay!'})" )
q = conn.query( "MATCH (d:Doop) RETURN d.thing" )
assert q.num_tuples == 1
assert q.hasNext == true
discard $q # consume the tuple result
assert q.num_tuples == 1
assert q.hasNext == false

View file

@ -0,0 +1,19 @@
# vim: set et sta sw=4 ts=4 :
discard """
output: "okay!"
"""
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.thing" )
var tup = q.getNext
echo $tup

View file

@ -0,0 +1,17 @@
# 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, thing STRING, PRIMARY KEY(id) )" )
q = conn.query( "MATCH (d:Doop) RETURN d.thing" )
try:
discard q.getNext
except KuzuQueryException as err:
assert err.msg.contains( re"""Unable to fetch next tuple.""" )

View file

@ -0,0 +1,20 @@
# 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, thing STRING, PRIMARY KEY(id) )" )
q = conn.query( "CREATE (d:Doop {thing: 'okay!'})" )
q = conn.query( "MATCH (d:Doop) RETURN d.thing" )
let tup = q.getNext
try:
echo tup[22]
except KuzuIndexException as err:
assert err.msg.contains( re"""Unable to fetch tuple value at idx 22.""" )

View file

@ -0,0 +1,20 @@
# vim: set et sta sw=4 ts=4 :
discard """
output: "okay!"
"""
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.thing" )
var tup = q.getNext
var val = tup[0]
echo $val