MuhammadTahaNaveed commented on code in PR #2376:
URL: https://github.com/apache/age/pull/2376#discussion_r3091327671


##########
src/backend/executor/cypher_merge.c:
##########
@@ -936,6 +937,10 @@ static void end_cypher_merge(CustomScanState *node)
     /* increment the command counter */
     CommandCounterIncrement();
 
+    /* invalidate VLE cache if merge created anything */
+    if (css->created_new_path)
+        increment_graph_version(css->graph_oid);

Review Comment:
   missing braces {}



##########
src/backend/utils/adt/age_global_graph.c:
##########
@@ -21,14 +21,24 @@
 
 #include "access/heapam.h"
 #include "catalog/namespace.h"
+#include "commands/trigger.h"
 #include "common/hashfn.h"
 #include "commands/label_commands.h"
+#include "port/atomics.h"
+#include "storage/lwlock.h"
 #include "utils/datum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/builtins.h"
 
+#if PG_VERSION_NUM >= 170000

Review Comment:
   we do not use version macros



##########
src/backend/utils/adt/age_global_graph.c:
##########
@@ -1531,3 +1676,291 @@ Datum age_graph_stats(PG_FUNCTION_ARGS)
 
     PG_RETURN_POINTER(agtype_value_to_agtype(result.res));
 }
+
+/*
+ * ============================================================================
+ * Graph Version Counter Implementation
+ *
+ * Provides per-graph monotonic version counters in shared memory for
+ * cross-backend VLE cache invalidation. Three modes are supported:
+ *
+ * DSM (PG 17+):  Uses GetNamedDSMSegment — works without shared_preload_libs
+ * SHMEM (PG <17): Uses shmem_request/startup hooks — needs shared_preload_libs
+ * SNAPSHOT:       Falls back to original snapshot-based invalidation
+ * ============================================================================
+ */
+
+#if PG_VERSION_NUM >= 170000
+/*
+ * DSM path: GetNamedDSMSegment init callback.
+ * Called once when the DSM segment is first created.
+ */
+static void age_dsm_init_callback(void *ptr)
+{
+    GraphVersionState *state = (GraphVersionState *) ptr;
+
+    LWLockInitialize(&state->lock,
+                     LWLockNewTrancheId());
+    LWLockRegisterTranche(state->lock.tranche, "age_graph_version");
+    state->num_entries = 0;
+    memset(state->entries, 0, sizeof(state->entries));
+}
+
+/*
+ * Get the shared GraphVersionState via DSM registry.
+ * The segment is created on first access and persists until server shutdown.
+ */
+static GraphVersionState *get_version_state_dsm(void)
+{
+    bool found;
+
+    return (GraphVersionState *)
+        GetNamedDSMSegment("age_graph_versions",
+                           sizeof(GraphVersionState),
+                           age_dsm_init_callback,
+                           &found);
+}
+#endif /* PG_VERSION_NUM >= 170000 */
+
+/*
+ * SHMEM path: request and startup hooks for PG < 17.
+ * These are registered in _PG_init when shared_preload_libraries is used.
+ * On PG 17+, DSM is used instead and these functions are not called.
+ */
+#if PG_VERSION_NUM < 170000
+void age_graph_version_shmem_request(void)
+{
+    RequestAddinShmemSpace(MAXALIGN(sizeof(GraphVersionState)));
+}
+
+void age_graph_version_shmem_startup(void)
+{
+    bool found;
+
+    LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
+
+    shmem_version_state =
+        (GraphVersionState *) ShmemInitStruct("AGE Graph Version State",
+                                              sizeof(GraphVersionState),
+                                              &found);
+    if (!found)
+    {
+        LWLockInitialize(&shmem_version_state->lock,
+                         LWLockNewTrancheId());
+        LWLockRegisterTranche(shmem_version_state->lock.tranche,
+                              "age_graph_version");
+        shmem_version_state->num_entries = 0;
+        memset(shmem_version_state->entries, 0,
+               sizeof(shmem_version_state->entries));
+    }
+
+    LWLockRelease(AddinShmemInitLock);
+}
+#endif /* PG_VERSION_NUM < 170000 */
+
+/*
+ * Detect which version mode to use. Called once per backend on first access.
+ * Emits a DEBUG1 log message indicating the chosen mode.
+ */
+static void detect_version_mode(void)
+{
+#if PG_VERSION_NUM >= 170000

Review Comment:
   we do not use version macros



##########
src/backend/utils/adt/age_global_graph.c:
##########
@@ -1261,9 +1328,55 @@ Oid get_vertex_entry_label_table_oid(vertex_entry *ve)
     return ve->vertex_label_table_oid;
 }
 
