# SQL API Reference

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

## Function Index

| Category | Functions |
|---|---|
| Availability | `test_enabled`, `status`, `memory_profile`, `sync_health`, `reset` |
| Registration | `add_table`, `add_edge`, `add_filter_column`, `registered_tables`, `registered_edges`, `remove_table`, `remove_edge`, `estimate` |
| Discovery | `auto_discover`, `auto_discover_tables` |
| Build jobs | `build`, `build_status` |
| Sync and maintenance | `enable_sync`, `apply_sync`, `run_scheduled_maintenance`, `vacuum`, `maintenance`, `maintenance_status` |
| 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.test_enabled()`

```sql
graph.test_enabled() RETURNS boolean
```

Returns the current `graph.enabled` value.

### `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` is `memory_limit`,
`edge_buffer_full`, or another typed reason. `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.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. Shared mmap
bytes are counted once; backend-private heap is multiplied by
`concurrent_backends`, with a minimum of one. This is an operator sizing aid,
not a live connection counter or cluster-wide hard cap.

### `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
)
```

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.

### `graph.reset()`

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

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

## Registration

### `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_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 `PG004`.

### `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.registered_tables()`

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

### `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
)
```

### `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_edge()`

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

Admin-only. Removes edge registrations with the given label. Rebuild 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')
RETURNS TABLE (
  item_type text,
  item_name text,
  details text
)
```

Admin-only. Discovers tables and FK edges in a schema, registers them, then
calls `graph.build()`.

### `graph.auto_discover_tables()`

```sql
graph.auto_discover_tables(
  tables regclass[],
  tenant_column 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, then
calls `graph.build()`.

## 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_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.

## 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
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 to the backend-local engine.

### `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.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_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.

## 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.
- Top-level single-relationship `OPTIONAL MATCH`.

Optional 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 target nodes, target properties, and relationship values.

Return value support:

- Node values and scalar properties.
- JSONB list/map properties, including dotted paths such as `u.profile.plan`.
- Coordinate-only single-hop relationship identities.
- Bounded relationship path values with a minimum of at least one hop.
- `RETURN DISTINCT`.
- `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.

Path behavior:

- Named relationship patterns can be returned as path objects with
  `_path.nodes` and `_path.relationships`.
- `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.
- If a required graph coordinate cannot be read from its backing source table,
  `graph.gql()` raises `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 objects for callers that
  explicitly want the compact `{table, id}` shape.

Not included in this release's GQL subset:

- Relationship source-row hydration.
- Path-function `WITH` projections.
- Aggregate `WITH` projections.
- Full path variable syntax.
- Node-only optional matches.
- Multi-pattern optional joins.

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

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

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

`MATCH (a:label)-[r:type]->(b:label) DELETE r RETURN ...` is supported for a
single matched relationship backed by a registered edge row table. It requires a
`mutable_overlay` projection, deletes exactly one PostgreSQL edge row first,
then tombstones the graph edge in the current backend. Bidirectional edge
registrations hide both neighbor directions after the delete. Endpoint nodes are
not deleted. Undirected relationship `DELETE` is not part of this release's GQL
subset.

`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. Relationship
creation and multi-row writes are not part of this release's GQL subset.

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

### `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.

### `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.

## 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.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 current_setting('graph.max_nodes')::int,
  max_frontier int DEFAULT current_setting('graph.max_frontier')::int
)
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
)
```

### `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
)
```

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 `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 current_setting('graph.max_exact_path_count')::int
) 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.
