# SparrowDB for Elixir

```elixir
Mix.install([
  # {:kino_vega_lite, "~> 0.1.11"},
  # {:graphic, path: "/home/calliope/share/gram/graphic"},
  {:sparrowdb, path: "/home/calliope/share/gram/sparrowdb-ex"},
])
```

## HexDocs

* [ ] merge the code from ~/share/gram/spare -> ~/share/gram/graphic
* [ ] pull in cypher libraries and map edge cases in sparrowdb
* [ ] run through genstage demos and begin filling in db queue
* [ ] where possible, compare approach with normal `:digraph` module

Compare:

* [Artefact LiveBook](https://livebook.dev/run/?url=https%3A%2F%2Fgithub.com%2Fdiffo-dev%2Fartefactory%2Fblob%2Fdev%2Fartefact_kino%2Fartefact_kino.livemd)

Reference:

* https://opencypher.org/

<!-- livebook:{"break_markdown":true} -->

[Source](https://hexdocs.pm/elixir/erlang-libraries.html#the-digraph-module)

The `:digraph` and `:digraph_utils` modules contain functions for dealing with directed graphs built of vertices and edges. After constructing the graph, the algorithms in there will help find, for instance, the shortest path between two vertices, or loops in the graph.

Given three vertices, find the shortest path from the first to the last.

Note that the functions in `:digraph` alter the graph structure in-place, this is possible because they are implemented as ETS tables, explained next.

```elixir
{:ok, graph} = SparrowDB.open("media.sparrowdb")
```

```elixir
import SparrowDB
```

```elixir
graph |> query("CREATE (alice:Person {name: 'Alice', age: 30})")
graph |> query("CREATE (bob:Person   {name: 'Bob',   age: 25})")
graph |> query("MATCH (a:Person {name:'Alice'}), (b:Person {name:'Bob'}) CREATE (a)-[:KNOWS]->(b)")
```

```elixir
graph |> query("MATCH (a:Person {name:'Alice'})-[:KNOWS*1..2]->(f) RETURN DISTINCT f.name")
```

## Experiments

### Shapes of Nodes and Edges

Seems like any ETS term can be used as a node, or as a label for an edge.

```elixir
sources = :digraph.new()
pages = %{
  main: "https://gizmodo.com/i-cut-the-big-five-tech-giants-from-my-life-it-was-hel-1831304194",
  amazon: "https://gizmodo.com/i-tried-to-block-amazon-from-my-life-it-was-impossible-1830565336",
  facebook: "https://gizmodo.com/i-cut-facebook-out-of-my-life-surprisingly-i-missed-i-1830565456",
  google: "https://gizmodo.com/i-cut-google-out-of-my-life-it-screwed-up-everything-1830565500",
  microsoft: "https://gizmodo.com/i-cut-microsoft-out-of-my-life-or-so-i-thought-1830863898",
  apple: "https://gizmodo.com/i-cut-apple-out-of-my-life-it-was-devastating-1831063868",
}

for {_company, addr} <- pages, do: :digraph.add_vertex(sources, addr)
for {company, addr} <- (pages |> Map.drop([:main])),
  do: sources |> :digraph.add_edge(pages[:main], addr, %{ name: company })

# sources |> :digraph.info()
sources |> :digraph.out_edges(pages[:main]) |> Enum.map(& sources |> :digraph.edge(&1) |> elem(3))
# sources |> :digraph.out_neighbours(pages[:main])
# sources |> :digraph.edge("apple")
```

```elixir
dg = :digraph

sources |> dg.vertices()
```

```elixir
sources |> dg.edges() |> Enum.map(& sources |> :digraph.edge(&1) |> Tuple.to_list() |> tl)
```

## Helpers!

```elixir
# defmodule DG do
#   @dg :digraph
  
#   def bridge(g, a, b) do
#     if !(g|>@dg.vertex(a)), do: g|>@dg.add_vertex(a)
#     if !(g|>@dg.vertex(b)), do: g|>@dg.add_vertex(b)
#     g|>@dg.add_edge(a, b)
#   end
#   def bridge(g, a, b, label) do
#     if !(g|>@dg.vertex(a)), do: g|>@dg.add_vertex(a)
#     if !(g|>@dg.vertex(b)), do: g|>@dg.add_vertex(b)
#     g|>@dg.add_edge(a, b, label)
#   end

#   def edges(g) do
#     g|>@dg.edges()|>Enum.map(& g|>@dg.edge(&1))
#   end
#   def neighbors(g,n) do
#     g|>@dg.out_neighbours(n)
#   end

#   def order(g) do
#     g|>:digraph_utils.topsort()
#   end
# end

alias Graphic, as: DG
```

## Reshaping a Graph

We'd like to render our graphic using d2-lang.

```elixir
g = dg.new()
g|>dg.add_vertex("a")
g|>DG.bridge("a", "b")
g|>DG.bridge("a", "c")
g|>DG.bridge("c", "d", "broken!")
g|>DG.bridge("b", "d")
g|>dg.vertices()
```

```elixir
g|>DG.edges()
```

## Render

```elixir
# repurposed from https://dashbit.co/blog/livebook-as-an-educational-tool
mermaid_edges =
  g
  |> :digraph.edges() # Get all of the edges in the graph
  |> Enum.map_join("\n", fn edge ->
    {_, vertex_1, vertex_2, _} = :digraph.edge(g, edge) # Get the edge vertices
    {_, vertex_1_name} = :digraph.vertex(g, vertex_1) # Get the label for the first vertex
    {_, vertex_2_name} = :digraph.vertex(g, vertex_2) # Get the label for the second vertex

    # Mermaid.js format for graph edges: VERTEX_1_ID[VERTEX_1_label] --> VERTEX_2_ID[VERTEX_2_label]
    # "#{vertex_1}[#{vertex_1_name}] --> #{vertex_2}[#{vertex_2_name}];"
    "#{vertex_1} --> #{vertex_2};"
  end)

```

````elixir
Kino.Markdown.new("""
```mermaid
graph TD;
#{mermaid_edges}
```
""")
````

```elixir

# Delete the graph instance so you don't leak ETS tables :)
:digraph.delete(g)
```

```elixir
g
|>DG.order()
|>Enum.map(&
  g
  |>DG.neighbors(&1)
  |>Enum.map(fn n -> "#{&1} -> #{n}" end)
)
|>List.flatten()
|>Enum.join("\n")
|>IO.puts
```
