Public API for querying NebulaGraph.
The recommended pattern — define a graph module
Borrowing from Ecto's Repo pattern, define a graph module in your application:
# lib/my_app/graph.ex
defmodule MyApp.Graph do
use NebulaGraphEx.Graph, otp_app: :my_app
endPut connection options in config:
# config/config.exs
config :my_app, MyApp.Graph,
hostname: "localhost",
port: 9669,
username: "root",
space: "my_graph",
pool_size: 10Override secrets at runtime (recommended for passwords):
# config/runtime.exs
import Config
config :my_app, MyApp.Graph,
hostname: System.fetch_env!("NEBULA_HOST"),
password: fn -> System.fetch_env!("NEBULA_PASS") endAdd to your supervision tree — that's it:
# lib/my_app/application.ex
def start(_type, _args) do
children = [MyApp.Graph]
Supervisor.start_link(children, strategy: :one_for_one)
endNow query without ever referencing a PID or pool name:
MyApp.Graph.query("MATCH (v:Player) RETURN v.name, v.age LIMIT 5")
MyApp.Graph.query!("RETURN 1+1 AS n") |> ResultSet.first!() |> Record.get!("n")Direct pool usage (without use)
When you need multiple pools, or want full control, start NebulaGraphEx.Graph
directly in your supervision tree:
children = [
{NebulaGraphEx.Graph,
name: MyApp.Graph,
hostname: "localhost",
port: 9669,
space: "my_graph",
pool_size: 10}
]Then pass the name (or PID) to every call:
NebulaGraphEx.Graph.query(MyApp.Graph, "MATCH (v:Player) RETURN v LIMIT 5")Parameterised queries
Always use parameters for user-supplied values — never interpolate into the statement string.
MyApp.Graph.query(
"MATCH (v:Player{name: $name}) RETURN v.age AS age",
%{"name" => "Tim Duncan"}
)Per-query options
Any pool option can be overridden for a single call:
MyApp.Graph.query(stmt, %{},
space: "other_space",
timeout: 30_000,
decode_mapper: &NebulaGraphEx.Record.to_map/1
)Bang variants
query!/3 raises NebulaGraphEx.Error on failure instead of returning
{:error, error}.
Query logging
Enable structured query logging to see what is being sent to NebulaGraph, where it was called from, and how long it took.
Turn on logging for all queries from a pool via config:
config :my_app, MyApp.Graph,
log: trueEnable logging for a single call:
MyApp.Graph.query("MATCH (v:Player) RETURN v.name LIMIT 5", %{}, log: true)Log levels
The log level is inferred automatically from the nGQL verb so that read
traffic stays at :debug while mutations stand out:
| Verb | Level |
|---|---|
MATCH, GO, LOOKUP, FETCH, RETURN, FIND, SHOW, DESCRIBE, EXPLAIN, USE | :debug |
INSERT, UPSERT, UPDATE | :info |
DELETE, DROP, CREATE, ALTER, ADD, REMOVE, REBUILD | :warning |
Override the inferred level for a whole pool or per call with :log_level.
Log output
Each query produces two lines. The caller annotation is rendered in muted gray to reduce visual noise:
↳ MyApp.Players.find/1, at: lib/my_app/players.ex:17
[NGE] QUERY OK 3 rows db=0.6ms queue=0.1ms
MATCH (v:Player) RETURN v.name LIMIT 3 []On error:
↳ MyApp.Players.find/1, at: lib/my_app/players.ex:17
[NGE] QUERY ERROR (e_syntax_error) db=0.3ms queue=0.1ms
THIS IS NOT VALID NGQL []Slow query logging
Log only queries that exceed a time threshold, without enabling full query logging:
config :my_app, MyApp.Graph,
slow_query_threshold: 100 # ms:slow_query_threshold and :log are independent — both can be set, and
the threshold fires regardless of :log.
See NebulaGraphEx.Options for the full option reference including
:log, :log_level, and :slow_query_threshold.
Connection status
Use status/1 or status/2 when you want a health check for a pool. It
verifies that the pool process is alive and, by default, executes RETURN 1
to confirm the connection can run a query.
{:ok, status} = MyApp.Graph.status()
status.connected?
#=> trueSee NebulaGraphEx.Options for the full option reference.
Summary
Functions
Injects a graph module into the calling module.
Executes a nGQL statement and applies fun to every %Record{} row,
returning a list of results.
Executes a nGQL statement and returns {:ok, %ResultSet{}} or
{:error, %NebulaGraphEx.Error{}}.
Like query/4 but raises NebulaGraphEx.Error on failure.
Starts a supervised connection pool and links it to the calling process.
Checks whether a pool is alive and able to execute a simple query.
Issues USE <space> on the given connection, switching the active graph
space for that connection's current session.
Functions
Injects a graph module into the calling module.
Options
:otp_app— required. The OTP application atom used to look up config viaApplication.get_env(otp_app, __MODULE__, []).
Generated API
When you use NebulaGraphEx.Graph, otp_app: :my_app, the following functions
are injected into your module, all scoped to the pool that the module itself
manages:
start_link/1— starts the pool (used by your supervision tree).child_spec/1— makes the module a valid child spec entry.query/1,2,3—query(statement, params \\ %{}, opts \\ []).query!/1,2,3— raises on failure.map/2,3,4—map(statement, params \\ %{}, opts \\ [], fun).status/0,1— checks whether the pool is alive and can run a simple query.use_space/1,2—use_space(space, opts \\ []).
All functions are overridable so you can add instrumentation or defaults.
@spec map( DBConnection.conn(), String.t(), map(), keyword(), (NebulaGraphEx.ResultSet.t() -> term()) ) :: {:ok, [term()]} | {:error, NebulaGraphEx.Error.t()}
Executes a nGQL statement and applies fun to every %Record{} row,
returning a list of results.
Equivalent to:
{:ok, rs} = query(conn, stmt, params, opts)
Enum.map(ResultSet.rows(rs), fun)Example
NebulaGraphEx.Graph.map(MyApp.Graph, "MATCH (v:Player) RETURN v.name", fn record ->
record |> NebulaGraphEx.Record.get!("v.name")
end)
#=> ["Tim Duncan", "LeBron James", ...]
@spec query(DBConnection.conn(), String.t(), map(), keyword()) :: {:ok, NebulaGraphEx.ResultSet.t()} | {:error, NebulaGraphEx.Error.t()}
Executes a nGQL statement and returns {:ok, %ResultSet{}} or
{:error, %NebulaGraphEx.Error{}}.
Arguments
conn— pool name or PID fromstart_link/1. When using theusemacro, this is the module itself and is passed automatically.statement— nGQL string.params— map of parameter bindings. Default:%{}.opts— per-query option overrides (seeNebulaGraphEx.Options). Default:[].
Examples
{:ok, rs} = NebulaGraphEx.Graph.query(MyApp.Graph, "RETURN 1+1 AS sum")
rs |> NebulaGraphEx.ResultSet.first!() |> NebulaGraphEx.Record.get!("sum")
#=> 2
{:error, %NebulaGraphEx.Error{code: :e_syntax_error}} =
NebulaGraphEx.Graph.query(MyApp.Graph, "THIS IS NOT VALID nGQL")
@spec query!(DBConnection.conn(), String.t(), map(), keyword()) :: NebulaGraphEx.ResultSet.t()
Like query/4 but raises NebulaGraphEx.Error on failure.
Example
NebulaGraphEx.Graph.query!(MyApp.Graph, "MATCH (v:Player) RETURN count(v) AS n")
|> NebulaGraphEx.ResultSet.first!()
|> NebulaGraphEx.Record.get!("n")
#=> 51
Starts a supervised connection pool and links it to the calling process.
Typically called via a module that uses NebulaGraphEx.Graph. When using the
pool directly, pass a :name so callers can reference it by atom:
children = [
{NebulaGraphEx.Graph,
name: MyApp.Graph,
hostname: System.fetch_env!("NEBULA_HOST"),
password: fn -> System.fetch_env!("NEBULA_PASS") end,
space: "my_graph",
pool_size: 20}
]See NebulaGraphEx.Options for the full option reference.
@spec status( DBConnection.conn(), keyword() ) :: {:ok, %{ pid: pid(), connected?: true, queryable?: boolean(), probe_statement: String.t() | nil }} | {:error, %{ pid: pid() | nil, connected?: boolean(), queryable?: false, probe_statement: String.t() | nil, error: term(), error_details: map() }}
Checks whether a pool is alive and able to execute a simple query.
This is an active health check. It first resolves the pool process and then,
unless disabled, executes RETURN 1 AS status to verify the checked-out
connection can talk to NebulaGraph successfully.
Options
:probe_statement— query used for the health check. Default:"RETURN 1 AS status".:probe_query— whether to run the health-check query. Default:true.:query_opts— options passed to the probe query. Default:[].
Return value
On success:
{:ok,
%{
pid: #PID<0.123.0>,
connected?: true,
queryable?: true,
probe_statement: "RETURN 1 AS status"
}}If the pool process is missing:
{:error,
%{
pid: nil,
connected?: false,
queryable?: false,
error: :not_running,
error_details: %{reason: :not_running, message: "pool process is not running"}
}}If the pool exists but the probe query fails:
{:error,
%{
pid: #PID<0.123.0>,
connected?: true,
queryable?: false,
probe_statement: "RETURN 1 AS status",
error: %NebulaGraphEx.Error{},
error_details: %{code: :e_syntax_error, message: "...", category: :query}
}}
@spec use_space(DBConnection.conn(), String.t(), keyword()) :: {:ok, NebulaGraphEx.ResultSet.t()} | {:error, NebulaGraphEx.Error.t()}
Issues USE <space> on the given connection, switching the active graph
space for that connection's current session.
This is only useful when you hold a checked-out connection directly. With
the pool, prefer passing the :space option to query/4 instead.