luckydududu opened a new issue, #2520:
URL: https://github.com/apache/age/issues/2520
## Summary
When `age` is in `shared_preload_libraries`, **`TRUNCATE` fails in any
database that does not have the AGE extension installed**:
```
ERROR: schema "ag_catalog" does not exist
```
This makes AGE unsafe to preload on a shared PostgreSQL cluster: a single
database wanting graph support breaks `TRUNCATE` for every other database in
the instance.
This is the same class of problem as #2180 (fixed for the
`object_access_hook` path by #2161), but on a different code path that was
added later and does not have the guard.
## Environment
- PostgreSQL 18.6 (Debian 18.6-1.pgdg13+2), x86_64
- Apache AGE 1.8.0 (`postgresql-18-pgdg-age`, PGDG apt)
- `shared_preload_libraries = age`
## Reproduction
```console
$ psql -U postgres -c "CREATE DATABASE app;"
CREATE DATABASE
$ psql -U postgres -d app -c "CREATE TABLE t (i int);"
CREATE TABLE
$ psql -U postgres -d app -c "TRUNCATE t;"
ERROR: schema "ag_catalog" does not exist
```
The database `app` never had `CREATE EXTENSION age` run in it.
Installing the extension in that database makes it work again:
```console
$ psql -U postgres -d app -c "CREATE EXTENSION age;"
CREATE EXTENSION
$ psql -U postgres -d app -c "TRUNCATE t;"
TRUNCATE TABLE
```
Only `TRUNCATE` is affected. `CREATE TABLE` / `INSERT` / `UPDATE` / `DELETE`
/ `CREATE INDEX` / `ALTER TABLE` / `VACUUM` / `ANALYZE` / `REINDEX` / `CLUSTER`
/ `DROP TABLE` / `COPY` / `CREATE EXTENSION <other>` / `pg_dump` / `pg_restore`
all behave normally in the same database.
## Root cause
`ag_ProcessUtility_hook()` in `src/backend/catalog/ag_catalog.c` handles
`T_TruncateStmt` and calls `get_graph_oid_for_table()` — which resolves
`ag_catalog` — **without first checking whether the AGE extension exists in the
current database**:
```c
case T_TruncateStmt:
{
TruncateStmt *tstmt = (TruncateStmt *) parsetree;
ListCell *lc;
foreach(lc, tstmt->relations)
{
RangeVar *rv = (RangeVar *) lfirst(lc);
Oid rel_oid = RangeVarGetRelid(rv, AccessShareLock, true);
if (OidIsValid(rel_oid))
{
Oid graph_oid = get_graph_oid_for_table(rel_oid); /* <--
resolves ag_catalog */
if (OidIsValid(graph_oid))
{
increment_graph_version(graph_oid);
}
}
}
}
break;
```
The file already provides the guard for exactly this purpose —
`is_age_extension_exists()`, with the comment:
> `We don't want most of hooks to do anything if the "age" extension isn't
created.`
but in the `PG18` branch it is only called from `is_age_drop()` and
`object_access()` — the `T_TruncateStmt` branch (lines 172–202) calls
`get_graph_oid_for_table()` with no guard at all. Since
`shared_preload_libraries` loads the library instance-wide, the hook runs in
every database, including those where `ag_catalog` does not exist.
## Suggested fix
Bail out early in the `T_TruncateStmt` branch, consistent with how the other
hooks guard themselves:
```c
case T_TruncateStmt:
{
TruncateStmt *tstmt = (TruncateStmt *) parsetree;
ListCell *lc;
if (!is_age_extension_exists())
break;
...
}
```
Guarding the whole `switch` (or the hook entry point) would also work and
would protect any future branch from the same mistake.
## Impact / workaround
Workaround is to run `CREATE EXTENSION age` in **every** database of the
instance, including `template1` so that newly created databases inherit it.
That is fragile: databases created with `TEMPLATE template0` — which
`pg_dump`/`pg_restore` output commonly uses — do not inherit it, and `TRUNCATE`
starts failing there with an error that gives no hint that AGE is involved.
Happy to submit a PR if the maintainers agree with the approach.
--
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]