---
title: "Getting Started with AstraeaDB"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Getting Started with AstraeaDB}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  eval = FALSE
)
```

This vignette walks through every major operation supported by the AstraeaDB
R package. Each section contains a self-contained code example that you can
copy into an R session once you have a running AstraeaDB server.

## Starting the Server

Before you can connect from R you need a running AstraeaDB instance. The
easiest way to start one locally is from the AstraeaDB source tree:

```{bash start-server}
cargo run -p astraea-cli -- serve
```

The default ports are 7687 (JSON/TCP), 7688 (gRPC), and 7689 (Arrow Flight).

## Connecting from R

There are two ways to create a client and connect to the server.

### Option A: Convenience Function

The simplest approach is the `astraea_connect()` helper, which creates a
client and calls `connect()` in one step:

```{r connect-convenience}
library(AstraeaDB)

client <- astraea_connect()
# Equivalent to: astraea_connect(host = "127.0.0.1", port = 7687L)
```

### Option B: Explicit Construction

If you need more control (for example, to set an authentication token), you
can construct and connect the client separately:

```{r connect-explicit}
library(AstraeaDB)

client <- AstraeaClient$new(
  host       = "127.0.0.1",
  port       = 7687L,
  auth_token = "my-secret-token"
)
client$connect()
```

In either case, you can verify the connection with a health-check ping:

```{r ping}
info <- client$ping()
info
```

## Creating Nodes

A node requires at least one **label** (character vector) and a set of
**properties** (named list). You may optionally provide an **embedding**
(numeric vector) for vector similarity search.

```{r create-nodes}
# Basic node
alice_id <- client$create_node(
  labels     = c("Person"),
  properties = list(name = "Alice", age = 30, city = "San Francisco")
)

# Node with multiple labels
bob_id <- client$create_node(
  labels     = c("Person", "Engineer"),
  properties = list(name = "Bob", age = 25, city = "New York")
)

# Node with an embedding vector
carol_id <- client$create_node(
  labels     = c("Person"),
  properties = list(name = "Carol", age = 35),
  embedding  = c(0.12, 0.87, 0.45, 0.33)
)
```

`create_node()` returns the integer ID assigned to the new node by the
server.

## Reading Nodes

Retrieve a node by its ID. The result is a list with `labels` and
`properties`:

```{r get-node}
node <- client$get_node(alice_id)

node$labels
#> [1] "Person"

node$properties$name
#> [1] "Alice"

node$properties$age
#> [1] 30
```

## Updating Nodes

`update_node()` uses **merge semantics**: existing properties that are not
mentioned in the update are preserved. New properties are added and existing
ones are overwritten.

```{r update-node}
# Add a new property; existing name, age, city are kept
client$update_node(alice_id, list(department = "Research"))

# Verify
updated <- client$get_node(alice_id)
updated$properties$department
#> [1] "Research"

updated$properties$city
#> [1] "San Francisco"
```

## Deleting Nodes

Deleting a node also removes all edges connected to it:

```{r delete-node}
temp_id <- client$create_node(
  labels     = c("Temp"),
  properties = list(note = "will be deleted")
)
client$delete_node(temp_id)
```

## Creating Edges

An edge connects a source node to a target node with a typed relationship.
Optional parameters include properties, weight, and temporal validity
bounds.

```{r create-edges}
# Simple edge
edge1 <- client$create_edge(
  source    = alice_id,
  target    = bob_id,
  edge_type = "KNOWS",
  properties = list(context = "work")
)

# Weighted edge
edge2 <- client$create_edge(
  source    = bob_id,
  target    = carol_id,
  edge_type = "FOLLOWS",
  weight    = 0.7
)

# Temporal edge with validity window (milliseconds since epoch)
# Valid from 2023-01-01 to 2024-01-01
edge3 <- client$create_edge(
  source     = alice_id,
  target     = carol_id,
  edge_type  = "MENTORS",
  properties = list(topic = "graph databases"),
  weight     = 1.0,
  valid_from = 1672531200000,
  valid_to   = 1704067200000
)
```

`create_edge()` returns the integer ID of the new edge. The `valid_from` and
`valid_to` parameters are numeric scalars representing milliseconds since the
Unix epoch. When set, the edge is only visible in temporal queries whose
timestamp falls within the window.

## Reading and Updating Edges

```{r edge-read-update}
# Read
edge <- client$get_edge(edge1)
edge$edge_type
#> [1] "KNOWS"
edge$properties$context
#> [1] "work"

