Getting started with rladybugdb

rladybugdb is a native R interface to LadybugDB, an embedded columnar graph database formerly known as Kuzu. It links directly to the LadybugDB 0.20.4 C API and requires neither Python nor a database server.

Optional integrations use packages such as arrow, igraph, and tidygraph that must already be installed. Functions and examples do not install packages; examples using an optional package are skipped when it is unavailable.

Open a database

Use ":memory:" for an ephemeral database or a file path for persistence. Connections keep their database reachable, but explicit cleanup gives errors a predictable scope.

db <- lb_database(":memory:")
conn <- lb_connection(db)

For code that should automatically clean up, use a scoped connection:

with_lb_connection(":memory:", {
  ladybugdb_version()
})
#> [1] "0.20.4"

Create and query a property graph

Node and relationship tables define a graph schema.

lb_close(lb_execute(
  conn,
  "CREATE NODE TABLE Person (name STRING, age INT64, PRIMARY KEY(name))"
))
lb_close(lb_execute(
  conn,
  "CREATE NODE TABLE City (name STRING, PRIMARY KEY(name))"
))
lb_close(lb_execute(
  conn,
  "CREATE REL TABLE LivesIn (FROM Person TO City, since INT64)"
))

lb_close(lb_execute(
  conn,
  "CREATE (:Person {name: $name, age: $age})",
  parameters = list(name = "Alice", age = 30L)
))
lb_close(lb_execute(conn, "CREATE (:City {name: 'London'})"))
lb_close(lb_execute(
  conn,
  paste(
    "MATCH (p:Person {name: 'Alice'}), (c:City {name: 'London'})",
    "CREATE (p)-[:LivesIn {since: 2018}]->(c)"
  )
))

lb_query(
  conn,
  paste(
    "MATCH (p:Person)-[r:LivesIn]->(c:City)",
    "RETURN p.name AS person, p.age AS age, c.name AS city, r.since AS since"
  )
)
#>   person age   city since
#> 1  Alice  30 London  2018

Parameters must be a named list with unique, non-empty names. Logical, integer, double, character, factor, Date, POSIXct, integer64, raw, list, array, and struct-like named-list values are supported. Typed missing scalars become database NULL.

Reusable and streaming results

lb_query() materializes and closes a result automatically. Use lb_execute() when you want result metadata, conversion to another format, or bounded streaming.

result <- lb_execute(
  conn,
  "UNWIND range(1, 25) AS id RETURN id, 'row ' + CAST(id AS STRING) AS label"
)

print(result, n = 3)
#> <lb_result> [25 x 2]
#>   id label
#> 1  1 row 1
#> 2  2 row 2
#> 3  3 row 3
#> # ... with 22 more rows
first <- lb_fetch(result, n = 10)
second <- lb_fetch(result, n = 10)
lb_result_info(result)
#> $column_names
#> [1] "id"    "label"
#> 
#> $column_types
#> [1] "INT64"  "STRING"
#> 
#> $num_columns
#> [1] 2
#> 
#> $num_tuples
#> [1] 25
#> 
#> $rows_fetched
#> [1] 20
#> 
#> $complete
#> [1] FALSE
#> 
#> $has_next_result
#> [1] FALSE
lb_query_summary(result)
#> $compiling_time_ms
#> [1] 5.24
#> 
#> $execution_time_ms
#> [1] 0.216

lb_reset(result)
identical(as.data.frame(result), as.data.frame(result))
#> [1] TRUE
lb_close(result)

Printing, formatting, and as.data.frame() preserve the current cursor. Successive lb_fetch() calls advance it; lb_reset() starts again.

Arrow and data loading

With the optional arrow package, conversion uses the Arrow C Data Interface instead of first creating a data frame.

result <- lb_execute(conn, "MATCH (p:Person) RETURN p.name AS name, p.age AS age")
arrow_table <- as_arrow_table(result)
lb_close(result)
arrow_table
#> Table
#> 1 rows x 2 columns
#> $name <string>
#> $age <int64>

Create a target table before bulk loading. lb_copy_from_df() prefers the Arrow path, which preserves empty strings and missing strings distinctly, and warns if it must use CSV instead.

lb_close(lb_execute(
  conn,
  "CREATE NODE TABLE Event (id INT64, note STRING, PRIMARY KEY(id))"
))
copy_result <- lb_copy_from_df(
  conn,
  data.frame(id = c(1, 2), note = c("", NA_character_)),
  "Event"
)
lb_close(copy_result)
lb_query(conn, "MATCH (e:Event) RETURN e.* ORDER BY e.id")
#>   e.id e.note
#> 1    1       
#> 2    2   <NA>

Use lb_copy_from_csv() for explicit file ingestion and lb_copy_from_arrow() for an Arrow Table or RecordBatch.

DBI

The DBI backend supports queries, binding, bounded fetches, transactions, quoting, and table metadata.

dbi_conn <- DBI::dbConnect(Ladybug(), dbname = ":memory:")
DBI::dbGetQuery(dbi_conn, "RETURN $value AS value", params = list(value = 42L))
#>   value
#> 1    42
DBI::dbDisconnect(dbi_conn)
#> [1] TRUE

Graph objects

Return full nodes and relationships to construct igraph or tidygraph objects. Scalar property projections alone do not contain graph structure.

graph_result <- lb_execute(
  conn,
  "MATCH (p:Person)-[r:LivesIn]->(c:City) RETURN p, r, c"
)
graph <- as_igraph(graph_result)
lb_close(graph_result)
graph
#> IGRAPH 676c99b DN-- 2 1 -- 
#> + attr: name (v/c), _LABEL (v/c), age (v/n), _LABEL (e/c), _ID (e/c),
#> | since (e/n)
#> + edge from 676c99b (vertex names):
#> [1] Alice->London

Recursive relationship paths are supported as well. Internal identifiers are kept as exact character components rather than converted through floating point.

Close resources

lb_close(conn)
lb_close(db)

Calling lb_close() twice is harmless. Closing a connection with a live result or a database with a live connection raises a clear error rather than risking use-after-free.