# `NebulaGraphEx`
[🔗](https://github.com/VChain/nebula_graph_ex/blob/v0.1.8/lib/nebula_graph_ex.ex#L1)

Elixir client for [NebulaGraph](https://www.nebula-graph.io/) — a distributed
open-source graph database.

`nebula_graph_ex` provides:

* **Connection pooling** via `DBConnection` (the same library used by
  Postgrex and MyXQL).
* **Full type decoding** — NebulaGraph's `Value` union is converted to
  idiomatic Elixir terms: integers, floats, binaries, booleans, `nil`,
  `%Vertex{}`, `%Edge{}`, `%Path{}`, date/time structs, geography, and more.
* **Parameterised queries** — values are encoded as Thrift `Value` structs,
  never interpolated into statement strings.
* **TLS / SSL** support out of the box.
* **Telemetry** events for every query start, stop, and exception.

## Quick start

### 1. Add to `mix.exs`

    defp deps do
      [
        {:nebula_graph_ex, "~> 0.1"}
      ]
    end

### 2. Define a graph module

Borrowing from Ecto's Repo pattern, create a module for your graph connection:

    # lib/my_app/graph.ex
    defmodule MyApp.Graph do
      use NebulaGraphEx.Graph, otp_app: :my_app
    end

### 3. Configure it

    # config/config.exs
    config :my_app, MyApp.Graph,
      hostname: "localhost",
      port: 9669,
      space: "my_graph",
      pool_size: 10

    # config/runtime.exs — secrets resolved at boot, not compile time
    config :my_app, MyApp.Graph,
      hostname: System.fetch_env!("NEBULA_HOST"),
      password: fn -> System.fetch_env!("NEBULA_PASS") end

### 4. Add to your supervision tree

    def start(_type, _args) do
      children = [MyApp.Graph]
      Supervisor.start_link(children, strategy: :one_for_one)
    end

### 5. Run queries

    alias NebulaGraphEx.{Record, ResultSet}

    # Simple query
    {:ok, rs} = MyApp.Graph.query("MATCH (v:Player) RETURN v.name, v.age LIMIT 5")

    rs |> ResultSet.count()
    #=> 5

    rs |> ResultSet.rows() |> Enum.map(&Record.to_map/1)
    #=> [%{"v.name" => "Tim Duncan", "v.age" => 42}, ...]

    # Parameterised query (always use params for user input)
    {:ok, rs} =
      MyApp.Graph.query(
        "MATCH (v:Player{name: $name}) RETURN v.age AS age",
        %{"name" => "Tim Duncan"}
      )

    rs |> ResultSet.first!() |> Record.get!("age")
    #=> 42

    # Bang variant raises NebulaGraphEx.Error on failure
    MyApp.Graph.query!("RETURN 1+1 AS n")
    |> ResultSet.first!()
    |> Record.get!("n")
    #=> 2

### 6. Check connection status

    {:ok, status} = MyApp.Graph.status()
    status.connected?
    #=> true

    status.queryable?
    #=> true

    {:error, failed_status} = MyApp.Graph.status(probe_statement: "THIS IS NOT VALID NGQL")
    failed_status.error_details
    #=> %{code: :e_syntax_error, message: "...", category: :query, statement: "THIS IS NOT VALID NGQL"}

## Working with graph types

When a column contains a vertex, edge, or path, it is decoded automatically:

    {:ok, rs} = MyApp.Graph.query("MATCH (v:Player) RETURN v LIMIT 1")

    rs
    |> ResultSet.first!()
    |> Record.get!("v")
    #=> %NebulaGraphEx.Types.Vertex{
    #     vid: "player100",
    #     tags: [%NebulaGraphEx.Types.Tag{name: "Player", props: %{"name" => "Tim Duncan"}}]
    #   }

    rs
    |> ResultSet.first!()
    |> Record.get!("v")
    |> NebulaGraphEx.Types.Vertex.prop("Player", "name")
    #=> "Tim Duncan"

## Per-query options

Options can be overridden at call-site:

    MyApp.Graph.query(stmt, %{},
      space: "other_space",
      timeout: 60_000,
      decode_mapper: &Record.to_map/1
    )

## Configuration reference

All options accepted by `NebulaGraphEx.Graph.start_link/1` are documented in
`NebulaGraphEx.Options`.

## Telemetry

Attach the default logger handler in development:

    NebulaGraphEx.Telemetry.attach_default_handler()

Or attach your own handler for production metrics — see `NebulaGraphEx.Telemetry`
for event names, measurements, and metadata shapes.

## Updating the Thrift IDL

NebulaGraph's wire protocol is defined by the Thrift IDL files in
`priv/thrift/`. To upgrade to a newer NebulaGraph server version:

1. Replace the files in `priv/thrift/` with the upstream versions from
   [vesoft-inc/nebula](https://github.com/vesoft-inc/nebula/tree/master/src/interface).
2. Re-add the `namespace elixir` lines (the only lines that differ from upstream).
3. Run `mix compile`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
