Rename the project to nim-ladybug, after kuzu's sudden October abandoning. Picked up by a new party under the new name, lets see where this goes!

No new functionality, just rename and docs updates.
This commit is contained in:
Mahlon E. Smith 2025-11-04 09:06:11 -08:00
parent ee0e8a72c0
commit 76718fa49f
Signed by: mahlon
SSH key fingerprint: SHA256:dP84sRGKZRpOOiPD/+GuOq+SHSxEw9qi5BWLQobaHm0
61 changed files with 3030 additions and 2998 deletions

View file

@ -1,4 +1,11 @@
# Release History for nim-kuzu
# Release History for nim-ladybug
---
## v0.7.0 [2025-11-04] Mahlon E. Smith <mahlon@martini.nu>
Rename the project from Kuzu to Ladybug, following new upstream efforts to keep
the project going after the Kuzu originator sudden abandonment.
---
## v0.6.1 [2025-07-19] Mahlon E. Smith <mahlon@martini.nu>

View file

@ -1,18 +1,18 @@
# Nim Kuzu
# Nim Ladybug
home
: https://code.martini.nu/mahlon/nim-kuzu
: https://code.martini.nu/mahlon/nim-ladybug
github_mirror
: https://github.com/mahlonsmith/nim-kuzu
: https://github.com/mahlonsmith/nim-ladybug
## Description
This is a Nim binding for the [Kuzu](https://kuzudb.com) graph database library.
This is a Nim binding for the [LadybugDB](https://ladybugdb.com) graph database library.
Kuzu is an embedded graph database built for query speed and scalability. It is
Ladybug is an embedded graph database built for query speed and scalability. It is
optimized for handling complex join-heavy analytical workloads on very large
graphs, with the following core feature set:
@ -25,22 +25,21 @@ graphs, with the following core feature set:
- Multi-core query parallelism
- Serializable ACID transactions
For more information about Kuzu itself, see its
[documentation](https://docs.kuzudb.com/).
For more information about Ladybug itself, see its
[documentation](https://docs.ladybugdb.com/).
## Prerequisites
* A functioning Nim >= 2 installation
- [KuzuDB](https://kuzudb.com) to be locally installed!
- [LadybugDB](https://ladybugdb.com) to be locally installed!
## Installation
$ nimble install kuzu
$ nimble install ladybug
The current version of this library is built for Kuzu v0.11.3, which sadly
seems may have been the final release of kuzudb. :(
The current version of this library is built for Ladybug v0.12.0.
## Usage
@ -53,11 +52,11 @@ You can also find a bunch of working examples in the tests.
## Contributing
You can check out the current development source via Git/Jujutsu at its
[home repo](https://code.martini.nu/mahlon/nim-kuzu), or the
[project mirror](https://github.com/mahlonsmith/nim-kuzu).
[home repo](https://code.martini.nu/mahlon/nim-ladybug), or the
[project mirror](https://github.com/mahlonsmith/nim-ladybug).
After checking out the source, uncomment the development dependencies
from the `kuzu.nimble` file, and run:
from the `ladybug.nimble` file, and run:
$ nimble setup
@ -71,4 +70,5 @@ development.
- Mahlon E. Smith <mahlon@martini.nu>
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.
KuzuDB (0.4.x) that I found after starting this project (the predecessor to
LadybugDB.)

173
USAGE.md
View file

@ -12,24 +12,28 @@ a lot and it's hard to know where to start.
## Prior Reading
If you're just starting with Kuzu or graph databases, it's probably a good idea
to familiarize yourself with the [Kuzu Documentation](https://docs.kuzudb.com/)
and the [Cypher Language](https://docs.kuzudb.com/tutorials/cypher/). This
library won't do much for you by itself without a basic understanding of Kuzu usage.
If you're just starting with Ladybug or graph databases, it's probably a good idea
to familiarize yourself with the [Ladybug Documentation](https://docs.ladybugdb.com/)
and the [Cypher Language](https://docs.ladybugdb.com/tutorials/cypher/). This
library won't do much for you by itself without a basic understanding of Ladybug usage.
## Checking Compatibility
This is a wrapper (with some additional niceties) for the system-installed Kuzu
This is a wrapper (with some additional niceties) for the system-installed Ladybug
shared library. As such, the version of this library might not match with what
you currently have installed.
Check the [README](README.md), the [History](History.md), and the following
table to ensure you're using the correct version for your Kuzu
installation. I'll make a modest effort for backwards compatibility while Kuzu
table to ensure you're using the correct version for your Ladybug
installation. I'll make a modest effort for backwards compatibility while Ladybug
is pre 1.0, and in practice, mismatched versions *might* work. Don't count too
heavily on it. :-) Once there's a 1.0, this should be less chaotic.
Ladybug was continued from the KuzuDB project, which was hastily abandoned in
October of 2025. Previous versions used a "Kuzu" namespace.
| Kuzu Library Version | Nim Kuzu Minimum Version |
| -------------------- | ------------------------ |
| v0.8.2 | v0.1.0 |
@ -37,34 +41,49 @@ heavily on it. :-) Once there's a 1.0, this should be less chaotic.
| v0.10.0 | v0.4.0 |
| v0.11.0 | v0.5.0 |
You can use the `kuzuVersionCompatible()` function (along with the
`kuzuGetVersion()` and the `KUZU_VERSION` constant) to quickly check if things
| Ladybug Library Version | Nim Ladybug Minimum Version |
| ----------------------- | --------------------------- |
| v0.12.0 | v0.7.0 |
You can use the `lbugVersionCompatible()` function (along with the
`lbugGetVersion()` and the `LBUG_VERSION` constant) to quickly check if things
are looking right.
```nim
import kuzu
import ladybug
echo KUZU_VERSION #=> "0.1.0"
echo kuzuGetVersion() #=> "0.8.2"
echo kuzuVersionCompatible() #=> true
echo LBUG_VERSION #=> "0.7.0"
echo lbugGetVersion() #=> "0.12.0"
echo lbugVersionCompatible() #=> true
```
## Namespace
The LadybugDB project internals are all prefixed with `lbug`. This wrapper
follows suit.
An exception to that is the import. You can import "ladybug" or "lbug", it's
functionally equivalent.
## Connecting to a Database
Just call `newKuzuDatabase()`. Without an argument (or with an empty string),
Just call `newLbugDatabase()`. Without an argument (or with an empty string),
the database is in-memory. Any other argument is considered a filesystem path
-- it will create an empty database if the path is currently non-existent, or
open an existing database otherwise.
```nim
# "db" is in-memory and will evaporate when the process ends.
var db = newKuzuDatabase()
var db = newLbugDatabase()
```
```nim
# "db" is persistent, stored in the file "data.kz".
var db = newKuzuDatabase("data.kz")
var db = newLbugDatabase("data.kz")
```
The database path is retained, and can be recalled via `db.path`.
@ -86,19 +105,19 @@ if db.config.enable_compression:
echo "Yes!"
```
You can alter configuration options when connecting by passing a `kuzuConfig`
object as the second argument to `newKuzuDatabase()`:
You can alter configuration options when connecting by passing a `lbugConfig`
object as the second argument to `newLbugDatabase()`:
```nim
# Open a readonly handle.
var db = newKuzuDatabase( "data.kz", kuzuConfig( read_only=true ) )
var db = newLbugDatabase( "data.kz", lbugConfig( read_only=true ) )
```
### The Connection
All interaction with the database is performed via a connection object. There
are limitations to database handles and connection objects -- see the
[Kuzu Concurrency](https://docs.kuzudb.com/concurrency/) docs for details!
[Lbug Concurrency](https://docs.ladybugdb.com/concurrency/) docs for details!
Call `connect` on an open database handle to create a new connection:
@ -121,10 +140,10 @@ conn.queryInterrupt()
You can perform a basic query via the appropriately named `query()` function on
the connection. Via this method, queries are run immediately. A
`KuzuQueryResult` is returned - this is the object you'll be interacting with to
`LbugQueryResult` is returned - this is the object you'll be interacting with to
see results.
A `KuzuQueryResult` can be turned into a string to quickly see the column
A `LbugQueryResult` can be turned into a string to quickly see the column
headers and all tuple results:
```nim
@ -154,7 +173,7 @@ echo res.execution_time #=> 1.624
assert res.column_names == @["hi", "pin", "list"]
# Return the column data types as a sequence.
assert res.column_types == @[KUZU_STRING, KUZU_INT64, KUZU_LIST]
assert res.column_types == @[LBUG_STRING, LBUG_INT64, LBUG_LIST]
```
### Prepared Statements
@ -172,7 +191,7 @@ RETURN
[1,2,3] AS list
"""
# This returns a KuzuQueryResult, just like `conn.query()`.
# This returns a LbugQueryResult, just like `conn.query()`.
var res = stmt.execute()
```
@ -198,13 +217,13 @@ echo $res #=>
#### Type Conversion
When binding variables to a prepared statement, most Nim types are automatically
converted to their respective Kuzu types.
converted to their respective Ladybug types.
```nim
var stmt = conn.prepare( """RETURN $num AS num""" )
var res = stmt.execute( (num: 12) )
assert res.column_types[0] == KUZU_INT32
assert res.column_types[0] == LBUG_INT32
```
This might not necessarily be what you want - sometimes you'd rather be strict
@ -215,37 +234,37 @@ You can use [integer type suffixes](https://nim-lang.org/docs/manual.html#lexica
```nim
var stmt = conn.prepare( """RETURN $num AS num""" )
var res: KuzuQueryResult
var res: LbugQueryResult
res = stmt.execute( (num: 12'u64) )
assert res.column_types[0] == KUZU_UINT64
assert res.column_types[0] == LBUG_UINT64
res = stmt.execute( (num: 12.float) )
assert res.column_types[0] == KUZU_DOUBLE
assert res.column_types[0] == LBUG_DOUBLE
```
#### Kuzu Specific Types
#### Ladybug Specific Types
In the example above, you may have noticed the `LIST_CREATION($list)` in the
prepared query, and that we passed a string `1,2,3` as the `$list` parameter.
This is a useful way to easily use most Kuzu types without needing corresponding
This is a useful way to easily use most Ladybug types without needing corresponding
Nim ones -- if you're inserting into a table that is using a custom type, you
can cast it using the query itself during insertion!
This has the additional advantage of letting Kuzu error check the validity of
This has the additional advantage of letting Ladybug error check the validity of
the content, and it works with the majority of types.
An extended example:
```nim
import std/sequtils
import kuzu
import lbug
var db = newKuzuDatabase()
var db = newLbugDatabase()
var conn = db.connect
var res: KuzuQueryResult
var res: LbugQueryResult
# Create a node table.
#
@ -277,7 +296,7 @@ CREATE (e:Example {
})
"""
# Add a node row that contains specific Kuzu types.
# Add a node row that contains specific Ladybug types.
#
res = stmt.execute((
num: 2,
@ -295,26 +314,26 @@ echo $res #=>
# e.id|e.num|e.done|e.comment|e.karma|e.thing|e.created|e.activity
# 0|2|True|Types!|16.700000|e0e7232e-bec9-4625-9822-9d1a31ea6f93|2025-03-29|2025-03-29 00:00:00
# Show column names and their Kuzu types.
# Show column names and their Ladybug types.
for pair in res.column_names.zip( res.column_types ):
echo pair #=>
# ("e.id", KUZU_SERIAL)
# ("e.num", KUZU_UINT8)
# ("e.done", KUZU_BOOL)
# ("e.comment", KUZU_STRING)
# ("e.karma", KUZU_DOUBLE)
# ("e.thing", KUZU_UUID)
# ("e.created", KUZU_DATE)
# ("e.activity", KUZU_TIMESTAMP)
# ("e.id", LBUG_SERIAL)
# ("e.num", LBUG_UINT8)
# ("e.done", LBUG_BOOL)
# ("e.comment", LBUG_STRING)
# ("e.karma", LBUG_DOUBLE)
# ("e.thing", LBUG_UUID)
# ("e.created", LBUG_DATE)
# ("e.activity", LBUG_TIMESTAMP)
```
## Reading Results
So far we've just been showing values by converting the entire `KuzuQueryResult`
So far we've just been showing values by converting the entire `LbugQueryResult`
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 a result set.
A `LbugQueryResult` is an iterator. You can use regular Nim functions that yield
each `LbugFlatTuple` -- essentially, each row that was returned in a result set.
```nim
var res = conn.query """
@ -323,7 +342,7 @@ var res = conn.query """
RETURN items, thing
"""
# KuzuFlatTuple can be stringified just like the result set.
# LbugFlatTuple can be stringified just like the result set.
for row in res:
echo row #=>
# 1|thing
@ -333,7 +352,7 @@ for row in res:
Once iteration has reached the end, it is automatically rewound for reuse.
You can manually get the next `KuzuFlatTuple` via `getNext()`. Calling
You can manually get the next `LbugFlatTuple` via `getNext()`. Calling
`getNext()` after the last row results in an error. Use `hasNext()` to check
before calling.
@ -350,23 +369,23 @@ if res.hasNext:
echo res.getNext #=> 2
echo res.getNext #=> 3
echo res.getNext #=> KuzuIterationError exception!
echo res.getNext #=> LbugIterationError exception!
```
Manually rewind the `KuzuQueryResult` via `rewind()`.
Manually rewind the `LbugQueryResult` via `rewind()`.
## Multiple Query Results
A query can potentially return any number of separate statements. In the case
of more potential `RETURN`s, the query will only contain the first. Iterate
over linked `KuzuQueryResult` objects with the `sets()` iterator to retreive the
over linked `LbugQueryResult` objects with the `sets()` iterator to retreive the
remaining:
```nim
import kuzu
import lbug
let db = newKuzuDatabase()
let db = newLbugDatabase()
let conn = db.connect
let query = conn.query """
@ -400,8 +419,8 @@ for set in query.sets:
## Working with Values
A `KuzuFlatTuple` contains the entire row. You can index a value at its column
position, returning a `KuzuValue`.
A `LbugFlatTuple` contains the entire row. You can index a value at its column
position, returning a `LbugValue`.
```nim
var res = conn.query """
@ -419,27 +438,27 @@ var row = res.getNext
for idx in ( 0 .. res.num_columns-1 ):
var value = row[idx]
echo res.column_names[idx], ": ", value, " (", value.kind, ")" #=>
# num: 1 (KUZU_INT64)
# done: True (KUZU_BOOL)
# comment: A comment (KUZU_STRING)
# karma: 12.840000 (KUZU_DOUBLE)
# thing: b41deae0-dddf-430b-981d-3fb93823e495 (KUZU_UUID)
# created: 2025-03-29 (KUZU_DATE)
# num: 1 (LBUG_INT64)
# done: True (LBUG_BOOL)
# comment: A comment (LBUG_STRING)
# karma: 12.840000 (LBUG_DOUBLE)
# thing: b41deae0-dddf-430b-981d-3fb93823e495 (LBUG_UUID)
# created: 2025-03-29 (LBUG_DATE)
```
### Types
A `KuzuValue` can always be stringified, irrespective of its Kuzu type. You can
A `LbugValue` can always be stringified, irrespective of its Lbug type. You can
check what type it is via the 'kind' property.
```nim
var res = conn.query """RETURN "hello""""
var value = res.getNext[0]
assert value.kind == KUZU_STRING
assert value.kind == LBUG_STRING
```
A `KuzuValue` has conversion methods for Nim base types. You'll likely want to
A `LbugValue` has conversion methods for Nim base types. You'll likely want to
convert it for regular Nim usage:
```nim
@ -455,12 +474,12 @@ assert value.toInt64 + 1 == 2561
### Lists
A `KuzuValue` of type `KUZU_LIST` can be converted to a Nim sequence of
`KuzuValues` with the `toList()` function:
A `LbugValue` of type `LBUG_LIST` can be converted to a Nim sequence of
`LbugValues` with the `toList()` function:
```nim
import std/sequtils
import kuzu
import lbug
var res = conn.query """
RETURN [10, 20, 30]
@ -471,16 +490,16 @@ var value = res.getNext[0]
var list = value.toList
echo list #=> @[10,20,30]
echo list.map( func(v:KuzuValue): int = v.toInt64 * 10 ) #=> @[100,200,300]
echo list.map( func(v:LbugValue): int = v.toInt64 * 10 ) #=> @[100,200,300]
```
### Struct-like Objects
Various Kuzu types can act like a struct - this includes `KUZU_NODE`,
`KUZU_REL`, and of course an explicit `KUZU_STRUCT` itself, among others.
Various Ladybug types can act like a struct - this includes `LBUG_NODE`,
`LBUG_REL`, and of course an explicit `LBUG_STRUCT` itself, among others.
Convert a `KuzuValue` to a `KuzuStructValue` with `toStruct()`. For
Convert a `LbugValue` to a `LbugStructValue` with `toStruct()`. For
convenience, this is also aliased to `toNode()` and `toRel()`.
Once converted, you can access struct values by passing the key name to `[]`:
@ -503,9 +522,9 @@ Here's a more elaborate example, following a node path:
import
std/sequtils,
std/strformat
import kuzu
import lbug
var db = newKuzuDatabase()
var db = newLbugDatabase()
var conn = db.connect
var res = conn.query """
@ -540,7 +559,7 @@ res = conn.query """
#
for row in res:
var since = row[0]
var people = row[1].toList.map( proc(p:KuzuValue):KuzuStructValue = p.toNode )
var people = row[1].toList.map( proc(p:LbugValue):LbugStructValue = p.toNode )
echo &"""{people[0]["name"]} has known {people[1]["name"]} since {since}.""" #=>
# Bob has known Bruce since 2003.
# Bob has known Alice since 2009.
@ -550,7 +569,7 @@ for row in res:
### Blobs
Kuzu can store small chunks of opaque binary data. For these BLOB columns,
Ladybug can store small chunks of opaque binary data. For these BLOB columns,
using `toBlob` will return the raw sequence of bytes.
```nim

View file

@ -1,8 +1,8 @@
# vim: set et sta sw=4 ts=4 :
version = "0.6.1"
version = "0.7.0"
author = "Mahlon E. Smith"
description = "Kuzu is an embedded graph database built for query speed and scalability."
description = "Ladybug is an embedded graph database built for query speed and scalability."
license = "BSD-3-Clause"
srcDir = "src"
@ -13,7 +13,7 @@ requires "nim ^= 2.0.0"
#requires "zip ^= 0.3.1"
task makewrapper, "Generate the C wrapper using Futhark":
exec "nim c -d:futharkWrap --outdir=tmp/ src/kuzu.nim"
exec "nim c -d:futharkWrap --outdir=tmp/ src/lbug.nim"
task test, "Run the test suite.":
exec "testament --megatest:off all"
@ -24,5 +24,5 @@ task docs, "Generate automated documentation.":
exec "nim md2html --project --outdir:docs README.md"
exec "nim md2html --project --outdir:docs History.md"
exec "nim md2html --project --outdir:docs USAGE.md"
exec "nim doc --project --outdir:docs src/kuzu.nim"
exec "nim doc --project --outdir:docs src/ladybug.nim"

View file

@ -1,49 +0,0 @@
# vim: set et sta sw=4 ts=4 :
#
{.passL:"-lkuzu".}
when defined( futharkWrap ):
import futhark, os
importc:
outputPath currentSourcePath.parentDir / "kuzu" / "0.11.3.nim"
"kuzu.h"
else:
include "kuzu/0.11.3.nim"
import
std/files,
std/paths,
std/strformat,
std/strutils
# Order very much matters here pre Nim 3.0 multi-pass compiling.
include
"kuzu/constants.nim",
"kuzu/types.nim",
"kuzu/config.nim",
"kuzu/database.nim",
"kuzu/connection.nim",
"kuzu/value.nim",
"kuzu/tuple.nim",
"kuzu/queries.nim"
proc kuzuVersionCompatible*(): bool =
## Returns true if the system installed Kuzu library
## is the expected version of this library wrapper.
result = KUZU_EXPECTED_LIBVERSION == $kuzuGetVersion()
when isMainModule:
echo "Nim-Kuzu version: ", KUZU_VERSION,
". Expected library version: ", KUZU_EXPECTED_LIBVERSION, "."
echo "Installed Kuzu library version ", kuzuGetVersion(),
" (storage version ", kuzuGetStorageVersion(), ")"
if kuzuVersionCompatible():
echo "Versions match!"
else:
echo "This library wraps a different version of Kuzu than what is installed."
echo "Behavior may be unexpected!"

File diff suppressed because it is too large Load diff

View file

@ -1,24 +0,0 @@
# vim: set et sta sw=4 ts=4 :
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,
read_only = KUZU_DEFAULT_CONFIG.read_only,
max_db_size = KUZU_DEFAULT_CONFIG.max_db_size,
auto_checkpoint = KUZU_DEFAULT_CONFIG.auto_checkpoint,
checkpoint_threshold = KUZU_DEFAULT_CONFIG.checkpoint_threshold
): kuzu_system_config =
## Returns a new kuzu database configuration object.
return kuzu_system_config(
buffer_pool_size: buffer_pool_size,
max_num_threads: max_num_threads,
enable_compression: enable_compression,
read_only: read_only,
max_db_size: max_db_size,
auto_checkpoint: auto_checkpoint,
checkpoint_threshold: checkpoint_threshold
)

View file

@ -1,27 +0,0 @@
# vim: set et sta sw=4 ts=4 :
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 )
func connect*( db: KuzuDatabase ): KuzuConnection =
## Connect to a database.
result = new KuzuConnection
if kuzu_connection_init( addr db.handle, addr result.handle ) == KuzuSuccess:
result.valid = true
else:
raise newException( KuzuException, "Unable to connect to the database." )
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 )
func queryInterrupt*( conn: KuzuConnection ) =
## Cancel any running queries.
kuzu_connection_interrupt( addr conn.handle )

View file

@ -1,8 +0,0 @@
# vim: set et sta sw=4 ts=4 :
const KUZU_VERSION* = "0.6.2"
const KUZU_EXPECTED_LIBVERSION* = "0.11.3"
const BLOB_MAXSIZE = 4096
let KUZU_DEFAULT_CONFIG* = kuzu_default_system_config()

View file

@ -1,197 +0,0 @@
# vim: set et sta sw=4 ts=4 :
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 )
# 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.
query.num_columns = kuzu_query_result_get_num_columns( addr query.handle )
query.num_tuples = kuzu_query_result_get_num_tuples( addr query.handle )
# Summary information.
var summary: kuzu_query_summary
discard kuzu_query_result_get_query_summary( addr query.handle, addr summary )
query.compile_time = kuzu_query_summary_get_compiling_time( addr summary )
query.execution_time = kuzu_query_summary_get_execution_time( 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.
query.column_types = @[]
query.column_names = @[]
if query.num_columns == 0: return
for idx in ( 0 .. query.num_columns-1 ):
# types
#
var logical_type: kuzu_logical_type
discard kuzu_query_result_get_column_data_type(
addr query.handle,
idx,
addr logical_type
)
query.column_types.add( kuzu_data_type_get_id( addr logical_type ))
kuzu_data_type_destroy( addr logical_type )
# names
#
var name: cstring
discard kuzu_query_result_get_column_name(
addr query.handle,
idx,
addr name
)
query.column_names.add( $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 =
## Perform a database +query+ and return the result.
result = new KuzuQueryResult
if kuzu_connection_query( addr conn.handle, query, addr result.handle ) == KuzuSuccess:
result.valid = true
result.getQueryMetadata( getAllQueryResults=true )
else:
var err = kuzu_query_result_get_error_message( addr result.handle )
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 )
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
if kuzu_connection_prepare( addr conn.handle, query, addr result.handle ) == KuzuSuccess:
result.conn = conn
result.valid = true
else:
var err = kuzu_prepared_statement_get_error_message( addr result.handle )
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*(
prepared: KuzuPreparedStatement,
params: tuple = ()
): KuzuQueryResult =
## Bind variables in *params* to the statement, and return
## a KuzuQueryResult.
result = new KuzuQueryResult
for key, val in params.fieldPairs:
prepared.handle.bindValue( key, val )
if kuzu_connection_execute(
addr prepared.conn.handle,
addr prepared.handle,
addr result.handle
) == KuzuSuccess:
result.valid = false
result.getQueryMetadata()
else:
var err = kuzu_query_result_get_error_message( addr result.handle )
raise newException( KuzuQueryError, &"Error executing prepared statement: {err}" )
func `$`*( query: KuzuQueryResult ): string =
## Return the entire result set as a string.
result = $kuzu_query_result_to_string( addr query.handle )
func hasNext*( query: KuzuQueryResult ): bool =
## Returns +true+ if there are more tuples to be consumed.
result = kuzu_query_result_has_next( addr query.handle )
func getNext*( query: KuzuQueryResult ): KuzuFlatTuple =
## 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( KuzuIterationError, &"Query iteration past end of tuples." )
func rewind*( query: KuzuQueryResult ) =
## Reset query iteration back to the beginning.
kuzu_query_result_reset_iterator( addr query.handle )
iterator items*( query: KuzuQueryResult ): KuzuFlatTuple =
## Iterate available tuples, yielding to the block.
while query.hasNext:
yield query.getNext
query.rewind

View file

@ -1,30 +0,0 @@
# vim: set et sta sw=4 ts=4 :
# NOTE: Constructor in queries.nim, #getNext
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 )
func `$`*( tpl: KuzuFlatTuple ): string =
## Stringify a tuple.
result = $kuzu_flat_tuple_to_string( addr tpl.handle )
result.removeSuffix( "\n" )
func `[]`*( tpl: KuzuFlatTuple, idx: int|uint64 ): 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( KuzuIndexError,
&"Unable to fetch tuple value at idx {idx}. ({tpl.num_columns} column(s).)" )

View file

@ -1,61 +0,0 @@
# vim: set et sta sw=4 ts=4 :
type
KuzuDBType* = enum
disk, memory
KuzuDatabaseObj = object
handle: kuzu_database
path*: string
kind*: KuzuDBType
config*: kuzu_system_config
valid = false
KuzuDatabase* = ref KuzuDatabaseObj
KuzuConnectionObj = object
handle: kuzu_connection
valid = false
KuzuConnection* = ref KuzuConnectionObj
KuzuQueryResultObj = object
handle: kuzu_query_result
num_columns*: uint64 = 0
num_tuples*: uint64 = 0
compile_time*: cdouble = 0
execution_time*: cdouble = 0
column_types*: seq[ kuzu_data_type_id ]
column_names*: seq[ string ]
sets*: seq[ KuzuQueryResult ]
valid = false
KuzuQueryResult* = ref KuzuQueryResultObj
KuzuPreparedStatementObj = object
handle: kuzu_prepared_statement
conn: KuzuConnection
valid = false
KuzuPreparedStatement* = ref KuzuPreparedStatementObj
KuzuFlatTupleObj = object
handle: kuzu_flat_tuple
num_columns: uint64 = 0
valid = false
KuzuFlatTuple* = ref KuzuFlatTupleObj
KuzuValueObj = object
handle: kuzu_value
valid = false
kind*: kuzu_data_type_id
KuzuValue* = ref KuzuValueObj
KuzuStructValueObj = object
value: KuzuValue
len*: uint64
keys*: seq[ string ]
KuzuStructValue* = ref KuzuStructValueObj
KuzuException* = object of CatchableError
KuzuQueryError* = object of KuzuException
KuzuIndexError* = object of KuzuException
KuzuIterationError* = object of KuzuException
KuzuTypeError* = object of KuzuException

View file

@ -1,197 +0,0 @@
# vim: set et sta sw=4 ts=4 :
# NOTE: Constructor in tuples.nim, #[]
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 )
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 )
value.kind = kuzu_data_type_get_id( addr logical_type )
kuzu_data_type_destroy( addr logical_type )
template checkType( kind: kuzu_data_type_id, valid_types: set ) =
## Raises a KuzuTypeError if the type conversion is incompatible.
if kind notin valid_types:
let msg = "Mismatched types: " & $kind & " != " & $valid_types
raise newException( KuzuTypeError, msg )
func `$`*( value: KuzuValue ): string =
## Stringify a value.
result = $kuzu_value_to_string( addr value.handle )
func toBool*( value: KuzuValue ): bool =
## Conversion from Kuzu type to Nim.
checkType( value.kind, {KUZU_BOOL} )
assert( kuzu_value_get_bool( addr value.handle, addr result ) == KuzuSuccess )
func toInt8*( value: KuzuValue ): int8 =
## Conversion from Kuzu type to Nim.
checkType( value.kind, {KUZU_INT8} )
assert( kuzu_value_get_int8( addr value.handle, addr result ) == KuzuSuccess )
func toInt16*( value: KuzuValue ): int16 =
## Conversion from Kuzu type to Nim.
checkType( value.kind, {KUZU_INT16} )
assert( kuzu_value_get_int16( addr value.handle, addr result ) == KuzuSuccess )
func toInt32*( value: KuzuValue ): int32 =
## Conversion from Kuzu type to Nim.
checkType( value.kind, {KUZU_INT32} )
assert( kuzu_value_get_int32( addr value.handle, addr result ) == KuzuSuccess )
func toInt64*( value: KuzuValue ): int64 =
## Conversion from Kuzu type to Nim.
checkType( value.kind, {KUZU_INT64} )
assert( kuzu_value_get_int64( addr value.handle, addr result ) == KuzuSuccess )
func toUint8*( value: KuzuValue ): uint8 =
## Conversion from Kuzu type to Nim.
checkType( value.kind, {KUZU_UINT8} )
assert( kuzu_value_get_uint8( addr value.handle, addr result ) == KuzuSuccess )
func toUint16*( value: KuzuValue ): uint16 =
## Conversion from Kuzu type to Nim.
checkType( value.kind, {KUZU_UINT16} )
assert( kuzu_value_get_uint16( addr value.handle, addr result ) == KuzuSuccess )
func toUint32*( value: KuzuValue ): uint32 =
## Conversion from Kuzu type to Nim.
checkType( value.kind, {KUZU_UINT32} )
assert( kuzu_value_get_uint32( addr value.handle, addr result ) == KuzuSuccess )
func toUint64*( value: KuzuValue ): uint64 =
## Conversion from Kuzu type to Nim.
checkType( value.kind, {KUZU_UINT64} )
assert( kuzu_value_get_uint64( addr value.handle, addr result ) == KuzuSuccess )
func toDouble*( value: KuzuValue ): float =
## Conversion from Kuzu type to Nim.
checkType( value.kind, {KUZU_DOUBLE} )
assert( kuzu_value_get_double( addr value.handle, addr result ) == KuzuSuccess )
func toFloat*( value: KuzuValue ): float =
## Conversion from Kuzu type to Nim.
checkType( value.kind, {KUZU_FLOAT} )
var rv: cfloat
assert( kuzu_value_get_float( addr value.handle, addr rv ) == KuzuSuccess )
result = rv
func toTimestamp*( value: KuzuValue ): int =
## Conversion from Kuzu type to Nim.
checkType( value.kind, {KUZU_TIMESTAMP} )
var rv: kuzu_timestamp_t
assert( kuzu_value_get_timestamp( addr value.handle, addr rv ) == KuzuSuccess )
result = rv.value
func toList*( value: KuzuValue ): seq[ KuzuValue ] =
## Return a sequence from KUZU_LIST values.
checkType( value.kind, {KUZU_LIST} )
result = @[]
var size: uint64
assert( kuzu_value_get_list_size( addr value.handle, addr size ) == KuzuSuccess )
if size == 0: return
for i in ( 0 .. size-1 ):
var kval = new KuzuValue
assert(
kuzu_value_get_list_element(
addr value.handle, i.uint64, addr kval.handle
) == KuzuSuccess )
kval.getType()
result.add( kval )
const toSeq* = toList
func toBlob*( value: KuzuValue ): seq[ byte ] =
## Conversion from Kuzu type to Nim - returns a BLOB as a sequence of bytes.
checkType( value.kind, {KUZU_BLOB} )
result = @[]
var data: ptr byte
assert( kuzu_value_get_blob( addr value.handle, addr data ) == KuzuSuccess )
for idx in 0 .. BLOB_MAXSIZE:
var byte = cast[ptr byte](cast[uint](data) + idx.uint)[]
if byte == 0: break
result.add( byte )
kuzu_destroy_blob( data )
func toStruct*( value: KuzuValue ): KuzuStructValue =
## Create a convenience class for struct-like KuzuValues.
checkType( value.kind, {
KUZU_STRUCT,
KUZU_NODE,
KUZU_REL,
KUZU_RECURSIVE_REL,
KUZU_UNION
})
result = new KuzuStructValue
result.value = value
discard kuzu_value_get_struct_num_fields( addr value.handle, addr result.len )
if result.len == 0: return
# Build keys
for idx in ( 0 .. result.len - 1 ):
var keyname: cstring
assert(
kuzu_value_get_struct_field_name(
addr value.handle, idx.uint64, addr keyname
) == KuzuSuccess )
result.keys.add( $keyname )
const toNode* = toStruct
const toRel* = toStruct
func `[]`*( struct: KuzuStructValue, key: string ): KuzuValue =
## Return a KuzuValue for the struct *key*.
var idx: uint64
var found = false
for i in ( 0 .. struct.len-1 ):
if struct.keys[i] == key:
found = true
idx = i
break
if not found:
raise newException( KuzuIndexError,
&"""No such struct key "{key}".""" )
result = new KuzuValue
assert(
kuzu_value_get_struct_field_value(
addr struct.value.handle, idx.uint64, addr result.handle
) == KuzuSuccess )
result.getType()
func `$`*( struct: KuzuStructValue ): string =
## Stringify a struct value.
result = $kuzu_value_to_string( addr struct.value.handle )

6
src/ladybug.nim Normal file
View file

@ -0,0 +1,6 @@
# vim: set et sta sw=4 ts=4 :
#
# Friendly alias to lbug.
include "lbug.nim"

49
src/lbug.nim Normal file
View file

@ -0,0 +1,49 @@
# vim: set et sta sw=4 ts=4 :
#
{.passL:"-llbug".}
when defined( futharkWrap ):
import futhark, os
importc:
outputPath currentSourcePath.parentDir / "lbug" / "0.12.0.nim"
"lbug.h"
else:
include "lbug/0.12.0.nim"
import
std/files,
std/paths,
std/strformat,
std/strutils
# Order very much matters here pre Nim 3.0 multi-pass compiling.
include
"lbug/constants.nim",
"lbug/types.nim",
"lbug/config.nim",
"lbug/database.nim",
"lbug/connection.nim",
"lbug/value.nim",
"lbug/tuple.nim",
"lbug/queries.nim"
proc lbugVersionCompatible*(): bool =
## Returns true if the system installed LadybugDB library
## is the expected version of this library wrapper.
result = LBUG_EXPECTED_LIBVERSION == $lbugGetVersion()
when isMainModule:
echo "Nim-Ladybug version: ", LBUG_VERSION,
". Expected library version: ", LBUG_EXPECTED_LIBVERSION, "."
echo "Installed lbug library version ", lbugGetVersion(),
" (storage version ", lbugGetStorageVersion(), ")"
if lbugVersionCompatible():
echo "Versions match!"
else:
echo "This library wraps a different version of LadybugDB than what is installed."
echo "Behavior may be unexpected!"

2168
src/lbug/0.12.0.nim Normal file

File diff suppressed because it is too large Load diff

24
src/lbug/config.nim Normal file
View file

@ -0,0 +1,24 @@
# vim: set et sta sw=4 ts=4 :
func lbugConfig*(
buffer_pool_size = LBUG_DEFAULT_CONFIG.buffer_pool_size,
max_num_threads = LBUG_DEFAULT_CONFIG.max_num_threads,
enable_compression = LBUG_DEFAULT_CONFIG.enable_compression,
read_only = LBUG_DEFAULT_CONFIG.read_only,
max_db_size = LBUG_DEFAULT_CONFIG.max_db_size,
auto_checkpoint = LBUG_DEFAULT_CONFIG.auto_checkpoint,
checkpoint_threshold = LBUG_DEFAULT_CONFIG.checkpoint_threshold
): lbug_system_config =
## Returns a new lbug database configuration object.
return lbug_system_config(
buffer_pool_size: buffer_pool_size,
max_num_threads: max_num_threads,
enable_compression: enable_compression,
read_only: read_only,
max_db_size: max_db_size,
auto_checkpoint: auto_checkpoint,
checkpoint_threshold: checkpoint_threshold
)

27
src/lbug/connection.nim Normal file
View file

@ -0,0 +1,27 @@
# vim: set et sta sw=4 ts=4 :
proc `=destroy`*( conn: LbugConnectionObj ) =
## Graceful cleanup for open connection handles.
if conn.valid:
when defined( debug ): echo &"Destroying connection: {conn}"
lbug_connection_destroy( addr conn.handle )
func connect*( db: LbugDatabase ): LbugConnection =
## Connect to a database.
result = new LbugConnection
if lbug_connection_init( addr db.handle, addr result.handle ) == LbugSuccess:
result.valid = true
else:
raise newException( LbugException, "Unable to connect to the database." )
func queryTimeout*( conn: LbugConnection, timeout: uint64 ) =
## Set a maximum time limit (in milliseconds) for query runtime.
discard lbug_connection_set_query_timeout( addr conn.handle, timeout )
func queryInterrupt*( conn: LbugConnection ) =
## Cancel any running queries.
lbug_connection_interrupt( addr conn.handle )

8
src/lbug/constants.nim Normal file
View file

@ -0,0 +1,8 @@
# vim: set et sta sw=4 ts=4 :
const LBUG_VERSION* = "0.7.0"
const LBUG_EXPECTED_LIBVERSION* = "0.12.0"
const BLOB_MAXSIZE = 4096
let LBUG_DEFAULT_CONFIG* = lbug_default_system_config()

View file

@ -1,13 +1,13 @@
# vim: set et sta sw=4 ts=4 :
proc `=destroy`*( db: KuzuDatabaseObj ) =
proc `=destroy`*( db: LbugDatabaseObj ) =
## 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 )
lbug_database_destroy( addr db.handle )
proc validateDatabase( db: KuzuDatabase ): void =
proc validateDatabase( db: LbugDatabase ): void =
## Perform basic validity checks against an existing on disk database
## for better error messaging.
@ -21,20 +21,20 @@ proc validateDatabase( db: KuzuDatabase ): void =
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 magic != "LBUG":
raise newException( LbugException, "Unable to open database: " &
&""""{db.path}" Doesn't appear to be a LadybugDB file.""" )
if storageVersion != kuzuGetStorageVersion():
raise newException( KuzuException, "Unable to open database: " &
&" mismatched storage versions - file is {storageVersion}, expected {kuzuGetStorageVersion()}." )
if storageVersion != lbugGetStorageVersion():
raise newException( LbugException, "Unable to open database: " &
&" mismatched storage versions - file is {storageVersion}, expected {lbugGetStorageVersion()}." )
proc newKuzuDatabase*( path="", config=kuzuConfig() ): KuzuDatabase =
## Create a new Kuzu database handle. Creates an in-memory
proc newlbugDatabase*( path="", config=lbugConfig() ): LbugDatabase =
## Create a new Lbug database handle. Creates an in-memory
## database by default, but writes to disk if a +path+ is supplied.
result = new KuzuDatabase
result = new LbugDatabase
result.config = config
if path != "" and path != ":memory:":
@ -44,13 +44,13 @@ proc newKuzuDatabase*( path="", config=kuzuConfig() ): KuzuDatabase =
result.path = "(in-memory)"
result.kind = memory
result.handle = kuzu_database()
result.handle = lbug_database()
if result.kind == disk:
result.validateDatabase()
if kuzu_database_init( path, config, addr result.handle ) == KuzuSuccess:
if lbug_database_init( path, config, addr result.handle ) == LbugSuccess:
result.valid = true
else:
raise newException( KuzuException, "Unable to open database." )
raise newException( LbugException, "Unable to open database." )

197
src/lbug/queries.nim Normal file
View file

@ -0,0 +1,197 @@
# vim: set et sta sw=4 ts=4 :
proc `=destroy`*( query: LbugQueryResultObj ) =
## Graceful cleanup for out of scope query objects.
if query.valid:
when defined( debug ): echo &"Destroying query: {query}"
lbug_query_result_destroy( addr query.handle )
# Forward declarations.
func hasNextSet( query: LbugQueryResult ): bool
func getNextSet( query: LbugQueryResult ): LbugQueryResult
func getQueryMetadata( query: LbugQueryResult, getAllQueryResults=false ) =
## Find and retain additional data for the query.
query.num_columns = lbug_query_result_get_num_columns( addr query.handle )
query.num_tuples = lbug_query_result_get_num_tuples( addr query.handle )
# Summary information.
var summary: lbug_query_summary
discard lbug_query_result_get_query_summary( addr query.handle, addr summary )
query.compile_time = lbug_query_summary_get_compiling_time( addr summary )
query.execution_time = lbug_query_summary_get_execution_time( addr summary )
lbug_query_summary_destroy( addr summary )
# Pull any additional query results.
query.sets = @[]
if getAllQueryResults:
while query.hasNextSet:
query.sets.add( query.getNextSet )
# Column information.
query.column_types = @[]
query.column_names = @[]
if query.num_columns == 0: return
for idx in ( 0 .. query.num_columns-1 ):
# types
#
var logical_type: lbug_logical_type
discard lbug_query_result_get_column_data_type(
addr query.handle,
idx,
addr logical_type
)
query.column_types.add( lbug_data_type_get_id( addr logical_type ))
lbug_data_type_destroy( addr logical_type )
# names
#
var name: cstring
discard lbug_query_result_get_column_name(
addr query.handle,
idx,
addr name
)
query.column_names.add( $name )
lbug_destroy_string( name )
func hasNextSet( query: LbugQueryResult ): bool =
## Returns +true+ if there are more result sets to be consumed.
result = lbug_query_result_has_next_query_result( addr query.handle )
func getNextSet( query: LbugQueryResult ): LbugQueryResult =
## Consume and return the next query set result, or raise a LbugIterationError
## if at the end of sets.
result = new LbugQueryResult
if lbug_query_result_get_next_query_result( addr query.handle, addr result.handle ) == LbugSuccess:
result.valid = true
result.getQueryMetadata()
else:
raise newException( LbugIterationError, &"Query iteration past end of set." )
func query*( conn: LbugConnection, query: string ): LbugQueryResult =
## Perform a database +query+ and return the result.
result = new LbugQueryResult
if lbug_connection_query( addr conn.handle, query, addr result.handle ) == LbugSuccess:
result.valid = true
result.getQueryMetadata( getAllQueryResults=true )
else:
var err = lbug_query_result_get_error_message( addr result.handle )
raise newException( LbugQueryError, &"Error running query: {err}" )
proc `=destroy`*( prepared: LbugPreparedStatementObj ) =
## Graceful cleanup for out of scope prepared objects.
if prepared.valid:
when defined( debug ): echo &"Destroying prepared statement: {prepared}"
lbug_prepared_statement_destroy( addr prepared.handle )
func prepare*( conn: LbugConnection, query: string ): LbugPreparedStatement =
## Return a prepared statement that can avoid planning for repeat calls,
## with optional variable binding via #execute.
result = new LbugPreparedStatement
if lbug_connection_prepare( addr conn.handle, query, addr result.handle ) == LbugSuccess:
result.conn = conn
result.valid = true
else:
var err = lbug_prepared_statement_get_error_message( addr result.handle )
raise newException( LbugQueryError, &"Error preparing statement: {err}" )
func bindValue[T](
stmtHandle: lbug_prepared_statement,
key: cstring,
val: T
) =
## Bind a key/value to a prepared statement handle.
when typeOf( val ) is bool:
assert( lbug_prepared_statement_bind_bool( addr stmtHandle, key, val ) == LbugSuccess )
elif typeOf( val ) is int8:
assert( lbug_prepared_statement_bind_int8( addr stmtHandle, key, val ) == LbugSuccess )
elif typeOf( val ) is int16:
assert( lbug_prepared_statement_bind_int16( addr stmtHandle, key, val ) == LbugSuccess )
elif typeOf( val ) is int64:
assert( lbug_prepared_statement_bind_int64( addr stmtHandle, key, val ) == LbugSuccess )
elif typeOf( val ) is int or typeOf( val ) is int32:
assert( lbug_prepared_statement_bind_int32( addr stmtHandle, key, val.int32 ) == LbugSuccess )
elif typeOf( val ) is uint8:
assert( lbug_prepared_statement_bind_uint8( addr stmtHandle, key, val ) == LbugSuccess )
elif typeOf( val ) is uint16:
assert( lbug_prepared_statement_bind_uint16( addr stmtHandle, key, val ) == LbugSuccess )
elif typeOf( val ) is uint64:
assert( lbug_prepared_statement_bind_uint64( addr stmtHandle, key, val ) == LbugSuccess )
elif typeOf( val ) is uint or typeOf( val ) is uint32:
assert( lbug_prepared_statement_bind_uint32( addr stmtHandle, key, val.uint32 ) == LbugSuccess )
elif typeOf( val ) is float:
assert( lbug_prepared_statement_bind_double( addr stmtHandle, key, val ) == LbugSuccess )
elif typeOf( val ) is string:
# Fallback to string. For custom types, just cast in the cypher query.
assert( lbug_prepared_statement_bind_string( addr stmtHandle, key, val.cstring ) == LbugSuccess )
else:
raise newException( LbugTypeError, &"""Unsupported type {$typeOf(val)} for prepared statement.""" )
proc execute*(
prepared: LbugPreparedStatement,
params: tuple = ()
): LbugQueryResult =
## Bind variables in *params* to the statement, and return
## a LbugQueryResult.
result = new LbugQueryResult
for key, val in params.fieldPairs:
prepared.handle.bindValue( key, val )
if lbug_connection_execute(
addr prepared.conn.handle,
addr prepared.handle,
addr result.handle
) == LbugSuccess:
result.valid = false
result.getQueryMetadata()
else:
var err = lbug_query_result_get_error_message( addr result.handle )
raise newException( LbugQueryError, &"Error executing prepared statement: {err}" )
func `$`*( query: LbugQueryResult ): string =
## Return the entire result set as a string.
result = $lbug_query_result_to_string( addr query.handle )
func hasNext*( query: LbugQueryResult ): bool =
## Returns +true+ if there are more tuples to be consumed.
result = lbug_query_result_has_next( addr query.handle )
func getNext*( query: LbugQueryResult ): LbugFlatTuple =
## Consume and return the next tuple result, or raise a LbugIterationError
## if at the end of the result tuples.
result = new LbugFlatTuple
if lbug_query_result_get_next( addr query.handle, addr result.handle ) == LbugSuccess:
result.valid = true
result.num_columns = query.num_columns
else:
raise newException( LbugIterationError, &"Query iteration past end of tuples." )
func rewind*( query: LbugQueryResult ) =
## Reset query iteration back to the beginning.
lbug_query_result_reset_iterator( addr query.handle )
iterator items*( query: LbugQueryResult ): LbugFlatTuple =
## Iterate available tuples, yielding to the block.
while query.hasNext:
yield query.getNext
query.rewind

30
src/lbug/tuple.nim Normal file
View file

@ -0,0 +1,30 @@
# vim: set et sta sw=4 ts=4 :
# NOTE: Constructor in queries.nim, #getNext
proc `=destroy`*( tpl: LbugFlatTupleObj ) =
## Graceful cleanup for out of scope tuples.
if tpl.valid:
when defined( debug ): echo &"Destroying tuple: {tpl}"
lbug_flat_tuple_destroy( addr tpl.handle )
func `$`*( tpl: LbugFlatTuple ): string =
## Stringify a tuple.
result = $lbug_flat_tuple_to_string( addr tpl.handle )
result.removeSuffix( "\n" )
func `[]`*( tpl: LbugFlatTuple, idx: int|uint64 ): LbugValue =
## Returns a LbugValue at the given *idx*.
result = new LbugValue
if lbug_flat_tuple_get_value( addr tpl.handle, idx.uint64, addr result.handle ) == LbugSuccess:
result.valid = true
result.getType()
else:
raise newException( LbugIndexError,
&"Unable to fetch tuple value at idx {idx}. ({tpl.num_columns} column(s).)" )

61
src/lbug/types.nim Normal file
View file

@ -0,0 +1,61 @@
# vim: set et sta sw=4 ts=4 :
type
LbugDBType* = enum
disk, memory
LbugDatabaseObj = object
handle: lbug_database
path*: string
kind*: LbugDBType
config*: lbug_system_config
valid = false
LbugDatabase* = ref LbugDatabaseObj
LbugConnectionObj = object
handle: lbug_connection
valid = false
LbugConnection* = ref LbugConnectionObj
LbugQueryResultObj = object
handle: lbug_query_result
num_columns*: uint64 = 0
num_tuples*: uint64 = 0
compile_time*: cdouble = 0
execution_time*: cdouble = 0
column_types*: seq[ lbug_data_type_id ]
column_names*: seq[ string ]
sets*: seq[ LbugQueryResult ]
valid = false
LbugQueryResult* = ref LbugQueryResultObj
LbugPreparedStatementObj = object
handle: lbug_prepared_statement
conn: LbugConnection
valid = false
LbugPreparedStatement* = ref LbugPreparedStatementObj
LbugFlatTupleObj = object
handle: lbug_flat_tuple
num_columns: uint64 = 0
valid = false
LbugFlatTuple* = ref LbugFlatTupleObj
LbugValueObj = object
handle: lbug_value
valid = false
kind*: lbug_data_type_id
LbugValue* = ref LbugValueObj
LbugStructValueObj = object
value: LbugValue
len*: uint64
keys*: seq[ string ]
LbugStructValue* = ref LbugStructValueObj
LbugException* = object of CatchableError
LbugQueryError* = object of LbugException
LbugIndexError* = object of LbugException
LbugIterationError* = object of LbugException
LbugTypeError* = object of LbugException

197
src/lbug/value.nim Normal file
View file

@ -0,0 +1,197 @@
# vim: set et sta sw=4 ts=4 :
# NOTE: Constructor in tuples.nim, #[]
proc `=destroy`*( value: LbugValueObj ) =
## Graceful cleanup for out of scope values.
if value.valid:
when defined( debug ): echo &"Destroying value: {value}"
lbug_value_destroy( addr value.handle )
func getType( value: LbugValue ) =
## Find and set the native Lbug type of this value.
var logical_type: lbug_logical_type
lbug_value_get_data_type( addr value.handle, addr logical_type )
value.kind = lbug_data_type_get_id( addr logical_type )
lbug_data_type_destroy( addr logical_type )
template checkType( kind: lbug_data_type_id, valid_types: set ) =
## Raises a LbugTypeError if the type conversion is incompatible.
if kind notin valid_types:
let msg = "Mismatched types: " & $kind & " != " & $valid_types
raise newException( LbugTypeError, msg )
func `$`*( value: LbugValue ): string =
## Stringify a value.
result = $lbug_value_to_string( addr value.handle )
func toBool*( value: LbugValue ): bool =
## Conversion from Lbug type to Nim.
checkType( value.kind, {LBUG_BOOL} )
assert( lbug_value_get_bool( addr value.handle, addr result ) == LbugSuccess )
func toInt8*( value: LbugValue ): int8 =
## Conversion from Lbug type to Nim.
checkType( value.kind, {LBUG_INT8} )
assert( lbug_value_get_int8( addr value.handle, addr result ) == LbugSuccess )
func toInt16*( value: LbugValue ): int16 =
## Conversion from Lbug type to Nim.
checkType( value.kind, {LBUG_INT16} )
assert( lbug_value_get_int16( addr value.handle, addr result ) == LbugSuccess )
func toInt32*( value: LbugValue ): int32 =
## Conversion from Lbug type to Nim.
checkType( value.kind, {LBUG_INT32} )
assert( lbug_value_get_int32( addr value.handle, addr result ) == LbugSuccess )
func toInt64*( value: LbugValue ): int64 =
## Conversion from Lbug type to Nim.
checkType( value.kind, {LBUG_INT64} )
assert( lbug_value_get_int64( addr value.handle, addr result ) == LbugSuccess )
func toUint8*( value: LbugValue ): uint8 =
## Conversion from Lbug type to Nim.
checkType( value.kind, {LBUG_UINT8} )
assert( lbug_value_get_uint8( addr value.handle, addr result ) == LbugSuccess )
func toUint16*( value: LbugValue ): uint16 =
## Conversion from Lbug type to Nim.
checkType( value.kind, {LBUG_UINT16} )
assert( lbug_value_get_uint16( addr value.handle, addr result ) == LbugSuccess )
func toUint32*( value: LbugValue ): uint32 =
## Conversion from Lbug type to Nim.
checkType( value.kind, {LBUG_UINT32} )
assert( lbug_value_get_uint32( addr value.handle, addr result ) == LbugSuccess )
func toUint64*( value: LbugValue ): uint64 =
## Conversion from Lbug type to Nim.
checkType( value.kind, {LBUG_UINT64} )
assert( lbug_value_get_uint64( addr value.handle, addr result ) == LbugSuccess )
func toDouble*( value: LbugValue ): float =
## Conversion from Lbug type to Nim.
checkType( value.kind, {LBUG_DOUBLE} )
assert( lbug_value_get_double( addr value.handle, addr result ) == LbugSuccess )
func toFloat*( value: LbugValue ): float =
## Conversion from Lbug type to Nim.
checkType( value.kind, {LBUG_FLOAT} )
var rv: cfloat
assert( lbug_value_get_float( addr value.handle, addr rv ) == LbugSuccess )
result = rv
func toTimestamp*( value: LbugValue ): int =
## Conversion from Lbug type to Nim.
checkType( value.kind, {LBUG_TIMESTAMP} )
var rv: lbug_timestamp_t
assert( lbug_value_get_timestamp( addr value.handle, addr rv ) == LbugSuccess )
result = rv.value
func toList*( value: LbugValue ): seq[ LbugValue ] =
## Return a sequence from LBUG_LIST values.
checkType( value.kind, {LBUG_LIST} )
result = @[]
var size: uint64
assert( lbug_value_get_list_size( addr value.handle, addr size ) == LbugSuccess )
if size == 0: return
for i in ( 0 .. size-1 ):
var kval = new LbugValue
assert(
lbug_value_get_list_element(
addr value.handle, i.uint64, addr kval.handle
) == LbugSuccess )
kval.getType()
result.add( kval )
const toSeq* = toList
func toBlob*( value: LbugValue ): seq[ byte ] =
## Conversion from Lbug type to Nim - returns a BLOB as a sequence of bytes.
checkType( value.kind, {LBUG_BLOB} )
result = @[]
var data: ptr byte
assert( lbug_value_get_blob( addr value.handle, addr data ) == LbugSuccess )
for idx in 0 .. BLOB_MAXSIZE:
var byte = cast[ptr byte](cast[uint](data) + idx.uint)[]
if byte == 0: break
result.add( byte )
lbug_destroy_blob( data )
func toStruct*( value: LbugValue ): LbugStructValue =
## Create a convenience class for struct-like LbugValues.
checkType( value.kind, {
LBUG_STRUCT,
LBUG_NODE,
LBUG_REL,
LBUG_RECURSIVE_REL,
LBUG_UNION
})
result = new LbugStructValue
result.value = value
discard lbug_value_get_struct_num_fields( addr value.handle, addr result.len )
if result.len == 0: return
# Build keys
for idx in ( 0 .. result.len - 1 ):
var keyname: cstring
assert(
lbug_value_get_struct_field_name(
addr value.handle, idx.uint64, addr keyname
) == LbugSuccess )
result.keys.add( $keyname )
const toNode* = toStruct
const toRel* = toStruct
func `[]`*( struct: LbugStructValue, key: string ): LbugValue =
## Return a LbugValue for the struct *key*.
var idx: uint64
var found = false
for i in ( 0 .. struct.len-1 ):
if struct.keys[i] == key:
found = true
idx = i
break
if not found:
raise newException( LbugIndexError,
&"""No such struct key "{key}".""" )
result = new LbugValue
assert(
lbug_value_get_struct_field_value(
addr struct.value.handle, idx.uint64, addr result.handle
) == LbugSuccess )
result.getType()
func `$`*( struct: LbugStructValue ): string =
## Stringify a struct value.
result = $lbug_value_to_string( addr struct.value.handle )

View file

@ -1,10 +1,10 @@
# vim: set et sta sw=4 ts=4 :
import kuzu
import lbug
let db = newKuzuDatabase()
let db = newLbugDatabase()
assert db.path == "(in-memory)"
assert typeOf( db.connect ) is KuzuConnection
assert typeOf( db.connect ) is LbugConnection

View file

@ -1,9 +1,9 @@
# vim: set et sta sw=4 ts=4 :
import kuzu
import lbug
let db = newKuzuDatabase()
let db = newLbugDatabase()
let conn = db.connect
# FIXME: This test should really perform some

View file

@ -1,8 +1,8 @@
# vim: set et sta sw=4 ts=4 :
import kuzu
import lbug
let db = newKuzuDatabase()
let db = newLbugDatabase()
let conn = db.connect
# There is currently no getter for this, so

View file

@ -1,6 +1,6 @@
# vim: set et sta sw=4 ts=4 :
import kuzu
import lbug
assert typeOf( KUZU_DEFAULT_CONFIG ) is kuzu_system_config
assert typeOf( LBUG_DEFAULT_CONFIG ) is lbug_system_config

View file

@ -1,7 +1,7 @@
# vim: set et sta sw=4 ts=4 :
import re
import kuzu
import lbug
assert KUZU_VERSION.contains( re"^\d+\.\d+\.\d+$" )
assert LBUG_VERSION.contains( re"^\d+\.\d+\.\d+$" )

View file

@ -1,8 +1,8 @@
# vim: set et sta sw=4 ts=4 :
import re
import kuzu
import lbug
let version = $kuzuGetVersion()
let version = $lbugGetVersion()
assert version.contains( re"^\d+\.\d+\.\d+(?:\.\d+)?$" )

View file

@ -1,6 +1,6 @@
# vim: set et sta sw=4 ts=4 :
import kuzu
import lbug
assert kuzuGetStorageVersion() >= 36
assert lbugGetStorageVersion() >= 36

View file

@ -1,8 +1,8 @@
# vim: set et sta sw=4 ts=4 :
import kuzu
import lbug
var db = newKuzuDatabase()
var db = newLbugDatabase()
assert db.path == "(in-memory)"
assert db.kind == memory

View file

@ -4,12 +4,12 @@ import
std/files,
std/paths
import kuzu
import lbug
const DATABASE_PATH = Path( "tmp/testdb" )
DATABASE_PATH.removeFile()
var db = newKuzuDatabase( $DATABASE_PATH, kuzuConfig( auto_checkpoint=false ) )
var db = newLbugDatabase( $DATABASE_PATH, lbugConfig( auto_checkpoint=false ) )
assert db.path == "tmp/testdb"
assert db.config.auto_checkpoint == false

View file

@ -5,7 +5,7 @@ import
std/paths,
std/re
import kuzu
import lbug
const NOT_A_DATABASE_PATH = Path( "tmp/not-a-db" )
@ -15,9 +15,9 @@ 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""" )
discard newLbugDatabase( $NOT_A_DATABASE_PATH )
except LbugException as err:
assert err.msg.contains( re"""Unable to open database: "tmp/not-a-db" Doesn't appear to be a LadybugDB file""" )
NOT_A_DATABASE_PATH.removeFile()

View file

@ -4,16 +4,16 @@ import
std/files,
std/paths
import kuzu
import lbug
const DATABASE_PATH = Path( "tmp/testdb" )
DATABASE_PATH.removeFile()
var db = newKuzuDatabase( $DATABASE_PATH )
var db = newLbugDatabase( $DATABASE_PATH )
assert db.path == $DATABASE_PATH
assert db.kind == disk
assert db.config == kuzuConfig()
assert db.config == lbugConfig()
assert db.config.read_only == false
DATABASE_PATH.removeFile()

View file

@ -1,8 +1,8 @@
# vim: set et sta sw=4 ts=4 :
import kuzu
import lbug
let db = newKuzuDatabase()
let db = newLbugDatabase()
let conn = db.connect
var q = conn.query """

View file

@ -4,19 +4,19 @@ discard """
output: "d.thing\nCamel\nLampshade\nDelicious Cake\n"
"""
import kuzu
import lbug
let db = newKuzuDatabase()
let db = newLbugDatabase()
let conn = db.connect
var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, thing STRING, PRIMARY KEY(id) )" )
var p = conn.prepare( "CREATE (d:Doop {thing: $thing})" )
assert typeOf( p ) is KuzuPreparedStatement
assert typeOf( p ) is LbugPreparedStatement
for thing in @[ "Camel", "Lampshade", "Delicious Cake" ]:
q = p.execute( (thing: thing) )
assert typeOf( q ) is KuzuQueryResult
assert typeOf( q ) is LbugQueryResult
# Fixed post v0.8.2:
# https://github.com/kuzudb/kuzu/issues/5102

View file

@ -4,9 +4,9 @@ discard """
output: "0|-222222|128|True|Stuff!|3.344903|239.299923|a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11|2025-03-29"
"""
import kuzu
import lbug
let db = newKuzuDatabase()
let db = newLbugDatabase()
let conn = db.connect
var q = conn.query( """CREATE NODE TABLE Doop (
@ -21,7 +21,7 @@ var q = conn.query( """CREATE NODE TABLE Doop (
date DATE,
PRIMARY KEY(id)
)""" )
assert typeOf( q ) is KuzuQueryResult
assert typeOf( q ) is LbugQueryResult
var stmt = conn.prepare( """CREATE (d:Doop {
@ -34,7 +34,7 @@ var stmt = conn.prepare( """CREATE (d:Doop {
uuid: UUID($uuid),
date: DATE($date)
})""" )
assert typeOf( stmt ) is KuzuPreparedStatement
assert typeOf( stmt ) is LbugPreparedStatement
q = stmt.execute((
@ -47,7 +47,7 @@ q = stmt.execute((
uuid: "A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11",
date: "2025-03-29"
))
assert typeOf( q ) is KuzuQueryResult
assert typeOf( q ) is LbugQueryResult
q = conn.query( "MATCH (d:Doop) RETURN d.*" )

View file

@ -4,14 +4,14 @@ discard """
output: "a\nb\nc\nd\ne\nf\n"
"""
import kuzu
import lbug
let db = newKuzuDatabase()
let db = newLbugDatabase()
let conn = db.connect
var q = conn.query( "RETURN 'hi'" )
assert typeOf( q ) is KuzuQueryResult
assert typeOf( q ) is LbugQueryResult
assert q.sets.len == 0
q = conn.query """
@ -23,7 +23,7 @@ q = conn.query """
RETURN "f";
"""
assert typeOf( q ) is KuzuQueryResult
assert typeOf( q ) is LbugQueryResult
assert q.sets.len == 5
echo q.getNext

View file

@ -1,16 +1,16 @@
# vim: set et sta sw=4 ts=4 :
import kuzu
import lbug
let db = newKuzuDatabase()
let db = newLbugDatabase()
let conn = db.connect
var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, thing STRING, PRIMARY KEY(id) )" )
assert typeOf( q ) is KuzuQueryResult
assert typeOf( q ) is LbugQueryResult
for thing in @[ "Camel", "Lampshade", "Delicious Cake" ]:
q = conn.query( "CREATE (d:Doop {thing: '" & thing & "'})" )
assert typeOf( q ) is KuzuQueryResult
assert typeOf( q ) is LbugQueryResult
q = conn.query( "MATCH (d:Doop) RETURN d.thing" )
assert q.num_columns == 1

View file

@ -4,9 +4,9 @@ discard """
output: "Camel\nLampshade\nDelicious Cake\n"
"""
import kuzu
import lbug
let db = newKuzuDatabase()
let db = newLbugDatabase()
let conn = db.connect
var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, thing STRING, PRIMARY KEY(id) )" )

View file

@ -4,9 +4,9 @@ discard """
output: "Camel\nLampshade\nCamel\nLampshade\n"
"""
import kuzu
import lbug
let db = newKuzuDatabase()
let db = newLbugDatabase()
let conn = db.connect
var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, thing STRING, PRIMARY KEY(id) )" )

View file

@ -4,9 +4,9 @@ discard """
output: "d.thing\nokay!\n\n"
"""
import kuzu
import lbug
let db = newKuzuDatabase()
let db = newLbugDatabase()
let conn = db.connect
var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, thing STRING, PRIMARY KEY(id) )" )

View file

@ -1,12 +1,12 @@
# vim: set et sta sw=4 ts=4 :
import kuzu
import lbug
let db = newKuzuDatabase()
let db = newLbugDatabase()
let conn = db.connect
var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, thing STRING, PRIMARY KEY(id) )" )
assert typeOf( q ) is KuzuQueryResult
assert typeOf( q ) is LbugQueryResult
q = conn.query( "MATCH (d:Doop) RETURN d.thing" )
assert q.num_tuples == 0

View file

@ -1,12 +1,12 @@
# vim: set et sta sw=4 ts=4 :
import kuzu
import lbug
let db = newKuzuDatabase()
let db = newLbugDatabase()
let conn = db.connect
var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, thing STRING, PRIMARY KEY(id) )" )
assert typeOf( q ) is KuzuQueryResult
assert typeOf( q ) is LbugQueryResult
q = conn.query( "CREATE (d:Doop {thing: 'okay!'})" )
q = conn.query( "MATCH (d:Doop) RETURN d.id AS IDENTIFIER, d.thing AS THING" )

View file

@ -1,17 +1,17 @@
# vim: set et sta sw=4 ts=4 :
import kuzu
import lbug
let db = newKuzuDatabase()
let db = newLbugDatabase()
let conn = db.connect
var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, thing STRING, PRIMARY KEY(id) )" )
assert typeOf( q ) is KuzuQueryResult
assert typeOf( q ) is LbugQueryResult
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"
assert $q.column_types[0] == "LBUG_SERIAL"
assert $q.column_types[1] == "LBUG_STRING"

View file

@ -2,13 +2,13 @@
import
std/re
import kuzu
import lbug
let db = newKuzuDatabase()
let db = newLbugDatabase()
let conn = db.connect
try:
discard conn.query( "NOPE NOPE NOPE" )
except KuzuQueryError as err:
except LbugQueryError as err:
assert err.msg.contains( re"""Parser exception: extraneous input 'NOPE'""" )

View file

@ -2,30 +2,30 @@
import
std/re
import kuzu
import lbug
let db = newKuzuDatabase()
let db = newLbugDatabase()
let conn = db.connect
var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, created DATE, PRIMARY KEY(id) )" )
assert typeOf( q ) is KuzuQueryResult
assert typeOf( q ) is LbugQueryResult
var p = conn.prepare( "CREATE (d:Doop {created: $created})" )
assert typeOf( p ) is KuzuPreparedStatement
assert typeOf( p ) is LbugPreparedStatement
# 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.""" )
except LbugQueryError as err:
assert err.msg.contains( re"""Conversion exception: Error occurred during parsing 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:
except LbugQueryError as err:
assert err.msg.contains( re"""Given: "1111-1111". Expected format: \(YYYY-MM-DD\)""" )

View file

@ -2,19 +2,19 @@
import
std/re
import kuzu
import ladybug
let db = newKuzuDatabase()
let db = newLbugDatabase()
let conn = db.connect
var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, thing STRING, PRIMARY KEY(id) )" )
var p = conn.prepare( "CREATE (d:Doop {thing: $thing})" )
assert typeOf( p ) is KuzuPreparedStatement
assert typeOf( p ) is LbugPreparedStatement
try:
discard p.execute( (nope: "undefined var in statement!") )
except KuzuQueryError as err:
discard p.execute( (nope: "undefined var in statement!", thing: "yep") )
except LbugQueryError as err:
assert err.msg.contains( re"""Parameter nope not found.""" )

View file

@ -2,19 +2,19 @@
import
std/re
import kuzu
import lbug
let db = newKuzuDatabase()
let db = newLbugDatabase()
let conn = db.connect
var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, thing STRING, PRIMARY KEY(id) )" )
var p = conn.prepare( "CREAET (d:Doop {thing: $thing})" )
assert typeOf( p ) is KuzuPreparedStatement
assert typeOf( p ) is LbugPreparedStatement
try:
discard p.execute
except KuzuQueryError as err:
except LbugQueryError as err:
assert err.msg.contains( re"""Parser exception: extraneous input 'CREAET'""" )

View file

@ -4,9 +4,9 @@ discard """
output: "okay!"
"""
import kuzu
import lbug
let db = newKuzuDatabase()
let db = newLbugDatabase()
let conn = db.connect
var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, thing STRING, PRIMARY KEY(id) )" )

View file

@ -2,9 +2,9 @@
import
std/re
import kuzu
import lbug
let db = newKuzuDatabase()
let db = newLbugDatabase()
let conn = db.connect
var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, thing STRING, PRIMARY KEY(id) )" )
@ -12,6 +12,6 @@ q = conn.query( "MATCH (d:Doop) RETURN d.thing" )
try:
discard q.getNext
except KuzuIterationError as err:
except LbugIterationError as err:
assert err.msg.contains( re"""Query iteration past end.""" )

View file

@ -2,9 +2,9 @@
import
std/re
import kuzu
import lbug
let db = newKuzuDatabase()
let db = newLbugDatabase()
let conn = db.connect
var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, thing STRING, PRIMARY KEY(id) )" )
@ -15,6 +15,6 @@ let tup = q.getNext
try:
echo tup[22]
except KuzuIndexError as err:
except LbugIndexError as err:
assert err.msg.contains( re"""Unable to fetch tuple value at idx 22.""" )

View file

@ -4,9 +4,9 @@ discard """
output: "okay!"
"""
import kuzu
import lbug
let db = newKuzuDatabase()
let db = newLbugDatabase()
let conn = db.connect
var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, thing STRING, PRIMARY KEY(id) )" )

View file

@ -1,8 +1,8 @@
# vim: set et sta sw=4 ts=4 :
import kuzu
import lbug
let db = newKuzuDatabase()
let db = newLbugDatabase()
let conn = db.connect
var q = conn.query( """CREATE NODE TABLE Doop (

View file

@ -1,8 +1,8 @@
# vim: set et sta sw=4 ts=4 :
import kuzu
import lbug
var db = newKuzuDatabase()
var db = newLbugDatabase()
var conn = db.connect
var q = conn.query( "CREATE NODE TABLE Doot ( id SERIAL, data BLOB, PRIMARY KEY(id) )" )
@ -16,12 +16,12 @@ q = conn.query( "MATCH (d:Doot) RETURN d.data" )
var expected: seq[byte] = @[188, 189, 186, 170]
var val = q.getNext[0]
assert val.kind == KUZU_BLOB
assert val.kind == LBUG_BLOB
assert val.toBlob == expected
expected = @[72, 101, 108, 108, 111, 33]
val = q.getNext[0]
assert val.kind == KUZU_BLOB
assert val.kind == LBUG_BLOB
assert val.toBlob == expected
var str: string

View file

@ -1,39 +1,39 @@
# vim: set et sta sw=4 ts=4 :
import kuzu
import lbug
let db = newKuzuDatabase()
let db = newLbugDatabase()
let conn = db.connect
var q = conn.query( "RETURN [1,2,3,4,5] AS list" )
var list = q.getNext[0]
assert list.kind == KUZU_LIST
assert list.kind == LBUG_LIST
var items = list.toSeq
assert items.len == 5
assert typeOf( items ) is seq[KuzuValue]
assert typeOf( items ) is seq[LbugValue]
for i in items:
assert( i.kind == KUZU_INT64 )
assert( i.kind == LBUG_INT64 )
q = conn.query( """RETURN ["woo", "hoo"] AS list""" )
list = q.getNext[0]
assert list.kind == KUZU_LIST
assert list.kind == LBUG_LIST
items = list.toSeq
assert items.len == 2
assert typeOf( items ) is seq[KuzuValue]
assert typeOf( items ) is seq[LbugValue]
for i in items:
assert( i.kind == KUZU_STRING )
assert( i.kind == LBUG_STRING )
q = conn.query( """RETURN [] AS list""" )
list = q.getNext[0]
assert list.kind == KUZU_LIST
assert list.kind == LBUG_LIST
items = list.toList
assert items.len == 0
assert typeOf( items ) is seq[KuzuValue]
assert typeOf( items ) is seq[LbugValue]

View file

@ -1,14 +1,14 @@
# vim: set et sta sw=4 ts=4 :
import kuzu
import lbug
let db = newKuzuDatabase()
let db = newLbugDatabase()
let conn = db.connect
var q = conn.query( """RETURN 12""" )
try:
discard q.getNext[0].toStruct
except KuzuTypeError:
except LbugTypeError:
discard

View file

@ -1,8 +1,8 @@
# vim: set et sta sw=4 ts=4 :
import kuzu
import lbug
let db = newKuzuDatabase()
let db = newLbugDatabase()
let conn = db.connect
var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, thing STRING, PRIMARY KEY(id) )" )
@ -15,7 +15,7 @@ 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
assert id.kind == LBUG_INT64
assert thing.kind == LBUG_STRING
assert node.kind == LBUG_NODE

View file

@ -2,9 +2,9 @@
import
std/re
import kuzu
import lbug
let db = newKuzuDatabase()
let db = newLbugDatabase()
let conn = db.connect
var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, thing STRING, PRIMARY KEY(id) )" )
@ -14,12 +14,12 @@ q = conn.query( "MATCH (d:Doop) RETURN d" )
var tup = q.getNext
var val = tup[0]
assert val.kind == KUZU_NODE
assert val.kind == LBUG_NODE
try:
discard val.toInt32
except KuzuTypeError as err:
assert err.msg.contains( re"""Mismatched types: KUZU_NODE != {KUZU_INT32}""" )
except LbugTypeError as err:
assert err.msg.contains( re"""Mismatched types: LBUG_NODE != {LBUG_INT32}""" )
q = conn.query( "RETURN 1" )
@ -27,7 +27,7 @@ val = q.getNext[0]
try:
discard val.toStruct
except KuzuTypeError as err:
assert err.msg.contains( re"""Mismatched types: KUZU_INT.* != {KUZU_NODE, KUZU_REL,.*}""" )
except LbugTypeError as err:
assert err.msg.contains( re"""Mismatched types: LBUG_INT.* != {LBUG_NODE, LBUG_REL,.*}""" )

View file

@ -2,9 +2,9 @@
import
std/re
import kuzu
import lbug
let db = newKuzuDatabase()
let db = newLbugDatabase()
let conn = db.connect
var q = conn.query( """RETURN {test1: 1, test2: "bewts"} AS struct""" )
@ -15,7 +15,7 @@ assert struct.keys == @["test1", "test2"]
try:
discard struct["nope"]
except KuzuIndexError as err:
except LbugIndexError as err:
assert err.msg.contains( re"""No such struct key "nope"""" )