LOAD 'age';
SET search_path TO ag_catalog;

--
-- delete_specific_GRAPH_global_contexts
--

--
SELECT * FROM create_graph('ag_graph_1');
SELECT * FROM cypher('ag_graph_1', $$ CREATE (v:vertex1) RETURN v  $$) AS (v agtype);

SELECT * FROM create_graph('ag_graph_2');
SELECT * FROM cypher('ag_graph_2', $$ CREATE (v:vertex2) RETURN v  $$) AS (v agtype);

SELECT * FROM create_graph('ag_graph_3');
SELECT * FROM cypher('ag_graph_3', $$ CREATE (v:vertex3) RETURN v  $$) AS (v agtype);

-- load contexts using the vertex_stats command
-- Build all three graph contexts under one snapshot. The vertex_stats()
-- calls are wrapped in a single REPEATABLE READ transaction so they share
-- one snapshot; this keeps the snapshot-fallback path in is_ggctx_invalid()
-- from purging an already-built context when concurrent xid activity
-- (autovacuum, parallel installcheck, replication) advances the snapshot
-- between calls. Read Committed is insufficient: it takes a fresh snapshot
-- per statement.
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT * FROM cypher('ag_graph_3', $$ MATCH (u) RETURN vertex_stats(u) ORDER BY id(u) $$) AS (result agtype);
SELECT * FROM cypher('ag_graph_2', $$ MATCH (u) RETURN vertex_stats(u) ORDER BY id(u) $$) AS (result agtype);
SELECT * FROM cypher('ag_graph_1', $$ MATCH (u) RETURN vertex_stats(u) ORDER BY id(u) $$) AS (result agtype);
COMMIT;

--- loading undefined contexts
--- should throw exception - graph "ag_graph_4" does not exist
SELECT * FROM cypher('ag_graph_4', $$ MATCH (u) RETURN vertex_stats(u) ORDER BY id(u) $$) AS (result agtype);

--- delete with invalid parameter
---should return false
SELECT * FROM cypher('ag_graph_2', $$ RETURN delete_global_graphs('E1337') $$) AS (result agtype);

-- delete ag_graph_2's context
-- should return true
SELECT * FROM cypher('ag_graph_2', $$ RETURN delete_global_graphs('ag_graph_2') $$) AS (result agtype);

-- delete ag_graph_1's context
-- should return true(succeed) because the previous command should not delete the 1st graph's context
SELECT * FROM cypher('ag_graph_1', $$ RETURN delete_global_graphs('ag_graph_1') $$) AS (result agtype);

-- delete ag_graph_3's context
-- should return true(succeed) because the previous commands should not delete the 3rd graph's context
SELECT * FROM cypher('ag_graph_3', $$ RETURN delete_global_graphs('ag_graph_3') $$) AS (result agtype);

-- delete all graphs' context again
-- should return false (did not succeed) for all of them because they are already removed
SELECT * FROM cypher('ag_graph_2', $$ RETURN delete_global_graphs('ag_graph_2') $$) AS (result agtype);
SELECT * FROM cypher('ag_graph_1', $$ RETURN delete_global_graphs('ag_graph_1') $$) AS (result agtype);
SELECT * FROM cypher('ag_graph_3', $$ RETURN delete_global_graphs('ag_graph_3') $$) AS (result agtype);

--- delete uninitialized graph context
--- should throw exception graph "ag_graph_4" does not exist
SELECT * FROM cypher('ag_graph_4', $$ RETURN delete_global_graphs('ag_graph_4') $$) AS (result agtype);

--
-- delete_GRAPH_global_contexts
--

-- load contexts again
-- Same REPEATABLE READ wrap as the first build phase above, for the same
-- snapshot-stability reason.
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT * FROM cypher('ag_graph_3', $$ MATCH (u) RETURN vertex_stats(u) ORDER BY id(u) $$) AS (result agtype);
SELECT * FROM cypher('ag_graph_2', $$ MATCH (u) RETURN vertex_stats(u) ORDER BY id(u) $$) AS (result agtype);
SELECT * FROM cypher('ag_graph_1', $$ MATCH (u) RETURN vertex_stats(u) ORDER BY id(u) $$) AS (result agtype);
COMMIT;

-- delete all graph contexts
-- should return true
SELECT * FROM cypher('ag_graph_1', $$ RETURN delete_global_graphs(NULL) $$) AS (result agtype);

