apache / apache/age

create_graph() fails when ag_catalog is not in search_path: operator class "graphid_ops" does not exist

Closed
#2,504 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
C
Stars
4.8k
Forks
523
Avg merge
1d 2h
Merged PRs (30d)
9

Description

### What happens

On a session whose `search_path` does not include `ag_catalog`, creating a graph fails — even though the call is fully schema qualified:

```sql
LOAD 'age';
SHOW search_path; -- "$user", public

SELECT ag_catalog.create_graph('g');
-- ERROR: operator class "graphid_ops" does not exist for access method "btree"
```

The whole transaction rolls back, so no partial graph is left behind.

The same failure hits every entry point that reaches `create_label()`:

* `ag_catalog.create_graph()`
* `ag_catalog.create_vlabel()` / `ag_catalog.create_elabel()`
* `ag_catalog.load_labels_from_file()` / `load_edges_from_file()` (via `get_or_create_label()`)
* a `CREATE` or `MERGE` clause that mentions a label for the first time — this one fails *in the middle of a running query*

### Expected

`create_graph()` should not depend on the caller's `search_path`. The error is also misleading: `graphid_ops` **does** exist (`ag_catalog`, `sql/age_main.sql`), it is simply not visible, and the message says `does not exist` with no hint about `search_path`. Combined with the `42704 undefined_object` code, this sends people looking at privileges (`GRANT USAGE ON SCHEMA ag_catalog`, `GRANT EXECUTE ...`), which changes nothing.

### Root cause

`create_index_on_column()` in `src/backend/commands/label_commands.c` passes the operator class as an **unqualified** name:

```c
index_col->opclass = list_make1(makeString("graphid_ops"));
```

`ResolveOpClass()` → `OpclassnameGetOpcid()` resolves an unqualified opclass name through `search_path`, so the lookup fails when `ag_catalog` is not on it.

This is the only unqualified name in that code path. Everything else the generated DDL references is already immune:

| reference | how it is built | search_path dependent |
| --- | --- | --- |
| column types `graphid` / `agtype` | `GRAPHIDOID` / `AGTYPEOID` (OIDs) | no |
| `_graphid()` default | `list_make2(makeString("ag_catalog"), makeString("_graphid"))` | no |
| `agtype_build_map()` default | `list_make2(makeString("ag_catalog"), ...)` | no |
| `_label_id()` | `list_make2(makeString("ag_catalog"), ...)` | no |
| `int4` / `regclass` | `SystemTypeName()` → `pg_catalog.*` | no |
| **`graphid_ops`** | **`list_make1(makeString(...))`** | **yes** |

So this looks like an oversight rather than a deliberate choice — the same function qualifies three other `ag_catalog` names explicitly.

### This is a regression

Before the id-column indexes were added in #2117, `create_label()` created no index and therefore resolved no name through `search_path`, so `create_graph()` worked under any `search_path`.

Commits carrying the unqualified name:

| branch | commit |
| --- | --- |
| `master`, `PG19`, `release/PG19/1.8.0` | `5aed9ec` (#2117) |
| `PG18`, `release/PG18/1.7.0`, `release/PG18/1.8.0` | `2f36b1c` (#2117) |
| `PG17`, `release/PG17/1.7.0` | `858a0b7` (#2117) |
| `PG16` | `8c74fd2` (#2375) |

**Affected: 1.7.0 and later. 1.6.0 and earlier are not affected.**

### Why no existing test catches it

Every file in `regress/sql/` that creates a graph sets `SET search_path TO ag_catalog;` — 35/35 on `release/PG18/1.7.0`, 46/47 on `master`, the single exception being `agehash.sql`, which only calls `ag_catalog._agehash_self_test()` and creates nothing. So no test exercises the unqualified-lookup path.

Contributor guide

Open the contributing guide

Research direction

Start in src/backend/commands/label_commands.c at create_index_on_column(), then inspect the existing regression cases under regress/sql/. Reproduce graph creation with ag_catalog absent from search_path and add coverage for that scenario; done means create_graph() and the listed label entry points succeed without requiring callers to modify search_path.

Written by the indexing model from the issue text.

Assessment

Tech stack
c, postgresql
Domain
backend, databases
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.