# Update (merge semantics, same as nodes)
client$update_edge(edge1, list(strength = "strong"))
```

## Deleting Edges

```{r delete-edge}
client$delete_edge(edge2)
```

## Graph Traversals

### Neighbors

Retrieve direct neighbors of a node. You can filter by direction
(`"outgoing"`, `"incoming"`, or `"both"`) and by edge type:

```{r neighbors}
# Outgoing neighbors (default)
out <- client$neighbors(alice_id, direction = "outgoing")

# Incoming neighbors
inc <- client$neighbors(bob_id, direction = "incoming")

# Both directions, filtered by edge type
knows <- client$neighbors(alice_id, direction = "both", edge_type = "KNOWS")
```

Each entry in the result contains at least `node_id` and `edge_id`.

### Breadth-First Search (BFS)

BFS explores the graph outward from a starting node up to a maximum depth:

```{r bfs}
bfs_result <- client$bfs(alice_id, max_depth = 2L)

# Each entry has node_id and depth
for (entry in bfs_result) {
  cat(sprintf("Node %d at depth %d\n", entry$node_id, entry$depth))
}
```

### Shortest Path

Find the shortest path between two nodes. Set `weighted = TRUE` to use edge
weights (Dijkstra's algorithm) instead of hop count:

```{r shortest-path}
# Unweighted (hop count)
sp <- client$shortest_path(alice_id, carol_id)
sp$path    # integer vector of node IDs
sp$length  # number of hops

# Weighted (minimizes total edge weight)
sp_w <- client$shortest_path(alice_id, carol_id, weighted = TRUE)
sp_w$path
sp_w$cost  # total weight
```

## GQL Queries

AstraeaDB supports a subset of Graph Query Language (GQL). You can run
queries with `MATCH`, `WHERE`, `RETURN`, `ORDER BY`, and `LIMIT` clauses:

```{r gql}
# Find all Person nodes
result <- client$query("MATCH (p:Person) RETURN p.name, p.age")

# Filter with WHERE
result <- client$query(
  "MATCH (p:Person) WHERE p.age > 25 RETURN p.name, p.city"
)

# Edges in the pattern
result <- client$query(
  "MATCH (a:Person)-[:KNOWS]->(b:Person) RETURN a.name, b.name"
)

# Ordering and limiting
result <- client$query(
  "MATCH (p:Person) RETURN p.name, p.age ORDER BY p.age LIMIT 10"
)
```

The `query()` method returns the raw server response data. For tabular
results you may want to use the `UnifiedClient$query_df()` method (see the
Advanced Features vignette for Arrow Flight support).

## Batch Operations

When you need to create or delete many nodes or edges at once, batch methods
are more convenient than calling single-item methods in a loop.

### Batch Create

```{r batch-create}
# Create multiple nodes
node_ids <- client$create_nodes(list(
  list(labels = c("City"), properties = list(name = "San Francisco", state = "CA")),
  list(labels = c("City"), properties = list(name = "New York", state = "NY")),
  list(labels = c("City"), properties = list(name = "Austin", state = "TX"))
))

# Create multiple edges
edge_ids <- client$create_edges(list(
  list(source = alice_id, target = node_ids[1], edge_type = "LIVES_IN"),
  list(source = bob_id,   target = node_ids[2], edge_type = "LIVES_IN"),
  list(source = carol_id, target = node_ids[3], edge_type = "LIVES_IN",
       weight = 0.8)
))
```

### Batch Delete

```{r batch-delete}
# Delete multiple nodes (also removes their edges)
deleted_count <- client$delete_nodes(node_ids)
cat(deleted_count, "nodes deleted\n")

# Delete multiple edges
deleted_edges <- client$delete_edges(edge_ids)
```

Batch delete operations silently skip any IDs that do not exist or have
already been deleted, and return the count of successful deletions.

## Data Frame Integration

The package provides import and export helpers that bridge between R data
frames and the AstraeaDB graph.

### Importing Nodes from a Data Frame

```{r import-nodes-df}
people_df <- data.frame(
  label = c("Person", "Person", "Person"),
  name  = c("Dave", "Eve", "Frank"),
  age   = c(40, 28, 55),
  city  = c("Chicago", "Boston", "Denver"),
  stringsAsFactors = FALSE
)