+/*
+ * Fetch vertex properties on demand from the heap via stored TID.
+ *
+ * Returns a datumCopy of the properties in the current memory context.
+ * The caller does not need to free the result explicitly — it will be
+ * freed when the memory context is reset (typically the SRF multi-call
+ * context for VLE, which is cleaned up when the SRF completes).
+ *
+ * If the tuple is no longer visible (e.g., concurrent mutation between
+ * cache build and fetch), the version counter should have invalidated
+ * the cache. If we get here with a stale TID, it indicates a bug in
+ * the invalidation logic.
+ */
 Datum get_vertex_entry_properties(vertex_entry *ve)
 {
-    return ve->vertex_properties;
+    Relation rel;
+    HeapTupleData tuple;
+    Buffer buffer;
+    Datum result = (Datum) 0;
+
+    rel = table_open(ve->vertex_label_table_oid, AccessShareLock);
+    tuple.t_self = ve->tid;
+
+    if (heap_fetch(rel, GetActiveSnapshot(), &tuple, &buffer, true))
+    {
+        TupleDesc tupdesc = RelationGetDescr(rel);
+        bool isnull;
+        Datum props;
+
+        /* properties is column 2 (1-indexed) */
+        props = heap_getattr(&tuple, 2, tupdesc, &isnull);
+        if (!isnull)
+            result = datumCopy(props, false, -1);

Review Comment:
   missing braces {}



##########
src/backend/utils/adt/age_global_graph.c:
##########
@@ -1531,3 +1676,291 @@ Datum age_graph_stats(PG_FUNCTION_ARGS)
 
     PG_RETURN_POINTER(agtype_value_to_agtype(result.res));
 }
+
+/*
+ * ============================================================================
+ * Graph Version Counter Implementation
+ *
+ * Provides per-graph monotonic version counters in shared memory for
+ * cross-backend VLE cache invalidation. Three modes are supported:
+ *
+ * DSM (PG 17+):  Uses GetNamedDSMSegment — works without shared_preload_libs
+ * SHMEM (PG <17): Uses shmem_request/startup hooks — needs shared_preload_libs
+ * SNAPSHOT:       Falls back to original snapshot-based invalidation
+ * ============================================================================
+ */
+
+#if PG_VERSION_NUM >= 170000
+/*
+ * DSM path: GetNamedDSMSegment init callback.
+ * Called once when the DSM segment is first created.
+ */
+static void age_dsm_init_callback(void *ptr)
+{
+    GraphVersionState *state = (GraphVersionState *) ptr;
+
+    LWLockInitialize(&state->lock,
+                     LWLockNewTrancheId());
+    LWLockRegisterTranche(state->lock.tranche, "age_graph_version");
+    state->num_entries = 0;
+    memset(state->entries, 0, sizeof(state->entries));
+}
+
+/*
+ * Get the shared GraphVersionState via DSM registry.
+ * The segment is created on first access and persists until server shutdown.
+ */
+static GraphVersionState *get_version_state_dsm(void)
+{
+    bool found;
+
+    return (GraphVersionState *)
+        GetNamedDSMSegment("age_graph_versions",
+                           sizeof(GraphVersionState),
+                           age_dsm_init_callback,
+                           &found);
+}
+#endif /* PG_VERSION_NUM >= 170000 */
+
+/*
+ * SHMEM path: request and startup hooks for PG < 17.
+ * These are registered in _PG_init when shared_preload_libraries is used.
+ * On PG 17+, DSM is used instead and these functions are not called.
+ */
+#if PG_VERSION_NUM < 170000
+void age_graph_version_shmem_request(void)
+{
+    RequestAddinShmemSpace(MAXALIGN(sizeof(GraphVersionState)));
+}
+
+void age_graph_version_shmem_startup(void)
+{
+    bool found;
+
+    LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
+
+    shmem_version_state =
+        (GraphVersionState *) ShmemInitStruct("AGE Graph Version State",
+                                              sizeof(GraphVersionState),
+                                              &found);
+    if (!found)
+    {
+        LWLockInitialize(&shmem_version_state->lock,
+                         LWLockNewTrancheId());
+        LWLockRegisterTranche(shmem_version_state->lock.tranche,
+                              "age_graph_version");
+        shmem_version_state->num_entries = 0;
+        memset(shmem_version_state->entries, 0,
+               sizeof(shmem_version_state->entries));
+    }
+
+    LWLockRelease(AddinShmemInitLock);
+}
+#endif /* PG_VERSION_NUM < 170000 */
+
+/*
+ * Detect which version mode to use. Called once per backend on first access.
+ * Emits a DEBUG1 log message indicating the chosen mode.
+ */
+static void detect_version_mode(void)
+{
+#if PG_VERSION_NUM >= 170000
+    version_mode = VERSION_MODE_DSM;
+    elog(DEBUG1, "AGE: VLE cache using DSM version counter");
+#else
+    if (shmem_version_state != NULL)
+    {
+        version_mode = VERSION_MODE_SHMEM;
+        elog(DEBUG1, "AGE: VLE cache using SHMEM version counter");
+    }
+    else
+    {
+        version_mode = VERSION_MODE_SNAPSHOT;
+        elog(DEBUG1, "AGE: VLE cache using snapshot-based invalidation "
+             "(add AGE to shared_preload_libraries for better caching)");
+    }
+#endif
+}
+
+/*
+ * Get a pointer to the GraphVersionState, regardless of mode.
+ * Returns NULL only in SNAPSHOT mode (no shared memory available).
+ */
+static GraphVersionState *get_version_state(void)
+{
+    if (version_mode == VERSION_MODE_UNKNOWN)
+        detect_version_mode();
+
+#if PG_VERSION_NUM >= 170000
+    if (version_mode == VERSION_MODE_DSM)
+        return get_version_state_dsm();
+#endif
+
+    if (version_mode == VERSION_MODE_SHMEM)
+        return shmem_version_state;
+
+    return NULL;
+}
+
+/*
+ * Get the current version counter for a graph.
+ * Returns 0 if the graph has never been tracked or if shared memory
+ * is not available. Lock-free read via pg_atomic_read_u64.
+ */
+uint64 get_graph_version(Oid graph_oid)
+{
+    GraphVersionState *state = get_version_state();
+    int i;
+
+    if (state == NULL)
+        return 0;
+
+    /* lock-free scan of the array */
+    for (i = 0; i < state->num_entries; i++)
+    {
+        if (state->entries[i].graph_oid == graph_oid)
+            return pg_atomic_read_u64(&state->entries[i].version);

Review Comment:
   missing braces



##########
src/backend/utils/adt/age_global_graph.c:
##########
@@ -1531,3 +1676,291 @@ Datum age_graph_stats(PG_FUNCTION_ARGS)
 
     PG_RETURN_POINTER(agtype_value_to_agtype(result.res));
 }
+
+/*
+ * ============================================================================
+ * Graph Version Counter Implementation
+ *
+ * Provides per-graph monotonic version counters in shared memory for
+ * cross-backend VLE cache invalidation. Three modes are supported:
+ *
+ * DSM (PG 17+):  Uses GetNamedDSMSegment — works without shared_preload_libs
+ * SHMEM (PG <17): Uses shmem_request/startup hooks — needs shared_preload_libs
+ * SNAPSHOT:       Falls back to original snapshot-based invalidation
+ * ============================================================================
+ */
+
+#if PG_VERSION_NUM >= 170000
+/*
+ * DSM path: GetNamedDSMSegment init callback.
+ * Called once when the DSM segment is first created.
+ */
+static void age_dsm_init_callback(void *ptr)
+{
+    GraphVersionState *state = (GraphVersionState *) ptr;
+
+    LWLockInitialize(&state->lock,
+                     LWLockNewTrancheId());
+    LWLockRegisterTranche(state->lock.tranche, "age_graph_version");
+    state->num_entries = 0;
+    memset(state->entries, 0, sizeof(state->entries));
+}
+
+/*
+ * Get the shared GraphVersionState via DSM registry.
+ * The segment is created on first access and persists until server shutdown.
+ */
+static GraphVersionState *get_version_state_dsm(void)
+{
+    bool found;
+
+    return (GraphVersionState *)
+        GetNamedDSMSegment("age_graph_versions",
+                           sizeof(GraphVersionState),
+                           age_dsm_init_callback,
+                           &found);
+}
+#endif /* PG_VERSION_NUM >= 170000 */
+
+/*
+ * SHMEM path: request and startup hooks for PG < 17.
+ * These are registered in _PG_init when shared_preload_libraries is used.
+ * On PG 17+, DSM is used instead and these functions are not called.
+ */
+#if PG_VERSION_NUM < 170000
+void age_graph_version_shmem_request(void)
+{
+    RequestAddinShmemSpace(MAXALIGN(sizeof(GraphVersionState)));
+}
+
+void age_graph_version_shmem_startup(void)
+{
+    bool found;
+
+    LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
+
+    shmem_version_state =
+        (GraphVersionState *) ShmemInitStruct("AGE Graph Version State",
+                                              sizeof(GraphVersionState),
+                                              &found);
+    if (!found)
+    {
+        LWLockInitialize(&shmem_version_state->lock,
+                         LWLockNewTrancheId());
+        LWLockRegisterTranche(shmem_version_state->lock.tranche,
+                              "age_graph_version");
+        shmem_version_state->num_entries = 0;
+        memset(shmem_version_state->entries, 0,
+               sizeof(shmem_version_state->entries));
+    }
+
+    LWLockRelease(AddinShmemInitLock);
+}
+#endif /* PG_VERSION_NUM < 170000 */
+
+/*
+ * Detect which version mode to use. Called once per backend on first access.
+ * Emits a DEBUG1 log message indicating the chosen mode.
+ */
+static void detect_version_mode(void)
+{
+#if PG_VERSION_NUM >= 170000
+    version_mode = VERSION_MODE_DSM;
+    elog(DEBUG1, "AGE: VLE cache using DSM version counter");
+#else
+    if (shmem_version_state != NULL)
+    {
+        version_mode = VERSION_MODE_SHMEM;
+        elog(DEBUG1, "AGE: VLE cache using SHMEM version counter");
+    }
+    else
+    {
+        version_mode = VERSION_MODE_SNAPSHOT;
+        elog(DEBUG1, "AGE: VLE cache using snapshot-based invalidation "
+             "(add AGE to shared_preload_libraries for better caching)");
+    }
+#endif
+}
+
+/*
+ * Get a pointer to the GraphVersionState, regardless of mode.
+ * Returns NULL only in SNAPSHOT mode (no shared memory available).
+ */
+static GraphVersionState *get_version_state(void)
+{
+    if (version_mode == VERSION_MODE_UNKNOWN)
+        detect_version_mode();
+
+#if PG_VERSION_NUM >= 170000
+    if (version_mode == VERSION_MODE_DSM)
+        return get_version_state_dsm();
+#endif
+
+    if (version_mode == VERSION_MODE_SHMEM)
+        return shmem_version_state;

Review Comment:
   missing braces



##########
src/backend/utils/adt/age_global_graph.c:
##########
@@ -1277,9 +1390,41 @@ Oid get_edge_entry_label_table_oid(edge_entry *ee)
     return ee->edge_label_table_oid;
 }
 
+/*
+ * Fetch edge properties on demand from the heap via stored TID.
+ * See get_vertex_entry_properties for memory and safety notes.
+ */
 Datum get_edge_entry_properties(edge_entry *ee)
 {
-    return ee->edge_properties;
+    Relation rel;
+    HeapTupleData tuple;
+    Buffer buffer;
+    Datum result = (Datum) 0;
+
+    rel = table_open(ee->edge_label_table_oid, AccessShareLock);
+    tuple.t_self = ee->tid;
+
+    if (heap_fetch(rel, GetActiveSnapshot(), &tuple, &buffer, true))
+    {
+        TupleDesc tupdesc = RelationGetDescr(rel);
+        bool isnull;
+        Datum props;
+
+        /* properties is column 4 (1-indexed) */
+        props = heap_getattr(&tuple, 4, tupdesc, &isnull);
+        if (!isnull)
+            result = datumCopy(props, false, -1);
+
+        ReleaseBuffer(buffer);
+    }
+
+    table_close(rel, AccessShareLock);
+
+    if (result == (Datum) 0)
+        elog(ERROR, "get_edge_entry_properties: stale TID - "

Review Comment:
   missing braces



##########
src/backend/utils/adt/age_global_graph.c:
##########
@@ -1531,3 +1676,291 @@ Datum age_graph_stats(PG_FUNCTION_ARGS)
 
     PG_RETURN_POINTER(agtype_value_to_agtype(result.res));
 }
+
+/*
+ * ============================================================================
+ * Graph Version Counter Implementation
+ *
+ * Provides per-graph monotonic version counters in shared memory for
+ * cross-backend VLE cache invalidation. Three modes are supported:
+ *
+ * DSM (PG 17+):  Uses GetNamedDSMSegment — works without shared_preload_libs
+ * SHMEM (PG <17): Uses shmem_request/startup hooks — needs shared_preload_libs
+ * SNAPSHOT:       Falls back to original snapshot-based invalidation
+ * ============================================================================
+ */
+
+#if PG_VERSION_NUM >= 170000
+/*
+ * DSM path: GetNamedDSMSegment init callback.
+ * Called once when the DSM segment is first created.
+ */
+static void age_dsm_init_callback(void *ptr)
+{
+    GraphVersionState *state = (GraphVersionState *) ptr;
+
+    LWLockInitialize(&state->lock,
+                     LWLockNewTrancheId());
+    LWLockRegisterTranche(state->lock.tranche, "age_graph_version");
+    state->num_entries = 0;
+    memset(state->entries, 0, sizeof(state->entries));
+}
+
+/*
+ * Get the shared GraphVersionState via DSM registry.
+ * The segment is created on first access and persists until server shutdown.
+ */
+static GraphVersionState *get_version_state_dsm(void)
+{
+    bool found;
+
+    return (GraphVersionState *)
+        GetNamedDSMSegment("age_graph_versions",
+                           sizeof(GraphVersionState),
+                           age_dsm_init_callback,
+                           &found);
+}
+#endif /* PG_VERSION_NUM >= 170000 */
+
+/*
+ * SHMEM path: request and startup hooks for PG < 17.
+ * These are registered in _PG_init when shared_preload_libraries is used.
+ * On PG 17+, DSM is used instead and these functions are not called.
+ */
+#if PG_VERSION_NUM < 170000
+void age_graph_version_shmem_request(void)
+{
+    RequestAddinShmemSpace(MAXALIGN(sizeof(GraphVersionState)));
+}
+
+void age_graph_version_shmem_startup(void)
+{
+    bool found;
+
+    LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
+
+    shmem_version_state =
+        (GraphVersionState *) ShmemInitStruct("AGE Graph Version State",
+                                              sizeof(GraphVersionState),
+                                              &found);
+    if (!found)
+    {
+        LWLockInitialize(&shmem_version_state->lock,
+                         LWLockNewTrancheId());
+        LWLockRegisterTranche(shmem_version_state->lock.tranche,
+                              "age_graph_version");
+        shmem_version_state->num_entries = 0;
+        memset(shmem_version_state->entries, 0,
+               sizeof(shmem_version_state->entries));
+    }
+
+    LWLockRelease(AddinShmemInitLock);
+}
+#endif /* PG_VERSION_NUM < 170000 */
+
+/*
+ * Detect which version mode to use. Called once per backend on first access.
+ * Emits a DEBUG1 log message indicating the chosen mode.
+ */
+static void detect_version_mode(void)
+{
+#if PG_VERSION_NUM >= 170000
+    version_mode = VERSION_MODE_DSM;
+    elog(DEBUG1, "AGE: VLE cache using DSM version counter");
+#else
+    if (shmem_version_state != NULL)
+    {
+        version_mode = VERSION_MODE_SHMEM;
+        elog(DEBUG1, "AGE: VLE cache using SHMEM version counter");
+    }
+    else
+    {
+        version_mode = VERSION_MODE_SNAPSHOT;
+        elog(DEBUG1, "AGE: VLE cache using snapshot-based invalidation "
+             "(add AGE to shared_preload_libraries for better caching)");
+    }
+#endif
+}
+
+/*
+ * Get a pointer to the GraphVersionState, regardless of mode.
+ * Returns NULL only in SNAPSHOT mode (no shared memory available).
+ */
+static GraphVersionState *get_version_state(void)
+{
+    if (version_mode == VERSION_MODE_UNKNOWN)
+        detect_version_mode();
+
+#if PG_VERSION_NUM >= 170000
+    if (version_mode == VERSION_MODE_DSM)
+        return get_version_state_dsm();
+#endif
+
+    if (version_mode == VERSION_MODE_SHMEM)
+        return shmem_version_state;
+
+    return NULL;
+}
+
+/*
+ * Get the current version counter for a graph.
+ * Returns 0 if the graph has never been tracked or if shared memory
+ * is not available. Lock-free read via pg_atomic_read_u64.
+ */
+uint64 get_graph_version(Oid graph_oid)
+{
+    GraphVersionState *state = get_version_state();
+    int i;
+
+    if (state == NULL)
+        return 0;

Review Comment:
   missing braces



##########
src/backend/utils/adt/age_global_graph.c:
##########
@@ -1531,3 +1676,291 @@ Datum age_graph_stats(PG_FUNCTION_ARGS)
 
     PG_RETURN_POINTER(agtype_value_to_agtype(result.res));
 }
+
+/*
+ * ============================================================================
+ * Graph Version Counter Implementation
+ *
+ * Provides per-graph monotonic version counters in shared memory for
+ * cross-backend VLE cache invalidation. Three modes are supported:
+ *
+ * DSM (PG 17+):  Uses GetNamedDSMSegment — works without shared_preload_libs
+ * SHMEM (PG <17): Uses shmem_request/startup hooks — needs shared_preload_libs
+ * SNAPSHOT:       Falls back to original snapshot-based invalidation
+ * ============================================================================
+ */
+
+#if PG_VERSION_NUM >= 170000
+/*
+ * DSM path: GetNamedDSMSegment init callback.
+ * Called once when the DSM segment is first created.
+ */
+static void age_dsm_init_callback(void *ptr)
+{
+    GraphVersionState *state = (GraphVersionState *) ptr;
+
+    LWLockInitialize(&state->lock,
+                     LWLockNewTrancheId());
+    LWLockRegisterTranche(state->lock.tranche, "age_graph_version");
+    state->num_entries = 0;
+    memset(state->entries, 0, sizeof(state->entries));
+}
+
+/*
+ * Get the shared GraphVersionState via DSM registry.
+ * The segment is created on first access and persists until server shutdown.
+ */
+static GraphVersionState *get_version_state_dsm(void)
+{
+    bool found;
+
+    return (GraphVersionState *)
+        GetNamedDSMSegment("age_graph_versions",
+                           sizeof(GraphVersionState),
+                           age_dsm_init_callback,
+                           &found);
+}
+#endif /* PG_VERSION_NUM >= 170000 */
+
+/*
+ * SHMEM path: request and startup hooks for PG < 17.
+ * These are registered in _PG_init when shared_preload_libraries is used.
+ * On PG 17+, DSM is used instead and these functions are not called.
+ */
+#if PG_VERSION_NUM < 170000
+void age_graph_version_shmem_request(void)
+{
+    RequestAddinShmemSpace(MAXALIGN(sizeof(GraphVersionState)));
+}
+
+void age_graph_version_shmem_startup(void)
+{
+    bool found;
+
+    LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
+
+    shmem_version_state =
+        (GraphVersionState *) ShmemInitStruct("AGE Graph Version State",
+                                              sizeof(GraphVersionState),
+                                              &found);
+    if (!found)
+    {
+        LWLockInitialize(&shmem_version_state->lock,
+                         LWLockNewTrancheId());
+        LWLockRegisterTranche(shmem_version_state->lock.tranche,
+                              "age_graph_version");
+        shmem_version_state->num_entries = 0;
+        memset(shmem_version_state->entries, 0,
+               sizeof(shmem_version_state->entries));
+    }
+
+    LWLockRelease(AddinShmemInitLock);
+}
+#endif /* PG_VERSION_NUM < 170000 */
+
+/*
+ * Detect which version mode to use. Called once per backend on first access.
+ * Emits a DEBUG1 log message indicating the chosen mode.
+ */
+static void detect_version_mode(void)
+{
+#if PG_VERSION_NUM >= 170000
+    version_mode = VERSION_MODE_DSM;
+    elog(DEBUG1, "AGE: VLE cache using DSM version counter");
+#else
+    if (shmem_version_state != NULL)
+    {
+        version_mode = VERSION_MODE_SHMEM;
+        elog(DEBUG1, "AGE: VLE cache using SHMEM version counter");
+    }
+    else
+    {
+        version_mode = VERSION_MODE_SNAPSHOT;
+        elog(DEBUG1, "AGE: VLE cache using snapshot-based invalidation "
+             "(add AGE to shared_preload_libraries for better caching)");
+    }
+#endif
+}
+
+/*
+ * Get a pointer to the GraphVersionState, regardless of mode.
+ * Returns NULL only in SNAPSHOT mode (no shared memory available).
+ */
+static GraphVersionState *get_version_state(void)
+{
+    if (version_mode == VERSION_MODE_UNKNOWN)
+        detect_version_mode();
+
+#if PG_VERSION_NUM >= 170000
+    if (version_mode == VERSION_MODE_DSM)

Review Comment:
   we do not use version macros



##########
src/backend/utils/adt/age_global_graph.c:
##########
@@ -1277,9 +1390,41 @@ Oid get_edge_entry_label_table_oid(edge_entry *ee)
     return ee->edge_label_table_oid;
 }
 
+/*
+ * Fetch edge properties on demand from the heap via stored TID.
+ * See get_vertex_entry_properties for memory and safety notes.
+ */
 Datum get_edge_entry_properties(edge_entry *ee)
 {
-    return ee->edge_properties;
+    Relation rel;
+    HeapTupleData tuple;
+    Buffer buffer;
+    Datum result = (Datum) 0;
+
+    rel = table_open(ee->edge_label_table_oid, AccessShareLock);
+    tuple.t_self = ee->tid;
+
+    if (heap_fetch(rel, GetActiveSnapshot(), &tuple, &buffer, true))
+    {
+        TupleDesc tupdesc = RelationGetDescr(rel);
+        bool isnull;
+        Datum props;
+
+        /* properties is column 4 (1-indexed) */
+        props = heap_getattr(&tuple, 4, tupdesc, &isnull);
+        if (!isnull)
+            result = datumCopy(props, false, -1);

Review Comment:
   missing braces



##########
src/backend/utils/adt/age_global_graph.c:
##########
@@ -1261,9 +1328,55 @@ Oid get_vertex_entry_label_table_oid(vertex_entry *ve)
     return ve->vertex_label_table_oid;
 }
 
+/*
+ * Fetch vertex properties on demand from the heap via stored TID.
+ *
+ * Returns a datumCopy of the properties in the current memory context.
+ * The caller does not need to free the result explicitly — it will be
+ * freed when the memory context is reset (typically the SRF multi-call
+ * context for VLE, which is cleaned up when the SRF completes).
+ *
+ * If the tuple is no longer visible (e.g., concurrent mutation between
+ * cache build and fetch), the version counter should have invalidated
+ * the cache. If we get here with a stale TID, it indicates a bug in
+ * the invalidation logic.
+ */
 Datum get_vertex_entry_properties(vertex_entry *ve)
 {
-    return ve->vertex_properties;
+    Relation rel;
+    HeapTupleData tuple;
+    Buffer buffer;
+    Datum result = (Datum) 0;
+
+    rel = table_open(ve->vertex_label_table_oid, AccessShareLock);
+    tuple.t_self = ve->tid;
+
+    if (heap_fetch(rel, GetActiveSnapshot(), &tuple, &buffer, true))
+    {
+        TupleDesc tupdesc = RelationGetDescr(rel);
+        bool isnull;
+        Datum props;
+
+        /* properties is column 2 (1-indexed) */
+        props = heap_getattr(&tuple, 2, tupdesc, &isnull);
+        if (!isnull)
+            result = datumCopy(props, false, -1);
+
+        ReleaseBuffer(buffer);
+    }
+
+    table_close(rel, AccessShareLock);
+
+    /*
+     * If heap_fetch failed, the tuple is no longer visible. This should
+     * not happen under normal operation because the version counter
+     * invalidates the cache when the graph is mutated.
+     */
+    if (result == (Datum) 0)
+        elog(ERROR, "get_vertex_entry_properties: stale TID - "

Review Comment:
   missing braces



##########
src/backend/age.c:
##########
@@ -35,6 +58,15 @@ void _PG_init(void)
     process_utility_hook_init();
     post_parse_analyze_init();
     define_config_params();
+
+#if PG_VERSION_NUM < 170000
+    /* Register shared memory hooks for graph version tracking.

Review Comment:
   we do not use version macros



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to