This is an automated email from the ASF dual-hosted git repository.

MuhammadTahaNaveed pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/age.git


The following commit(s) were added to refs/heads/master by this push:
     new f02eda0e perf: VLE terminal-qual rewrite (#2420)
f02eda0e is described below

commit f02eda0e0504bf3e941dd4a05e3060d2f3bb4f04
Author: John Gemignani <[email protected]>
AuthorDate: Mon Jun 1 09:05:49 2026 -0700

    perf: VLE terminal-qual rewrite (#2420)
    
    perf: VLE terminal-qual rewrite — emit endpoint equalities instead of SRF
    qual functions.
    
    Removes the per-row age_match_vle_terminal_edge and age_match_two_vle_edges
    qual functions from VLE query plans. The cypher transformer now emits the
    endpoint match as a plain graphid/int8 equality on new SRF output columns,
    evaluated by the planner like any other join clause — no detoasting, no
    per-row C function dispatch. Stages land as one commit:
    
      S1  Inline start_vid/end_vid in VLE_path_container header
      S2  Read VLE qual endpoints from header-only TOAST slice
      S4  Emit start_id/end_id as scalar SRF output columns
          (age_vle now RETURNS SETOF record with edges/start_id/end_id)
      S5  Cypher transformer rewrites terminal-edge match quals as
          integer equalities (drops age_match_vle_terminal_edge call)
      S6  Cypher transformer emits graphid equality for two-VLE-edge
          joins (drops age_match_two_vle_edges call)
    
    Performance (SF3 LDBC SNB, 5 runs/3 warmup, vs clean master baseline_v2):
    
      IC sum    198,958 → 109,322 ms   −45.05 %  (1.82× end-to-end speedup)
      IC1   8,625 →  4,600 ms  −46.67 %
      IC3  21,239 →  9,784 ms  −53.93 %
      IC5  21,051 →  5,696 ms  −72.94 %
      IC6  15,916 →  4,447 ms  −72.06 %
      IC9  44,839 → 21,161 ms  −52.81 %
      IC10 13,104 →  2,432 ms  −81.44 %
      IC11 11,676 →    241 ms  −97.93 %  (48× speedup)
      IC2/4/7/8/12: parity (within ±3.3 %; IC4 is −2.47 %, no regression)
      IS sum:  1,009 → 1,004 ms   −0.51 %  (no VLE traffic)
      IU sum:     77 →    71 ms   −8.38 %  (IU1 −16.09 %; incidental)
    
    Memory: header-only TOAST slice for VLE qual evaluation avoids
    detoasting full path containers on every row; reduces per-call
    palloc/pfree churn in long DFS paths. No measured RSS change.
    
    Dead-code removal:
      - Bodies of age_match_vle_terminal_edge and age_match_two_vle_edges
        are gone from age_vle.c (~225 lines).  C entry points remain as
        error-raising stubs solely so the upgrade-test snapshot loader
        (which sources an older 1.7.0_initial SQL against the current
        age.so) can resolve the symbols before the immediate ALTER
        EXTENSION UPDATE drops them.  No regress test references either
        function.
      - SQL CREATE FUNCTION declarations removed from fresh install
        (sql/agtype_typecast.sql).
      - DROP FUNCTION IF EXISTS for both qual functions added to the
        upgrade script (age--1.7.0--y.y.y.sql).
    
    API change: ag_catalog.age_vle(...) now RETURNS SETOF record with
    output columns (edges agtype, start_id graphid, end_id graphid)
    instead of RETURNS SETOF agtype.  Both 7-arg and 8-arg overloads
    are updated in fresh-install (sql/agtype_typecast.sql) and upgrade
    (age--1.7.0--y.y.y.sql) paths.  age_match_vle_terminal_edge and
    age_match_two_vle_edges are dropped on upgrade and absent from
    fresh installs.  Internal AGE callers are unaffected; external SQL
    that called any of these directly must adapt.
    
    Hardening (in response to PR #2420 review feedback):
      - cypher_clause.c: terminal-edge and two-VLE-edge join-qual
        emission paths now bracket the existing Assert(vle_alias != NULL)
        with a runtime ereport(ERROR, ...) so a missing alias produces a
        clean error in production builds (where Asserts compile out)
        instead of a NULL-deref crash inside makeString().
      - age_vle.c: VLE_path_container struct comment now documents that
        the layout is transient (consumed within the producing query and
        never persisted), so the new start_vid/end_vid fields do not
        require an AGT_FBINARY_TYPE_VLE_PATH version bump or backward-
        compatible reader. The note also flags the constraint a future
        change would need to honor if the container ever became
        persistable.
    
    Tested on PostgreSQL 18.3 (REL_18_STABLE): all 34 regression tests
    pass (installcheck), warning-free build.
    
    modified:   age--1.7.0--y.y.y.sql
    modified:   regress/expected/cypher_match.out
    modified:   regress/expected/cypher_vle.out
    modified:   regress/expected/expr.out
    modified:   sql/agtype_typecast.sql
    modified:   src/backend/parser/cypher_clause.c
    modified:   src/backend/parser/cypher_transform_entity.c
    modified:   src/backend/utils/adt/age_vle.c
    modified:   src/include/parser/cypher_transform_entity.h
---
 age--1.7.0--y.y.y.sql                        |  46 ++++
 regress/expected/cypher_match.out            |   6 +-
 regress/expected/cypher_vle.out              |  28 +--
 regress/expected/expr.out                    |  64 +++---
 sql/agtype_typecast.sql                      |  31 +--
 src/backend/parser/cypher_clause.c           | 117 +++++++---
 src/backend/parser/cypher_transform_entity.c |   1 +
 src/backend/utils/adt/age_vle.c              | 310 ++++++++++-----------------
 src/include/parser/cypher_transform_entity.h |  11 +
 9 files changed, 316 insertions(+), 298 deletions(-)

diff --git a/age--1.7.0--y.y.y.sql b/age--1.7.0--y.y.y.sql
index 52044777..f7ef8066 100644
--- a/age--1.7.0--y.y.y.sql
+++ b/age--1.7.0--y.y.y.sql
@@ -490,3 +490,49 @@ AS 'SELECT $1::text::agtype';
 
 CREATE CAST (jsonb AS agtype)
     WITH FUNCTION ag_catalog.jsonb_to_agtype(jsonb);
+
+--
+-- S4: VLE SRF signature change
+--
+-- The age_vle SRF now emits start_id and end_id as scalar graphid columns
+-- alongside the existing `edges` column. This allows the cypher transformer
+-- to rewrite terminal-edge match quals as plain integer equalities,
+-- removing the per-row age_match_vle_terminal_edge and age_match_two_vle_edges
+-- function calls from VLE query plans.  Both qual functions are dropped.
+--
+-- BREAKING CHANGE for any external SQL that called age_vle(...) directly
+-- and relied on `RETURNS SETOF agtype`, or called age_match_vle_terminal_edge
+-- / age_match_two_vle_edges directly.  Internal AGE callers (the cypher
+-- transformer) are not affected.
+--
+DROP FUNCTION IF EXISTS ag_catalog.age_match_vle_terminal_edge(variadic "any");
+DROP FUNCTION IF EXISTS ag_catalog.age_match_two_vle_edges(agtype, agtype);
+
+DROP FUNCTION IF EXISTS ag_catalog.age_vle(agtype, agtype, agtype, agtype,
+                                           agtype, agtype, agtype);
+DROP FUNCTION IF EXISTS ag_catalog.age_vle(agtype, agtype, agtype, agtype,
+                                           agtype, agtype, agtype, agtype);
+
+CREATE FUNCTION ag_catalog.age_vle(IN agtype, IN agtype, IN agtype, IN agtype,
+                                   IN agtype, IN agtype, IN agtype,
+                                   OUT edges    agtype,
+                                   OUT start_id graphid,
+                                   OUT end_id   graphid)
+    RETURNS SETOF record
+LANGUAGE C
+STABLE
+CALLED ON NULL INPUT
+PARALLEL UNSAFE
+AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION ag_catalog.age_vle(IN agtype, IN agtype, IN agtype, IN agtype,
+                                   IN agtype, IN agtype, IN agtype, IN agtype,
+                                   OUT edges    agtype,
+                                   OUT start_id graphid,
+                                   OUT end_id   graphid)
+    RETURNS SETOF record
+LANGUAGE C
+STABLE
+CALLED ON NULL INPUT
+PARALLEL UNSAFE
+AS 'MODULE_PATHNAME';
diff --git a/regress/expected/cypher_match.out 
b/regress/expected/cypher_match.out
index e55ed23c..e99460e5 100644
--- a/regress/expected/cypher_match.out
+++ b/regress/expected/cypher_match.out
@@ -2784,13 +2784,13 @@ SELECT * FROM cypher('cypher_match', $$ MATCH 
p=()-[*]->() RETURN length(p) $$)
  length 
 --------
  1
- 2
  1
+ 2
  1
  1
  2
- 1
  2
+ 1
 (8 rows)
 
 SELECT * FROM cypher('cypher_match', $$ MATCH p=()-[*]->() WHERE length(p) > 1 
RETURN length(p) $$) as (length agtype);
@@ -2812,8 +2812,8 @@ SELECT * FROM cypher('cypher_match', $$ MATCH 
p=()-[*]->() WHERE size(nodes(p))
 SELECT * FROM cypher('cypher_match', $$ MATCH (n {name:'Dave'}) MATCH 
p=()-[*]->() WHERE nodes(p)[0] = n RETURN length(p) $$) as (length agtype);
  length 
 --------
- 1
  2
+ 1
 (2 rows)
 
 SELECT * FROM cypher('cypher_match', $$ MATCH p1=(n {name:'Dave'})-[]->() 
MATCH p2=()-[*]->() WHERE p2=p1 RETURN p2=p1 $$) as (path agtype);
diff --git a/regress/expected/cypher_vle.out b/regress/expected/cypher_vle.out
index 6574e060..27e3bb82 100644
--- a/regress/expected/cypher_vle.out
+++ b/regress/expected/cypher_vle.out
@@ -508,37 +508,37 @@ SELECT * FROM cypher('cypher_vle', $$MATCH 
p=(u)-[e*0..0]->(v) RETURN id(u), p,
 SELECT * FROM cypher('cypher_vle', $$MATCH p=()-[*0..0]->()-[]->() RETURN p 
$$) AS (p agtype);
                                                                                
                                                                                
                                      p                                         
                                                                                
                                                                            
 
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- [{"id": 1407374883553282, "label": "middle", "properties": {}}::vertex, 
{"id": 2533274790395906, "label": "bypass_edge", "end_id": 844424930131969, 
"start_id": 1407374883553282, "properties": {"name": "bypass edge", "number": 
2, "packages": [1, 3, 5, 7], "dangerous": {"type": "poisons", "level": 
"all"}}}::edge, {"id": 844424930131969, "label": "begin", "properties": 
{}}::vertex]::path
  [{"id": 844424930131969, "label": "begin", "properties": {}}::vertex, {"id": 
1125899906842628, "label": "edge", "end_id": 1407374883553281, "start_id": 
844424930131969, "properties": {"name": "main edge", "number": 1, "dangerous": 
{"type": "all", "level": "all"}}}::edge, {"id": 1407374883553281, "label": 
"middle", "properties": {}}::vertex]::path
- [{"id": 1407374883553281, "label": "middle", "properties": {}}::vertex, 
{"id": 1970324836974593, "label": "self_loop", "end_id": 1407374883553281, 
"start_id": 1407374883553281, "properties": {"name": "self loop", "number": 1, 
"dangerous": {"type": "all", "level": "all"}}}::edge, {"id": 1407374883553281, 
"label": "middle", "properties": {}}::vertex]::path
  [{"id": 844424930131969, "label": "begin", "properties": {}}::vertex, {"id": 
2251799813685249, "label": "alternate_edge", "end_id": 1407374883553281, 
"start_id": 844424930131969, "properties": {"name": "alternate edge", "number": 
1, "packages": [2, 4, 6], "dangerous": {"type": "poisons", "level": 
"all"}}}::edge, {"id": 1407374883553281, "label": "middle", "properties": 
{}}::vertex]::path
- [{"id": 1407374883553283, "label": "middle", "properties": {}}::vertex, 
{"id": 2251799813685253, "label": "alternate_edge", "end_id": 1407374883553282, 
"start_id": 1407374883553283, "properties": {"name": "backup edge", "number": 
2, "packages": [1, 3, 5, 7]}}::edge, {"id": 1407374883553282, "label": 
"middle", "properties": {}}::vertex]::path
+ [{"id": 1407374883553281, "label": "middle", "properties": {}}::vertex, 
{"id": 1970324836974593, "label": "self_loop", "end_id": 1407374883553281, 
"start_id": 1407374883553281, "properties": {"name": "self loop", "number": 1, 
"dangerous": {"type": "all", "level": "all"}}}::edge, {"id": 1407374883553281, 
"label": "middle", "properties": {}}::vertex]::path
  [{"id": 1407374883553281, "label": "middle", "properties": {}}::vertex, 
{"id": 1125899906842627, "label": "edge", "end_id": 1407374883553282, 
"start_id": 1407374883553281, "properties": {"name": "main edge", "number": 2, 
"packages": [2, 4, 6], "dangerous": {"type": "all", "level": "all"}}}::edge, 
{"id": 1407374883553282, "label": "middle", "properties": {}}::vertex]::path
  [{"id": 1407374883553282, "label": "middle", "properties": {}}::vertex, 
{"id": 1125899906842626, "label": "edge", "end_id": 1407374883553283, 
"start_id": 1407374883553282, "properties": {"name": "main edge", "number": 3, 
"dangerous": {"type": "all", "level": "all"}}}::edge, {"id": 1407374883553283, 
"label": "middle", "properties": {}}::vertex]::path
  [{"id": 1407374883553282, "label": "middle", "properties": {}}::vertex, 
{"id": 2251799813685250, "label": "alternate_edge", "end_id": 1407374883553283, 
"start_id": 1407374883553282, "properties": {"name": "alternate edge", 
"number": 2, "packages": [2, 4, 6], "dangerous": {"type": "poisons", "level": 
"all"}}}::edge, {"id": 1407374883553283, "label": "middle", "properties": 
{}}::vertex]::path
- [{"id": 1688849860263937, "label": "end", "properties": {}}::vertex, {"id": 
2251799813685252, "label": "alternate_edge", "end_id": 1407374883553283, 
"start_id": 1688849860263937, "properties": {"name": "backup edge", "number": 
1, "packages": [1, 3, 5, 7]}}::edge, {"id": 1407374883553283, "label": 
"middle", "properties": {}}::vertex]::path
- [{"id": 1407374883553283, "label": "middle", "properties": {}}::vertex, 
{"id": 2251799813685251, "label": "alternate_edge", "end_id": 1688849860263937, 
"start_id": 1407374883553283, "properties": {"name": "alternate edge", 
"number": 3, "packages": [2, 4, 6], "dangerous": {"type": "poisons", "level": 
"all"}}}::edge, {"id": 1688849860263937, "label": "end", "properties": 
{}}::vertex]::path
  [{"id": 1407374883553282, "label": "middle", "properties": {}}::vertex, 
{"id": 2533274790395905, "label": "bypass_edge", "end_id": 1688849860263937, 
"start_id": 1407374883553282, "properties": {"name": "bypass edge", "number": 
1, "packages": [1, 3, 5, 7]}}::edge, {"id": 1688849860263937, "label": "end", 
"properties": {}}::vertex]::path
- [{"id": 1688849860263937, "label": "end", "properties": {}}::vertex, {"id": 
1970324836974594, "label": "self_loop", "end_id": 1688849860263937, "start_id": 
1688849860263937, "properties": {"name": "self loop", "number": 2, "dangerous": 
{"type": "all", "level": "all"}}}::edge, {"id": 1688849860263937, "label": 
"end", "properties": {}}::vertex]::path
+ [{"id": 1407374883553282, "label": "middle", "properties": {}}::vertex, 
{"id": 2533274790395906, "label": "bypass_edge", "end_id": 844424930131969, 
"start_id": 1407374883553282, "properties": {"name": "bypass edge", "number": 
2, "packages": [1, 3, 5, 7], "dangerous": {"type": "poisons", "level": 
"all"}}}::edge, {"id": 844424930131969, "label": "begin", "properties": 
{}}::vertex]::path
  [{"id": 1407374883553283, "label": "middle", "properties": {}}::vertex, 
{"id": 1125899906842625, "label": "edge", "end_id": 1688849860263937, 
"start_id": 1407374883553283, "properties": {"name": "main edge", "number": 4, 
"dangerous": {"type": "all", "level": "all"}}}::edge, {"id": 1688849860263937, 
"label": "end", "properties": {}}::vertex]::path
+ [{"id": 1407374883553283, "label": "middle", "properties": {}}::vertex, 
{"id": 2251799813685251, "label": "alternate_edge", "end_id": 1688849860263937, 
"start_id": 1407374883553283, "properties": {"name": "alternate edge", 
"number": 3, "packages": [2, 4, 6], "dangerous": {"type": "poisons", "level": 
"all"}}}::edge, {"id": 1688849860263937, "label": "end", "properties": 
{}}::vertex]::path
+ [{"id": 1407374883553283, "label": "middle", "properties": {}}::vertex, 
{"id": 2251799813685253, "label": "alternate_edge", "end_id": 1407374883553282, 
"start_id": 1407374883553283, "properties": {"name": "backup edge", "number": 
2, "packages": [1, 3, 5, 7]}}::edge, {"id": 1407374883553282, "label": 
"middle", "properties": {}}::vertex]::path
+ [{"id": 1688849860263937, "label": "end", "properties": {}}::vertex, {"id": 
2251799813685252, "label": "alternate_edge", "end_id": 1407374883553283, 
"start_id": 1688849860263937, "properties": {"name": "backup edge", "number": 
1, "packages": [1, 3, 5, 7]}}::edge, {"id": 1407374883553283, "label": 
"middle", "properties": {}}::vertex]::path
+ [{"id": 1688849860263937, "label": "end", "properties": {}}::vertex, {"id": 
1970324836974594, "label": "self_loop", "end_id": 1688849860263937, "start_id": 
1688849860263937, "properties": {"name": "self loop", "number": 2, "dangerous": 
{"type": "all", "level": "all"}}}::edge, {"id": 1688849860263937, "label": 
"end", "properties": {}}::vertex]::path
 (13 rows)
 
 SELECT * FROM cypher('cypher_vle', $$MATCH p=()-[]->()-[*0..0]->() RETURN p 
$$) AS (p agtype);
                                                                                
                                                                                
                                      p                                         
                                                                                
                                                                            
 
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ [{"id": 1407374883553282, "label": "middle", "properties": {}}::vertex, 
{"id": 2533274790395906, "label": "bypass_edge", "end_id": 844424930131969, 
"start_id": 1407374883553282, "properties": {"name": "bypass edge", "number": 
2, "packages": [1, 3, 5, 7], "dangerous": {"type": "poisons", "level": 
"all"}}}::edge, {"id": 844424930131969, "label": "begin", "properties": 
{}}::vertex]::path
  [{"id": 844424930131969, "label": "begin", "properties": {}}::vertex, {"id": 
1125899906842628, "label": "edge", "end_id": 1407374883553281, "start_id": 
844424930131969, "properties": {"name": "main edge", "number": 1, "dangerous": 
{"type": "all", "level": "all"}}}::edge, {"id": 1407374883553281, "label": 
"middle", "properties": {}}::vertex]::path
- [{"id": 844424930131969, "label": "begin", "properties": {}}::vertex, {"id": 
2251799813685249, "label": "alternate_edge", "end_id": 1407374883553281, 
"start_id": 844424930131969, "properties": {"name": "alternate edge", "number": 
1, "packages": [2, 4, 6], "dangerous": {"type": "poisons", "level": 
"all"}}}::edge, {"id": 1407374883553281, "label": "middle", "properties": 
{}}::vertex]::path
  [{"id": 1407374883553281, "label": "middle", "properties": {}}::vertex, 
{"id": 1970324836974593, "label": "self_loop", "end_id": 1407374883553281, 
"start_id": 1407374883553281, "properties": {"name": "self loop", "number": 1, 
"dangerous": {"type": "all", "level": "all"}}}::edge, {"id": 1407374883553281, 
"label": "middle", "properties": {}}::vertex]::path
+ [{"id": 844424930131969, "label": "begin", "properties": {}}::vertex, {"id": 
2251799813685249, "label": "alternate_edge", "end_id": 1407374883553281, 
"start_id": 844424930131969, "properties": {"name": "alternate edge", "number": 
1, "packages": [2, 4, 6], "dangerous": {"type": "poisons", "level": 
"all"}}}::edge, {"id": 1407374883553281, "label": "middle", "properties": 
{}}::vertex]::path
  [{"id": 1407374883553281, "label": "middle", "properties": {}}::vertex, 
{"id": 1125899906842627, "label": "edge", "end_id": 1407374883553282, 
"start_id": 1407374883553281, "properties": {"name": "main edge", "number": 2, 
"packages": [2, 4, 6], "dangerous": {"type": "all", "level": "all"}}}::edge, 
{"id": 1407374883553282, "label": "middle", "properties": {}}::vertex]::path
+ [{"id": 1407374883553283, "label": "middle", "properties": {}}::vertex, 
{"id": 2251799813685253, "label": "alternate_edge", "end_id": 1407374883553282, 
"start_id": 1407374883553283, "properties": {"name": "backup edge", "number": 
2, "packages": [1, 3, 5, 7]}}::edge, {"id": 1407374883553282, "label": 
"middle", "properties": {}}::vertex]::path
  [{"id": 1407374883553282, "label": "middle", "properties": {}}::vertex, 
{"id": 1125899906842626, "label": "edge", "end_id": 1407374883553283, 
"start_id": 1407374883553282, "properties": {"name": "main edge", "number": 3, 
"dangerous": {"type": "all", "level": "all"}}}::edge, {"id": 1407374883553283, 
"label": "middle", "properties": {}}::vertex]::path
  [{"id": 1407374883553282, "label": "middle", "properties": {}}::vertex, 
{"id": 2251799813685250, "label": "alternate_edge", "end_id": 1407374883553283, 
"start_id": 1407374883553282, "properties": {"name": "alternate edge", 
"number": 2, "packages": [2, 4, 6], "dangerous": {"type": "poisons", "level": 
"all"}}}::edge, {"id": 1407374883553283, "label": "middle", "properties": 
{}}::vertex]::path
- [{"id": 1407374883553282, "label": "middle", "properties": {}}::vertex, 
{"id": 2533274790395905, "label": "bypass_edge", "end_id": 1688849860263937, 
"start_id": 1407374883553282, "properties": {"name": "bypass edge", "number": 
1, "packages": [1, 3, 5, 7]}}::edge, {"id": 1688849860263937, "label": "end", 
"properties": {}}::vertex]::path
- [{"id": 1407374883553282, "label": "middle", "properties": {}}::vertex, 
{"id": 2533274790395906, "label": "bypass_edge", "end_id": 844424930131969, 
"start_id": 1407374883553282, "properties": {"name": "bypass edge", "number": 
2, "packages": [1, 3, 5, 7], "dangerous": {"type": "poisons", "level": 
"all"}}}::edge, {"id": 844424930131969, "label": "begin", "properties": 
{}}::vertex]::path
- [{"id": 1407374883553283, "label": "middle", "properties": {}}::vertex, 
{"id": 1125899906842625, "label": "edge", "end_id": 1688849860263937, 
"start_id": 1407374883553283, "properties": {"name": "main edge", "number": 4, 
"dangerous": {"type": "all", "level": "all"}}}::edge, {"id": 1688849860263937, 
"label": "end", "properties": {}}::vertex]::path
- [{"id": 1407374883553283, "label": "middle", "properties": {}}::vertex, 
{"id": 2251799813685251, "label": "alternate_edge", "end_id": 1688849860263937, 
"start_id": 1407374883553283, "properties": {"name": "alternate edge", 
"number": 3, "packages": [2, 4, 6], "dangerous": {"type": "poisons", "level": 
"all"}}}::edge, {"id": 1688849860263937, "label": "end", "properties": 
{}}::vertex]::path
- [{"id": 1407374883553283, "label": "middle", "properties": {}}::vertex, 
{"id": 2251799813685253, "label": "alternate_edge", "end_id": 1407374883553282, 
"start_id": 1407374883553283, "properties": {"name": "backup edge", "number": 
2, "packages": [1, 3, 5, 7]}}::edge, {"id": 1407374883553282, "label": 
"middle", "properties": {}}::vertex]::path
  [{"id": 1688849860263937, "label": "end", "properties": {}}::vertex, {"id": 
2251799813685252, "label": "alternate_edge", "end_id": 1407374883553283, 
"start_id": 1688849860263937, "properties": {"name": "backup edge", "number": 
1, "packages": [1, 3, 5, 7]}}::edge, {"id": 1407374883553283, "label": 
"middle", "properties": {}}::vertex]::path
+ [{"id": 1407374883553283, "label": "middle", "properties": {}}::vertex, 
{"id": 1125899906842625, "label": "edge", "end_id": 1688849860263937, 
"start_id": 1407374883553283, "properties": {"name": "main edge", "number": 4, 
"dangerous": {"type": "all", "level": "all"}}}::edge, {"id": 1688849860263937, 
"label": "end", "properties": {}}::vertex]::path
  [{"id": 1688849860263937, "label": "end", "properties": {}}::vertex, {"id": 
1970324836974594, "label": "self_loop", "end_id": 1688849860263937, "start_id": 
1688849860263937, "properties": {"name": "self loop", "number": 2, "dangerous": 
{"type": "all", "level": "all"}}}::edge, {"id": 1688849860263937, "label": 
"end", "properties": {}}::vertex]::path
+ [{"id": 1407374883553283, "label": "middle", "properties": {}}::vertex, 
{"id": 2251799813685251, "label": "alternate_edge", "end_id": 1688849860263937, 
"start_id": 1407374883553283, "properties": {"name": "alternate edge", 
"number": 3, "packages": [2, 4, 6], "dangerous": {"type": "poisons", "level": 
"all"}}}::edge, {"id": 1688849860263937, "label": "end", "properties": 
{}}::vertex]::path
+ [{"id": 1407374883553282, "label": "middle", "properties": {}}::vertex, 
{"id": 2533274790395905, "label": "bypass_edge", "end_id": 1688849860263937, 
"start_id": 1407374883553282, "properties": {"name": "bypass edge", "number": 
1, "packages": [1, 3, 5, 7]}}::edge, {"id": 1688849860263937, "label": "end", 
"properties": {}}::vertex]::path
 (13 rows)
 
 --
@@ -598,9 +598,9 @@ SELECT * FROM cypher('mygraph', $$
 $$) AS (g5 agtype);
                                                                                
                                                                                
                                                                                
     g5                                                                         
                                                                                
                                                                                
            
 
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- [{"id": 844424930131969, "label": "Node", "properties": {"name": 
"a"}}::vertex, {"id": 1125899906842627, "label": "Edge", "end_id": 
844424930131971, "start_id": 844424930131969, "properties": {}}::edge, {"id": 
844424930131971, "label": "Node", "properties": {"name": "b"}}::vertex]::path
  [{"id": 844424930131969, "label": "Node", "properties": {"name": 
"a"}}::vertex, {"id": 1125899906842627, "label": "Edge", "end_id": 
844424930131971, "start_id": 844424930131969, "properties": {}}::edge, {"id": 
844424930131971, "label": "Node", "properties": {"name": "b"}}::vertex, {"id": 
1125899906842626, "label": "Edge", "end_id": 844424930131970, "start_id": 
844424930131971, "properties": {}}::edge, {"id": 844424930131970, "label": 
"Node", "properties": {"name": "c"}}::vertex]::path
  [{"id": 844424930131971, "label": "Node", "properties": {"name": 
"b"}}::vertex, {"id": 1125899906842626, "label": "Edge", "end_id": 
844424930131970, "start_id": 844424930131971, "properties": {}}::edge, {"id": 
844424930131970, "label": "Node", "properties": {"name": "c"}}::vertex]::path
+ [{"id": 844424930131969, "label": "Node", "properties": {"name": 
"a"}}::vertex, {"id": 1125899906842627, "label": "Edge", "end_id": 
844424930131971, "start_id": 844424930131969, "properties": {}}::edge, {"id": 
844424930131971, "label": "Node", "properties": {"name": "b"}}::vertex]::path
 (3 rows)
 
 SELECT drop_graph('mygraph', true);
@@ -1027,9 +1027,9 @@ SELECT * FROM cypher('issue_1043', $$ CREATE (n)-[:KNOWS 
{n:'hello'}]->({n:'hell
 SELECT * FROM cypher('issue_1043', $$ MATCH (x)<-[y *]-(),({n:y[0].n}) RETURN 
x $$) as (a agtype);
                                      a                                      
 ----------------------------------------------------------------------------
- {"id": 281474976710658, "label": "", "properties": {"n": "hello"}}::vertex
  {"id": 281474976710658, "label": "", "properties": {"n": "hello"}}::vertex
  {"id": 281474976710660, "label": "", "properties": {"n": "hello"}}::vertex
+ {"id": 281474976710658, "label": "", "properties": {"n": "hello"}}::vertex
  {"id": 281474976710660, "label": "", "properties": {"n": "hello"}}::vertex
 (4 rows)
 
diff --git a/regress/expected/expr.out b/regress/expected/expr.out
index 4a332a33..23a3af28 100644
--- a/regress/expected/expr.out
+++ b/regress/expected/expr.out
@@ -8354,11 +8354,11 @@ $$) AS (vle_array agtype);
                                                                                
                                        vle_array                               
                                                                                
         
 
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge]
- [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge, {"id": 1407374883553281, 
"label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, 
"properties": {}}::edge]
+ [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge, {"id": 1407374883553282, 
"label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, 
"properties": {}}::edge]
  [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge]
  [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge]
+ [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge, {"id": 1407374883553281, 
"label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, 
"properties": {}}::edge]
  [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge]
- [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge, {"id": 1407374883553282, 
"label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, 
"properties": {}}::edge]
 (6 rows)
 
 SELECT * FROM cypher('expr', $$
@@ -8368,8 +8368,8 @@ SELECT * FROM cypher('expr', $$
 $$) AS (vle_array agtype);
                                                                                
                                        vle_array                               
                                                                                
         
 
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge, {"id": 1407374883553281, 
"label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, 
"properties": {}}::edge]
  [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge, {"id": 1407374883553282, 
"label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, 
"properties": {}}::edge]
+ [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge, {"id": 1407374883553281, 
"label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, 
"properties": {}}::edge]
 (2 rows)
 
 SELECT * FROM cypher('expr', $$
@@ -8388,12 +8388,12 @@ SELECT * FROM cypher('expr', $$
 $$) AS (vle_array agtype);
                                                                                
                                        vle_array                               
                                                                                
         
 
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge]
- [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge, {"id": 1407374883553281, 
"label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, 
"properties": {}}::edge]
+ [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge, {"id": 1407374883553282, 
"label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, 
"properties": {}}::edge]
  [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge]
  [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge]
+ [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge]
  [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge]
- [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge, {"id": 1407374883553282, 
"label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, 
"properties": {}}::edge]
+ [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge, {"id": 1407374883553281, 
"label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, 
"properties": {}}::edge]
 (6 rows)
 
 -- head
@@ -8403,11 +8403,11 @@ SELECT * FROM cypher('expr', $$
 $$) AS (head agtype);
                                                            head                
                                            
 
---------------------------------------------------------------------------------------------------------------------------
- {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge
- {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge
  {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge
  {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge
+ {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge
  {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge
+ {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge
  {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge
 (6 rows)
 
@@ -8418,12 +8418,12 @@ SELECT * FROM cypher('expr', $$
 $$) AS (head agtype);
                                                                                
                                           head                                 
                                                                                
         
 
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge]
- [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge, {"id": 1407374883553281, 
"label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, 
"properties": {}}::edge]
+ [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge, {"id": 1407374883553282, 
"label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, 
"properties": {}}::edge]
  [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge]
  [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge]
+ [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge]
  [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge]
- [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge, {"id": 1407374883553282, 
"label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, 
"properties": {}}::edge]
+ [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge, {"id": 1407374883553281, 
"label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, 
"properties": {}}::edge]
 (6 rows)
 
 SELECT * FROM cypher('expr', $$
@@ -8433,12 +8433,12 @@ SELECT * FROM cypher('expr', $$
 $$) AS (head agtype);
                                                                                
                                           head                                 
                                                                                
         
 
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge]
- [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge, {"id": 1407374883553281, 
"label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, 
"properties": {}}::edge]
+ [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge, {"id": 1407374883553282, 
"label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, 
"properties": {}}::edge]
  [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge]
  [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge]
+ [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge]
  [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge]
- [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge, {"id": 1407374883553282, 
"label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, 
"properties": {}}::edge]
+ [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge, {"id": 1407374883553281, 
"label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, 
"properties": {}}::edge]
 (6 rows)
 
 SELECT * FROM cypher('expr', $$
@@ -8459,11 +8459,11 @@ $$) AS (head agtype);
                                                            head                
                                            
 
---------------------------------------------------------------------------------------------------------------------------
  {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge
- {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge
+ {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge
  {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge
  {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge
  {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge
- {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge
+ {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge
 (6 rows)
 
 SELECT * FROM cypher('expr', $$
@@ -8473,9 +8473,9 @@ SELECT * FROM cypher('expr', $$
 $$) AS (head agtype);
                                                             head               
                                              
 
-----------------------------------------------------------------------------------------------------------------------------
- [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge]
  [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge]
  [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge]
+ [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge]
  [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge]
 (4 rows)
 
@@ -8486,12 +8486,12 @@ SELECT * FROM cypher('expr', $$
 $$) AS (head agtype);
                                                                                
                                           head                                 
                                                                                
         
 
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge]
- [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge, {"id": 1407374883553281, 
"label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, 
"properties": {}}::edge]
+ [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge, {"id": 1407374883553282, 
"label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, 
"properties": {}}::edge]
  [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge]
  [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge]
+ [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge]
  [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge]
- [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge, {"id": 1407374883553282, 
"label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, 
"properties": {}}::edge]
+ [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge, {"id": 1407374883553281, 
"label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, 
"properties": {}}::edge]
 (6 rows)
 
 SELECT * FROM cypher('expr', $$
@@ -8522,11 +8522,11 @@ $$) AS (head agtype);
                                                                                
                                           head                                 
                                                                                
         
 
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge]
- [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge, {"id": 1407374883553281, 
"label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, 
"properties": {}}::edge]
+ [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge, {"id": 1407374883553282, 
"label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, 
"properties": {}}::edge]
  [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge]
  [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge]
+ [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge, {"id": 1407374883553281, 
"label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, 
"properties": {}}::edge]
  [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge]
- [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge, {"id": 1407374883553282, 
"label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, 
"properties": {}}::edge]
 (6 rows)
 
 SELECT * FROM cypher('expr', $$
@@ -8537,11 +8537,11 @@ $$) AS (head agtype);
                                                                                
                                           head                                 
                                                                                
         
 
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge]
- [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge, {"id": 1407374883553281, 
"label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, 
"properties": {}}::edge]
+ [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge, {"id": 1407374883553282, 
"label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, 
"properties": {}}::edge]
  [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge]
  [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge]
+ [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge, {"id": 1407374883553281, 
"label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, 
"properties": {}}::edge]
  [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge]
- [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge, {"id": 1407374883553282, 
"label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, 
"properties": {}}::edge]
 (6 rows)
 
 SELECT * FROM cypher('expr', $$
@@ -8552,11 +8552,11 @@ $$) AS (head agtype);
                                                                                
                                           head                                 
                                                                                
         
 
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge]
- [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge, {"id": 1407374883553281, 
"label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, 
"properties": {}}::edge]
+ [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge, {"id": 1407374883553282, 
"label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, 
"properties": {}}::edge]
  [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge]
  [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge]
+ [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge, {"id": 1407374883553281, 
"label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, 
"properties": {}}::edge]
  [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge]
- [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge, {"id": 1407374883553282, 
"label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, 
"properties": {}}::edge]
 (6 rows)
 
 -- reverse
@@ -8567,11 +8567,11 @@ $$) as (u agtype);
                                                                                
                                            u                                   
                                                                                
         
 
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge]
- [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge, {"id": 1407374883553282, 
"label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, 
"properties": {}}::edge]
+ [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge, {"id": 1407374883553281, 
"label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, 
"properties": {}}::edge]
  [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge]
  [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge]
+ [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge, {"id": 1407374883553282, 
"label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, 
"properties": {}}::edge]
  [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge]
- [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge, {"id": 1407374883553281, 
"label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, 
"properties": {}}::edge]
 (6 rows)
 
 SELECT * FROM cypher('expr', $$
@@ -8581,12 +8581,12 @@ SELECT * FROM cypher('expr', $$
 $$) as (u agtype);
                                                                                
                                            u                                   
                                                                                
         
 
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge]
- [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge, {"id": 1407374883553282, 
"label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, 
"properties": {}}::edge]
+ [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge, {"id": 1407374883553281, 
"label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, 
"properties": {}}::edge]
  [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge]
  [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge]
+ [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge]
  [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge]
- [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge, {"id": 1407374883553281, 
"label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, 
"properties": {}}::edge]
+ [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge, {"id": 1407374883553282, 
"label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, 
"properties": {}}::edge]
 (6 rows)
 
 -- IN operator
@@ -8611,8 +8611,8 @@ $$) AS (a agtype);
                                                                                
                                            a                                   
                                                                                
         
 
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge]
- [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge, {"id": 1407374883553281, 
"label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, 
"properties": {}}::edge]
  [{"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, 
"start_id": 1125899906842626, "properties": {}}::edge]
+ [{"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, 
"start_id": 1125899906842625, "properties": {}}::edge, {"id": 1407374883553281, 
"label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, 
"properties": {}}::edge]
 (3 rows)
 
 SELECT * FROM cypher('expr', $$
diff --git a/sql/agtype_typecast.sql b/sql/agtype_typecast.sql
index c29c0a65..abca5e51 100644
--- a/sql/agtype_typecast.sql
+++ b/sql/agtype_typecast.sql
@@ -70,10 +70,14 @@ PARALLEL SAFE
 AS 'MODULE_PATHNAME';
 
 -- original VLE function definition
+-- S4: emit start_id/end_id as scalar columns to enable transformer rewrite
+-- of terminal-edge quals as integer equalities (see 
PERF_VLE_TERMINAL_QUAL_PLAN).
 CREATE FUNCTION ag_catalog.age_vle(IN agtype, IN agtype, IN agtype, IN agtype,
                                    IN agtype, IN agtype, IN agtype,
-                                   OUT edges agtype)
-    RETURNS SETOF agtype
+                                   OUT edges    agtype,
+                                   OUT start_id graphid,
+                                   OUT end_id   graphid)
+    RETURNS SETOF record
 LANGUAGE C
 STABLE
 CALLED ON NULL INPUT
@@ -84,8 +88,10 @@ AS 'MODULE_PATHNAME';
 -- caching mechanism to coexist with the previous VLE version.
 CREATE FUNCTION ag_catalog.age_vle(IN agtype, IN agtype, IN agtype, IN agtype,
                                    IN agtype, IN agtype, IN agtype, IN agtype,
-                                   OUT edges agtype)
-    RETURNS SETOF agtype
+                                   OUT edges    agtype,
+                                   OUT start_id graphid,
+                                   OUT end_id   graphid)
+    RETURNS SETOF record
 LANGUAGE C
 STABLE
 CALLED ON NULL INPUT
@@ -100,15 +106,6 @@ CREATE FUNCTION 
ag_catalog.age_build_vle_match_edge(agtype, agtype)
 PARALLEL SAFE
 AS 'MODULE_PATHNAME';
 
--- function to match a terminal vle edge
-CREATE FUNCTION ag_catalog.age_match_vle_terminal_edge(variadic "any")
-    RETURNS boolean
-    LANGUAGE C
-    STABLE
-CALLED ON NULL INPUT
-PARALLEL SAFE
-AS 'MODULE_PATHNAME';
-
 -- function to create an AGTV_PATH from a VLE_path_container
 CREATE FUNCTION ag_catalog.age_materialize_vle_path(agtype)
     RETURNS agtype
@@ -135,14 +132,6 @@ RETURNS NULL ON NULL INPUT
 PARALLEL SAFE
 AS 'MODULE_PATHNAME';
 
-CREATE FUNCTION ag_catalog.age_match_two_vle_edges(agtype, agtype)
-    RETURNS boolean
-    LANGUAGE C
-    STABLE
-RETURNS NULL ON NULL INPUT
-PARALLEL SAFE
-AS 'MODULE_PATHNAME';
-
 -- list functions
 CREATE FUNCTION ag_catalog.age_keys(agtype)
     RETURNS agtype
diff --git a/src/backend/parser/cypher_clause.c 
b/src/backend/parser/cypher_clause.c
index 3083c52e..015c0719 100644
--- a/src/backend/parser/cypher_clause.c
+++ b/src/backend/parser/cypher_clause.c
@@ -3982,10 +3982,6 @@ static List 
*make_join_condition_for_edge(cypher_parsestate *cpstate,
     {
         Node *left_id = NULL;
         Node *right_id = NULL;
-        String *ag_catalog = makeString("ag_catalog");
-        String *func_name;
-        List *qualified_func_name;
-        List *args = NIL;
         List *quals = NIL;
 
         /*
@@ -3998,56 +3994,107 @@ static List 
*make_join_condition_for_edge(cypher_parsestate *cpstate,
         }
 
         /*
-         * If the previous node and the next node are in the join tree, we need
-         * to create the age_match_vle_terminal_edge to compare the vle 
returned
-         * results against the two nodes.
+         * S5: if the previous and next nodes are both in the join tree,
+         * emit two graphid equality A_Exprs:
+         *   <vle_alias>.start_id = prev_node.id
+         *   <vle_alias>.end_id   = next_node.id
+         * This replaces the historical per-row
+         *   age_match_vle_terminal_edge(prev.id, next.id, edges)
+         * function call with plain integer (int8) equality quals on the
+         * SRF's S4 output columns.  The planner can now drive the join
+         * directly on these keys (HashJoin hash keys, NestLoop index
+         * conditions where indexed).
          */
         if (prev_node->in_join_tree)
         {
-            func_name = makeString("age_match_vle_terminal_edge");
-            qualified_func_name = list_make2(ag_catalog, func_name);
+            ColumnRef *cr_start;
+            ColumnRef *cr_end;
+            A_Expr    *eq_start;
+            A_Expr    *eq_end;
 
             /*
-             * Get the vertex's id and pass to the function. Pass in NULL
-             * otherwise.
+             * Production-build runtime guard. Asserts compile out in
+             * non-debug builds, so a NULL vle_alias would otherwise reach
+             * makeString() and crash the backend during ColumnRef build.
              */
-            left_id = (Node *)make_qual(cpstate, prev_node, "id");
+            if (entity->vle_alias == NULL)
+            {
+                ereport(ERROR,
+                        (errcode(ERRCODE_INTERNAL_ERROR),
+                         errmsg("VLE edge entity is missing its alias; cannot 
emit terminal-edge join qual")));
+            }
+            Assert(entity->vle_alias != NULL);
+
+            cr_start = makeNode(ColumnRef);
+            cr_start->fields = list_make2(makeString(entity->vle_alias),
+                                          makeString("start_id"));
+            cr_start->location = -1;
+
+            cr_end = makeNode(ColumnRef);
+            cr_end->fields = list_make2(makeString(entity->vle_alias),
+                                        makeString("end_id"));
+            cr_end->location = -1;
+
+            left_id  = (Node *)make_qual(cpstate, prev_node, "id");
             right_id = (Node *)make_qual(cpstate, next_node, "id");
 
-            /* create the argument list */
-            args = list_make3(left_id, right_id, entity->expr);
+            eq_start = makeSimpleA_Expr(AEXPR_OP, "=",
+                                        (Node *)cr_start, left_id, -1);
+            eq_end   = makeSimpleA_Expr(AEXPR_OP, "=",
+                                        (Node *)cr_end,   right_id, -1);
 
-            /* add to quals */
-            quals = lappend(quals, makeFuncCall(qualified_func_name, args,
-                                                COERCE_EXPLICIT_CALL, -1));
+            quals = lappend(quals, eq_start);
+            quals = lappend(quals, eq_end);
         }
 
         /*
-         * When the previous node is not in the join tree, but there is a vle
-         * edge before that join, then we need to compare this vle's start node
-         * against the previous vle's end node. No need to check the next edge,
-         * because that would be redundant.
+         * S6: when the previous node is not in the join tree but there is
+         * a vle edge before that join, emit a single graphid equality
+         * connecting the two VLE SRFs:
+         *
+         *     prev_vle.end_id = this_vle.start_id
+         *
+         * This replaces the per-row age_match_two_vle_edges(prev, this)
+         * function call with a plain int8 equality on the S4 scalar
+         * output columns of both age_vle SRFs.  No detoasting of either
+         * VLE_path_container is needed.
          */
         if (!prev_node->in_join_tree &&
             prev_edge != NULL &&
             prev_edge->type == ENT_VLE_EDGE)
         {
-            List *qualified_name;
-            String *match_qual;
-            FuncCall *fc;
+            ColumnRef *cr_prev_end;
+            ColumnRef *cr_this_start;
+            A_Expr    *eq_chain;
 
-            match_qual = makeString("age_match_two_vle_edges");
+            /*
+             * Production-build runtime guard for both VLE aliases; see
+             * note above on entity->vle_alias.
+             */
+            if (prev_edge->vle_alias == NULL || entity->vle_alias == NULL)
+            {
+                ereport(ERROR,
+                        (errcode(ERRCODE_INTERNAL_ERROR),
+                         errmsg("VLE edge entity is missing its alias; cannot 
emit two-VLE-edge join qual")));
+            }
+            Assert(prev_edge->vle_alias != NULL);
+            Assert(entity->vle_alias != NULL);
 
-            /* make the qualified function name */
-            qualified_name = list_make2(ag_catalog, match_qual);
+            cr_prev_end = makeNode(ColumnRef);
+            cr_prev_end->fields = list_make2(makeString(prev_edge->vle_alias),
+                                             makeString("end_id"));
+            cr_prev_end->location = -1;
 
-            /* make the args */
-            args = list_make2(prev_edge->expr, entity->expr);
+            cr_this_start = makeNode(ColumnRef);
+            cr_this_start->fields = list_make2(makeString(entity->vle_alias),
+                                               makeString("start_id"));
+            cr_this_start->location = -1;
 
-            /* create the function call */
-            fc = makeFuncCall(qualified_name, args, COERCE_EXPLICIT_CALL, -1);
+            eq_chain = makeSimpleA_Expr(AEXPR_OP, "=",
+                                        (Node *)cr_prev_end,
+                                        (Node *)cr_this_start, -1);
 
-            quals = lappend(quals, fc);
+            quals = lappend(quals, eq_chain);
         }
 
         return quals;
@@ -4898,6 +4945,12 @@ static transform_entity 
*transform_VLE_edge_entity(cypher_parsestate *cpstate,
     vle_entity = make_transform_entity(cpstate, ENT_VLE_EDGE, (Node *)rel,
                                        (Expr *)var);
 
+    /*
+     * S5: stash the auto-generated alias name so make_join_condition_for_edge
+     * can build ColumnRefs for the SRF's start_id/end_id output columns.
+     */
+    vle_entity->vle_alias = alias->aliasname;
+
     /* return the vle entity */
     return vle_entity;
 }
diff --git a/src/backend/parser/cypher_transform_entity.c 
b/src/backend/parser/cypher_transform_entity.c
index 1b2ab0ed..13f73360 100644
--- a/src/backend/parser/cypher_transform_entity.c
+++ b/src/backend/parser/cypher_transform_entity.c
@@ -52,6 +52,7 @@ transform_entity *make_transform_entity(cypher_parsestate 
*cpstate,
     entity->declared_in_current_clause = true;
     entity->expr = expr;
     entity->in_join_tree = expr != NULL;
+    entity->vle_alias = NULL;
 
     return entity;
 }
diff --git a/src/backend/utils/adt/age_vle.c b/src/backend/utils/adt/age_vle.c
index 22c268cd..c312a62e 100644
--- a/src/backend/utils/adt/age_vle.c
+++ b/src/backend/utils/adt/age_vle.c
@@ -140,6 +140,36 @@ typedef struct VLE_local_context
  * structure is set up to contains a BINARY container that can be accessed by
  * functions that need to process the path.
  */
+/*
+ * Layout (offsets, with int64 alignment):
+ *
+ *     0:  vl_len_[4]              varlena length header (int32 + pad)
+ *     4:  header                  AGT_FBINARY | AGT_FBINARY_TYPE_VLE_PATH
+ *     8:  graph_oid               source graph oid
+ *    12:  (4 bytes pad)           int64 alignment
+ *    16:  graphid_array_size      number of graphids in the path
+ *    24:  container_size_bytes    total bytes of this container
+ *    32:  start_vid               redundant cache of graphid_array[0]
+ *    40:  end_vid                 redundant cache of
+ *                                 graphid_array[graphid_array_size - 1]
+ *    48:  graphid_array_data      flexible array start
+ *
+ * start_vid / end_vid are populated whenever the container is built and let
+ * downstream consumers (the age_vle SRF's start_id/end_id output columns)
+ * read the join endpoints without traversing the (potentially toasted)
+ * variadic payload.
+ *
+ * Persistence note: VLE_path_container is a transient SRF output. It is
+ * consumed within the same query that produces it (by the planner-emitted
+ * endpoint equalities and by age_materialize_vle_path / _vle_edges) and is
+ * never written back to disk. Because no on-disk instance of this layout
+ * can exist, adding fields to the struct does not require a binary
+ * version bump or a backward-compatible decoder. If a future change ever
+ * makes a VLE container persistable (e.g. by allowing it to be returned
+ * directly as agtype and stored in a column), the AGT_FBINARY_TYPE_VLE_PATH
+ * tag must be versioned and the readers (GET_GRAPHID_ARRAY_FROM_CONTAINER
+ * etc.) must branch on the version.
+ */
 typedef struct VLE_path_container
 {
     char vl_len_[4]; /* Do not touch this field! */
@@ -147,6 +177,8 @@ typedef struct VLE_path_container
     uint32 graph_oid;
     int64 graphid_array_size;
     int64 container_size_bytes;
+    graphid start_vid;
+    graphid end_vid;
     graphid graphid_array_data;
 } VLE_path_container;
 
@@ -1421,9 +1453,11 @@ static VLE_path_container 
*create_VLE_path_container(int64 path_size)
      *     One for both the header and graph oid (they are both 32 bits).
      *     One for the size of the graphid_array_size.
      *     One for the container_size_bytes.
+     *     One for start_vid (Stage 1: inline endpoint cache).
+     *     One for end_vid   (Stage 1: inline endpoint cache).
      *
      */
-    container_size_bytes = sizeof(graphid) * (path_size + 4);
+    container_size_bytes = sizeof(graphid) * (path_size + 6);
 
     /* allocate the container */
     vpc = palloc0(container_size_bytes);
@@ -1533,6 +1567,13 @@ static VLE_path_container 
*build_VLE_path_container(VLE_local_context *vlelctx)
         graphid_array[index+1] = vid;
     }
 
+    /*
+     * Stage 1: cache endpoints in the fixed header so the join qual can read
+     * them without touching the (possibly toasted) graphid array.
+     */
+    vpc->start_vid = graphid_array[0];
+    vpc->end_vid = graphid_array[vpc->graphid_array_size - 1];
+
     /* return the container */
     return vpc;
 }
@@ -1569,6 +1610,12 @@ static VLE_path_container 
*build_VLE_zero_container(VLE_local_context *vlelctx)
     vid = vlelctx->vsid;
     graphid_array[0] = vid;
 
+    /*
+     * Stage 1: zero-edge container; start and end are both the start vertex.
+     */
+    vpc->start_vid = vid;
+    vpc->end_vid = vid;
+
     return vpc;
 }
 
@@ -1788,6 +1835,26 @@ Datum age_vle(PG_FUNCTION_ARGS)
         /* create a function context for cross-call persistence */
         funcctx = SRF_FIRSTCALL_INIT();
 
+        /*
+         * S4: capture the result tuple descriptor.  age_vle now emits a
+         * composite (edges, start_id, end_id) row, so we need a blessed
+         * TupleDesc that survives across SRF calls.
+         */
+        {
+            TupleDesc      tupdesc;
+            MemoryContext  tdesc_oldctx;
+
+            tdesc_oldctx = 
MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+            if (get_call_result_type(fcinfo, NULL, &tupdesc) != 
TYPEFUNC_COMPOSITE)
+            {
+                ereport(ERROR,
+                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                         errmsg("age_vle: function returning record called in 
context that cannot accept type record")));
+            }
+            funcctx->tuple_desc = BlessTupleDesc(tupdesc);
+            MemoryContextSwitchTo(tdesc_oldctx);
+        }
+
         /* build the local vle context */
         vlelctx = build_local_vle_context(fcinfo, funcctx);
 
@@ -1920,8 +1987,24 @@ Datum age_vle(PG_FUNCTION_ARGS)
             vpc = build_VLE_zero_container(vlelctx);
         }
 
-        /* return the result and signal that the function is not yet done */
-        SRF_RETURN_NEXT(funcctx, PointerGetDatum(vpc));
+        /*
+         * S4: emit a composite (edges, start_id, end_id) row.  The
+         * scalar endpoint columns let the cypher transformer (S5)
+         * rewrite terminal-edge quals as integer equalities, removing
+         * the per-row age_match_vle_terminal_edge function call.
+         */
+        {
+            Datum     values[3];
+            bool      nulls[3] = {false, false, false};
+            HeapTuple tuple;
+
+            values[0] = PointerGetDatum(vpc);
+            values[1] = GRAPHID_GET_DATUM(vpc->start_vid);
+            values[2] = GRAPHID_GET_DATUM(vpc->end_vid);
+
+            tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
+            SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
+        }
     }
     /* otherwise, we are done and we need to cleanup and signal done */
     else
@@ -1992,66 +2075,31 @@ agtype_value *agtv_materialize_vle_path(agtype 
*agt_arg_vpc)
     return agtv_path;
 }
 
-/* PG function to match 2 VLE edges */
+/*
+ * age_match_two_vle_edges and age_match_vle_terminal_edge are retained as
+ * stub C symbols only.  The cypher transformer no longer emits calls to
+ * either function: terminal-edge match quals are now plain graphid
+ * equalities on the age_vle SRF's start_id/end_id output columns
+ * (Stages S4/S5/S6 of the VLE terminal-qual rewrite).
+ *
+ * The corresponding SQL declarations have been removed from fresh
+ * installs (sql/agtype_typecast.sql) and are DROP'd by the upgrade
+ * script (age--1.7.0--y.y.y.sql).  These C entry points exist solely so
+ * the upgrade-test machinery, which loads an older "1.7.0_initial" SQL
+ * snapshot against the current age.so, can resolve the symbols before
+ * the immediate ALTER EXTENSION UPDATE drops them.  They should never
+ * be reachable from any committed SQL path.
+ */
 PG_FUNCTION_INFO_V1(age_match_two_vle_edges);
 
 Datum age_match_two_vle_edges(PG_FUNCTION_ARGS)
 {
-    agtype *agt_arg_vpc = NULL;
-    VLE_path_container *left_path = NULL, *right_path = NULL;
-    graphid *left_array, *right_array;
-    int left_array_size;
-
-    /*
-     * If either argument is NULL, return FALSE. This can occur in
-     * OPTIONAL MATCH (LEFT JOIN) contexts where a preceding clause
-     * produced no results.
-     */
-    if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
-    {
-        PG_RETURN_BOOL(false);
-    }
-
-    /* get the VLE_path_container argument */
-    agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0);
-
-    if (!AGT_ROOT_IS_BINARY(agt_arg_vpc) ||
-        AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) != AGT_FBINARY_TYPE_VLE_PATH)
-    {
-        ereport(ERROR,
-            (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-            errmsg("argument 1 of age_match_two_vle_edges must be a 
VLE_Path_Container")));
-    }
-
-    /* cast argument as a VLE_Path_Container and extract graphid array */
-    left_path = (VLE_path_container *)agt_arg_vpc;
-    left_array_size = left_path->graphid_array_size;
-    left_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(left_path);
-
-    PG_FREE_IF_COPY(agt_arg_vpc, 0);
-
-    agt_arg_vpc = AG_GET_ARG_AGTYPE_P(1);
-
-    if (!AGT_ROOT_IS_BINARY(agt_arg_vpc) ||
-        AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) != AGT_FBINARY_TYPE_VLE_PATH)
-    {
-        ereport(ERROR,
-            (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-            errmsg("argument 2 of age_match_two_vle_edges must be a 
VLE_Path_Container")));
-    }
-
-    /* cast argument as a VLE_Path_Container and extract graphid array */
-    right_path = (VLE_path_container *)agt_arg_vpc;
-    right_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(right_path);
-
-    PG_FREE_IF_COPY(agt_arg_vpc, 1);
-
-    if (left_array[left_array_size - 1] != right_array[0])
-    {
-        PG_RETURN_BOOL(false);
-    }
-
-    PG_RETURN_BOOL(true);
+    ereport(ERROR,
+        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+         errmsg("age_match_two_vle_edges() is removed; "
+                "VLE endpoint matching is now handled by the planner via "
+                "the age_vle SRF's start_id/end_id output columns")));
+    PG_RETURN_BOOL(false);
 }
 
 /*
@@ -2289,147 +2337,17 @@ Datum age_materialize_vle_path(PG_FUNCTION_ARGS)
     PG_RETURN_POINTER(agt_materialize_vle_path(agt_arg_vpc));
 }
 
-/*
- * PG function to take a VLE_path_container and return whether the supplied end
- * vertex (target/veid) matches against the last edge in the VLE path. The VLE
- * path is encoded in a BINARY container.
- */
+/* Stub: see comment on age_match_two_vle_edges above. */
 PG_FUNCTION_INFO_V1(age_match_vle_terminal_edge);
 
 Datum age_match_vle_terminal_edge(PG_FUNCTION_ARGS)
 {
-    VLE_path_container *vpc = NULL;
-    agtype *agt_arg_vsid = NULL;
-    agtype *agt_arg_veid = NULL;
-    agtype *agt_arg_path = NULL;
-    agtype_value *agtv_temp = NULL;
-    graphid vsid = 0;
-    graphid veid = 0;
-    graphid *gida = NULL;
-    int gidasize = 0;
-    Oid type0, type1;
-
-    /* check argument count */
-    if (PG_NARGS() != 3)
-    {
-        ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                        errmsg("age_match_vle_terminal_edge() invalid number 
of arguments")));
-    }
-
-    /*
-     * If any argument is NULL, return FALSE. This can occur when this
-     * function is used as a join qual in an OPTIONAL MATCH (LEFT JOIN)
-     * where a preceding OPTIONAL MATCH produced no results. Returning
-     * FALSE allows PostgreSQL to produce the correct NULL-extended rows.
-     */
-    if (PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2))
-    {
-        PG_RETURN_BOOL(false);
-    }
-
-    /* get the vpc */
-    agt_arg_path = DATUM_GET_AGTYPE_P(PG_GETARG_DATUM(2));
-
-    /* if the vpc is an agtype NULL, return FALSE */
-    if (is_agtype_null(agt_arg_path))
-    {
-        PG_RETURN_BOOL(false);
-    }
-
-    /*
-     * The vpc (path) must be a binary container and the type of the object in
-     * the container must be an AGT_FBINARY_TYPE_VLE_PATH.
-     */
-    Assert(AGT_ROOT_IS_BINARY(agt_arg_path));
-    Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_path) == AGT_FBINARY_TYPE_VLE_PATH);
-
-    /* get the container */
-    vpc = (VLE_path_container *)agt_arg_path;
-
-    /* get the graphid array from the container */
-    gida = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc);
-
-    /* get the gida array size */
-    gidasize = vpc->graphid_array_size;
-
-    /* verify the minimum size is 3 or 1 */
-    Assert(gidasize >= 3 || gidasize == 1);
-
-    /*
-     * Get argument types directly instead of using extract_variadic_args.
-     * This avoids the expensive exprType/get_call_expr_argtype overhead
-     * on every call. Cache the types in fn_extra on first invocation.
-     */
-    if (fcinfo->flinfo->fn_extra == NULL)
-    {
-        Oid *cached_types = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
-                                                2 * sizeof(Oid));
-        cached_types[0] = get_fn_expr_argtype(fcinfo->flinfo, 0);
-        cached_types[1] = get_fn_expr_argtype(fcinfo->flinfo, 1);
-        fcinfo->flinfo->fn_extra = cached_types;
-    }
-    type0 = ((Oid *)fcinfo->flinfo->fn_extra)[0];
-    type1 = ((Oid *)fcinfo->flinfo->fn_extra)[1];
-
-    /* get the vsid */
-    if (type0 == AGTYPEOID)
-    {
-        agt_arg_vsid = DATUM_GET_AGTYPE_P(PG_GETARG_DATUM(0));
-
-        if (!is_agtype_null(agt_arg_vsid))
-        {
-            agtv_temp =
-               get_ith_agtype_value_from_container(&agt_arg_vsid->root, 0);
-
-            Assert(agtv_temp->type == AGTV_INTEGER);
-            vsid = agtv_temp->val.int_value;
-        }
-        else
-        {
-            PG_RETURN_BOOL(false);
-        }
-    }
-    else if (type0 == GRAPHIDOID)
-    {
-        vsid = DATUM_GET_GRAPHID(PG_GETARG_DATUM(0));
-    }
-    else
-    {
-        ereport(ERROR,
-            (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-             errmsg("match_vle_terminal_edge() argument 1 must be an agtype 
integer or a graphid")));
-    }
-
-    /* get the veid */
-    if (type1 == AGTYPEOID)
-    {
-        agt_arg_veid = DATUM_GET_AGTYPE_P(PG_GETARG_DATUM(1));
-
-        if (!is_agtype_null(agt_arg_veid))
-        {
-            agtv_temp = 
get_ith_agtype_value_from_container(&agt_arg_veid->root,
-                                                            0);
-            Assert(agtv_temp->type == AGTV_INTEGER);
-            veid = agtv_temp->val.int_value;
-        }
-        else
-        {
-            PG_RETURN_BOOL(false);
-        }
-    }
-    else if (type1 == GRAPHIDOID)
-    {
-        veid = DATUM_GET_GRAPHID(PG_GETARG_DATUM(1));
-    }
-    else
-    {
-        ereport(ERROR,
-            (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-             errmsg("match_vle_terminal_edge() argument 2 must be an agtype 
integer or a graphid")));
-    }
-
-    /* compare the path beginning or end points */
-    PG_RETURN_BOOL(gida[0] == vsid && veid == gida[gidasize - 1]);
+    ereport(ERROR,
+        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+         errmsg("age_match_vle_terminal_edge() is removed; "
+                "VLE endpoint matching is now handled by the planner via "
+                "the age_vle SRF's start_id/end_id output columns")));
+    PG_RETURN_BOOL(false);
 }
 
 /* PG helper function to build an agtype (Datum) edge for matching */
diff --git a/src/include/parser/cypher_transform_entity.h 
b/src/include/parser/cypher_transform_entity.h
index d24d4c37..d0492210 100644
--- a/src/include/parser/cypher_transform_entity.h
+++ b/src/include/parser/cypher_transform_entity.h
@@ -75,6 +75,17 @@ typedef struct
      * declared by itself or a previous clause.
      */
     bool declared_in_current_clause;
+
+    /*
+     * S5: for ENT_VLE_EDGE entities, the auto-generated alias of the
+     * RangeFunction added to the FROM clause for the age_vle SRF.
+     * Used to build ColumnRefs for the SRF's start_id/end_id output
+     * columns when emitting the terminal-edge join quals as plain
+     * graphid equality A_Exprs (replacing the per-row
+     * age_match_vle_terminal_edge function call).  NULL for non-VLE
+     * entities.
+     */
+    char *vle_alias;
     /* The parse data structure that we transformed */
     union
     {

Reply via email to