NotHimmel opened a new issue, #2504:
URL: https://github.com/apache/age/issues/2504
### 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.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]