# SQL API Reference

{/* Canonical public URL: /user_guide/api-reference. Section ownership is the Function Index below. */}

This page documents the SQL surface implemented by the current `graph` crate.
All functions live in the `graph` schema.

The generated [SQL 1.0 profile](./sql-profile) is the machine-checked list of
public function groups, compatibility dispositions, and executable assurance
dimensions. [Exact SQL Signatures](./sql-signatures) is generated from the
frozen release schema and is authoritative for overloads, defaults, return
types, and result columns. The sections below provide task-oriented guidance.

## Function Index

| Category | Functions |
|---|---|
| Availability | `status`, `memory_profile`, `sync_health`, `reset` |
| Named graph metadata | `create_graph`, `alter_graph`, `drop_graph`, `list_graphs`, `current_graph`, `set_current_graph`, `set_graph_residency`, `grant_graph`, `revoke_graph`, `graph_privileges`, `transfer_graph_ownership`, `set_graph_quota`, `graph_quotas`, `graph_quota_usage` |
| Runtime graph loading | `select_graph`, `load_graph`, `unload_graph`, `loaded_graphs`, `graph_runtime_status` |
| Registration | `add_table`, `add_table_to_graph`, `add_edge`, `add_edge_to_graph`, `add_filter_column`, `add_filter_column_to_graph`, `registered_tables`, `registered_tables_for_graph`, `registered_edges`, `registered_edges_for_graph`, `list_edges`, `rename_edge`, `alter_edge`, `graph_map`, `remove_table`, `remove_table_from_graph`, `remove_edge`, `remove_edge_from_graph`, `estimate` |
| Discovery | `auto_discover`, `auto_discover_tables`, `preview_discover`, `preview_discover_tables`, `create_row_predicate_subgraph` |
| Build jobs | `build`, `build_graph`, `build_async_graph`, `build_status`, `build_status_for_graph` |
| Sync and maintenance | `enable_sync`, `apply_sync`, `add_sync_policy`, `alter_sync_policy`, `drop_sync_policy`, `run_sync_policy`, `sync_policy_status`, `jobs`, `job_runs`, `job_stats`, `run_job`, `run_due_jobs`, `run_due_jobs_async`, `alter_job`, `remove_job`, `run_scheduled_maintenance`, `projection_compact`, `projection_gc`, `projection_repair`, `vacuum`, `vacuum_graph`, `maintenance`, `maintenance_graph`, `maintenance_status`, `maintenance_status_for_graph` |
| GQL | `gql`, `gql_explain` |
| Search | `search`, `search_nodes` |
| Traversal | `traverse`, `traverse_search`, `node_ref`, `node_ref_string`, `format_path` |
| Filters | `equals`, `eq`, `not_equals`, `neq`, `greater_than`, `gt`, `at_least`, `gte`, `less_than`, `lt`, `at_most`, `lte`, `between`, `on_node`, `all` |
| Workflow | `find`, `expand`, `find_related`, `path`, `connection`, `neighborhood` |
| Paths | `shortest_path`, `weighted_shortest_path` |
| Aggregation | `path_count_estimate`, `aggregate` |
| Components | `connected_components`, `component_stats`, `components`, `largest_component`, `component`, `isolated_nodes` |

## Availability

### `graph.status()`

```sql
graph.status()
RETURNS TABLE (
  node_count int,
  edge_count int,
  memory_used_mb double precision,
  memory_limit_mb int,
  sync_mode text,
  sync_status text,
  last_build timestamptz,
  last_vacuum timestamptz,
  edge_types text[],
  edge_buffer_used int,
  has_unidirectional_edges boolean,
  schema_status text,
  sync_lag bigint,
  pending_edge_deltas int,
  needs_vacuum boolean,
  needs_rebuild boolean,
  applied_sync_id bigint,
  pending_sync_rows bigint,
  invalid_reason text,
  disabled_trigger_count int,
  read_only boolean,
  read_only_reason text,
  projection_mode text,
  overlay_tombstone_count int,
  overlay_memory_bytes bigint,
  compaction_recommended boolean,
  tx_delta_dirty boolean,
  tx_delta_added_nodes int,
  tx_delta_deleted_nodes int,
  tx_delta_added_edges int,
  tx_delta_deleted_edges int,
  tx_delta_memory_bytes bigint
)
```

Reports backend-local engine state and current catalog/sync validity. When
`read_only` is true, `read_only_reason` identifies the serving-state cause,
such as `edge_buffer_full`. `projection_mode` is
`csr_readonly` or `mutable_overlay`. Overlay columns summarize tombstones,
memory, and durable compaction policy; `edge_buffer_used` plus `tx_delta_dirty`
show whether pending sync or transaction-local overlays exist.

### `graph.build_resource_status()`

```sql
SELECT * FROM graph.build_resource_status();
```

Returns one row with `budget_bytes`, `peak_bytes`, `peak_phase`, and
`pressure_events` for the most recent successful build in the current backend.
The budget is the effective replacement budget after subtracting serving
residency and the safety reserve from the configured limit. The peak measures
replacement construction and persistence against that budget. Pressure events
count adaptive byte-triggered batch flushes; row-ceiling flushes remain
controlled by `graph.build_batch_size`.

### `graph.resource_status()`

```sql
SELECT * FROM graph.resource_status();
```

Returns one row for the most recent completed governed operation in the current
PostgreSQL backend, or no rows before such an operation has completed:

```text
operation text,
memory_budget_bytes bigint,
memory_peak_bytes bigint,
memory_peak_phase text,
disk_peak_bytes bigint,
rows bigint,
work_units bigint
```

Operations include `load`, `query`, `maintenance`, and `analytics`. The row is
backend-local diagnostic telemetry. It is replaced when the next governed
operation completes and is not an aggregate across sessions.

### `graph.active_generation_count()`

```sql
graph.active_generation_count()
RETURNS int
```

Returns the number of unexpired backend heartbeats currently retaining durable
projection generations for generation-aware cleanup.

### `graph.ingest_projection()`

```sql
graph.ingest_projection(
  max_rows bigint DEFAULT NULL,
  max_bytes bigint DEFAULT NULL
)
RETURNS TABLE (
  rows_ingested bigint,
  segments_published bigint,
  sync_watermark bigint
)
```

Admin-only. Publishes committed sync-log rows into durable projection segments
above the active manifest watermark. `max_rows` and `max_bytes` bound one
ingestion pass; `NULL` uses the configured defaults. Segment manifests record
the dirty source-node range when committed rows provide one, so later reads and
maintenance do not have to treat every L0 segment as whole-graph coverage.
For registered edge-row mappings, inserted relationship segments retain the
source relationship identity used by GQL relationship values and
relationship-row visibility checks. The cumulative identity dictionary is
checksummed and referenced by the active projection manifest, so those
identities survive backend reloads.
Post-build node inserts retain their exact registered table and primary-key
identity, typed filter values, tenant membership, and mapped same-batch edge
endpoints. Publication is serialized per graph across PostgreSQL backends, and
a successful direct ingest reloads the current backend before returning.
Registered source triggers take a shared transaction-scoped writer lock. A
durable ingest takes the corresponding exclusive lock without waiting, so it
returns SQLSTATE `55P03` while any registered source transaction is active.
This prevents out-of-order commits and later rollbacks from being skipped by
the durable watermark while allowing source writers to run concurrently with
one another. A node identity change also fails with rebuild guidance when a
standalone relationship mapping could retain rows pointing at the old node
slot.
Installations upgraded from an earlier trigger definition must run
`graph.enable_sync()` or a trigger-mode `graph.build()` first. Durable ingestion
checks all applicable trigger definitions and fails with upgrade guidance
instead of publishing against an unprotected source table.
Operations that cannot yet be represented incrementally, including
`TRUNCATE`, fail with rebuild guidance and leave the manifest watermark
unchanged.

### `graph.projection_status()`

```sql
graph.projection_status()
RETURNS TABLE (
  manifest_generation bigint,
  manifest_watermark bigint,
  pending_durable_rows bigint,
  base_artifact_bytes bigint,
  manifest_bytes bigint,
  artifact_bytes bigint,
  segment_count int,
  segment_bytes bigint,
  segment_fanout int,
  read_amplification int,
  l0_segment_count int,
  l1_segment_count int,
  l2_segment_count int,
  edge_segment_count int,
  node_segment_count int,
  dirty_chunk_count int,
  dirty_chunk_bytes bigint,
  tombstone_ratio double precision,
  compaction_backlog int,
  obsolete_file_count int,
  obsolete_bytes bigint,
  active_generation_count int,
  artifact_validation_state text,
  last_ingestion_unix_micros bigint,
  last_compaction_unix_micros bigint,
  last_gc_unix_micros bigint,
  last_repair_unix_micros bigint,
  ingest_recommended boolean,
  compaction_recommended boolean,
  gc_recommended boolean,
  repair_recommended boolean
)
```

Reports durable projection manifest, segment, chunk, GC, heartbeat, validation,
storage, fanout, read-amplification, and recommendation state.
`artifact_validation_state` is `healthy`, `targeted_chunk_repair`,
`full_rebuild`, or `no_projection`. `artifact_bytes` is the active base,
manifest, segment, and replacement-chunk footprint for the selected graph.
`segment_fanout` is the highest number of durable segments that cover one
source-node range, and `read_amplification` is the durable base/chunk layer plus
that segment fanout. The `last_*` timestamp columns are persisted operation
timestamps: ingestion, compaction, and repair are carried in the active
manifest lineage, and GC is recorded when `graph.projection_gc()` completes.

### `graph.projection_compact()`

```sql
graph.projection_compact(
  max_rows integer DEFAULT 10000,
  max_bytes bigint DEFAULT NULL,
  max_segments integer DEFAULT 1000,
  dirty_chunk_segment_threshold integer DEFAULT NULL
)
RETURNS TABLE (
  manifest_generation bigint,
  segments_compacted int,
  chunks_rewritten int,
  segment_count int,
  dirty_chunk_count int,
  sync_watermark bigint
)
```

