From 007ae0181a01bebbbe710757ddae1dad01f4768e Mon Sep 17 00:00:00 2001 From: John Gemignani Date: Tue, 16 Apr 2024 15:33:14 -0700 Subject: [PATCH] Add the command graph_stats and improve VLE messaging for load (#1750) Added the command graph_stats. This command will force a reload of the specified graph. This is done to generate current statistics and dump any warning messages about the graph generated during a the load. Cleaned up some messaging from the VLE load process for duplicate vertices and edges. Added regression tests. --- age--1.5.0--y.y.y.sql | 8 ++ regress/expected/age_global_graph.out | 154 +++++++++++++++++++- regress/sql/age_global_graph.sql | 34 +++++ sql/agtype_typecast.sql | 7 + src/backend/utils/adt/age_global_graph.c | 171 +++++++++++++++++++---- 5 files changed, 348 insertions(+), 26 deletions(-) diff --git a/age--1.5.0--y.y.y.sql b/age--1.5.0--y.y.y.sql index 3e05a6a01..ef5bf0a60 100644 --- a/age--1.5.0--y.y.y.sql +++ b/age--1.5.0--y.y.y.sql @@ -117,3 +117,11 @@ RETURNS NULL ON NULL INPUT PARALLEL SAFE AS 'MODULE_PATHNAME'; +-- this is a new function for graph statistics +CREATE FUNCTION ag_catalog.age_graph_stats(agtype) + RETURNS agtype + LANGUAGE c + STABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + diff --git a/regress/expected/age_global_graph.out b/regress/expected/age_global_graph.out index 73377b314..f2d9b059f 100644 --- a/regress/expected/age_global_graph.out +++ b/regress/expected/age_global_graph.out @@ -216,12 +216,164 @@ SELECT * FROM cypher('ag_graph_2', $$ MATCH (a) RETURN vertex_stats(a) $$) AS (r {"id": 1125899906842625, "label": "Person", "in_degree": 0, "out_degree": 0, "self_loops": 0} (2 rows) +-- +-- graph_stats command +-- +-- what's in the current graphs? +SELECT * FROM cypher('ag_graph_1', $$ RETURN graph_stats('ag_graph_1') $$) AS (result agtype); + result +-------------------------------------------------------------------------- + {"graph": "ag_graph_1", "num_loaded_edges": 0, "num_loaded_vertices": 3} +(1 row) + +SELECT * FROM cypher('ag_graph_1', $$ RETURN graph_stats('ag_graph_2') $$) AS (result agtype); + result +-------------------------------------------------------------------------- + {"graph": "ag_graph_2", "num_loaded_edges": 0, "num_loaded_vertices": 2} +(1 row) + +SELECT * FROM cypher('ag_graph_1', $$ RETURN graph_stats('ag_graph_3') $$) AS (result agtype); + result +-------------------------------------------------------------------------- + {"graph": "ag_graph_3", "num_loaded_edges": 0, "num_loaded_vertices": 1} +(1 row) + +-- add some edges +SELECT * FROM cypher('ag_graph_1', $$ CREATE ()-[:knows]->() $$) AS (results agtype); + results +--------- +(0 rows) + +SELECT * FROM cypher('ag_graph_1', $$ CREATE ()-[:knows]->() $$) AS (results agtype); + results +--------- +(0 rows) + +SELECT * FROM cypher('ag_graph_1', $$ CREATE ()-[:knows]->() $$) AS (results agtype); + results +--------- +(0 rows) + +SELECT * FROM cypher('ag_graph_1', $$ CREATE ()-[:knows]->() $$) AS (results agtype); + results +--------- +(0 rows) + +-- what is there now? +SELECT * FROM cypher('ag_graph_1', $$ RETURN graph_stats('ag_graph_1') $$) AS (result agtype); + result +--------------------------------------------------------------------------- + {"graph": "ag_graph_1", "num_loaded_edges": 4, "num_loaded_vertices": 11} +(1 row) + +-- 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 $$) AS (u agtype, v agtype); + u | v +--------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------- + {"id": 281474976710659, "label": "", "properties": {"id": 281474976710659, "name": "u"}}::vertex | {"id": 281474976710660, "label": "", "properties": {"id": 281474976710660, "name": "v"}}::vertex + {"id": 281474976710661, "label": "", "properties": {"id": 281474976710661, "name": "u"}}::vertex | {"id": 281474976710662, "label": "", "properties": {"id": 281474976710662, "name": "v"}}::vertex + {"id": 281474976710663, "label": "", "properties": {"id": 281474976710663, "name": "u"}}::vertex | {"id": 281474976710664, "label": "", "properties": {"id": 281474976710664, "name": "v"}}::vertex + {"id": 281474976710665, "label": "", "properties": {"id": 281474976710665, "name": "u"}}::vertex | {"id": 281474976710666, "label": "", "properties": {"id": 281474976710666, "name": "v"}}::vertex +(4 rows) + +SELECT * FROM cypher('ag_graph_1', $$ MATCH (u)-[]->(v) MERGE (v)-[:stalks]->(u) $$) AS (result agtype); + result +-------- +(0 rows) + +SELECT * FROM cypher('ag_graph_1', $$ MATCH (u)-[e]->(v) RETURN u, e, v $$) AS (u agtype, e agtype, v agtype); + u | e | v +--------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------- + {"id": 281474976710660, "label": "", "properties": {"id": 281474976710660, "name": "v"}}::vertex | {"id": 1407374883553281, "label": "stalks", "end_id": 281474976710659, "start_id": 281474976710660, "properties": {}}::edge | {"id": 281474976710659, "label": "", "properties": {"id": 281474976710659, "name": "u"}}::vertex + {"id": 281474976710659, "label": "", "properties": {"id": 281474976710659, "name": "u"}}::vertex | {"id": 1125899906842625, "label": "knows", "end_id": 281474976710660, "start_id": 281474976710659, "properties": {}}::edge | {"id": 281474976710660, "label": "", "properties": {"id": 281474976710660, "name": "v"}}::vertex + {"id": 281474976710662, "label": "", "properties": {"id": 281474976710662, "name": "v"}}::vertex | {"id": 1407374883553282, "label": "stalks", "end_id": 281474976710661, "start_id": 281474976710662, "properties": {}}::edge | {"id": 281474976710661, "label": "", "properties": {"id": 281474976710661, "name": "u"}}::vertex + {"id": 281474976710661, "label": "", "properties": {"id": 281474976710661, "name": "u"}}::vertex | {"id": 1125899906842626, "label": "knows", "end_id": 281474976710662, "start_id": 281474976710661, "properties": {}}::edge | {"id": 281474976710662, "label": "", "properties": {"id": 281474976710662, "name": "v"}}::vertex + {"id": 281474976710664, "label": "", "properties": {"id": 281474976710664, "name": "v"}}::vertex | {"id": 1407374883553283, "label": "stalks", "end_id": 281474976710663, "start_id": 281474976710664, "properties": {}}::edge | {"id": 281474976710663, "label": "", "properties": {"id": 281474976710663, "name": "u"}}::vertex + {"id": 281474976710663, "label": "", "properties": {"id": 281474976710663, "name": "u"}}::vertex | {"id": 1125899906842627, "label": "knows", "end_id": 281474976710664, "start_id": 281474976710663, "properties": {}}::edge | {"id": 281474976710664, "label": "", "properties": {"id": 281474976710664, "name": "v"}}::vertex + {"id": 281474976710666, "label": "", "properties": {"id": 281474976710666, "name": "v"}}::vertex | {"id": 1407374883553284, "label": "stalks", "end_id": 281474976710665, "start_id": 281474976710666, "properties": {}}::edge | {"id": 281474976710665, "label": "", "properties": {"id": 281474976710665, "name": "u"}}::vertex + {"id": 281474976710665, "label": "", "properties": {"id": 281474976710665, "name": "u"}}::vertex | {"id": 1125899906842628, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710665, "properties": {}}::edge | {"id": 281474976710666, "label": "", "properties": {"id": 281474976710666, "name": "v"}}::vertex +(8 rows) + +-- what is there now? +SELECT * FROM cypher('ag_graph_1', $$ RETURN graph_stats('ag_graph_1') $$) AS (result agtype); + result +--------------------------------------------------------------------------- + {"graph": "ag_graph_1", "num_loaded_edges": 8, "num_loaded_vertices": 11} +(1 row) + +-- remove some vertices +SELECT * FROM ag_graph_1._ag_label_vertex; + id | properties +-----------------+-------------------------------------- + 281474976710657 | {} + 281474976710658 | {} + 281474976710659 | {"id": 281474976710659, "name": "u"} + 281474976710660 | {"id": 281474976710660, "name": "v"} + 281474976710661 | {"id": 281474976710661, "name": "u"} + 281474976710662 | {"id": 281474976710662, "name": "v"} + 281474976710663 | {"id": 281474976710663, "name": "u"} + 281474976710664 | {"id": 281474976710664, "name": "v"} + 281474976710665 | {"id": 281474976710665, "name": "u"} + 281474976710666 | {"id": 281474976710666, "name": "v"} + 844424930131969 | {} +(11 rows) + +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; + id | properties +-----------------+-------------------------------------- + 281474976710657 | {} + 281474976710658 | {} + 281474976710659 | {"id": 281474976710659, "name": "u"} + 281474976710660 | {"id": 281474976710660, "name": "v"} + 281474976710663 | {"id": 281474976710663, "name": "u"} + 281474976710665 | {"id": 281474976710665, "name": "u"} + 281474976710666 | {"id": 281474976710666, "name": "v"} + 844424930131969 | {} +(8 rows) + +SELECT * FROM ag_graph_1._ag_label_edge; + id | start_id | end_id | properties +------------------+-----------------+-----------------+------------ + 1125899906842625 | 281474976710659 | 281474976710660 | {} + 1125899906842626 | 281474976710661 | 281474976710662 | {} + 1125899906842627 | 281474976710663 | 281474976710664 | {} + 1125899906842628 | 281474976710665 | 281474976710666 | {} + 1407374883553281 | 281474976710660 | 281474976710659 | {} + 1407374883553282 | 281474976710662 | 281474976710661 | {} + 1407374883553283 | 281474976710664 | 281474976710663 | {} + 1407374883553284 | 281474976710666 | 281474976710665 | {} +(8 rows) + +-- there should be warning messages +SELECT * FROM cypher('ag_graph_1', $$ RETURN graph_stats('ag_graph_1') $$) AS (result agtype); +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 + result +-------------------------------------------------------------------------- + {"graph": "ag_graph_1", "num_loaded_edges": 8, "num_loaded_vertices": 8} +(1 row) + --drop graphs SELECT * FROM drop_graph('ag_graph_1', true); -NOTICE: drop cascades to 3 other objects +NOTICE: drop cascades to 5 other objects DETAIL: drop cascades to table ag_graph_1._ag_label_vertex drop cascades to table ag_graph_1._ag_label_edge drop cascades to table ag_graph_1.vertex1 +drop cascades to table ag_graph_1.knows +drop cascades to table ag_graph_1.stalks NOTICE: graph "ag_graph_1" has been dropped drop_graph ------------ diff --git a/regress/sql/age_global_graph.sql b/regress/sql/age_global_graph.sql index badc0b5b2..acd95fbf0 100644 --- a/regress/sql/age_global_graph.sql +++ b/regress/sql/age_global_graph.sql @@ -90,6 +90,40 @@ SELECT * FROM cypher('ag_graph_1', $$ MATCH (n) RETURN vertex_stats(n) $$) AS (r --should return 1 vertice and 1 label SELECT * FROM cypher('ag_graph_2', $$ MATCH (a) RETURN vertex_stats(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 $$) 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 $$) 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; +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; +SELECT * FROM ag_graph_1._ag_label_edge; +-- there should be warning messages +SELECT * FROM cypher('ag_graph_1', $$ RETURN graph_stats('ag_graph_1') $$) AS (result agtype); + --drop graphs SELECT * FROM drop_graph('ag_graph_1', true); diff --git a/sql/agtype_typecast.sql b/sql/agtype_typecast.sql index 5326182fb..aa551407a 100644 --- a/sql/agtype_typecast.sql +++ b/sql/agtype_typecast.sql @@ -196,6 +196,13 @@ CREATE FUNCTION ag_catalog.age_vertex_stats(agtype, agtype) PARALLEL SAFE AS 'MODULE_PATHNAME'; +CREATE FUNCTION ag_catalog.age_graph_stats(agtype) + RETURNS agtype + LANGUAGE c + STABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + CREATE FUNCTION ag_catalog.age_delete_global_graphs(agtype) RETURNS boolean LANGUAGE c diff --git a/src/backend/utils/adt/age_global_graph.c b/src/backend/utils/adt/age_global_graph.c index 9967a78f7..d4a02242b 100644 --- a/src/backend/utils/adt/age_global_graph.c +++ b/src/backend/utils/adt/age_global_graph.c @@ -96,9 +96,9 @@ static void load_edge_hashtable(GRAPH_global_context *ggctx); static void freeze_GRAPH_global_hashtables(GRAPH_global_context *ggctx); static List *get_ag_labels_names(Snapshot snapshot, Oid graph_oid, char label_type); -static bool insert_edge(GRAPH_global_context *ggctx, graphid edge_id, - Datum edge_properties, graphid start_vertex_id, - graphid end_vertex_id, Oid edge_label_table_oid); +static bool insert_edge_entry(GRAPH_global_context *ggctx, graphid edge_id, + Datum edge_properties, graphid start_vertex_id, + graphid end_vertex_id, Oid edge_label_table_oid); static bool insert_vertex_edge(GRAPH_global_context *ggctx, graphid start_vertex_id, graphid end_vertex_id, graphid edge_id, char *edge_label_name); @@ -237,39 +237,57 @@ static List *get_ag_labels_names(Snapshot snapshot, Oid graph_oid, * Helper function to insert one edge/edge->vertex, key/value pair, in the * current GRAPH global edge hashtable. */ -static bool insert_edge(GRAPH_global_context *ggctx, graphid edge_id, - Datum edge_properties, graphid start_vertex_id, - graphid end_vertex_id, Oid edge_label_table_oid) +static bool insert_edge_entry(GRAPH_global_context *ggctx, graphid edge_id, + Datum edge_properties, graphid start_vertex_id, + graphid end_vertex_id, Oid edge_label_table_oid) { - edge_entry *value = NULL; + edge_entry *ee = NULL; bool found = false; /* search for the edge */ - value = (edge_entry *)hash_search(ggctx->edge_hashtable, (void *)&edge_id, + ee = (edge_entry *)hash_search(ggctx->edge_hashtable, (void *)&edge_id, HASH_ENTER, &found); + + /* if the hash enter returned is NULL, error out */ + if (ee == NULL) + { + elog(ERROR, "insert_edge_entry: hash table returned NULL for ee"); + } + /* * If we found the key, either we have a duplicate, or we made a mistake and * inserted it already. Either way, this isn't good so don't insert it and - * return false. Likewise, if the value returned is NULL, don't do anything, - * just return false. This way the caller can decide what to do. + * return false. */ - if (found || value == NULL) + if (found) { + ereport(WARNING, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("edge: [id: %ld, start: %ld, end: %ld, label oid: %d] %s", + edge_id, start_vertex_id, end_vertex_id, + edge_label_table_oid, "duplicate edge found"))); + + ereport(WARNING, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("previous edge: [id: %ld, start: %ld, end: %ld, label oid: %d]", + ee->edge_id, ee->start_vertex_id, ee->end_vertex_id, + ee->edge_label_table_oid))); + return false; } /* not sure if we really need to zero out the entry, as we set everything */ - MemSet(value, 0, sizeof(edge_entry)); + MemSet(ee, 0, sizeof(edge_entry)); /* * Set the edge id - this is important as this is the hash key value used * for hash function collisions. */ - value->edge_id = edge_id; - value->edge_properties = edge_properties; - value->start_vertex_id = start_vertex_id; - value->end_vertex_id = end_vertex_id; - value->edge_label_table_oid = edge_label_table_oid; + ee->edge_id = edge_id; + ee->edge_properties = edge_properties; + ee->start_vertex_id = start_vertex_id; + ee->end_vertex_id = end_vertex_id; + ee->edge_label_table_oid = edge_label_table_oid; /* increment the number of loaded edges */ ggctx->num_loaded_edges++; @@ -292,9 +310,26 @@ static bool insert_vertex_entry(GRAPH_global_context *ggctx, graphid vertex_id, ve = (vertex_entry *)hash_search(ggctx->vertex_hashtable, (void *)&vertex_id, HASH_ENTER, &found); - /* we should never have duplicates, return false */ + /* if the hash enter returned is NULL, error out */ + if (ve == NULL) + { + elog(ERROR, "insert_vertex_entry: hash table returned NULL for ve"); + } + + /* we should never have duplicates, warn and return false */ if (found) { + ereport(WARNING, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("vertex: [id: %ld, label oid: %d] %s", + vertex_id, vertex_label_table_oid, + "duplicate vertex found"))); + + ereport(WARNING, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("previous vertex: [id: %ld, label oid: %d]", + ve->vertex_id, ve->vertex_label_table_oid))); + return false; } @@ -482,10 +517,12 @@ static void load_vertex_hashtable(GRAPH_global_context *ggctx) vertex_label_table_oid, vertex_properties); - /* this insert must not fail, it means there is a duplicate */ + /* warn if there is a duplicate */ if (!inserted) { - elog(ERROR, "insert_vertex_entry: failed due to duplicate"); + ereport(WARNING, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("ignored duplicate vertex"))); } } @@ -598,14 +635,17 @@ static void load_edge_hashtable(GRAPH_global_context *ggctx) edge_properties = datumCopy(edge_properties, false, -1); /* insert edge into edge hashtable */ - inserted = insert_edge(ggctx, edge_id, edge_properties, - edge_vertex_start_id, edge_vertex_end_id, - edge_label_table_oid); + inserted = insert_edge_entry(ggctx, edge_id, edge_properties, + edge_vertex_start_id, + edge_vertex_end_id, + edge_label_table_oid); - /* this insert must not fail */ + /* warn if there is a duplicate */ if (!inserted) { - elog(ERROR, "insert_edge: failed to insert"); + ereport(WARNING, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("ignored duplicate edge"))); } /* insert the edge into the start and end vertices edge lists */ @@ -1193,3 +1233,84 @@ Datum age_vertex_stats(PG_FUNCTION_ARGS) PG_RETURN_POINTER(agtype_value_to_agtype(result.res)); } + +/* PG wrapper function for age_graph_stats */ +PG_FUNCTION_INFO_V1(age_graph_stats); + +Datum age_graph_stats(PG_FUNCTION_ARGS) +{ + GRAPH_global_context *ggctx = NULL; + agtype_value *agtv_temp = NULL; + agtype_value agtv_integer; + agtype_in_state result; + char *graph_name = NULL; + Oid graph_oid = InvalidOid; + + /* the graph name is required, but this generally isn't user supplied */ + if (PG_ARGISNULL(0)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("graph_stats: graph name cannot be NULL"))); + } + + /* get the graph name */ + agtv_temp = get_agtype_value("graph_stats", AG_GET_ARG_AGTYPE_P(0), + AGTV_STRING, true); + + graph_name = pnstrdup(agtv_temp->val.string.val, + agtv_temp->val.string.len); + + /* + * Remove any context for this graph. This is done to allow graph_stats to + * show any load issues. + */ + delete_specific_GRAPH_global_contexts(graph_name); + + /* get the graph oid */ + graph_oid = get_graph_oid(graph_name); + + /* + * Create or retrieve the GRAPH global context for this graph. This function + * will also purge off invalidated contexts. + */ + ggctx = manage_GRAPH_global_contexts(graph_name, graph_oid); + + /* free the graph name */ + pfree(graph_name); + + /* zero the state */ + memset(&result, 0, sizeof(agtype_in_state)); + + /* start the object */ + result.res = push_agtype_value(&result.parse_state, WAGT_BEGIN_OBJECT, + NULL); + /* store the graph name */ + result.res = push_agtype_value(&result.parse_state, WAGT_KEY, + string_to_agtype_value("graph")); + result.res = push_agtype_value(&result.parse_state, WAGT_VALUE, agtv_temp); + + /* set up an integer for returning values */ + agtv_temp = &agtv_integer; + agtv_temp->type = AGTV_INTEGER; + agtv_temp->val.int_value = 0; + + /* get and store num_loaded_vertices */ + agtv_temp->val.int_value = ggctx->num_loaded_vertices; + result.res = push_agtype_value(&result.parse_state, WAGT_KEY, + string_to_agtype_value("num_loaded_vertices")); + result.res = push_agtype_value(&result.parse_state, WAGT_VALUE, agtv_temp); + + /* get and store num_loaded_edges */ + agtv_temp->val.int_value = ggctx->num_loaded_edges; + result.res = push_agtype_value(&result.parse_state, WAGT_KEY, + string_to_agtype_value("num_loaded_edges")); + result.res = push_agtype_value(&result.parse_state, WAGT_VALUE, agtv_temp); + + /* close the object */ + result.res = push_agtype_value(&result.parse_state, WAGT_END_OBJECT, NULL); + + result.res->type = AGTV_OBJECT; + + PG_RETURN_POINTER(agtype_value_to_agtype(result.res)); +}