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> ## v0.6.1 [2025-07-19] Mahlon E. Smith <mahlon@martini.nu>

View file

@ -1,18 +1,18 @@
# Nim Kuzu # Nim Ladybug
home home
: https://code.martini.nu/mahlon/nim-kuzu : https://code.martini.nu/mahlon/nim-ladybug
github_mirror github_mirror
: https://github.com/mahlonsmith/nim-kuzu : https://github.com/mahlonsmith/nim-ladybug
## Description ## 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 optimized for handling complex join-heavy analytical workloads on very large
graphs, with the following core feature set: graphs, with the following core feature set:
@ -25,22 +25,21 @@ graphs, with the following core feature set:
- Multi-core query parallelism - Multi-core query parallelism
- Serializable ACID transactions - Serializable ACID transactions
For more information about Kuzu itself, see its For more information about Ladybug itself, see its
[documentation](https://docs.kuzudb.com/). [documentation](https://docs.ladybugdb.com/).
## Prerequisites ## Prerequisites
* A functioning Nim >= 2 installation * A functioning Nim >= 2 installation
- [KuzuDB](https://kuzudb.com) to be locally installed! - [LadybugDB](https://ladybugdb.com) to be locally installed!
## Installation ## Installation
$ nimble install kuzu $ nimble install ladybug
The current version of this library is built for Kuzu v0.11.3, which sadly The current version of this library is built for Ladybug v0.12.0.
seems may have been the final release of kuzudb. :(
## Usage ## Usage
@ -53,11 +52,11 @@ You can also find a bunch of working examples in the tests.
## Contributing ## Contributing
You can check out the current development source via Git/Jujutsu at its You can check out the current development source via Git/Jujutsu at its
[home repo](https://code.martini.nu/mahlon/nim-kuzu), or the [home repo](https://code.martini.nu/mahlon/nim-ladybug), or the
[project mirror](https://github.com/mahlonsmith/nim-kuzu). [project mirror](https://github.com/mahlonsmith/nim-ladybug).
After checking out the source, uncomment the development dependencies 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 $ nimble setup
@ -71,4 +70,5 @@ development.
- Mahlon E. Smith <mahlon@martini.nu> - Mahlon E. Smith <mahlon@martini.nu>
A note of thanks to @mantielero on Github, who has a Kuzu binding for an early 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 ## Prior Reading
If you're just starting with Kuzu or graph databases, it's probably a good idea If you're just starting with Ladybug or graph databases, it's probably a good idea
to familiarize yourself with the [Kuzu Documentation](https://docs.kuzudb.com/) to familiarize yourself with the [Ladybug Documentation](https://docs.ladybugdb.com/)
and the [Cypher Language](https://docs.kuzudb.com/tutorials/cypher/). This 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 Kuzu usage. library won't do much for you by itself without a basic understanding of Ladybug usage.
## Checking Compatibility ## 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 shared library. As such, the version of this library might not match with what
you currently have installed. you currently have installed.
Check the [README](README.md), the [History](History.md), and the following Check the [README](README.md), the [History](History.md), and the following
table to ensure you're using the correct version for your Kuzu table to ensure you're using the correct version for your Ladybug
installation. I'll make a modest effort for backwards compatibility while Kuzu 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 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. 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 | | Kuzu Library Version | Nim Kuzu Minimum Version |
| -------------------- | ------------------------ | | -------------------- | ------------------------ |
| v0.8.2 | v0.1.0 | | 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.10.0 | v0.4.0 |
| v0.11.0 | v0.5.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. are looking right.
```nim ```nim
import kuzu import ladybug
echo KUZU_VERSION #=> "0.1.0" echo LBUG_VERSION #=> "0.7.0"
echo kuzuGetVersion() #=> "0.8.2" echo lbugGetVersion() #=> "0.12.0"
echo kuzuVersionCompatible() #=> true 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 ## 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 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 -- it will create an empty database if the path is currently non-existent, or
open an existing database otherwise. open an existing database otherwise.
```nim ```nim
# "db" is in-memory and will evaporate when the process ends. # "db" is in-memory and will evaporate when the process ends.
var db = newKuzuDatabase() var db = newLbugDatabase()
``` ```
```nim ```nim
# "db" is persistent, stored in the file "data.kz". # "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`. The database path is retained, and can be recalled via `db.path`.
@ -86,19 +105,19 @@ if db.config.enable_compression:
echo "Yes!" echo "Yes!"
``` ```
You can alter configuration options when connecting by passing a `kuzuConfig` You can alter configuration options when connecting by passing a `lbugConfig`
object as the second argument to `newKuzuDatabase()`: object as the second argument to `newLbugDatabase()`:
```nim ```nim
# Open a readonly handle. # Open a readonly handle.
var db = newKuzuDatabase( "data.kz", kuzuConfig( read_only=true ) ) var db = newLbugDatabase( "data.kz", lbugConfig( read_only=true ) )
``` ```
### The Connection ### The Connection
All interaction with the database is performed via a connection object. There All interaction with the database is performed via a connection object. There
are limitations to database handles and connection objects -- see the 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: 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 You can perform a basic query via the appropriately named `query()` function on
the connection. Via this method, queries are run immediately. A 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. 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: headers and all tuple results:
```nim ```nim
@ -154,7 +173,7 @@ echo res.execution_time #=> 1.624
assert res.column_names == @["hi", "pin", "list"] assert res.column_names == @["hi", "pin", "list"]
# Return the column data types as a sequence. # 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 ### Prepared Statements
@ -172,7 +191,7 @@ RETURN
[1,2,3] AS list [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() var res = stmt.execute()
``` ```
@ -198,13 +217,13 @@ echo $res #=>
#### Type Conversion #### Type Conversion
When binding variables to a prepared statement, most Nim types are automatically 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 ```nim
var stmt = conn.prepare( """RETURN $num AS num""" ) var stmt = conn.prepare( """RETURN $num AS num""" )
var res = stmt.execute( (num: 12) ) 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 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 ```nim
var stmt = conn.prepare( """RETURN $num AS num""" ) var stmt = conn.prepare( """RETURN $num AS num""" )
var res: KuzuQueryResult var res: LbugQueryResult
res = stmt.execute( (num: 12'u64) ) 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) ) 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 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. 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 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! 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. the content, and it works with the majority of types.
An extended example: An extended example:
```nim ```nim
import std/sequtils import std/sequtils
import kuzu import lbug
var db = newKuzuDatabase() var db = newLbugDatabase()
var conn = db.connect var conn = db.connect
var res: KuzuQueryResult var res: LbugQueryResult
# Create a node table. # 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(( res = stmt.execute((
num: 2, num: 2,
@ -295,26 +314,26 @@ echo $res #=>
# e.id|e.num|e.done|e.comment|e.karma|e.thing|e.created|e.activity # 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 # 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 ): for pair in res.column_names.zip( res.column_types ):
echo pair #=> echo pair #=>
# ("e.id", KUZU_SERIAL) # ("e.id", LBUG_SERIAL)
# ("e.num", KUZU_UINT8) # ("e.num", LBUG_UINT8)
# ("e.done", KUZU_BOOL) # ("e.done", LBUG_BOOL)
# ("e.comment", KUZU_STRING) # ("e.comment", LBUG_STRING)
# ("e.karma", KUZU_DOUBLE) # ("e.karma", LBUG_DOUBLE)
# ("e.thing", KUZU_UUID) # ("e.thing", LBUG_UUID)
# ("e.created", KUZU_DATE) # ("e.created", LBUG_DATE)
# ("e.activity", KUZU_TIMESTAMP) # ("e.activity", LBUG_TIMESTAMP)
``` ```
## Reading Results ## 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. 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 A `LbugQueryResult` is an iterator. You can use regular Nim functions that yield
each `KuzuFlatTuple` -- essentially, each row that was returned in a result set. each `LbugFlatTuple` -- essentially, each row that was returned in a result set.
```nim ```nim
var res = conn.query """ var res = conn.query """
@ -323,7 +342,7 @@ var res = conn.query """
RETURN items, thing 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: for row in res:
echo row #=> echo row #=>
# 1|thing # 1|thing
@ -333,7 +352,7 @@ for row in res:
Once iteration has reached the end, it is automatically rewound for reuse. 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 `getNext()` after the last row results in an error. Use `hasNext()` to check
before calling. before calling.
@ -350,23 +369,23 @@ if res.hasNext:
echo res.getNext #=> 2 echo res.getNext #=> 2
echo res.getNext #=> 3 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 ## Multiple Query Results
A query can potentially return any number of separate statements. In the case 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 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: remaining:
```nim ```nim
import kuzu import lbug
let db = newKuzuDatabase() let db = newLbugDatabase()
let conn = db.connect let conn = db.connect
let query = conn.query """ let query = conn.query """
@ -400,8 +419,8 @@ for set in query.sets:
## Working with Values ## Working with Values
A `KuzuFlatTuple` contains the entire row. You can index a value at its column A `LbugFlatTuple` contains the entire row. You can index a value at its column
position, returning a `KuzuValue`. position, returning a `LbugValue`.
```nim ```nim
var res = conn.query """ var res = conn.query """
@ -419,27 +438,27 @@ var row = res.getNext
for idx in ( 0 .. res.num_columns-1 ): for idx in ( 0 .. res.num_columns-1 ):
var value = row[idx] var value = row[idx]
echo res.column_names[idx], ": ", value, " (", value.kind, ")" #=> echo res.column_names[idx], ": ", value, " (", value.kind, ")" #=>
# num: 1 (KUZU_INT64) # num: 1 (LBUG_INT64)
# done: True (KUZU_BOOL) # done: True (LBUG_BOOL)
# comment: A comment (KUZU_STRING) # comment: A comment (LBUG_STRING)
# karma: 12.840000 (KUZU_DOUBLE) # karma: 12.840000 (LBUG_DOUBLE)
# thing: b41deae0-dddf-430b-981d-3fb93823e495 (KUZU_UUID) # thing: b41deae0-dddf-430b-981d-3fb93823e495 (LBUG_UUID)
# created: 2025-03-29 (KUZU_DATE) # created: 2025-03-29 (LBUG_DATE)
``` ```
### Types ### 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. check what type it is via the 'kind' property.
```nim ```nim
var res = conn.query """RETURN "hello"""" var res = conn.query """RETURN "hello""""
var value = res.getNext[0] 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: convert it for regular Nim usage:
```nim ```nim
@ -455,12 +474,12 @@ assert value.toInt64 + 1 == 2561
### Lists ### Lists
A `KuzuValue` of type `KUZU_LIST` can be converted to a Nim sequence of A `LbugValue` of type `LBUG_LIST` can be converted to a Nim sequence of
`KuzuValues` with the `toList()` function: `LbugValues` with the `toList()` function:
```nim ```nim
import std/sequtils import std/sequtils
import kuzu import lbug
var res = conn.query """ var res = conn.query """
RETURN [10, 20, 30] RETURN [10, 20, 30]
@ -471,16 +490,16 @@ var value = res.getNext[0]
var list = value.toList var list = value.toList
echo list #=> @[10,20,30] 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 ### Struct-like Objects
Various Kuzu types can act like a struct - this includes `KUZU_NODE`, Various Ladybug types can act like a struct - this includes `LBUG_NODE`,
`KUZU_REL`, and of course an explicit `KUZU_STRUCT` itself, among others. `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()`. convenience, this is also aliased to `toNode()` and `toRel()`.
Once converted, you can access struct values by passing the key name to `[]`: 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 import
std/sequtils, std/sequtils,
std/strformat std/strformat
import kuzu import lbug
var db = newKuzuDatabase() var db = newLbugDatabase()
var conn = db.connect var conn = db.connect
var res = conn.query """ var res = conn.query """
@ -540,7 +559,7 @@ res = conn.query """
# #
for row in res: for row in res:
var since = row[0] 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}.""" #=> echo &"""{people[0]["name"]} has known {people[1]["name"]} since {since}.""" #=>
# Bob has known Bruce since 2003. # Bob has known Bruce since 2003.
# Bob has known Alice since 2009. # Bob has known Alice since 2009.
@ -550,7 +569,7 @@ for row in res:
### Blobs ### 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. using `toBlob` will return the raw sequence of bytes.
```nim ```nim

View file

@ -1,8 +1,8 @@
# vim: set et sta sw=4 ts=4 : # vim: set et sta sw=4 ts=4 :
version = "0.6.1" version = "0.7.0"
author = "Mahlon E. Smith" 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" license = "BSD-3-Clause"
srcDir = "src" srcDir = "src"
@ -13,7 +13,7 @@ requires "nim ^= 2.0.0"
#requires "zip ^= 0.3.1" #requires "zip ^= 0.3.1"
task makewrapper, "Generate the C wrapper using Futhark": 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.": task test, "Run the test suite.":
exec "testament --megatest:off all" 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 README.md"
exec "nim md2html --project --outdir:docs History.md" exec "nim md2html --project --outdir:docs History.md"
exec "nim md2html --project --outdir:docs USAGE.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 : # 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. ## Graceful cleanup for an open DB handle when it goes out of scope.
if db.valid: if db.valid:
when defined( debug ): echo &"Destroying database: {db}" 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 ## Perform basic validity checks against an existing on disk database
## for better error messaging. ## for better error messaging.
@ -21,20 +21,20 @@ proc validateDatabase( db: KuzuDatabase ): void =
let magic = buf[0..3].join let magic = buf[0..3].join
let storage_version = buf[4].uint let storage_version = buf[4].uint
if magic != "KUZU": if magic != "LBUG":
raise newException( KuzuException, "Unable to open database: " & raise newException( LbugException, "Unable to open database: " &
&""""{db.path}" Doesn't appear to be a Kuzu file.""" ) &""""{db.path}" Doesn't appear to be a LadybugDB file.""" )
if storageVersion != kuzuGetStorageVersion(): if storageVersion != lbugGetStorageVersion():
raise newException( KuzuException, "Unable to open database: " & raise newException( LbugException, "Unable to open database: " &
&" mismatched storage versions - file is {storageVersion}, expected {kuzuGetStorageVersion()}." ) &" mismatched storage versions - file is {storageVersion}, expected {lbugGetStorageVersion()}." )
proc newKuzuDatabase*( path="", config=kuzuConfig() ): KuzuDatabase = proc newlbugDatabase*( path="", config=lbugConfig() ): LbugDatabase =
## Create a new Kuzu database handle. Creates an in-memory ## Create a new Lbug 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 LbugDatabase
result.config = config result.config = config
if path != "" and path != ":memory:": if path != "" and path != ":memory:":
@ -44,13 +44,13 @@ proc newKuzuDatabase*( path="", config=kuzuConfig() ): KuzuDatabase =
result.path = "(in-memory)" result.path = "(in-memory)"
result.kind = memory result.kind = memory
result.handle = kuzu_database() result.handle = lbug_database()
if result.kind == disk: if result.kind == disk:
result.validateDatabase() 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 result.valid = true
else: 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 : # vim: set et sta sw=4 ts=4 :
import kuzu import lbug
let db = newKuzuDatabase() let db = newLbugDatabase()
assert db.path == "(in-memory)" 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 : # vim: set et sta sw=4 ts=4 :
import kuzu import lbug
let db = newKuzuDatabase() let db = newLbugDatabase()
let conn = db.connect let conn = db.connect
# FIXME: This test should really perform some # FIXME: This test should really perform some

View file

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

View file

@ -1,6 +1,6 @@
# vim: set et sta sw=4 ts=4 : # 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 : # vim: set et sta sw=4 ts=4 :
import re 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 : # vim: set et sta sw=4 ts=4 :
import re import re
import kuzu import lbug
let version = $kuzuGetVersion() let version = $lbugGetVersion()
assert version.contains( re"^\d+\.\d+\.\d+(?:\.\d+)?$" ) assert version.contains( re"^\d+\.\d+\.\d+(?:\.\d+)?$" )

View file

@ -1,6 +1,6 @@
# vim: set et sta sw=4 ts=4 : # 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 : # 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.path == "(in-memory)"
assert db.kind == memory assert db.kind == memory

View file

@ -4,12 +4,12 @@ import
std/files, std/files,
std/paths std/paths
import kuzu import lbug
const DATABASE_PATH = Path( "tmp/testdb" ) const DATABASE_PATH = Path( "tmp/testdb" )
DATABASE_PATH.removeFile() 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.path == "tmp/testdb"
assert db.config.auto_checkpoint == false assert db.config.auto_checkpoint == false

View file

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

View file

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

View file

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

View file

@ -4,19 +4,19 @@ discard """
output: "d.thing\nCamel\nLampshade\nDelicious Cake\n" output: "d.thing\nCamel\nLampshade\nDelicious Cake\n"
""" """
import kuzu import lbug
let db = newKuzuDatabase() let db = newLbugDatabase()
let conn = db.connect let conn = db.connect
var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, thing STRING, PRIMARY KEY(id) )" ) var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, thing STRING, PRIMARY KEY(id) )" )
var p = conn.prepare( "CREATE (d:Doop {thing: $thing})" ) 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" ]: for thing in @[ "Camel", "Lampshade", "Delicious Cake" ]:
q = p.execute( (thing: thing) ) q = p.execute( (thing: thing) )
assert typeOf( q ) is KuzuQueryResult assert typeOf( q ) is LbugQueryResult
# Fixed post v0.8.2: # Fixed post v0.8.2:
# https://github.com/kuzudb/kuzu/issues/5102 # 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" 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 let conn = db.connect
var q = conn.query( """CREATE NODE TABLE Doop ( var q = conn.query( """CREATE NODE TABLE Doop (
@ -21,7 +21,7 @@ var q = conn.query( """CREATE NODE TABLE Doop (
date DATE, date DATE,
PRIMARY KEY(id) PRIMARY KEY(id)
)""" ) )""" )
assert typeOf( q ) is KuzuQueryResult assert typeOf( q ) is LbugQueryResult
var stmt = conn.prepare( """CREATE (d:Doop { var stmt = conn.prepare( """CREATE (d:Doop {
@ -34,7 +34,7 @@ var stmt = conn.prepare( """CREATE (d:Doop {
uuid: UUID($uuid), uuid: UUID($uuid),
date: DATE($date) date: DATE($date)
})""" ) })""" )
assert typeOf( stmt ) is KuzuPreparedStatement assert typeOf( stmt ) is LbugPreparedStatement
q = stmt.execute(( q = stmt.execute((
@ -47,7 +47,7 @@ q = stmt.execute((
uuid: "A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11", uuid: "A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11",
date: "2025-03-29" date: "2025-03-29"
)) ))
assert typeOf( q ) is KuzuQueryResult assert typeOf( q ) is LbugQueryResult
q = conn.query( "MATCH (d:Doop) RETURN d.*" ) q = conn.query( "MATCH (d:Doop) RETURN d.*" )

View file

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

View file

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

View file

@ -4,9 +4,9 @@ discard """
output: "Camel\nLampshade\nDelicious Cake\n" output: "Camel\nLampshade\nDelicious Cake\n"
""" """
import kuzu import lbug
let db = newKuzuDatabase() let db = newLbugDatabase()
let conn = db.connect let conn = db.connect
var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, thing STRING, PRIMARY KEY(id) )" ) 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" output: "Camel\nLampshade\nCamel\nLampshade\n"
""" """
import kuzu import lbug
let db = newKuzuDatabase() let db = newLbugDatabase()
let conn = db.connect let conn = db.connect
var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, thing STRING, PRIMARY KEY(id) )" ) 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" output: "d.thing\nokay!\n\n"
""" """
import kuzu import lbug
let db = newKuzuDatabase() let db = newLbugDatabase()
let conn = db.connect let conn = db.connect
var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, thing STRING, PRIMARY KEY(id) )" ) 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 : # vim: set et sta sw=4 ts=4 :
import kuzu import lbug
let db = newKuzuDatabase() let db = newLbugDatabase()
let conn = db.connect let conn = db.connect
var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, thing STRING, PRIMARY KEY(id) )" ) 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" ) q = conn.query( "MATCH (d:Doop) RETURN d.thing" )
assert q.num_tuples == 0 assert q.num_tuples == 0

View file

@ -1,12 +1,12 @@
# vim: set et sta sw=4 ts=4 : # vim: set et sta sw=4 ts=4 :
import kuzu import lbug
let db = newKuzuDatabase() let db = newLbugDatabase()
let conn = db.connect let conn = db.connect
var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, thing STRING, PRIMARY KEY(id) )" ) 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( "CREATE (d:Doop {thing: 'okay!'})" )
q = conn.query( "MATCH (d:Doop) RETURN d.id AS IDENTIFIER, d.thing AS THING" ) 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 : # vim: set et sta sw=4 ts=4 :
import kuzu import lbug
let db = newKuzuDatabase() let db = newLbugDatabase()
let conn = db.connect let conn = db.connect
var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, thing STRING, PRIMARY KEY(id) )" ) 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( "CREATE (d:Doop {thing: 'okay!'})" )
q = conn.query( "MATCH (d:Doop) RETURN d.id AS IDENTIFIER, d.thing AS THING" ) 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.len == 2
assert $q.column_types[0] == "KUZU_SERIAL" assert $q.column_types[0] == "LBUG_SERIAL"
assert $q.column_types[1] == "KUZU_STRING" assert $q.column_types[1] == "LBUG_STRING"

View file

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

View file

@ -2,30 +2,30 @@
import import
std/re std/re
import kuzu import lbug
let db = newKuzuDatabase() let db = newLbugDatabase()
let conn = db.connect let conn = db.connect
var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, created DATE, PRIMARY KEY(id) )" ) 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})" ) var p = conn.prepare( "CREATE (d:Doop {created: $created})" )
assert typeOf( p ) is KuzuPreparedStatement assert typeOf( p ) is LbugPreparedStatement
# Typecast binding failure # Typecast binding failure
# #
try: try:
discard p.execute( (created: "1111-1111") ) discard p.execute( (created: "1111-1111") )
except KuzuQueryError as err: except LbugQueryError as err:
assert err.msg.contains( re"""Expression \$created has data type STRING but expected DATE.""" ) assert err.msg.contains( re"""Conversion exception: Error occurred during parsing date.""" )
# Invalid value for typecast # Invalid value for typecast
# #
p = conn.prepare( "CREATE (d:Doop {created: DATE($created)})" ) p = conn.prepare( "CREATE (d:Doop {created: DATE($created)})" )
try: try:
discard p.execute( (created: "1111-1111") ) 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\)""" ) assert err.msg.contains( re"""Given: "1111-1111". Expected format: \(YYYY-MM-DD\)""" )

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,9 +4,9 @@ discard """
output: "okay!" output: "okay!"
""" """
import kuzu import lbug
let db = newKuzuDatabase() let db = newLbugDatabase()
let conn = db.connect let conn = db.connect
var q = conn.query( "CREATE NODE TABLE Doop ( id SERIAL, thing STRING, PRIMARY KEY(id) )" ) 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 : # vim: set et sta sw=4 ts=4 :
import kuzu import lbug
let db = newKuzuDatabase() let db = newLbugDatabase()
let conn = db.connect let conn = db.connect
var q = conn.query( """CREATE NODE TABLE Doop ( var q = conn.query( """CREATE NODE TABLE Doop (

View file

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

View file

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

View file

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

View file

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

View file

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