Admin-only. Publishes one copy-on-write projection compaction generation. The
compactor reads the active manifest, validates the referenced durable segments,
and either writes a higher-level compacted segment or, when
`dirty_chunk_segment_threshold` is reached, writes replacement base chunks for
the affected source-node ranges. Existing base CSR bytes are never overwritten.
Compacted segment rows retain their source relationship identity when one is
available. Parallel relationship rows with identical source, target, type, and
schema direction remain separate through segment compaction and dirty-range
base-chunk rewrite; identity-specific tombstones remove only the matching
source relationship row.

### `graph.projection_gc()`

```sql
graph.projection_gc()
RETURNS TABLE (
  valid_generations_scanned int,
  retained_generations bigint[],
  active_generations bigint[],
  obsolete_candidates int,
  protected_candidates int,
  deleted_files int,
  deleted_bytes bigint
)
```

Admin-only. Deletes manifest-declared obsolete files, abandoned generation
manifests and their unshared artifacts, and abandoned manifest/pointer temp
files. Retained current-generation ancestry and unexpired active-generation
heartbeats protect every referenced artifact. `deleted_files` and
`deleted_bytes` include all removed categories; `obsolete_candidates` and
`protected_candidates` describe artifact candidates, not manifest or temp-file
counts. Missing candidates are ignored so repeated calls are idempotent.
Retention uses `graph.projection_retention_generations`.

### `graph.projection_repair()`

```sql
graph.projection_repair()
RETURNS TABLE (
  action text,
  generation_id bigint,
  rebuilt boolean,
  chunks_rewritten int,
  reason text
)
```

Admin-only. Validates the active durable projection manifest and referenced
artifacts. Healthy or absent projection metadata returns without rebuilding.
Corrupt base chunks are repaired by publishing a replacement generation from
the current base graph. Corrupt manifests, corrupt active segments, or invalid
base metadata trigger a full persisted rebuild from PostgreSQL source tables,
then publish a fresh base-only projection manifest. Readable prior metadata is
kept until the replacement publishes so its superseded artifacts can be tracked
for garbage collection. An unreadable prior manifest is removed only after the
replacement has published and reloaded successfully.

### `graph.memory_profile()`

```sql
graph.memory_profile(concurrent_backends int DEFAULT 1)
RETURNS TABLE (
  active_backend_private_mb double precision,
  active_backend_shared_mb double precision,
  active_backend_total_mb double precision,
  estimated_instance_private_mb double precision,
  estimated_instance_shared_mb double precision,
  estimated_instance_total_mb double precision,
  memory_limit_mb int,
  assumed_concurrent_backends int
)
```

Reports sizing estimates for the current backend's active engine. The immutable
artifact snapshot and backend-private heap are multiplied by
`concurrent_backends`, with a minimum of one. The `*_shared_mb` columns remain
for 1.x compatibility and currently return zero. This is an operator sizing
aid, not a live connection counter or cluster-wide hard cap.

## Named Graph Metadata

Named graph metadata is the identity and policy catalog for multi-graph
support. The current release keeps existing build, registration, sync, and
query functions on the compatibility default graph while this metadata surface
is introduced. Later graph-scoped APIs use the same graph identity rows.
Each backend can have at most one loaded graph engine at a time. Selecting a
different graph clears any previously loaded graph from that backend; it does
not delete persisted artifacts.

Graph metadata rows include:

| Column | Meaning |
|---|---|
| `graph_id` | Stable UUID for the graph metadata row |
| `graph_name` | User-visible graph name |
| `owner_role` | PostgreSQL role that owns this graph metadata |
| `created_by` | PostgreSQL role that created this graph metadata |
| `tenant` | Optional tenant discriminator and default tenant scope for selected-graph queries |
| `namespace` | Optional graph namespace; defaults to `public` |
| `graph_kind` | `global`, `user`, `tenant`, `workspace`, or `subgraph` |
| `residency` | `hot`, `warm`, or `cold` |
| `materialization` | `shared` or `dedicated` |
| `projection_mode` | `csr_readonly` or `mutable_overlay` |
| `created_at`, `updated_at` | Catalog timestamps |

Graph privileges are stored separately from source-table permissions:

| Privilege | Allows |
|---|---|
| `read` | Select and query the graph when source-table privileges also allow access |
| `write` | Reserved for mapped graph writes; PostgreSQL source-table write privileges are still required |
| `build` | Build, vacuum, and maintenance operations for the graph |
| `admin` | Grant/revoke graph privileges and manage graph-owned lifecycle operations |

### `graph.create_graph()`

```sql
graph.create_graph(
  graph_name text,
  tenant text DEFAULT NULL,
  namespace text DEFAULT NULL,
  graph_kind text DEFAULT 'user',
  residency text DEFAULT 'hot',
  materialization text DEFAULT 'shared',
  projection_mode text DEFAULT 'csr_readonly'
)
RETURNS TABLE (...)
```

Admin-only. Creates a graph metadata row for the current role. The tuple
`(tenant, owner_role, namespace, graph_name)` must be unique, treating a `NULL`
tenant or namespace as an empty value for identity checks. Invalid policy
values and duplicate identities return SQLSTATE `22023` with diagnostic
`PG005`.

### `graph.alter_graph()`

```sql
graph.alter_graph(
  graph_name text,
  tenant text DEFAULT NULL,
  namespace text DEFAULT NULL,
  graph_kind text DEFAULT NULL,
  residency text DEFAULT NULL,
  materialization text DEFAULT NULL,
  projection_mode text DEFAULT NULL
)
RETURNS TABLE (...)
```

Admin-only. Updates mutable policy metadata for an existing graph owned by the
current role. Omitted values keep the existing policy. Missing graphs and
invalid policy values return SQLSTATE `22023` with diagnostic `PG005`.

### `graph.drop_graph()`

```sql
graph.drop_graph(
  graph_name text,
  tenant text DEFAULT NULL,
  namespace text DEFAULT NULL
)
RETURNS TABLE (...)
```

Admin-only. Drops graph metadata for an existing non-default graph owned by the
current role and returns the deleted row. The compatibility default graph cannot
be dropped. A graph with registered tables, edges, or filter columns must be
emptied before it can be dropped; non-empty graphs return SQLSTATE `22023` with
diagnostic `PG005`.

### `graph.list_graphs()`

```sql
graph.list_graphs()
RETURNS TABLE (...)
```

Lists graph metadata visible to the current role. The default global graph is
visible to all roles with schema usage; role-owned graphs are visible to their
owner.

### `graph.current_graph()`

```sql
graph.current_graph()
RETURNS TABLE (...)
```

Returns the session-selected graph metadata. If no graph has been selected,
this returns the compatibility default graph.

### `graph.set_current_graph()`

```sql
graph.set_current_graph(
  graph_name text,
  tenant text DEFAULT NULL,
  namespace text DEFAULT NULL
)
RETURNS TABLE (...)
```

Selects graph metadata for the current session and returns the selected row.
Registration, build, query, and inspection functions that use the compatibility
SQL surface resolve the selected graph, or the default graph when no graph is
selected. Selecting a graph does not load a persisted artifact by itself; the
backend-local engine changes when you build or load graph state. Missing or
unauthorized graphs return SQLSTATE `22023` with diagnostic `PG005`.

### `graph.grant_graph()`

```sql
graph.grant_graph(
  graph_name text,
  grantee text,
  privilege text,
  tenant text DEFAULT NULL,
  namespace text DEFAULT NULL
)
RETURNS TABLE (...)
```

Owner/admin-only. Grants `read`, `write`, `build`, or `admin` on a graph to a
PostgreSQL role. Graph privileges do not bypass PostgreSQL source-table ACL or
RLS checks.

### `graph.revoke_graph()`

```sql
graph.revoke_graph(
  graph_name text,
  grantee text,
  privilege text,
  tenant text DEFAULT NULL,
  namespace text DEFAULT NULL
)
RETURNS TABLE (...)
```

Owner/admin-only. Revokes one graph privilege from a PostgreSQL role and
returns the revoked grant row. Missing grants return SQLSTATE `22023` with
diagnostic `PG005`.

### `graph.graph_privileges()`

```sql
graph.graph_privileges(
  graph_name text DEFAULT NULL,
  tenant text DEFAULT NULL,
  namespace text DEFAULT NULL
)
RETURNS TABLE (...)
```

Lists graph grants visible to the current role. Owners and graph admins can
inspect grants for their graphs.

### `graph.transfer_graph_ownership()`

```sql
graph.transfer_graph_ownership(
  graph_name text,
  new_owner text,
  tenant text DEFAULT NULL,
  namespace text DEFAULT NULL
)
RETURNS TABLE (...)
```

Owner/admin-only. Transfers a non-default graph to another PostgreSQL role.
The compatibility default graph cannot be transferred.

### `graph.set_graph_residency()`

```sql
graph.set_graph_residency(
  graph_name text,
  residency text,
  tenant text DEFAULT NULL,
  namespace text DEFAULT NULL
)
RETURNS TABLE (...)
```

Admin-only. Updates a graph's runtime residency policy. `hot` and `warm` graphs
are eligible for persisted auto-load when `graph.auto_load` is enabled. `cold`
graphs keep their persisted artifacts available for explicit
`graph.load_graph()` calls but are not auto-loaded by query execution.

### `graph.set_graph_quota()`

```sql
graph.set_graph_quota(
  scope_type text,
  dimension text,
  limit_value bigint,
  scope_key text DEFAULT NULL,
  enforcement text DEFAULT 'hard'
)
RETURNS TABLE (...)
```

Graph schema admin only. Upserts a quota policy row. Supported scopes are
`cluster`, `tenant`, `owner`, `namespace`, and `graph`; supported enforcement
modes are `hard` and `warn`. `owner` scope keys are PostgreSQL role names and
are stored as role OIDs. Current hard enforcement points are
`max_named_graphs`, checked before graph metadata is created,
`max_loaded_graphs_per_backend`, checked before a persisted artifact is loaded
into a backend-local engine, and `max_graph_jobs`, checked before a sync policy
creates its backing durable job.

