2025-03-31 19:35:15 +00:00
|
|
|
|
|
|
|
|
# Usage
|
|
|
|
|
|
|
|
|
|
This document is a quick guide for how to use this library. If you've cloned
|
|
|
|
|
this repository, you can:
|
|
|
|
|
|
|
|
|
|
> % nimble docs
|
|
|
|
|
|
2025-03-31 19:38:09 +00:00
|
|
|
... to auto-generate API docs -- unfortunately, with all the C wrappers, there's
|
|
|
|
|
a lot and it's hard to know where to start.
|
2025-03-31 19:35:15 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
## Prior Reading
|
|
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
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.
|
2025-03-31 19:35:15 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
## Checking Compatibility
|
|
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
This is a wrapper (with some additional niceties) for the system-installed Ladybug
|
2025-03-31 19:35:15 +00:00
|
|
|
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
|
2025-11-04 09:06:11 -08:00
|
|
|
table to ensure you're using the correct version for your Ladybug
|
|
|
|
|
installation. I'll make a modest effort for backwards compatibility while Ladybug
|
2025-05-27 18:31:59 +00:00
|
|
|
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.
|
2025-03-31 19:35:15 +00:00
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
Ladybug was continued from the KuzuDB project, which was hastily abandoned in
|
|
|
|
|
October of 2025. Previous versions used a "Kuzu" namespace.
|
|
|
|
|
|
|
|
|
|
|
2025-04-26 21:12:45 +00:00
|
|
|
| Kuzu Library Version | Nim Kuzu Minimum Version |
|
|
|
|
|
| -------------------- | ------------------------ |
|
|
|
|
|
| v0.8.2 | v0.1.0 |
|
|
|
|
|
| v0.9.0 | v0.2.0 |
|
2025-05-09 04:12:44 +00:00
|
|
|
| v0.10.0 | v0.4.0 |
|
2025-07-13 13:10:33 -07:00
|
|
|
| v0.11.0 | v0.5.0 |
|
2025-03-31 19:35:15 +00:00
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
|
|
|
|
|
| Ladybug Library Version | Nim Ladybug Minimum Version |
|
|
|
|
|
| ----------------------- | --------------------------- |
|
|
|
|
|
| v0.12.0 | v0.7.0 |
|
2025-11-15 16:38:21 -08:00
|
|
|
| v0.12.2 | v0.8.0 |
|
2025-11-04 09:06:11 -08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
You can use the `lbugVersionCompatible()` function (along with the
|
|
|
|
|
`lbugGetVersion()` and the `LBUG_VERSION` constant) to quickly check if things
|
2025-03-31 23:15:45 +00:00
|
|
|
are looking right.
|
2025-03-31 19:35:15 +00:00
|
|
|
|
|
|
|
|
```nim
|
2025-11-04 09:06:11 -08:00
|
|
|
import ladybug
|
2025-03-31 19:35:15 +00:00
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
echo LBUG_VERSION #=> "0.7.0"
|
|
|
|
|
echo lbugGetVersion() #=> "0.12.0"
|
|
|
|
|
echo lbugVersionCompatible() #=> true
|
2025-03-31 19:35:15 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
## 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.
|
|
|
|
|
|
|
|
|
|
|
2025-03-31 19:35:15 +00:00
|
|
|
## Connecting to a Database
|
|
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
Just call `newLbugDatabase()`. Without an argument (or with an empty string),
|
2025-03-31 19:35:15 +00:00
|
|
|
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.
|
2025-11-04 09:06:11 -08:00
|
|
|
var db = newLbugDatabase()
|
2025-03-31 19:35:15 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```nim
|
2025-11-04 18:18:03 -08:00
|
|
|
# "db" is persistent, stored in the file "data.db".
|
|
|
|
|
var db = newLbugDatabase("data.db")
|
2025-03-31 19:35:15 +00:00
|
|
|
```
|
|
|
|
|
The database path is retained, and can be recalled via `db.path`.
|
|
|
|
|
|
|
|
|
|
```nim
|
2025-11-04 18:18:03 -08:00
|
|
|
db.path #=> "data.db"
|
2025-03-31 19:35:15 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Database Configuration
|
|
|
|
|
|
|
|
|
|
The database is configured with default options by default. You can see them
|
|
|
|
|
via:
|
|
|
|
|
|
|
|
|
|
```nim
|
|
|
|
|
echo $db.config
|
|
|
|
|
#=> (buffer_pool_size: 23371415552, max_num_threads: 16, ...
|
|
|
|
|
|
|
|
|
|
# Is compression enabled?
|
|
|
|
|
if db.config.enable_compression:
|
2025-03-31 19:43:49 +00:00
|
|
|
echo "Yes!"
|
2025-03-31 19:35:15 +00:00
|
|
|
```
|
|
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
You can alter configuration options when connecting by passing a `lbugConfig`
|
|
|
|
|
object as the second argument to `newLbugDatabase()`:
|
2025-03-31 19:35:15 +00:00
|
|
|
|
|
|
|
|
```nim
|
|
|
|
|
# Open a readonly handle.
|
2025-11-04 18:18:03 -08:00
|
|
|
var db = newLbugDatabase( "data.db", lbugConfig( read_only=true ) )
|
2025-03-31 19:35:15 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### The Connection
|
|
|
|
|
|
|
|
|
|
All interaction with the database is performed via a connection object. There
|
|
|
|
|
are limitations to database handles and connection objects -- see the
|
2025-11-04 09:06:11 -08:00
|
|
|
[Lbug Concurrency](https://docs.ladybugdb.com/concurrency/) docs for details!
|
2025-03-31 19:35:15 +00:00
|
|
|
|
|
|
|
|
Call `connect` on an open database handle to create a new connection:
|
|
|
|
|
|
|
|
|
|
```nim
|
|
|
|
|
var conn = db.connect
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
You can set a maximum query lifetime, and interrupt any running queries (thread
|
|
|
|
|
shutdown, ctrl-c, etc):
|
|
|
|
|
|
|
|
|
|
```nim
|
|
|
|
|
# Set a maximum ceiling on how long a query can run, in milliseconds.
|
|
|
|
|
conn.queryTimeout( 10 * 1000 ) # 10 seconds
|
|
|
|
|
|
|
|
|
|
# Cancel a running query.
|
|
|
|
|
conn.queryInterrupt()
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Performing Queries
|
|
|
|
|
|
|
|
|
|
You can perform a basic query via the appropriately named `query()` function on
|
|
|
|
|
the connection. Via this method, queries are run immediately. A
|
2025-11-04 09:06:11 -08:00
|
|
|
`LbugQueryResult` is returned - this is the object you'll be interacting with to
|
2025-03-31 19:35:15 +00:00
|
|
|
see results.
|
|
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
A `LbugQueryResult` can be turned into a string to quickly see the column
|
2025-03-31 19:35:15 +00:00
|
|
|
headers and all tuple results:
|
|
|
|
|
|
|
|
|
|
```nim
|
|
|
|
|
var res = conn.query( """RETURN "Hello world", 1234, [1,2,3]""" )
|
|
|
|
|
|
|
|
|
|
echo $res #=>
|
|
|
|
|
# Hello world|1234|LIST_CREATION(1,2,3)
|
|
|
|
|
# Hello world|1234|[1,2,3]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Additionally, various query metadata is available for introspection:
|
|
|
|
|
|
|
|
|
|
```nim
|
2025-03-31 21:56:54 +00:00
|
|
|
var res = conn.query """
|
2025-03-31 19:35:15 +00:00
|
|
|
RETURN
|
|
|
|
|
"Hello world" AS hi,
|
|
|
|
|
1234 AS pin,
|
|
|
|
|
[1,2,3] AS list
|
2025-03-31 21:56:54 +00:00
|
|
|
"""
|
2025-03-31 19:35:15 +00:00
|
|
|
|
2025-03-31 21:56:54 +00:00
|
|
|
assert res.num_columns == 3
|
|
|
|
|
assert res.num_tuples == 1
|
2025-03-31 19:35:15 +00:00
|
|
|
echo res.compile_time #=> 14.028
|
|
|
|
|
echo res.execution_time #=> 1.624
|
|
|
|
|
|
|
|
|
|
# Return the column names as a sequence.
|
2025-03-31 21:56:54 +00:00
|
|
|
assert res.column_names == @["hi", "pin", "list"]
|
2025-03-31 19:35:15 +00:00
|
|
|
|
|
|
|
|
# Return the column data types as a sequence.
|
2025-11-04 09:06:11 -08:00
|
|
|
assert res.column_types == @[LBUG_STRING, LBUG_INT64, LBUG_LIST]
|
2025-03-31 19:35:15 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Prepared Statements
|
|
|
|
|
|
|
|
|
|
If you're supplying an argument to a query, or you're running a query
|
|
|
|
|
repeatedly, it's safer and faster to create a prepared statement via `prepare()`
|
|
|
|
|
on the connection. These statements are only compiled once, and execution is
|
|
|
|
|
deferred until you call `execute()`.
|
|
|
|
|
|
|
|
|
|
```nim
|
2025-03-31 21:56:54 +00:00
|
|
|
var stmt = conn.prepare """
|
2025-03-31 19:35:15 +00:00
|
|
|
RETURN
|
|
|
|
|
"Hello world" AS hi,
|
|
|
|
|
1234 AS pin,
|
|
|
|
|
[1,2,3] AS list
|
2025-03-31 21:56:54 +00:00
|
|
|
"""
|
2025-03-31 19:35:15 +00:00
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
# This returns a LbugQueryResult, just like `conn.query()`.
|
2025-03-31 19:35:15 +00:00
|
|
|
var res = stmt.execute()
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Arguments are labeled variables (prefixed with `$`) within the query.
|
|
|
|
|
Parameters are matched by providing a Nim tuple argument to `execute()` - a
|
|
|
|
|
simple round trip example:
|
|
|
|
|
|
|
|
|
|
```nim
|
2025-03-31 21:56:54 +00:00
|
|
|
var stmt = conn.prepare """
|
2025-03-31 19:35:15 +00:00
|
|
|
RETURN
|
|
|
|
|
$message AS message,
|
|
|
|
|
$digits AS digits,
|
|
|
|
|
LIST_CREATION($list) AS list
|
2025-03-31 21:56:54 +00:00
|
|
|
"""
|
2025-03-31 19:35:15 +00:00
|
|
|
|
|
|
|
|
var res = stmt.execute( (message: "Hello", digits: 1234, list: "1,2,3") )
|
|
|
|
|
|
|
|
|
|
echo $res #=>
|
|
|
|
|
# message|digits|list
|
|
|
|
|
# Hello|1234|[1,2,3]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### Type Conversion
|
|
|
|
|
|
|
|
|
|
When binding variables to a prepared statement, most Nim types are automatically
|
2025-11-04 09:06:11 -08:00
|
|
|
converted to their respective Ladybug types.
|
2025-03-31 19:35:15 +00:00
|
|
|
|
|
|
|
|
```nim
|
|
|
|
|
var stmt = conn.prepare( """RETURN $num AS num""" )
|
|
|
|
|
var res = stmt.execute( (num: 12) )
|
|
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
assert res.column_types[0] == LBUG_INT32
|
2025-03-31 19:35:15 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
This might not necessarily be what you want - sometimes you'd rather be strict
|
|
|
|
|
with typing, and you might be inserting into a column that has a different type
|
|
|
|
|
than the default.
|
|
|
|
|
|
|
|
|
|
You can use [integer type suffixes](https://nim-lang.org/docs/manual.html#lexical-analysis-numeric-literals), or casting to be explicit as usual:
|
|
|
|
|
|
|
|
|
|
```nim
|
|
|
|
|
var stmt = conn.prepare( """RETURN $num AS num""" )
|
2025-11-04 09:06:11 -08:00
|
|
|
var res: LbugQueryResult
|
2025-03-31 19:35:15 +00:00
|
|
|
|
|
|
|
|
res = stmt.execute( (num: 12'u64) )
|
2025-11-04 09:06:11 -08:00
|
|
|
assert res.column_types[0] == LBUG_UINT64
|
2025-03-31 19:35:15 +00:00
|
|
|
|
|
|
|
|
res = stmt.execute( (num: 12.float) )
|
2025-11-04 09:06:11 -08:00
|
|
|
assert res.column_types[0] == LBUG_DOUBLE
|
2025-03-31 19:35:15 +00:00
|
|
|
```
|
|
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
#### Ladybug Specific Types
|
2025-03-31 19:35:15 +00:00
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
This is a useful way to easily use most Ladybug types without needing corresponding
|
2025-03-31 19:35:15 +00:00
|
|
|
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!
|
|
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
This has the additional advantage of letting Ladybug error check the validity of
|
2025-03-31 19:35:15 +00:00
|
|
|
the content, and it works with the majority of types.
|
|
|
|
|
|
|
|
|
|
An extended example:
|
|
|
|
|
|
|
|
|
|
```nim
|
|
|
|
|
import std/sequtils
|
2025-11-04 09:06:11 -08:00
|
|
|
import lbug
|
2025-03-31 19:35:15 +00:00
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
var db = newLbugDatabase()
|
2025-03-31 19:35:15 +00:00
|
|
|
var conn = db.connect
|
|
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
var res: LbugQueryResult
|
2025-03-31 19:35:15 +00:00
|
|
|
|
|
|
|
|
# Create a node table.
|
|
|
|
|
#
|
|
|
|
|
res = conn.query """
|
|
|
|
|
CREATE NODE TABLE Example (
|
|
|
|
|
id SERIAL,
|
|
|
|
|
num UINT8,
|
|
|
|
|
done BOOL,
|
|
|
|
|
comment STRING,
|
|
|
|
|
karma DOUBLE,
|
|
|
|
|
thing UUID,
|
|
|
|
|
created DATE,
|
|
|
|
|
activity TIMESTAMP,
|
|
|
|
|
PRIMARY KEY(id)
|
|
|
|
|
)
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# Prepare a statement for adding a node.
|
|
|
|
|
#
|
|
|
|
|
var stmt = conn.prepare """
|
|
|
|
|
CREATE (e:Example {
|
|
|
|
|
num: $num,
|
|
|
|
|
done: $done,
|
|
|
|
|
comment: $comment,
|
|
|
|
|
karma: $karma,
|
|
|
|
|
thing: UUID($thing),
|
|
|
|
|
created: DATE($created),
|
|
|
|
|
activity: TIMESTAMP($activity)
|
|
|
|
|
})
|
|
|
|
|
"""
|
|
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
# Add a node row that contains specific Ladybug types.
|
2025-03-31 19:35:15 +00:00
|
|
|
#
|
|
|
|
|
res = stmt.execute((
|
|
|
|
|
num: 2,
|
|
|
|
|
done: true,
|
|
|
|
|
comment: "Types!",
|
|
|
|
|
karma: 16.7,
|
|
|
|
|
thing: "e0e7232e-bec9-4625-9822-9d1a31ea6f93",
|
|
|
|
|
created: "2025-03-29",
|
|
|
|
|
activity: "2025-03-29"
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
# Show the current contents.
|
|
|
|
|
res = conn.query( """MATCH (e:Example) RETURN e.*""" )
|
|
|
|
|
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
|
|
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
# Show column names and their Ladybug types.
|
2025-03-31 19:35:15 +00:00
|
|
|
for pair in res.column_names.zip( res.column_types ):
|
|
|
|
|
echo pair #=>
|
2025-11-04 09:06:11 -08:00
|
|
|
# ("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)
|
2025-03-31 19:35:15 +00:00
|
|
|
```
|
|
|
|
|
|
2025-07-17 16:04:12 -07:00
|
|
|
## Reading Results
|
2025-03-31 19:35:15 +00:00
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
So far we've just been showing values by converting the entire `LbugQueryResult`
|
2025-03-31 19:35:15 +00:00
|
|
|
to a string. Convenient for quick examples and debugging, but not much else.
|
|
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
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.
|
2025-03-31 19:35:15 +00:00
|
|
|
|
|
|
|
|
```nim
|
|
|
|
|
var res = conn.query """
|
2025-03-31 19:43:49 +00:00
|
|
|
UNWIND [1,2,3] AS items
|
|
|
|
|
UNWIND ["thing"] AS thing
|
|
|
|
|
RETURN items, thing
|
2025-03-31 19:35:15 +00:00
|
|
|
"""
|
|
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
# LbugFlatTuple can be stringified just like the result set.
|
2025-03-31 19:35:15 +00:00
|
|
|
for row in res:
|
|
|
|
|
echo row #=>
|
|
|
|
|
# 1|thing
|
|
|
|
|
# 2|thing
|
|
|
|
|
# 3|thing
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Once iteration has reached the end, it is automatically rewound for reuse.
|
|
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
You can manually get the next `LbugFlatTuple` via `getNext()`. Calling
|
2025-03-31 19:35:15 +00:00
|
|
|
`getNext()` after the last row results in an error. Use `hasNext()` to check
|
|
|
|
|
before calling.
|
|
|
|
|
|
|
|
|
|
```nim
|
|
|
|
|
var res = conn.query """
|
2025-03-31 19:43:49 +00:00
|
|
|
UNWIND [1,2,3] AS items
|
|
|
|
|
RETURN items
|
2025-03-31 19:35:15 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# Get the first row.
|
|
|
|
|
if res.hasNext:
|
|
|
|
|
var row = res.getNext
|
|
|
|
|
echo row #=> 1
|
|
|
|
|
|
|
|
|
|
echo res.getNext #=> 2
|
|
|
|
|
echo res.getNext #=> 3
|
2025-11-04 09:06:11 -08:00
|
|
|
echo res.getNext #=> LbugIterationError exception!
|
2025-03-31 19:35:15 +00:00
|
|
|
```
|
|
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
Manually rewind the `LbugQueryResult` via `rewind()`.
|
2025-03-31 19:35:15 +00:00
|
|
|
|
|
|
|
|
|
2025-07-17 16:04:12 -07:00
|
|
|
## Multiple Query Results
|
|
|
|
|
|
2025-07-18 20:23:20 -07:00
|
|
|
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
|
2025-11-04 09:06:11 -08:00
|
|
|
over linked `LbugQueryResult` objects with the `sets()` iterator to retreive the
|
2025-07-18 20:23:20 -07:00
|
|
|
remaining:
|
2025-07-17 16:04:12 -07:00
|
|
|
|
|
|
|
|
```nim
|
2025-11-04 09:06:11 -08:00
|
|
|
import lbug
|
2025-07-17 16:04:12 -07:00
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
let db = newLbugDatabase()
|
2025-07-17 16:04:12 -07:00
|
|
|
let conn = db.connect
|
|
|
|
|
|
|
|
|
|
let query = conn.query """
|
|
|
|
|
UNWIND [1,2,3] as items
|
|
|
|
|
RETURN items;
|
|
|
|
|
|
|
|
|
|
UNWIND [4,5,6] as items
|
|
|
|
|
RETURN items;
|
|
|
|
|
|
|
|
|
|
UNWIND [7,8,9] as items
|
|
|
|
|
RETURN items;
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
for row in query:
|
|
|
|
|
echo row
|
|
|
|
|
# 1
|
|
|
|
|
# 2
|
|
|
|
|
# 3
|
|
|
|
|
|
|
|
|
|
for set in query.sets:
|
|
|
|
|
for row in set:
|
|
|
|
|
echo row
|
|
|
|
|
# 4
|
|
|
|
|
# 5
|
|
|
|
|
# 6
|
|
|
|
|
# 7
|
|
|
|
|
# 8
|
|
|
|
|
# 9
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
2025-03-31 19:35:15 +00:00
|
|
|
## Working with Values
|
|
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
A `LbugFlatTuple` contains the entire row. You can index a value at its column
|
|
|
|
|
position, returning a `LbugValue`.
|
2025-03-31 19:35:15 +00:00
|
|
|
|
|
|
|
|
```nim
|
|
|
|
|
var res = conn.query """
|
|
|
|
|
RETURN
|
|
|
|
|
1 AS num,
|
|
|
|
|
true AS done,
|
|
|
|
|
"A comment" AS comment,
|
|
|
|
|
12.84 AS karma,
|
|
|
|
|
UUID("b41deae0-dddf-430b-981d-3fb93823e495") AS thing,
|
|
|
|
|
DATE("2025-03-29") AS created
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
var row = res.getNext
|
|
|
|
|
|
|
|
|
|
for idx in ( 0 .. res.num_columns-1 ):
|
|
|
|
|
var value = row[idx]
|
|
|
|
|
echo res.column_names[idx], ": ", value, " (", value.kind, ")" #=>
|
2025-11-04 09:06:11 -08:00
|
|
|
# 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)
|
2025-03-31 19:35:15 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Types
|
|
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
A `LbugValue` can always be stringified, irrespective of its Lbug type. You can
|
2025-03-31 19:35:15 +00:00
|
|
|
check what type it is via the 'kind' property.
|
|
|
|
|
|
|
|
|
|
```nim
|
|
|
|
|
var res = conn.query """RETURN "hello""""
|
|
|
|
|
var value = res.getNext[0]
|
|
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
assert value.kind == LBUG_STRING
|
2025-03-31 19:35:15 +00:00
|
|
|
```
|
|
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
A `LbugValue` has conversion methods for Nim base types. You'll likely want to
|
2025-03-31 19:35:15 +00:00
|
|
|
convert it for regular Nim usage:
|
|
|
|
|
|
|
|
|
|
```nim
|
|
|
|
|
var res = conn.query( "RETURN 2560" )
|
|
|
|
|
var value = res.getNext[0]
|
|
|
|
|
|
|
|
|
|
echo value + 1 #=> Type error!
|
|
|
|
|
|
2025-03-31 21:56:54 +00:00
|
|
|
assert $value == "2560"
|
|
|
|
|
assert value.toInt64 + 1 == 2561
|
2025-03-31 19:35:15 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Lists
|
|
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
A `LbugValue` of type `LBUG_LIST` can be converted to a Nim sequence of
|
|
|
|
|
`LbugValues` with the `toList()` function:
|
2025-03-31 19:35:15 +00:00
|
|
|
|
|
|
|
|
```nim
|
|
|
|
|
import std/sequtils
|
2025-11-04 09:06:11 -08:00
|
|
|
import lbug
|
2025-03-31 19:35:15 +00:00
|
|
|
|
|
|
|
|
var res = conn.query """
|
|
|
|
|
RETURN [10, 20, 30]
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
var value = res.getNext[0]
|
|
|
|
|
|
|
|
|
|
var list = value.toList
|
|
|
|
|
echo list #=> @[10,20,30]
|
|
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
echo list.map( func(v:LbugValue): int = v.toInt64 * 10 ) #=> @[100,200,300]
|
2025-03-31 19:35:15 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Struct-like Objects
|
|
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
Various Ladybug types can act like a struct - this includes `LBUG_NODE`,
|
|
|
|
|
`LBUG_REL`, and of course an explicit `LBUG_STRUCT` itself, among others.
|
2025-03-31 19:35:15 +00:00
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
Convert a `LbugValue` to a `LbugStructValue` with `toStruct()`. For
|
2025-03-31 19:35:15 +00:00
|
|
|
convenience, this is also aliased to `toNode()` and `toRel()`.
|
|
|
|
|
|
|
|
|
|
Once converted, you can access struct values by passing the key name to `[]`:
|
|
|
|
|
|
|
|
|
|
```nim
|
|
|
|
|
var res = conn.query """
|
|
|
|
|
RETURN {movie: "The Fifth Element", year: 1997}
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
var value = res.getNext[0]
|
|
|
|
|
|
|
|
|
|
var struct = value.toStruct
|
|
|
|
|
echo struct["movie"], " was released in ", struct["year"], "." #=>
|
|
|
|
|
# "The Fifth Element was released in 1997."
|
|
|
|
|
```
|
|
|
|
|
|
2025-03-31 21:56:54 +00:00
|
|
|
Here's a more elaborate example, following a node path:
|
2025-03-31 19:35:15 +00:00
|
|
|
|
|
|
|
|
```nim
|
|
|
|
|
import
|
|
|
|
|
std/sequtils,
|
|
|
|
|
std/strformat
|
2025-11-04 09:06:11 -08:00
|
|
|
import lbug
|
2025-03-31 19:35:15 +00:00
|
|
|
|
2025-11-04 09:06:11 -08:00
|
|
|
var db = newLbugDatabase()
|
2025-03-31 19:35:15 +00:00
|
|
|
var conn = db.connect
|
|
|
|
|
|
|
|
|
|
var res = conn.query """
|
2025-03-31 21:56:54 +00:00
|
|
|
CREATE NODE TABLE Person (
|
|
|
|
|
id SERIAL,
|
|
|
|
|
name STRING, PRIMARY KEY (id)
|
|
|
|
|
);
|
|
|
|
|
CREATE REL TABLE Knows (
|
|
|
|
|
FROM Person TO Person,
|
|
|
|
|
since INT
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
CREATE (p:Person {name: "Bob"});
|
|
|
|
|
CREATE (p:Person {name: "Alice"});
|
|
|
|
|
CREATE (p:Person {name: "Bruce"});
|
|
|
|
|
CREATE (p:Person {name: "Tom"});
|
|
|
|
|
|
|
|
|
|
CREATE (a:Person {name: "Bruce"})-[r:Knows {since: 1997}]->(b:Person {name: "Tom"});
|
|
|
|
|
CREATE (a:Person {name: "Bob"})-[r:Knows {since: 2009}]->(b:Person {name: "Alice"});
|
|
|
|
|
CREATE (a:Person {name: "Alice"})-[r:Knows {since: 2010}]->(b:Person {name: "Bob"});
|
|
|
|
|
CREATE (a:Person {name: "Bob"})-[r:Knows {since: 2003}]->(b:Person {name: "Bruce"});
|
2025-03-31 19:35:15 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
res = conn.query """
|
2025-03-31 19:43:49 +00:00
|
|
|
MATCH path = (a:Person)-[r:Knows]->(b:Person)
|
|
|
|
|
WHERE r.since > 2000
|
|
|
|
|
RETURN r.since as Since, nodes(path) as People
|
|
|
|
|
ORDER BY r.since
|
2025-03-31 19:35:15 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# Who knows who since when?
|
|
|
|
|
#
|
|
|
|
|
for row in res:
|
|
|
|
|
var since = row[0]
|
2025-11-04 09:06:11 -08:00
|
|
|
var people = row[1].toList.map( proc(p:LbugValue):LbugStructValue = p.toNode )
|
2025-04-01 18:08:53 +00:00
|
|
|
echo &"""{people[0]["name"]} has known {people[1]["name"]} since {since}.""" #=>
|
|
|
|
|
# Bob has known Bruce since 2003.
|
|
|
|
|
# Bob has known Alice since 2009.
|
|
|
|
|
# Alice has known Bob since 2010.
|
2025-03-31 19:35:15 +00:00
|
|
|
```
|
2025-04-19 17:45:53 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
### Blobs
|
|
|
|
|
|
2025-11-15 16:38:21 -08:00
|
|
|
Ladybug can store chunks of opaque binary data. For these BLOB columns, using
|
|
|
|
|
`toBlob` will return the raw sequence of bytes.
|
2025-04-19 17:45:53 +00:00
|
|
|
|
|
|
|
|
```nim
|
|
|
|
|
var q = conn.query """
|
|
|
|
|
CREATE NODE TABLE Doot ( id SERIAL, data BLOB, PRIMARY KEY(id) )
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
var stmt = conn.prepare( "CREATE (d:Doot {data: encode($str)})" )
|
|
|
|
|
q = stmt.execute( (str: "Hello!") )
|
|
|
|
|
q = conn.query( "MATCH (d:Doot) RETURN d.data" )
|
|
|
|
|
|
|
|
|
|
var blob = q.getNext[0].toBlob #=> @[72, 101, 108, 108, 111, 33]
|
|
|
|
|
```
|