-- delete all graphs' context individually
-- should return false for all of them because already removed
SELECT * FROM cypher('ag_graph_1', $$ RETURN delete_global_graphs('ag_graph_1') $$) AS (result agtype);
SELECT * FROM cypher('ag_graph_2', $$ RETURN delete_global_graphs('ag_graph_2') $$) AS (result agtype);
SELECT * FROM cypher('ag_graph_3', $$ RETURN delete_global_graphs('ag_graph_3') $$) AS (result agtype);

--
-- age_vertex_stats
--

--checking validity of vertex_stats
--adding unlabelled vertices to ag_graph_1
SELECT * FROM cypher('ag_graph_1', $$ CREATE (n), (m) $$) as (v agtype);

--adding labelled vertice to graph 2
SELECT * FROM cypher('ag_graph_2', $$ CREATE (:Person) $$) as (v agtype);

---adding edges between nodes
SELECT * FROM cypher('ag_graph_2', $$ MATCH (a:Person), (b:Person) WHERE a.name = 'A' AND b.name = 'B' CREATE (a)-[e:RELTYPE]->(b) RETURN e ORDER BY id(e) $$) as (e agtype);

--checking if vertex stats have been updated along with the new label
--should return 3 vertices
SELECT * FROM cypher('ag_graph_1', $$ MATCH (n) RETURN vertex_stats(n) ORDER BY id(n) $$) AS (result agtype);

--should return 1 vertice and 1 label
SELECT * FROM cypher('ag_graph_2', $$ MATCH (a) RETURN vertex_stats(a) ORDER BY id(a) $$) AS (result agtype);

--
-- graph_stats command
--
-- what's in the current graphs?
SELECT * FROM cypher('ag_graph_1', $$ RETURN graph_stats('ag_graph_1') $$) AS (result agtype);
SELECT * FROM cypher('ag_graph_1', $$ RETURN graph_stats('ag_graph_2') $$) AS (result agtype);
SELECT * FROM cypher('ag_graph_1', $$ RETURN graph_stats('ag_graph_3') $$) AS (result agtype);
-- add some edges
SELECT * FROM cypher('ag_graph_1', $$ CREATE ()-[:knows]->() $$) AS (results agtype);
SELECT * FROM cypher('ag_graph_1', $$ CREATE ()-[:knows]->() $$) AS (results agtype);
SELECT * FROM cypher('ag_graph_1', $$ CREATE ()-[:knows]->() $$) AS (results agtype);
SELECT * FROM cypher('ag_graph_1', $$ CREATE ()-[:knows]->() $$) AS (results agtype);
-- what is there now?
SELECT * FROM cypher('ag_graph_1', $$ RETURN graph_stats('ag_graph_1') $$) AS (result agtype);
-- add some more
SELECT * FROM cypher('ag_graph_1', $$ MATCH (u)-[]->(v) SET u.id = id(u)
                                                        SET v.id = id(v)
                                                        SET u.name = 'u'
                                                        SET v.name = 'v'
                                      RETURN u,v ORDER BY id(u), id(v) $$) AS (u agtype, v agtype);
SELECT * FROM cypher('ag_graph_1', $$ MATCH (u)-[]->(v) MERGE (v)-[:stalks]->(u) $$) AS (result agtype);
SELECT * FROM cypher('ag_graph_1', $$ MATCH (u)-[e]->(v) RETURN u, e, v ORDER BY id(e) $$) AS (u agtype, e agtype, v agtype);
-- what is there now?
SELECT * FROM cypher('ag_graph_1', $$ RETURN graph_stats('ag_graph_1') $$) AS (result agtype);
-- remove some vertices
SELECT * FROM ag_graph_1._ag_label_vertex ORDER BY id;
DELETE FROM ag_graph_1._ag_label_vertex WHERE id::text = '281474976710661';
DELETE FROM ag_graph_1._ag_label_vertex WHERE id::text = '281474976710662';
DELETE FROM ag_graph_1._ag_label_vertex WHERE id::text = '281474976710664';
SELECT * FROM ag_graph_1._ag_label_vertex ORDER BY id;
SELECT * FROM ag_graph_1._ag_label_edge ORDER BY id;
-- The graph_stats query below will produce warnings for the dangling edges
-- created by the DELETE commands above. The warnings appear in nondeterministic
-- order because they come from iterating edge label tables (knows, stalks),
-- so we suppress them with client_min_messages. Without suppression, the
-- output would include these warnings (in some order):
--
-- WARNING:  edge: [id: 1125899906842626, start: 281474976710661, end: 281474976710662, label: knows] start and end vertices not found
-- WARNING:  ignored malformed or dangling edge
-- WARNING:  edge: [id: 1125899906842627, start: 281474976710663, end: 281474976710664, label: knows] end vertex not found
-- WARNING:  ignored malformed or dangling edge
-- WARNING:  edge: [id: 1407374883553282, start: 281474976710662, end: 281474976710661, label: stalks] start and end vertices not found
-- WARNING:  ignored malformed or dangling edge
-- WARNING:  edge: [id: 1407374883553283, start: 281474976710664, end: 281474976710663, label: stalks] start vertex not found
-- WARNING:  ignored malformed or dangling edge
--
-- The result row validates that graph_stats handled the dangling edges correctly.
SET client_min_messages = error;
SELECT * FROM cypher('ag_graph_1', $$ RETURN graph_stats('ag_graph_1') $$) AS (result agtype);
RESET client_min_messages;