### `graph.graph_quotas()`

```sql
graph.graph_quotas()
RETURNS TABLE (...)
```

Lists configured quota policy rows.

### `graph.graph_quota_usage()`

```sql
graph.graph_quota_usage()
RETURNS TABLE (
  scope_type text,
  scope_key text,
  dimension text,
  limit_value bigint,
  usage_value bigint,
  enforcement text,
  exceeded boolean
)
```

Reports SQL-computable quota usage for supported dimensions. A `hard`
`max_named_graphs` quota blocks new graph metadata before catalog rows are
created. A `hard` `max_loaded_graphs_per_backend` quota blocks runtime loads.
A `hard` `max_graph_jobs` quota blocks new sync policy jobs. A `hard`
`max_artifact_storage_bytes` quota blocks durable projection ingest or
compaction when active artifact bytes plus the requested write budget would
exceed the configured limit. A `warn` quota reports `exceeded = true` without
blocking the operation.

## Runtime Graph Loading

Runtime loading controls the backend-local graph engine. It does not mutate
PostgreSQL source tables or graph metadata.

### `graph.select_graph()`

```sql
graph.select_graph(
  graph_name text,
  tenant text DEFAULT NULL,
  namespace text DEFAULT NULL
)
RETURNS TABLE (
  graph_id text,
  graph_name text,
  loaded boolean
)
```

Selects a visible graph for the current session. If the selected graph is
already loaded in this backend, `loaded` is true and the engine remains active.
If another graph is loaded, pgGraph clears the backend-local engine before
returning. Use `graph.load_graph()` when you want to load the selected graph's
persisted artifact immediately.

### `graph.load_graph()`

```sql
graph.load_graph(
  graph_name text,
  tenant text DEFAULT NULL,
  namespace text DEFAULT NULL
)
RETURNS TABLE (
  graph_id text,
  graph_name text,
  loaded boolean,
  node_count bigint,
  edge_count bigint,
  memory_used_mb double precision,
  projection_mode text
)
```

Admin-only. Selects the graph and loads its current persisted generation into
the backend-local engine. The loader follows `projection-current.json` and its
checksummed manifest to the immutable base artifact; `main.pggraph` remains the
logical compatibility fallback for a store without projection metadata. This
explicit load path does not depend on `graph.auto_load`; missing or invalid
artifacts raise an error so operators can rebuild or repair the graph.

### `graph.unload_graph()`

```sql
graph.unload_graph(
  graph_name text,
  tenant text DEFAULT NULL,
  namespace text DEFAULT NULL
)
RETURNS TABLE (
  graph_id text,
  graph_name text,
  unloaded boolean
)
```

Admin-only. Clears the backend-local engine when the requested graph is the one
currently loaded in this backend. The persisted graph files remain on disk.

### `graph.loaded_graphs()`

```sql
graph.loaded_graphs()
RETURNS TABLE (
  graph_id text,
  graph_name text,
  residency text,
  node_count bigint,
  edge_count bigint,
  memory_used_mb double precision,
  projection_mode text,
  last_access_unix_micros bigint
)
```

Reports the graph currently loaded in this backend. The current implementation
returns zero or one row because each PostgreSQL backend has one graph engine
slot.

### `graph.graph_runtime_status()`

```sql
graph.graph_runtime_status()
RETURNS TABLE (
  graph_id text,
  graph_name text,
  residency text,
  loaded boolean,
  artifact_exists boolean,
  artifact_bytes bigint,
  node_count bigint,
  edge_count bigint,
  memory_used_mb double precision,
  projection_mode text,
  last_access_unix_micros bigint
)
```

Lists visible graphs with their runtime residency, backend-local loaded state,
persisted artifact presence, and loaded-engine metrics when that graph is
active in the current backend. This is the primary inspection API for deciding
whether a graph is cold, missing an artifact, blocked by quota, or already
loaded.

### `graph.sync_health()`

```sql
graph.sync_health()
RETURNS TABLE (
  sync_mode text,
  query_freshness text,
  sync_batch_size int,
  applied_sync_id bigint,
  max_sync_log_id bigint,
  pending_sync_rows bigint,
  disabled_trigger_count int,
  edge_buffer_used int,
  edge_buffer_size int,
  needs_vacuum boolean,
  needs_rebuild boolean,
  read_only boolean,
  read_only_reason text,
  projection_mode text,
  overlay_tombstone_count int,
  overlay_memory_bytes bigint,
  compaction_recommended boolean,
  tx_delta_dirty boolean,
  tx_delta_added_nodes int,
  tx_delta_deleted_nodes int,
  tx_delta_added_edges int,
  tx_delta_deleted_edges int,
  tx_delta_memory_bytes bigint,
  apply_sync_recommended boolean,
  maintenance_recommended boolean,
  durable_ingest_recommended boolean,
  durable_compaction_recommended boolean,
  durable_gc_recommended boolean,
  durable_repair_recommended boolean,
  sync_log_retention_floor bigint,
  sync_log_prune_recommended boolean,
  active_sync_watermark_backends int
)
```

