NebulaGraphEx.Graph (nebula_graph_ex v0.1.10)

Copy Markdown View Source

Public API for querying NebulaGraph.

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
end

Put connection options in config:

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

Override 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") end

Add 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)
end

Now 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: true

Enable 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:

VerbLevel
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?
#=> true

See 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{}}.

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

__using__(opts)

(macro)

Injects a graph module into the calling module.

Options

  • :otp_app — required. The OTP application atom used to look up config via Application.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,3query(statement, params \\ %{}, opts \\ []).
  • query!/1,2,3 — raises on failure.
  • map/2,3,4map(statement, params \\ %{}, opts \\ [], fun).
  • status/0,1 — checks whether the pool is alive and can run a simple query.
  • use_space/1,2use_space(space, opts \\ []).

All functions are overridable so you can add instrumentation or defaults.

map(conn, statement, params \\ %{}, opts \\ [], fun)

@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", ...]

query(conn, statement, params \\ %{}, opts \\ [])

@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 from start_link/1. When using the use macro, this is the module itself and is passed automatically.
  • statement — nGQL string.
  • params — map of parameter bindings. Default: %{}.
  • opts — per-query option overrides (see NebulaGraphEx.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")

query!(conn, statement, params \\ %{}, opts \\ [])

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

start_link(opts)

@spec start_link(keyword()) :: {:ok, pid()} | {:error, term()}

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.

status(conn, opts \\ [])

@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}
 }}

use_space(conn, space, opts \\ [])

@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.