--drop graphs

SELECT * FROM drop_graph('ag_graph_1', true);
SELECT * FROM drop_graph('ag_graph_2', true);
SELECT * FROM drop_graph('ag_graph_3', true);

-----------------------------------------------------------------------------------------------------------------------------
--
-- VLE cache invalidation tests
--
-- These tests verify that the graph version counter properly invalidates
-- the VLE hash table cache when the graph is mutated, and that thin
-- entry lazy property fetch returns correct data.
--

-- Setup: create a graph with a chain a->b->c->d
SELECT * FROM create_graph('vle_cache_test');

SELECT * FROM cypher('vle_cache_test', $$
  CREATE (a:Node {name: 'a'})-[:Edge]->(b:Node {name: 'b'})-[:Edge]->(c:Node {name: 'c'})-[:Edge]->(d:Node {name: 'd'})
$$) AS (v agtype);

-- VLE query: find all paths from a's neighbors (should find b, b->c, b->c->d)
SELECT * FROM cypher('vle_cache_test', $$
  MATCH (a:Node {name: 'a'})-[:Edge*1..3]->(n:Node)
  RETURN n.name
  ORDER BY n.name
$$) AS (name agtype);

-- Now add a new node e connected to d. This should invalidate the cache.
SELECT * FROM cypher('vle_cache_test', $$
  MATCH (d:Node {name: 'd'})
  CREATE (d)-[:Edge]->(:Node {name: 'e'})
$$) AS (v agtype);

-- VLE query again: should now also find e via a->b->c->d->e (4 hops won't reach,
-- but d->e is 1 hop from d, and a->b->c->d->e would be 4 hops from a).
-- Increase range to *1..4 to include e
SELECT * FROM cypher('vle_cache_test', $$
  MATCH (a:Node {name: 'a'})-[:Edge*1..4]->(n:Node)
  RETURN n.name
  ORDER BY n.name
$$) AS (name agtype);

-- Test cache invalidation on DELETE: remove node c and its edges
SELECT * FROM cypher('vle_cache_test', $$
  MATCH (c:Node {name: 'c'})
  DETACH DELETE c
$$) AS (v agtype);

-- VLE query: should only find b now (c is gone, so b->c path is broken)
SELECT * FROM cypher('vle_cache_test', $$
  MATCH (a:Node {name: 'a'})-[:Edge*1..4]->(n:Node)
  RETURN n.name
  ORDER BY n.name
$$) AS (name agtype);

-- Test cache invalidation on SET: change b's name property
SELECT * FROM cypher('vle_cache_test', $$
  MATCH (b:Node {name: 'b'})
  SET b.name = 'b_modified'
  RETURN b.name
$$) AS (name agtype);

-- VLE query: verify the updated property is returned via lazy fetch
SELECT * FROM cypher('vle_cache_test', $$
  MATCH (a:Node {name: 'a'})-[:Edge*1..4]->(n:Node)
  RETURN n.name
  ORDER BY n.name
$$) AS (name agtype);

-- Test VLE with edge properties (exercises thin entry edge property fetch)
SELECT * FROM drop_graph('vle_cache_test', true);
SELECT * FROM create_graph('vle_cache_test2');

SELECT * FROM cypher('vle_cache_test2', $$
  CREATE (a:N {name: 'a'})-[:E {weight: 1}]->(b:N {name: 'b'})-[:E {weight: 2}]->(c:N {name: 'c'})
$$) AS (v agtype);

