defmodule NebulaGraphEx do @moduledoc """ 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 ## 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`. """ end