# Administration And Security Administrative functions mutate extension catalogs, build state, persisted artifacts, sync state, or run global graph algorithms. The code checks graph admin privileges before these operations. ## Graph Admin Check A caller is considered a graph admin when either: | Condition | Source | |---|---| | Current role is a superuser | `pg_roles.rolsuper` | | Current role has `CREATE` privilege on schema `graph` | `has_schema_privilege(current_user, 'graph', 'CREATE')` | Grant a non-superuser admin role: ```sql GRANT USAGE, CREATE ON SCHEMA graph TO graph_admin; ``` ## Admin-Protected Functions Admin protection applies to catalog mutation, build/vacuum/maintenance, sync apply, reset, and global analytics. | Function family | Examples | |---|---| | Registration | `add_table`, `add_edge`, `add_filter_column`, `rename_edge`, `alter_edge`, `remove_table`, `remove_edge` | | Build lifecycle | `build`, `vacuum`, `maintenance`, `reset` | | Sync | `enable_sync`, `apply_sync`, `add_sync_policy`, `alter_sync_policy`, `drop_sync_policy`, `run_sync_policy`, `run_job`, `run_due_jobs`, `run_due_jobs_async`, `alter_job`, `remove_job`, `projection_compact`, `projection_gc`, `projection_repair` | | Global analytics | `connected_components`, `component_stats`, component pagination helpers | ## Graph Privileges Named graphs can grant graph-level privileges to PostgreSQL roles: ```sql SELECT * FROM graph.grant_graph('customer_360', 'app_reader', 'read', namespace := 'analytics'); SELECT * FROM graph.grant_graph('customer_360', 'etl_worker', 'build', namespace := 'analytics'); SELECT * FROM graph.graph_privileges('customer_360', namespace := 'analytics'); ``` | Privilege | Effect | |---|---| | `read` | Allows selecting/querying the graph when source-table privileges also allow access | | `write` | Reserved for mapped graph writes; source-table write privileges remain required | | `build` | Allows build, vacuum, and maintenance for the graph | | `admin` | Allows grant/revoke and graph lifecycle administration | Graph owners, graph schema admins, and graph `admin` grantees can grant and revoke graph privileges. Graph owners and graph schema admins retain all graph privileges. ## Graph Quotas Graph schema admins can configure quota policies: ```sql SELECT * FROM graph.set_graph_quota( 'owner', 'max_named_graphs', 25, scope_key := 'app_owner', enforcement := 'hard' ); SELECT * FROM graph.graph_quota_usage(); ``` Quota scopes are `cluster`, `tenant`, `owner`, `namespace`, and `graph`. Enforcement can be `hard` or `warn`. Hard `max_named_graphs` quotas check cluster and owner limits before creating a graph metadata row. Hard `max_loaded_graphs_per_backend` quotas check cluster and owner limits before loading a persisted graph artifact into the current backend. Hard `max_graph_jobs` quotas count generic jobs, build jobs, and maintenance jobs before creating a sync policy job. Hard `max_artifact_storage_bytes` quotas check active projection artifact bytes plus the requested ingest or compaction write budget before publishing new derived files. Warning quotas are visible through `graph.graph_quota_usage()` but do not block the operation. ## Reader Functions Application roles typically need execute privileges on search/traversal/path functions and `SELECT` privilege on the source tables they query. ```sql GRANT USAGE ON SCHEMA graph TO app_reader; GRANT SELECT ON public.users TO app_reader; GRANT SELECT ON public.orders TO app_reader; ``` Then grant only the functions your application uses. PostgreSQL function grants must match argument types; inspect installed signatures with: ```sql SELECT p.oid::regprocedure FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace WHERE n.nspname = 'graph' ORDER BY p.proname, p.oid::regprocedure::text; ``` ## Source Table ACL Checks Query functions check source-table `SELECT` privileges before accessing graph data for the relevant table coordinates. Missing privilege raises `PG002`. Mapped GQL writes also preflight the privileges they need on source tables: `MERGE` requires `SELECT` and `INSERT`, and it requires `UPDATE` only when the query includes `ON MATCH SET`. Graph `read` is necessary but not sufficient for reading source data. A role with graph `read` but no source-table `SELECT` still receives `PG002` when a query hydrates or otherwise reads source rows. A role with source-table `SELECT` but no graph `read` cannot select or query that graph. ## Tenant Scope When a selected graph has a `tenant` value, pgGraph uses it as the default tenant scope for traversal, search, GQL, and Cypher calls. An explicit tenant argument or a session tenant from `graph.tenant_setting` must match the selected graph tenant; mismatches return `PG005`. Resolution order is: 1. explicit tenant argument; 2. session tenant from `graph.tenant_setting`; 3. selected graph tenant; 4. no tenant, unless `graph.enforce_tenant_scope = on` and registered tables have `tenant_column`. The graph artifact contains source primary keys and topology. ACL checks are part of query execution so a role cannot use graph queries to bypass source table `SELECT` permissions. ## Internal Catalog Tables Internal catalog tables are implementation details. Bootstrap SQL revokes direct `PUBLIC` read and write access to sensitive named-graph, grant, quota, job, sync, and projection metadata tables: ```text graph._graphs graph._graph_grants graph._graph_quotas graph._jobs graph._job_runs graph._sync_policies graph._sync_log graph._projection_generations graph._sync_buffer ``` Application code should not depend on raw internal reads. Use filtered public functions such as `graph.list_graphs()`, `graph.graph_privileges(...)`, `graph.graph_quotas()`, `graph.graph_quota_usage()`, `graph.jobs(...)`, `graph.job_runs(...)`, `graph.sync_health()`, and `graph.projection_status()`. Extension owners and superusers can still inspect internals for maintenance when needed. ## Error Codes The extension emits custom SQLSTATEs through PostgreSQL's error reporting API. | SQLSTATE | Error | Common fix | |---|---|---| | `PG001` | Memory limit exceeded | Raise `graph.memory_limit_mb`, reduce registered graph size, or change OOM policy | | `PG002` | ACL denied | Grant `SELECT` on the relevant source table or graph admin privilege for admin functions | | `PG003` | Graph not built | Run `SELECT * FROM graph.build();` | | `PG004` | Edge type limit exceeded | Reduce distinct edge labels; max user labels are 254 | | `PG005` | Invalid filter | Register the filter column and use supported operators/types | | `PG006` | Build locked | Wait for current build/vacuum/maintenance | | `PG008` | Edge buffer full | Run `graph.vacuum()`/`graph.maintenance()` or increase `graph.edge_buffer_size` | | `PG009` | Corrupt graph file | Rebuild persisted artifact | | `PG010` | Node not found | Check table/primary key and rebuild/apply sync if data changed | | `PG011` | Incompatible graph file version | Rebuild artifact with current extension version | | `PG012` | Read-only graph state | Inspect `graph.status().read_only_reason`, then rebuild, vacuum, or adjust memory policy | | `PG013` | GQL syntax error | Check the query text against the documented GQL subset | | `PG014` | Unsupported GQL feature | Rewrite the query using supported GQL compatibility-matrix features | | `PG015` | GQL semantic error | Verify labels, relationship types, aliases, and return bindings | | `PG016` | GQL parameter error | Pass a JSON object containing every `$parameter` referenced by the query | | `PG017` | GQL execution error | Reduce result cardinality, fix invalid runtime values, or rebuild/apply sync if a graph coordinate can no longer hydrate its source row | | `PG018` | Unsupported graph operation | Use a supported query shape or merge pending overlays before retrying | | `PG019` | Overlay limit exceeded | Commit or roll back the transaction, or raise the relevant overlay GUC | | `55000` | Extension disabled | `SET graph.enabled = on` | | `XX000` | Internal error | Report with full message and reproduction | ## Disable Query Functions ```sql SET graph.enabled = off; ``` When disabled, query functions fail with SQLSTATE `55000`. Administrative functions such as `status`, `build`, and `reset` are not intended as regular query paths and are not all gated by the kill switch. ## Backup And Restore The bootstrap SQL marks extension-owned operational tables for config dump with `pg_extension_config_dump`. This preserves registration, jobs, and unapplied sync rows across dump/restore. Source tables remain authoritative for graph contents. After restore: ```sql SELECT * FROM graph.status(); SELECT * FROM graph.build(); ``` If the persisted file was not restored with `$PGDATA`, rebuild from source tables. ## Admin SQL Examples Use the named-graph APIs when you want to work on a graph without changing the session default. **Create, register, and build** ```sql SELECT * FROM graph.create_graph('customer_360', namespace := 'analytics'); SELECT * FROM graph.add_table_to_graph( 'customer_360', 'public.customers'::regclass, 'id', ARRAY['name', 'email'], graph_namespace := 'analytics' ); SELECT * FROM graph.add_edge_to_graph( 'customer_360', 'public.orders'::regclass, 'customer_id', 'public.customers'::regclass, 'id', 'placed_order', graph_namespace := 'analytics' ); SELECT * FROM graph.build_graph('customer_360', force_persist := true, graph_namespace := 'analytics'); ``` **Select, sync, and run jobs** ```sql SELECT * FROM graph.select_graph('customer_360', namespace := 'analytics'); SELECT graph.enable_sync(); SELECT * FROM graph.add_sync_policy('customer_360', schedule_interval_secs := 300, graph_namespace := 'analytics'); SELECT * FROM graph.jobs('customer_360', graph_namespace := 'analytics'); SELECT * FROM graph.run_due_jobs(); ``` **Set residency and quotas** ```sql SELECT * FROM graph.set_graph_residency('customer_360', 'warm', namespace := 'analytics'); SELECT * FROM graph.set_graph_quota('owner', 'max_named_graphs', 25, current_user::text, 'hard'); SELECT * FROM graph.graph_quota_usage(); ``` **Inspect storage, status, and memory** ```sql SELECT artifact_bytes, segment_count, artifact_validation_state FROM graph.projection_status(); SELECT node_count, edge_count, schema_status, sync_status, sync_lag FROM graph.status(); SELECT * FROM graph.memory_profile(concurrent_backends := 4); ``` **Grant access and export the map** ```sql SELECT * FROM graph.grant_graph('customer_360', 'app_reader', 'read', namespace := 'analytics'); SELECT * FROM graph.graph_map('customer_360', graph_namespace := 'analytics'); ``` **Direct node lookup and search** ```sql SELECT * FROM graph.get_node('customer_360', 'customers', 'cust_123', graph_namespace := 'analytics'); SELECT * FROM graph.search('name', 'Ada'); ```