# Known Issues The selective-RLS exact-result, 1M and 10M evidence for pgGraph 1.2.1 covers the stated fixtures and resource envelopes. This page lists known user-visible limitations in pgGraph 1.2. See [Release Notes](./release-notes) for resolved issues and [Roadmap](./roadmap) for planned expansion. ## Operations ### External scheduling is required outside the packaged Docker setup pgGraph exposes bounded, concurrency-safe maintenance entry points, but it does not run an always-on scheduler inside every PostgreSQL installation. The packaged Docker image configures `pg_cron`. Other installations must schedule `graph.run_scheduled_maintenance()` with `pg_cron` or an external scheduler. Without a scheduler, reads remain available, but pending trigger-sync work, overlay compaction, and cleanup do not run automatically. Follow the setup, health, retry, and failure guidance in [Sync And Maintenance](/user_guide/sync-and-maintenance#scheduled-maintenance). ## Access Control ### RLS protects results, not every timing or resource side channel pgGraph 1.2 excludes caller-hidden node and relationship identities before they can affect observable topology. The resource governor may still charge projection entries examined while constructing or applying that visibility boundary. The supported contract is PostgreSQL-like result and topology visibility; pgGraph does not claim formal timing or resource-exhaustion noninterference between roles with different policies. ### Some RLS workloads still use eager visibility preparation pgGraph 1.2 uses bounded lazy visibility resolution for supported targeted identity, traversal, path, and GQL/Cypher expansion work. Whole-graph analytics, wide source scans, all-possible-path enumeration, and other global work retain the eager PostgreSQL visibility oracle. Large policy-scoped graphs can therefore spend work proportional to the visible source set and may reach `statement_timeout` even when the final result is small. The eager route preserves caller authorization. Use bounded targeted queries where possible, keep PostgreSQL source-key indexes current, and set an explicit `statement_timeout`. The candidate's retained [1M measurements](../release/evidence/engine/2026-09-11-p5-selective-rls-1m/README.md), [10M measurements](../release/evidence/engine/2026-09-12-p5-selective-rls-10m/README.md) and [exact-result comparisons](../release/evidence/engine/2026-09-11-selective-rls-exact/README.md) cover their stated fixtures and query shapes. The 10M run reached its 6 GiB container ceiling with memory reclamation and no OOM on a 12 GiB outer VM; an earlier build attempt on an approximately 8 GiB VM failed with a global OOM before query sampling. These measurements are not a latency SLA. Visibility metrics count returned policy rows rather than all PostgreSQL rows examined. GQL predicates, ordering and returned properties can require source hydration even with `hydrate := false`; zero repeated read rechecks does not mean zero source SQL. Governor memory measurements are not process RSS or a cluster-wide memory limit. ### Tenant scope requires the session-setting path under enforcement For a graph whose tables are registered with `tenant_column` rather than pinned to a single graph-level tenant, an explicit tenant argument is a caller-supplied value, not one verified against the calling role's identity. While `graph.enforce_tenant_scope = on` (the default), pgGraph refuses an explicit tenant argument for such a graph and requires the session tenant (`graph.tenant_setting`) instead. pgGraph cannot verify that a session's tenant setting reflects who is actually connected; that trust boundary belongs to whatever sets the session GUC before the caller's own queries run. See [Tenant Scope](/user_guide/administration-and-security#tenant-scope) for the full boundary and mitigation guidance. ## Query Compatibility ### GQL writes accept one parameter object per call The 1.0 mapped GQL write profile creates, merges, updates, or deletes one node or relationship per `graph.gql()` call. It does not expose a multi-row mutation or `UNWIND` write surface. An adapter that calls `graph.gql()` once per item therefore pays repeated client round trips and parse, bind, ACL, and SPI work. For bulk ingestion in 1.0, write registered PostgreSQL source tables directly with set-based DML, commit, and apply sync once: ```sql BEGIN; INSERT INTO app.entities (id, name) VALUES ('n-1', 'Ada'), ('n-2', 'Grace') ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name; COMMIT; SELECT * FROM graph.apply_sync(); ``` PostgreSQL remains authoritative for constraints, triggers, ACLs, RLS, MVCC, and partition routing. In persisted `mutable_overlay` mode, committed edge changes become durable projection segments after sync without a full graph rebuild. Applications with the default pending-sync query freshness may let the next topology read catch up instead of calling `graph.apply_sync()` explicitly. A bounded, set-based graph mutation API is not currently available. ### GQL and openCypher support are bounded profiles `graph.gql()` implements the generated [GQL 1.0 profile](/user_guide/gql-profile), not full ISO GQL. `graph.cypher()` accepts only the overlapping compatibility profile and does not claim full openCypher support. Unsupported constructs fail with a stable SQLSTATE and actionable guidance rather than being interpreted with approximate semantics. SQL/PGQ `GRAPH_TABLE` is not part of pgGraph's PostgreSQL 14–18 contract. PostgreSQL [removed SQL/PGQ from its PostgreSQL 19 release branch](https://github.com/postgres/postgres/commit/2b9e1aff4d3d933ae8ee377fef22c2af9c7797e8) on September 7, 2026. Future native integration has no committed target major; see the [Roadmap](./roadmap). ### GQL candidate limits depend on the query shape An ordinary node-only read with a `WHERE` predicate evaluates candidates before retaining up to 10,000 matching rows. Nonmatching source rows do not consume this retained-row cap, but scanning and hydration still use the query's work, memory and elapsed-time budgets. This incremental filtering does not apply to `OPTIONAL MATCH`, `DISTINCT`, aggregation, `ORDER BY`, `SKIP` or `LIMIT`. Those query shapes, joins and path expansions retain the 10,000-row candidate cap and can reach it before evaluating residual properties, even when the final result would be small. Increasing `graph.max_nodes` or `graph.query_memory_mb` does not raise the cap. Exceeding the applicable row cap fails with SQLSTATE `22000` and diagnostic `PG017`; queries do not silently return a filtered prefix. A supported primary-key equality can seed a node scan or the first mandatory source of a multi-pattern join before expansion. For searches that exceed these limits, use PostgreSQL source queries to select a bounded set of keys, then issue identity-seeded graph reads. Registered filter columns do not imply general GQL predicate pushdown or indexed property evaluation. ## Resource Scope ### Limits are per operation and backend, not a cluster-wide memory quota Build, load, query, synchronization, compaction, and supported analytics enforce their documented byte, row, work, disk, and elapsed-time policies. PostgreSQL's own buffer cache, executor memory, and other sessions remain governed by PostgreSQL configuration and operating-system controls. `graph.memory_limit_mb` therefore does not replace cluster-wide capacity planning. Use `graph.resource_status()`, `graph.build_resource_status()`, PostgreSQL `statement_timeout`, and the production-shaped RSS/PSS release evidence when sizing concurrent workloads. See [Configuration](/user_guide/configuration) and [Troubleshooting](/user_guide/troubleshooting#query-or-maintenance-hits-a-resource-limit). ## Reporting A New Issue Include the pgGraph version, PostgreSQL major, platform, exact SQLSTATE and `PGxxx` diagnostic, relevant `graph.status()`/`graph.resource_status()` output, and a minimal source schema. Do not include credentials or production data.