TRUNCATE fails with `schema "ag_catalog" does not exist` in databases without AGE, when AGE is in shared_preload_libraries
维护者通常 1 天内回复
评估
- 难度
- 2/5
- 预计耗时
- 1-3 小时
- 新手友好度
- 78/100
调研方向
从 src/backend/catalog/ag_catalog.c 中的 ag_ProcessUtility_hook() 的 T_TruncateStmt 分支开始,然后阅读 is_age_extension_exists() 以及附近受保护的 hook 路径。在预加载 age 的情况下,在没有 AGE 的数据库中重现 TRUNCATE,并在安装了 AGE 的数据库中验证行为。完成的标准是:未扩展的数据库可以正常执行 truncate,且不会出现 ag_catalog 错误,同时 AGE 图表仍保留现有行为。
由索引模型根据 Issue 内容生成。
描述
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
$ 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:
$ 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:
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:
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.
- 主要语言
- C
- 星标
- 4.9k
- 派生
- 532
- 平均合并
- 8 天 15 小时
- 30 天内合并 PR
- 3
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 没有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
apache/age 的其他 Issue
-
Mark agtype_string_match_starts_with / _ends_with IMMUTABLE (contains already is)可能已有人在做 @mmustafasenoglu 于 15 天前认领。 未关闭
难度 1/5 1 小时以内 新手友好度 88/100
apache/age#2576 · 1 个 reaction ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 88/100
维护者通常 1 天内回复
-
Integer modulo (%) by a zero divisor errors with `floating-point exception` (22P01) instead of `division by zero`可能已有人在做 @cocofabio 于 21 天前认领。 未关闭bug
难度 2/5 1-3 小时 新手友好度 86/100
维护者通常 1 天内回复
-
难度 5/5 一周以上 新手友好度 30/100
维护者通常 1 天内回复
-
难度 4/5 3-5 天 新手友好度 48/100
维护者通常 1 天内回复
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 82/100
OpenPrinting/cups#1751 ·
维护者通常 1 天内回复
-
enhancement good first issue
难度 2/5 1-3 小时 新手友好度 66/100
维护者通常 1 天内回复
-
compile: jv_mem_calloc assertion abort after "too many function parameters" error in a nested function可能已有人在做 关联的 PR 仍在进行中或已合并。 未关闭
难度 2/5 1-3 小时 新手友好度 72/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 72/100
NASA-AMMOS/BSL#355 ·
维护者通常 1 天内回复
-
bug
难度 2/5 1-3 小时 新手友好度 78/100
arancormonk/dsd-neo#660 ·
维护者通常 1 天内回复