NebulaGraphEx (nebula_graph_ex v0.1.8)

Copy Markdown View Source

Elixir client for NebulaGraph — 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.
  2. Re-add the namespace elixir lines (the only lines that differ from upstream).
  3. Run mix compile.