Reports the sync and compaction signals most useful to schedulers and external
monitors. Use `apply_sync_recommended` to decide when a lightweight replay is
useful for a writable backend-local engine, and `maintenance_recommended` to
decide when overlay/read-only/rebuild state should be folded back into the base
graph. The `durable_*_recommended` columns mirror `graph.projection_status()`
for projection ingestion, compaction, GC, and persisted repair pressure without
decoding projection artifacts. Use `graph.projection_status()` when you need
full artifact validation. `sync_log_retention_floor`,
`sync_log_prune_recommended`, and `active_sync_watermark_backends` report
`graph._sync_log` pruning state; see
[Sync-Log Retention](/user_guide/sync-and-maintenance#sync-log-retention).

### `graph.reset()`

```sql
graph.reset() RETURNS void
```

Admin-only. Clears the backend-local engine and removes persisted graph files.

## Registration

Registration functions operate on the session-selected graph. If no graph has
been selected with `graph.set_current_graph()`, they use the compatibility
default graph. Use the `*_to_graph`, `*_for_graph`, and `*_from_graph`
functions to target a named graph without changing the session selection.

### `graph.add_table()`

```sql
graph.add_table(
  table_name regclass,
  id_column text,
  columns text[] DEFAULT NULL,
  tenant_column text DEFAULT NULL
) RETURNS void
```

```sql
graph.add_table(
  table_name regclass,
  id_columns text[],
  columns text[] DEFAULT NULL,
  tenant_column text DEFAULT NULL
) RETURNS void
```

Admin-only. Registers a source table as graph nodes. The identifier columns must
match a primary key or unique `NOT NULL` index.

### `graph.add_table_to_graph()`

```sql
graph.add_table_to_graph(
  graph_name text,
  table_name regclass,
  id_column text,
  columns text[] DEFAULT NULL,
  tenant_column text DEFAULT NULL,
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL
) RETURNS void
```

```sql
graph.add_table_to_graph(
  graph_name text,
  table_name regclass,
  id_columns text[],
  columns text[] DEFAULT NULL,
  tenant_column text DEFAULT NULL,
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL
) RETURNS void
```

Admin-only. Registers a source table in a named graph without changing the
current session graph. `graph_namespace` defaults to `public` when omitted.

### `graph.add_edge()`

```sql
graph.add_edge(
  from_table regclass,
  from_column text,
  to_table regclass,
  to_column text,
  label text,
  bidirectional boolean DEFAULT true,
  weight_column text DEFAULT NULL,
  label_column text DEFAULT NULL
) RETURNS void
```

Admin-only. Registers a relationship. `weight_column` must be numeric.
`label_column`, when supplied, provides per-row dynamic edge labels. A graph can
use up to 254 distinct user-facing edge labels; registering or building a graph
that would require another label fails with SQLSTATE `54000` and diagnostic
`PG004`.

`graph.add_edge()` supports two registration modes:

- FK-style edges: when `from_table` is already registered with
  `graph.add_table()`, each source row becomes an edge from that row's
  registered primary key to the value in `from_column`. In this mode,
  `to_column` names the target table primary-key column on `to_table`.
- Edge-table relationships: when `from_table` is not a registered node table,
  it is treated as a table of relationship rows. In this mode, both endpoint
  values are read from `from_table`: `from_column` is the source endpoint value
  and `to_column` is the target endpoint value.

For edge-table relationships, `to_column` must exist on the source edge table.
For FK-style edges, `to_column` must exist on the target table.

Many-to-many junction tables use edge-table mode. For example,
`graph.add_edge('public.follows', 'follower', 'public.users', 'followee', ...)`
registers `follows.follower -> follows.followee`. `to_table` is the preferred
target node table when it is registered; otherwise endpoint resolution falls
back across the registered node tables. Use the target table primary-key
column, such as `'id'`, only for FK-style edges from a registered source node
table.

### `graph.add_edge_to_graph()`

```sql
graph.add_edge_to_graph(
  graph_name text,
  from_table regclass,
  from_column text,
  to_table regclass,
  to_column text,
  label text,
  bidirectional boolean DEFAULT true,
  weight_column text DEFAULT NULL,
  label_column text DEFAULT NULL,
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL
) RETURNS void
```

Admin-only. Registers a relationship in a named graph without changing the
current session graph. Edge labels are unique and resolved inside the target
graph's registration set.

### `graph.add_filter_column()`

```sql
graph.add_filter_column(
  table_name regclass,
  column_name text,
  column_type text DEFAULT 'numeric'
) RETURNS void
```

Admin-only. Supported types are `numeric`, `text`, `boolean`, `date`,
`timestamptz`, and `uuid`.

### `graph.add_filter_column_to_graph()`

```sql
graph.add_filter_column_to_graph(
  graph_name text,
  table_name regclass,
  column_name text,
  column_type text DEFAULT 'numeric',
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL
) RETURNS void
```

Admin-only. Registers a traversal filter column in a named graph.

### `graph.registered_tables()`

```sql
graph.registered_tables()
RETURNS TABLE (
  table_name text,
  id_columns text[],
  columns text[],
  tenant_column text
)
```

Returns tables registered in the selected graph, or the default graph when no
session graph has been selected.

### `graph.registered_tables_for_graph()`

```sql
graph.registered_tables_for_graph(
  graph_name text,
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL
)
RETURNS TABLE (
  table_name text,
  id_columns text[],
  columns text[],
  tenant_column text
)
```

Returns tables registered in a named graph.

### `graph.registered_edges()`

```sql
graph.registered_edges()
RETURNS TABLE (
  from_table text,
  from_column text,
  to_table text,
  to_column text,
  label text,
  bidirectional boolean,
  weight_column text,
  label_column text
)
```

Returns edges registered in the selected graph, or the default graph when no
session graph has been selected.

### `graph.registered_edges_for_graph()`

```sql
graph.registered_edges_for_graph(
  graph_name text,
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL
)
RETURNS TABLE (
  from_table text,
  from_column text,
  to_table text,
  to_column text,
  label text,
  bidirectional boolean,
  weight_column text,
  label_column text
)
```

Returns edges registered in a named graph.

### `graph.list_edges()`

```sql
graph.list_edges(
  graph_name text,
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL
)
RETURNS TABLE (
  from_table text,
  from_column text,
  to_table text,
  to_column text,
  label text,
  bidirectional boolean,
  weight_column text,
  label_column text
)
```

Returns relationship mappings for one named graph. This is the graph-scoped
inspection form to use before editing relationship definitions.

### `graph.rename_edge()`

```sql
graph.rename_edge(
  graph_name text,
  old_label text,
  new_label text,
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL
) RETURNS void
```

Admin-only. Renames registered relationship labels inside one graph without
removing and re-adding the mapping. Rebuild the graph after calling.

### `graph.alter_edge()`

```sql
graph.alter_edge(
  graph_name text,
  label text,
  bidirectional boolean DEFAULT NULL,
  weight_column text DEFAULT NULL,
  label_column text DEFAULT NULL,
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL
) RETURNS void
```

Admin-only. Updates relationship options inside one graph and validates the
referenced endpoint, weight, and label columns. Omitted options keep their
current values. Rebuild the graph after calling.

### `graph.graph_map()`

```sql
graph.graph_map(
  graph_name text,
  format text DEFAULT 'json',
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL
) RETURNS jsonb
```

Returns deterministic metadata for one graph. The JSON includes graph identity,
registered node tables, relationship mappings, filter columns, build/sync/
projection status, and warnings such as missing builds, schema drift, ambiguous
relationship labels, or rebuild-required state. The map never exports source
rows, node payloads, or relationship data.

### `graph.remove_table()`

```sql
graph.remove_table(table_name regclass) RETURNS void
```

Admin-only. Removes table registration, associated filter columns, and edges
where the table is either endpoint. Rebuild after calling.

### `graph.remove_table_from_graph()`

```sql
graph.remove_table_from_graph(
  graph_name text,
  table_name regclass,
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL
) RETURNS void
```

Admin-only. Removes a table registration, associated filter columns, and edges
from a named graph only. Rebuild that graph after calling.

### `graph.remove_edge()`

```sql
graph.remove_edge(label text) RETURNS void

graph.remove_edge(
  graph_name text,
  label text,
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL
) RETURNS void
```

Admin-only. Removes edge registrations with the given label. The one-argument
form uses the selected graph, or the default graph when no graph has been
selected. The named-graph form removes the label from one graph only. Rebuild
after calling.

### `graph.remove_edge_from_graph()`

```sql
graph.remove_edge_from_graph(
  graph_name text,
  label text,
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL
) RETURNS void
```

Admin-only. Removes edge registrations with the given label from a named graph
only. Rebuild that graph after calling.

### `graph.estimate()`

```sql
graph.estimate()
RETURNS TABLE (
  estimated_nodes bigint,
  estimated_edges bigint,
  estimated_memory_mb double precision,
  memory_limit_mb int,
  fits_in_memory boolean
)
```

Estimates graph size from `pg_class.reltuples`.

## Discovery

### `graph.auto_discover()`

```sql
graph.auto_discover(
  schema_name text DEFAULT 'public',
  graph_name text DEFAULT NULL,
  build boolean DEFAULT true,
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL
)
RETURNS TABLE (
  item_type text,
  item_name text,
  details text
)
```

Admin-only. Discovers tables and FK edges in a schema and registers them in the
selected/default graph, or in `graph_name` when supplied. When `build` is true,
the target graph is selected and `graph.build()` runs after registration.

### `graph.auto_discover_tables()`

```sql
graph.auto_discover_tables(
  tables regclass[],
  tenant_column text DEFAULT NULL,
  graph_name text DEFAULT NULL,
  build boolean DEFAULT true,
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL
)
RETURNS TABLE (
  item_type text,
  item_name text,
  details text
)
```

Admin-only. Discovers selected tables and FK edges only among those tables and
registers them in the selected/default graph, or in `graph_name` when supplied.
When `build` is true, the target graph is selected and `graph.build()` runs
after registration.

### `graph.preview_discover()`

```sql
graph.preview_discover(
  schema_name text DEFAULT 'public',
  graph_name text DEFAULT NULL,
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL
)
RETURNS TABLE (
  item_type text,
  item_name text,
  details text
)
```

Previews schema discovery rows without writing registration catalog state.

### `graph.preview_discover_tables()`

```sql
graph.preview_discover_tables(
  tables regclass[],
  tenant_column text DEFAULT NULL,
  graph_name text DEFAULT NULL,
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL
)
RETURNS TABLE (
  item_type text,
  item_name text,
  details text
)
```

Previews selected-table discovery rows without writing registration catalog
state.

### `graph.create_row_predicate_subgraph()`

```sql
graph.create_row_predicate_subgraph(
  graph_name text,
  row_predicate jsonb,
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL
) RETURNS void
```

Always returns SQLSTATE `0A000` with diagnostic `PG018`. Row-predicate
subgraphs are not supported
because graph writes and sync must continue to obey PostgreSQL source-table
semantics. Use `graph.auto_discover_tables()` with an explicit table set and
optional `tenant_column` for catalog-subset subgraphs.

## Build Jobs

### `graph.build()`

```sql
graph.build()
RETURNS TABLE (
  nodes_loaded bigint,
  edges_loaded bigint,
  build_time_ms double precision,
  memory_used_mb double precision,
  sync_mode text,
  projection_mode text
)
```

Admin-only. Builds the graph synchronously using
`graph.default_projection_mode`.

```sql
graph.build(mode text)
RETURNS TABLE (
  nodes_loaded bigint,
  edges_loaded bigint,
  build_time_ms double precision,
  memory_used_mb double precision,
  sync_mode text,
  projection_mode text
)
```

Admin-only. Builds the graph synchronously with `mode` set to `csr_readonly` or
`mutable_overlay`. `mutable_overlay` requires `graph.mutable_enabled = on`.

```sql
graph.build(concurrently boolean)
RETURNS TABLE (
  build_id text,
  status text,
  nodes_loaded bigint,
  edges_loaded bigint,
  build_time_ms double precision,
  memory_used_mb double precision,
  sync_mode text,
  projection_mode text
)
```

Admin-only. `false` runs the compatibility synchronous path. `true` creates a
build job and attempts to launch a dynamic background worker.

### `graph.build_graph()`

```sql
graph.build_graph(
  graph_name text,
  force_persist boolean DEFAULT false,
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL
)
```

Admin-only. Selects the named graph and runs the synchronous build path for
that graph. Use this when a session should target a graph by name without a
separate `graph.set_current_graph()` call.

### `graph.build_async_graph()`

```sql
graph.build_async_graph(
  graph_name text,
  projection_mode text DEFAULT NULL,
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL
)
```

Admin-only. Selects the named graph, creates a durable build job for it, and
attempts to launch a dynamic background worker. The returned row includes
`graph_id` and `graph_name` so job ownership is visible without manual catalog
joins.

### `graph.build_status()`

```sql
graph.build_status(build_id text)
RETURNS TABLE (
  build_id text,
  status text,
  nodes_loaded bigint,
  edges_loaded bigint,
  build_time_ms double precision,
  memory_used_mb double precision,
  progress_phase text,
  progress_message text,
  started_at timestamp with time zone,
  finished_at timestamp with time zone,
  error text
)
```

Reads durable build job state, or returns `completed`/`not_found` for the
special synchronous zero UUID path.

### `graph.build_status_for_graph()`

```sql
graph.build_status_for_graph(
  graph_name text,
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL,
  max_rows integer DEFAULT 50
)
```

Returns the latest durable build jobs for the named graph, newest first. Rows
include graph metadata, progress fields, timestamps, and any recorded error.

## Sync And Maintenance

### `graph.enable_sync()`

```sql
graph.enable_sync() RETURNS void
```

Admin-only. Creates durable sync tables if needed and installs graph triggers on
the selected graph's registered source tables. Trigger rows are stored in the
global source-table sync log; replay and status APIs filter that log to the
selected graph's registered tables.

### `graph.apply_sync()`

```sql
graph.apply_sync()
RETURNS TABLE (
  inserts_applied bigint,
  updates_applied bigint,
  deletes_applied bigint
)
```

Admin-only. Applies pending sync rows for the selected graph's registered
source tables. For persisted `mutable_overlay` graphs, committed edge changes
are published into durable projection segments and the engine reloads the latest
manifest so other backends can read them without a full rebuild. Those durable
segments carry source-node range metadata when sync replay can determine the
affected range. Backend-local node/filter/tenant replay and legacy overlay paths
remain for sync surfaces that are not yet represented by durable segments.

### `graph.add_sync_policy()`

```sql
graph.add_sync_policy(
  graph_name text,
  schedule_interval_secs bigint DEFAULT 60,
  max_sync_lag_rows bigint DEFAULT NULL,
  enabled boolean DEFAULT true,
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL
)
RETURNS TABLE (...)
```

Admin-only. Creates a durable sync policy for the named graph and the backing
generic job row used by hosted schedulers. `schedule_interval_secs` records the
intended cadence in seconds. `max_sync_lag_rows` is optional policy metadata for
operator tooling. Hard `max_graph_jobs` quotas are enforced before the backing
job is inserted.

### `graph.alter_sync_policy()`

```sql
graph.alter_sync_policy(
  policy_id text,
  schedule_interval_secs bigint DEFAULT NULL,
  max_sync_lag_rows bigint DEFAULT NULL,
  enabled boolean DEFAULT NULL
)
RETURNS TABLE (...)
```

Admin-only. Updates a sync policy and its backing job. `NULL` arguments leave
the existing values unchanged.

### `graph.drop_sync_policy()`

```sql
graph.drop_sync_policy(policy_id text)
RETURNS TABLE (
  policy_id text,
  job_id text,
  graph_id text,
  graph_name text,
  dropped boolean
)
```

Admin-only. Removes the sync policy and its backing job.

### `graph.run_sync_policy()`

```sql
graph.run_sync_policy(policy_id text)
RETURNS TABLE (...)
```

Admin-only. Runs a sync policy immediately through the same replay path as
`graph.apply_sync()` and records a durable job run. Disabled policies return a
`disabled` run row and do not apply sync.

### `graph.sync_policy_status()`

```sql
graph.sync_policy_status(
  graph_name text DEFAULT NULL,
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL,
  max_rows integer DEFAULT 50
)
RETURNS TABLE (...)
```

Lists sync policies visible to the current role. When a graph name is provided,
the caller must be able to see that graph.

### `graph.jobs()`

```sql
graph.jobs(
  graph_name text DEFAULT NULL,
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL,
  max_rows integer DEFAULT 50
)
RETURNS TABLE (...)
```

Lists durable generic jobs visible to the current role. Current generic jobs are
sync policy jobs.

### `graph.job_runs()`

```sql
graph.job_runs(
  job_id text DEFAULT NULL,
  graph_name text DEFAULT NULL,
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL,
  max_rows integer DEFAULT 50
)
RETURNS TABLE (...)
```

Lists durable job runs, newest first. Rows include status, error details,
`rows_applied`, retry count, worker identity, and execution mode.

### `graph.job_stats()`

```sql
graph.job_stats(
  graph_name text DEFAULT NULL,
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL
)
RETURNS TABLE (...)
```

Summarizes visible generic jobs by graph, including run counts, failures,
disabled jobs, and the latest run timestamp.

### `graph.run_job()`

```sql
graph.run_job(job_id text)
RETURNS TABLE (...)
```

Admin-only. Runs a durable generic job immediately. For sync policy jobs this is
equivalent to `graph.run_sync_policy(policy_id)` after resolving the policy
attached to the job.

### `graph.run_due_jobs()`

```sql
graph.run_due_jobs(max_jobs integer DEFAULT 64)
RETURNS TABLE (...)
```

Admin-only. Runs due, enabled durable jobs through the hosted scheduler path.
Rows are selected by `next_run_at`, locked with `FOR UPDATE SKIP LOCKED`, and
then executed through the same primitive as `graph.run_job()`. Each job also
uses a graph/job advisory lock; if another runner already holds it, pgGraph
records a `lock_skipped` run instead of applying work twice.

### `graph.run_due_jobs_async()`

```sql
graph.run_due_jobs_async(max_jobs integer DEFAULT 64)
RETURNS boolean
```

Admin-only. Launches one internal background-worker pass over due durable jobs
and returns `true` once the worker is registered. The worker executes the same
claiming and run path as `graph.run_due_jobs()`, but records job runs with
`execution_mode = 'internal'`. It is a one-shot worker, not an always-on
scheduler loop.

### `graph.alter_job()`

```sql
graph.alter_job(
  job_id text,
  enabled boolean DEFAULT NULL,
  schedule_interval_secs bigint DEFAULT NULL,
  max_runtime_secs bigint DEFAULT NULL,
  max_retries integer DEFAULT NULL
)
RETURNS TABLE (...)
```

Admin-only. Updates durable job controls. For sync policy jobs, `enabled` and
`schedule_interval_secs` are kept in sync with the policy row.

### `graph.remove_job()`

```sql
graph.remove_job(job_id text)
RETURNS TABLE (
  job_id text,
  graph_id text,
  graph_name text,
  policy_kind text,
  removed boolean
)
```

Admin-only. Removes a durable generic job. Removing a sync policy job also
removes its policy row.

### `graph.run_scheduled_maintenance()`

```sql
graph.run_scheduled_maintenance()
RETURNS TABLE (
  applied_sync boolean,
  maintenance_started boolean,
  maintenance_job_id text,
  pending_sync_rows bigint,
  edge_buffer_used int,
  message text
)
```

Admin-only. Applies pending sync when `graph.sync_health()` says it is safe,
then starts `graph.maintenance(concurrently := true)` when refreshed health
still recommends compaction or rebuild work. pgGraph does not run an internal
scheduler; call this function from `pg_cron` or an external scheduler such as a
Kubernetes CronJob, systemd timer, Docker init schedule, or application
scheduler.

### `graph.vacuum()`

```sql
graph.vacuum()
RETURNS TABLE (
  nodes_before bigint,
  nodes_after bigint,
  tombstones_removed bigint,
  edges_rebuilt bigint,
  vacuum_time_ms double precision
)
```

Admin-only. Rebuilds from source tables and swaps in the rebuilt backend-local
engine; persistence writes the rebuilt base graph when requested by the caller.

### `graph.vacuum_graph()`

```sql
graph.vacuum_graph(
  graph_name text,
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL
)
```

Admin-only. Selects the named graph, then runs `graph.vacuum()` for that graph.

### `graph.maintenance()`

```sql
graph.maintenance(concurrently boolean DEFAULT false)
RETURNS TABLE (
  job_id text,
  status text,
  sync_rows_applied bigint,
  nodes_after bigint,
  edges_after bigint,
  vacuum_time_ms double precision,
  error text
)
```

Admin-only. Foreground path applies maintenance rebuild immediately. Background
path creates a durable maintenance job and launches a dynamic worker.

### `graph.maintenance_graph()`

```sql
graph.maintenance_graph(
  graph_name text,
  concurrently boolean DEFAULT false,
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL
)
```

Admin-only. Selects the named graph, then runs or queues maintenance for that
graph. Asynchronous rows include `graph_id` and `graph_name`.

### `graph.maintenance_status()`

```sql
graph.maintenance_status(job_id text DEFAULT NULL)
RETURNS TABLE (
  job_id text,
  status text,
  sync_rows_applied bigint,
  nodes_after bigint,
  edges_after bigint,
  vacuum_time_ms double precision,
  progress_phase text,
  progress_message text,
  started_at timestamp with time zone,
  finished_at timestamp with time zone,
  error text
)
```

With `NULL`, returns the latest 50 jobs. With a job ID, returns that job or a
single `not_found` row.

### `graph.maintenance_status_for_graph()`

```sql
graph.maintenance_status_for_graph(
  graph_name text,
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL,
  max_rows integer DEFAULT 50
)
```

Returns the latest durable maintenance jobs for the named graph, newest first.
Rows include graph metadata, progress fields, timestamps, and any recorded
error.

## GQL

### `graph.gql()`

```sql
graph.gql(
  query text,
  params jsonb DEFAULT NULL,
  hydrate boolean DEFAULT true
)
RETURNS TABLE (row jsonb)
```

Executes the documented GQL-compatible subset over the active graph. Each
output row is a JSONB object keyed by the `RETURN` aliases or expressions. Node
labels resolve to registered table names, relationship types resolve to
registered edge labels, and `$parameters` are read from the `params` JSON
object. See [GQL JSONB result format](/user_guide/querying#gql-jsonb-result-format)
for the stable node, relationship, path, scalar, and null shapes returned to
SQL clients and SDKs.

Read pattern support:

- Node-only `MATCH`.
- Single-relationship graph patterns.
- Comma-separated fixed single-hop patterns with reused node variables, such
  as `MATCH (u:users)-[:works_at]->(c:companies),
  (v:users)-[:works_at]->(c)`, including fixed single-hop path variables.
- Fixed path variables: `MATCH p=()-[]->()`,
  `MATCH p=(s)-[r]->(e)`, `MATCH p=()<-[]-()`,
  `MATCH p=()-[]-()`, and `MATCH p=(a)-[]->(b)-[]->(c)`.
- Node-only and top-level single-relationship `OPTIONAL MATCH`.

Optional node-only matches emit one JSON `null` row when no node matches.
Optional relationship matches return matched relationship rows, and emit one
null-extended row for each source node with no qualifying target.
Null-extended rows use JSON `null` for missing nodes, node properties,
relationship values, and path values.

Return value support:

- Node values and scalar properties.
- Node values, scalar properties, and fixed single-hop relationship variables
  from supported multi-pattern joins.
- JSONB list/map properties, including dotted paths such as `u.profile.plan`.
- Coordinate-only single-hop relationship identities when `hydrate := false`,
  and hydrated edge-row properties on whole relationship values when the
  relationship is backed by a registered edge row table and `hydrate := true`.
- Bounded relationship path values with a minimum of at least one hop.
- Fixed path values, including named node variables and single-hop named
  relationship variables.
- Fixed single-hop path values from supported multi-pattern joins.
- Path functions over fixed single-hop path values from supported
  multi-pattern joins.
- `RETURN DISTINCT` for non-wildcard path reads, including projected rows from
  supported multi-pattern joins.
- `RETURN` aggregates: `count`, `sum`, `avg`, `min`, `max`, and `collect`.

JSONB arrays and objects keep their JSON shape. A missing key inside a JSONB
property path projects as JSON `null`, but it does not satisfy `IS NULL`;
explicit JSON `null` does.

Aggregate behavior:

- Non-aggregate return items are implicit grouping keys.
- `count(*)` counts every input row.
- `count(expr)`, `sum`, `avg`, `min`, `max`, and `collect` ignore JSON
  `null` inputs.
- Aggregate-only empty inputs return one row with `0`, `null`, or `[]`
  according to the aggregate.
- Aggregate `DISTINCT` is supported.
- Multi-pattern aggregate reads support `count(*)`, node-value
  `count`/`collect`, relationship-value and path-value `count`/`collect`, and
  node-property `count`, `sum`, `avg`, `min`, `max`, and `collect`.

Wildcard path-variable reads support `WHERE`, `RETURN`, `SKIP`, and `LIMIT` in this
phase. They can bind node variables across fixed paths, bind relationship
variables for single-hop paths, and use concrete node labels or concrete
relationship types as filters, including type alternation such as
`p=()-[:friend|works_at]->()`. A single wildcard segment may use bounded
variable length, such as `p=(s)-[r*1..3]->(e)`. Multi-segment wildcard paths may
also include bounded variable-length segments when those segments do not bind
segment-local node or relationship variables. Named relationship variables on
single bounded segments are path values, and named target nodes bind to the
emitted endpoint. Variable-length wildcard paths use bounded walk
semantics: nodes and relationships may repeat within the explicit maximum hop
bound, and row caps are still enforced before projection.
`WHERE` can read properties
from named path-node variables only when every possible concrete table for that
variable exposes the same registered property or JSONB property path, including
the same registered nested paths under a JSONB root. They
reject `ORDER BY`, `RETURN DISTINCT`, `WITH`, aggregates, optional matches,
writes, relationship properties, relationship variables in multi-segment paths,
named target variables on multi-segment variable-length boundaries, and
relationship type alternation outside wildcard path-variable reads.

Multi-pattern reads support fixed single-hop patterns with concrete labels and
relationship types. Reused node variables join by graph coordinate, independent
patterns produce Cartesian combinations up to the GQL result cap, joined
node-property `WHERE` predicates filter after all referenced slots are bound,
fixed single-hop relationship variables can be returned as coordinate identity
objects, fixed single-hop path variables can be returned as path JSON objects,
`nodes(path)`, `relationships(path)`, and `length(path)` can project from fixed
single-hop path variables, node, node-property, relationship-value, and
path-value aggregate inputs can be grouped over joined rows, `WITH` can
project node, node-property, relationship, path, path-function, and
grouped aggregate aliases over supported aggregate inputs between a
multi-pattern `MATCH` and `RETURN`, and
`WITH DISTINCT` can deduplicate non-aggregate projected row streams or mark
direct aggregate projection clauses distinct, later `WITH DISTINCT` can
deduplicate projected aggregate aliases, final aggregate expressions can read
projected aggregate aliases after an aggregate `WITH` boundary,
`ORDER BY` can sort joined node properties or returned property aliases, and
`RETURN DISTINCT`, `SKIP`, and `LIMIT` apply after projection. Distinct
multi-pattern reads must order by returned scalar expressions. Optional
multi-pattern reads null-extend unmatched fixed single-hop pattern expansions
and return JSON `null` for missing node, relationship, and path projections.
Bounded variable-length relationship patterns can participate in
multi-pattern joins for node and path projections. Variable-length relationship
variables are treated as path values. Relationship properties,
relationship/path property aggregate inputs, and numeric aggregates over
relationship/path values remain rejected.

Path behavior:

- Named relationship patterns can be returned as path objects with
  `_path.nodes` and `_path.relationships`.
- Fixed path variables return the same path object shape while scanning
  all registered edge labels or applying the concrete labels/types in the
  pattern.
- `nodes(path)`, `relationships(path)`, and `length(path)` return those
  components directly.

Projection and deduplication:

- `WITH` introduces projection stages over the matched row stream.
- Downstream `RETURN` and `ORDER BY` clauses see projected aliases, including
  shadowed names.
- `WITH DISTINCT` deduplicates the row stream at that projection boundary.
- DISTINCT forms use a bounded in-memory key set and abort if the unique-key
  cap is exceeded.
- `RETURN DISTINCT` queries must order by returned scalar expressions.

Hydration behavior:

- `hydrate := true` returns hydrated source rows for node values when
  PostgreSQL ACL and RLS allow them.
- `hydrate := true` also returns readable edge-row columns on whole
  relationship values for registered edge-row mappings. Reserved graph keys
  such as `_type`, `_start`, and `_end` remain pgGraph-owned.
- If a required graph coordinate cannot be read from its backing source table,
  `graph.gql()` raises SQLSTATE `22000` with diagnostic `PG017` rather than
  returning a partial empty node
  object.
- Queries that only need identifiers should project identifier properties,
  such as `RETURN n.id AS id`, rather than returning the whole node.
- `hydrate := false` returns coordinate-only node and relationship objects for
  callers that explicitly want compact graph coordinates.

Not included in this release's GQL subset:

- Relationship property expressions such as `r.weight`.
- Multi-pattern relationship/path property aggregate inputs, numeric
  aggregates over relationship/path values, and relationship properties.

`CREATE (n:label {properties}) RETURN ...` is supported for a single node whose
label maps to a registered source table. It requires a `mutable_overlay`
projection and inserts the PostgreSQL source row first, so table privileges,
constraints, triggers, RLS, and MVCC remain authoritative. `CREATE` is rejected
on `csr_readonly`. The same transaction can read created nodes through
node-only `MATCH`, relationship traversal, multi-pattern joins, wildcard path
reads, and later relationship `CREATE` statements through backend-local
temporary graph node indexes.
After all statement triggers finish, pgGraph re-reads the inserted row using
the primary key returned by PostgreSQL. If a trigger side effect removes that
row or moves it to another primary key, pgGraph raises SQLSTATE `22000` with
diagnostic `PG017` and rolls back the insert. Ordinary property changes made by
triggers are returned from the final source row.

`MATCH (a:label {...}), (b:label {...}) CREATE (a)-[r:type {properties}]->(b)
RETURN ...` is supported for a single directed relationship backed by a
registered edge row table. pgGraph matches exactly one source endpoint and one
target endpoint, locks and re-checks both endpoint predicates, inserts the
PostgreSQL edge row first, and records a transaction-local graph edge so later
reads in the same transaction can see the relationship. Relationship `CREATE`
requires concrete endpoint labels, a concrete relationship type, and a named
relationship variable. Properties map to ordinary edge-row columns; endpoint
columns and a registered dynamic label column are filled from the graph pattern
and cannot be supplied as user properties. For dynamic relationship
label-column mappings, the GQL relationship type is written into the registered
label column and must already exist in the built graph's dynamic label
registry. After all statement triggers finish, pgGraph re-reads the edge row by
its registered source key. If a trigger side effect removes or changes that key,
either registered endpoint, or the registered dynamic label, pgGraph raises
SQLSTATE `22000` with diagnostic `PG017` and rolls back the source insert rather
than recording graph coordinates for different topology. Ordinary property
changes are returned from the final source row.
Registered partitioned relationship tables use their parent relation as the
mapping identity while PostgreSQL routes each insert to a child partition.
Composite relationship primary keys are preserved as the source identity used
by later hydration and deletion.
When concurrent relationship `CREATE` statements use the same registered
source-row primary key, PostgreSQL selects one winner and reports SQLSTATE
`23505` to the conflicting statement. A concurrent relationship `DELETE` that
loses its matched source row reports SQLSTATE `22000`. Rejected statements do
not retain transaction-local graph changes.

`MATCH (n:label {...}) SET n.property = value RETURN ...` is supported for a
single matched node and one registered non-key, non-tenant property column. It requires a
`mutable_overlay` projection, updates the PostgreSQL source row first, and
refreshes the backend's typed filter index value when the property is also a
registered filter column. PostgreSQL rejects type mismatches according to the
source column type. Before updating, pgGraph locks the matched source row and
re-checks the matched GQL predicate against the current PostgreSQL row.
If a trigger changes the registered primary key or moves the returned row out
of the active tenant scope, pgGraph raises SQLSTATE `22000` with diagnostic
`PG017` and rolls back the update. Trigger changes to ordinary property values
are returned as the authoritative PostgreSQL result. A registered tenant value
is projection identity and cannot change during this write even when tenant
scope enforcement is disabled; such a change is also rejected and rolled back.

`MATCH (n:label {...}) REMOVE n.property RETURN ...` is supported for a single
matched node and one registered non-key, non-tenant property. It requires a
`mutable_overlay` projection, updates the PostgreSQL source row first, and sets
scalar columns to SQL `NULL`. For registered dotted JSONB property paths,
`REMOVE` drops the addressed key and missing keys remain missing, so repeated
removes are idempotent. Removing labels is rejected because labels map to
registered source tables. Before updating, pgGraph locks the matched source row
and re-checks the matched GQL predicate against the current PostgreSQL row.
Primary-key changes, registered tenant-identity changes, and moves outside the
active tenant scope made by a trigger are rejected and rolled back under the
same identity rule as `SET`.

`MATCH (a:label)-[r:type]->(b:label) DELETE r RETURN ...` is supported for a
single matched relationship backed by a registered edge row table. Endpoint
labels and the relationship type may be omitted only when the wildcard pattern
resolves to exactly one static-label registered edge-row mapping; ambiguous
wildcard delete patterns and dynamic relationship label-column mappings are
rejected before execution. It requires a `mutable_overlay`
projection, deletes exactly one PostgreSQL edge row first, then tombstones the
graph edge by the edge row's registered primary-key identity in the current
backend. Parallel rows with the same endpoints and relationship type remain
visible. Bidirectional edge registrations hide both neighbor directions for
that identity after the delete. Endpoint nodes are not deleted. Undirected
relationship `DELETE` is not part of this release's GQL subset. Before deleting
the edge row, pgGraph locks the matched endpoint source rows and re-checks the
matched GQL predicate against the current PostgreSQL rows.

`MATCH (n:label {...}) DETACH DELETE n RETURN ...` is supported for a single
matched node. It requires a `mutable_overlay` projection, deletes registered
incident edge-row relationships first, then deletes the PostgreSQL node row.
The current backend records transaction-local edge and node tombstones so later
reads in the same transaction do not see the deleted topology. Incident
relationships must be backed by registered edge row tables. Multi-row graph
writes are not part of this release's GQL subset.
Before deleting incident edge rows or the node row, pgGraph locks the matched
node source row and re-checks the matched GQL predicate against the current
PostgreSQL row. Partitioned node and standalone relationship tables are
supported; foreign-key endpoints are resolved through the registered partition
roots. A constraint or trigger failure in any incident-edge or node deletion
rolls back the complete statement without leaving transaction-local
tombstones.

`MERGE (n:label {properties}) [ON CREATE SET ...] [ON MATCH SET ...] RETURN ...`
is supported for a single node whose label maps to a registered source table. It
requires a `mutable_overlay` projection and uses the registered primary key
columns as the match identity. Existing rows are locked with `FOR UPDATE` only
when `ON MATCH SET` can update them; new rows are inserted with PostgreSQL
`ON CONFLICT DO NOTHING` on that identity.
`ON CREATE` expressions are evaluated only for inserted rows, and `ON MATCH`
expressions are evaluated only for matched rows. `MERGE` requires source-table
`SELECT` and `INSERT`; `UPDATE` is required only for statements that include
`ON MATCH SET`. The matched update retains the active tenant scope and rejects
trigger changes to the registered primary key or registered tenant identity,
regardless of whether tenant enforcement is active, before the result is exposed
or any local projection index is updated. The insert branch
also re-reads the returned primary key after statement triggers and rolls back
if a trigger side effect moved or removed the row.

### `graph.gql_explain()`

```sql
graph.gql_explain(query text) RETURNS text
```

Parses, binds, and lowers a GQL query and returns stable explain text for the
frontend, logical-plan, and physical-plan stages. It performs the same catalog
binding and ACL preflight as `graph.gql()` but does not execute the plan.
Node-only identity predicates explain as `NodeLookup(...)` when the query can
resolve a registered primary key directly.

### `graph.cypher()`

```sql
graph.cypher(
  query text,
  params jsonb DEFAULT NULL,
  hydrate boolean DEFAULT true
) RETURNS TABLE(row jsonb)
```

Executes the openCypher compatibility subset. This function is separate from
`graph.gql()` and intentionally does not claim full openCypher compatibility. Supported
overlapping read patterns and mapped writes lower into the same shared IR as
GQL, so the same PostgreSQL catalog registration, ACL, RLS, constraint, trigger,
MVCC, and mutable-overlay rules apply. Cypher-only procedure, DDL, import, and
control-flow constructs are rejected with the GQL unsupported-feature SQLSTATE.

### `graph.cypher_explain()`

```sql
graph.cypher_explain(query text) RETURNS text
```

Parses, binds, and lowers an openCypher-compatible query without executing it.
The explain output is the same physical-plan shape used by `graph.gql_explain()`
for equivalent supported queries.

### `graph.cypher_compatibility()`

```sql
graph.cypher_compatibility()
RETURNS TABLE(feature text, status text, notes text)
```

Returns the current openCypher compatibility matrix. Use it to distinguish
supported overlapping syntax from rejected Cypher-only features and from the
explicit non-claim of full openCypher compatibility.

### `graph.pgq()`

```sql
graph.pgq(query text)
```

This endpoint is a placeholder. Full public SQL/PGQ execution is permanently
rejected until PostgreSQL graph-pattern hooks are stable in supported PostgreSQL
versions.

## Search

### `graph.search()`

```sql
graph.search(
  property_key text,
  property_value text,
  table_filter regclass DEFAULT NULL,
  mode text DEFAULT 'contains',
  case_sensitive boolean DEFAULT false,
  max_rows int DEFAULT 100,
  row_offset int DEFAULT 0,
  tenant text DEFAULT NULL,
  hydrate boolean DEFAULT true
)
RETURNS TABLE (
  node_table oid,
  node_id text,
  match_type text,
  score real,
  verified boolean,
  node jsonb,
  node_table_name text
)
```

Searches registered source-table columns.

Results are deduplicated by `(node_table, node_id)`, ordered by table OID then
node ID, and paginated after the global merge. Per-table source scans are
bounded to `row_offset + max_rows` candidates.

### `graph.search_nodes()`

```sql
graph.search_nodes(
  property_key text,
  property_value text,
  table_filter regclass DEFAULT NULL,
  mode text DEFAULT 'contains',
  case_sensitive boolean DEFAULT false,
  max_rows int DEFAULT 100,
  row_offset int DEFAULT 0,
  tenant text DEFAULT NULL
)
RETURNS TABLE (
  node_table oid,
  node_id text,
  match_type text,
  score real,
  verified boolean,
  node_table_name text
)
```

Coordinate-only search.

## Traversal

### `graph.node_ref`

```sql
CREATE TYPE graph.node_ref AS (
  node_table regclass,
  node_id text
);

graph.node_ref(node_table regclass, node_id text) RETURNS graph.node_ref
```

### `graph.node_ref_string()`

```sql
graph.node_ref_string(table_name regclass, node_id text) RETURNS text
```

Returns canonical JSON text for strict traversal specs.

### `graph.get_node()`

```sql
graph.get_node(
  graph_name text,
  label text,
  id text,
  hydrate boolean DEFAULT true,
  tenant text DEFAULT NULL,
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL
)
RETURNS TABLE (
  graph_id text,
  graph_name text,
  node_table oid,
  node_table_name text,
  node_id text,
  node_idx bigint,
  node jsonb
)
```

Resolves one registered node in a named graph by `(graph_name, label, id)`.
Returns zero rows when the node is absent or the source row is not visible
through PostgreSQL ACL/RLS. With `hydrate := false`, the coordinate fields are
returned and `node` is `NULL`.

### `graph.get_neighbors()`

```sql
graph.get_neighbors(
  graph_name text,
  label text,
  id text,
  direction text DEFAULT 'any',
  edge_types text[] DEFAULT NULL,
  tenant text DEFAULT NULL,
  hydrate boolean DEFAULT true,
  max_rows int DEFAULT 1000,
  graph_tenant text DEFAULT NULL,
  graph_namespace text DEFAULT NULL
)
RETURNS TABLE (
  root_table oid,
  root_id text,
  node_table oid,
  node_id text,
  depth int,
  path jsonb,
  edge_path jsonb,
  node jsonb,
  root_table_name text,
  node_table_name text,
  capped boolean
)
```

Resolves the start node by registered business id and expands one hop. The
returned row shape matches `graph.traverse()`. Tenant scope is applied before
expansion.

### `graph.traverse()` single-start

```sql
graph.traverse(
  seed_table regclass,
  seed_id text,
  max_depth int DEFAULT current_setting('graph.default_max_depth')::int,
  edge_types text[] DEFAULT NULL,
  direction text DEFAULT 'any',
  node_tables oid[] DEFAULT NULL,
  filter jsonb DEFAULT NULL,
  tenant text DEFAULT NULL,
  strategy text DEFAULT 'bfs',
  uniqueness text DEFAULT 'node_global',
  include_start boolean DEFAULT true,
  hydrate boolean DEFAULT true,
  max_rows int DEFAULT 1000,
  row_offset int DEFAULT 0,
  max_nodes int DEFAULT COALESCE(NULLIF(current_setting('graph.max_nodes', true), '')::int, 100000),
  max_frontier int DEFAULT COALESCE(NULLIF(current_setting('graph.max_frontier', true), '')::int, 100000)
)
RETURNS TABLE (
  root_table oid,
  root_id text,
  node_table oid,
  node_id text,
  depth int,
  path jsonb,
  edge_path jsonb,
  node jsonb,
  root_table_name text,
  node_table_name text,
  capped boolean
)
```

### `graph.traverse()` multi-start arrays

```sql
graph.traverse(
  start_tables regclass[],
  start_ids text[],
  ...
)
```

`start_tables[i]` pairs with `start_ids[i]`.
With `uniqueness := 'node_global'`, duplicate reached nodes are removed across
the merged multi-start result before final pagination. With
`uniqueness := 'node_per_root'`, a reached node may appear once per root.

### `graph.traverse()` node-ref array

```sql
graph.traverse(
  starts graph.node_ref[],
  max_depth int DEFAULT current_setting('graph.default_max_depth')::int,
  ...
)
```

SQL wrapper over the parallel-array overload.

### `graph.traverse_search()`

```sql
graph.traverse_search(
  property_key text,
  property_value text,
  table_filter regclass DEFAULT NULL,
  search_mode text DEFAULT current_setting('graph.default_search_mode'),
  case_sensitive boolean DEFAULT current_setting('graph.default_case_sensitive')::boolean,
  search_max_rows int DEFAULT 100,
  search_row_offset int DEFAULT 0,
  max_depth int DEFAULT current_setting('graph.default_max_depth')::int,
  edge_types text[] DEFAULT NULL,
  direction text DEFAULT 'any',
  node_tables oid[] DEFAULT NULL,
  filter jsonb DEFAULT NULL,
  tenant text DEFAULT NULL,
  strategy text DEFAULT 'bfs',
  uniqueness text DEFAULT 'node_per_root',
  include_start boolean DEFAULT true,
  hydrate boolean DEFAULT current_setting('graph.default_hydrate')::boolean,
  max_rows int DEFAULT 1000,
  row_offset int DEFAULT 0
)
RETURNS TABLE (
  root_table oid,
  root_id text,
  node_table oid,
  node_id text,
  depth int,
  path jsonb,
  edge_path jsonb,
  node jsonb,
  root_table_name text,
  node_table_name text,
  capped boolean
)
```

Searches start nodes, then traverses from verified matches.
`uniqueness` has the same merged-result behavior as multi-start
`graph.traverse()`.

### `graph.format_path()`

```sql
graph.format_path(
  path jsonb,
  edge_path jsonb,
  separator text DEFAULT ' | '
) RETURNS text
```

Formats a path and edge path into readable hop text.

## Filter Constructors

```sql
graph.equals(column_name text, value jsonb) RETURNS jsonb
graph.equals(column_name text, value text) RETURNS jsonb
graph.equals(column_name text, value bigint) RETURNS jsonb
graph.eq(...) RETURNS jsonb

graph.not_equals(column_name text, value jsonb) RETURNS jsonb
graph.neq(column_name text, value jsonb) RETURNS jsonb
graph.neq(column_name text, value text) RETURNS jsonb
graph.neq(column_name text, value bigint) RETURNS jsonb

graph.in(column_name text, values jsonb) RETURNS jsonb
graph.not_in(column_name text, values jsonb) RETURNS jsonb
graph.contains_text(column_name text, value text) RETURNS jsonb
graph.prefix_text(column_name text, value text) RETURNS jsonb
graph.is_null(column_name text) RETURNS jsonb
graph.is_not_null(column_name text) RETURNS jsonb

graph.greater_than(column_name text, value jsonb) RETURNS jsonb
graph.greater_than(column_name text, value bigint) RETURNS jsonb
graph.gt(column_name text, value jsonb) RETURNS jsonb
graph.gt(column_name text, value bigint) RETURNS jsonb

graph.at_least(column_name text, value jsonb) RETURNS jsonb
graph.gte(column_name text, value jsonb) RETURNS jsonb
graph.gte(column_name text, value bigint) RETURNS jsonb

graph.less_than(column_name text, value jsonb) RETURNS jsonb
graph.lt(column_name text, value jsonb) RETURNS jsonb
graph.lt(column_name text, value bigint) RETURNS jsonb

graph.at_most(column_name text, value jsonb) RETURNS jsonb
graph.lte(column_name text, value jsonb) RETURNS jsonb
graph.lte(column_name text, value bigint) RETURNS jsonb

graph.between(column_name text, lower jsonb, upper jsonb) RETURNS jsonb
graph.on_node(filter jsonb) RETURNS jsonb
graph.all(filters jsonb[]) RETURNS jsonb
```

## Workflow API

### `graph.find()`

```sql
graph.find(
  property_key text,
  property_value text,
  table_name regclass DEFAULT NULL,
  mode text DEFAULT 'contains',
  case_sensitive boolean DEFAULT false,
  max_rows int DEFAULT 20,
  row_offset int DEFAULT 0,
  tenant text DEFAULT NULL
)
RETURNS TABLE (
  node_table oid,
  node_table_name text,
  node_id text,
  match_type text,
  score real,
  verified boolean,
  rank int,
  node jsonb
)
```

### `graph.expand()`

```sql
graph.expand(
  seed_table regclass,
  seed_id text,
  max_depth int DEFAULT current_setting('graph.default_max_depth')::int,
  edge_types text[] DEFAULT NULL,
  direction text DEFAULT 'any',
  target_table regclass DEFAULT NULL,
  target_tables regclass[] DEFAULT NULL,
  where_node jsonb DEFAULT NULL,
  tenant text DEFAULT NULL,
  max_rows int DEFAULT 50,
  row_offset int DEFAULT 0,
  include_start boolean DEFAULT false
)
RETURNS TABLE (
  root_table oid,
  root_id text,
  root_table_name text,
  node_table oid,
  node_table_name text,
  node_id text,
  depth int,
  rank int,
  path jsonb,
  edge_path jsonb,
  readable_path text,
  node jsonb,
  truncated boolean
)
```

### `graph.find_related()`

```sql
graph.find_related(
  property_key text,
  property_value text,
  source_table regclass DEFAULT NULL,
  search_mode text DEFAULT current_setting('graph.default_search_mode'),
  case_sensitive boolean DEFAULT current_setting('graph.default_case_sensitive')::boolean,
  search_max_rows int DEFAULT 1,
  search_row_offset int DEFAULT 0,
  max_depth int DEFAULT current_setting('graph.default_max_depth')::int,
  edge_types text[] DEFAULT NULL,
  direction text DEFAULT 'any',
  target_table regclass DEFAULT NULL,
  target_tables regclass[] DEFAULT NULL,
  where_node jsonb DEFAULT NULL,
  tenant text DEFAULT NULL,
  max_rows int DEFAULT 20,
  row_offset int DEFAULT 0,
  include_counts boolean DEFAULT false,
  candidate_limit int DEFAULT 10000,
  include_start boolean DEFAULT false
)
RETURNS TABLE (
  root_table oid,
  root_id text,
  root_table_name text,
  node_table oid,
  node_table_name text,
  node_id text,
  depth int,
  score real,
  rank int,
  path jsonb,
  edge_path jsonb,
  readable_path text,
  node jsonb,
  candidate_count bigint,
  filtered_count bigint,
  truncated boolean
)
```

### `graph.path()`

```sql
graph.path(
  source_table regclass,
  source_id text,
  target_table regclass,
  target_id text,
  max_depth int DEFAULT 20
)
RETURNS TABLE (
  step int,
  node_table oid,
  node_table_name text,
  node_id text,
  edge_label text,
  readable_path text,
  node jsonb
)
```

### `graph.connection()`

```sql
graph.connection(
  source_key text,
  source_value text,
  target_key text,
  target_value text,
  source_table regclass DEFAULT NULL,
  target_table regclass DEFAULT NULL,
  source_k int DEFAULT 3,
  target_k int DEFAULT 3,
  search_mode text DEFAULT 'contains',
  max_depth int DEFAULT 6
)
RETURNS TABLE (
  source_table_name text,
  source_id text,
  target_table_name text,
  target_id text,
  hop_count int,
  step int,
  node_table oid,
  node_table_name text,
  node_id text,
  edge_label text,
  readable_path text,
  node jsonb
)
```

### `graph.neighborhood()`

```sql
graph.neighborhood(
  property_key text,
  property_value text,
  source_table regclass DEFAULT NULL,
  search_mode text DEFAULT 'contains',
  search_max_rows int DEFAULT 1,
  max_depth int DEFAULT 4,
  edge_types text[] DEFAULT NULL,
  direction text DEFAULT 'any',
  tenant text DEFAULT NULL,
  sample_k int DEFAULT 5,
  node_limit int DEFAULT 10000
)
RETURNS TABLE (
  depth int,
  node_table oid,
  node_table_name text,
  node_count bigint,
  sample_nodes jsonb,
  truncated boolean
)
```

## Paths

### `graph.shortest_path()`

```sql
graph.shortest_path(
  source_table regclass,
  source_id text,
  target_table regclass,
  target_id text,
  max_depth int DEFAULT 20,
  hydrate boolean DEFAULT true
)
RETURNS TABLE (
  step int,
  node_table oid,
  node_id text,
  edge_label text,
  node jsonb,
  node_table_name text
)
```

Returns no rows when no path is found. Pending trigger-sync edge overlays are
included, so a backend that has applied sync sees inserted/deleted unweighted
edges before the next vacuum or maintenance rebuild.

### `graph.weighted_shortest_path()`

```sql
graph.weighted_shortest_path(
  source_table regclass,
  source_id text,
  target_table regclass,
  target_id text
)
RETURNS TABLE (
  step int,
  node_table oid,
  node_table_name text,
  node_id text,
  edge_label text,
  edge_weight bigint,
  step_cost bigint,
  total_cost bigint
)
```

Uses registered edge weights and returns one row per path step. Step `0` is the
source node and has NULL `edge_label` and `edge_weight`. `step_cost` is the
cumulative cost at that step, and `total_cost` is repeated on every returned row.
Returns no rows when no weighted path exists or the graph has no weight data.
If pending edge overlays exist, weighted path queries fail with SQLSTATE
`0A000` and diagnostic `PG018` until
`graph.vacuum()` or `graph.maintenance()` merges them, because overlay edge
mutations do not carry edge weights.

## Aggregation

### `graph.path_count_estimate()`

```sql
graph.path_count_estimate(traversal jsonb)
RETURNS TABLE (
  estimated_paths bigint,
  exact boolean,
  capped boolean
)
```

### `graph.aggregate()`

```sql
graph.aggregate(
  traversal jsonb,
  aggregations jsonb,
  scope text DEFAULT 'returned_nodes',
  path_limit int DEFAULT COALESCE(NULLIF(current_setting('graph.max_exact_path_count', true), '')::int, 100000)
) RETURNS jsonb
```

Supported `scope` values: `returned_nodes`, `chosen_parent_path`,
`all_possible_paths`.

## Components

### `graph.connected_components()`

```sql
graph.connected_components()
RETURNS TABLE (
  node_table oid,
  node_id text,
  component_id bigint,
  component_size int
)
```

Admin-only. Returns one row per active node. Pending trigger-sync edge overlays
are included in the component computation.

### `graph.component_stats()`

```sql
graph.component_stats()
RETURNS TABLE (
  num_components int,
  largest_component int,
  num_isolated_nodes int,
  total_active_nodes int
)
```

Admin-only.

### `graph.components()`

```sql
graph.components(
  max_rows int DEFAULT 100,
  row_offset int DEFAULT 0
)
RETURNS TABLE (
  component_id bigint,
  component_size bigint,
  rank int
)
```

Admin-only. Ordered by descending component size.

### `graph.largest_component()`

```sql
graph.largest_component(
  max_rows int DEFAULT 100,
  row_offset int DEFAULT 0,
  hydrate boolean DEFAULT true
)
RETURNS TABLE (
  component_id bigint,
  node_table oid,
  node_id text,
  node jsonb
)
```

Admin-only.

### `graph.component()`

```sql
graph.component(
  component_id bigint,
  max_rows int DEFAULT 100,
  row_offset int DEFAULT 0,
  hydrate boolean DEFAULT true
)
RETURNS TABLE (
  component_id bigint,
  node_table oid,
  node_id text,
  node jsonb
)
```

Admin-only.

### `graph.isolated_nodes()`

```sql
graph.isolated_nodes(
  max_rows int DEFAULT 100,
  row_offset int DEFAULT 0,
  hydrate boolean DEFAULT true
)
RETURNS TABLE (
  component_id bigint,
  node_table oid,
  node_id text,
  node jsonb
)
```

Admin-only.
