Merge query iterator and file magic branches.
This commit is contained in:
commit
ebd0d3dc45
13 changed files with 118 additions and 58 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,10 +1,13 @@
|
||||||
experiments/imdb/*
|
experiments/imdb/*
|
||||||
|
!experiments/imdb/*.nim
|
||||||
|
!experiments/imdb/Makefile
|
||||||
nimble.paths
|
nimble.paths
|
||||||
config.nims
|
config.nims
|
||||||
tmp/*
|
tmp/*
|
||||||
kuzu
|
kuzu
|
||||||
nimcache/*
|
nimcache/*
|
||||||
tests/*
|
tests/*
|
||||||
|
!tests/*/t_*.nim
|
||||||
testresults/*
|
testresults/*
|
||||||
testresults.html
|
testresults.html
|
||||||
|
|
||||||
|
|
|
||||||
6
USAGE.md
6
USAGE.md
|
|
@ -358,8 +358,10 @@ Manually rewind the `KuzuQueryResult` via `rewind()`.
|
||||||
|
|
||||||
## Multiple Query Results
|
## Multiple Query Results
|
||||||
|
|
||||||
A query can potentially return any number of separate statements. Iterate over
|
A query can potentially return any number of separate statements. In the case
|
||||||
linked `KuzuQueryResult` objects with the `sets()` iterator.
|
of more potential `RETURN`s, the query will only contain the first. Iterate
|
||||||
|
over linked `KuzuQueryResult` objects with the `sets()` iterator to retreive the
|
||||||
|
remaining:
|
||||||
|
|
||||||
```nim
|
```nim
|
||||||
import kuzu
|
import kuzu
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -18,9 +18,7 @@ task makewrapper, "Generate the C wrapper using Futhark":
|
||||||
task test, "Run the test suite.":
|
task test, "Run the test suite.":
|
||||||
exec "testament --megatest:off all"
|
exec "testament --megatest:off all"
|
||||||
exec "testament html"
|
exec "testament html"
|
||||||
|
exec """find tests/ -type f \! -name \*.nim -delete"""
|
||||||
task clean, "Remove all non-repository artifacts.":
|
|
||||||
exec "fossil clean -x"
|
|
||||||
|
|
||||||
task docs, "Generate automated documentation.":
|
task docs, "Generate automated documentation.":
|
||||||
exec "nim md2html --project --outdir:docs README.md"
|
exec "nim md2html --project --outdir:docs README.md"
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ else:
|
||||||
include "kuzu/0.11.0.nim"
|
include "kuzu/0.11.0.nim"
|
||||||
|
|
||||||
import
|
import
|
||||||
|
std/files,
|
||||||
|
std/paths,
|
||||||
std/strformat,
|
std/strformat,
|
||||||
std/strutils
|
std/strutils
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,5 +4,5 @@ const KUZU_VERSION* = "0.6.0"
|
||||||
const KUZU_EXPECTED_LIBVERSION* = "0.11.0"
|
const KUZU_EXPECTED_LIBVERSION* = "0.11.0"
|
||||||
const BLOB_MAXSIZE = 4096
|
const BLOB_MAXSIZE = 4096
|
||||||
|
|
||||||
let KUZU_DEFAULT_CONFIG* = kuzu_default_system_config()
|
let KUZU_DEFAULT_CONFIG* = kuzu_default_system_config()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,18 +7,50 @@ proc `=destroy`*( db: KuzuDatabaseObj ) =
|
||||||
kuzu_database_destroy( addr db.handle )
|
kuzu_database_destroy( addr db.handle )
|
||||||
|
|
||||||
|
|
||||||
func newKuzuDatabase*( path="", config=kuzuConfig() ): KuzuDatabase =
|
proc validateDatabase( db: KuzuDatabase ): void =
|
||||||
|
## Perform basic validity checks against an existing on disk database
|
||||||
|
## for better error messaging.
|
||||||
|
|
||||||
|
if not Path( db.path ).fileExists: return
|
||||||
|
|
||||||
|
var buf = newSeq[char]( 5 )
|
||||||
|
let f = open( db.path )
|
||||||
|
discard f.readChars( buf )
|
||||||
|
f.close
|
||||||
|
|
||||||
|
let magic = buf[0..3].join
|
||||||
|
let storage_version = buf[4].uint
|
||||||
|
|
||||||
|
if magic != "KUZU":
|
||||||
|
raise newException( KuzuException, "Unable to open database: " &
|
||||||
|
&""""{db.path}" Doesn't appear to be a Kuzu file.""" )
|
||||||
|
|
||||||
|
if storageVersion != kuzuGetStorageVersion():
|
||||||
|
raise newException( KuzuException, "Unable to open database: " &
|
||||||
|
&" mismatched storage versions - file is {storageVersion}, expected {kuzuGetStorageVersion()}." )
|
||||||
|
|
||||||
|
|
||||||
|
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 KuzuDatabase
|
result = new KuzuDatabase
|
||||||
result.config = config
|
result.config = config
|
||||||
result.path = if path != "" and path != ":memory:": path else: "(in-memory)"
|
|
||||||
|
if path != "" and path != ":memory:":
|
||||||
|
result.path = path
|
||||||
|
result.kind = disk
|
||||||
|
else:
|
||||||
|
result.path = "(in-memory)"
|
||||||
|
result.kind = memory
|
||||||
|
|
||||||
result.handle = kuzu_database()
|
result.handle = kuzu_database()
|
||||||
|
|
||||||
|
if result.kind == disk:
|
||||||
|
result.validateDatabase()
|
||||||
|
|
||||||
if kuzu_database_init( path, config, addr result.handle ) == KuzuSuccess:
|
if kuzu_database_init( path, config, addr result.handle ) == KuzuSuccess:
|
||||||
result.valid = true
|
result.valid = true
|
||||||
else:
|
else:
|
||||||
raise newException( KuzuException, "Unable to open database." )
|
raise newException( KuzuException, "Unable to open database." )
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,12 @@ proc `=destroy`*( query: KuzuQueryResultObj ) =
|
||||||
kuzu_query_result_destroy( addr query.handle )
|
kuzu_query_result_destroy( addr query.handle )
|
||||||
|
|
||||||
|
|
||||||
func getQueryMetadata( query: KuzuQueryResult ) =
|
# Forward declarations.
|
||||||
|
func hasNextSet( query: KuzuQueryResult ): bool
|
||||||
|
func getNextSet( query: KuzuQueryResult ): KuzuQueryResult
|
||||||
|
|
||||||
|
|
||||||
|
func getQueryMetadata( query: KuzuQueryResult, getAllQueryResults=false ) =
|
||||||
## Find and retain additional data for the query.
|
## Find and retain additional data for the query.
|
||||||
query.num_columns = kuzu_query_result_get_num_columns( addr query.handle )
|
query.num_columns = kuzu_query_result_get_num_columns( addr query.handle )
|
||||||
query.num_tuples = kuzu_query_result_get_num_tuples( addr query.handle )
|
query.num_tuples = kuzu_query_result_get_num_tuples( addr query.handle )
|
||||||
|
|
@ -19,6 +24,12 @@ func getQueryMetadata( query: KuzuQueryResult ) =
|
||||||
query.execution_time = kuzu_query_summary_get_execution_time( addr summary )
|
query.execution_time = kuzu_query_summary_get_execution_time( addr summary )
|
||||||
kuzu_query_summary_destroy( addr summary )
|
kuzu_query_summary_destroy( addr summary )
|
||||||
|
|
||||||
|
# Pull any additional query results.
|
||||||
|
query.sets = @[]
|
||||||
|
if getAllQueryResults:
|
||||||
|
while query.hasNextSet:
|
||||||
|
query.sets.add( query.getNextSet )
|
||||||
|
|
||||||
# Column information.
|
# Column information.
|
||||||
query.column_types = @[]
|
query.column_types = @[]
|
||||||
query.column_names = @[]
|
query.column_names = @[]
|
||||||
|
|
@ -48,13 +59,29 @@ func getQueryMetadata( query: KuzuQueryResult ) =
|
||||||
kuzu_destroy_string( name )
|
kuzu_destroy_string( name )
|
||||||
|
|
||||||
|
|
||||||
|
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 )
|
||||||
|
|
||||||
|
|
||||||
|
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." )
|
||||||
|
|
||||||
|
|
||||||
func query*( conn: KuzuConnection, query: string ): KuzuQueryResult =
|
func 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
|
||||||
|
|
||||||
if kuzu_connection_query( addr conn.handle, query, addr result.handle ) == KuzuSuccess:
|
if kuzu_connection_query( addr conn.handle, query, addr result.handle ) == KuzuSuccess:
|
||||||
result.valid = true
|
result.valid = true
|
||||||
result.getQueryMetadata()
|
result.getQueryMetadata( getAllQueryResults=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( KuzuQueryError, &"Error running query: {err}" )
|
raise newException( KuzuQueryError, &"Error running query: {err}" )
|
||||||
|
|
@ -162,32 +189,6 @@ func rewind*( query: KuzuQueryResult ) =
|
||||||
kuzu_query_result_reset_iterator( addr query.handle )
|
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 =
|
iterator items*( query: KuzuQueryResult ): KuzuFlatTuple =
|
||||||
## Iterate available tuples, yielding to the block.
|
## Iterate available tuples, yielding to the block.
|
||||||
while query.hasNext:
|
while query.hasNext:
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
# vim: set et sta sw=4 ts=4 :
|
# vim: set et sta sw=4 ts=4 :
|
||||||
|
|
||||||
type
|
type
|
||||||
|
KuzuDBType* = enum
|
||||||
|
disk, memory
|
||||||
|
|
||||||
KuzuDatabaseObj = object
|
KuzuDatabaseObj = object
|
||||||
handle: kuzu_database
|
handle: kuzu_database
|
||||||
path*: string
|
path*: string
|
||||||
|
kind*: KuzuDBType
|
||||||
config*: kuzu_system_config
|
config*: kuzu_system_config
|
||||||
valid = false
|
valid = false
|
||||||
KuzuDatabase* = ref KuzuDatabaseObj
|
KuzuDatabase* = ref KuzuDatabaseObj
|
||||||
|
|
@ -21,6 +25,7 @@ type
|
||||||
execution_time*: cdouble = 0
|
execution_time*: cdouble = 0
|
||||||
column_types*: seq[ kuzu_data_type_id ]
|
column_types*: seq[ kuzu_data_type_id ]
|
||||||
column_names*: seq[ string ]
|
column_names*: seq[ string ]
|
||||||
|
sets*: seq[ KuzuQueryResult ]
|
||||||
valid = false
|
valid = false
|
||||||
KuzuQueryResult* = ref KuzuQueryResultObj
|
KuzuQueryResult* = ref KuzuQueryResultObj
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,4 +4,5 @@ import kuzu
|
||||||
|
|
||||||
var db = newKuzuDatabase()
|
var db = newKuzuDatabase()
|
||||||
assert db.path == "(in-memory)"
|
assert db.path == "(in-memory)"
|
||||||
|
assert db.kind == memory
|
||||||
|
|
||||||
|
|
|
||||||
23
tests/database/t_checks_for_valid_kuzu_file.nim
Normal file
23
tests/database/t_checks_for_valid_kuzu_file.nim
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# vim: set et sta sw=4 ts=4 :
|
||||||
|
|
||||||
|
import
|
||||||
|
std/files,
|
||||||
|
std/paths,
|
||||||
|
std/re
|
||||||
|
|
||||||
|
import kuzu
|
||||||
|
|
||||||
|
const NOT_A_DATABASE_PATH = Path( "tmp/not-a-db" )
|
||||||
|
|
||||||
|
NOT_A_DATABASE_PATH.removeFile()
|
||||||
|
var fh = NOT_A_DATABASE_PATH.string.open( fmWrite )
|
||||||
|
fh.write( "Hi." )
|
||||||
|
fh.close
|
||||||
|
|
||||||
|
try:
|
||||||
|
discard newKuzuDatabase( $NOT_A_DATABASE_PATH )
|
||||||
|
except KuzuException as err:
|
||||||
|
assert err.msg.contains( re"""Unable to open database: "tmp/not-a-db" Doesn't appear to be a Kuzu file""" )
|
||||||
|
|
||||||
|
NOT_A_DATABASE_PATH.removeFile()
|
||||||
|
|
||||||
|
|
@ -12,6 +12,7 @@ DATABASE_PATH.removeFile()
|
||||||
var db = newKuzuDatabase( $DATABASE_PATH )
|
var db = newKuzuDatabase( $DATABASE_PATH )
|
||||||
|
|
||||||
assert db.path == $DATABASE_PATH
|
assert db.path == $DATABASE_PATH
|
||||||
|
assert db.kind == disk
|
||||||
assert db.config == kuzuConfig()
|
assert db.config == kuzuConfig()
|
||||||
assert db.config.read_only == false
|
assert db.config.read_only == false
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# vim: set et sta sw=4 ts=4 :
|
# vim: set et sta sw=4 ts=4 :
|
||||||
|
|
||||||
discard """
|
discard """
|
||||||
output: "Jenny|Lenny\nLenny\nJenny\n"
|
output: "a\nb\nc\nd\ne\nf\n"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import kuzu
|
import kuzu
|
||||||
|
|
@ -9,34 +9,26 @@ import kuzu
|
||||||
let db = newKuzuDatabase()
|
let db = newKuzuDatabase()
|
||||||
let conn = db.connect
|
let conn = db.connect
|
||||||
|
|
||||||
var q = conn.query """
|
var q = conn.query( "RETURN 'hi'" )
|
||||||
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 typeOf( q ) is KuzuQueryResult
|
||||||
assert q.hasNextSet == false
|
assert q.sets.len == 0
|
||||||
|
|
||||||
q = conn.query """
|
q = conn.query """
|
||||||
MATCH (a:User)<-[f:Follows]-(b:User) RETURN a.name, b.name;
|
RETURN "a";
|
||||||
MATCH (u:User) RETURN u.name;
|
RETURN "b";
|
||||||
|
RETURN "c";
|
||||||
|
RETURN "d";
|
||||||
|
RETURN "e";
|
||||||
|
RETURN "f";
|
||||||
"""
|
"""
|
||||||
|
|
||||||
assert typeOf( q ) is KuzuQueryResult
|
assert typeOf( q ) is KuzuQueryResult
|
||||||
assert q.hasNextSet == true
|
assert q.sets.len == 5
|
||||||
|
|
||||||
echo q.getNext
|
echo q.getNext
|
||||||
for query_result in q.sets:
|
for set in q.sets:
|
||||||
for row in query_result.items:
|
for row in set.items:
|
||||||
echo row
|
echo row
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue