# `NebulaGraphEx.Graph`
[🔗](https://github.com/VChain/nebula_graph_ex/blob/v0.1.10/lib/nebula_graph_ex/graph.ex#L1)

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

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

See `NebulaGraphEx.Options` for the full option reference.

# `__using__`
*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,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.

# `map`

```elixir
@spec map(
  DBConnection.conn(),
  String.t(),
  map(),
  keyword(),
  (NebulaGraphEx.ResultSet.t() -&gt; 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`

```elixir
@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!`

```elixir
@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

# `start_link`

```elixir
@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`

```elixir
@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`

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

---

*Consult [api-reference.md](api-reference.md) for complete listing*
