# Planner And Streaming Runtime ## Goals - Execute GQL as composable binding-table operators. - Choose plans from graph and PostgreSQL statistics rather than source order. - Stream rows in bounded batches and spill blocking operators. - Push identity, property, visibility, and relational work to PostgreSQL when PostgreSQL is the cheaper authoritative executor. - Expose estimates, budgets, and decisions through explain. ## Canonical Logical IR Core logical operators: ```text GraphScan / IdentityLookup / PropertyIndexScan Expand / ExpandInto / PathSearch Filter / Project / Unwind InnerJoin / LeftJoin / SemiJoin / AntiJoin Aggregate / Distinct / Sort / TopK / Window Union / Intersect / Except Subquery / Exists Insert / Update / Remove / Delete VisibilityFilter / Hydrate ``` Logical rows carry typed slots for nodes, relationships, paths, scalars, and records. Relationship slots always carry `RelationshipId`; node slots carry source table and stable PK identity. JSON serialization happens at the outer SQL boundary only. Slot indexes are distinct `NodeSlotId`, `RelationshipSlotId`, `PathSlotId`, `PatternId`, and `ColumnIndex` types backed by typed arenas/indexed vectors. The compiler must reject using one slot family to index another. Scalar slots carry the exact `GraphValue` model from the [Rust boundary plan](./10-rust-type-safety-pgrx-boundaries.md), never an internal JSON/`f64` surrogate. ## Statistics And Costing Maintain versioned statistics per graph generation: - label/table cardinality and active/tombstone counts; - relationship type/mapping cardinality by direction; - in/out degree histograms, heavy hitters, and zero-degree rate; - property null fraction, distinct count, ranges, and selected histograms; - tenant/selectivity summaries that do not leak values to unauthorized roles; - overlay/segment depth, dirty source ranges, and expected merge cost; - source visibility/hydration batch and PostgreSQL index estimates. Use PostgreSQL statistics where mappings align, supplemented by projection statistics. Invalidate plans on catalog fingerprint, artifact generation, relevant statistics, role/security context, or semantic GUC change. Cost dimensions include expected rows, expansions, random/sequential bytes, private memory, spill bytes, PostgreSQL calls, and parallel/concurrency cost. The optimizer must compare scan/expand order, join reorder, expand-into, identity/property lookup, nested-loop/hash/merge join, top-k versus full sort, and sparse versus dense path state. ## Physical Runtime Use synchronous, pull-based batch operators suited to a PostgreSQL backend: ```rust trait Operator { fn next_batch( &mut self, cx: &mut ExecutionContext, out: &mut RowBatch, ) -> GraphResult; } ``` `ExecutionContext` owns the snapshot contract, graph generation pin, role and tenant context, resource leases, spill manager, counters, and interrupt cadence. Do not introduce an async runtime into backend execution. ### Streaming Rules - Source scans iterate bitmaps/ranges lazily and honor safe LIMIT early stop. - Neighbor expansion merges base, segment, sync, and transaction sources without a degree-sized materialization. - Hydration and RLS visibility use table-wise key batches, not per-coordinate SPI calls. - Sort, DISTINCT, grouping, and eligible joins use bounded in-memory runs and external merge. - Path search is an iterative state machine with explicit work/frontier/path budgets. It fails deterministically when a semantic result cannot be completed within the declared bound. - Output is yielded as batches to the SQL SRF; avoid collecting the final JSONB result twice. - Long loops check PostgreSQL interrupts by work count, not only wall time. ### Spill Manager One statement spill manager creates secure operation-scoped files, enforces disk/file quotas, checks free space, encrypts or avoids sensitive cleartext when required by the deployment policy, fsyncs only where recovery semantics need it, and cleans up on success, error, interrupt, and backend exit. Spillable operators encode typed rows with version and checksum. Query spill does not need crash recovery; build/compaction staging does. ### Path Algorithms Keep specialized algorithms behind common operator contracts: - bidirectional and single-direction BFS; - weighted Dijkstra with sparse/dense state choice; - shortest-path and path-mode automata required by GQL; - bounded k-shortest paths when added; - worker-only whole-graph analytics. A planner selects an algorithm from directionality, weights, predicates, statistics, semantic path mode, available memory, and requested result shape. No algorithm receives an implicit whole-graph allocation entitlement. ## PostgreSQL Integration Retain `graph.gql()` as a compatibility facade. Investigate typed set-returning descriptors and eligible PostgreSQL planner integration so relational predicates, joins, and costs are visible instead of hiding all work behind one `VOLATILE` JSONB SRF. Potential integration, gated by supported-version APIs: - parameter-aware cost and row estimates; - predicate/projection pushdown; - plan invalidation hooks; - custom scan only for stable, eligible shapes; - native PostgreSQL 19 property-graph catalog/SQL-PGQ adapter; - explain fields that combine PostgreSQL and graph work. Never cache visible row sets or hydrated values across statements. A prepared plan may cache immutable structure only when role, RLS-sensitive context, catalog, generation, and GUC invalidation are correct. ## Performance Program Define scorecards by workload class instead of promising universal wins: | Workload | Intended advantage | |---|---| | PostgreSQL-resident operational graph | No ETL, authoritative DML, local indexes, transaction semantics | | Warm bounded traversal | Compact CSR, mmap page sharing, typed filters | | Selective property + graph query | PostgreSQL index pushdown plus graph expansion | | Continuous source updates | Delta projection and bounded catch-up | | Large build under small memory | External runs and predictable spill | | Whole-graph analytics | Quota-controlled workers; not the OLTP backend path | Compare identical data and semantics, publish hardware/configuration, validate results, and report cold/warm p50/p95/p99, throughput, RSS/PSS, spill, build, load, sync lag, and storage amplification. ## Acceptance Gates - Explain includes estimated/actual rows, expansions, memory, spill, and chosen operators. - Reordering never changes results; differential plan tests compare alternatives. - `LIMIT 1` over a huge label has bounded source-scan memory. - A supernode and an unreachable bounded path obey work/memory limits. - Wide rows, DISTINCT, grouping, and sort spill below query memory cap. - Batched hydration and visibility remove per-coordinate SPI behavior. - Cancellation interrupts every operator within a bounded work interval. - The same logical IR serves GQL and the SQL/PGQ adapter.