You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Make ag_catalog ownership and built-in resolution explicit
AGE places all of its objects in the ag_catalog schema. Make the
assumptions around that schema explicit so installs and upgrades behave
predictably regardless of how a database is provisioned:
- Ownership-checked install: CREATE EXTENSION age installs into
ag_catalog only when that schema does not already exist under a
different owner, keeping ownership of AGE's catalog well-defined.
- Deterministic name resolution: the pg_upgrade helper functions resolve
built-ins from pg_catalog first and schema-qualify their
format()/hashtext() calls, so their behavior does not depend on what
else is defined in ag_catalog.
- README note describing ag_catalog ownership and the install-time check.
The upgrade script applies the same helper changes so existing
installations get them on ALTER EXTENSION UPDATE. Adds an
extension_security regression test covering the ownership check and the
qualified-call / search_path properties.
Assisted-by: GitHub Copilot (Claude Opus 4.8)
modified: Makefile
modified: README.md
modified: age--1.7.0--y.y.y.sql
new file: regress/expected/extension_security.out
new file: regress/sql/extension_security.sql
modified: sql/age_main.sql
modified: sql/age_pg_upgrade.sql
Copy file name to clipboardExpand all lines: README.md
+10Lines changed: 10 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -215,6 +215,16 @@ LOAD 'age';
215
215
SET search_path = ag_catalog, "$user", public;
216
216
```
217
217
218
+
### Note on `ag_catalog` ownership
219
+
220
+
AGE installs all of its objects into the `ag_catalog` schema. Install AGE
221
+
(`CREATE EXTENSION age`) **before** granting the `CREATE` privilege on the
222
+
database to other roles. A role that can create schemas could otherwise
223
+
pre-create `ag_catalog` and own it; `CREATE EXTENSION age` therefore refuses to
224
+
install when `ag_catalog` already exists and is owned by a different role. If you
225
+
hit that error, drop the stray schema (`DROP SCHEMA ag_catalog CASCADE`) or
226
+
transfer its ownership to the installing role, then retry.
227
+
218
228
<h2><imgheight="20"src="/img/contents.svg"> Using AGE with Non-Autocommit Clients (psycopg, JDBC, etc.)</h2>
219
229
220
230
If you are using AGE from a database client that does **not** default to autocommit — most commonly `psycopg` v3 or JDBC — you must understand how PostgreSQL's transaction semantics apply to AGE's setup and DDL-like functions. Otherwise, you may see graphs or labels that appear to be created successfully, but are not visible from new connections.
Copy file name to clipboardExpand all lines: sql/age_main.sql
+27Lines changed: 27 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -20,6 +20,33 @@
20
20
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
21
21
\echo Use "CREATE EXTENSION age" to load this file. \quit
22
22
23
+
--
24
+
-- Ensure ag_catalog is created and owned by the installing role.
25
+
--
26
+
-- CREATE EXTENSION places all of AGE's objects in ag_catalog. A normal install
27
+
-- creates that schema, owned by the installer. If ag_catalog already exists and
28
+
-- is owned by a different role, that role would retain control over the schema
29
+
-- that holds AGE's catalog objects. To keep ownership well-defined, refuse to
30
+
-- install into a pre-existing ag_catalog owned by another role. Ownership is
31
+
-- compared directly (not via role membership) so the check is exact even for a
32
+
-- superuser, who is otherwise considered a member of every role.
33
+
--
34
+
DO $age_install_guard$
35
+
BEGIN
36
+
IF EXISTS (
37
+
SELECT1
38
+
FROMpg_catalog.pg_namespace n
39
+
WHEREn.nspname='ag_catalog'
40
+
ANDn.nspowner<> (SELECTr.oid
41
+
FROMpg_catalog.pg_roles r
42
+
WHEREr.rolname=current_user)
43
+
) THEN
44
+
RAISE EXCEPTION 'schema "ag_catalog" already exists and is not owned by the installing role "%"', current_user
45
+
USING HINT ='Apache AGE will not install into a pre-existing ag_catalog owned by another role. Drop it (DROP SCHEMA ag_catalog CASCADE) or transfer its ownership to the installing role, then retry CREATE EXTENSION age.';
0 commit comments