NebulaGraphEx.Graph (nebula_graph_ex v0.1.8)

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

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.