new_ids <- client$import_nodes_df(people_df, label_col = "label")
new_ids
#> [1] 10 11 12
```

The `label_col` parameter identifies which column holds the node label. All
other columns become node properties. You can also specify `embedding_cols`
if some columns should be assembled into an embedding vector:

```{r import-nodes-embedding}
embed_df <- data.frame(
  label = c("Document", "Document"),
  title = c("Doc A", "Doc B"),
  e1    = c(0.1, 0.4),
  e2    = c(0.9, 0.5),
  e3    = c(0.3, 0.8),
  stringsAsFactors = FALSE
)

doc_ids <- client$import_nodes_df(
  embed_df,
  label_col      = "label",
  embedding_cols = c("e1", "e2", "e3")
)
```

### Importing Edges from a Data Frame

```{r import-edges-df}
edges_df <- data.frame(
  source = new_ids[c(1, 2)],
  target = new_ids[c(2, 3)],
  type   = c("KNOWS", "MENTORS"),
  since  = c(2018, 2021),
  stringsAsFactors = FALSE
)

edge_ids <- client$import_edges_df(
  edges_df,
  source_col = "source",
  target_col = "target",
  type_col   = "type"
)
```

Additional column-mapping parameters are available: `weight_col`,
`valid_from_col`, and `valid_to_col`. All columns not mapped to a structural
role become edge properties.

### Exporting Nodes to a Data Frame

```{r export-nodes-df}
df <- client$export_nodes_df(new_ids)
df
#>   node_id labels   name age    city
#> 1      10 Person   Dave  40 Chicago
#> 2      11 Person    Eve  28  Boston
#> 3      12 Person  Frank  55  Denver
```

The exported data frame contains a `node_id` column, a comma-separated
`labels` column, and one column per property. Nodes with different property
sets are aligned; missing properties are filled with `NA`.

### Exporting BFS Results to a Data Frame

```{r export-bfs-df}
bfs_df <- client$export_bfs_df(alice_id, max_depth = 2L)
bfs_df
#>   node_id depth labels   name age          city
#> 1       1     0 Person  Alice  30 San Francisco
#> 2       2     1 Person    Bob  25      New York
#> 3       3     2 Person  Carol  35          <NA>
```

This runs a BFS internally, then fetches each visited node and flattens the
results into a data frame with an additional `depth` column.

## Disconnecting

Always disconnect when you are done. This closes the underlying TCP socket:

```{r disconnect}
client$disconnect()
```

A good pattern for scripts is to use `on.exit()` to guarantee cleanup:
```{r on-exit}
client <- astraea_connect()
on.exit(client$disconnect(), add = TRUE)

# ... do work ...
```

## Error Handling

All client methods signal R errors when the server returns an error response
or the connection fails. Use `tryCatch()` to handle errors gracefully:

```{r error-handling}
tryCatch(
  {
    node <- client$get_node(999999L)
  },
  error = function(e) {
    message("Operation failed: ", conditionMessage(e))
  }
)
```

Typical error scenarios include:

- **Connection refused** -- The server is not running or the port is wrong.
- **Node/edge not found** -- The requested ID does not exist.
- **Validation errors** -- Invalid parameters (e.g., empty labels,
  non-numeric embedding).
- **Authentication errors** -- Missing or invalid `auth_token`.

You can check whether the server is reachable before attempting to connect:

```{r check-available}
if (astraea_server_available()) {
  client <- astraea_connect()
  # ... work ...
  client$disconnect()
} else {
  message("AstraeaDB server is not available.")
}
```

## Summary

This vignette covered the core workflow for interacting with AstraeaDB from
R:

1. Start the server and connect from R.
2. Create, read, update, and delete nodes and edges.
3. Traverse the graph with neighbors, BFS, and shortest-path queries.
4. Run GQL queries for declarative pattern matching.
5. Use batch and data-frame helpers for bulk operations.
6. Handle errors and clean up connections.

For vector search, temporal queries, GraphRAG, and Arrow Flight transport,
see `vignette("advanced-features")`.