-- VLE path output to verify edge properties are fetched correctly via
-- thin entry lazy fetch. Returning the full path forces build_path()
-- to call get_edge_entry_properties() for each edge in the result.
-- The output must contain the correct weight values (1 and 2).
SELECT * FROM cypher('vle_cache_test2', $$
  MATCH p=(a:N {name: 'a'})-[:E *1..2]->(n:N)
  RETURN p
  ORDER BY n.name
$$) AS (p agtype);

-- VLE edge properties via UNWIND + relationships() to individually verify
-- each edge's properties are correctly fetched from the heap via TID.
SELECT * FROM cypher('vle_cache_test2', $$
  MATCH p=(a:N {name: 'a'})-[:E *1..2]->(n:N)
  WITH p, n
  UNWIND relationships(p) AS e
  RETURN n.name, e.weight
  ORDER BY n.name, e.weight
$$) AS (name agtype, weight agtype);

-- Cleanup
SELECT * FROM drop_graph('vle_cache_test2', true);

-----------------------------------------------------------------------------------------------------------------------------
--
-- VLE cache invalidation via direct SQL (trigger tests)
--
-- These tests verify that the SQL trigger (age_invalidate_graph_cache)
-- properly invalidates the VLE cache when label tables are mutated
-- via direct SQL INSERT, UPDATE, DELETE, and TRUNCATE — not via Cypher.
--

-- Setup: create graph with a chain a->b->c using Cypher
SELECT * FROM create_graph('vle_trigger_test');

SELECT * FROM cypher('vle_trigger_test', $$
  CREATE (a:Node {name: 'a'})-[:Edge]->(b:Node {name: 'b'})-[:Edge]->(c:Node {name: 'c'})
$$) AS (v agtype);

-- Prime the VLE cache: find all nodes reachable from a
SELECT * FROM cypher('vle_trigger_test', $$
  MATCH (a:Node {name: 'a'})-[:Edge*1..3]->(n:Node)
  RETURN n.name
  ORDER BY n.name
$$) AS (name agtype);

-- Direct SQL INSERT on vertex: add a new vertex via SQL.
-- This should fire the trigger and invalidate the VLE cache.
-- Use _graphid() with the label's id and nextval for the entry id.
INSERT INTO vle_trigger_test."Node" (id, properties)
SELECT ag_catalog._graphid(l.id,
           nextval('vle_trigger_test."Node_id_seq"'::regclass)),
       '{"name": "d"}'::agtype
FROM ag_catalog.ag_label l
JOIN ag_catalog.ag_graph g ON g.graphid = l.graph
WHERE g.name = 'vle_trigger_test' AND l.name = 'Node';

-- VLE query: results should be unchanged (d has no edges) but cache was rebuilt
SELECT * FROM cypher('vle_trigger_test', $$
  MATCH (a:Node {name: 'a'})-[:Edge*1..3]->(n:Node)
  RETURN n.name
  ORDER BY n.name
$$) AS (name agtype);

-- Direct SQL UPDATE on vertex: change b's name to 'b_updated'
-- This should fire the trigger and invalidate the VLE cache.
UPDATE vle_trigger_test."Node"
SET properties = '{"name": "b_updated"}'::agtype
WHERE properties @> '{"name": "b"}'::agtype;

-- VLE query: verify updated property is visible (cache invalidated by trigger)
SELECT * FROM cypher('vle_trigger_test', $$
  MATCH (a:Node {name: 'a'})-[:Edge*1..3]->(n:Node)
  RETURN n.name
  ORDER BY n.name
$$) AS (name agtype);

-- Direct SQL DELETE on edge: remove the edge from b_updated to c
DELETE FROM vle_trigger_test."Edge"
WHERE end_id = (SELECT id FROM vle_trigger_test."Node"
                WHERE properties @> '{"name": "c"}'::agtype);

-- VLE query: only b_updated reachable now (edge to c is gone)
SELECT * FROM cypher('vle_trigger_test', $$
  MATCH (a:Node {name: 'a'})-[:Edge*1..3]->(n:Node)
  RETURN n.name
  ORDER BY n.name
$$) AS (name agtype);

-- Direct SQL TRUNCATE on edge table: remove all edges
TRUNCATE vle_trigger_test."Edge";

-- VLE query: no edges exist, should return no rows
SELECT * FROM cypher('vle_trigger_test', $$
  MATCH (a:Node {name: 'a'})-[:Edge*1..3]->(n:Node)
  RETURN n.name
  ORDER BY n.name
$$) AS (name agtype);

-- Cleanup
SELECT * FROM drop_graph('vle_trigger_test', true);

-----------------------------------------------------------------------------------------------------------------------------
--
-- End of tests
--

