diff --git a/regress/expected/analyze.out b/regress/expected/analyze.out index 676dab9da..84b3c71ad 100644 --- a/regress/expected/analyze.out +++ b/regress/expected/analyze.out @@ -60,7 +60,7 @@ SELECT * FROM cypher('analyze', $$ $$) AS (result agtype); ERROR: syntax error at end of input LINE 1: SELECT * FROM cypher('analyze', $$ $$) AS (result agtype); ^ --- should return false due to invalid input to age_prepare_function +-- age_prepare_cypher is a compatibility stub and must not affect cypher() SELECT * FROM age_prepare_cypher(NULL, NULL); age_prepare_cypher -------------------- @@ -79,46 +79,40 @@ SELECT * FROM age_prepare_cypher(NULL, ''); f (1 row) --- should return true but cypher should fail SELECT * FROM age_prepare_cypher('analyze', ''); age_prepare_cypher -------------------- - t + f (1 row) SELECT * FROM cypher(NULL, NULL) AS (result agtype); -ERROR: syntax error at end of input +ERROR: a name constant is expected LINE 1: SELECT * FROM cypher(NULL, NULL) AS (result agtype); - ^ --- should return true and execute cypher command + ^ SELECT * FROM age_prepare_cypher('analyze', 'MATCH (u) RETURN (u)'); age_prepare_cypher -------------------- - t + f (1 row) SELECT * FROM cypher(NULL, NULL) AS (result agtype); - result ----------------------------------------------------------------- - {"id": 281474976710657, "label": "", "properties": {}}::vertex -(1 row) - --- should error due to invalid input to cypher function +ERROR: a name constant is expected +LINE 1: SELECT * FROM cypher(NULL, NULL) AS (result agtype); + ^ SELECT * FROM cypher(NULL, NULL) AS (result agtype); ERROR: a name constant is expected LINE 1: SELECT * FROM cypher(NULL, NULL) AS (result agtype); ^ --- should return true but cypher should fail SELECT * FROM age_prepare_cypher('analyze', '$$ $$'); age_prepare_cypher -------------------- - t + f (1 row) SELECT * FROM cypher(NULL, NULL) AS (result agtype); -ERROR: unexpected character at or near "$" +ERROR: a name constant is expected LINE 1: SELECT * FROM cypher(NULL, NULL) AS (result agtype); - ^ + ^ -- should return errors SELECT * FROM cypher() AS (result agtype); ERROR: cypher function requires a minimum of 2 arguments diff --git a/regress/expected/cypher_vle.out b/regress/expected/cypher_vle.out index 6574e0608..b9d031603 100644 --- a/regress/expected/cypher_vle.out +++ b/regress/expected/cypher_vle.out @@ -280,6 +280,77 @@ SELECT * FROM cypher('cypher_vle', $$MATCH ()-[*]->(v) RETURN count(*) $$) AS (e 2922 (1 row) +\pset format unaligned +-- Empty direct age_vle finite range should not traverse +SELECT count(edges) FROM start_and_end_points, age_vle( '"cypher_vle"'::agtype, start_vertex, end_vertex, '{"id": 1111111111111111, "label": "", "end_id": 2222222222222222, "start_id": 333333333333333, "properties": {}}::edge'::agtype, '3'::agtype, '2'::agtype, '1'::agtype); +count +0 +(1 row) +-- Direct runtime nodes()/relationships() should project compact VLE paths +SELECT count(age_nodes(edges)), count(age_relationships(edges)), sum((age_length(edges))::text::int) FROM start_and_end_points, age_vle( '"cypher_vle"'::agtype, start_vertex, end_vertex, '{"id": 1111111111111111, "label": "", "end_id": 2222222222222222, "start_id": 333333333333333, "properties": {}}::edge'::agtype, '3'::agtype, '3'::agtype, '1'::agtype); +count|count|sum +2|2|6 +(1 row) +-- Optimized anonymous terminal: bound start, unused anonymous end +SELECT * FROM cypher('cypher_vle', $$MATCH (u:begin)-[e*1..2 {name: "main edge"}]->() RETURN count(*) $$) AS (e agtype); +e +2 +(1 row) +SELECT * FROM cypher('cypher_vle', $$MATCH p=(u:begin)-[e*1..2 {name: "main edge"}]->() RETURN count(p) $$) AS (e agtype); +e +2 +(1 row) +-- Optimized right-bound paths-to: anonymous start, selective anonymous end +SELECT * FROM cypher('cypher_vle', $$MATCH ()-[e*1..2 {name: "main edge"}]->(:end) RETURN count(*) $$) AS (e agtype); +e +2 +(1 row) +SELECT * FROM cypher('cypher_vle', $$MATCH p=()-[e*1..2 {name: "main edge"}]->(:end) RETURN count(p) $$) AS (e agtype); +e +2 +(1 row) +SELECT * FROM cypher('cypher_vle', $$EXPLAIN (COSTS OFF) MATCH p=()-[e*1..2 {name: "main edge"}]->(:end) RETURN count(p) $$) AS (plan agtype); +QUERY PLAN +Aggregate + -> Nested Loop + -> Seq Scan on "end" _age_default_vle_function_end_var_36 + -> Function Scan on age_vle _age_default_alias_0 +(4 rows) +SELECT * FROM cypher('cypher_vle', $$EXPLAIN (COSTS OFF) MATCH ()-[e*1..2 {name: "main edge"}]->(:end) RETURN count(*) $$) AS (plan agtype); +QUERY PLAN +Aggregate + -> Nested Loop + -> Seq Scan on "end" _age_default_vle_function_end_var_39 + -> Function Scan on age_vle _age_default_alias_0 +(4 rows) +SELECT * FROM cypher('cypher_vle', $$EXPLAIN (COSTS OFF) MATCH (:begin)-[e*1..2 {name: "main edge"}]->(:end) RETURN count(*) $$) AS (plan agtype); +QUERY PLAN +Aggregate + -> Nested Loop + -> Seq Scan on "end" _age_default_vle_function_end_var_42 + -> Nested Loop + -> Seq Scan on begin _age_default_vle_function_start_var_41 + -> Function Scan on age_vle _age_default_alias_0 +(6 rows) +-- Optimized two-bound lower-zero VLE: age_vle handles start=end zero-path semantics. +SELECT * FROM cypher('cypher_vle', $$MATCH (:begin)-[e*0..2 {name: "main edge"}]->(:end) RETURN count(*) $$) AS (e agtype); +e +0 +(1 row) +SELECT * FROM cypher('cypher_vle', $$MATCH (u:begin)-[e*0..2 {name: "main edge"}]->(u) RETURN count(*) $$) AS (e agtype); +e +1 +(1 row) +SELECT * FROM cypher('cypher_vle', $$EXPLAIN (COSTS OFF) MATCH (:begin)-[e*0..2 {name: "main edge"}]->(:end) RETURN count(*) $$) AS (plan agtype); +QUERY PLAN +Aggregate + -> Nested Loop + -> Seq Scan on "end" _age_default_vle_function_end_var_49 + -> Nested Loop + -> Seq Scan on begin _age_default_vle_function_start_var_48 + -> Function Scan on age_vle _age_default_alias_0 +(6 rows) +\pset format aligned -- Should find 2 SELECT * FROM cypher('cypher_vle', $$MATCH (u:begin)<-[e*]-(v:end) RETURN e $$) AS (e agtype); e @@ -929,6 +1000,522 @@ SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN properties(e[2]) $ (3 rows) +\pset format unaligned +SELECT * FROM cypher('access',$$ MATCH p=()-[e*2..2]->() RETURN e[1].arry[2].stats, tail(e)[0].id, relationships(p)[1].id $$) as (edge_index_stats agtype, tail_index_id agtype, relationship_index_id agtype); +edge_index_stats|tail_index_id|relationship_index_id +|| +|1|1 +{"age": 1000}|3|3 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN type(e[0]), label(e[0]) $$) as (edge_type agtype, edge_label agtype); +edge_type|edge_label +"knows"|"knows" +"knows"|"knows" +"knows"|"knows" +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN e[0] = e[0], e[0] = e[1] $$) as (same_edge agtype, different_edge agtype); +same_edge|different_edge +true|false +true|false +true|false +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[e*2..2]->() RETURN head(e) IN e, tail(e)[0] IN e, relationships(p)[1] IN relationships(p), last(e) IN e $$) as (head_in_edges agtype, tail_index_in_edges agtype, relationship_index_in_path agtype, last_in_edges agtype); +head_in_edges|tail_index_in_edges|relationship_index_in_path|last_in_edges +true|true|true|true +true|true|true|true +true|true|true|true +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN last([e[0]]) = head(e), head([tail(e)[0]]) = last(e), last([e[1]]) IN e $$) as (singleton_head_equal agtype, singleton_tail_equal agtype, singleton_in_edges agtype); +singleton_head_equal|singleton_tail_equal|singleton_in_edges +true|true|true +true|true|true +true|true|true +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN tail(reverse(e))[0] IN e, reverse(tail(e))[0] IN e, tail(tail(e))[0] IN e $$) as (tail_reverse_in_edges agtype, reverse_tail_in_edges agtype, double_tail_in_edges agtype); +tail_reverse_in_edges|reverse_tail_in_edges|double_tail_in_edges +true|true| +true|true| +true|true| +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[e*2..2]->() RETURN tail(reverse(e))[0] = e[0], reverse(tail(e))[0] = e[1], tail(tail(e))[0] = e[0], reverse(tail(e))[0] = relationships(p)[1] $$) as (nested_tail_reverse_same agtype, nested_reverse_tail_same agtype, nested_double_tail_same agtype, nested_path_relationship_same agtype); +nested_tail_reverse_same|nested_reverse_tail_same|nested_double_tail_same|nested_path_relationship_same +true|true||true +true|true||true +true|true||true +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN tail(reverse(e))[0] = reverse(tail(e))[0], tail(reverse(e))[0] = tail(reverse(e))[0], reverse(tail(e))[0] = tail(reverse(e))[0], tail(tail(e))[0] = tail(tail(e))[0] $$) as (nested_cross_same agtype, nested_tail_reverse_self agtype, nested_cross_symmetric agtype, nested_empty_self agtype); +nested_cross_same|nested_tail_reverse_self|nested_cross_symmetric|nested_empty_self +false|true|false| +false|true|false| +false|true|false| +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN relationships(p)[1].id $$) as (relationship_id agtype); +relationship_id + +1 +3 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN size(relationships(p)) $$) as (relationship_count agtype); +relationship_count +2 +2 +2 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN size(relationships(p)[1..]), size(nodes(p)[1..]), size(tail(nodes(p))[0..1]) $$) as (relationship_suffix_count agtype, node_suffix_count agtype, tail_node_prefix_count agtype); +relationship_suffix_count|node_suffix_count|tail_node_prefix_count +1|2|1 +1|2|1 +1|2|1 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN isEmpty(relationships(p)[2..]), isEmpty(nodes(p)[3..]), isEmpty(tail(nodes(p))[1..]) $$) as (relationship_suffix_empty agtype, node_suffix_empty agtype, tail_node_suffix_empty agtype); +relationship_suffix_empty|node_suffix_empty|tail_node_suffix_empty +true|true|false +true|true|false +true|true|false +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN size(tail(nodes(p))), size(reverse(nodes(p))), size(tail(relationships(p))), size(reverse(relationships(p))) $$) as (tail_node_count agtype, reverse_node_count agtype, tail_relationship_count agtype, reverse_relationship_count agtype); +tail_node_count|reverse_node_count|tail_relationship_count|reverse_relationship_count +2|3|1|2 +2|3|1|2 +2|3|1|2 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN isEmpty(tail(nodes(p))), isEmpty(reverse(nodes(p))), isEmpty(tail(relationships(p))), isEmpty(reverse(relationships(p))) $$) as (tail_nodes_empty agtype, reverse_nodes_empty agtype, tail_relationships_empty agtype, reverse_relationships_empty agtype); +tail_nodes_empty|reverse_nodes_empty|tail_relationships_empty|reverse_relationships_empty +false|false|false|false +false|false|false|false +false|false|false|false +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN head(nodes(p)).id, last(nodes(p)).id, head(relationships(p)).id, last(relationships(p)).id $$) as (first_node agtype, last_node agtype, first_relationship agtype, last_relationship agtype); +first_node|last_node|first_relationship|last_relationship +||| +||0|1 +||2|3 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(nodes(p)[1]), id(relationships(p)[1]) $$) as (node_id agtype, relationship_id agtype); +node_id|relationship_id +281474976710660|844424930131970 +281474976710663|844424930131972 +281474976710666|844424930131974 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(startNode(relationships(p)[1])), id(endNode(relationships(p)[1])) $$) as (start_node_id agtype, end_node_id agtype); +start_node_id|end_node_id +281474976710660|281474976710661 +281474976710663|281474976710664 +281474976710666|281474976710667 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(startNode(head(relationships(p)))), id(endNode(head(reverse(relationships(p))))) $$) as (head_start_id agtype, reverse_end_id agtype); +head_start_id|reverse_end_id +281474976710659|281474976710661 +281474976710662|281474976710664 +281474976710665|281474976710667 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN startNode(relationships(p)[1]), endNode(relationships(p)[1]) $$) as (start_node agtype, end_node agtype); +start_node|end_node +{"id": 281474976710660, "label": "_ag_label_vertex", "properties": {}}::vertex|{"id": 281474976710661, "label": "_ag_label_vertex", "properties": {}}::vertex +{"id": 281474976710663, "label": "_ag_label_vertex", "properties": {}}::vertex|{"id": 281474976710664, "label": "_ag_label_vertex", "properties": {}}::vertex +{"id": 281474976710666, "label": "_ag_label_vertex", "properties": {}}::vertex|{"id": 281474976710667, "label": "_ag_label_vertex", "properties": {}}::vertex +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN startNode(head(relationships(p))), endNode(head(reverse(relationships(p)))) $$) as (head_start_node agtype, reverse_end_node agtype); +head_start_node|reverse_end_node +{"id": 281474976710659, "label": "_ag_label_vertex", "properties": {}}::vertex|{"id": 281474976710661, "label": "_ag_label_vertex", "properties": {}}::vertex +{"id": 281474976710662, "label": "_ag_label_vertex", "properties": {}}::vertex|{"id": 281474976710664, "label": "_ag_label_vertex", "properties": {}}::vertex +{"id": 281474976710665, "label": "_ag_label_vertex", "properties": {}}::vertex|{"id": 281474976710667, "label": "_ag_label_vertex", "properties": {}}::vertex +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN label(startNode(relationships(p)[1])), labels(endNode(head(relationships(p)))), properties(startNode(reverse(relationships(p))[0])) $$) as (endpoint_label agtype, endpoint_labels agtype, endpoint_properties agtype); +endpoint_label|endpoint_labels|endpoint_properties +"_ag_label_vertex"|["_ag_label_vertex"]|{} +"_ag_label_vertex"|["_ag_label_vertex"]|{} +"_ag_label_vertex"|["_ag_label_vertex"]|{} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN label(nodes(p)[1]), labels(nodes(p)[1]), properties(nodes(p)[1]) $$) as (node_label agtype, node_labels agtype, node_properties agtype); +node_label|node_labels|node_properties +"_ag_label_vertex"|["_ag_label_vertex"]|{} +"_ag_label_vertex"|["_ag_label_vertex"]|{} +"_ag_label_vertex"|["_ag_label_vertex"]|{} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN label(head(nodes(p))), labels(head(tail(nodes(p)))), properties(head(reverse(nodes(p)))) $$) as (head_node_label agtype, tail_node_labels agtype, reverse_node_properties agtype); +head_node_label|tail_node_labels|reverse_node_properties +"_ag_label_vertex"|["_ag_label_vertex"]|{} +"_ag_label_vertex"|["_ag_label_vertex"]|{} +"_ag_label_vertex"|["_ag_label_vertex"]|{} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN type(relationships(p)[1]), label(relationships(p)[1]) $$) as (edge_type agtype, edge_label agtype); +edge_type|edge_label +"knows"|"knows" +"knows"|"knows" +"knows"|"knows" +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN properties(relationships(p)[1]) $$) as (edge_properties agtype); +edge_properties +{} +{"id": 1} +{"id": 3, "arry": [1, 3, {"name": "john", "stats": {"age": 1000}}]} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN type(head(relationships(p))), label(head(reverse(relationships(p)))), properties(head(tail(relationships(p)))) $$) as (head_edge_type agtype, reverse_edge_label agtype, tail_edge_properties agtype); +head_edge_type|reverse_edge_label|tail_edge_properties +"knows"|"knows"|{} +"knows"|"knows"|{"id": 1} +"knows"|"knows"|{"id": 3, "arry": [1, 3, {"name": "john", "stats": {"age": 1000}}]} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN nodes(p)[1..] $$) as (node_suffix agtype); +node_suffix +[{"id": 281474976710660, "label": "_ag_label_vertex", "properties": {}}::vertex, {"id": 281474976710661, "label": "_ag_label_vertex", "properties": {}}::vertex] +[{"id": 281474976710663, "label": "_ag_label_vertex", "properties": {}}::vertex, {"id": 281474976710664, "label": "_ag_label_vertex", "properties": {}}::vertex] +[{"id": 281474976710666, "label": "_ag_label_vertex", "properties": {}}::vertex, {"id": 281474976710667, "label": "_ag_label_vertex", "properties": {}}::vertex] +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN head(tail(nodes(p))).id, head(reverse(nodes(p))).id $$) as (tail_first_node agtype, reverse_first_node agtype); +tail_first_node|reverse_first_node +| +| +| +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(head(nodes(p))), id(head(tail(nodes(p)))), id(head(reverse(nodes(p)))) $$) as (first_node_id agtype, tail_node_id agtype, reverse_node_id agtype); +first_node_id|tail_node_id|reverse_node_id +281474976710659|281474976710660|281474976710661 +281474976710662|281474976710663|281474976710664 +281474976710665|281474976710666|281474976710667 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*0..1]->() RETURN count(last(tail(nodes(p)))), count(last(tail(relationships(p)))) $$) as (tail_last_node_count agtype, tail_last_relationship_count agtype); +tail_last_node_count|tail_last_relationship_count +7|0 +(1 row) +SELECT * FROM cypher('access',$$ MATCH p=()-[*0..1]->() RETURN count(id(last(tail(nodes(p))))), count(id(last(tail(relationships(p))))) $$) as (tail_last_node_id_count agtype, tail_last_relationship_id_count agtype); +tail_last_node_id_count|tail_last_relationship_id_count +7|0 +(1 row) +SELECT * FROM cypher('access',$$ MATCH p=()-[*0..1]->() RETURN count(label(last(tail(nodes(p))))), count(properties(last(tail(nodes(p))))), count(type(last(tail(relationships(p))))) $$) as (tail_last_node_label_count agtype, tail_last_node_properties_count agtype, tail_last_relationship_type_count agtype); +tail_last_node_label_count|tail_last_node_properties_count|tail_last_relationship_type_count +7|7|0 +(1 row) +SELECT * FROM cypher('access',$$ MATCH p=()-[*1..2]->() RETURN count(startNode(last(tail(relationships(p))))), count(endNode(last(tail(relationships(p))))), count(id(startNode(last(tail(relationships(p)))))), count(id(endNode(last(tail(relationships(p)))))) $$) as (tail_last_start_node_count agtype, tail_last_end_node_count agtype, tail_last_start_id_count agtype, tail_last_end_id_count agtype); +tail_last_start_node_count|tail_last_end_node_count|tail_last_start_id_count|tail_last_end_id_count +3|3|3|3 +(1 row) +SELECT * FROM cypher('access',$$ MATCH p=()-[*1..2]->() RETURN count(label(startNode(last(tail(relationships(p)))))), count(labels(endNode(last(tail(relationships(p)))))), count(properties(startNode(last(tail(relationships(p)))))) $$) as (tail_last_start_label_count agtype, tail_last_end_labels_count agtype, tail_last_start_properties_count agtype); +tail_last_start_label_count|tail_last_end_labels_count|tail_last_start_properties_count +3|3|3 +(1 row) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(tail(nodes(p))[0]), id(tail(relationships(p))[0]) $$) as (tail_node0_id agtype, tail_relationship0_id agtype); +tail_node0_id|tail_relationship0_id +281474976710660|844424930131970 +281474976710663|844424930131972 +281474976710666|844424930131974 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(reverse(nodes(p))[0]), id(reverse(relationships(p))[0]) $$) as (reverse_node0_id agtype, reverse_relationship0_id agtype); +reverse_node0_id|reverse_relationship0_id +281474976710661|844424930131970 +281474976710664|844424930131972 +281474976710667|844424930131974 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN label(reverse(nodes(p))[0]), properties(reverse(nodes(p))[0]), type(reverse(relationships(p))[0]), properties(reverse(relationships(p))[0]) $$) as (reverse_node0_label agtype, reverse_node0_properties agtype, reverse_relationship0_type agtype, reverse_relationship0_properties agtype); +reverse_node0_label|reverse_node0_properties|reverse_relationship0_type|reverse_relationship0_properties +"_ag_label_vertex"|{}|"knows"|{} +"_ag_label_vertex"|{}|"knows"|{"id": 1} +"_ag_label_vertex"|{}|"knows"|{"id": 3, "arry": [1, 3, {"name": "john", "stats": {"age": 1000}}]} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(startNode(reverse(relationships(p))[0])), id(endNode(reverse(relationships(p))[0])) $$) as (reverse_relationship0_start_id agtype, reverse_relationship0_end_id agtype); +reverse_relationship0_start_id|reverse_relationship0_end_id +281474976710660|281474976710661 +281474976710663|281474976710664 +281474976710666|281474976710667 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(head(reverse(nodes(p))[0..1])), id(head(reverse(relationships(p))[0..1])) $$) as (reverse_node_slice_head_id agtype, reverse_relationship_slice_head_id agtype); +reverse_node_slice_head_id|reverse_relationship_slice_head_id +281474976710661|844424930131970 +281474976710664|844424930131972 +281474976710667|844424930131974 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN type(head(reverse(relationships(p))[0..1])), properties(head(reverse(relationships(p))[0..1])) $$) as (reverse_relationship_slice_head_type agtype, reverse_relationship_slice_head_properties agtype); +reverse_relationship_slice_head_type|reverse_relationship_slice_head_properties +"knows"|{} +"knows"|{"id": 1} +"knows"|{"id": 3, "arry": [1, 3, {"name": "john", "stats": {"age": 1000}}]} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN reverse(relationships(p))[0..1], reverse(nodes(p))[0..1] $$) as (reverse_relationship_slice agtype, reverse_node_slice agtype); +reverse_relationship_slice|reverse_node_slice +[{"id": 844424930131970, "label": "knows", "end_id": 281474976710661, "start_id": 281474976710660, "properties": {}}::edge]|[{"id": 281474976710661, "label": "_ag_label_vertex", "properties": {}}::vertex] +[{"id": 844424930131972, "label": "knows", "end_id": 281474976710664, "start_id": 281474976710663, "properties": {"id": 1}}::edge]|[{"id": 281474976710664, "label": "_ag_label_vertex", "properties": {}}::vertex] +[{"id": 844424930131974, "label": "knows", "end_id": 281474976710667, "start_id": 281474976710666, "properties": {"id": 3, "arry": [1, 3, {"name": "john", "stats": {"age": 1000}}]}}::edge]|[{"id": 281474976710667, "label": "_ag_label_vertex", "properties": {}}::vertex] +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN head(tail(relationships(p))).id, head(reverse(relationships(p))).id $$) as (tail_first_relationship agtype, reverse_first_relationship agtype); +tail_first_relationship|reverse_first_relationship +| +1|1 +3|3 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(head(relationships(p))), id(head(tail(relationships(p)))), id(head(reverse(relationships(p)))) $$) as (first_relationship_id agtype, tail_relationship_id agtype, reverse_relationship_id agtype); +first_relationship_id|tail_relationship_id|reverse_relationship_id +844424930131971|844424930131970|844424930131970 +844424930131973|844424930131972|844424930131972 +844424930131975|844424930131974|844424930131974 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN head(tail(relationships(p))).id, head(reverse(relationships(p))).arry[2].stats $$) as (tail_first_relationship_id agtype, reverse_relationship_stats agtype); +tail_first_relationship_id|reverse_relationship_stats +| +1| +3|{"age": 1000} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN relationships(p)[1..] $$) as (relationship_suffix agtype); +relationship_suffix +[{"id": 844424930131970, "label": "knows", "end_id": 281474976710661, "start_id": 281474976710660, "properties": {}}::edge] +[{"id": 844424930131972, "label": "knows", "end_id": 281474976710664, "start_id": 281474976710663, "properties": {"id": 1}}::edge] +[{"id": 844424930131974, "label": "knows", "end_id": 281474976710667, "start_id": 281474976710666, "properties": {"id": 3, "arry": [1, 3, {"name": "john", "stats": {"age": 1000}}]}}::edge] +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN e[0..1] $$) as (edge_slice agtype); +edge_slice +[{"id": 844424930131971, "label": "knows", "end_id": 281474976710660, "start_id": 281474976710659, "properties": {}}::edge] +[{"id": 844424930131973, "label": "knows", "end_id": 281474976710663, "start_id": 281474976710662, "properties": {"id": 0}}::edge] +[{"id": 844424930131975, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710665, "properties": {"id": 2, "arry": [0, 1, 2, 3, {"name": "joe"}]}}::edge] +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN e[-1..] $$) as (edge_slice agtype); +edge_slice +[{"id": 844424930131970, "label": "knows", "end_id": 281474976710661, "start_id": 281474976710660, "properties": {}}::edge] +[{"id": 844424930131972, "label": "knows", "end_id": 281474976710664, "start_id": 281474976710663, "properties": {"id": 1}}::edge] +[{"id": 844424930131974, "label": "knows", "end_id": 281474976710667, "start_id": 281474976710666, "properties": {"id": 3, "arry": [1, 3, {"name": "john", "stats": {"age": 1000}}]}}::edge] +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN size(e), isEmpty(e) $$) as (edge_size agtype, is_empty agtype); +edge_size|is_empty +2|false +2|false +2|false +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(head(e)), id(last(e)), type(head(e)), id(startNode(last(e))) $$) as (head_edge_id agtype, last_edge_id agtype, head_edge_type agtype, last_edge_start_id agtype); +head_edge_id|last_edge_id|head_edge_type|last_edge_start_id +844424930131971|844424930131970|"knows"|281474976710660 +844424930131973|844424930131972|"knows"|281474976710663 +844424930131975|844424930131974|"knows"|281474976710666 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN head(e).id, last(e).id, last(e).arry[2].stats $$) as (head_edge_id agtype, last_edge_id agtype, last_edge_stats agtype); +head_edge_id|last_edge_id|last_edge_stats +|| +0|1| +2|3|{"age": 1000} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN head(e).label, last(e).start_id, last(e).end_id $$) as (head_edge_label agtype, last_edge_start_id agtype, last_edge_end_id agtype); +head_edge_label|last_edge_start_id|last_edge_end_id +|| +|| +|| +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN size(e[0..1]), size(e[-1..]), size(tail(e)[0..1]) $$) as (edge_prefix_count agtype, edge_suffix_count agtype, tail_edge_prefix_count agtype); +edge_prefix_count|edge_suffix_count|tail_edge_prefix_count +1|1|1 +1|1|1 +1|1|1 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN isEmpty(e[0..0]), isEmpty(e[0..1]), isEmpty(tail(e)[1..]) $$) as (edge_empty_prefix agtype, edge_nonempty_prefix agtype, tail_edge_suffix_empty agtype); +edge_empty_prefix|edge_nonempty_prefix|tail_edge_suffix_empty +true|false|true +true|false|true +true|false|true +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(head(e[0..1])), id(last(e[-1..])), id(head(tail(e)[0..1])) $$) as (edge_slice_head_id agtype, edge_slice_last_id agtype, tail_edge_slice_head_id agtype); +edge_slice_head_id|edge_slice_last_id|tail_edge_slice_head_id +844424930131971|844424930131970|844424930131970 +844424930131973|844424930131972|844424930131972 +844424930131975|844424930131974|844424930131974 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(head(relationships(p)[1..])), id(last(nodes(p)[..2])), id(head(tail(nodes(p))[0..1])) $$) as (relationship_slice_head_id agtype, node_slice_last_id agtype, tail_node_slice_head_id agtype); +relationship_slice_head_id|node_slice_last_id|tail_node_slice_head_id +844424930131970|281474976710660|281474976710660 +844424930131972|281474976710663|281474976710663 +844424930131974|281474976710666|281474976710666 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN type(head(e[0..1])), properties(last(e[-1..])) $$) as (edge_slice_head_type agtype, edge_slice_last_properties agtype); +edge_slice_head_type|edge_slice_last_properties +"knows"|{} +"knows"|{"id": 1} +"knows"|{"id": 3, "arry": [1, 3, {"name": "john", "stats": {"age": 1000}}]} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN label(head(nodes(p)[1..])), labels(last(tail(nodes(p))[0..1])), properties(last(nodes(p)[..2])) $$) as (node_slice_head_label agtype, tail_node_slice_last_labels agtype, node_slice_last_properties agtype); +node_slice_head_label|tail_node_slice_last_labels|node_slice_last_properties +"_ag_label_vertex"|["_ag_label_vertex"]|{} +"_ag_label_vertex"|["_ag_label_vertex"]|{} +"_ag_label_vertex"|["_ag_label_vertex"]|{} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN last(e[-1..]).arry[2].stats $$) as (edge_slice_last_stats agtype); +edge_slice_last_stats + + +{"age": 1000} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(startNode(head(e[0..1]))), id(endNode(last(e[-1..]))) $$) as (edge_slice_head_start_id agtype, edge_slice_last_end_id agtype); +edge_slice_head_start_id|edge_slice_last_end_id +281474976710659|281474976710661 +281474976710662|281474976710664 +281474976710665|281474976710667 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(startNode(head(relationships(p)[1..]))), labels(endNode(last(relationships(p)[1..]))), properties(startNode(head(relationships(p)[1..]))) $$) as (rel_slice_head_start_id agtype, rel_slice_last_end_labels agtype, rel_slice_head_start_properties agtype); +rel_slice_head_start_id|rel_slice_last_end_labels|rel_slice_head_start_properties +281474976710660|["_ag_label_vertex"]|{} +281474976710663|["_ag_label_vertex"]|{} +281474976710666|["_ag_label_vertex"]|{} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN size(tail(e)), size(reverse(e)) $$) as (tail_edge_size agtype, reverse_edge_size agtype); +tail_edge_size|reverse_edge_size +1|2 +1|2 +1|2 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN isEmpty(tail(e)), isEmpty(reverse(e)) $$) as (tail_edge_empty agtype, reverse_edge_empty agtype); +tail_edge_empty|reverse_edge_empty +false|false +false|false +false|false +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN size(tail(reverse(e))), size(reverse(tail(e))), isEmpty(tail(reverse(e))), isEmpty(reverse(tail(e))) $$) as (tail_reverse_edge_size agtype, reverse_tail_edge_size agtype, tail_reverse_edge_empty agtype, reverse_tail_edge_empty agtype); +tail_reverse_edge_size|reverse_tail_edge_size|tail_reverse_edge_empty|reverse_tail_edge_empty +1|1|false|false +1|1|false|false +1|1|false|false +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN size(tail(tail(e))), isEmpty(tail(tail(e))) $$) as (double_tail_edge_size agtype, double_tail_edge_empty agtype); +double_tail_edge_size|double_tail_edge_empty +0|true +0|true +0|true +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN tail(tail(e))[0..1] $$) as (double_tail_edge_slice agtype); +double_tail_edge_slice +[] +[] +[] +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN count(head(tail(tail(nodes(p)))[0..1])), count(head(tail(tail(relationships(p)))[0..1])) $$) as (double_tail_node_slice_count agtype, double_tail_rel_slice_count agtype); +double_tail_node_slice_count|double_tail_rel_slice_count +3|0 +(1 row) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN size(tail(tail(e))[0..1]), isEmpty(tail(tail(e))[0..1]) $$) as (double_tail_edge_slice_size agtype, double_tail_edge_slice_empty agtype); +double_tail_edge_slice_size|double_tail_edge_slice_empty +0|true +0|true +0|true +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN count(startNode(e[0])), count(endNode(e[0])), count(id(startNode(e[0]))), count(id(endNode(e[0]))) $$) as (edge_start_node_count agtype, edge_end_node_count agtype, edge_start_id_count agtype, edge_end_id_count agtype); +edge_start_node_count|edge_end_node_count|edge_start_id_count|edge_end_id_count +3|3|3|3 +(1 row) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(tail(e)[0]) $$) as (tail_edge0_id agtype); +tail_edge0_id +844424930131970 +844424930131972 +844424930131974 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(reverse(e)[0]) $$) as (reverse_edge0_id agtype); +reverse_edge0_id +844424930131970 +844424930131972 +844424930131974 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN type(reverse(e)[0]), properties(reverse(e)[0]) $$) as (reverse_edge0_type agtype, reverse_edge0_properties agtype); +reverse_edge0_type|reverse_edge0_properties +"knows"|{} +"knows"|{"id": 1} +"knows"|{"id": 3, "arry": [1, 3, {"name": "john", "stats": {"age": 1000}}]} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(head(reverse(e)[0..1])), head(reverse(e)[0..1]).arry[2].stats, id(endNode(head(reverse(e)[0..1]))) $$) as (reverse_edge_slice_head_id agtype, reverse_edge_slice_stats agtype, reverse_edge_slice_end_id agtype); +reverse_edge_slice_head_id|reverse_edge_slice_stats|reverse_edge_slice_end_id +844424930131970||281474976710661 +844424930131972||281474976710664 +844424930131974|{"age": 1000}|281474976710667 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN tail(e)[0..1], reverse(e)[0..1] $$) as (tail_edge_slice agtype, reverse_edge_slice agtype); +tail_edge_slice|reverse_edge_slice +[{"id": 844424930131970, "label": "knows", "end_id": 281474976710661, "start_id": 281474976710660, "properties": {}}::edge]|[{"id": 844424930131970, "label": "knows", "end_id": 281474976710661, "start_id": 281474976710660, "properties": {}}::edge] +[{"id": 844424930131972, "label": "knows", "end_id": 281474976710664, "start_id": 281474976710663, "properties": {"id": 1}}::edge]|[{"id": 844424930131972, "label": "knows", "end_id": 281474976710664, "start_id": 281474976710663, "properties": {"id": 1}}::edge] +[{"id": 844424930131974, "label": "knows", "end_id": 281474976710667, "start_id": 281474976710666, "properties": {"id": 3, "arry": [1, 3, {"name": "john", "stats": {"age": 1000}}]}}::edge]|[{"id": 844424930131974, "label": "knows", "end_id": 281474976710667, "start_id": 281474976710666, "properties": {"id": 3, "arry": [1, 3, {"name": "john", "stats": {"age": 1000}}]}}::edge] +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN tail(reverse(e))[0..1], reverse(tail(e))[0..1] $$) as (tail_reverse_edge_slice agtype, reverse_tail_edge_slice agtype); +tail_reverse_edge_slice|reverse_tail_edge_slice +[{"id": 844424930131971, "label": "knows", "end_id": 281474976710660, "start_id": 281474976710659, "properties": {}}::edge]|[{"id": 844424930131970, "label": "knows", "end_id": 281474976710661, "start_id": 281474976710660, "properties": {}}::edge] +[{"id": 844424930131973, "label": "knows", "end_id": 281474976710663, "start_id": 281474976710662, "properties": {"id": 0}}::edge]|[{"id": 844424930131972, "label": "knows", "end_id": 281474976710664, "start_id": 281474976710663, "properties": {"id": 1}}::edge] +[{"id": 844424930131975, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710665, "properties": {"id": 2, "arry": [0, 1, 2, 3, {"name": "joe"}]}}::edge]|[{"id": 844424930131974, "label": "knows", "end_id": 281474976710667, "start_id": 281474976710666, "properties": {"id": 3, "arry": [1, 3, {"name": "john", "stats": {"age": 1000}}]}}::edge] +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN count(head(tail(reverse(nodes(p)))[0..1])), count(head(reverse(tail(nodes(p)))[0..1])), count(head(tail(reverse(relationships(p)))[0..1])), count(head(reverse(tail(relationships(p)))[0..1])) $$) as (tail_reverse_node_slice_count agtype, reverse_tail_node_slice_count agtype, tail_reverse_rel_slice_count agtype, reverse_tail_rel_slice_count agtype); +tail_reverse_node_slice_count|reverse_tail_node_slice_count|tail_reverse_rel_slice_count|reverse_tail_rel_slice_count +3|3|3|3 +(1 row) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN size(tail(reverse(e))[0..1]), size(reverse(tail(e))[0..1]), isEmpty(tail(reverse(e))[0..1]), isEmpty(reverse(tail(e))[0..1]) $$) as (tail_reverse_edge_slice_size agtype, reverse_tail_edge_slice_size agtype, tail_reverse_edge_slice_empty agtype, reverse_tail_edge_slice_empty agtype); +tail_reverse_edge_slice_size|reverse_tail_edge_slice_size|tail_reverse_edge_slice_empty|reverse_tail_edge_slice_empty +1|1|false|false +1|1|false|false +1|1|false|false +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(head(tail(reverse(e))[0..1])), id(last(reverse(tail(e))[0..1])), id(head(tail(tail(e))[0..1])) $$) as (tail_reverse_slice_head_id agtype, reverse_tail_slice_last_id agtype, double_tail_slice_head_id agtype); +tail_reverse_slice_head_id|reverse_tail_slice_last_id|double_tail_slice_head_id +844424930131971|844424930131970| +844424930131973|844424930131972| +844424930131975|844424930131974| +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(last(tail(reverse(e)))), id(head(reverse(tail(e)))), last(tail(tail(e))).id $$) as (tail_reverse_last_id agtype, reverse_tail_head_id agtype, double_tail_last_id agtype); +tail_reverse_last_id|reverse_tail_head_id|double_tail_last_id +844424930131971|844424930131970| +844424930131973|844424930131972| +844424930131975|844424930131974| +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN type(last(tail(reverse(e)))), properties(head(reverse(tail(e)))), head(reverse(tail(e))).arry[2].stats $$) as (tail_reverse_last_type agtype, reverse_tail_head_properties agtype, reverse_tail_head_stats agtype); +tail_reverse_last_type|reverse_tail_head_properties|reverse_tail_head_stats +"knows"|{}| +"knows"|{"id": 1}| +"knows"|{"id": 3, "arry": [1, 3, {"name": "john", "stats": {"age": 1000}}]}|{"age": 1000} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(startNode(last(tail(reverse(e))))), id(endNode(head(reverse(tail(e))))), labels(startNode(head(reverse(tail(e))))), properties(endNode(last(tail(reverse(e))))) $$) as (tail_reverse_last_start_id agtype, reverse_tail_head_end_id agtype, reverse_tail_head_start_labels agtype, tail_reverse_last_end_properties agtype); +tail_reverse_last_start_id|reverse_tail_head_end_id|reverse_tail_head_start_labels|tail_reverse_last_end_properties +281474976710659|281474976710661|["_ag_label_vertex"]|{} +281474976710662|281474976710664|["_ag_label_vertex"]|{} +281474976710665|281474976710667|["_ag_label_vertex"]|{} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(tail(reverse(e))[0]), id(reverse(tail(e))[0]), id(tail(tail(e))[0]) $$) as (tail_reverse_index_id agtype, reverse_tail_index_id agtype, double_tail_index_id agtype); +tail_reverse_index_id|reverse_tail_index_id|double_tail_index_id +844424930131971|844424930131970| +844424930131973|844424930131972| +844424930131975|844424930131974| +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN type(tail(reverse(e))[0]), properties(reverse(tail(e))[0]), properties(tail(tail(e))[0]) $$) as (tail_reverse_index_type agtype, reverse_tail_index_properties agtype, double_tail_index_properties agtype); +tail_reverse_index_type|reverse_tail_index_properties|double_tail_index_properties +"knows"|{}| +"knows"|{"id": 1}| +"knows"|{"id": 3, "arry": [1, 3, {"name": "john", "stats": {"age": 1000}}]}| +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(startNode(tail(reverse(e))[0])), id(endNode(reverse(tail(e))[0])), labels(startNode(tail(tail(e))[0])) $$) as (tail_reverse_start_id agtype, reverse_tail_end_id agtype, double_tail_start_labels agtype); +tail_reverse_start_id|reverse_tail_end_id|double_tail_start_labels +281474976710659|281474976710661| +281474976710662|281474976710664| +281474976710665|281474976710667| +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN reverse(tail(e))[0].arry[2].stats, tail(reverse(e))[0].id, tail(tail(e))[0].id $$) as (reverse_tail_index_stats agtype, tail_reverse_index_property_id agtype, double_tail_index_property_id agtype); +reverse_tail_index_stats|tail_reverse_index_property_id|double_tail_index_property_id +|| +|0| +{"age": 1000}|2| +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*1..2]->() RETURN count(last(tail(e))) $$) as (tail_last_edge_count agtype); +tail_last_edge_count +3 +(1 row) +SELECT * FROM cypher('access',$$ MATCH ()-[e*1..2]->() RETURN count(id(last(tail(e)))) $$) as (tail_last_edge_id_count agtype); +tail_last_edge_id_count +3 +(1 row) +SELECT * FROM cypher('access',$$ MATCH ()-[e*1..2]->() RETURN count(type(last(tail(e)))), count(properties(last(tail(e)))), count(last(tail(e)).arry[2].stats) $$) as (tail_last_edge_type_count agtype, tail_last_edge_properties_count agtype, tail_last_edge_property_count agtype); +tail_last_edge_type_count|tail_last_edge_properties_count|tail_last_edge_property_count +3|3|1 +(1 row) +SELECT * FROM cypher('access',$$ MATCH ()-[e*1..2]->() RETURN count(startNode(last(tail(e)))), count(endNode(last(tail(e)))), count(id(startNode(last(tail(e))))), count(id(endNode(last(tail(e))))) $$) as (tail_last_edge_start_node_count agtype, tail_last_edge_end_node_count agtype, tail_last_edge_start_id_count agtype, tail_last_edge_end_id_count agtype); +tail_last_edge_start_node_count|tail_last_edge_end_node_count|tail_last_edge_start_id_count|tail_last_edge_end_id_count +3|3|3|3 +(1 row) +SELECT * FROM cypher('access',$$ MATCH ()-[e*1..2]->() RETURN count(label(startNode(last(tail(e))))), count(labels(endNode(last(tail(e))))), count(properties(endNode(last(tail(e))))) $$) as (tail_last_edge_start_label_count agtype, tail_last_edge_end_labels_count agtype, tail_last_edge_end_properties_count agtype); +tail_last_edge_start_label_count|tail_last_edge_end_labels_count|tail_last_edge_end_properties_count +3|3|3 +(1 row) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN tail(e) $$) as (edge_tail agtype); +edge_tail +[{"id": 844424930131970, "label": "knows", "end_id": 281474976710661, "start_id": 281474976710660, "properties": {}}::edge] +[{"id": 844424930131972, "label": "knows", "end_id": 281474976710664, "start_id": 281474976710663, "properties": {"id": 1}}::edge] +[{"id": 844424930131974, "label": "knows", "end_id": 281474976710667, "start_id": 281474976710666, "properties": {"id": 3, "arry": [1, 3, {"name": "john", "stats": {"age": 1000}}]}}::edge] +(3 rows) +\pset format aligned SELECT * FROM cypher('access',$$ MATCH ()-[e*]->() RETURN properties(e[0]), properties(e[1]) $$) as (prop_1st agtype, prop_2nd agtype); prop_1st | prop_2nd ---------------------------------------------------------------------+--------------------------------------------------------------------- @@ -1098,6 +1685,32 @@ SELECT * FROM cypher('issue_1910', $$ MATCH (n) WHERE EXISTS((n)-[*2..2]-({name: "Jane Doe" (1 row) +\pset format unaligned +SELECT * FROM cypher('issue_1910', $$ MATCH p=()-[*1..1]->() RETURN count(startNode(relationships(p)[0]).name) + count(endNode(relationships(p)[0]).name) $$) AS (c agtype); +c +6 +(1 row) +SELECT * FROM cypher('issue_1910', $$ MATCH ()-[e*1..1]->() RETURN count(startNode(e[0]).name) + count(endNode(e[0]).name) $$) AS (c agtype); +c +6 +(1 row) +SELECT * FROM cypher('issue_1910', $$ MATCH ()-[e*1..2]->() RETURN count(startNode(last(tail(e))).name) + count(endNode(last(tail(e))).name) $$) AS (c agtype); +c +2 +(1 row) +SELECT * FROM cypher('issue_1910', $$ MATCH ()-[e*1..1]->() RETURN count(startNode(head(e[0..1])).name) + count(endNode(last(e[0..])).name) $$) AS (c agtype); +c +6 +(1 row) +SELECT * FROM cypher('issue_1910', $$ MATCH ()-[e*1..2]->() RETURN count(startNode(tail(reverse(e))[0]).name) + count(endNode(reverse(tail(e))[0]).name) $$) AS (c agtype); +c +2 +(1 row) +SELECT * FROM cypher('issue_1910', $$ MATCH ()-[e*1..2]->() RETURN count(startNode(last(tail(reverse(e)))).name) + count(endNode(head(reverse(tail(e)))).name) $$) AS (c agtype); +c +2 +(1 row) +\pset format aligned SELECT drop_graph('issue_1910', true); NOTICE: drop cascades to 3 other objects DETAIL: drop cascades to table issue_1910._ag_label_vertex diff --git a/regress/sql/analyze.sql b/regress/sql/analyze.sql index 1da70b239..f0bc6d9dc 100644 --- a/regress/sql/analyze.sql +++ b/regress/sql/analyze.sql @@ -36,19 +36,15 @@ SELECT * FROM cypher('analyze', '') AS (result agtype); -- should error due to bad cypher statement SELECT * FROM cypher('analyze', $$ $$) AS (result agtype); --- should return false due to invalid input to age_prepare_function +-- age_prepare_cypher is a compatibility stub and must not affect cypher() SELECT * FROM age_prepare_cypher(NULL, NULL); SELECT * FROM age_prepare_cypher('analyze', NULL); SELECT * FROM age_prepare_cypher(NULL, ''); --- should return true but cypher should fail SELECT * FROM age_prepare_cypher('analyze', ''); SELECT * FROM cypher(NULL, NULL) AS (result agtype); --- should return true and execute cypher command SELECT * FROM age_prepare_cypher('analyze', 'MATCH (u) RETURN (u)'); SELECT * FROM cypher(NULL, NULL) AS (result agtype); --- should error due to invalid input to cypher function SELECT * FROM cypher(NULL, NULL) AS (result agtype); --- should return true but cypher should fail SELECT * FROM age_prepare_cypher('analyze', '$$ $$'); SELECT * FROM cypher(NULL, NULL) AS (result agtype); diff --git a/regress/sql/cypher_vle.sql b/regress/sql/cypher_vle.sql index c960aa7a4..483ff2143 100644 --- a/regress/sql/cypher_vle.sql +++ b/regress/sql/cypher_vle.sql @@ -104,6 +104,25 @@ SELECT * FROM cypher('cypher_vle', $$MATCH ()<-[*4..4 {name: "main edge"}]-(v) R SELECT * FROM cypher('cypher_vle', $$MATCH ()-[*]->() RETURN count(*) $$) AS (e agtype); SELECT * FROM cypher('cypher_vle', $$MATCH (u)-[*]->() RETURN count(*) $$) AS (e agtype); SELECT * FROM cypher('cypher_vle', $$MATCH ()-[*]->(v) RETURN count(*) $$) AS (e agtype); +\pset format unaligned +-- Empty direct age_vle finite range should not traverse +SELECT count(edges) FROM start_and_end_points, age_vle( '"cypher_vle"'::agtype, start_vertex, end_vertex, '{"id": 1111111111111111, "label": "", "end_id": 2222222222222222, "start_id": 333333333333333, "properties": {}}::edge'::agtype, '3'::agtype, '2'::agtype, '1'::agtype); +-- Direct runtime nodes()/relationships() should project compact VLE paths +SELECT count(age_nodes(edges)), count(age_relationships(edges)), sum((age_length(edges))::text::int) FROM start_and_end_points, age_vle( '"cypher_vle"'::agtype, start_vertex, end_vertex, '{"id": 1111111111111111, "label": "", "end_id": 2222222222222222, "start_id": 333333333333333, "properties": {}}::edge'::agtype, '3'::agtype, '3'::agtype, '1'::agtype); +-- Optimized anonymous terminal: bound start, unused anonymous end +SELECT * FROM cypher('cypher_vle', $$MATCH (u:begin)-[e*1..2 {name: "main edge"}]->() RETURN count(*) $$) AS (e agtype); +SELECT * FROM cypher('cypher_vle', $$MATCH p=(u:begin)-[e*1..2 {name: "main edge"}]->() RETURN count(p) $$) AS (e agtype); +-- Optimized right-bound paths-to: anonymous start, selective anonymous end +SELECT * FROM cypher('cypher_vle', $$MATCH ()-[e*1..2 {name: "main edge"}]->(:end) RETURN count(*) $$) AS (e agtype); +SELECT * FROM cypher('cypher_vle', $$MATCH p=()-[e*1..2 {name: "main edge"}]->(:end) RETURN count(p) $$) AS (e agtype); +SELECT * FROM cypher('cypher_vle', $$EXPLAIN (COSTS OFF) MATCH p=()-[e*1..2 {name: "main edge"}]->(:end) RETURN count(p) $$) AS (plan agtype); +SELECT * FROM cypher('cypher_vle', $$EXPLAIN (COSTS OFF) MATCH ()-[e*1..2 {name: "main edge"}]->(:end) RETURN count(*) $$) AS (plan agtype); +SELECT * FROM cypher('cypher_vle', $$EXPLAIN (COSTS OFF) MATCH (:begin)-[e*1..2 {name: "main edge"}]->(:end) RETURN count(*) $$) AS (plan agtype); +-- Optimized two-bound lower-zero VLE: age_vle handles start=end zero-path semantics. +SELECT * FROM cypher('cypher_vle', $$MATCH (:begin)-[e*0..2 {name: "main edge"}]->(:end) RETURN count(*) $$) AS (e agtype); +SELECT * FROM cypher('cypher_vle', $$MATCH (u:begin)-[e*0..2 {name: "main edge"}]->(u) RETURN count(*) $$) AS (e agtype); +SELECT * FROM cypher('cypher_vle', $$EXPLAIN (COSTS OFF) MATCH (:begin)-[e*0..2 {name: "main edge"}]->(:end) RETURN count(*) $$) AS (plan agtype); +\pset format aligned -- Should find 2 SELECT * FROM cypher('cypher_vle', $$MATCH (u:begin)<-[e*]-(v:end) RETURN e $$) AS (e agtype); -- Should find 5 @@ -322,6 +341,98 @@ SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN e[1].id $$) as (re SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN e[1].arry[2] $$) as (results agtype); SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN e[1].arry[2].stats $$) as (results agtype); SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN properties(e[2]) $$) as (prop_third_edge agtype); +\pset format unaligned +SELECT * FROM cypher('access',$$ MATCH p=()-[e*2..2]->() RETURN e[1].arry[2].stats, tail(e)[0].id, relationships(p)[1].id $$) as (edge_index_stats agtype, tail_index_id agtype, relationship_index_id agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN type(e[0]), label(e[0]) $$) as (edge_type agtype, edge_label agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN e[0] = e[0], e[0] = e[1] $$) as (same_edge agtype, different_edge agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[e*2..2]->() RETURN head(e) IN e, tail(e)[0] IN e, relationships(p)[1] IN relationships(p), last(e) IN e $$) as (head_in_edges agtype, tail_index_in_edges agtype, relationship_index_in_path agtype, last_in_edges agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN last([e[0]]) = head(e), head([tail(e)[0]]) = last(e), last([e[1]]) IN e $$) as (singleton_head_equal agtype, singleton_tail_equal agtype, singleton_in_edges agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN tail(reverse(e))[0] IN e, reverse(tail(e))[0] IN e, tail(tail(e))[0] IN e $$) as (tail_reverse_in_edges agtype, reverse_tail_in_edges agtype, double_tail_in_edges agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[e*2..2]->() RETURN tail(reverse(e))[0] = e[0], reverse(tail(e))[0] = e[1], tail(tail(e))[0] = e[0], reverse(tail(e))[0] = relationships(p)[1] $$) as (nested_tail_reverse_same agtype, nested_reverse_tail_same agtype, nested_double_tail_same agtype, nested_path_relationship_same agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN tail(reverse(e))[0] = reverse(tail(e))[0], tail(reverse(e))[0] = tail(reverse(e))[0], reverse(tail(e))[0] = tail(reverse(e))[0], tail(tail(e))[0] = tail(tail(e))[0] $$) as (nested_cross_same agtype, nested_tail_reverse_self agtype, nested_cross_symmetric agtype, nested_empty_self agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN relationships(p)[1].id $$) as (relationship_id agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN size(relationships(p)) $$) as (relationship_count agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN size(relationships(p)[1..]), size(nodes(p)[1..]), size(tail(nodes(p))[0..1]) $$) as (relationship_suffix_count agtype, node_suffix_count agtype, tail_node_prefix_count agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN isEmpty(relationships(p)[2..]), isEmpty(nodes(p)[3..]), isEmpty(tail(nodes(p))[1..]) $$) as (relationship_suffix_empty agtype, node_suffix_empty agtype, tail_node_suffix_empty agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN size(tail(nodes(p))), size(reverse(nodes(p))), size(tail(relationships(p))), size(reverse(relationships(p))) $$) as (tail_node_count agtype, reverse_node_count agtype, tail_relationship_count agtype, reverse_relationship_count agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN isEmpty(tail(nodes(p))), isEmpty(reverse(nodes(p))), isEmpty(tail(relationships(p))), isEmpty(reverse(relationships(p))) $$) as (tail_nodes_empty agtype, reverse_nodes_empty agtype, tail_relationships_empty agtype, reverse_relationships_empty agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN head(nodes(p)).id, last(nodes(p)).id, head(relationships(p)).id, last(relationships(p)).id $$) as (first_node agtype, last_node agtype, first_relationship agtype, last_relationship agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(nodes(p)[1]), id(relationships(p)[1]) $$) as (node_id agtype, relationship_id agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(startNode(relationships(p)[1])), id(endNode(relationships(p)[1])) $$) as (start_node_id agtype, end_node_id agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(startNode(head(relationships(p)))), id(endNode(head(reverse(relationships(p))))) $$) as (head_start_id agtype, reverse_end_id agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN startNode(relationships(p)[1]), endNode(relationships(p)[1]) $$) as (start_node agtype, end_node agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN startNode(head(relationships(p))), endNode(head(reverse(relationships(p)))) $$) as (head_start_node agtype, reverse_end_node agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN label(startNode(relationships(p)[1])), labels(endNode(head(relationships(p)))), properties(startNode(reverse(relationships(p))[0])) $$) as (endpoint_label agtype, endpoint_labels agtype, endpoint_properties agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN label(nodes(p)[1]), labels(nodes(p)[1]), properties(nodes(p)[1]) $$) as (node_label agtype, node_labels agtype, node_properties agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN label(head(nodes(p))), labels(head(tail(nodes(p)))), properties(head(reverse(nodes(p)))) $$) as (head_node_label agtype, tail_node_labels agtype, reverse_node_properties agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN type(relationships(p)[1]), label(relationships(p)[1]) $$) as (edge_type agtype, edge_label agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN properties(relationships(p)[1]) $$) as (edge_properties agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN type(head(relationships(p))), label(head(reverse(relationships(p)))), properties(head(tail(relationships(p)))) $$) as (head_edge_type agtype, reverse_edge_label agtype, tail_edge_properties agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN nodes(p)[1..] $$) as (node_suffix agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN head(tail(nodes(p))).id, head(reverse(nodes(p))).id $$) as (tail_first_node agtype, reverse_first_node agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(head(nodes(p))), id(head(tail(nodes(p)))), id(head(reverse(nodes(p)))) $$) as (first_node_id agtype, tail_node_id agtype, reverse_node_id agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*0..1]->() RETURN count(last(tail(nodes(p)))), count(last(tail(relationships(p)))) $$) as (tail_last_node_count agtype, tail_last_relationship_count agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*0..1]->() RETURN count(id(last(tail(nodes(p))))), count(id(last(tail(relationships(p))))) $$) as (tail_last_node_id_count agtype, tail_last_relationship_id_count agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*0..1]->() RETURN count(label(last(tail(nodes(p))))), count(properties(last(tail(nodes(p))))), count(type(last(tail(relationships(p))))) $$) as (tail_last_node_label_count agtype, tail_last_node_properties_count agtype, tail_last_relationship_type_count agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*1..2]->() RETURN count(startNode(last(tail(relationships(p))))), count(endNode(last(tail(relationships(p))))), count(id(startNode(last(tail(relationships(p)))))), count(id(endNode(last(tail(relationships(p)))))) $$) as (tail_last_start_node_count agtype, tail_last_end_node_count agtype, tail_last_start_id_count agtype, tail_last_end_id_count agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*1..2]->() RETURN count(label(startNode(last(tail(relationships(p)))))), count(labels(endNode(last(tail(relationships(p)))))), count(properties(startNode(last(tail(relationships(p)))))) $$) as (tail_last_start_label_count agtype, tail_last_end_labels_count agtype, tail_last_start_properties_count agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(tail(nodes(p))[0]), id(tail(relationships(p))[0]) $$) as (tail_node0_id agtype, tail_relationship0_id agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(reverse(nodes(p))[0]), id(reverse(relationships(p))[0]) $$) as (reverse_node0_id agtype, reverse_relationship0_id agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN label(reverse(nodes(p))[0]), properties(reverse(nodes(p))[0]), type(reverse(relationships(p))[0]), properties(reverse(relationships(p))[0]) $$) as (reverse_node0_label agtype, reverse_node0_properties agtype, reverse_relationship0_type agtype, reverse_relationship0_properties agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(startNode(reverse(relationships(p))[0])), id(endNode(reverse(relationships(p))[0])) $$) as (reverse_relationship0_start_id agtype, reverse_relationship0_end_id agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(head(reverse(nodes(p))[0..1])), id(head(reverse(relationships(p))[0..1])) $$) as (reverse_node_slice_head_id agtype, reverse_relationship_slice_head_id agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN type(head(reverse(relationships(p))[0..1])), properties(head(reverse(relationships(p))[0..1])) $$) as (reverse_relationship_slice_head_type agtype, reverse_relationship_slice_head_properties agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN reverse(relationships(p))[0..1], reverse(nodes(p))[0..1] $$) as (reverse_relationship_slice agtype, reverse_node_slice agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN head(tail(relationships(p))).id, head(reverse(relationships(p))).id $$) as (tail_first_relationship agtype, reverse_first_relationship agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(head(relationships(p))), id(head(tail(relationships(p)))), id(head(reverse(relationships(p)))) $$) as (first_relationship_id agtype, tail_relationship_id agtype, reverse_relationship_id agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN head(tail(relationships(p))).id, head(reverse(relationships(p))).arry[2].stats $$) as (tail_first_relationship_id agtype, reverse_relationship_stats agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN relationships(p)[1..] $$) as (relationship_suffix agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN e[0..1] $$) as (edge_slice agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN e[-1..] $$) as (edge_slice agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN size(e), isEmpty(e) $$) as (edge_size agtype, is_empty agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(head(e)), id(last(e)), type(head(e)), id(startNode(last(e))) $$) as (head_edge_id agtype, last_edge_id agtype, head_edge_type agtype, last_edge_start_id agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN head(e).id, last(e).id, last(e).arry[2].stats $$) as (head_edge_id agtype, last_edge_id agtype, last_edge_stats agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN head(e).label, last(e).start_id, last(e).end_id $$) as (head_edge_label agtype, last_edge_start_id agtype, last_edge_end_id agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN size(e[0..1]), size(e[-1..]), size(tail(e)[0..1]) $$) as (edge_prefix_count agtype, edge_suffix_count agtype, tail_edge_prefix_count agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN isEmpty(e[0..0]), isEmpty(e[0..1]), isEmpty(tail(e)[1..]) $$) as (edge_empty_prefix agtype, edge_nonempty_prefix agtype, tail_edge_suffix_empty agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(head(e[0..1])), id(last(e[-1..])), id(head(tail(e)[0..1])) $$) as (edge_slice_head_id agtype, edge_slice_last_id agtype, tail_edge_slice_head_id agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(head(relationships(p)[1..])), id(last(nodes(p)[..2])), id(head(tail(nodes(p))[0..1])) $$) as (relationship_slice_head_id agtype, node_slice_last_id agtype, tail_node_slice_head_id agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN type(head(e[0..1])), properties(last(e[-1..])) $$) as (edge_slice_head_type agtype, edge_slice_last_properties agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN label(head(nodes(p)[1..])), labels(last(tail(nodes(p))[0..1])), properties(last(nodes(p)[..2])) $$) as (node_slice_head_label agtype, tail_node_slice_last_labels agtype, node_slice_last_properties agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN last(e[-1..]).arry[2].stats $$) as (edge_slice_last_stats agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(startNode(head(e[0..1]))), id(endNode(last(e[-1..]))) $$) as (edge_slice_head_start_id agtype, edge_slice_last_end_id agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(startNode(head(relationships(p)[1..]))), labels(endNode(last(relationships(p)[1..]))), properties(startNode(head(relationships(p)[1..]))) $$) as (rel_slice_head_start_id agtype, rel_slice_last_end_labels agtype, rel_slice_head_start_properties agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN size(tail(e)), size(reverse(e)) $$) as (tail_edge_size agtype, reverse_edge_size agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN isEmpty(tail(e)), isEmpty(reverse(e)) $$) as (tail_edge_empty agtype, reverse_edge_empty agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN size(tail(reverse(e))), size(reverse(tail(e))), isEmpty(tail(reverse(e))), isEmpty(reverse(tail(e))) $$) as (tail_reverse_edge_size agtype, reverse_tail_edge_size agtype, tail_reverse_edge_empty agtype, reverse_tail_edge_empty agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN size(tail(tail(e))), isEmpty(tail(tail(e))) $$) as (double_tail_edge_size agtype, double_tail_edge_empty agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN tail(tail(e))[0..1] $$) as (double_tail_edge_slice agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN count(head(tail(tail(nodes(p)))[0..1])), count(head(tail(tail(relationships(p)))[0..1])) $$) as (double_tail_node_slice_count agtype, double_tail_rel_slice_count agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN size(tail(tail(e))[0..1]), isEmpty(tail(tail(e))[0..1]) $$) as (double_tail_edge_slice_size agtype, double_tail_edge_slice_empty agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN count(startNode(e[0])), count(endNode(e[0])), count(id(startNode(e[0]))), count(id(endNode(e[0]))) $$) as (edge_start_node_count agtype, edge_end_node_count agtype, edge_start_id_count agtype, edge_end_id_count agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(tail(e)[0]) $$) as (tail_edge0_id agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(reverse(e)[0]) $$) as (reverse_edge0_id agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN type(reverse(e)[0]), properties(reverse(e)[0]) $$) as (reverse_edge0_type agtype, reverse_edge0_properties agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(head(reverse(e)[0..1])), head(reverse(e)[0..1]).arry[2].stats, id(endNode(head(reverse(e)[0..1]))) $$) as (reverse_edge_slice_head_id agtype, reverse_edge_slice_stats agtype, reverse_edge_slice_end_id agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN tail(e)[0..1], reverse(e)[0..1] $$) as (tail_edge_slice agtype, reverse_edge_slice agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN tail(reverse(e))[0..1], reverse(tail(e))[0..1] $$) as (tail_reverse_edge_slice agtype, reverse_tail_edge_slice agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN count(head(tail(reverse(nodes(p)))[0..1])), count(head(reverse(tail(nodes(p)))[0..1])), count(head(tail(reverse(relationships(p)))[0..1])), count(head(reverse(tail(relationships(p)))[0..1])) $$) as (tail_reverse_node_slice_count agtype, reverse_tail_node_slice_count agtype, tail_reverse_rel_slice_count agtype, reverse_tail_rel_slice_count agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN size(tail(reverse(e))[0..1]), size(reverse(tail(e))[0..1]), isEmpty(tail(reverse(e))[0..1]), isEmpty(reverse(tail(e))[0..1]) $$) as (tail_reverse_edge_slice_size agtype, reverse_tail_edge_slice_size agtype, tail_reverse_edge_slice_empty agtype, reverse_tail_edge_slice_empty agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(head(tail(reverse(e))[0..1])), id(last(reverse(tail(e))[0..1])), id(head(tail(tail(e))[0..1])) $$) as (tail_reverse_slice_head_id agtype, reverse_tail_slice_last_id agtype, double_tail_slice_head_id agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(last(tail(reverse(e)))), id(head(reverse(tail(e)))), last(tail(tail(e))).id $$) as (tail_reverse_last_id agtype, reverse_tail_head_id agtype, double_tail_last_id agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN type(last(tail(reverse(e)))), properties(head(reverse(tail(e)))), head(reverse(tail(e))).arry[2].stats $$) as (tail_reverse_last_type agtype, reverse_tail_head_properties agtype, reverse_tail_head_stats agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(startNode(last(tail(reverse(e))))), id(endNode(head(reverse(tail(e))))), labels(startNode(head(reverse(tail(e))))), properties(endNode(last(tail(reverse(e))))) $$) as (tail_reverse_last_start_id agtype, reverse_tail_head_end_id agtype, reverse_tail_head_start_labels agtype, tail_reverse_last_end_properties agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(tail(reverse(e))[0]), id(reverse(tail(e))[0]), id(tail(tail(e))[0]) $$) as (tail_reverse_index_id agtype, reverse_tail_index_id agtype, double_tail_index_id agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN type(tail(reverse(e))[0]), properties(reverse(tail(e))[0]), properties(tail(tail(e))[0]) $$) as (tail_reverse_index_type agtype, reverse_tail_index_properties agtype, double_tail_index_properties agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(startNode(tail(reverse(e))[0])), id(endNode(reverse(tail(e))[0])), labels(startNode(tail(tail(e))[0])) $$) as (tail_reverse_start_id agtype, reverse_tail_end_id agtype, double_tail_start_labels agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN reverse(tail(e))[0].arry[2].stats, tail(reverse(e))[0].id, tail(tail(e))[0].id $$) as (reverse_tail_index_stats agtype, tail_reverse_index_property_id agtype, double_tail_index_property_id agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*1..2]->() RETURN count(last(tail(e))) $$) as (tail_last_edge_count agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*1..2]->() RETURN count(id(last(tail(e)))) $$) as (tail_last_edge_id_count agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*1..2]->() RETURN count(type(last(tail(e)))), count(properties(last(tail(e)))), count(last(tail(e)).arry[2].stats) $$) as (tail_last_edge_type_count agtype, tail_last_edge_properties_count agtype, tail_last_edge_property_count agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*1..2]->() RETURN count(startNode(last(tail(e)))), count(endNode(last(tail(e)))), count(id(startNode(last(tail(e))))), count(id(endNode(last(tail(e))))) $$) as (tail_last_edge_start_node_count agtype, tail_last_edge_end_node_count agtype, tail_last_edge_start_id_count agtype, tail_last_edge_end_id_count agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*1..2]->() RETURN count(label(startNode(last(tail(e))))), count(labels(endNode(last(tail(e))))), count(properties(endNode(last(tail(e))))) $$) as (tail_last_edge_start_label_count agtype, tail_last_edge_end_labels_count agtype, tail_last_edge_end_properties_count agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN tail(e) $$) as (edge_tail agtype); +\pset format aligned SELECT * FROM cypher('access',$$ MATCH ()-[e*]->() RETURN properties(e[0]), properties(e[1]) $$) as (prop_1st agtype, prop_2nd agtype); SELECT * FROM cypher('access',$$ MATCH ()-[e*]->() RETURN e[0].id, e[1].id $$) as (results_1st agtype, results_2nd agtype); @@ -355,6 +466,15 @@ SELECT * FROM cypher('issue_1910', $$ MATCH (n) WHERE EXISTS((n)-[*1]-({name: 'W SELECT * FROM cypher('issue_1910', $$ MATCH (n) WHERE EXISTS((n)-[*2..2]-({name: 'Willem Defoe'})) RETURN n.name $$) AS (name agtype); +\pset format unaligned +SELECT * FROM cypher('issue_1910', $$ MATCH p=()-[*1..1]->() RETURN count(startNode(relationships(p)[0]).name) + count(endNode(relationships(p)[0]).name) $$) AS (c agtype); +SELECT * FROM cypher('issue_1910', $$ MATCH ()-[e*1..1]->() RETURN count(startNode(e[0]).name) + count(endNode(e[0]).name) $$) AS (c agtype); +SELECT * FROM cypher('issue_1910', $$ MATCH ()-[e*1..2]->() RETURN count(startNode(last(tail(e))).name) + count(endNode(last(tail(e))).name) $$) AS (c agtype); +SELECT * FROM cypher('issue_1910', $$ MATCH ()-[e*1..1]->() RETURN count(startNode(head(e[0..1])).name) + count(endNode(last(e[0..])).name) $$) AS (c agtype); +SELECT * FROM cypher('issue_1910', $$ MATCH ()-[e*1..2]->() RETURN count(startNode(tail(reverse(e))[0]).name) + count(endNode(reverse(tail(e))[0]).name) $$) AS (c agtype); +SELECT * FROM cypher('issue_1910', $$ MATCH ()-[e*1..2]->() RETURN count(startNode(last(tail(reverse(e)))).name) + count(endNode(head(reverse(tail(e)))).name) $$) AS (c agtype); +\pset format aligned + SELECT drop_graph('issue_1910', true); -- issue 2092: VLE with chained OPTIONAL MATCH and NULL handling diff --git a/sql/agtype_typecast.sql b/sql/agtype_typecast.sql index c29c0a657..459e4e98d 100644 --- a/sql/agtype_typecast.sql +++ b/sql/agtype_typecast.sql @@ -118,6 +118,42 @@ RETURNS NULL ON NULL INPUT PARALLEL SAFE AS 'MODULE_PATHNAME'; +-- function to read the length of a VLE_path_container without materializing it +CREATE FUNCTION ag_catalog.age_vle_path_length(agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read the number of nodes in a VLE_path_container +CREATE FUNCTION ag_catalog.age_vle_path_node_count(agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read the number of edges in tail(relationships) for a VLE path +CREATE FUNCTION ag_catalog.age_vle_edge_tail_count(agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to test VLE node/edge list emptiness without materializing lists +CREATE FUNCTION ag_catalog.age_vle_list_is_empty(agtype, agtype) + RETURNS boolean + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + -- function to create an AGTV_ARRAY of edges from a VLE_path_container CREATE FUNCTION ag_catalog.age_materialize_vle_edges(agtype) RETURNS agtype @@ -127,6 +163,325 @@ RETURNS NULL ON NULL INPUT PARALLEL SAFE AS 'MODULE_PATHNAME'; +-- function to create an AGTV_ARRAY of nodes from a VLE_path_container +CREATE FUNCTION ag_catalog.age_materialize_vle_nodes(agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to create one vertex from a VLE_path_container node index +CREATE FUNCTION ag_catalog.age_materialize_vle_node_at(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to create one vertex from a reversed VLE_path_container node index +CREATE FUNCTION ag_catalog.age_materialize_vle_node_reversed_at(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to create the last vertex from a VLE_path_container node-list tail +CREATE FUNCTION ag_catalog.age_materialize_vle_node_tail_last(agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read the last vertex id from a VLE_path_container node-list tail +CREATE FUNCTION ag_catalog.age_vle_node_tail_last_id(agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read one vertex id from a VLE_path_container node index +CREATE FUNCTION ag_catalog.age_vle_node_id_at(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read one vertex label from a VLE_path_container node index +CREATE FUNCTION ag_catalog.age_vle_node_label_at(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read one vertex labels array from a VLE_path_container node index +CREATE FUNCTION ag_catalog.age_vle_node_labels_at(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read one vertex properties object from a VLE_path_container node index +CREATE FUNCTION ag_catalog.age_vle_node_properties_at(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to create a node slice from a VLE_path_container +CREATE FUNCTION ag_catalog.age_materialize_vle_nodes_slice(agtype, agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to create a VLE list slice without materializing transformed lists +CREATE FUNCTION ag_catalog.age_materialize_vle_list_slice(agtype, agtype, agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to count a VLE list slice without materializing it +CREATE FUNCTION ag_catalog.age_vle_list_slice_count(agtype, agtype, agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to check whether a VLE list slice is empty without materializing it +CREATE FUNCTION ag_catalog.age_vle_list_slice_is_empty(agtype, agtype, agtype, agtype) + RETURNS boolean + LANGUAGE C + STABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to create the head/last element of a VLE list slice +CREATE FUNCTION ag_catalog.age_materialize_vle_slice_boundary(agtype, agtype, agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to create the tail of a VLE_path_container node list +CREATE FUNCTION ag_catalog.age_materialize_vle_nodes_tail(agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to create a reversed VLE_path_container node list +CREATE FUNCTION ag_catalog.age_materialize_vle_nodes_reversed(agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to create one edge from a VLE_path_container relationship index +CREATE FUNCTION ag_catalog.age_materialize_vle_edge_at(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to create one edge from a reversed VLE_path_container relationship index +CREATE FUNCTION ag_catalog.age_materialize_vle_edge_reversed_at(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to create the last edge from a VLE_path_container edge-list tail +CREATE FUNCTION ag_catalog.age_materialize_vle_edge_tail_last(agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read the last edge id from a VLE_path_container edge-list tail +CREATE FUNCTION ag_catalog.age_vle_edge_tail_last_id(agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read one field from the last element of a VLE tail list +CREATE FUNCTION ag_catalog.age_vle_tail_last_field(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read an endpoint from the last edge of a VLE tail list +CREATE FUNCTION ag_catalog.age_vle_tail_last_edge_endpoint(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read one endpoint field from the last edge of a VLE tail list +CREATE FUNCTION ag_catalog.age_vle_tail_last_endpoint_field(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read one edge id from a VLE_path_container relationship index +CREATE FUNCTION ag_catalog.age_vle_edge_id_at(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to check whether a VLE edge index resolves to an edge +CREATE FUNCTION ag_catalog.age_vle_edge_index_exists(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to compare two indexed VLE edges by id +CREATE FUNCTION ag_catalog.age_vle_edge_indices_equal(agtype, agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to compare a reversed-indexed VLE edge with a normal indexed edge +CREATE FUNCTION ag_catalog.age_vle_edge_reversed_index_equal(agtype, agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read an indexed edge's label/type +CREATE FUNCTION ag_catalog.age_vle_edge_label_at(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read an indexed edge's properties +CREATE FUNCTION ag_catalog.age_vle_edge_properties_at(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to create an indexed edge's stored start vertex +CREATE FUNCTION ag_catalog.age_vle_edge_start_node_at(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to create an indexed edge's stored end vertex +CREATE FUNCTION ag_catalog.age_vle_edge_end_node_at(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read one indexed edge endpoint field +CREATE FUNCTION ag_catalog.age_vle_edge_endpoint_field_at(agtype, agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read an indexed edge's stored start vertex id +CREATE FUNCTION ag_catalog.age_vle_edge_start_id_at(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read an indexed edge's stored end vertex id +CREATE FUNCTION ag_catalog.age_vle_edge_end_id_at(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to create the tail of a VLE_path_container edge list +CREATE FUNCTION ag_catalog.age_materialize_vle_edges_tail(agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to create a reversed VLE_path_container edge list +CREATE FUNCTION ag_catalog.age_materialize_vle_edges_reversed(agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + CREATE FUNCTION ag_catalog.age_match_vle_edge_to_id_qual(variadic "any") RETURNS boolean LANGUAGE C diff --git a/src/backend/catalog/ag_catalog.c b/src/backend/catalog/ag_catalog.c index 79beeb42e..0bdfeeb38 100644 --- a/src/backend/catalog/ag_catalog.c +++ b/src/backend/catalog/ag_catalog.c @@ -27,7 +27,12 @@ #include "commands/defrem.h" #include "nodes/parsenodes.h" #include "tcop/utility.h" +#include "utils/builtins.h" +#include "utils/catcache.h" +#include "utils/hsearch.h" +#include "utils/inval.h" #include "utils/lsyscache.h" +#include "utils/memutils.h" #include "catalog/ag_graph.h" #include "catalog/ag_label.h" @@ -37,6 +42,14 @@ static object_access_hook_type prev_object_access_hook; static ProcessUtility_hook_type prev_process_utility_hook; static bool prev_object_hook_is_set; +static HTAB *ag_relation_id_cache = NULL; +static bool ag_relation_id_callback_registered = false; + +typedef struct ag_relation_id_cache_entry +{ + NameData name; + Oid oid; +} ag_relation_id_cache_entry; static void object_access(ObjectAccessType access, Oid class_id, Oid object_id, int sub_id, void *arg); @@ -47,6 +60,10 @@ void ag_ProcessUtility_hook(PlannedStmt *pstmt, const char *queryString, bool re static bool is_age_drop(PlannedStmt *pstmt); static void drop_age_extension(DropStmt *stmt); +static void initialize_ag_relation_id_cache(void); +static void invalidate_ag_relation_id_cache(Datum arg, Oid relid); +static int ag_relation_name_hash_compare(const void *key1, const void *key2, + Size keysize); void object_access_hook_init(void) { @@ -234,14 +251,13 @@ static void object_access(ObjectAccessType access, Oid class_id, Oid object_id, if (drop_arg->dropflags & PERFORM_DELETION_INTERNAL) return; - cache_data = search_graph_namespace_cache(object_id); + cache_data = search_graph_namespace_cache_cached(object_id); if (cache_data) { - char *nspname = get_namespace_name(object_id); - ereport(ERROR, (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST), errmsg("schema \"%s\" is for graph \"%s\"", - nspname, NameStr(cache_data->name)))); + NameStr(cache_data->name), + NameStr(cache_data->name)))); } return; @@ -251,7 +267,7 @@ static void object_access(ObjectAccessType access, Oid class_id, Oid object_id, { label_cache_data *cache_data; - cache_data = search_label_relation_cache(object_id); + cache_data = search_label_relation_cache_cached(object_id); /* We are interested in only tables that are labels. */ if (!cache_data) @@ -268,11 +284,10 @@ static void object_access(ObjectAccessType access, Oid class_id, Oid object_id, } else { - char *relname = get_rel_name(object_id); - ereport(ERROR, (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST), errmsg("table \"%s\" is for label \"%s\"", - relname, NameStr(cache_data->name)))); + get_label_cache_relation_name(cache_data), + NameStr(cache_data->name)))); } } } @@ -280,6 +295,18 @@ static void object_access(ObjectAccessType access, Oid class_id, Oid object_id, Oid ag_relation_id(const char *name, const char *kind) { Oid id; + NameData name_key; + ag_relation_id_cache_entry *entry; + bool found; + + initialize_ag_relation_id_cache(); + + namestrcpy(&name_key, name); + entry = hash_search(ag_relation_id_cache, &name_key, HASH_FIND, NULL); + if (entry != NULL) + { + return entry->oid; + } id = get_relname_relid(name, ag_catalog_namespace_id()); if (!OidIsValid(id)) @@ -288,5 +315,101 @@ Oid ag_relation_id(const char *name, const char *kind) errmsg("%s \"%s\" does not exist", kind, name))); } + if (ag_relation_id_cache == NULL) + { + initialize_ag_relation_id_cache(); + } + + entry = hash_search(ag_relation_id_cache, &name_key, HASH_ENTER, &found); + if (found) + { + return entry->oid; + } + + entry->oid = id; + return id; } + +static void initialize_ag_relation_id_cache(void) +{ + HASHCTL hash_ctl; + + if (ag_relation_id_cache != NULL) + { + return; + } + + if (!CacheMemoryContext) + { + CreateCacheMemoryContext(); + } + + MemSet(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(NameData); + hash_ctl.entrysize = sizeof(ag_relation_id_cache_entry); + hash_ctl.match = ag_relation_name_hash_compare; + hash_ctl.hcxt = CacheMemoryContext; + + ag_relation_id_cache = hash_create("ag_catalog relation oid cache", 8, + &hash_ctl, + HASH_ELEM | HASH_BLOBS | HASH_COMPARE | + HASH_CONTEXT); + + if (!ag_relation_id_callback_registered) + { + CacheRegisterRelcacheCallback(invalidate_ag_relation_id_cache, + (Datum)0); + ag_relation_id_callback_registered = true; + } +} + +static void invalidate_ag_relation_id_cache(Datum arg, Oid relid) +{ + HASH_SEQ_STATUS hash_seq; + ag_relation_id_cache_entry *entry; + + if (ag_relation_id_cache == NULL) + { + return; + } + + if (!OidIsValid(relid)) + { + hash_destroy(ag_relation_id_cache); + ag_relation_id_cache = NULL; + return; + } + + hash_seq_init(&hash_seq, ag_relation_id_cache); + while ((entry = hash_seq_search(&hash_seq)) != NULL) + { + if (entry->oid == relid) + { + void *removed; + + removed = hash_search(ag_relation_id_cache, &entry->name, + HASH_REMOVE, NULL); + hash_seq_term(&hash_seq); + + if (!removed) + { + ereport(ERROR, + (errmsg_internal("ag relation oid cache corrupted"))); + } + + break; + } + } +} + +static int ag_relation_name_hash_compare(const void *key1, const void *key2, + Size keysize) +{ + Name name1 = (Name)key1; + Name name2 = (Name)key2; + + Assert(keysize == NAMEDATALEN); + + return strncmp(NameStr(*name1), NameStr(*name2), NAMEDATALEN); +} diff --git a/src/backend/catalog/ag_graph.c b/src/backend/catalog/ag_graph.c index 833cba252..bffedb3f7 100644 --- a/src/backend/catalog/ag_graph.c +++ b/src/backend/catalog/ag_graph.c @@ -22,13 +22,10 @@ #include "access/genam.h" #include "access/heapam.h" #include "catalog/indexing.h" -#include "utils/lsyscache.h" #include "catalog/ag_graph.h" #include "utils/ag_cache.h" -static Oid get_graph_namespace(const char *graph_name); - /* INSERT INTO ag_catalog.ag_graph VALUES (graph_name, nsp_id) */ void insert_graph(const Name graph_name, const Oid nsp_id) { @@ -59,6 +56,8 @@ void insert_graph(const Name graph_name, const Oid nsp_id) */ CatalogTupleInsert(ag_graph, tuple); + invalidate_graph_cache(); + table_close(ag_graph, RowExclusiveLock); } @@ -87,6 +86,8 @@ void delete_graph(const Name graph_name) CatalogTupleDelete(ag_graph, &tuple->t_self); + invalidate_graph_cache(); + systable_endscan(scan_desc); table_close(ag_graph, RowExclusiveLock); } @@ -135,6 +136,8 @@ void update_graph_name(const Name graph_name, const Name new_name) /* update the current tuple with the new tuple */ CatalogTupleUpdate(ag_graph, &cur_tuple->t_self, new_tuple); + invalidate_graph_cache(); + /* end scan and close ag_graph */ systable_endscan(scan_desc); table_close(ag_graph, RowExclusiveLock); @@ -144,7 +147,7 @@ Oid get_graph_oid(const char *graph_name) { graph_cache_data *cache_data; - cache_data = search_graph_name_cache(graph_name); + cache_data = search_graph_name_cache_cached(graph_name); if (cache_data) { return cache_data->oid; @@ -155,30 +158,25 @@ Oid get_graph_oid(const char *graph_name) } } -static Oid get_graph_namespace(const char *graph_name) +char *get_graph_namespace_name(const char *graph_name) { graph_cache_data *cache_data; - cache_data = search_graph_name_cache(graph_name); + cache_data = search_graph_name_cache_cached(graph_name); if (!cache_data) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), errmsg("graph \"%s\" does not exist", graph_name))); } - return cache_data->namespace; -} - -char *get_graph_namespace_name(const char *graph_name) -{ - return get_namespace_name(get_graph_namespace(graph_name)); + return pstrdup(NameStr(cache_data->name)); } bool graph_namespace_exists(Oid graph_oid) { graph_cache_data *cache_data; - cache_data = search_graph_namespace_cache(graph_oid); + cache_data = search_graph_namespace_cache_cached(graph_oid); if (cache_data) { return true; diff --git a/src/backend/catalog/ag_label.c b/src/backend/catalog/ag_label.c index d407626fd..02d0f703c 100644 --- a/src/backend/catalog/ag_label.c +++ b/src/backend/catalog/ag_label.c @@ -20,6 +20,8 @@ #include "postgres.h" #include "access/genam.h" +#include "access/heapam.h" +#include "access/tableam.h" #include "catalog/indexing.h" #include "catalog/namespace.h" #include "executor/executor.h" @@ -30,7 +32,6 @@ #include "catalog/ag_graph.h" #include "catalog/ag_label.h" #include "commands/label_commands.h" -#include "executor/cypher_utils.h" #include "utils/ag_cache.h" /* @@ -89,6 +90,8 @@ void insert_label(const char *label_name, Oid graph_oid, int32 label_id, */ CatalogTupleInsert(ag_label, tuple); + invalidate_label_cache(); + table_close(ag_label, RowExclusiveLock); } @@ -117,6 +120,8 @@ void delete_label(Oid relation) CatalogTupleDelete(ag_label, &tuple->t_self); + invalidate_label_cache(); + systable_endscan(scan_desc); table_close(ag_label, RowExclusiveLock); } @@ -125,7 +130,7 @@ int32 get_label_id(const char *label_name, Oid graph_oid) { label_cache_data *cache_data; - cache_data = search_label_name_graph_cache(label_name, graph_oid); + cache_data = search_label_name_graph_cache_cached(label_name, graph_oid); if (cache_data) return cache_data->id; else @@ -136,7 +141,7 @@ Oid get_label_relation(const char *label_name, Oid graph_oid) { label_cache_data *cache_data; - cache_data = search_label_name_graph_cache(label_name, graph_oid); + cache_data = search_label_name_graph_cache_cached(label_name, graph_oid); if (cache_data) return cache_data->relation; else @@ -145,14 +150,20 @@ Oid get_label_relation(const char *label_name, Oid graph_oid) char *get_label_relation_name(const char *label_name, Oid graph_oid) { - return get_rel_name(get_label_relation(label_name, graph_oid)); + label_cache_data *cache_data; + + cache_data = search_label_name_graph_cache_cached(label_name, graph_oid); + if (cache_data) + return pstrdup(get_label_cache_relation_name(cache_data)); + else + return NULL; } char get_label_kind(const char *label_name, Oid label_graph) { label_cache_data *cache_data; - cache_data = search_label_name_graph_cache(label_name, label_graph); + cache_data = search_label_name_graph_cache_cached(label_name, label_graph); if (cache_data) { return cache_data->kind; @@ -188,28 +199,19 @@ Datum _label_name(PG_FUNCTION_ARGS) } graph = PG_GETARG_OID(0); - - /* Check if the graph OID is valid */ - if (!graph_namespace_exists(graph)) - { - ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("graph with oid %u does not exist", graph))); - } label_id = (int32)(((uint64)AG_GETARG_GRAPHID(1)) >> ENTRY_ID_BITS); - label_cache = search_label_graph_oid_cache(graph, label_id); - - label_name = NameStr(label_cache->name); - - /* If label_name is not found, error out */ - if (label_name == NULL) + label_cache = search_label_graph_oid_cache_cached(graph, label_id); + if (label_cache == NULL) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("label with id %d does not exist in graph %u", label_id, graph))); } + label_name = NameStr(label_cache->name); + if (IS_AG_DEFAULT_LABEL(label_name)) PG_RETURN_CSTRING(""); @@ -224,6 +226,14 @@ Datum _label_id(PG_FUNCTION_ARGS) Name label_name; Oid graph; int32 id; + static NameData cached_graph_name; + static NameData cached_label_name; + static int32 cached_label_id = INVALID_LABEL_ID; + static uint64 cached_graph_generation = 0; + static uint64 cached_label_generation = 0; + static bool cached_valid = false; + uint64 graph_generation; + uint64 label_generation; if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) { @@ -233,9 +243,27 @@ Datum _label_id(PG_FUNCTION_ARGS) graph_name = PG_GETARG_NAME(0); label_name = PG_GETARG_NAME(1); + graph_generation = get_graph_cache_generation(); + label_generation = get_label_cache_generation(); + if (cached_valid && + cached_graph_generation == graph_generation && + cached_label_generation == label_generation && + namestrcmp(&cached_graph_name, NameStr(*graph_name)) == 0 && + namestrcmp(&cached_label_name, NameStr(*label_name)) == 0) + { + PG_RETURN_INT32(cached_label_id); + } + graph = get_graph_oid(NameStr(*graph_name)); id = get_label_id(NameStr(*label_name), graph); + cached_graph_name = *graph_name; + cached_label_name = *label_name; + cached_label_id = id; + cached_graph_generation = graph_generation; + cached_label_generation = label_generation; + cached_valid = true; + PG_RETURN_INT32(id); } @@ -259,7 +287,7 @@ bool label_id_exists(Oid graph_oid, int32 label_id) { label_cache_data *cache_data; - cache_data = search_label_graph_oid_cache(graph_oid, label_id); + cache_data = search_label_graph_oid_cache_cached(graph_oid, label_id); if (cache_data) return true; else @@ -275,138 +303,81 @@ RangeVar *get_label_range_var(char *graph_name, Oid graph_oid, char *relname; label_cache_data *label_cache; - label_cache = search_label_name_graph_cache(label_name, graph_oid); + label_cache = search_label_name_graph_cache_cached(label_name, graph_oid); - relname = get_rel_name(label_cache->relation); + relname = pstrdup(get_label_cache_relation_name(label_cache)); return makeRangeVar(graph_name, relname, 2); } /* - * Retrieves a list of all the names of a graph. + * Retrieves a list of all the edge labels of a graph. * * XXX: We may want to use the cache system for this function, * however the cache system currently requires us to know the * name of the label we want. */ -List *get_all_edge_labels_per_graph(EState *estate, Oid graph_oid) +List *get_all_edge_labels_per_graph(Snapshot snapshot, Oid graph_oid) { List *labels = NIL; - ScanKeyData scan_keys[2]; + ScanKeyData scan_key; Relation ag_label; - TableScanDesc scan_desc; + SysScanDesc scan_desc; HeapTuple tuple; - TupleTableSlot *slot; - ResultRelInfo *resultRelInfo; - Oid index_oid; + TupleDesc tupdesc; /* setup the table to be scanned */ ag_label = table_open(ag_label_relation_id(), AccessShareLock); + tupdesc = RelationGetDescr(ag_label); - index_oid = find_usable_btree_index_for_attr(ag_label, Anum_ag_label_graph); + ScanKeyInit(&scan_key, Anum_ag_label_graph, BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(graph_oid)); - resultRelInfo = create_entity_result_rel_info(estate, "ag_catalog", - "ag_label"); + scan_desc = systable_beginscan(ag_label, ag_label_graph_oid_index_id(), + true, snapshot, 1, &scan_key); - if (OidIsValid(index_oid)) + while (HeapTupleIsValid(tuple = systable_getnext(scan_desc))) { - Relation index_rel; - IndexScanDesc index_scan_desc; - - slot = ExecInitExtraTupleSlot( - estate, RelationGetDescr(resultRelInfo->ri_RelationDesc), - &TTSOpsBufferHeapTuple); - - index_rel = index_open(index_oid, AccessShareLock); - - /* - * Use 1 as the attribute number because 'graph' is the 1st column - * in the ag_label_graph_oid_index - */ - ScanKeyInit(&scan_keys[0], 1, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(graph_oid)); - - index_scan_desc = index_beginscan(ag_label, index_rel, estate->es_snapshot, NULL, 1, 0); - index_rescan(index_scan_desc, scan_keys, 1, NULL, 0); - - while (index_getnext_slot(index_scan_desc, ForwardScanDirection, slot)) + ag_label_info *label_info; + Name label; + bool is_null; + Datum datum; + Oid label_relation; + + datum = heap_getattr(tuple, Anum_ag_label_kind, tupdesc, &is_null); + if (is_null || DatumGetChar(datum) != LABEL_TYPE_EDGE) { - Name label; - Name lval; - bool isNull; - Datum datum; - char kind; - - /*There isn't field kind in index. So we should check it by hands*/ - datum = slot_getattr(slot, Anum_ag_label_kind, &isNull); - if (isNull) - { - continue; - } - - kind = DatumGetChar(datum); - - if (kind != LABEL_TYPE_EDGE) - { - continue; - } - - datum = slot_getattr(slot, Anum_ag_label_name, &isNull); - if (!isNull) - { - label = DatumGetName(datum); - lval = (Name) palloc(NAMEDATALEN); - namestrcpy(lval, NameStr(*label)); - labels = lappend(labels, lval); - } + continue; } - index_endscan(index_scan_desc); - index_close(index_rel, AccessShareLock); - } - else - { - slot = ExecInitExtraTupleSlot( - estate, RelationGetDescr(resultRelInfo->ri_RelationDesc), - &TTSOpsHeapTuple); - - /* setup scan keys to get all edges for the given graph oid */ - ScanKeyInit(&scan_keys[1], Anum_ag_label_graph, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(graph_oid)); - ScanKeyInit(&scan_keys[0], Anum_ag_label_kind, BTEqualStrategyNumber, - F_CHAREQ, CharGetDatum(LABEL_TYPE_EDGE)); - - scan_desc = table_beginscan(ag_label, estate->es_snapshot, 2, scan_keys); - - /* scan through the results and get all the label names. */ - while(true) + datum = heap_getattr(tuple, Anum_ag_label_name, tupdesc, &is_null); + if (is_null) { - Name label; - Name lval; - bool isNull; - Datum datum; - - tuple = heap_getnext(scan_desc, ForwardScanDirection); - - /* no more labels to process */ - if (!HeapTupleIsValid(tuple)) - break; + continue; + } - ExecStoreHeapTuple(tuple, slot, false); + label = DatumGetName(datum); - datum = slot_getattr(slot, Anum_ag_label_name, &isNull); - label = DatumGetName(datum); + datum = heap_getattr(tuple, Anum_ag_label_relation, tupdesc, &is_null); + if (is_null) + { + continue; + } - lval = (Name) palloc(NAMEDATALEN); - namestrcpy(lval, NameStr(*label)); - labels = lappend(labels, lval); + label_relation = DatumGetObjectId(datum); + if (!OidIsValid(label_relation)) + { + continue; } - table_endscan(scan_desc); + label_info = palloc(sizeof(ag_label_info)); + label_info->name = pstrdup(NameStr(*label)); + label_info->relation = label_relation; + labels = lappend(labels, label_info); } - destroy_entity_result_rel_info(resultRelInfo); - table_close(resultRelInfo->ri_RelationDesc, AccessShareLock); + systable_endscan(scan_desc); + table_close(ag_label, AccessShareLock); return labels; } diff --git a/src/backend/catalog/ag_namespace.c b/src/backend/catalog/ag_namespace.c index f074609cd..36732deea 100644 --- a/src/backend/catalog/ag_namespace.c +++ b/src/backend/catalog/ag_namespace.c @@ -20,15 +20,58 @@ #include "postgres.h" #include "catalog/namespace.h" +#include "utils/inval.h" +#include "utils/syscache.h" #include "catalog/ag_namespace.h" +static Oid cached_ag_catalog_namespace = InvalidOid; +static Oid cached_pg_catalog_namespace = InvalidOid; +static bool namespace_cache_callback_registered = false; + +static void invalidate_namespace_oid_cache(Datum arg, int cache_id, + uint32 hash_value); +static void initialize_namespace_oid_cache(void); + Oid ag_catalog_namespace_id(void) { - return get_namespace_oid("ag_catalog", false); + initialize_namespace_oid_cache(); + + if (!OidIsValid(cached_ag_catalog_namespace)) + { + cached_ag_catalog_namespace = get_namespace_oid("ag_catalog", false); + } + + return cached_ag_catalog_namespace; } Oid pg_catalog_namespace_id(void) { - return get_namespace_oid("pg_catalog", false); + initialize_namespace_oid_cache(); + + if (!OidIsValid(cached_pg_catalog_namespace)) + { + cached_pg_catalog_namespace = get_namespace_oid("pg_catalog", false); + } + + return cached_pg_catalog_namespace; +} + +static void initialize_namespace_oid_cache(void) +{ + if (namespace_cache_callback_registered) + { + return; + } + + CacheRegisterSyscacheCallback(NAMESPACEOID, invalidate_namespace_oid_cache, + (Datum)0); + namespace_cache_callback_registered = true; +} + +static void invalidate_namespace_oid_cache(Datum arg, int cache_id, + uint32 hash_value) +{ + cached_ag_catalog_namespace = InvalidOid; + cached_pg_catalog_namespace = InvalidOid; } diff --git a/src/backend/commands/graph_commands.c b/src/backend/commands/graph_commands.c index f7e8d070b..5fd44a1c5 100644 --- a/src/backend/commands/graph_commands.c +++ b/src/backend/commands/graph_commands.c @@ -26,6 +26,7 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "parser/parser.h" +#include "utils/builtins.h" #include "catalog/ag_graph.h" #include "catalog/ag_label.h" @@ -40,6 +41,7 @@ #define gen_graph_namespace_name(graph_name) (graph_name) static Oid create_schema_for_graph(const Name graph_name); +static void drop_graph_internal(const char *graph_name_str, const bool cascade); static void drop_schema_for_graph(char *graph_name_str, const bool cascade); static void remove_schema(Node *schema_name, DropBehavior behavior); static void rename_graph(const Name graph_name, const Name new_name); @@ -104,8 +106,10 @@ Oid create_graph_internal(const Name graph_name) CommandCounterIncrement(); /* Create the default label tables */ - create_label(graph_name_str, AG_DEFAULT_LABEL_VERTEX, LABEL_TYPE_VERTEX, NIL); - create_label(graph_name_str, AG_DEFAULT_LABEL_EDGE, LABEL_TYPE_EDGE, NIL); + create_label_with_graph_oid(graph_name_str, nsp_id, AG_DEFAULT_LABEL_VERTEX, + LABEL_TYPE_VERTEX, NIL); + create_label_with_graph_oid(graph_name_str, nsp_id, AG_DEFAULT_LABEL_EDGE, + LABEL_TYPE_EDGE, NIL); return nsp_id; } @@ -192,7 +196,6 @@ PG_FUNCTION_INFO_V1(drop_graph); Datum drop_graph(PG_FUNCTION_ARGS) { Name graph_name; - char *graph_name_str; bool cascade; if (PG_ARGISNULL(0)) @@ -203,21 +206,28 @@ Datum drop_graph(PG_FUNCTION_ARGS) graph_name = PG_GETARG_NAME(0); cascade = PG_GETARG_BOOL(1); - graph_name_str = NameStr(*graph_name); + drop_graph_internal(NameStr(*graph_name), cascade); + + PG_RETURN_VOID(); +} + +static void drop_graph_internal(const char *graph_name_str, const bool cascade) +{ + NameData graph_name; + if (!graph_exists(graph_name_str)) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), errmsg("graph \"%s\" does not exist", graph_name_str))); } - drop_schema_for_graph(graph_name_str, cascade); + drop_schema_for_graph((char *)graph_name_str, cascade); - delete_graph(graph_name); + namestrcpy(&graph_name, graph_name_str); + delete_graph(&graph_name); CommandCounterIncrement(); ereport(NOTICE, (errmsg("graph \"%s\" has been dropped", graph_name_str))); - - PG_RETURN_VOID(); } static void drop_schema_for_graph(char *graph_name_str, const bool cascade) @@ -407,7 +417,8 @@ List *get_graphnames(void) slot_getallattrs(slot); - str = DatumGetCString(slot->tts_values[Anum_ag_graph_name - 1]); + str = pstrdup(NameStr(*DatumGetName( + slot->tts_values[Anum_ag_graph_name - 1]))); graphnames = lappend(graphnames, str); } @@ -427,7 +438,6 @@ void drop_graphs(List *graphnames) { char *graphname = lfirst(lc); - DirectFunctionCall2( - drop_graph, CStringGetDatum(graphname), BoolGetDatum(true)); + drop_graph_internal(graphname, true); } } diff --git a/src/backend/commands/label_commands.c b/src/backend/commands/label_commands.c index ac789ecce..1e320be56 100644 --- a/src/backend/commands/label_commands.c +++ b/src/backend/commands/label_commands.c @@ -85,15 +85,19 @@ static void create_index_on_column(char *schema_name, char *rel_name, char *colname, bool unique); +static Oid create_label_with_graph_cache(char *graph_name, char *label_name, + char label_type, List *parents, + graph_cache_data *cache_data); PG_FUNCTION_INFO_V1(age_is_valid_label_name); Datum age_is_valid_label_name(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; - agtype_value *agtv_value = NULL; + agtype_value agtv_value; char *label_name = NULL; bool is_valid = false; + bool value_needs_free = false; if (PG_ARGISNULL(0)) { @@ -110,17 +114,23 @@ Datum age_is_valid_label_name(PG_FUNCTION_ARGS) errmsg("is_valid_label_name() only supports scalar arguments"))); } - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + (void)get_ith_agtype_value_from_container_no_copy(&agt_arg->root, 0, + &agtv_value, + &value_needs_free); - if (agtv_value->type != AGTV_STRING) + if (agtv_value.type != AGTV_STRING) { + if (value_needs_free) + pfree_agtype_value_content(&agtv_value); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("is_valid_label_name() only supports string arguments"))); } - label_name = pnstrdup(agtv_value->val.string.val, - agtv_value->val.string.len); + label_name = pnstrdup(agtv_value.val.string.val, + agtv_value.val.string.len); + if (value_needs_free) + pfree_agtype_value_content(&agtv_value); is_valid = is_valid_label_name(label_name, 0); pfree_if_not_null(label_name); @@ -149,6 +159,7 @@ Datum create_vlabel(PG_FUNCTION_ARGS) { char *graph_name; Oid graph_oid; + graph_cache_data *graph_cache; List *parent; RangeVar *rv; char *label_name; @@ -184,14 +195,15 @@ Datum create_vlabel(PG_FUNCTION_ARGS) } /* Check if graph does not exist */ - if (!graph_exists(graph_name)) + graph_cache = search_graph_name_cache_cached(graph_name); + if (graph_cache == NULL) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), errmsg("graph \"%s\" does not exist.", graph_name))); } - graph_oid = get_graph_oid(graph_name); + graph_oid = graph_cache->oid; /* Check if label with the input name already exists */ if (label_exists(label_name, graph_oid)) @@ -202,11 +214,12 @@ Datum create_vlabel(PG_FUNCTION_ARGS) } /* Create the default label tables */ - rv = get_label_range_var(graph_name, graph_oid, AG_DEFAULT_LABEL_VERTEX); + rv = makeRangeVar(graph_name, pstrdup(AG_DEFAULT_LABEL_VERTEX), -1); parent = list_make1(rv); - create_label(graph_name, label_name, LABEL_TYPE_VERTEX, parent); + create_label_with_graph_cache(graph_name, label_name, LABEL_TYPE_VERTEX, + parent, graph_cache); ereport(NOTICE, (errmsg("VLabel \"%s\" has been created", label_name))); @@ -230,6 +243,7 @@ Datum create_elabel(PG_FUNCTION_ARGS) { char *graph_name; Oid graph_oid; + graph_cache_data *graph_cache; List *parent; RangeVar *rv; char *label_name; @@ -265,14 +279,15 @@ Datum create_elabel(PG_FUNCTION_ARGS) } /* Check if graph does not exist */ - if (!graph_exists(graph_name)) + graph_cache = search_graph_name_cache_cached(graph_name); + if (graph_cache == NULL) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), errmsg("graph \"%s\" does not exist.", graph_name))); } - graph_oid = get_graph_oid(graph_name); + graph_oid = graph_cache->oid; /* Check if label with the input name already exists */ if (label_exists(label_name, graph_oid)) @@ -283,10 +298,11 @@ Datum create_elabel(PG_FUNCTION_ARGS) } /* Create the default label tables */ - rv = get_label_range_var(graph_name, graph_oid, AG_DEFAULT_LABEL_EDGE); + rv = makeRangeVar(graph_name, pstrdup(AG_DEFAULT_LABEL_EDGE), -1); parent = list_make1(rv); - create_label(graph_name, label_name, LABEL_TYPE_EDGE, parent); + create_label_with_graph_cache(graph_name, label_name, LABEL_TYPE_EDGE, + parent, graph_cache); ereport(NOTICE, (errmsg("ELabel \"%s\" has been created", label_name))); @@ -296,21 +312,12 @@ Datum create_elabel(PG_FUNCTION_ARGS) /* * For the new label, create an entry in ag_catalog.ag_label, create a - * new table and sequence. Returns the oid from the new tuple in - * ag_catalog.ag_label. + * new table and sequence. Returns the new label relation OID. */ -void create_label(char *graph_name, char *label_name, char label_type, - List *parents) +Oid create_label(char *graph_name, char *label_name, char label_type, + List *parents) { graph_cache_data *cache_data; - Oid graph_oid; - Oid nsp_id; - char *schema_name; - char *rel_name; - char *seq_name; - RangeVar *seq_range_var; - int32 label_id; - Oid relation_id; if (!is_valid_label_name(label_name, label_type)) { @@ -318,17 +325,62 @@ void create_label(char *graph_name, char *label_name, char label_type, errmsg("label name is invalid"))); } - cache_data = search_graph_name_cache(graph_name); + cache_data = search_graph_name_cache_cached(graph_name); if (!cache_data) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), errmsg("graph \"%s\" does not exist", graph_name))); } + + return create_label_with_graph_cache(graph_name, label_name, label_type, + parents, cache_data); +} + +Oid create_label_with_graph_oid(char *graph_name, Oid graph_oid, + char *label_name, char label_type, + List *parents) +{ + graph_cache_data *cache_data; + + if (!is_valid_label_name(label_name, label_type)) + { + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("label name is invalid"))); + } + + cache_data = search_graph_namespace_cache_cached(graph_oid); + if (!cache_data || strcmp(NameStr(cache_data->name), graph_name) != 0) + { + cache_data = search_graph_name_cache_cached(graph_name); + } + if (!cache_data || cache_data->oid != graph_oid) + { + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("graph \"%s\" does not exist", graph_name))); + } + + return create_label_with_graph_cache(graph_name, label_name, label_type, + parents, cache_data); +} + +static Oid create_label_with_graph_cache(char *graph_name, char *label_name, + char label_type, List *parents, + graph_cache_data *cache_data) +{ + Oid graph_oid; + Oid nsp_id; + char *schema_name; + char *rel_name; + char *seq_name; + RangeVar *seq_range_var; + int32 label_id; + Oid relation_id; + graph_oid = cache_data->oid; nsp_id = cache_data->namespace; - /* create a sequence for the new label to generate unique IDs for vertices */ - schema_name = get_namespace_name(nsp_id); + /* graph namespace names are kept in sync with graph names */ + schema_name = pstrdup(NameStr(cache_data->name)); rel_name = gen_label_relation_name(label_name); seq_name = ChooseRelationName(rel_name, "id", "seq", nsp_id, false); seq_range_var = makeRangeVar(schema_name, seq_name, -1); @@ -356,6 +408,8 @@ void create_label(char *graph_name, char *label_name, char label_type, relation_id, seq_name); CommandCounterIncrement(); + + return relation_id; } /* @@ -623,14 +677,16 @@ static void create_sequence_for_label(RangeVar *seq_range_var) /* greater than MAXINT8LEN+1 */ char buf[32]; DefElem *maxvalue; + int len; pstate = make_parsestate(NULL); pstate->p_sourcetext = "(generated CREATE SEQUENCE command)"; seq_stmt = makeNode(CreateSeqStmt); seq_stmt->sequence = seq_range_var; - pg_lltoa(ENTRY_ID_MAX, buf); - maxvalue = makeDefElem("maxvalue", (Node *)makeFloat(pstrdup(buf)), -1); + len = pg_lltoa(ENTRY_ID_MAX, buf); + maxvalue = makeDefElem("maxvalue", (Node *)makeFloat(pnstrdup(buf, len)), + -1); seq_stmt->options = list_make1(maxvalue); seq_stmt->ownerId = InvalidOid; seq_stmt->for_identity = false; @@ -887,10 +943,9 @@ Datum drop_label(PG_FUNCTION_ARGS) bool force; char *graph_name_str; graph_cache_data *cache_data; + label_cache_data *label_cache; Oid graph_oid; - Oid nsp_id; char *label_name_str; - Oid label_relation; char *schema_name; char *rel_name; List *qname; @@ -910,7 +965,7 @@ Datum drop_label(PG_FUNCTION_ARGS) force = PG_GETARG_BOOL(2); graph_name_str = NameStr(*graph_name); - cache_data = search_graph_name_cache(graph_name_str); + cache_data = search_graph_name_cache_cached(graph_name_str); if (!cache_data) { ereport(ERROR, @@ -918,11 +973,11 @@ Datum drop_label(PG_FUNCTION_ARGS) errmsg("graph \"%s\" does not exist", graph_name_str))); } graph_oid = cache_data->oid; - nsp_id = cache_data->namespace; label_name_str = NameStr(*label_name); - label_relation = get_label_relation(label_name_str, graph_oid); - if (!OidIsValid(label_relation)) + label_cache = search_label_name_graph_cache_cached(label_name_str, + graph_oid); + if (label_cache == NULL) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), @@ -935,25 +990,9 @@ Datum drop_label(PG_FUNCTION_ARGS) errmsg("force option is not supported yet"))); } - /* validate schema_name */ - schema_name = get_namespace_name(nsp_id); - if (schema_name == NULL) - { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("schema_name not found for namespace id \"%d\"", - nsp_id))); - } + schema_name = pstrdup(NameStr(cache_data->name)); - /* validate rel_name */ - rel_name = get_rel_name(label_relation); - if (rel_name == NULL) - { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("rel_name not found for label \"%s\"", - label_name_str))); - } + rel_name = pstrdup(get_label_cache_relation_name(label_cache)); /* build qualified name */ qname = list_make2(makeString(schema_name), makeString(rel_name)); diff --git a/src/backend/executor/cypher_create.c b/src/backend/executor/cypher_create.c index 876b6f250..354ea5911 100644 --- a/src/backend/executor/cypher_create.c +++ b/src/backend/executor/cypher_create.c @@ -65,6 +65,7 @@ static void begin_cypher_create(CustomScanState *node, EState *estate, (cypher_create_custom_scan_state *)node; ListCell *lc; Plan *subplan; + int result_rtindex; Assert(list_length(css->cs->custom_plans) == 1); @@ -84,6 +85,10 @@ static void begin_cypher_create(CustomScanState *node, EState *estate, ExecAssignProjectionInfo(&node->ss.ps, tupdesc); } + result_rtindex = list_length(estate->es_range_table); + css->result_rel_info_cache = + create_entity_result_rel_info_cache("create_result_rel_info_cache"); + foreach (lc, css->pattern) { cypher_create_path *path = lfirst(lc); @@ -97,17 +102,10 @@ static void begin_cypher_create(CustomScanState *node, EState *estate, if (!CYPHER_TARGET_NODE_INSERT_ENTITY(cypher_node->flags)) continue; - /* Open relation and acquire a row exclusive lock. */ - rel = table_open(cypher_node->relid, RowExclusiveLock); - - /* Initialize resultRelInfo for the vertex */ - cypher_node->resultRelInfo = makeNode(ResultRelInfo); - InitResultRelInfo(cypher_node->resultRelInfo, rel, - list_length(estate->es_range_table), NULL, - estate->es_instrument); - - /* Open all indexes for the relation */ - ExecOpenIndices(cypher_node->resultRelInfo, false); + cypher_node->resultRelInfo = get_entity_result_rel_info( + estate, css->result_rel_info_cache, cypher_node->relid); + rel = cypher_node->resultRelInfo->ri_RelationDesc; + cypher_node->resultRelInfo->ri_RangeTableIndex = result_rtindex; /* Setup the relation's tuple slot */ cypher_node->elemTupleSlot = table_slot_create( @@ -145,6 +143,11 @@ static void begin_cypher_create(CustomScanState *node, EState *estate, estate->es_output_cid = estate->es_snapshot->curcid; } + css->entity_exists_index_cache = + create_entity_exists_index_cache("create_entity_exists_index_cache"); + css->entity_exists_label_relation_cache = + create_label_relation_cache("create_entity_exists_label_relation_cache"); + Increment_Estate_CommandId(estate); } @@ -181,12 +184,14 @@ static void process_pattern(cypher_create_custom_scan_state *css) ps = css->css.ss.ps.lefttree; scantuple = ps->ps_ExprContext->ecxt_scantuple; - result = make_path(css->path_values); + result = make_path_with_length(css->path_values, + path->path_length); scantuple->tts_values[path->path_attr_num - 1] = result; scantuple->tts_isnull[path->path_attr_num - 1] = false; } + list_free(css->path_values); css->path_values = NIL; } } @@ -199,7 +204,8 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *node) ExprContext *econtext = css->css.ss.ps.ps_ExprContext; TupleTableSlot *slot; bool terminal = CYPHER_CLAUSE_IS_TERMINAL(css->flags); - bool used = false; + + css->graph_mutated = false; /* * If the CREATE clause was the final cypher clause written then we aren't @@ -226,23 +232,13 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *node) process_pattern(css); - /* - * This may not be necessary. If we have an empty pattern, nothing was - * inserted and the current command Id was not used. So, only flag it - * if there is a non empty pattern. - */ - if (list_length(css->pattern) > 0) - { - /* the current command Id has been used */ - used = true; - } } while (terminal); /* * If the current command Id wasn't used, nothing was inserted and we're * done. */ - if (!used) + if (!css->graph_mutated) { return NULL; } @@ -274,6 +270,18 @@ static void end_cypher_create(CustomScanState *node) ExecEndNode(node->ss.ps.lefttree); + if (css->entity_exists_index_cache != NULL) + { + destroy_entity_exists_index_cache(css->entity_exists_index_cache); + css->entity_exists_index_cache = NULL; + } + + if (css->entity_exists_label_relation_cache != NULL) + { + destroy_label_relation_cache(css->entity_exists_label_relation_cache); + css->entity_exists_label_relation_cache = NULL; + } + foreach (lc, css->pattern) { cypher_create_path *path = lfirst(lc); @@ -289,14 +297,15 @@ static void end_cypher_create(CustomScanState *node) continue; } - /* close all indices for the node */ - ExecCloseIndices(cypher_node->resultRelInfo); - - /* close the relation itself */ - table_close(cypher_node->resultRelInfo->ri_RelationDesc, - RowExclusiveLock); + cypher_node->resultRelInfo = NULL; } } + + if (css->result_rel_info_cache != NULL) + { + destroy_entity_result_rel_info_cache(css->result_rel_info_cache); + css->result_rel_info_cache = NULL; + } } static void rescan_cypher_create(CustomScanState *node) @@ -419,6 +428,7 @@ static void create_edge(cypher_create_custom_scan_state *css, /* Insert the new edge */ insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); + css->graph_mutated = true; /* restore the old result relation info */ estate->es_result_relations = old_estate_es_result_relations; @@ -506,6 +516,7 @@ static Datum create_vertex(cypher_create_custom_scan_state *css, /* Insert the new vertex */ insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); + css->graph_mutated = true; /* restore the old result relation info */ estate->es_result_relations = old_estate_es_result_relations; @@ -550,8 +561,10 @@ static Datum create_vertex(cypher_create_custom_scan_state *css, else { agtype *a; - agtype_value *v; + agtype_value v; agtype_value *id_value; + bool v_needs_free = false; + bool v_found; TupleTableSlot *scantuple; PlanState *ps; @@ -562,21 +575,32 @@ static Datum create_vertex(cypher_create_custom_scan_state *css, a = DATUM_GET_AGTYPE_P(scantuple->tts_values[node->tuple_position - 1]); /* Convert to an agtype value */ - v = get_ith_agtype_value_from_container(&a->root, 0); + v_found = get_ith_agtype_value_from_container_no_copy(&a->root, 0, &v, + &v_needs_free); + Assert(v_found); - if (v->type != AGTV_VERTEX) + if (v.type != AGTV_VERTEX) { + if (v_needs_free) + { + pfree_agtype_value_content(&v); + } ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("agtype must resolve to a vertex"))); } /* extract the id agtype field */ - id_value = GET_AGTYPE_VALUE_OBJECT_VALUE(v, "id"); + id_value = AGTYPE_VERTEX_GET_ID(&v); /* extract the graphid and cast to a Datum */ id = GRAPHID_GET_DATUM(id_value->val.int_value); + if (v_needs_free) + { + pfree_agtype_value_content(&v); + } + /* * Its possible the variable has already been deleted. There are two * ways this can happen. One is the query explicitly deleted the @@ -590,7 +614,10 @@ static Datum create_vertex(cypher_create_custom_scan_state *css, */ if (!SAFE_TO_SKIP_EXISTENCE_CHECK(node->flags)) { - if (!entity_exists(estate, css->graph_oid, DATUM_GET_GRAPHID(id))) + if (!entity_exists_with_cache(estate, css->graph_oid, + DATUM_GET_GRAPHID(id), + css->entity_exists_index_cache, + css->entity_exists_label_relation_cache)) { ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), @@ -615,4 +642,3 @@ static Datum create_vertex(cypher_create_custom_scan_state *css, return id; } - diff --git a/src/backend/executor/cypher_delete.c b/src/backend/executor/cypher_delete.c index 19eca3ad6..934386254 100644 --- a/src/backend/executor/cypher_delete.c +++ b/src/backend/executor/cypher_delete.c @@ -29,6 +29,7 @@ #include "catalog/ag_label.h" #include "executor/cypher_executor.h" #include "utils/age_global_graph.h" +#include "utils/ag_cache.h" #include "executor/cypher_utils.h" static void begin_cypher_delete(CustomScanState *node, EState *estate, @@ -40,11 +41,23 @@ static void rescan_cypher_delete(CustomScanState *node); static void process_delete_list(CustomScanState *node); static void check_for_connected_edges(CustomScanState *node); -static agtype_value *extract_entity(CustomScanState *node, - TupleTableSlot *scanTupleSlot, - int entity_position); -static void delete_entity(EState *estate, ResultRelInfo *resultRelInfo, +static void ensure_detach_delete_rls(CustomScanState *node, + ResultRelInfo *resultRelInfo, + Oid relid, bool *rls_checked, + bool *rls_enabled, List **qualExprs, + ExprContext **econtext); +static void extract_entity(CustomScanState *node, + TupleTableSlot *scanTupleSlot, + int entity_position, + enum agtype_value_type *entity_type, + graphid *entity_id); +static bool delete_entity(EState *estate, ResultRelInfo *resultRelInfo, HeapTuple tuple); +static void init_delete_caches(cypher_delete_custom_scan_state *css); +static HTAB *create_delete_vertex_htab(void); +static int *ensure_delete_item_positions(EState *estate, + cypher_delete_information *delete_data, + int *delete_item_count); const CustomExecMethods cypher_delete_exec_methods = {DELETE_SCAN_STATE_NAME, begin_cypher_delete, @@ -72,7 +85,6 @@ static void begin_cypher_delete(CustomScanState *node, EState *estate, cypher_delete_custom_scan_state *css = (cypher_delete_custom_scan_state *)node; Plan *subplan; - HASHCTL hashctl; Assert(list_length(css->cs->custom_plans) == 1); @@ -95,22 +107,7 @@ static void begin_cypher_delete(CustomScanState *node, EState *estate, ExecAssignProjectionInfo(&node->ss.ps, tupdesc); } - /* - * Get all the labels that are visible to this delete clause at this point - * in the transaction. To be used later when the delete clause finds - * vertices. - */ - css->edge_labels = get_all_edge_labels_per_graph(estate, css->delete_data->graph_oid); - - /* init vertex_id_htab */ - MemSet(&hashctl, 0, sizeof(hashctl)); - hashctl.keysize = sizeof(graphid); - hashctl.entrysize = - sizeof(graphid); /* entries are not used, but entrysize must >= keysize */ - hashctl.hash = tag_hash; - css->vertex_id_htab = hash_create(DELETE_VERTEX_HTAB_NAME, - DELETE_VERTEX_HTAB_SIZE, &hashctl, - HASH_ELEM | HASH_FUNCTION); + init_delete_caches(css); /* * Postgres does not assign the es_output_cid in queries that do @@ -200,13 +197,123 @@ static void end_cypher_delete(CustomScanState *node) check_for_connected_edges(node); /* invalidate VLE cache — graph was mutated */ - increment_graph_version(css->delete_data->graph_oid); + if (css->graph_mutated) + { + increment_graph_version(css->delete_data->graph_oid); + } - hash_destroy(((cypher_delete_custom_scan_state *)node)->vertex_id_htab); + if (css->qual_cache != NULL) + { + hash_destroy(css->qual_cache); + css->qual_cache = NULL; + } + + if (css->index_cache != NULL) + { + destroy_index_cache(css->index_cache, false); + css->index_cache = NULL; + } + + if (css->result_rel_info_cache != NULL) + { + destroy_entity_result_rel_info_cache(css->result_rel_info_cache); + css->result_rel_info_cache = NULL; + } + + if (css->label_relation_cache != NULL) + { + destroy_label_relation_cache(css->label_relation_cache); + css->label_relation_cache = NULL; + } + + if (css->vertex_id_htab != NULL) + { + hash_destroy(css->vertex_id_htab); + css->vertex_id_htab = NULL; + } ExecEndNode(node->ss.ps.lefttree); } +static void init_delete_caches(cypher_delete_custom_scan_state *css) +{ + HASHCTL hashctl; + HASHCTL idx_hashctl; + + MemSet(&hashctl, 0, sizeof(hashctl)); + hashctl.keysize = sizeof(Oid); + hashctl.entrysize = sizeof(RLSCacheEntry); + hashctl.hcxt = CurrentMemoryContext; + css->qual_cache = hash_create("delete_qual_cache", 8, &hashctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + + MemSet(&idx_hashctl, 0, sizeof(idx_hashctl)); + idx_hashctl.keysize = sizeof(Oid); + idx_hashctl.entrysize = sizeof(IndexCacheEntry); + idx_hashctl.hcxt = CurrentMemoryContext; + css->index_cache = hash_create("delete_index_cache", 8, &idx_hashctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + + css->result_rel_info_cache = + create_entity_result_rel_info_cache("delete_result_rel_info_cache"); + css->label_relation_cache = + create_label_relation_cache("delete_label_relation_cache"); +} + +static HTAB *create_delete_vertex_htab(void) +{ + HASHCTL hashctl; + + MemSet(&hashctl, 0, sizeof(hashctl)); + hashctl.keysize = sizeof(graphid); + hashctl.entrysize = + sizeof(graphid); /* entries are not used, but entrysize must >= keysize */ + hashctl.hash = tag_hash; + + return hash_create(DELETE_VERTEX_HTAB_NAME, + DELETE_VERTEX_HTAB_INITIAL_SIZE, + &hashctl, HASH_ELEM | HASH_FUNCTION); +} + +static int *ensure_delete_item_positions(EState *estate, + cypher_delete_information *delete_data, + int *delete_item_count) +{ + ListCell *lc; + int count; + int index = 0; + + if (delete_data->delete_item_positions_valid) + { + *delete_item_count = delete_data->delete_item_count; + return delete_data->delete_item_positions; + } + + count = delete_data->delete_item_count; + Assert(count == list_length(delete_data->delete_items)); + + if (delete_data->delete_item_positions != NULL) + { + pfree(delete_data->delete_item_positions); + } + + delete_data->delete_item_positions = count > 0 ? + MemoryContextAlloc(estate->es_query_cxt, sizeof(int) * count) : NULL; + foreach(lc, delete_data->delete_items) + { + cypher_delete_item *item = lfirst(lc); + + delete_data->delete_item_positions[index++] = + item->entity_position->ival; + } + + Assert(index == count); + delete_data->delete_item_positions_valid = true; + + *delete_item_count = count; + return delete_data->delete_item_positions; +} + /* * Used for rewinding the scan state and reprocessing the results. * @@ -257,13 +364,17 @@ Node *create_cypher_delete_plan_state(CustomScan *cscan) * Extract the vertex or edge to be deleted, perform some type checking to * validate datum is an agtype vertex or edge. */ -static agtype_value *extract_entity(CustomScanState *node, - TupleTableSlot *scanTupleSlot, - int entity_position) +static void extract_entity(CustomScanState *node, + TupleTableSlot *scanTupleSlot, + int entity_position, + enum agtype_value_type *entity_type, + graphid *entity_id) { - agtype_value *original_entity_value; + agtype_value original_entity_value; + agtype_value *id; agtype *original_entity; TupleDesc tupleDescriptor; + bool entity_needs_free = false; tupleDescriptor = scanTupleSlot->tts_tupleDescriptor; @@ -272,21 +383,39 @@ static agtype_value *extract_entity(CustomScanState *node, ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("DELETE clause can only delete agtype"))); - original_entity = DATUM_GET_AGTYPE_P(scanTupleSlot->tts_values[entity_position - 1]); - original_entity_value = get_ith_agtype_value_from_container(&original_entity->root, 0); + original_entity = + DATUM_GET_AGTYPE_P(scanTupleSlot->tts_values[entity_position - 1]); + (void)get_ith_agtype_value_from_container_no_copy(&original_entity->root, + 0, + &original_entity_value, + &entity_needs_free); - if (original_entity_value->type != AGTV_VERTEX && original_entity_value->type != AGTV_EDGE) + if (original_entity_value.type != AGTV_VERTEX && + original_entity_value.type != AGTV_EDGE) + { + if (entity_needs_free) + pfree_agtype_value_content(&original_entity_value); ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("DELETE clause can only delete vertices and edges"))); + } + + if (original_entity_value.type == AGTV_VERTEX) + id = AGTYPE_VERTEX_GET_ID(&original_entity_value); + else + id = AGTYPE_EDGE_GET_ID(&original_entity_value); + + *entity_type = original_entity_value.type; + *entity_id = id->val.int_value; - return original_entity_value; + if (entity_needs_free) + pfree_agtype_value_content(&original_entity_value); } /* * Try and delete the entity that is describe by the HeapTuple in the table * described by the resultRelInfo. */ -static void delete_entity(EState *estate, ResultRelInfo *resultRelInfo, +static bool delete_entity(EState *estate, ResultRelInfo *resultRelInfo, HeapTuple tuple) { ResultRelInfo **saved_resultRels; @@ -295,6 +424,8 @@ static void delete_entity(EState *estate, ResultRelInfo *resultRelInfo, TM_Result lock_result; TM_Result delete_result; Buffer buffer; + CommandId current_cid; + bool deleted = false; /* Find the physical tuple, this variable is coming from */ saved_resultRels = estate->es_result_relations; @@ -328,6 +459,7 @@ static void delete_entity(EState *estate, ResultRelInfo *resultRelInfo, switch (delete_result) { case TM_Ok: + deleted = true; break; case TM_SelfModified: ereport( @@ -353,8 +485,9 @@ static void delete_entity(EState *estate, ResultRelInfo *resultRelInfo, CommandCounterIncrement(); /* Update command id in estate */ - estate->es_snapshot->curcid = GetCurrentCommandId(false); - estate->es_output_cid = GetCurrentCommandId(false); + current_cid = GetCurrentCommandId(false); + estate->es_snapshot->curcid = current_cid; + estate->es_output_cid = current_cid; } else if (lock_result != TM_Invisible && lock_result != TM_SelfModified) { @@ -367,6 +500,8 @@ static void delete_entity(EState *estate, ResultRelInfo *resultRelInfo, ReleaseBuffer(buffer); estate->es_result_relations = saved_resultRels; + + return deleted; } /* @@ -377,42 +512,29 @@ static void process_delete_list(CustomScanState *node) { cypher_delete_custom_scan_state *css = (cypher_delete_custom_scan_state *)node; - ListCell *lc; ExprContext *econtext = css->css.ss.ps.ps_ExprContext; TupleTableSlot *scanTupleSlot = econtext->ecxt_scantuple; EState *estate = node->ss.ps.state; - HTAB *qual_cache = NULL; - HASHCTL hashctl; - HTAB *index_cache = NULL; - HASHCTL idx_hashctl; - - /* Hash table for caching compiled security quals per label */ - MemSet(&hashctl, 0, sizeof(hashctl)); - hashctl.keysize = sizeof(Oid); - hashctl.entrysize = sizeof(RLSCacheEntry); - hashctl.hcxt = CurrentMemoryContext; - qual_cache = hash_create("delete_qual_cache", 8, &hashctl, - HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + HTAB *qual_cache = css->qual_cache; + HTAB *index_cache = css->index_cache; + int delete_item_count; + int *delete_item_positions; + int i; - MemSet(&idx_hashctl, 0, sizeof(idx_hashctl)); - idx_hashctl.keysize = sizeof(Oid); - idx_hashctl.entrysize = sizeof(IndexCacheEntry); - idx_hashctl.hcxt = CurrentMemoryContext; - index_cache = hash_create("delete_index_cache", 8, &idx_hashctl, - HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + delete_item_positions = ensure_delete_item_positions( + estate, css->delete_data, &delete_item_count); - foreach(lc, css->delete_data->delete_items) + for (i = 0; i < delete_item_count; i++) { - cypher_delete_item *item; - agtype_value *original_entity_value, *id, *label; + enum agtype_value_type entity_type; + graphid entity_id; ScanKeyData scan_keys[1]; TableScanDesc scan_desc = NULL; ResultRelInfo *resultRelInfo; HeapTuple heap_tuple = NULL; - char *label_name; - Integer *pos; int entity_position; Oid relid; + Oid label_relation; Relation rel; int id_attr_num; Oid index_oid = InvalidOid; @@ -421,25 +543,34 @@ static void process_delete_list(CustomScanState *node) IndexScanDesc index_scan_desc = NULL; bool shouldFree = false; IndexCacheEntry *idx_entry; - bool found_idx_entry; - - item = lfirst(lc); + bool found_idx_entry; + RLSCacheEntry *rls_entry; + bool found_rls_entry; + int32 label_id; - pos = item->entity_position; - entity_position = pos->ival; + entity_position = delete_item_positions[i]; /* skip if the entity is null */ if (scanTupleSlot->tts_isnull[entity_position - 1]) continue; - original_entity_value = extract_entity(node, scanTupleSlot, - entity_position); + extract_entity(node, scanTupleSlot, entity_position, &entity_type, + &entity_id); + label_id = GET_LABEL_ID(entity_id); - id = GET_AGTYPE_VALUE_OBJECT_VALUE(original_entity_value, "id"); - label = GET_AGTYPE_VALUE_OBJECT_VALUE(original_entity_value, "label"); - label_name = pnstrdup(label->val.string.val, label->val.string.len); + if (!get_label_relation_from_cache(css->label_relation_cache, + css->delete_data->graph_oid, + label_id, &label_relation, NULL)) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("label id %d does not exist", + label_id))); + } - resultRelInfo = create_entity_result_rel_info(estate, css->delete_data->graph_name, label_name); + resultRelInfo = get_entity_result_rel_info(estate, + css->result_rel_info_cache, + label_relation); rel = resultRelInfo->ri_RelationDesc; relid = RelationGetRelid(rel); @@ -447,19 +578,19 @@ static void process_delete_list(CustomScanState *node) * Setup the scan key to require the id field on-disc to match the * entity's graphid. */ - if (original_entity_value->type == AGTV_VERTEX) + if (entity_type == AGTV_VERTEX) { id_attr_num = Anum_ag_label_vertex_table_id; ScanKeyInit(&scan_keys[0], Anum_ag_label_vertex_table_id, BTEqualStrategyNumber, F_GRAPHIDEQ, - GRAPHID_GET_DATUM(id->val.int_value)); + GRAPHID_GET_DATUM(entity_id)); } - else if (original_entity_value->type == AGTV_EDGE) + else if (entity_type == AGTV_EDGE) { id_attr_num = Anum_ag_label_edge_table_id; ScanKeyInit(&scan_keys[0], Anum_ag_label_edge_table_id, BTEqualStrategyNumber, F_GRAPHIDEQ, - GRAPHID_GET_DATUM(id->val.int_value)); + GRAPHID_GET_DATUM(entity_id)); } else { @@ -467,24 +598,60 @@ static void process_delete_list(CustomScanState *node) errmsg("DELETE clause can only delete vertices and edges"))); } - idx_entry = hash_search(index_cache, &relid, HASH_ENTER, &found_idx_entry); - + idx_entry = hash_search(index_cache, &relid, HASH_ENTER, + &found_idx_entry); if (!found_idx_entry) + { + init_index_cache_entry(idx_entry); + } + + if (!idx_entry->index_oid_cached) { idx_entry->index_oid = find_usable_btree_index_for_attr(rel, id_attr_num); + idx_entry->index_oid_cached = true; } index_oid = idx_entry->index_oid; + rls_entry = hash_search(qual_cache, &relid, HASH_ENTER, + &found_rls_entry); + if (!found_rls_entry) + { + rls_entry->rls_enabled = + check_enable_rls(relid, InvalidOid, true) == RLS_ENABLED; + + if (rls_entry->rls_enabled) + { + rls_entry->qualExprs = setup_security_quals(resultRelInfo, + estate, node, + CMD_DELETE); + rls_entry->slot = ExecInitExtraTupleSlot( + estate, RelationGetDescr(rel), &TTSOpsHeapTuple); + } + } + /* * Setup the scan description, with the correct snapshot and scan keys. */ - estate->es_snapshot->curcid = GetCurrentCommandId(false); - estate->es_output_cid = GetCurrentCommandId(false); + { + CommandId current_cid = GetCurrentCommandId(false); + + estate->es_snapshot->curcid = current_cid; + estate->es_output_cid = current_cid; + } if (OidIsValid(index_oid)) { - slot = table_slot_create(rel, NULL); + slot = idx_entry->slot; + if (slot == NULL) + { + slot = table_slot_create(rel, NULL); + idx_entry->slot = slot; + } + else + { + ExecClearTuple(slot); + } index_rel = index_open(index_oid, RowExclusiveLock); index_scan_desc = index_beginscan(rel, index_rel, estate->es_snapshot, NULL, 1, 0); @@ -507,21 +674,12 @@ static void process_delete_list(CustomScanState *node) bool passed_rls = true; /* Check RLS security quals (USING policy) before delete */ - if (check_enable_rls(relid, InvalidOid, true) == RLS_ENABLED) + if (rls_entry->rls_enabled) { - RLSCacheEntry *entry; - bool found_rls; + ExecStoreHeapTuple(heap_tuple, rls_entry->slot, false); - entry = hash_search(qual_cache, &relid, HASH_ENTER, &found_rls); - if (!found_rls) - { - entry->qualExprs = setup_security_quals(resultRelInfo, estate, node, CMD_DELETE); - entry->slot = ExecInitExtraTupleSlot(estate, RelationGetDescr(rel), &TTSOpsHeapTuple); - } - - ExecStoreHeapTuple(heap_tuple, entry->slot, false); - - if (!check_security_quals(entry->qualExprs, entry->slot, econtext)) + if (!check_security_quals(rls_entry->qualExprs, + rls_entry->slot, econtext)) { passed_rls = false; } @@ -529,20 +687,28 @@ static void process_delete_list(CustomScanState *node) if (passed_rls) { - /* - * For vertices, we insert the vertex ID in the hashtable - * vertex_id_htab. This hashtable is used later to process - * connected edges. - */ - if (original_entity_value->type == AGTV_VERTEX) + /* At this point, we are ready to delete the node/vertex. */ + if (delete_entity(estate, resultRelInfo, heap_tuple)) { - bool found; - hash_search(css->vertex_id_htab, (void *)&(id->val.int_value), - HASH_ENTER, &found); - } + css->graph_mutated = true; + /* + * For vertices, store successfully deleted IDs for the + * later connected-edge pass. + */ + if (entity_type == AGTV_VERTEX) + { + bool found; - /* At this point, we are ready to delete the node/vertex. */ - delete_entity(estate, resultRelInfo, heap_tuple); + if (css->vertex_id_htab == NULL) + { + css->vertex_id_htab = create_delete_vertex_htab(); + } + + hash_search(css->vertex_id_htab, + (void *)&entity_id, + HASH_ENTER, &found); + } + } } if (shouldFree) @@ -553,7 +719,7 @@ static void process_delete_list(CustomScanState *node) if (OidIsValid(index_oid)) { - ExecDropSingleTupleTableSlot(slot); + ExecClearTuple(slot); index_endscan(index_scan_desc); index_close(index_rel, RowExclusiveLock); } @@ -562,12 +728,8 @@ static void process_delete_list(CustomScanState *node) table_endscan(scan_desc); } - destroy_entity_result_rel_info(resultRelInfo); } - /* Clean up the cache */ - hash_destroy(qual_cache); - hash_destroy(index_cache); } /* @@ -578,13 +740,16 @@ static void process_edges_by_index(Oid index_oid, Relation rel, EState *estate, cypher_delete_custom_scan_state *css, + IndexCacheEntry *idx_entry, ResultRelInfo *resultRelInfo, Oid relid, char *label_name, - bool rls_enabled, - List *qualExprs, - ExprContext *econtext, - bool is_pass_two) + bool *rls_checked, + bool *rls_enabled, + List **qualExprs, + ExprContext **econtext, + bool is_pass_two, + bool *delete_acl_checked) { HASH_SEQ_STATUS hash_status; graphid *vid; @@ -593,7 +758,16 @@ static void process_edges_by_index(Oid index_oid, ScanKeyData key; TupleTableSlot *slot; - slot = table_slot_create(rel, NULL); + slot = idx_entry->slot; + if (slot == NULL) + { + slot = table_slot_create(rel, NULL); + idx_entry->slot = slot; + } + else + { + ExecClearTuple(slot); + } index_rel = index_open(index_oid, RowExclusiveLock); scan = index_beginscan(rel, index_rel, estate->es_snapshot, NULL, 1, 0); @@ -630,21 +804,31 @@ static void process_edges_by_index(Oid index_oid, /* If edge found - delete it (or error if not DETACH) */ if (css->delete_data->detach) { - AclResult aclresult; bool shouldFree; HeapTuple tuple; - /* Check that the user has DELETE permission on the edge table */ - aclresult = pg_class_aclcheck(relid, GetUserId(), ACL_DELETE); - if (aclresult != ACLCHECK_OK) + if (!*delete_acl_checked) { - aclcheck_error(aclresult, OBJECT_TABLE, label_name); + AclResult aclresult; + + aclresult = pg_class_aclcheck(relid, GetUserId(), + ACL_DELETE); + if (aclresult != ACLCHECK_OK) + { + aclcheck_error(aclresult, OBJECT_TABLE, label_name); + } + + *delete_acl_checked = true; } + ensure_detach_delete_rls(&css->css, resultRelInfo, relid, + rls_checked, rls_enabled, qualExprs, + econtext); + /* Check RLS security quals (USING policy) before delete */ - if (rls_enabled) + if (*rls_enabled) { - if (!check_security_quals(qualExprs, slot, econtext)) + if (!check_security_quals(*qualExprs, slot, *econtext)) { ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), @@ -655,7 +839,10 @@ static void process_edges_by_index(Oid index_oid, } tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree); - delete_entity(estate, resultRelInfo, tuple); + if (delete_entity(estate, resultRelInfo, tuple)) + { + css->graph_mutated = true; + } if (shouldFree) { @@ -674,7 +861,29 @@ static void process_edges_by_index(Oid index_oid, } index_endscan(scan); index_close(index_rel, RowExclusiveLock); - ExecDropSingleTupleTableSlot(slot); + ExecClearTuple(slot); +} + +static void ensure_detach_delete_rls(CustomScanState *node, + ResultRelInfo *resultRelInfo, + Oid relid, bool *rls_checked, + bool *rls_enabled, List **qualExprs, + ExprContext **econtext) +{ + if (*rls_checked) + { + return; + } + + *rls_checked = true; + + if (check_enable_rls(relid, InvalidOid, true) == RLS_ENABLED) + { + *rls_enabled = true; + *econtext = node->ss.ps.ps_ExprContext; + *qualExprs = setup_security_quals(resultRelInfo, node->ss.ps.state, + node, CMD_DELETE); + } } /* @@ -688,73 +897,113 @@ static void check_for_connected_edges(CustomScanState *node) cypher_delete_custom_scan_state *css = (cypher_delete_custom_scan_state *)node; EState *estate = css->css.ss.ps.state; - char *graph_name = css->delete_data->graph_name; + + if (css->vertex_id_htab == NULL || + hash_get_num_entries(css->vertex_id_htab) == 0) + { + return; + } + + /* + * Edge labels are only needed after at least one vertex was deleted. + * Edge-only DELETEs and empty inputs can skip the catalog lookup entirely. + */ + if (css->edge_labels == NIL) + { + css->edge_labels = get_all_edge_labels_per_graph(estate->es_snapshot, + css->delete_data->graph_oid); + } /* scans each label from css->edge_labels */ foreach (lc, css->edge_labels) { - char *label_name = lfirst(lc); + ag_label_info *label_info = lfirst(lc); + char *label_name = label_info->name; ResultRelInfo *resultRelInfo; TableScanDesc scan_desc; HeapTuple tuple; TupleTableSlot *slot; Oid relid; + bool rls_checked = false; bool rls_enabled = false; + bool delete_acl_checked = false; List *qualExprs = NIL; ExprContext *econtext = NULL; - Oid start_index_oid = InvalidOid; - Oid end_index_oid = InvalidOid; + Oid start_index_oid; + Oid end_index_oid; Relation rel; + IndexCacheEntry *idx_entry; + bool found_idx_entry; - resultRelInfo = create_entity_result_rel_info(estate, graph_name, - label_name); + resultRelInfo = get_entity_result_rel_info(estate, + css->result_rel_info_cache, + label_info->relation); rel = resultRelInfo->ri_RelationDesc; relid = RelationGetRelid(rel); - estate->es_snapshot->curcid = GetCurrentCommandId(false); - estate->es_output_cid = GetCurrentCommandId(false); + { + CommandId current_cid = GetCurrentCommandId(false); - /* - * For DETACH DELETE with RLS enabled, compile the security qual - * expressions once per label for efficient evaluation. - */ - if (css->delete_data->detach) + estate->es_snapshot->curcid = current_cid; + estate->es_output_cid = current_cid; + } + + idx_entry = hash_search(css->index_cache, &relid, HASH_ENTER, + &found_idx_entry); + if (!found_idx_entry) { - /* Setup RLS security quals for this label */ - if (check_enable_rls(relid, InvalidOid, true) == RLS_ENABLED) - { - rls_enabled = true; - econtext = css->css.ss.ps.ps_ExprContext; - qualExprs = setup_security_quals(resultRelInfo, estate, node, - CMD_DELETE); - } + init_index_cache_entry(idx_entry); + } + if (!idx_entry->start_index_oid_cached) + { + idx_entry->start_index_oid = find_usable_btree_index_for_attr( + rel, Anum_ag_label_edge_table_start_id); + idx_entry->start_index_oid_cached = true; + } + if (!idx_entry->end_index_oid_cached) + { + idx_entry->end_index_oid = find_usable_btree_index_for_attr( + rel, Anum_ag_label_edge_table_end_id); + idx_entry->end_index_oid_cached = true; } - /* Look for indexes on start_id and end_id columns. */ - start_index_oid = find_usable_btree_index_for_attr(rel, Anum_ag_label_edge_table_start_id); - end_index_oid = find_usable_btree_index_for_attr(rel, Anum_ag_label_edge_table_end_id); + start_index_oid = idx_entry->start_index_oid; + end_index_oid = idx_entry->end_index_oid; if (OidIsValid(start_index_oid) && OidIsValid(end_index_oid)) { /* PASS 1: Find edges where the deleted vertex is the START_ID. */ - process_edges_by_index(start_index_oid, rel, estate, css, resultRelInfo, - relid, label_name, rls_enabled, qualExprs, econtext, false); + process_edges_by_index(start_index_oid, rel, estate, css, + idx_entry, resultRelInfo, relid, + label_name, &rls_checked, + &rls_enabled, &qualExprs, &econtext, false, + &delete_acl_checked); /* PASS 2: Find edges where the deleted vertex is the END_ID. */ - process_edges_by_index(end_index_oid, rel, estate, css, resultRelInfo, - relid, label_name, rls_enabled, qualExprs, econtext, true); + process_edges_by_index(end_index_oid, rel, estate, css, + idx_entry, resultRelInfo, relid, + label_name, &rls_checked, + &rls_enabled, &qualExprs, &econtext, true, + &delete_acl_checked); } else { scan_desc = table_beginscan(rel, estate->es_snapshot, 0, NULL); - slot = ExecInitExtraTupleSlot( - estate, RelationGetDescr(rel), - &TTSOpsHeapTuple); + slot = idx_entry->slot; + if (slot == NULL) + { + slot = ExecInitExtraTupleSlot(estate, RelationGetDescr(rel), + &TTSOpsHeapTuple); + idx_entry->slot = slot; + } + else + { + ExecClearTuple(slot); + } /* for each row */ while (true) { graphid startid; - graphid endid; bool isNull; bool found_startid = false; bool found_endid = false; @@ -771,14 +1020,16 @@ static void check_for_connected_edges(CustomScanState *node) startid = GRAPHID_GET_DATUM(slot_getattr( slot, Anum_ag_label_edge_table_start_id, &isNull)); - endid = GRAPHID_GET_DATUM( - slot_getattr(slot, Anum_ag_label_edge_table_end_id, &isNull)); hash_search(css->vertex_id_htab, (void *)&startid, HASH_FIND, &found_startid); if (!found_startid) { + graphid endid; + + endid = GRAPHID_GET_DATUM(slot_getattr( + slot, Anum_ag_label_edge_table_end_id, &isNull)); hash_search(css->vertex_id_htab, (void *)&endid, HASH_FIND, &found_endid); } @@ -787,15 +1038,25 @@ static void check_for_connected_edges(CustomScanState *node) { if (css->delete_data->detach) { - AclResult aclresult; - - /* Check that the user has DELETE permission on the edge table */ - aclresult = pg_class_aclcheck(relid, GetUserId(), ACL_DELETE); - if (aclresult != ACLCHECK_OK) + if (!delete_acl_checked) { - aclcheck_error(aclresult, OBJECT_TABLE, label_name); + AclResult aclresult; + + aclresult = pg_class_aclcheck(relid, GetUserId(), + ACL_DELETE); + if (aclresult != ACLCHECK_OK) + { + aclcheck_error(aclresult, OBJECT_TABLE, + label_name); + } + + delete_acl_checked = true; } + ensure_detach_delete_rls(node, resultRelInfo, relid, + &rls_checked, &rls_enabled, + &qualExprs, &econtext); + /* Check RLS security quals (USING policy) before delete */ if (rls_enabled) { @@ -815,7 +1076,10 @@ static void check_for_connected_edges(CustomScanState *node) } } - delete_entity(estate, resultRelInfo, tuple); + if (delete_entity(estate, resultRelInfo, tuple)) + { + css->graph_mutated = true; + } } else { @@ -829,9 +1093,9 @@ static void check_for_connected_edges(CustomScanState *node) } } + ExecClearTuple(slot); table_endscan(scan_desc); } - destroy_entity_result_rel_info(resultRelInfo); } } diff --git a/src/backend/executor/cypher_merge.c b/src/backend/executor/cypher_merge.c index 2b3d1f7dd..1060aedcc 100644 --- a/src/backend/executor/cypher_merge.c +++ b/src/backend/executor/cypher_merge.c @@ -58,9 +58,17 @@ typedef struct path_entry typedef struct created_path { struct created_path *next; /* next link in linked list of path_entrys */ + struct created_path *hash_next; /* next link in the path_hash bucket */ struct path_entry **entry; /* path_entry array for this link */ + uint32 path_hash; /* cheap precheck before full path compare */ } created_path; +typedef struct created_path_hash_entry +{ + uint32 path_hash; + created_path *paths; +} created_path_hash_entry; + static void begin_cypher_merge(CustomScanState *node, EState *estate, int eflags); static TupleTableSlot *exec_cypher_merge(CustomScanState *node); @@ -82,6 +90,9 @@ static void process_path(cypher_merge_custom_scan_state *css, path_entry **path_array, bool should_insert); static void mark_tts_isnull(TupleTableSlot *slot); static void mark_scan_slot_valid(TupleTableSlot *slot); +static void apply_merge_update_list(cypher_merge_custom_scan_state *css, + cypher_update_information *set_info, + int num_set_items); const CustomExecMethods cypher_merge_exec_methods = {MERGE_SCAN_STATE_NAME, begin_cypher_merge, @@ -92,12 +103,27 @@ const CustomExecMethods cypher_merge_exec_methods = {MERGE_SCAN_STATE_NAME, NULL, NULL, NULL, NULL}; static path_entry **prebuild_path(CustomScanState *node); +static uint32 hash_path_entries(path_entry **path_array, int path_length); static bool compare_2_paths(path_entry **lhs, path_entry **rhs, int path_length); static path_entry **find_duplicate_path(CustomScanState *node, - path_entry **path_array); + path_entry **path_array, + uint32 path_hash); +static void remember_created_path(cypher_merge_custom_scan_state *css, + path_entry **path_array, + uint32 path_hash); static void free_path_entry_array(path_entry **path_array, int length); +static void apply_merge_update_list(cypher_merge_custom_scan_state *css, + cypher_update_information *set_info, + int num_set_items) +{ + if (apply_update_list(&css->css, set_info, num_set_items)) + { + css->graph_mutated = true; + } +} + /* * Initializes the MERGE Execution Node at the beginning of the execution * phase. @@ -109,6 +135,7 @@ static void begin_cypher_merge(CustomScanState *node, EState *estate, (cypher_merge_custom_scan_state *)node; ListCell *lc = NULL; Plan *subplan = NULL; + int result_rtindex; css->created_paths_list = NULL; Assert(list_length(css->cs->custom_plans) == 1); @@ -135,6 +162,10 @@ static void begin_cypher_merge(CustomScanState *node, EState *estate, ExecAssignProjectionInfo(&node->ss.ps, tupdesc); } + result_rtindex = list_length(estate->es_range_table); + css->result_rel_info_cache = + create_entity_result_rel_info_cache("merge_result_rel_info_cache"); + /* * For each vertex and edge in the path, setup the information * needed if we need to create them. @@ -156,17 +187,10 @@ static void begin_cypher_merge(CustomScanState *node, EState *estate, continue; } - /* Open relation and acquire a row exclusive lock. */ - rel = table_open(cypher_node->relid, RowExclusiveLock); - - /* Initialize resultRelInfo for the vertex */ - cypher_node->resultRelInfo = makeNode(ResultRelInfo); - InitResultRelInfo(cypher_node->resultRelInfo, rel, - list_length(estate->es_range_table), NULL, - estate->es_instrument); - - /* Open all indexes for the relation */ - ExecOpenIndices(cypher_node->resultRelInfo, false); + cypher_node->resultRelInfo = get_entity_result_rel_info( + estate, css->result_rel_info_cache, cypher_node->relid); + rel = cypher_node->resultRelInfo->ri_RelationDesc; + cypher_node->resultRelInfo->ri_RangeTableIndex = result_rtindex; /* Setup the relation's tuple slot */ cypher_node->elemTupleSlot = ExecInitExtraTupleSlot( @@ -237,6 +261,11 @@ static void begin_cypher_merge(CustomScanState *node, EState *estate, /* store the currentCommandId for this instance */ css->base_currentCommandId = GetCurrentCommandId(false); + css->entity_exists_index_cache = + create_entity_exists_index_cache("merge_entity_exists_index_cache"); + css->entity_exists_label_relation_cache = + create_label_relation_cache("merge_entity_exists_label_relation_cache"); + Increment_Estate_CommandId(estate); } @@ -318,13 +347,17 @@ static void process_path(cypher_merge_custom_scan_state *css, (tuple_position < scantuple->tts_tupleDescriptor->natts || scantuple->tts_tupleDescriptor->natts != 1)) { - result = make_path(css->path_values); + result = make_path_with_length(css->path_values, + path->path_length); /* store the result */ scantuple->tts_values[tuple_position] = result; scantuple->tts_isnull[tuple_position] = false; } } + + list_free(css->path_values); + css->path_values = NIL; } /* @@ -360,7 +393,8 @@ static void process_simple_merge(CustomScanState *node) if (css->on_create_set_info) { mark_scan_slot_valid(econtext->ecxt_scantuple); - apply_update_list(&css->css, css->on_create_set_info); + apply_merge_update_list(css, css->on_create_set_info, + css->on_create_set_item_count); } } else @@ -373,7 +407,8 @@ static void process_simple_merge(CustomScanState *node) econtext->ecxt_scantuple = node->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple; - apply_update_list(&css->css, css->on_match_set_info); + apply_merge_update_list(css, css->on_match_set_info, + css->on_match_set_item_count); } } } @@ -428,6 +463,41 @@ static void free_path_entry_array(path_entry **path_array, int length) } } +static uint32 hash_path_entries(path_entry **path_array, int path_length) +{ + uint32 hash = 2166136261U; + int i; + + for (i = 0; i < path_length; i++) + { + path_entry *entry = path_array[i]; + +#define PATH_HASH_COMBINE(v) \ + do { \ + hash ^= (uint32)(v); \ + hash *= 16777619U; \ + } while (0) + + PATH_HASH_COMBINE(entry->actual); + if (entry->actual) + { + PATH_HASH_COMBINE(entry->id); + PATH_HASH_COMBINE(entry->id >> 32); + continue; + } + + PATH_HASH_COMBINE(entry->label); + PATH_HASH_COMBINE(entry->direction); + PATH_HASH_COMBINE(entry->prop_isNull); + if (!entry->prop_isNull) + PATH_HASH_COMBINE(entry->dih); + +#undef PATH_HASH_COMBINE + } + + return hash; +} + /* * Helper function to prebuild a path. The user needs to free the returned * path_entry when done. @@ -442,20 +512,19 @@ static path_entry **prebuild_path(CustomScanState *node) cypher_merge_custom_scan_state *css = (cypher_merge_custom_scan_state *)node; List *nodes = css->path->target_nodes; - int path_length = list_length(nodes); ListCell *lc = NULL; ExprContext *econtext = css->css.ss.ps.ps_ExprContext; int counter = 0; path_entry **path_array = NULL; - path_array = palloc0(sizeof(path_entry *) * path_length); + path_array = palloc(sizeof(path_entry *) * css->path_length); /* iterate through the path, partially prebuilding it */ foreach (lc, nodes) { /* get the node/edge and allocate the memory needed */ cypher_target_node *node = lfirst(lc); - path_entry *entry = palloc0(sizeof(path_entry)); + path_entry *entry = palloc(sizeof(path_entry)); /* if this isn't an actual passed in tuple */ if (CYPHER_TARGET_NODE_INSERT_ENTITY(node->flags)) @@ -470,7 +539,7 @@ static path_entry **prebuild_path(CustomScanState *node) entry->prop = ExecEvalExprSwitchContext(node->prop_expr_state, econtext, &isNull); entry->prop_isNull = isNull; - entry->dih = datum_image_hash(entry->prop, false, -1); + entry->dih = isNull ? 0 : datum_image_hash(entry->prop, false, -1); } /* otherwise, it is */ else @@ -480,8 +549,10 @@ static path_entry **prebuild_path(CustomScanState *node) agtype *agt = NULL; Datum d; - agtype_value *agtv_vertex = NULL; + agtype_value agtv_vertex; agtype_value *agtv_id = NULL; + bool vertex_needs_free = false; + bool vertex_found; /* check that the variable isn't NULL */ if (scanTupleSlot->tts_isnull[node->tuple_position - 1]) @@ -497,21 +568,33 @@ static path_entry **prebuild_path(CustomScanState *node) agt = DATUM_GET_AGTYPE_P(d); /* Convert to an agtype value */ - agtv_vertex = get_ith_agtype_value_from_container(&agt->root, 0); + vertex_found = get_ith_agtype_value_from_container_no_copy( + &agt->root, 0, &agtv_vertex, &vertex_needs_free); + Assert(vertex_found); - if (agtv_vertex->type != AGTV_VERTEX) + if (agtv_vertex.type != AGTV_VERTEX) { + if (vertex_needs_free) + { + pfree_agtype_value_content(&agtv_vertex); + } ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("agtype must resolve to a vertex"))); } /* extract the id agtype field */ - agtv_id = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_vertex, "id"); + agtv_id = AGTYPE_VERTEX_GET_ID(&agtv_vertex); /* set the necessary entry fields - actual & id */ entry->actual = true; entry->id = (graphid) agtv_id->val.int_value; + + if (vertex_needs_free) + { + pfree_agtype_value_content(&agtv_vertex); + } + entry->id_isNull = false; entry->prop = 0; entry->prop_isNull = true; @@ -519,7 +602,10 @@ static path_entry **prebuild_path(CustomScanState *node) if (!SAFE_TO_SKIP_EXISTENCE_CHECK(node->flags)) { - if (!entity_exists(estate, css->graph_oid, entry->id)) + if (!entity_exists_with_cache(estate, css->graph_oid, + entry->id, + css->entity_exists_index_cache, + css->entity_exists_label_relation_cache)) { ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), @@ -575,6 +661,16 @@ static bool compare_2_paths(path_entry **lhs, path_entry **rhs, int path_length) } /* are the properties datum hashes the same */ + if (lhs[i]->prop_isNull != rhs[i]->prop_isNull) + { + return false; + } + + if (lhs[i]->prop_isNull) + { + continue; + } + if (lhs[i]->dih != rhs[i]->dih) { return false; @@ -591,43 +687,99 @@ static bool compare_2_paths(path_entry **lhs, path_entry **rhs, int path_length) return true; } -/* helper function to find a duplicate path in the created_paths_list */ +/* helper function to find a duplicate path in the created path cache */ static path_entry **find_duplicate_path(CustomScanState *node, - path_entry **path_array) + path_entry **path_array, + uint32 path_hash) { cypher_merge_custom_scan_state *css = (cypher_merge_custom_scan_state *)node; - int path_length = list_length(css->path->target_nodes); - /* if the list is NULL just return NULL */ if (css->created_paths_list == NULL) { return NULL; } - /* otherwise, check to see if the path already exists */ + + if (css->created_paths_hash != NULL) + { + created_path_hash_entry *entry; + + entry = hash_search(css->created_paths_hash, &path_hash, HASH_FIND, + NULL); + if (entry == NULL) + { + return NULL; + } + + for (created_path *curr_path = entry->paths; curr_path != NULL; + curr_path = curr_path->hash_next) + { + if (compare_2_paths(path_array, curr_path->entry, + css->path_length)) + { + return curr_path->entry; + } + } + } else { - /* set to the top of the list */ created_path *curr_path = css->created_paths_list; - /* iterate through our list of created paths */ while (curr_path != NULL) { - /* if we have found the entry, return it */ - if (compare_2_paths(path_array, curr_path->entry, path_length)) + if (curr_path->path_hash == path_hash && + compare_2_paths(path_array, curr_path->entry, + css->path_length)) { return curr_path->entry; } - /* otherwise, get the next path */ curr_path = curr_path->next; } } - /* if we didn't find it, return NULL */ return NULL; } +static void remember_created_path(cypher_merge_custom_scan_state *css, + path_entry **path_array, + uint32 path_hash) +{ + created_path *new_path; + created_path_hash_entry *entry; + bool found; + + if (css->created_paths_hash == NULL) + { + HASHCTL hashctl; + + MemSet(&hashctl, 0, sizeof(hashctl)); + hashctl.keysize = sizeof(uint32); + hashctl.entrysize = sizeof(created_path_hash_entry); + hashctl.hcxt = css->css.ss.ps.state->es_query_cxt; + css->created_paths_hash = hash_create("merge_created_paths_hash", 32, + &hashctl, + HASH_ELEM | HASH_BLOBS | + HASH_CONTEXT); + } + + new_path = palloc(sizeof(created_path)); + new_path->next = css->created_paths_list; + new_path->entry = path_array; + new_path->path_hash = path_hash; + css->created_paths_list = new_path; + + entry = hash_search(css->created_paths_hash, &path_hash, HASH_ENTER, + &found); + if (!found) + { + entry->paths = NULL; + } + + new_path->hash_next = entry->paths; + entry->paths = new_path; +} + /* * Function that is called mid-execution. This function will call * its subtree in the execution tree, and depending on the results @@ -683,7 +835,7 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) if (!terminal && !css->eager_buffer_filled) { css->eager_tuples = NIL; - css->eager_tuples_index = 0; + css->eager_tuples_cursor = NULL; while (true) { @@ -713,48 +865,50 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) { path_entry **prebuilt_path_array = NULL; path_entry **found_path_array = NULL; - int path_length = - list_length(css->path->target_nodes); + uint32 path_hash; prebuilt_path_array = prebuild_path(node); + path_hash = hash_path_entries(prebuilt_path_array, + css->path_length); found_path_array = - find_duplicate_path(node, prebuilt_path_array); + find_duplicate_path(node, prebuilt_path_array, + path_hash); if (found_path_array) { free_path_entry_array(prebuilt_path_array, - path_length); + css->path_length); + pfree(prebuilt_path_array); process_path(css, found_path_array, false); /* ON MATCH SET: path was found as duplicate */ if (css->on_match_set_info) - apply_update_list(&css->css, - css->on_match_set_info); + apply_merge_update_list(css, + css->on_match_set_info, + css->on_match_set_item_count); } else { - created_path *new_path = - palloc0(sizeof(created_path)); - - new_path->next = css->created_paths_list; - new_path->entry = prebuilt_path_array; - css->created_paths_list = new_path; + remember_created_path(css, prebuilt_path_array, + path_hash); process_path(css, prebuilt_path_array, true); mark_scan_slot_valid(econtext->ecxt_scantuple); /* ON CREATE SET: path was just created */ if (css->on_create_set_info) - apply_update_list(&css->css, - css->on_create_set_info); + apply_merge_update_list(css, + css->on_create_set_info, + css->on_create_set_item_count); } } else { /* ON MATCH SET: path already existed from lateral join */ if (css->on_match_set_info) - apply_update_list(&css->css, css->on_match_set_info); + apply_merge_update_list(css, css->on_match_set_info, + css->on_match_set_item_count); } /* Project the result and save a copy */ @@ -768,20 +922,22 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) } css->eager_buffer_filled = true; + css->eager_tuples_cursor = list_head(css->eager_tuples); } /* Non-terminal: return the next buffered row (or NULL if empty) */ if (!terminal && css->eager_buffer_filled) { - if (css->eager_tuples_index < list_length(css->eager_tuples)) + if (css->eager_tuples_cursor != NULL) { HeapTuple htup; TupleTableSlot *result_slot = node->ss.ps.ps_ResultTupleSlot; htup = (HeapTuple) - list_nth(css->eager_tuples, css->eager_tuples_index); - css->eager_tuples_index++; + lfirst(css->eager_tuples_cursor); + css->eager_tuples_cursor = + lnext(css->eager_tuples, css->eager_tuples_cursor); ExecForceStoreHeapTuple(htup, result_slot, false); return result_slot; @@ -817,43 +973,48 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) { path_entry **prebuilt_path_array = NULL; path_entry **found_path_array = NULL; - int path_length = list_length(css->path->target_nodes); + uint32 path_hash; prebuilt_path_array = prebuild_path(node); + path_hash = hash_path_entries(prebuilt_path_array, + css->path_length); found_path_array = find_duplicate_path(node, - prebuilt_path_array); + prebuilt_path_array, + path_hash); if (found_path_array) { - free_path_entry_array(prebuilt_path_array, path_length); + free_path_entry_array(prebuilt_path_array, + css->path_length); + pfree(prebuilt_path_array); process_path(css, found_path_array, false); /* ON MATCH SET: path was found as duplicate */ if (css->on_match_set_info) - apply_update_list(&css->css, css->on_match_set_info); + apply_merge_update_list(css, css->on_match_set_info, + css->on_match_set_item_count); } else { - created_path *new_path = palloc0(sizeof(created_path)); - - new_path->next = css->created_paths_list; - new_path->entry = prebuilt_path_array; - css->created_paths_list = new_path; + remember_created_path(css, prebuilt_path_array, + path_hash); process_path(css, prebuilt_path_array, true); mark_scan_slot_valid(econtext->ecxt_scantuple); /* ON CREATE SET: path was just created */ if (css->on_create_set_info) - apply_update_list(&css->css, css->on_create_set_info); + apply_merge_update_list(css, css->on_create_set_info, + css->on_create_set_item_count); } } else { /* ON MATCH SET: path already existed from lateral join */ if (css->on_match_set_info) - apply_update_list(&css->css, css->on_match_set_info); + apply_merge_update_list(css, css->on_match_set_info, + css->on_match_set_item_count); } } while (true); @@ -932,7 +1093,8 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) { econtext->ecxt_scantuple = node->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple; - apply_update_list(&css->css, css->on_match_set_info); + apply_merge_update_list(css, css->on_match_set_info, + css->on_match_set_item_count); } econtext->ecxt_scantuple = ExecProject(node->ss.ps.lefttree->ps_ProjInfo); @@ -1010,7 +1172,8 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) /* ON CREATE SET: path was just created */ if (css->on_create_set_info) - apply_update_list(&css->css, css->on_create_set_info); + apply_merge_update_list(css, css->on_create_set_info, + css->on_create_set_item_count); /* mark the create_new_path flag to true. */ css->created_new_path = true; @@ -1045,19 +1208,54 @@ static void end_cypher_merge(CustomScanState *node) (cypher_merge_custom_scan_state *)node; cypher_create_path *path = css->path; ListCell *lc = NULL; - int path_length = list_length(path->target_nodes); /* increment the command counter */ CommandCounterIncrement(); - /* invalidate VLE cache if merge created anything */ - if (css->created_new_path) + /* invalidate VLE cache if MERGE changed the graph */ + if (css->graph_mutated) { increment_graph_version(css->graph_oid); } ExecEndNode(node->ss.ps.lefttree); + if (css->entity_exists_index_cache != NULL) + { + destroy_entity_exists_index_cache(css->entity_exists_index_cache); + css->entity_exists_index_cache = NULL; + } + + if (css->entity_exists_label_relation_cache != NULL) + { + destroy_label_relation_cache(css->entity_exists_label_relation_cache); + css->entity_exists_label_relation_cache = NULL; + } + + if (css->update_qual_cache != NULL) + { + hash_destroy(css->update_qual_cache); + css->update_qual_cache = NULL; + } + + if (css->update_index_cache != NULL) + { + destroy_index_cache(css->update_index_cache, false); + css->update_index_cache = NULL; + } + + if (css->update_result_rel_info_cache != NULL) + { + destroy_entity_result_rel_info_cache(css->update_result_rel_info_cache); + css->update_result_rel_info_cache = NULL; + } + + if (css->update_label_relation_cache != NULL) + { + destroy_label_relation_cache(css->update_label_relation_cache); + css->update_label_relation_cache = NULL; + } + foreach (lc, path->target_nodes) { cypher_target_node *cypher_node = (cypher_target_node *)lfirst(lc); @@ -1067,12 +1265,19 @@ static void end_cypher_merge(CustomScanState *node) continue; } - /* close all indices for the node */ - ExecCloseIndices(cypher_node->resultRelInfo); + cypher_node->resultRelInfo = NULL; + } + + if (css->result_rel_info_cache != NULL) + { + destroy_entity_result_rel_info_cache(css->result_rel_info_cache); + css->result_rel_info_cache = NULL; + } - /* close the relation itself */ - table_close(cypher_node->resultRelInfo->ri_RelationDesc, - RowExclusiveLock); + if (css->created_paths_hash != NULL) + { + hash_destroy(css->created_paths_hash); + css->created_paths_hash = NULL; } /* free up our created paths lists */ @@ -1082,7 +1287,7 @@ static void end_cypher_merge(CustomScanState *node) path_entry **entry = css->created_paths_list->entry; /* free up the path array elements */ - free_path_entry_array(entry, path_length); + free_path_entry_array(entry, css->path_length); /* free up the array container */ pfree_if_not_null(entry); @@ -1146,11 +1351,16 @@ Node *create_cypher_merge_plan_state(CustomScan *cscan) cypher_css->flags = merge_information->flags; cypher_css->merge_function_attr = merge_information->merge_function_attr; cypher_css->path = merge_information->path; + cypher_css->path_length = merge_information->path_length; cypher_css->created_new_path = false; cypher_css->found_a_path = false; cypher_css->graph_oid = merge_information->graph_oid; cypher_css->on_match_set_info = merge_information->on_match_set_info; cypher_css->on_create_set_info = merge_information->on_create_set_info; + cypher_css->on_match_set_item_count = + merge_information->on_match_set_item_count; + cypher_css->on_create_set_item_count = + merge_information->on_create_set_item_count; cypher_css->css.ss.ps.type = T_CustomScanState; cypher_css->css.methods = &cypher_merge_exec_methods; @@ -1296,6 +1506,7 @@ static Datum merge_vertex(cypher_merge_custom_scan_state *css, css->base_currentCommandId == GetCurrentCommandId(false)) { insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); + css->graph_mutated = true; /* * Increment the currentCommandId since we processed an update. We @@ -1309,6 +1520,7 @@ static Datum merge_vertex(cypher_merge_custom_scan_state *css, { insert_entity_tuple_cid(resultRelInfo, elemTupleSlot, estate, css->base_currentCommandId); + css->graph_mutated = true; } /* restore the old result relation info */ @@ -1386,8 +1598,10 @@ static Datum merge_vertex(cypher_merge_custom_scan_state *css, { agtype *a = NULL; Datum d; - agtype_value *v = NULL; + agtype_value v; agtype_value *id_value = NULL; + bool v_needs_free = false; + bool v_found; /* check that the variable isn't NULL */ if (scanTupleSlot->tts_isnull[node->tuple_position - 1]) @@ -1403,21 +1617,32 @@ static Datum merge_vertex(cypher_merge_custom_scan_state *css, a = DATUM_GET_AGTYPE_P(d); /* Convert to an agtype value */ - v = get_ith_agtype_value_from_container(&a->root, 0); + v_found = get_ith_agtype_value_from_container_no_copy(&a->root, 0, &v, + &v_needs_free); + Assert(v_found); - if (v->type != AGTV_VERTEX) + if (v.type != AGTV_VERTEX) { + if (v_needs_free) + { + pfree_agtype_value_content(&v); + } ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("agtype must resolve to a vertex"))); } /* extract the id agtype field */ - id_value = GET_AGTYPE_VALUE_OBJECT_VALUE(v, "id"); + id_value = AGTYPE_VERTEX_GET_ID(&v); /* extract the graphid and cast to a Datum */ id = GRAPHID_GET_DATUM(id_value->val.int_value); + if (v_needs_free) + { + pfree_agtype_value_content(&v); + } + /* * Its possible the variable has already been deleted. There are two * ways this can happen. One is the query explicitly deleted the @@ -1431,7 +1656,10 @@ static Datum merge_vertex(cypher_merge_custom_scan_state *css, */ if (!SAFE_TO_SKIP_EXISTENCE_CHECK(node->flags)) { - if (!entity_exists(estate, css->graph_oid, DATUM_GET_GRAPHID(id))) + if (!entity_exists_with_cache(estate, css->graph_oid, + DATUM_GET_GRAPHID(id), + css->entity_exists_index_cache, + css->entity_exists_label_relation_cache)) { ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), @@ -1635,6 +1863,7 @@ static void merge_edge(cypher_merge_custom_scan_state *css, css->base_currentCommandId == GetCurrentCommandId(false)) { insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); + css->graph_mutated = true; /* * Increment the currentCommandId since we processed an update. We @@ -1648,6 +1877,7 @@ static void merge_edge(cypher_merge_custom_scan_state *css, { insert_entity_tuple_cid(resultRelInfo, elemTupleSlot, estate, css->base_currentCommandId); + css->graph_mutated = true; } /* restore the old result relation info */ diff --git a/src/backend/executor/cypher_set.c b/src/backend/executor/cypher_set.c index a6e64ba56..f5786abd3 100644 --- a/src/backend/executor/cypher_set.c +++ b/src/backend/executor/cypher_set.c @@ -24,10 +24,12 @@ #include "storage/bufmgr.h" #include "utils/rls.h" +#include "commands/label_commands.h" +#include "catalog/ag_graph.h" #include "executor/cypher_executor.h" #include "executor/cypher_utils.h" #include "utils/age_global_graph.h" -#include "catalog/ag_graph.h" +#include "utils/ag_cache.h" static void begin_cypher_set(CustomScanState *node, EState *estate, int eflags); @@ -35,10 +37,24 @@ static TupleTableSlot *exec_cypher_set(CustomScanState *node); static void end_cypher_set(CustomScanState *node); static void rescan_cypher_set(CustomScanState *node); -static void process_update_list(CustomScanState *node); +static bool process_update_list(CustomScanState *node); static HeapTuple update_entity_tuple(ResultRelInfo *resultRelInfo, TupleTableSlot *elemTupleSlot, EState *estate, HeapTuple old_tuple); +static void init_update_caches(HTAB **qual_cache, HTAB **index_cache, + HTAB **result_rel_info_cache, + const char *qual_name, const char *index_name, + const char *result_rel_info_name); +static void init_update_lookup_caches(HTAB **qual_cache, HTAB **index_cache, + const char *qual_name, + const char *index_name); +static void ensure_path_update_attrs(CustomScanState *node, + TupleDesc tupleDescriptor, + int **attrs, int *attr_count); +static int *ensure_last_update_indexes(EState *estate, + cypher_update_information *set_info, + int num_set_items, + int natts); const CustomExecMethods cypher_set_exec_methods = {SET_SCAN_STATE_NAME, begin_cypher_set, @@ -60,8 +76,17 @@ static void begin_cypher_set(CustomScanState *node, EState *estate, cypher_set_custom_scan_state *css = (cypher_set_custom_scan_state *)node; Plan *subplan; + ListCell *lc; Assert(list_length(css->cs->custom_plans) == 1); + css->graph_oid = css->set_list->graph_oid; + if (!OidIsValid(css->graph_oid)) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("graph \"%s\" does not exist", + css->set_list->graph_name))); + } subplan = linitial(css->cs->custom_plans); node->ss.ps.lefttree = ExecInitNode(subplan, estate, eflags); @@ -91,9 +116,63 @@ static void begin_cypher_set(CustomScanState *node, EState *estate, estate->es_output_cid = estate->es_snapshot->curcid; } + css->set_item_count = css->set_list->set_item_count; + Assert(css->set_item_count == list_length(css->set_list->set_items)); + + foreach(lc, css->set_list->set_items) + { + cypher_update_item *item = (cypher_update_item *)lfirst(lc); + + if (item->prop_expr != NULL) + { + item->prop_expr_state = ExecInitExpr((Expr *)item->prop_expr, + (PlanState *)node); + } + } + + init_update_caches(&css->qual_cache, &css->index_cache, + &css->result_rel_info_cache, + "set_qual_cache", "set_index_cache", + "set_result_rel_info_cache"); + css->label_relation_cache = + create_label_relation_cache("set_label_relation_cache"); + Increment_Estate_CommandId(estate); } +static void init_update_caches(HTAB **qual_cache, HTAB **index_cache, + HTAB **result_rel_info_cache, + const char *qual_name, const char *index_name, + const char *result_rel_info_name) +{ + init_update_lookup_caches(qual_cache, index_cache, qual_name, index_name); + + *result_rel_info_cache = + create_entity_result_rel_info_cache(result_rel_info_name); +} + +static void init_update_lookup_caches(HTAB **qual_cache, HTAB **index_cache, + const char *qual_name, + const char *index_name) +{ + HASHCTL hashctl; + HASHCTL idx_hashctl; + + MemSet(&hashctl, 0, sizeof(hashctl)); + hashctl.keysize = sizeof(Oid); + hashctl.entrysize = sizeof(RLSCacheEntry); + hashctl.hcxt = CurrentMemoryContext; + *qual_cache = hash_create(qual_name, 8, &hashctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + + MemSet(&idx_hashctl, 0, sizeof(idx_hashctl)); + idx_hashctl.keysize = sizeof(Oid); + idx_hashctl.entrysize = sizeof(IndexCacheEntry); + idx_hashctl.hcxt = CurrentMemoryContext; + *index_cache = hash_create(index_name, 8, &idx_hashctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); +} + static HeapTuple update_entity_tuple(ResultRelInfo *resultRelInfo, TupleTableSlot *elemTupleSlot, EState *estate, HeapTuple old_tuple) @@ -216,19 +295,22 @@ static HeapTuple update_entity_tuple(ResultRelInfo *resultRelInfo, * When the CREATE clause is the last cypher clause, consume all input from the * previous clause(s) in the first call of exec_cypher_create. */ -static void process_all_tuples(CustomScanState *node) +static bool process_all_tuples(CustomScanState *node) { cypher_set_custom_scan_state *css = (cypher_set_custom_scan_state *)node; TupleTableSlot *slot; EState *estate = css->css.ss.ps.state; + bool graph_mutated = false; do { - process_update_list(node); + graph_mutated |= process_update_list(node); Decrement_Estate_CommandId(estate) slot = ExecProcNode(node->ss.ps.lefttree); Increment_Estate_CommandId(estate) } while (!TupIsNull(slot)); + + return graph_mutated; } /* @@ -244,7 +326,22 @@ static bool check_path(agtype_value *path, graphid updated_id) { agtype_value *elem = &path->val.array.elems[i]; - agtype_value *id = GET_AGTYPE_VALUE_OBJECT_VALUE(elem, "id"); + agtype_value *id; + + if (elem->type != AGTV_VERTEX && elem->type != AGTV_EDGE) + { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unsupported agtype found in a path"))); + } + + if (elem->type == AGTV_VERTEX) + { + id = AGTYPE_VERTEX_GET_ID(elem); + } + else + { + id = AGTYPE_EDGE_GET_ID(elem); + } if (updated_id == id->val.int_value) { @@ -259,26 +356,20 @@ static bool check_path(agtype_value *path, graphid updated_id) * Construct a new agtype path with the entity with updated_id * replacing all of its instances in path with updated_entity */ -static agtype_value *replace_entity_in_path(agtype_value *path, - graphid updated_id, - agtype *updated_entity) +static agtype *replace_entity_in_path(agtype_value *path, + graphid updated_id, + agtype *updated_entity) { - agtype_iterator *it; - agtype_iterator_token tok = WAGT_DONE; agtype_parse_state *parse_state = NULL; - agtype_value *r; agtype_value *parsed_agtype_value = NULL; - agtype *prop_agtype; + agtype_value updated_entity_value; + agtype *result; + bool updated_entity_value_valid = false; + bool updated_entity_needs_free = false; int i; - r = palloc(sizeof(agtype_value)); - - prop_agtype = agtype_value_to_agtype(path); - it = agtype_iterator_init(&prop_agtype->root); - tok = agtype_iterator_next(&it, r, true); - - parsed_agtype_value = push_agtype_value(&parse_state, tok, - tok < WAGT_BEGIN_ARRAY ? r : NULL); + parsed_agtype_value = push_agtype_value(&parse_state, WAGT_BEGIN_ARRAY, + NULL); /* Iterate through the path, replace entities as necessary. */ for (i = 0; i < path->val.array.num_elems; i++) @@ -295,7 +386,14 @@ static agtype_value *replace_entity_in_path(agtype_value *path, } /* extract the id field */ - id = GET_AGTYPE_VALUE_OBJECT_VALUE(elem, "id"); + if (elem->type == AGTV_VERTEX) + { + id = AGTYPE_VERTEX_GET_ID(elem); + } + else + { + id = AGTYPE_EDGE_GET_ID(elem); + } /* * Either replace or keep the entity in the new path, depending on the id @@ -303,8 +401,16 @@ static agtype_value *replace_entity_in_path(agtype_value *path, */ if (updated_id == id->val.int_value) { + if (!updated_entity_value_valid) + { + (void)get_ith_agtype_value_from_container_no_copy( + &updated_entity->root, 0, &updated_entity_value, + &updated_entity_needs_free); + updated_entity_value_valid = true; + } + parsed_agtype_value = push_agtype_value(&parse_state, WAGT_ELEM, - get_ith_agtype_value_from_container(&updated_entity->root, 0)); + &updated_entity_value); } else { @@ -316,7 +422,13 @@ static agtype_value *replace_entity_in_path(agtype_value *path, parsed_agtype_value = push_agtype_value(&parse_state, WAGT_END_ARRAY, NULL); parsed_agtype_value->type = AGTV_PATH; - return parsed_agtype_value; + result = agtype_value_to_agtype(parsed_agtype_value); + if (updated_entity_needs_free) + { + pfree_agtype_value_content(&updated_entity_value); + } + + return result; } /* @@ -329,26 +441,32 @@ static void update_all_paths(CustomScanState *node, graphid id, { ExprContext *econtext = node->ss.ps.ps_ExprContext; TupleTableSlot *scanTupleSlot = econtext->ecxt_scantuple; + TupleDesc tupleDescriptor = scanTupleSlot->tts_tupleDescriptor; + Datum *values = scanTupleSlot->tts_values; + bool *isnull = scanTupleSlot->tts_isnull; + int *attrs = NULL; + int attr_count = 0; int i; - for (i = 0; i < scanTupleSlot->tts_tupleDescriptor->natts; i++) + ensure_path_update_attrs(node, tupleDescriptor, &attrs, &attr_count); + if (attr_count == 0) { - agtype *original_entity; - agtype_value *original_entity_value; + return; + } - /* skip nulls */ - if (TupleDescAttr(scanTupleSlot->tts_tupleDescriptor, i)->atttypid != AGTYPEOID) - { - continue; - } + for (i = 0; i < attr_count; i++) + { + agtype *original_entity; + agtype_value original_entity_value; + bool entity_needs_free = false; + int attr = attrs[i]; - /* skip non agtype values */ - if (scanTupleSlot->tts_isnull[i]) + if (isnull[attr]) { continue; } - original_entity = DATUM_GET_AGTYPE_P(scanTupleSlot->tts_values[i]); + original_entity = DATUM_GET_AGTYPE_P(values[attr]); /* if the value is not a scalar type, its not a path */ if (!AGTYPE_CONTAINER_IS_SCALAR(&original_entity->root)) @@ -356,21 +474,99 @@ static void update_all_paths(CustomScanState *node, graphid id, continue; } - original_entity_value = get_ith_agtype_value_from_container(&original_entity->root, 0); + (void)get_ith_agtype_value_from_container_no_copy( + &original_entity->root, 0, &original_entity_value, + &entity_needs_free); /* we found a path */ - if (original_entity_value->type == AGTV_PATH) + if (original_entity_value.type == AGTV_PATH) { /* check if the path contains the entity. */ - if (check_path(original_entity_value, id)) + if (check_path(&original_entity_value, id)) { /* the path does contain the entity replace with the new entity. */ - agtype_value *new_path = replace_entity_in_path(original_entity_value, id, updated_entity); + agtype *new_path = replace_entity_in_path( + &original_entity_value, id, updated_entity); + + values[attr] = AGTYPE_P_GET_DATUM(new_path); + } + } + + if (entity_needs_free) + { + pfree_agtype_value_content(&original_entity_value); + } + } +} + +static void ensure_path_update_attrs(CustomScanState *node, + TupleDesc tupleDescriptor, + int **attrs, int *attr_count) +{ + int **cached_attrs = NULL; + int *cached_count = NULL; + bool *cached_valid = NULL; + int i; + + if (node->methods == &cypher_set_exec_methods) + { + cypher_set_custom_scan_state *css = + (cypher_set_custom_scan_state *)node; + + cached_attrs = &css->path_update_attrs; + cached_count = &css->path_update_attr_count; + cached_valid = &css->path_update_attrs_valid; + } + else if (node->methods == &cypher_merge_exec_methods) + { + cypher_merge_custom_scan_state *css = + (cypher_merge_custom_scan_state *)node; + + cached_attrs = &css->path_update_attrs; + cached_count = &css->path_update_attr_count; + cached_valid = &css->path_update_attrs_valid; + } + + if (cached_valid == NULL) + { + *attrs = NULL; + *attr_count = 0; + return; + } + + if (!*cached_valid) + { + int count = 0; + + for (i = 0; i < tupleDescriptor->natts; i++) + { + if (TupleDescAttr(tupleDescriptor, i)->atttypid == AGTYPEOID) + { + count++; + } + } + + if (count > 0) + { + int index = 0; - scanTupleSlot->tts_values[i] = AGTYPE_P_GET_DATUM(agtype_value_to_agtype(new_path)); + *cached_attrs = MemoryContextAlloc(node->ss.ps.state->es_query_cxt, + sizeof(int) * count); + for (i = 0; i < tupleDescriptor->natts; i++) + { + if (TupleDescAttr(tupleDescriptor, i)->atttypid == AGTYPEOID) + { + (*cached_attrs)[index++] = i; + } } } + + *cached_count = count; + *cached_valid = true; } + + *attrs = *cached_attrs; + *attr_count = *cached_count; } /* @@ -378,8 +574,9 @@ static void update_all_paths(CustomScanState *node, graphid id, * Takes the CustomScanState for expression context and a * cypher_update_information describing which properties to set. */ -void apply_update_list(CustomScanState *node, - cypher_update_information *set_info) +bool apply_update_list(CustomScanState *node, + cypher_update_information *set_info, + int num_set_items) { ExprContext *econtext = node->ss.ps.ps_ExprContext; TupleTableSlot *scanTupleSlot = econtext->ecxt_scantuple; @@ -388,63 +585,96 @@ void apply_update_list(CustomScanState *node, int *luindex = NULL; int lidx = 0; HTAB *qual_cache = NULL; - HASHCTL hashctl; HTAB *index_cache = NULL; - HASHCTL idx_hashctl; - - /* allocate an array to hold the last update index of each 'entity' */ - luindex = palloc0(sizeof(int) * scanTupleSlot->tts_nvalid); + HTAB *result_rel_info_cache = NULL; + HTAB *label_relation_cache = NULL; + bool local_caches = false; + bool graph_mutated = false; + Oid graph_oid; - /* Hash table for caching compiled security quals per label */ - MemSet(&hashctl, 0, sizeof(hashctl)); - hashctl.keysize = sizeof(Oid); - hashctl.entrysize = sizeof(RLSCacheEntry); - hashctl.hcxt = CurrentMemoryContext; - qual_cache = hash_create("update_qual_cache", 8, &hashctl, - HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + if (node->methods == &cypher_set_exec_methods) + { + cypher_set_custom_scan_state *css = + (cypher_set_custom_scan_state *)node; + + qual_cache = css->qual_cache; + index_cache = css->index_cache; + result_rel_info_cache = css->result_rel_info_cache; + label_relation_cache = css->label_relation_cache; + graph_oid = css->graph_oid; + } + else if (node->methods == &cypher_merge_exec_methods) + { + cypher_merge_custom_scan_state *css = + (cypher_merge_custom_scan_state *)node; - MemSet(&idx_hashctl, 0, sizeof(idx_hashctl)); - idx_hashctl.keysize = sizeof(Oid); - idx_hashctl.entrysize = sizeof(IndexCacheEntry); - idx_hashctl.hcxt = CurrentMemoryContext; - index_cache = hash_create("update_index_cache", 8, &idx_hashctl, - HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + if (css->update_qual_cache == NULL || + css->update_index_cache == NULL || + css->update_result_rel_info_cache == NULL || + css->update_label_relation_cache == NULL) + { + init_update_caches(&css->update_qual_cache, + &css->update_index_cache, + &css->update_result_rel_info_cache, + "merge_update_qual_cache", + "merge_update_index_cache", + "merge_update_result_rel_info_cache"); + css->update_label_relation_cache = + create_label_relation_cache("merge_update_label_relation_cache"); + } - /* - * Iterate through the SET items list and store the loop index of each - * 'entity' update. As there is only one entry for each entity, this will - * have the effect of overwriting the previous loop index stored - if this - * 'entity' is used more than once. This will create an array of the last - * loop index for the update of that particular 'entity'. This will allow us - * to correctly update an 'entity' after all other previous updates to that - * 'entity' have been done. - */ - foreach (lc, set_info->set_items) + qual_cache = css->update_qual_cache; + index_cache = css->update_index_cache; + result_rel_info_cache = css->update_result_rel_info_cache; + label_relation_cache = css->update_label_relation_cache; + graph_oid = css->graph_oid; + } + else { - cypher_update_item *update_item = NULL; + init_update_caches(&qual_cache, &index_cache, + &result_rel_info_cache, + "update_qual_cache", "update_index_cache", + "update_result_rel_info_cache"); + label_relation_cache = + create_label_relation_cache("update_label_relation_cache"); + local_caches = true; + graph_oid = set_info->graph_oid; + if (!OidIsValid(graph_oid)) + { + graph_oid = get_graph_oid(set_info->graph_name); + if (!OidIsValid(graph_oid)) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("graph \"%s\" does not exist", + set_info->graph_name))); + } + } + } - update_item = (cypher_update_item *)lfirst(lc); - luindex[update_item->entity_position - 1] = lidx; + if (num_set_items <= 0) + num_set_items = set_info->set_item_count; + Assert(num_set_items == list_length(set_info->set_items)); - /* increment the loop index */ - lidx++; + if (num_set_items > 1) + { + luindex = ensure_last_update_indexes(estate, set_info, num_set_items, + scanTupleSlot->tts_tupleDescriptor->natts); } - /* reset loop index */ - lidx = 0; - /* iterate through SET set items */ foreach (lc, set_info->set_items) { agtype_value *altered_properties; - agtype_value *original_entity_value; + agtype_value original_entity_value; agtype_value *original_properties; agtype_value *id; - agtype_value *label; + agtype_value *startid = NULL; + agtype_value *endid = NULL; agtype *original_entity; agtype *new_property_value = NULL; - TupleTableSlot *slot; - ResultRelInfo *resultRelInfo; + TupleTableSlot *slot = NULL; + ResultRelInfo *resultRelInfo = NULL; ScanKeyData scan_keys[1]; TableScanDesc scan_desc; bool remove_property; @@ -453,12 +683,15 @@ void apply_update_list(CustomScanState *node, Datum new_entity; HeapTuple heap_tuple; char *clause_name = set_info->clause_name; - int cid; Oid index_oid = InvalidOid; - Relation rel; - Oid relid; + Relation rel = NULL; + Oid relid = InvalidOid; IndexCacheEntry *idx_entry; bool found_idx_entry; + RLSCacheEntry *rls_entry = NULL; + bool found_rls_entry; + bool original_entity_needs_free = false; + int32 label_id; update_item = (cypher_update_item *)lfirst(lc); @@ -480,25 +713,45 @@ void apply_update_list(CustomScanState *node, } original_entity = DATUM_GET_AGTYPE_P(scanTupleSlot->tts_values[update_item->entity_position - 1]); - original_entity_value = get_ith_agtype_value_from_container(&original_entity->root, 0); + (void)get_ith_agtype_value_from_container_no_copy( + &original_entity->root, 0, &original_entity_value, + &original_entity_needs_free); - if (original_entity_value->type != AGTV_VERTEX && - original_entity_value->type != AGTV_EDGE) + if (original_entity_value.type != AGTV_VERTEX && + original_entity_value.type != AGTV_EDGE) { + if (original_entity_needs_free) + { + pfree_agtype_value_content(&original_entity_value); + } ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("age %s clause can only update vertex and edges", clause_name))); } - /* get the id and label for later */ - id = GET_AGTYPE_VALUE_OBJECT_VALUE(original_entity_value, "id"); - label = GET_AGTYPE_VALUE_OBJECT_VALUE(original_entity_value, "label"); - - label_name = pnstrdup(label->val.string.val, label->val.string.len); - /* get the properties we need to update */ - original_properties = GET_AGTYPE_VALUE_OBJECT_VALUE(original_entity_value, - "properties"); + /* get the id and label metadata for later */ + if (original_entity_value.type == AGTV_VERTEX) + { + id = AGTYPE_VERTEX_GET_ID(&original_entity_value); + original_properties = + AGTYPE_VERTEX_GET_PROPERTIES(&original_entity_value); + } + else + { + id = AGTYPE_EDGE_GET_ID(&original_entity_value); + original_properties = + AGTYPE_EDGE_GET_PROPERTIES(&original_entity_value); + } + label_id = GET_LABEL_ID(id->val.int_value); + if (!get_label_relation_from_cache(label_relation_cache, graph_oid, + label_id, &relid, &label_name)) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("label id %d does not exist", + label_id))); + } /* * Determine if the property should be removed. This will be because @@ -521,9 +774,8 @@ void apply_update_list(CustomScanState *node, bool isnull; /* - * Use the pre-initialized ExprState if available (set during - * plan init in begin_cypher_merge). Fall back to per-row init - * for callers that haven't pre-initialized (e.g. plain SET). + * Use the pre-initialized ExprState if available. Fall back to + * per-row init for callers that haven't pre-initialized. */ if (update_item->prop_expr_state != NULL) { @@ -562,7 +814,7 @@ void apply_update_list(CustomScanState *node, /* Alter the properties Agtype value. */ if (update_item->prop_name != NULL && - strcmp(update_item->prop_name, "") != 0) + update_item->prop_name[0] != '\0') { altered_properties = alter_property_value(original_properties, update_item->prop_name, @@ -587,81 +839,28 @@ void apply_update_list(CustomScanState *node, } } - resultRelInfo = create_entity_result_rel_info( - estate, set_info->graph_name, label_name); - - rel = resultRelInfo->ri_RelationDesc; - relid = RelationGetRelid(rel); - - idx_entry = hash_search(index_cache, &relid, HASH_ENTER, &found_idx_entry); - if (!found_idx_entry) - { - /* Check if there is a valid index on the 'id' column */ - idx_entry->index_oid = find_usable_btree_index_for_attr(rel, 1); - } - index_oid = idx_entry->index_oid; - - slot = ExecInitExtraTupleSlot( - estate, RelationGetDescr(resultRelInfo->ri_RelationDesc), - &TTSOpsHeapTuple); - - /* Setup RLS policies if RLS is enabled */ - if (check_enable_rls(resultRelInfo->ri_RelationDesc->rd_id, - InvalidOid, true) == RLS_ENABLED) - { - RLSCacheEntry *entry; - bool found; - - /* Get cached RLS state for this label, or set it up */ - entry = hash_search(qual_cache, &relid, HASH_ENTER, &found); - if (!found) - { - /* Setup WITH CHECK policies */ - setup_wcos(resultRelInfo, estate, node, CMD_UPDATE); - entry->withCheckOptions = resultRelInfo->ri_WithCheckOptions; - entry->withCheckOptionExprs = resultRelInfo->ri_WithCheckOptionExprs; - - /* Setup security quals */ - entry->qualExprs = setup_security_quals(resultRelInfo, estate, - node, CMD_UPDATE); - entry->slot = ExecInitExtraTupleSlot( - estate, RelationGetDescr(resultRelInfo->ri_RelationDesc), - &TTSOpsHeapTuple); - } - else - { - /* Use cached WCOs */ - resultRelInfo->ri_WithCheckOptions = entry->withCheckOptions; - resultRelInfo->ri_WithCheckOptionExprs = entry->withCheckOptionExprs; - } - } - /* * Now that we have the updated properties, create a either a vertex or - * edge Datum for the in-memory update, and setup the tupleTableSlot - * for the on-disc update. + * edge Datum for the in-memory update. The tupleTableSlot and relation + * metadata for the on-disc update are only needed for the last update + * targeting this entity. */ - if (original_entity_value->type == AGTV_VERTEX) + if (original_entity_value.type == AGTV_VERTEX) { new_entity = make_vertex(GRAPHID_GET_DATUM(id->val.int_value), CStringGetDatum(label_name), AGTYPE_P_GET_DATUM(agtype_value_to_agtype(altered_properties))); - - slot = populate_vertex_tts(slot, id, altered_properties); } - else if (original_entity_value->type == AGTV_EDGE) + else if (original_entity_value.type == AGTV_EDGE) { - agtype_value *startid = GET_AGTYPE_VALUE_OBJECT_VALUE(original_entity_value, "start_id"); - agtype_value *endid = GET_AGTYPE_VALUE_OBJECT_VALUE(original_entity_value, "end_id"); + startid = AGTYPE_EDGE_GET_START_ID(&original_entity_value); + endid = AGTYPE_EDGE_GET_END_ID(&original_entity_value); new_entity = make_edge(GRAPHID_GET_DATUM(id->val.int_value), GRAPHID_GET_DATUM(startid->val.int_value), GRAPHID_GET_DATUM(endid->val.int_value), CStringGetDatum(label_name), AGTYPE_P_GET_DATUM(agtype_value_to_agtype(altered_properties))); - - slot = populate_edge_tts(slot, id, startid, endid, - altered_properties); } else { @@ -685,11 +884,91 @@ void apply_update_list(CustomScanState *node, * If the last update index for the entity is equal to the current loop * index, then update this tuple. */ - cid = estate->es_snapshot->curcid; - estate->es_snapshot->curcid = GetCurrentCommandId(false); - - if (luindex[update_item->entity_position - 1] == lidx) + if (num_set_items == 1 || + luindex[update_item->entity_position - 1] == lidx) { + int cid; + + cid = estate->es_snapshot->curcid; + estate->es_snapshot->curcid = GetCurrentCommandId(false); + + resultRelInfo = get_entity_result_rel_info( + estate, result_rel_info_cache, relid); + + rel = resultRelInfo->ri_RelationDesc; + relid = RelationGetRelid(rel); + + idx_entry = hash_search(index_cache, &relid, HASH_ENTER, + &found_idx_entry); + if (!found_idx_entry) + { + init_index_cache_entry(idx_entry); + } + if (!idx_entry->index_oid_cached) + { + /* Check if there is a valid index on the 'id' column. */ + idx_entry->index_oid = find_usable_btree_index_for_attr(rel, 1); + idx_entry->index_oid_cached = true; + } + index_oid = idx_entry->index_oid; + + rls_entry = hash_search(qual_cache, &relid, HASH_ENTER, + &found_rls_entry); + if (!found_rls_entry) + { + rls_entry->rls_enabled = + check_enable_rls(resultRelInfo->ri_RelationDesc->rd_id, + InvalidOid, true) == RLS_ENABLED; + + if (rls_entry->rls_enabled) + { + /* Setup WITH CHECK policies. */ + setup_wcos(resultRelInfo, estate, node, CMD_UPDATE); + rls_entry->withCheckOptions = + resultRelInfo->ri_WithCheckOptions; + rls_entry->withCheckOptionExprs = + resultRelInfo->ri_WithCheckOptionExprs; + + /* Setup security quals. */ + rls_entry->qualExprs = setup_security_quals(resultRelInfo, + estate, node, + CMD_UPDATE); + rls_entry->slot = ExecInitExtraTupleSlot( + estate, RelationGetDescr(resultRelInfo->ri_RelationDesc), + &TTSOpsHeapTuple); + } + } + else if (rls_entry->rls_enabled) + { + /* Use cached WCOs. */ + resultRelInfo->ri_WithCheckOptions = + rls_entry->withCheckOptions; + resultRelInfo->ri_WithCheckOptionExprs = + rls_entry->withCheckOptionExprs; + } + + slot = idx_entry->update_slot; + if (slot == NULL) + { + slot = ExecInitExtraTupleSlot(estate, RelationGetDescr(rel), + &TTSOpsHeapTuple); + idx_entry->update_slot = slot; + } + else + { + ExecClearTuple(slot); + } + + if (original_entity_value.type == AGTV_VERTEX) + { + slot = populate_vertex_tts(slot, id, altered_properties); + } + else + { + slot = populate_edge_tts(slot, id, startid, endid, + altered_properties); + } + if (OidIsValid(index_oid)) { Relation index_rel; @@ -705,7 +984,17 @@ void apply_update_list(CustomScanState *node, ScanKeyInit(&scan_keys[0], 1, BTEqualStrategyNumber, F_GRAPHIDEQ, GRAPHID_GET_DATUM(id->val.int_value)); - index_slot = table_slot_create(rel, NULL); + index_slot = idx_entry->slot; + if (index_slot == NULL) + { + index_slot = table_slot_create(rel, NULL); + idx_entry->slot = index_slot; + } + else + { + ExecClearTuple(index_slot); + } + idx_scan_desc = index_beginscan(rel, index_rel, estate->es_snapshot, NULL, 1, 0); index_rescan(idx_scan_desc, scan_keys, 1, NULL, 0); @@ -722,23 +1011,12 @@ void apply_update_list(CustomScanState *node, HeapTuple original_tuple = heap_tuple; /* Check RLS security quals (USING policy) before update */ - if (check_enable_rls(relid, InvalidOid, true) == RLS_ENABLED) + if (rls_entry->rls_enabled) { - RLSCacheEntry *entry; - - /* Entry was already created earlier when setting up WCOs */ - entry = hash_search(qual_cache, &relid, HASH_FIND, NULL); - if (!entry) - { - ereport(ERROR, - (errcode(ERRCODE_INTERNAL_ERROR), - errmsg("missing RLS cache entry for relation %u", - relid))); - } - - ExecStoreHeapTuple(heap_tuple, entry->slot, false); - should_update = check_security_quals(entry->qualExprs, - entry->slot, + ExecStoreHeapTuple(heap_tuple, rls_entry->slot, + false); + should_update = check_security_quals(rls_entry->qualExprs, + rls_entry->slot, econtext); } @@ -747,6 +1025,7 @@ void apply_update_list(CustomScanState *node, { heap_tuple = update_entity_tuple(resultRelInfo, slot, estate, original_tuple); + graph_mutated |= HeapTupleIsValid(heap_tuple); } if (shouldFree) @@ -756,7 +1035,7 @@ void apply_update_list(CustomScanState *node, } } - ExecDropSingleTupleTableSlot(index_slot); + ExecClearTuple(index_slot); index_endscan(idx_scan_desc); index_close(index_rel, RowExclusiveLock); } @@ -786,23 +1065,11 @@ void apply_update_list(CustomScanState *node, bool should_update = true; /* Check RLS security quals (USING policy) before update */ - if (check_enable_rls(relid, InvalidOid, true) == RLS_ENABLED) + if (rls_entry->rls_enabled) { - RLSCacheEntry *entry; - - /* Entry was already created earlier when setting up WCOs */ - entry = hash_search(qual_cache, &relid, HASH_FIND, NULL); - if (!entry) - { - ereport(ERROR, - (errcode(ERRCODE_INTERNAL_ERROR), - errmsg("missing RLS cache entry for relation %u", - relid))); - } - - ExecStoreHeapTuple(heap_tuple, entry->slot, false); - should_update = check_security_quals(entry->qualExprs, - entry->slot, + ExecStoreHeapTuple(heap_tuple, rls_entry->slot, false); + should_update = check_security_quals(rls_entry->qualExprs, + rls_entry->slot, econtext); } @@ -811,35 +1078,82 @@ void apply_update_list(CustomScanState *node, { heap_tuple = update_entity_tuple(resultRelInfo, slot, estate, heap_tuple); + graph_mutated |= HeapTupleIsValid(heap_tuple); } } /* close the ScanDescription */ table_endscan(scan_desc); } + + estate->es_snapshot->curcid = cid; } - estate->es_snapshot->curcid = cid; - /* close relation */ - ExecCloseIndices(resultRelInfo); - table_close(resultRelInfo->ri_RelationDesc, RowExclusiveLock); + if (original_entity_needs_free) + { + pfree_agtype_value_content(&original_entity_value); + } /* increment loop index */ lidx++; } - /* Clean up the cache */ - hash_destroy(qual_cache); - hash_destroy(index_cache); + if (local_caches) + { + hash_destroy(qual_cache); + destroy_index_cache(index_cache, false); + destroy_entity_result_rel_info_cache(result_rel_info_cache); + destroy_label_relation_cache(label_relation_cache); + } + + return graph_mutated; +} + +static int *ensure_last_update_indexes(EState *estate, + cypher_update_information *set_info, + int num_set_items, + int natts) +{ + ListCell *lc; + int lidx = 0; + + if (set_info->last_update_indexes_valid && + set_info->last_update_index_count == natts) + { + return set_info->last_update_indexes; + } + + if (set_info->last_update_indexes != NULL) + { + pfree(set_info->last_update_indexes); + } + + set_info->last_update_indexes = + MemoryContextAlloc(estate->es_query_cxt, sizeof(int) * natts); + set_info->last_update_index_count = natts; + + /* + * Store the last update index for each entity position once per plan + * execution. This used to be rebuilt for every input row. + */ + foreach (lc, set_info->set_items) + { + cypher_update_item *update_item = (cypher_update_item *)lfirst(lc); + + set_info->last_update_indexes[update_item->entity_position - 1] = lidx; + lidx++; + } + + Assert(lidx == num_set_items); + set_info->last_update_indexes_valid = true; - /* free our lookup array */ - pfree_if_not_null(luindex); + return set_info->last_update_indexes; } -static void process_update_list(CustomScanState *node) +static bool process_update_list(CustomScanState *node) { cypher_set_custom_scan_state *css = (cypher_set_custom_scan_state *)node; - apply_update_list(node, css->set_list); + return apply_update_list(node, css->set_list, css->set_item_count); } static TupleTableSlot *exec_cypher_set(CustomScanState *node) @@ -867,27 +1181,31 @@ static TupleTableSlot *exec_cypher_set(CustomScanState *node) if (CYPHER_CLAUSE_IS_TERMINAL(css->flags)) { + bool graph_mutated; + estate->es_result_relations = saved_resultRels; - process_all_tuples(node); + graph_mutated = process_all_tuples(node); /* increment the command counter to reflect the updates */ CommandCounterIncrement(); - /* invalidate VLE cache — graph was mutated */ - increment_graph_version(get_graph_oid(css->set_list->graph_name)); + if (graph_mutated) + { + increment_graph_version(css->graph_oid); + } return NULL; } - process_update_list(node); + if (process_update_list(node)) + { + increment_graph_version(css->graph_oid); + } /* increment the command counter to reflect the updates */ CommandCounterIncrement(); - /* invalidate VLE cache — graph was mutated */ - increment_graph_version(get_graph_oid(css->set_list->graph_name)); - estate->es_result_relations = saved_resultRels; econtext->ecxt_scantuple = ExecProject(node->ss.ps.lefttree->ps_ProjInfo); @@ -897,6 +1215,32 @@ static TupleTableSlot *exec_cypher_set(CustomScanState *node) static void end_cypher_set(CustomScanState *node) { + cypher_set_custom_scan_state *css = (cypher_set_custom_scan_state *)node; + + if (css->qual_cache != NULL) + { + hash_destroy(css->qual_cache); + css->qual_cache = NULL; + } + + if (css->index_cache != NULL) + { + destroy_index_cache(css->index_cache, false); + css->index_cache = NULL; + } + + if (css->result_rel_info_cache != NULL) + { + destroy_entity_result_rel_info_cache(css->result_rel_info_cache); + css->result_rel_info_cache = NULL; + } + + if (css->label_relation_cache != NULL) + { + destroy_label_relation_cache(css->label_relation_cache); + css->label_relation_cache = NULL; + } + ExecEndNode(node->ss.ps.lefttree); } diff --git a/src/backend/executor/cypher_utils.c b/src/backend/executor/cypher_utils.c index 8237cdcce..f27e62705 100644 --- a/src/backend/executor/cypher_utils.c +++ b/src/backend/executor/cypher_utils.c @@ -24,10 +24,11 @@ #include "postgres.h" +#include "access/tableam.h" +#include "common/hashfn.h" #include "executor/executor.h" #include "miscadmin.h" #include "nodes/makefuncs.h" -#include "parser/parse_relation.h" #include "rewrite/rewriteManip.h" #include "rewrite/rowsecurity.h" #include "utils/acl.h" @@ -38,6 +39,19 @@ #include "executor/cypher_utils.h" #include "utils/ag_cache.h" +typedef struct EntityResultRelInfoCacheEntry +{ + Oid relid; + ResultRelInfo *resultRelInfo; +} EntityResultRelInfoCacheEntry; + +typedef struct LabelRelationCacheEntry +{ + int32 label_id; + Oid relation; + char *label_name; +} LabelRelationCacheEntry; + /* RLS helper function declarations */ static void get_policies_for_relation(Relation relation, CmdType cmd, Oid user_id, List **permissive_policies, @@ -54,67 +68,18 @@ static void sort_policies_by_name(List *policies); static int row_security_policy_cmp(const ListCell *a, const ListCell *b); static bool check_role_for_policy(ArrayType *policy_roles, Oid user_id); -/* - * Given the graph name and the label name, create a ResultRelInfo for the table - * those two variables represent. Open the Indices too. - */ -ResultRelInfo *create_entity_result_rel_info(EState *estate, char *graph_name, - char *label_name) +ResultRelInfo *create_entity_result_rel_info_by_oid(EState *estate, Oid relid) { - RangeVar *rv = NULL; - Relation label_relation = NULL; - ResultRelInfo *resultRelInfo = NULL; - ParseState *pstate = NULL; - RangeTblEntry *rte = NULL; - int pii = 0; - - /* create a new parse state for this operation */ - pstate = make_parsestate(NULL); + Relation label_relation; + ResultRelInfo *resultRelInfo; + label_relation = table_open(relid, RowExclusiveLock); resultRelInfo = palloc(sizeof(ResultRelInfo)); - if (strlen(label_name) == 0) - { - rv = makeRangeVar(graph_name, AG_DEFAULT_LABEL_VERTEX, -1); - } - else - { - rv = makeRangeVar(graph_name, label_name, -1); - } - - label_relation = parserOpenTable(pstate, rv, RowExclusiveLock); - - /* - * Get the rte to determine the correct perminfoindex value. Some rtes - * may have it set up, some created here (executor) may not. - * - * Note: The RTEPermissionInfo structure was added in PostgreSQL version 16. - * - * Note: We use the list_length because exec_rt_fetch starts at 1, not 0. - * Doing this gives us the last rte in the es_range_table list, which - * is the rte in question. - * - * If the rte is created here and doesn't have a perminfoindex, we - * need to pass on a 0. Otherwise, later on GetResultRTEPermissionInfo - * will attempt to get the rte's RTEPermissionInfo data, which doesn't - * exist. - * - * TODO: Ideally, we should consider creating the RTEPermissionInfo data, - * but as this is just a read of the label relation, it is likely - * unnecessary. - */ - rte = exec_rt_fetch(list_length(estate->es_range_table), estate); - pii = (rte->perminfoindex == 0) ? 0 : list_length(estate->es_range_table); - - /* initialize the resultRelInfo */ - InitResultRelInfo(resultRelInfo, label_relation, pii, NULL, + InitResultRelInfo(resultRelInfo, label_relation, 0, NULL, estate->es_instrument); - - /* open the indices */ ExecOpenIndices(resultRelInfo, false); - free_parsestate(pstate); - return resultRelInfo; } @@ -128,6 +93,115 @@ void destroy_entity_result_rel_info(ResultRelInfo *result_rel_info) table_close(result_rel_info->ri_RelationDesc, RowExclusiveLock); } +HTAB *create_entity_result_rel_info_cache(const char *name) +{ + HASHCTL hashctl; + + MemSet(&hashctl, 0, sizeof(hashctl)); + hashctl.keysize = sizeof(Oid); + hashctl.entrysize = sizeof(EntityResultRelInfoCacheEntry); + hashctl.hcxt = CurrentMemoryContext; + + return hash_create(name, 8, &hashctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); +} + +ResultRelInfo *get_entity_result_rel_info(EState *estate, + HTAB *result_rel_info_cache, + Oid relid) +{ + EntityResultRelInfoCacheEntry *entry; + bool found; + + entry = hash_search(result_rel_info_cache, &relid, HASH_ENTER, &found); + if (!found) + { + entry->resultRelInfo = create_entity_result_rel_info_by_oid(estate, + relid); + } + + return entry->resultRelInfo; +} + +void destroy_entity_result_rel_info_cache(HTAB *result_rel_info_cache) +{ + HASH_SEQ_STATUS hash_seq; + EntityResultRelInfoCacheEntry *entry; + + hash_seq_init(&hash_seq, result_rel_info_cache); + while ((entry = hash_seq_search(&hash_seq)) != NULL) + { + destroy_entity_result_rel_info(entry->resultRelInfo); + } + + hash_destroy(result_rel_info_cache); +} + +HTAB *create_label_relation_cache(const char *name) +{ + HASHCTL hashctl; + + MemSet(&hashctl, 0, sizeof(hashctl)); + hashctl.keysize = sizeof(int32); + hashctl.entrysize = sizeof(LabelRelationCacheEntry); + hashctl.hcxt = CurrentMemoryContext; + + return hash_create(name, 8, &hashctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); +} + +bool get_label_relation_from_cache(HTAB *label_relation_cache, Oid graph_oid, + int32 label_id, Oid *relation, + char **label_name) +{ + LabelRelationCacheEntry *entry; + bool found; + + entry = hash_search(label_relation_cache, &label_id, HASH_ENTER, &found); + if (!found) + { + label_cache_data *label; + char *cached_label_name; + + label = search_label_graph_oid_cache_cached(graph_oid, label_id); + if (label == NULL) + { + hash_search(label_relation_cache, &label_id, HASH_REMOVE, NULL); + return false; + } + + cached_label_name = NameStr(label->name); + entry->relation = label->relation; + entry->label_name = IS_AG_DEFAULT_LABEL(cached_label_name) ? + "" : pstrdup(cached_label_name); + } + + *relation = entry->relation; + if (label_name != NULL) + { + *label_name = entry->label_name; + } + + return true; +} + +void destroy_label_relation_cache(HTAB *label_relation_cache) +{ + HASH_SEQ_STATUS hash_seq; + LabelRelationCacheEntry *entry; + + hash_seq_init(&hash_seq, label_relation_cache); + while ((entry = hash_seq_search(&hash_seq)) != NULL) + { + if (entry->label_name != NULL && entry->label_name[0] != '\0') + { + pfree(entry->label_name); + } + } + + hash_destroy(label_relation_cache); +} + TupleTableSlot *populate_vertex_tts( TupleTableSlot *elemTupleSlot, agtype_value *id, agtype_value *properties) { @@ -196,11 +270,56 @@ TupleTableSlot *populate_edge_tts( } +HTAB *create_entity_exists_index_cache(const char *name) +{ + HASHCTL hashctl; + + MemSet(&hashctl, 0, sizeof(hashctl)); + hashctl.keysize = sizeof(Oid); + hashctl.entrysize = sizeof(IndexCacheEntry); + hashctl.hcxt = CurrentMemoryContext; + + return hash_create(name, 8, &hashctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); +} + +void destroy_entity_exists_index_cache(HTAB *index_cache) +{ + destroy_index_cache(index_cache, true); +} + +void destroy_index_cache(HTAB *index_cache, bool close_relations) +{ + HASH_SEQ_STATUS hash_seq; + IndexCacheEntry *entry; + + hash_seq_init(&hash_seq, index_cache); + while ((entry = hash_seq_search(&hash_seq)) != NULL) + { + if (entry->slot != NULL) + { + ExecDropSingleTupleTableSlot(entry->slot); + } + if (close_relations && entry->rel != NULL) + { + table_close(entry->rel, AccessShareLock); + } + } + + hash_destroy(index_cache); +} + +bool entity_exists(EState *estate, Oid graph_oid, graphid id) +{ + return entity_exists_with_cache(estate, graph_oid, id, NULL, NULL); +} + /* * Find out if the entity still exists. This is for 'implicit' deletion * of an entity. */ -bool entity_exists(EState *estate, Oid graph_oid, graphid id) +bool entity_exists_with_cache(EState *estate, Oid graph_oid, graphid id, + HTAB *index_cache, HTAB *label_relation_cache) { label_cache_data *label; ScanKeyData scan_keys[1]; @@ -211,12 +330,33 @@ bool entity_exists(EState *estate, Oid graph_oid, graphid id) TupleTableSlot *slot; Oid index_oid = InvalidOid; CommandId saved_curcid; + CommandId current_cid; + IndexCacheEntry *cache_entry = NULL; + Oid relid; + int32 label_id; /* * Extract the label id from the graph id and get the table name * the entity is part of. */ - label = search_label_graph_oid_cache(graph_oid, GET_LABEL_ID(id)); + label_id = GET_LABEL_ID(id); + if (label_relation_cache != NULL) + { + if (!get_label_relation_from_cache(label_relation_cache, graph_oid, + label_id, &relid, NULL)) + { + return false; + } + } + else + { + label = search_label_graph_oid_cache_cached(graph_oid, label_id); + if (label == NULL) + { + return false; + } + relid = label->relation; + } /* Setup the scan key to be the graphid */ ScanKeyInit(&scan_keys[0], 1, BTEqualStrategyNumber, @@ -236,19 +376,54 @@ bool entity_exists(EState *estate, Oid graph_oid, graphid id) * are visible at the current curcid become invisible. */ saved_curcid = estate->es_snapshot->curcid; - estate->es_snapshot->curcid = Max(saved_curcid, - GetCurrentCommandId(false)); + current_cid = GetCurrentCommandId(false); + estate->es_snapshot->curcid = Max(saved_curcid, current_cid); - rel = table_open(label->relation, RowExclusiveLock); + if (index_cache != NULL) + { + bool found; - index_oid = find_usable_btree_index_for_attr(rel, 1); + cache_entry = hash_search(index_cache, &relid, HASH_ENTER, + &found); + if (!found) + { + init_index_cache_entry(cache_entry); + cache_entry->rel = table_open(relid, AccessShareLock); + rel = cache_entry->rel; + cache_entry->index_oid = find_usable_btree_index_for_attr(rel, 1); + cache_entry->index_oid_cached = true; + } + else + { + rel = cache_entry->rel; + } + index_oid = cache_entry->index_oid; + slot = cache_entry->slot; + } + else + { + rel = table_open(relid, AccessShareLock); + index_oid = find_usable_btree_index_for_attr(rel, 1); + slot = NULL; + } if (OidIsValid(index_oid)) { IndexScanDesc index_scan_desc; Relation index_rel; - slot = table_slot_create(rel, NULL); + if (slot == NULL) + { + slot = table_slot_create(rel, NULL); + if (cache_entry != NULL) + { + cache_entry->slot = slot; + } + } + else + { + ExecClearTuple(slot); + } index_rel = index_open(index_oid, AccessShareLock); @@ -262,7 +437,11 @@ bool entity_exists(EState *estate, Oid graph_oid, graphid id) index_endscan(index_scan_desc); index_close(index_rel, AccessShareLock); - ExecDropSingleTupleTableSlot(slot); + ExecClearTuple(slot); + if (index_cache == NULL) + { + ExecDropSingleTupleTableSlot(slot); + } } else { @@ -281,7 +460,10 @@ bool entity_exists(EState *estate, Oid graph_oid, graphid id) table_endscan(scan_desc); } - table_close(rel, RowExclusiveLock); + if (index_cache == NULL) + { + table_close(rel, AccessShareLock); + } /* Restore the original curcid */ estate->es_snapshot->curcid = saved_curcid; @@ -655,7 +837,7 @@ add_with_check_options(Relation rel, wco->polname = NULL; wco->cascaded = false; - if (list_length(permissive_quals) == 1) + if (lnext(permissive_quals, list_head(permissive_quals)) == NULL) { wco->qual = (Node *) linitial(permissive_quals); } @@ -859,7 +1041,7 @@ add_security_quals(int rt_index, * Then add a single security qual combining together the USING * clauses from all the permissive policies using OR. */ - if (list_length(permissive_quals) == 1) + if (lnext(permissive_quals, list_head(permissive_quals)) == NULL) { rowsec_expr = (Expr *) linitial(permissive_quals); } @@ -1019,13 +1201,13 @@ check_rls_for_tuple(Relation rel, HeapTuple tuple, CmdType cmd) List *permissive_policies; List *restrictive_policies; List *securityQuals = NIL; - ListCell *lc; Oid user_id; bool hasSubLinks = false; bool result = true; EState *estate; ExprContext *econtext; TupleTableSlot *slot; + ExprState *qualExpr; /* If RLS is not enabled, tuple passes */ if (check_enable_rls(RelationGetRelid(rel), InvalidOid, true) != RLS_ENABLED) @@ -1064,32 +1246,9 @@ check_rls_for_tuple(Relation rel, HeapTuple tuple, CmdType cmd) ExecStoreHeapTuple(tuple, slot, false); econtext->ecxt_scantuple = slot; - /* Compile and evaluate each qual */ - foreach(lc, securityQuals) - { - Expr *qual = (Expr *) lfirst(lc); - ExprState *qualExpr; - List *qualList; - - /* ExecPrepareQual expects a List */ - if (!IsA(qual, List)) - { - qualList = list_make1(qual); - } - else - { - qualList = (List *) qual; - } - - /* Use ExecPrepareQual for standalone expression evaluation */ - qualExpr = ExecPrepareQual(qualList, estate); - - if (!ExecQual(qualExpr, econtext)) - { - result = false; - break; - } - } + /* Compile the qual list once and evaluate it as an implicit AND. */ + qualExpr = ExecPrepareQual(securityQuals, estate); + result = ExecQual(qualExpr, econtext); /* Clean up */ ExecDropSingleTupleTableSlot(slot); diff --git a/src/backend/nodes/cypher_copyfuncs.c b/src/backend/nodes/cypher_copyfuncs.c index 283096ca7..f14e0d808 100644 --- a/src/backend/nodes/cypher_copyfuncs.c +++ b/src/backend/nodes/cypher_copyfuncs.c @@ -86,6 +86,7 @@ void copy_cypher_create_path(ExtensibleNode *newnode, const ExtensibleNode *from COPY_SCALAR_FIELD(path_attr_num); COPY_STRING_FIELD(var_name); + COPY_SCALAR_FIELD(path_length); COPY_NODE_FIELD(target_nodes); } @@ -121,7 +122,13 @@ void copy_cypher_update_information(ExtensibleNode *newnode, const ExtensibleNod COPY_SCALAR_FIELD(flags); COPY_SCALAR_FIELD(tuple_position); COPY_STRING_FIELD(graph_name); + COPY_SCALAR_FIELD(graph_oid); COPY_STRING_FIELD(clause_name); + COPY_SCALAR_FIELD(set_item_count); + + extended_newnode->last_update_indexes = NULL; + extended_newnode->last_update_index_count = 0; + extended_newnode->last_update_indexes_valid = false; } /* copy function for cypher_update_item */ @@ -150,6 +157,10 @@ void copy_cypher_delete_information(ExtensibleNode *newnode, const ExtensibleNod COPY_STRING_FIELD(graph_name); COPY_SCALAR_FIELD(graph_oid); COPY_SCALAR_FIELD(detach); + COPY_SCALAR_FIELD(delete_item_count); + + extended_newnode->delete_item_positions = NULL; + extended_newnode->delete_item_positions_valid = false; } /* copy function for cypher_delete_item */ @@ -170,8 +181,11 @@ void copy_cypher_merge_information(ExtensibleNode *newnode, const ExtensibleNode COPY_SCALAR_FIELD(graph_oid); COPY_SCALAR_FIELD(merge_function_attr); COPY_NODE_FIELD(path); + COPY_SCALAR_FIELD(path_length); COPY_NODE_FIELD(on_match_set_info); COPY_NODE_FIELD(on_create_set_info); + COPY_SCALAR_FIELD(on_match_set_item_count); + COPY_SCALAR_FIELD(on_create_set_item_count); } /* copy function for cypher_predicate_function */ diff --git a/src/backend/nodes/cypher_outfuncs.c b/src/backend/nodes/cypher_outfuncs.c index 84d32a8f8..7348433ff 100644 --- a/src/backend/nodes/cypher_outfuncs.c +++ b/src/backend/nodes/cypher_outfuncs.c @@ -393,6 +393,7 @@ void out_cypher_create_path(StringInfo str, const ExtensibleNode *node) WRITE_NODE_FIELD(target_nodes); WRITE_INT32_FIELD(path_attr_num); WRITE_STRING_FIELD(var_name); + WRITE_INT32_FIELD(path_length); } /* serialization function for the cypher_target_node ExtensibleNode. */ @@ -425,7 +426,9 @@ void out_cypher_update_information(StringInfo str, const ExtensibleNode *node) WRITE_INT32_FIELD(flags); WRITE_INT32_FIELD(tuple_position); WRITE_STRING_FIELD(graph_name); + WRITE_INT32_FIELD(graph_oid); WRITE_STRING_FIELD(clause_name); + WRITE_INT32_FIELD(set_item_count); } /* serialization function for the cypher_update_item ExtensibleNode. */ @@ -454,6 +457,7 @@ void out_cypher_delete_information(StringInfo str, const ExtensibleNode *node) WRITE_STRING_FIELD(graph_name); WRITE_INT32_FIELD(graph_oid); WRITE_BOOL_FIELD(detach); + WRITE_INT32_FIELD(delete_item_count); } /* serialization function for the cypher_delete_item ExtensibleNode. */ @@ -474,8 +478,11 @@ void out_cypher_merge_information(StringInfo str, const ExtensibleNode *node) WRITE_INT32_FIELD(graph_oid); WRITE_INT32_FIELD(merge_function_attr); WRITE_NODE_FIELD(path); + WRITE_INT32_FIELD(path_length); WRITE_NODE_FIELD(on_match_set_info); WRITE_NODE_FIELD(on_create_set_info); + WRITE_INT32_FIELD(on_match_set_item_count); + WRITE_INT32_FIELD(on_create_set_item_count); } /* @@ -494,4 +501,3 @@ outChar(StringInfo str, char c) outToken(str, in); } - diff --git a/src/backend/nodes/cypher_readfuncs.c b/src/backend/nodes/cypher_readfuncs.c index 1e7e0ef82..4187683ba 100644 --- a/src/backend/nodes/cypher_readfuncs.c +++ b/src/backend/nodes/cypher_readfuncs.c @@ -213,6 +213,7 @@ void read_cypher_create_path(struct ExtensibleNode *node) READ_NODE_FIELD(target_nodes); READ_INT_FIELD(path_attr_num); READ_STRING_FIELD(var_name); + READ_INT_FIELD(path_length); } /* @@ -251,7 +252,13 @@ void read_cypher_update_information(struct ExtensibleNode *node) READ_UINT_FIELD(flags); READ_INT_FIELD(tuple_position); READ_STRING_FIELD(graph_name); + READ_UINT_FIELD(graph_oid); READ_STRING_FIELD(clause_name); + READ_INT_FIELD(set_item_count); + + local_node->last_update_indexes = NULL; + local_node->last_update_index_count = 0; + local_node->last_update_indexes_valid = false; } /* @@ -286,6 +293,10 @@ void read_cypher_delete_information(struct ExtensibleNode *node) READ_STRING_FIELD(graph_name); READ_UINT_FIELD(graph_oid); READ_BOOL_FIELD(detach); + READ_INT_FIELD(delete_item_count); + + local_node->delete_item_positions = NULL; + local_node->delete_item_positions_valid = false; } /* @@ -312,8 +323,11 @@ void read_cypher_merge_information(struct ExtensibleNode *node) READ_UINT_FIELD(graph_oid); READ_INT_FIELD(merge_function_attr); READ_NODE_FIELD(path); + READ_INT_FIELD(path_length); READ_NODE_FIELD(on_match_set_info); READ_NODE_FIELD(on_create_set_info); + READ_INT_FIELD(on_match_set_item_count); + READ_INT_FIELD(on_create_set_item_count); } /* diff --git a/src/backend/optimizer/cypher_createplan.c b/src/backend/optimizer/cypher_createplan.c index 288da05d7..d7434dece 100644 --- a/src/backend/optimizer/cypher_createplan.c +++ b/src/backend/optimizer/cypher_createplan.c @@ -31,68 +31,42 @@ const CustomScanMethods cypher_delete_plan_methods = { const CustomScanMethods cypher_merge_plan_methods = { "Cypher Merge", create_cypher_merge_plan_state}; -Plan *plan_cypher_create_path(PlannerInfo *root, RelOptInfo *rel, - CustomPath *best_path, List *tlist, - List *clauses, List *custom_plans) +static int get_custom_plan_width(CustomPath *best_path) { - CustomScan *cs; - Plan *subplan = linitial(custom_plans); + if (best_path->path.pathtarget == NULL) + return 0; - cs = makeNode(CustomScan); + return best_path->path.pathtarget->width; +} +static void apply_custom_plan_metadata(CustomScan *cs, CustomPath *best_path) +{ cs->scan.plan.startup_cost = best_path->path.startup_cost; cs->scan.plan.total_cost = best_path->path.total_cost; +#if PG_VERSION_NUM >= 170000 + cs->scan.plan.disabled_nodes = best_path->path.disabled_nodes; +#endif cs->scan.plan.plan_rows = best_path->path.rows; - cs->scan.plan.plan_width = 0; + cs->scan.plan.plan_width = get_custom_plan_width(best_path); cs->scan.plan.parallel_aware = best_path->path.parallel_aware; cs->scan.plan.parallel_safe = best_path->path.parallel_safe; - - /* Set later in set_plan_refs */ - cs->scan.plan.plan_node_id = 0; - cs->scan.plan.targetlist = tlist; - cs->scan.plan.qual = NIL; - cs->scan.plan.lefttree = NULL; - cs->scan.plan.righttree = NULL; - cs->scan.plan.initPlan = NIL; - - cs->scan.plan.extParam = NULL; - cs->scan.plan.allParam = NULL; - - cs->scan.scanrelid = 0; - - cs->flags = best_path->flags; - - cs->custom_plans = custom_plans; - cs->custom_exprs = NIL; - cs->custom_private = best_path->custom_private; - cs->custom_scan_tlist = subplan->targetlist; - cs->custom_relids = NULL; - cs->methods = &cypher_create_plan_methods; - - return (Plan *)cs; } -Plan *plan_cypher_set_path(PlannerInfo *root, RelOptInfo *rel, - CustomPath *best_path, List *tlist, - List *clauses, List *custom_plans) +static CustomScan *make_cypher_dml_plan(CustomPath *best_path, List *tlist, + List *custom_plans, + const CustomScanMethods *methods) { CustomScan *cs; Plan *subplan = linitial(custom_plans); cs = makeNode(CustomScan); - cs->scan.plan.startup_cost = best_path->path.startup_cost; - cs->scan.plan.total_cost = best_path->path.total_cost; - - cs->scan.plan.plan_rows = best_path->path.rows; - cs->scan.plan.plan_width = 0; + apply_custom_plan_metadata(cs, best_path); - cs->scan.plan.parallel_aware = best_path->path.parallel_aware; - cs->scan.plan.parallel_safe = best_path->path.parallel_safe; - - cs->scan.plan.plan_node_id = 0; /* Set later in set_plan_refs */ + /* Set later in set_plan_refs */ + cs->scan.plan.plan_node_id = 0; cs->scan.plan.targetlist = tlist; cs->scan.plan.qual = NIL; cs->scan.plan.lefttree = NULL; @@ -110,11 +84,26 @@ Plan *plan_cypher_set_path(PlannerInfo *root, RelOptInfo *rel, cs->custom_exprs = NIL; cs->custom_private = best_path->custom_private; cs->custom_scan_tlist = subplan->targetlist; - cs->custom_relids = NULL; - cs->methods = &cypher_set_plan_methods; + cs->methods = methods; + + return cs; +} + +Plan *plan_cypher_create_path(PlannerInfo *root, RelOptInfo *rel, + CustomPath *best_path, List *tlist, + List *clauses, List *custom_plans) +{ + return (Plan *)make_cypher_dml_plan(best_path, tlist, custom_plans, + &cypher_create_plan_methods); +} - return (Plan *)cs; +Plan *plan_cypher_set_path(PlannerInfo *root, RelOptInfo *rel, + CustomPath *best_path, List *tlist, + List *clauses, List *custom_plans) +{ + return (Plan *)make_cypher_dml_plan(best_path, tlist, custom_plans, + &cypher_set_plan_methods); } /* @@ -125,57 +114,8 @@ Plan *plan_cypher_delete_path(PlannerInfo *root, RelOptInfo *rel, CustomPath *best_path, List *tlist, List *clauses, List *custom_plans) { - CustomScan *cs; - Plan *subplan = linitial(custom_plans); - - cs = makeNode(CustomScan); - - cs->scan.plan.startup_cost = best_path->path.startup_cost; - cs->scan.plan.total_cost = best_path->path.total_cost; - - cs->scan.plan.plan_rows = best_path->path.rows; - cs->scan.plan.plan_width = 0; - - cs->scan.plan.parallel_aware = best_path->path.parallel_aware; - cs->scan.plan.parallel_safe = best_path->path.parallel_safe; - - cs->scan.plan.plan_node_id = 0; /* Set later in set_plan_refs */ - /* - * the scan list of the delete node, used for its ScanTupleSlot used - * by its parent in the execution phase. - */ - cs->scan.plan.targetlist = tlist; - cs->scan.plan.qual = NIL; - cs->scan.plan.lefttree = NULL; - cs->scan.plan.righttree = NULL; - cs->scan.plan.initPlan = NIL; - - cs->scan.plan.extParam = NULL; - cs->scan.plan.allParam = NULL; - - /* - * We do not want Postgres to assume we are scanning a table, postgres' - * optimizer will make assumptions about our targetlist that are false - */ - cs->scan.scanrelid = 0; - - cs->flags = best_path->flags; - - /* child plan nodes are here, Postgres processed them for us. */ - cs->custom_plans = custom_plans; - cs->custom_exprs = NIL; - /* transfer delete metadata needed by the DELETE clause. */ - cs->custom_private = best_path->custom_private; - /* - * the scan list of the delete node's children, used for ScanTupleSlot - * in execution. - */ - cs->custom_scan_tlist = subplan->targetlist; - - cs->custom_relids = NULL; - cs->methods = &cypher_delete_plan_methods; - - return (Plan *)cs; + return (Plan *)make_cypher_dml_plan(best_path, tlist, custom_plans, + &cypher_delete_plan_methods); } /* @@ -186,55 +126,6 @@ Plan *plan_cypher_merge_path(PlannerInfo *root, RelOptInfo *rel, CustomPath *best_path, List *tlist, List *clauses, List *custom_plans) { - CustomScan *cs; - Plan *subplan = linitial(custom_plans); - - cs = makeNode(CustomScan); - - cs->scan.plan.startup_cost = best_path->path.startup_cost; - cs->scan.plan.total_cost = best_path->path.total_cost; - - cs->scan.plan.plan_rows = best_path->path.rows; - cs->scan.plan.plan_width = 0; - - cs->scan.plan.parallel_aware = best_path->path.parallel_aware; - cs->scan.plan.parallel_safe = best_path->path.parallel_safe; - - cs->scan.plan.plan_node_id = 0; /* Set later in set_plan_refs */ - /* - * the scan list of the merge node, used for its ScanTupleSlot used - * by its parent in the execution phase. - */ - cs->scan.plan.targetlist = tlist; - cs->scan.plan.qual = NIL; - cs->scan.plan.lefttree = NULL; - cs->scan.plan.righttree = NULL; - cs->scan.plan.initPlan = NIL; - - cs->scan.plan.extParam = NULL; - cs->scan.plan.allParam = NULL; - - /* - * We do not want Postgres to assume we are scanning a table, postgres' - * optimizer will make assumptions about our targetlist that are false - */ - cs->scan.scanrelid = 0; - - cs->flags = best_path->flags; - - /* child plan nodes are here, Postgres processed them for us. */ - cs->custom_plans = custom_plans; - cs->custom_exprs = NIL; - /* transfer delete metadata needed by the MERGE clause. */ - cs->custom_private = best_path->custom_private; - /* - * the scan list of the merge node's children, used for ScanTupleSlot - * in execution. - */ - cs->custom_scan_tlist = subplan->targetlist; - - cs->custom_relids = NULL; - cs->methods = &cypher_merge_plan_methods; - - return (Plan *)cs; + return (Plan *)make_cypher_dml_plan(best_path, tlist, custom_plans, + &cypher_merge_plan_methods); } diff --git a/src/backend/optimizer/cypher_pathnode.c b/src/backend/optimizer/cypher_pathnode.c index 5e4344254..664093aa5 100644 --- a/src/backend/optimizer/cypher_pathnode.c +++ b/src/backend/optimizer/cypher_pathnode.c @@ -25,12 +25,19 @@ #include "optimizer/cypher_pathnode.h" #include "parser/cypher_analyze.h" #include "executor/cypher_utils.h" +#include "optimizer/optimizer.h" +#include "optimizer/pathnode.h" #include "optimizer/subselect.h" #include "nodes/makefuncs.h" static Const *convert_sublink_to_subplan(PlannerInfo *root, List *custom_private); static bool expr_has_sublink(Node *node, void *context); +static CustomPath *make_cypher_dml_path(RelOptInfo *rel, List *custom_private, + const CustomPathMethods *methods); +static Path *select_best_child_path(RelOptInfo *rel); +static void initialize_cypher_dml_path(CustomPath *cp, RelOptInfo *rel); +static void apply_child_path_costs(CustomPath *cp, Path *best_child); const CustomPathMethods cypher_create_path_methods = { CREATE_PATH_NAME, plan_cypher_create_path, NULL}; @@ -44,73 +51,14 @@ const CustomPathMethods cypher_merge_path_methods = { CustomPath *create_cypher_create_path(PlannerInfo *root, RelOptInfo *rel, List *custom_private) { - CustomPath *cp; - - cp = makeNode(CustomPath); - - cp->path.pathtype = T_CustomScan; - - cp->path.parent = rel; - cp->path.pathtarget = rel->reltarget; - - cp->path.param_info = NULL; - - /* Do not allow parallel methods */ - cp->path.parallel_aware = false; - cp->path.parallel_safe = false; - cp->path.parallel_workers = 0; - - cp->path.rows = 0; /* Basic CREATE will not return rows */ - cp->path.startup_cost = 0; /* Basic CREATE will not fetch any pages */ - cp->path.total_cost = 0; - - /* No output ordering for basic CREATE */ - cp->path.pathkeys = NULL; - - /* Disable all custom flags for now */ - cp->flags = 0; - - cp->custom_paths = rel->pathlist; - cp->custom_private = custom_private; - cp->methods = &cypher_create_path_methods; - - return cp; + return make_cypher_dml_path(rel, custom_private, + &cypher_create_path_methods); } CustomPath *create_cypher_set_path(PlannerInfo *root, RelOptInfo *rel, List *custom_private) { - CustomPath *cp; - - cp = makeNode(CustomPath); - - cp->path.pathtype = T_CustomScan; - - cp->path.parent = rel; - cp->path.pathtarget = rel->reltarget; - - cp->path.param_info = NULL; - - /* Do not allow parallel methods */ - cp->path.parallel_aware = false; - cp->path.parallel_safe = false; - cp->path.parallel_workers = 0; - - cp->path.rows = 0; /* Basic SET will not return rows */ - cp->path.startup_cost = 0; /* Basic SET will not fetch any pages */ - cp->path.total_cost = 0; - - /* No output ordering for basic SET */ - cp->path.pathkeys = NULL; - - /* Disable all custom flags for now */ - cp->flags = 0; - - cp->custom_paths = rel->pathlist; - cp->custom_private = custom_private; - cp->methods = &cypher_set_path_methods; - - return cp; + return make_cypher_dml_path(rel, custom_private, &cypher_set_path_methods); } /* @@ -120,52 +68,47 @@ CustomPath *create_cypher_set_path(PlannerInfo *root, RelOptInfo *rel, CustomPath *create_cypher_delete_path(PlannerInfo *root, RelOptInfo *rel, List *custom_private) { - CustomPath *cp; - - cp = makeNode(CustomPath); - - cp->path.pathtype = T_CustomScan; - - cp->path.parent = rel; - cp->path.pathtarget = rel->reltarget; - - cp->path.param_info = NULL; + return make_cypher_dml_path(rel, custom_private, + &cypher_delete_path_methods); +} - /* Do not allow parallel methods */ - cp->path.parallel_aware = false; - cp->path.parallel_safe = false; - cp->path.parallel_workers = 0; +/* + * Creates a merge path. Makes the original path a child of the new + * path. We leave it to the caller to replace the pathlist of the rel. + */ +CustomPath *create_cypher_merge_path(PlannerInfo *root, RelOptInfo *rel, + List *custom_private) +{ + /* + * Store the metadata Merge will need in the execution phase. + * We may have a sublink here in case the user used a list + * comprehension in merge. + */ + if (rel->subroot->parse->hasSubLinks) + { + custom_private = list_make1(convert_sublink_to_subplan(root, + custom_private)); + } - cp->path.rows = 0; - cp->path.startup_cost = 0; - cp->path.total_cost = 0; + return make_cypher_dml_path(rel, custom_private, &cypher_merge_path_methods); +} - /* No output ordering for basic SET */ - cp->path.pathkeys = NULL; +static CustomPath *make_cypher_dml_path(RelOptInfo *rel, List *custom_private, + const CustomPathMethods *methods) +{ + CustomPath *cp = makeNode(CustomPath); - /* Disable all custom flags for now */ + initialize_cypher_dml_path(cp, rel); cp->flags = 0; - - /* Make the original paths the children of the new path */ - cp->custom_paths = rel->pathlist; - /* Store the metadata Delete will need in the execution phase. */ cp->custom_private = custom_private; - /* Tells Postgres how to turn this path to the correct CustomScan */ - cp->methods = &cypher_delete_path_methods; + cp->methods = methods; return cp; } -/* - * Creates a merge path. Makes the original path a child of the new - * path. We leave it to the caller to replace the pathlist of the rel. - */ -CustomPath *create_cypher_merge_path(PlannerInfo *root, RelOptInfo *rel, - List *custom_private) +static void initialize_cypher_dml_path(CustomPath *cp, RelOptInfo *rel) { - CustomPath *cp; - - cp = makeNode(CustomPath); + Path *best_child; cp->path.pathtype = T_CustomScan; @@ -179,37 +122,12 @@ CustomPath *create_cypher_merge_path(PlannerInfo *root, RelOptInfo *rel, cp->path.parallel_safe = false; cp->path.parallel_workers = 0; - cp->path.rows = 0; - cp->path.startup_cost = 0; - cp->path.total_cost = 0; + best_child = select_best_child_path(rel); + cp->custom_paths = list_make1(best_child); + apply_child_path_costs(cp, best_child); - /* No output ordering for basic SET */ + /* Cypher DML nodes do not preserve child output ordering. */ cp->path.pathkeys = NULL; - - /* Disable all custom flags for now */ - cp->flags = 0; - - /* Make the original paths the children of the new path */ - cp->custom_paths = rel->pathlist; - - /* - * Store the metadata Merge will need in the execution phase. - * We may have a sublink here in case the user used a list - * comprehension in merge. - */ - if (rel->subroot->parse->hasSubLinks) - { - cp->custom_private = list_make1(convert_sublink_to_subplan(root, custom_private)); - } - else - { - cp->custom_private = custom_private; - } - - /* Tells Postgres how to turn this path to the correct CustomScan */ - cp->methods = &cypher_merge_path_methods; - - return cp; } /* @@ -224,7 +142,7 @@ static Const *convert_sublink_to_subplan(PlannerInfo *root, List *custom_private char *serialized_data = NULL; Const *c = NULL; ListCell *lc = NULL; - StringInfo str = makeStringInfo(); + bool changed = false; c = linitial(custom_private); serialized_data = (char *)c->constvalue; @@ -241,14 +159,22 @@ static Const *convert_sublink_to_subplan(PlannerInfo *root, List *custom_private if (expr_has_sublink(prop_expr, NULL)) { node->prop_expr = (Expr *) SS_process_sublinks(root, prop_expr, false); + changed = true; } } + if (!changed) + return c; + /* Serialize the information again and return it. */ - outNode(str, (Node *)merge_information); + { + StringInfo str = makeStringInfo(); + + outNode(str, (Node *)merge_information); - return makeConst(INTERNALOID, -1, InvalidOid, str->len, - PointerGetDatum(str->data), false, false); + return makeConst(INTERNALOID, -1, InvalidOid, str->len, + PointerGetDatum(str->data), false, false); + } } /* @@ -268,3 +194,62 @@ static bool expr_has_sublink(Node *node, void *context) return cypher_expr_tree_walker(node, expr_has_sublink, context); } + +static Path *select_best_child_path(RelOptInfo *rel) +{ + Path *best_child = NULL; + ListCell *lc; + + if (rel->cheapest_total_path != NULL) + { + return rel->cheapest_total_path; + } + + foreach (lc, rel->pathlist) + { + Path *child = (Path *)lfirst(lc); + + if (best_child == NULL || + compare_path_costs(child, best_child, TOTAL_COST) < 0) + best_child = child; + } + + if (best_child == NULL) + elog(ERROR, "Cypher custom path requires a child path"); + + return best_child; +} + +static void apply_child_path_costs(CustomPath *cp, Path *best_child) +{ + if (best_child == NULL) + { + cp->path.param_info = NULL; +#if PG_VERSION_NUM >= 170000 + cp->path.disabled_nodes = 0; +#endif + cp->path.rows = 0; + cp->path.startup_cost = 0; + cp->path.total_cost = 0; + return; + } + + /* + * The custom DML node still performs the graph-specific mutation, but its + * child paths are PostgreSQL-planned scans/subqueries. Preserve their + * estimates so upstream planning can see row counts, index selectivity, + * and scan costs instead of treating every Cypher DML clause as free. + * Preserve the child parameterization as well so add_path() can compare + * required outer relids using the same information as the child path. + * Charge one cpu_tuple_cost per input row for the mutation coordination + * work that happens above the child plan. + */ + cp->path.param_info = best_child->param_info; +#if PG_VERSION_NUM >= 170000 + cp->path.disabled_nodes = best_child->disabled_nodes; +#endif + cp->path.rows = best_child->rows; + cp->path.startup_cost = best_child->startup_cost; + cp->path.total_cost = best_child->total_cost + + cpu_tuple_cost * best_child->rows; +} diff --git a/src/backend/optimizer/cypher_paths.c b/src/backend/optimizer/cypher_paths.c index 6c4fd7e07..4bc4ca2b5 100644 --- a/src/backend/optimizer/cypher_paths.c +++ b/src/backend/optimizer/cypher_paths.c @@ -21,6 +21,8 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" +#include "utils/inval.h" +#include "utils/syscache.h" #include "optimizer/cypher_pathnode.h" #include "optimizer/cypher_paths.h" @@ -35,19 +37,27 @@ typedef enum cypher_clause_kind CYPHER_CLAUSE_MERGE } cypher_clause_kind; +typedef CustomPath *(*cypher_path_factory)(PlannerInfo *root, RelOptInfo *rel, + List *custom_private); + static set_rel_pathlist_hook_type prev_set_rel_pathlist_hook; +static Oid cypher_create_clause_func_oid = InvalidOid; +static Oid cypher_set_clause_func_oid = InvalidOid; +static Oid cypher_delete_clause_func_oid = InvalidOid; +static Oid cypher_merge_clause_func_oid = InvalidOid; +static bool cypher_clause_func_callback_registered = false; + static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static cypher_clause_kind get_cypher_clause_kind(RangeTblEntry *rte); -static void handle_cypher_create_clause(PlannerInfo *root, RelOptInfo *rel, - Index rti, RangeTblEntry *rte); -static void handle_cypher_set_clause(PlannerInfo *root, RelOptInfo *rel, - Index rti, RangeTblEntry *rte); -static void handle_cypher_delete_clause(PlannerInfo *root, RelOptInfo *rel, - Index rti, RangeTblEntry *rte); -static void handle_cypher_merge_clause(PlannerInfo *root, RelOptInfo *rel, - Index rti, RangeTblEntry *rte); +static void register_cypher_clause_function_oid_callbacks(void); +static void load_cypher_clause_function_oids(void); +static void invalidate_cypher_clause_function_oids(Datum arg, int cache_id, + uint32 hash_value); +static void replace_with_cypher_dml_path(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, + cypher_path_factory factory); void set_rel_pathlist_init(void) { @@ -69,16 +79,16 @@ static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, switch (get_cypher_clause_kind(rte)) { case CYPHER_CLAUSE_CREATE: - handle_cypher_create_clause(root, rel, rti, rte); + replace_with_cypher_dml_path(root, rel, rte, create_cypher_create_path); break; case CYPHER_CLAUSE_SET: - handle_cypher_set_clause(root, rel, rti, rte); + replace_with_cypher_dml_path(root, rel, rte, create_cypher_set_path); break; case CYPHER_CLAUSE_DELETE: - handle_cypher_delete_clause(root, rel, rti, rte); + replace_with_cypher_dml_path(root, rel, rte, create_cypher_delete_path); break; case CYPHER_CLAUSE_MERGE: - handle_cypher_merge_clause(root, rel, rti, rte); + replace_with_cypher_dml_path(root, rel, rte, create_cypher_merge_path); break; case CYPHER_CLAUSE_NONE: break; @@ -114,98 +124,75 @@ static cypher_clause_kind get_cypher_clause_kind(RangeTblEntry *rte) fe = (FuncExpr *)te->expr; - if (is_oid_ag_func(fe->funcid, CREATE_CLAUSE_FUNCTION_NAME)) + load_cypher_clause_function_oids(); + + if (fe->funcid == cypher_create_clause_func_oid) return CYPHER_CLAUSE_CREATE; - if (is_oid_ag_func(fe->funcid, SET_CLAUSE_FUNCTION_NAME)) + if (fe->funcid == cypher_set_clause_func_oid) return CYPHER_CLAUSE_SET; - if (is_oid_ag_func(fe->funcid, DELETE_CLAUSE_FUNCTION_NAME)) + if (fe->funcid == cypher_delete_clause_func_oid) return CYPHER_CLAUSE_DELETE; - if (is_oid_ag_func(fe->funcid, MERGE_CLAUSE_FUNCTION_NAME)) + if (fe->funcid == cypher_merge_clause_func_oid) return CYPHER_CLAUSE_MERGE; else return CYPHER_CLAUSE_NONE; } -/* replace all possible paths with our CustomPath */ -static void handle_cypher_delete_clause(PlannerInfo *root, RelOptInfo *rel, - Index rti, RangeTblEntry *rte) +static void register_cypher_clause_function_oid_callbacks(void) { - TargetEntry *te; - FuncExpr *fe; - List *custom_private; - CustomPath *cp; - - /* Add the pattern to the CustomPath */ - te = (TargetEntry *)llast(rte->subquery->targetList); - fe = (FuncExpr *)te->expr; - /* pass the const that holds the data structure to the path. */ - custom_private = fe->args; - - cp = create_cypher_delete_path(root, rel, custom_private); - - /* Discard any preexisting paths */ - rel->pathlist = NIL; - rel->partial_pathlist = NIL; - - add_path(rel, (Path *)cp); + if (!cypher_clause_func_callback_registered) + { + CacheRegisterSyscacheCallback(PROCOID, + invalidate_cypher_clause_function_oids, + (Datum)0); + CacheRegisterSyscacheCallback(PROCNAMEARGSNSP, + invalidate_cypher_clause_function_oids, + (Datum)0); + cypher_clause_func_callback_registered = true; + } } -/* - * Take the paths possible for the RelOptInfo that represents our - * _cypher_delete_clause function replace them with our delete clause - * path. The original paths will be children to the new delete path. - */ -static void handle_cypher_create_clause(PlannerInfo *root, RelOptInfo *rel, - Index rti, RangeTblEntry *rte) +static void load_cypher_clause_function_oids(void) { - TargetEntry *te; - FuncExpr *fe; - List *custom_private; - CustomPath *cp; + register_cypher_clause_function_oid_callbacks(); - /* Add the pattern to the CustomPath */ - te = (TargetEntry *)llast(rte->subquery->targetList); - fe = (FuncExpr *)te->expr; - /* pass the const that holds the data structure to the path. */ - custom_private = fe->args; + if (!OidIsValid(cypher_create_clause_func_oid)) + { + cypher_create_clause_func_oid = + get_ag_func_oid(CREATE_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); + } - cp = create_cypher_create_path(root, rel, custom_private); + if (!OidIsValid(cypher_set_clause_func_oid)) + { + cypher_set_clause_func_oid = + get_ag_func_oid(SET_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); + } - /* Discard any preexisting paths, they should be under the cp path */ - rel->pathlist = NIL; - rel->partial_pathlist = NIL; + if (!OidIsValid(cypher_delete_clause_func_oid)) + { + cypher_delete_clause_func_oid = + get_ag_func_oid(DELETE_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); + } - /* Add the new path to the rel. */ - add_path(rel, (Path *)cp); + if (!OidIsValid(cypher_merge_clause_func_oid)) + { + cypher_merge_clause_func_oid = + get_ag_func_oid(MERGE_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); + } } -/* replace all possible paths with our CustomPath */ -static void handle_cypher_set_clause(PlannerInfo *root, RelOptInfo *rel, - Index rti, RangeTblEntry *rte) +static void invalidate_cypher_clause_function_oids(Datum arg, int cache_id, + uint32 hash_value) { - TargetEntry *te; - FuncExpr *fe; - List *custom_private; - CustomPath *cp; - - /* Add the pattern to the CustomPath */ - te = (TargetEntry *)llast(rte->subquery->targetList); - fe = (FuncExpr *)te->expr; - /* pass the const that holds the data structure to the path. */ - custom_private = fe->args; - - cp = create_cypher_set_path(root, rel, custom_private); - - /* Discard any preexisting paths */ - rel->pathlist = NIL; - rel->partial_pathlist = NIL; - - add_path(rel, (Path *)cp); + cypher_create_clause_func_oid = InvalidOid; + cypher_set_clause_func_oid = InvalidOid; + cypher_delete_clause_func_oid = InvalidOid; + cypher_merge_clause_func_oid = InvalidOid; } -/* replace all possible paths with our CustomPath */ -static void handle_cypher_merge_clause(PlannerInfo *root, RelOptInfo *rel, - Index rti, RangeTblEntry *rte) +static void replace_with_cypher_dml_path(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, + cypher_path_factory factory) { TargetEntry *te; FuncExpr *fe; @@ -218,7 +205,7 @@ static void handle_cypher_merge_clause(PlannerInfo *root, RelOptInfo *rel, /* pass the const that holds the data structure to the path. */ custom_private = fe->args; - cp = create_cypher_merge_path(root, rel, custom_private); + cp = factory(root, rel, custom_private); /* Discard any preexisting paths */ rel->pathlist = NIL; diff --git a/src/backend/parser/cypher_analyze.c b/src/backend/parser/cypher_analyze.c index b2c9256ce..e72c8c889 100644 --- a/src/backend/parser/cypher_analyze.c +++ b/src/backend/parser/cypher_analyze.c @@ -27,13 +27,14 @@ #include "parser/parse_relation.h" #include "parser/parse_target.h" #include "utils/builtins.h" +#include "utils/inval.h" +#include "utils/syscache.h" #include "catalog/ag_graph.h" #include "parser/cypher_analyze.h" #include "parser/cypher_clause.h" #include "parser/cypher_parser.h" #include "utils/ag_func.h" -#include "utils/age_session_info.h" typedef bool (*cypher_expression_condition)(Node *expr); @@ -50,11 +51,16 @@ static Node *extra_node = NULL; static void build_explain_query(Query *query, Node *explain_node); static post_parse_analyze_hook_type prev_post_parse_analyze_hook; +static Oid cypher_func_oid = InvalidOid; +static bool cypher_func_oid_callback_registered = false; static void post_parse_analyze(ParseState *pstate, Query *query, JumbleState *jstate); static bool convert_cypher_walker(Node *node, ParseState *pstate); static bool is_rte_cypher(RangeTblEntry *rte); static bool is_func_cypher(FuncExpr *funcexpr); +static Oid get_cypher_func_oid(void); +static void invalidate_cypher_func_oid(Datum arg, int cache_id, + uint32 hash_value); static void convert_cypher_to_subquery(RangeTblEntry *rte, ParseState *pstate); static Name expr_get_const_name(Node *expr); static const char *expr_get_const_cstring(Node *expr, const char *source_str); @@ -313,12 +319,14 @@ static bool is_rte_cypher(RangeTblEntry *rte) { RangeTblFunction *rtfunc; FuncExpr *funcexpr; + ListCell *first_function; /* * The planner expects RangeTblFunction nodes in rte->functions list. * We cannot replace one of them to a SELECT subquery. */ - if (list_length(rte->functions) != 1) + first_function = list_head(rte->functions); + if (first_function == NULL || lnext(rte->functions, first_function) != NULL) return false; /* @@ -370,7 +378,34 @@ static bool is_func_cypher(FuncExpr *funcexpr) return false; } - return is_oid_ag_func(funcexpr->funcid, "cypher"); + return funcexpr->funcid == get_cypher_func_oid(); +} + +static Oid get_cypher_func_oid(void) +{ + if (!cypher_func_oid_callback_registered) + { + CacheRegisterSyscacheCallback(PROCOID, invalidate_cypher_func_oid, + (Datum)0); + CacheRegisterSyscacheCallback(PROCNAMEARGSNSP, + invalidate_cypher_func_oid, + (Datum)0); + cypher_func_oid_callback_registered = true; + } + + if (!OidIsValid(cypher_func_oid)) + { + cypher_func_oid = get_ag_func_oid("cypher", 3, NAMEOID, CSTRINGOID, + AGTYPEOID); + } + + return cypher_func_oid; +} + +static void invalidate_cypher_func_oid(Datum arg, int cache_id, + uint32 hash_value) +{ + cypher_func_oid = InvalidOid; } /* convert cypher() call to SELECT subquery in-place */ @@ -378,6 +413,8 @@ static void convert_cypher_to_subquery(RangeTblEntry *rte, ParseState *pstate) { RangeTblFunction *rtfunc = linitial(rte->functions); FuncExpr *funcexpr = (FuncExpr *)rtfunc->funcexpr; + List *args = funcexpr->args; + int nargs = list_length(args); Node *arg1 = NULL; Node *arg2 = NULL; Node *arg3 = NULL; @@ -406,7 +443,7 @@ static void convert_cypher_to_subquery(RangeTblEntry *rte, ParseState *pstate) } /* verify that we have 2 input parameters as it is possible to get 1 or 0 */ - if (funcexpr->args == NULL || list_length(funcexpr->args) < 2) + if (nargs < 2) { ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), @@ -415,8 +452,8 @@ static void convert_cypher_to_subquery(RangeTblEntry *rte, ParseState *pstate) } /* get our first 2 arguments */ - arg1 = linitial(funcexpr->args); - arg2 = lsecond(funcexpr->args); + arg1 = linitial(args); + arg2 = lsecond(args); Assert(exprType(arg1) == NAMEOID); Assert(exprType(arg2) == CSTRINGOID); @@ -437,103 +474,31 @@ static void convert_cypher_to_subquery(RangeTblEntry *rte, ParseState *pstate) */ query_str = expr_get_const_cstring(arg2, pstate->p_sourcetext); - /* - * Validate appropriate cypher function usage - - * - * Session info OVERRIDES ANY INPUT PASSED and if any is passed, it will - * cause the cypher function to error out. - * - * If this is using session info, both of the first 2 input parameters need - * to be NULL, in addition to the session info being set up. Furthermore, - * the input parameters passed in by session info need to both be non-NULL. - * - * If this is not using session info, both input parameters need to be - * non-NULL. - * - */ - if (is_session_info_prepared()) + /* get the graph name string from the passed parameters */ + if (!graph_name) { - /* check to see if either input parameter is non-NULL*/ - if (graph_name != NULL || query_str != NULL) - { - Node *arg = (graph_name == NULL) ? arg1 : arg2; - - /* - * Make sure to clean up session info because the ereport will - * cause the function to exit. - */ - reset_session_info(); - - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("session info requires cypher(NULL, NULL) to be passed"), - parser_errposition(pstate, exprLocation(arg)))); - } - /* get our input parameters from session info */ - else - { - graph_name_str = get_session_info_graph_name(); - query_str = get_session_info_cypher_statement(); - - /* check to see if either are NULL */ - if (graph_name_str == NULL || query_str == NULL) - { - /* - * Make sure to clean up session info because the ereport will - * cause the function to exit. - */ - reset_session_info(); - - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("both session info parameters need to be non-NULL"), - parser_errposition(pstate, -1))); - } - } + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("a name constant is expected"), + parser_errposition(pstate, exprLocation(arg1)))); } - /* otherwise, we get the parameters from the passed function input */ else { - /* get the graph name string from the passed parameters */ - if (!graph_name) - { - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("a name constant is expected"), - parser_errposition(pstate, exprLocation(arg1)))); - } - else - { - graph_name_str = NameStr(*graph_name); - } - /* get the query string from the passed parameters */ - if (!query_str) - { - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("a dollar-quoted string constant is expected"), - parser_errposition(pstate, exprLocation(arg2)))); - } + graph_name_str = NameStr(*graph_name); } - /* - * The session info is only valid for one cypher call. Now that we are done - * with it, if it was used, we need to reset it to free the memory used. - * Additionally, the query location is dependent on how we got the query - * string, so set the location accordingly. - */ - if (is_session_info_prepared()) + /* get the query string from the passed parameters */ + if (!query_str) { - reset_session_info(); - query_loc = 0; - } - else - { - /* this call will crash if we use session info */ - query_loc = get_query_location(((Const *)arg2)->location, - pstate->p_sourcetext); + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("a dollar-quoted string constant is expected"), + parser_errposition(pstate, exprLocation(arg2)))); } + query_loc = get_query_location(((Const *)arg2)->location, + pstate->p_sourcetext); + /* validate the graph exists */ graph_oid = get_graph_oid(graph_name_str); if (!OidIsValid(graph_oid)) @@ -548,9 +513,9 @@ static void convert_cypher_to_subquery(RangeTblEntry *rte, ParseState *pstate) * Check to see if the cypher function had a third parameter passed to it, * if so make sure Postgres parsed the second argument to a Param node. */ - if (list_length(funcexpr->args) == 3) + if (nargs == 3) { - arg3 = lthird(funcexpr->args); + arg3 = lthird(args); if (!IsA(arg3, Param)) { ereport(ERROR, diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 3083c52e1..c2a778d2e 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -25,6 +25,7 @@ #include "postgres.h" #include "access/heapam.h" +#include "access/tableam.h" #include "catalog/pg_aggregate.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" @@ -39,6 +40,8 @@ #include "parser/parsetree.h" #include "parser/parse_relation.h" #include "rewrite/rewriteHandler.h" +#include "utils/inval.h" +#include "utils/syscache.h" #include "catalog/ag_graph.h" #include "catalog/ag_label.h" @@ -95,6 +98,20 @@ typedef Query *(*transform_method)(cypher_parsestate *cpstate, cypher_clause *clause); +static Oid create_clause_func_oid = InvalidOid; +static Oid set_clause_func_oid = InvalidOid; +static Oid delete_clause_func_oid = InvalidOid; +static Oid merge_clause_func_oid = InvalidOid; +static Oid bool_or_func_oid = InvalidOid; +static Oid materialize_vle_edges_func_oid = InvalidOid; +static Oid build_path_func_oid = InvalidOid; +static Oid build_edge_func_oid = InvalidOid; +static Oid build_vertex_func_oid = InvalidOid; +static Oid label_name_func_oid = InvalidOid; +static Oid volatile_wrapper_func_oid = InvalidOid; +static Oid age_properties_func_oid = InvalidOid; +static bool clause_func_oid_callback_registered = false; + /* projection */ static Query *transform_cypher_return(cypher_parsestate *cpstate, cypher_clause *clause); @@ -113,17 +130,27 @@ static Query *transform_cypher_clause_with_where(cypher_parsestate *cpstate, transform_method transform, cypher_clause *clause, Node *where); +static Relation open_label_relation_with_cache(cypher_parsestate *cpstate, + const char *label_name, + int location, + LOCKMODE lockmode, + label_cache_data *label_cache); +static RangeVar *make_default_label_range_var(cypher_parsestate *cpstate, + char *label_name); /* match clause */ static Query *transform_cypher_match(cypher_parsestate *cpstate, cypher_clause *clause); static Query *transform_cypher_match_pattern(cypher_parsestate *cpstate, cypher_clause *clause); +static Query *transform_cypher_match_pattern_internal( + cypher_parsestate *cpstate, cypher_clause *clause, bool valid_label); static List *transform_match_entities(cypher_parsestate *cpstate, Query *query, - cypher_path *path); + cypher_path *path, bool valid_label); static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, - List *pattern, Node *where); + List *pattern, Node *where, + bool valid_label); static List *transform_match_path(cypher_parsestate *cpstate, Query *query, - cypher_path *path); + cypher_path *path, bool valid_label); static Expr *transform_cypher_edge(cypher_parsestate *cpstate, cypher_relationship *rel, List **target_list, bool valid_label); @@ -332,7 +359,23 @@ static ParseNamespaceItem *get_namespace_item(ParseState *pstate, RangeTblEntry *rte); static List *make_target_list_from_join(ParseState *pstate, RangeTblEntry *rte); -static FuncExpr *make_clause_func_expr(char *function_name, +static void initialize_clause_function_oid_cache(void); +static Oid get_create_clause_func_oid(void); +static Oid get_set_clause_func_oid(void); +static Oid get_delete_clause_func_oid(void); +static Oid get_merge_clause_func_oid(void); +static Oid get_bool_or_func_oid(void); +static Oid get_materialize_vle_edges_func_oid(void); +static Oid get_build_path_func_oid(void); +static Oid get_build_edge_func_oid(void); +static Oid get_build_vertex_func_oid(void); +static Oid get_label_name_func_oid(void); +static Oid get_volatile_wrapper_func_oid(void); +static Oid get_age_properties_func_oid(void); +static Node *make_age_properties_func_expr(Node *arg); +static void invalidate_clause_function_oids(Datum arg, int cache_id, + uint32 hash_value); +static FuncExpr *make_clause_func_expr(Oid func_oid, Node *clause_information); static void markRelsAsNulledBy(ParseState *pstate, Node *n, int jindex); @@ -406,6 +449,46 @@ add_rte_permissions(ParseState *pstate, Oid relid, AclMode permissions) relid, permissions); } +static Relation +open_label_relation_with_lock(cypher_parsestate *cpstate, + const char *label_name, int location, + LOCKMODE lockmode) +{ + return open_label_relation_with_cache(cpstate, label_name, location, + lockmode, NULL); +} + +static Relation +open_label_relation_with_cache(cypher_parsestate *cpstate, + const char *label_name, int location, + LOCKMODE lockmode, + label_cache_data *label_cache) +{ + Oid relid; + + if (label_cache == NULL) + { + label_cache = get_label_cache_data(cpstate, label_name); + } + + relid = label_cache != NULL ? label_cache->relation : InvalidOid; + if (!OidIsValid(relid)) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("label \"%s\" does not exist", label_name), + parser_errposition(&cpstate->pstate, location))); + } + + return table_open(relid, lockmode); +} + +static RangeVar *make_default_label_range_var(cypher_parsestate *cpstate, + char *label_name) +{ + return makeRangeVar(cpstate->graph_name, pstrdup(label_name), 2); +} + /* * Add required permissions to the label table for a given entity variable. * Looks up the entity by variable name, extracts its label, and adds @@ -418,6 +501,7 @@ add_entity_permissions(cypher_parsestate *cpstate, char *var_name, ParseState *pstate = (ParseState *)cpstate; transform_entity *entity; char *label = NULL; + label_cache_data *label_cache; Oid relid; entity = find_variable(cpstate, var_name); @@ -440,7 +524,8 @@ add_entity_permissions(cypher_parsestate *cpstate, char *var_name, return; } - relid = get_label_relation(label, cpstate->graph_oid); + label_cache = get_label_cache_data(cpstate, label); + relid = label_cache != NULL ? label_cache->relation : InvalidOid; if (OidIsValid(relid)) { add_rte_permissions(pstate, relid, permissions); @@ -1414,6 +1499,7 @@ static Query *transform_cypher_delete(cypher_parsestate *cpstate, delete_data->delete_items = transform_cypher_delete_item_list(cpstate, self->exprs, query); + delete_data->delete_item_count = list_length(delete_data->delete_items); delete_data->graph_name = cpstate->graph_name; delete_data->graph_oid = cpstate->graph_oid; delete_data->detach = self->detach; @@ -1423,7 +1509,7 @@ static Query *transform_cypher_delete(cypher_parsestate *cpstate, delete_data->flags |= CYPHER_CLAUSE_FLAG_TERMINAL; } - func_expr = make_clause_func_expr(DELETE_CLAUSE_FUNCTION_NAME, + func_expr = make_clause_func_expr(get_delete_clause_func_oid(), (Node *)delete_data); /* Create the target entry */ @@ -1637,19 +1723,13 @@ static Node *make_bool_or_agg(ParseState *pstate, Node *arg) { Aggref *agg; TargetEntry *te; - Oid bool_or_oid; - Oid argtypes[1] = { BOOLOID }; - - /* Look up bool_or(boolean) */ - bool_or_oid = LookupFuncName(list_make1(makeString("bool_or")), - 1, argtypes, false); /* Build the TargetEntry for the aggregate argument */ te = makeTargetEntry((Expr *) arg, 1, NULL, false); /* Construct the Aggref */ agg = makeNode(Aggref); - agg->aggfnoid = bool_or_oid; + agg->aggfnoid = get_bool_or_func_oid(); agg->aggtype = BOOLOID; agg->aggcollid = InvalidOid; agg->inputcollid = InvalidOid; @@ -2059,13 +2139,14 @@ static Query *transform_cypher_set(cypher_parsestate *cpstate, set_items_target_list->clause_name = clause_name; set_items_target_list->graph_name = cpstate->graph_name; + set_items_target_list->graph_oid = cpstate->graph_oid; if (!clause->next) { set_items_target_list->flags |= CYPHER_CLAUSE_FLAG_TERMINAL; } - func_expr = make_clause_func_expr(SET_CLAUSE_FUNCTION_NAME, + func_expr = make_clause_func_expr(get_set_clause_func_oid(), (Node *)set_items_target_list); /* Create the target entry */ @@ -2091,6 +2172,7 @@ cypher_update_information *transform_cypher_remove_item_list( info->set_items = NIL; info->flags = 0; + info->set_item_count = 0; foreach (li, remove_item_list) { @@ -2187,6 +2269,7 @@ cypher_update_information *transform_cypher_remove_item_list( item->prop_name = property_name; info->set_items = lappend(info->set_items, item); + info->set_item_count++; } return info; @@ -2202,6 +2285,7 @@ cypher_update_information *transform_cypher_set_item_list( info->set_items = NIL; info->flags = 0; + info->set_item_count = 0; foreach (li, set_item_list) { @@ -2382,6 +2466,7 @@ cypher_update_information *transform_cypher_set_item_list( query->targetList = lappend(query->targetList, target_item); info->set_items = lappend(info->set_items, item); + info->set_item_count++; } return info; @@ -2876,8 +2961,7 @@ static bool match_check_valid_label(cypher_match *match, if (node->label) { label_cache_data *lcd = - search_label_name_graph_cache(node->label, - cpstate->graph_oid); + get_label_cache_data(cpstate, node->label); if (lcd == NULL || lcd->kind != LABEL_KIND_VERTEX) @@ -2895,8 +2979,7 @@ static bool match_check_valid_label(cypher_match *match, if (rel->label) { label_cache_data *lcd = - search_label_name_graph_cache(rel->label, - cpstate->graph_oid); + get_label_cache_data(cpstate, rel->label); if (lcd == NULL || lcd->kind != LABEL_KIND_EDGE) { @@ -2973,6 +3056,8 @@ static Query *transform_cypher_match(cypher_parsestate *cpstate, { cypher_match *match_self = (cypher_match*) clause->self; Node *where = match_self->where; + bool has_dml = clause_chain_has_dml(clause->prev); + bool valid_label = true; /* @@ -2982,12 +3067,15 @@ static Query *transform_cypher_match(cypher_parsestate *cpstate, * cache, so the check is deferred to after transform_prev_cypher_clause() * for those cases. */ - if (!clause_chain_has_dml(clause->prev) && - !match_check_valid_label(match_self, cpstate)) + if (!has_dml) { - /* Label is invalid -- inject a false WHERE so the MATCH returns - * zero rows. No DML predecessor here, so constant-foldable is fine. */ - match_self->where = make_false_where_clause(false); + valid_label = match_check_valid_label(match_self, cpstate); + if (!valid_label) + { + /* Label is invalid -- inject a false WHERE so the MATCH returns + * zero rows. No DML predecessor here, so constant-foldable is fine. */ + match_self->where = make_false_where_clause(false); + } } /* @@ -3013,7 +3101,8 @@ static Query *transform_cypher_match(cypher_parsestate *cpstate, transform_cypher_match_pattern, clause, where); } - return transform_cypher_match_pattern(cpstate, clause); + return transform_cypher_match_pattern_internal(cpstate, clause, + valid_label); } /* @@ -3266,6 +3355,12 @@ static RangeTblEntry *transform_cypher_optional_match_clause(cypher_parsestate * static Query *transform_cypher_match_pattern(cypher_parsestate *cpstate, cypher_clause *clause) +{ + return transform_cypher_match_pattern_internal(cpstate, clause, true); +} + +static Query *transform_cypher_match_pattern_internal( + cypher_parsestate *cpstate, cypher_clause *clause, bool valid_label) { ParseState *pstate = (ParseState *)cpstate; cypher_match *self = (cypher_match *)clause->self; @@ -3362,13 +3457,18 @@ static Query *transform_cypher_match_pattern(cypher_parsestate *cpstate, * Filter: false, eliminating the entire plan subtree -- * including the DML predecessor scan -- without executing it. */ - if (has_dml && !match_check_valid_label(self, cpstate)) + if (has_dml) { - where = make_false_where_clause(true); + valid_label = match_check_valid_label(self, cpstate); + if (!valid_label) + { + where = make_false_where_clause(true); + } } } - transform_match_pattern(cpstate, query, self->pattern, where); + transform_match_pattern(cpstate, query, self->pattern, where, + valid_label); } markTargetListOrigins(pstate, query->targetList); @@ -3760,7 +3860,8 @@ static ParseNamespaceItem *transform_RangeFunction(cypher_parsestate *cpstate, } static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, - List *pattern, Node *where) + List *pattern, Node *where, + bool valid_label) { ParseState *pstate = (ParseState *)cpstate; ListCell *lc; @@ -3779,7 +3880,7 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, /* get the path and transform it */ path = (cypher_path *) lfirst(lc); - qual = transform_match_path(cpstate, query, path); + qual = transform_match_path(cpstate, query, path, valid_label); quals = list_concat(quals, qual); } @@ -3988,6 +4089,23 @@ static List *make_join_condition_for_edge(cypher_parsestate *cpstate, List *args = NIL; List *quals = NIL; + if (prev_node->in_join_tree && next_node->in_join_tree) + { + FuncCall *vle_func = (FuncCall *)entity->entity.rel->varlen; + Node *start_arg = linitial(vle_func->args); + Node *end_arg = lsecond(vle_func->args); + + /* + * age_vle() already enforces both endpoints when both endpoint + * arguments are bound. Avoid adding a redundant terminal-edge join + * filter that forces path post-filtering. + */ + if (!IsA(start_arg, A_Const) && !IsA(end_arg, A_Const)) + { + return NIL; + } + } + /* * If the next node is not in the join tree, we don't need to make any * quals. @@ -4228,7 +4346,7 @@ static List *join_to_entity(cypher_parsestate *cpstate, { List *edge_quals = make_edge_quals(cpstate, entity, side); - if (list_length(edge_quals) > 1) + if (lnext(edge_quals, list_head(edge_quals)) != NULL) { expr = makeSimpleA_Expr(AEXPR_IN, "=", qual, (Node *)edge_quals, -1); @@ -4363,8 +4481,7 @@ static List *make_edge_quals(cypher_parsestate *cpstate, static A_Expr *filter_vertices_on_label_id(cypher_parsestate *cpstate, Node *id_field, char *label) { - label_cache_data *lcd = search_label_name_graph_cache(label, - cpstate->graph_oid); + label_cache_data *lcd = get_label_cache_data(cpstate, label); A_Const *n; FuncCall *fc; String *ag_catalog, *extract_label_id; @@ -4441,7 +4558,7 @@ static Node *transform_map_to_ind(cypher_parsestate *cpstate, Assert(quals != NIL); - if (list_length(quals) > 1) + if (lnext(quals, list_head(quals)) != NULL) { return (Node *)makeBoolExpr(AND_EXPR, quals, -1); } @@ -4721,7 +4838,7 @@ static Node *create_property_constraints(cypher_parsestate *cpstate, * correct join tree, and enforce edge uniqueness. */ static List *transform_match_path(cypher_parsestate *cpstate, Query *query, - cypher_path *path) + cypher_path *path, bool valid_label) { ParseState *pstate = (ParseState *)cpstate; List *qual = NIL; @@ -4730,7 +4847,7 @@ static List *transform_match_path(cypher_parsestate *cpstate, Query *query, List *join_quals; /* transform the entities in the path */ - entities = transform_match_entities(cpstate, query, path); + entities = transform_match_entities(cpstate, query, path, valid_label); /* create the path variable, if needed. */ if (path->var_name != NULL) @@ -4881,7 +4998,7 @@ static transform_entity *transform_VLE_edge_entity(cypher_parsestate *cpstate, * edges. For a VLE edge variable we need to return a list of edges, * not a path. */ - func_oid = get_ag_func_oid("age_materialize_vle_edges", 1, AGTYPEOID); + func_oid = get_materialize_vle_edges_func_oid(); /* build the expr node for the function */ fexpr = makeFuncExpr(func_oid, AGTYPEOID, args, InvalidOid, InvalidOid, @@ -4927,61 +5044,67 @@ static bool isa_special_VLE_case(cypher_path *path) return false; } -static bool path_check_valid_label(cypher_path *path, - cypher_parsestate *cpstate) +static transform_entity *transform_match_node_entity( + cypher_parsestate *cpstate, Query *query, cypher_node *node, + bool output_node, bool valid_label) { - ListCell *lc = NULL; - int i = 0; + ParseState *pstate = (ParseState *)cpstate; + Expr *expr = NULL; + transform_entity *entity = NULL; - foreach (lc, path->path) - { - if (i % 2 == 0) - { - cypher_node *node = NULL; + expr = transform_cypher_node(cpstate, node, &query->targetList, + output_node, valid_label); - node = lfirst(lc); + entity = make_transform_entity(cpstate, ENT_VERTEX, (Node *)node, expr); - if (node->label) - { - label_cache_data *lcd = - search_label_name_graph_cache(node->label, - cpstate->graph_oid); + /* + * Add the entity before transforming property constraints so property + * expressions that reference this same entity can be resolved. + */ + cpstate->entities = lappend(cpstate->entities, entity); - if (lcd == NULL || lcd->kind != LABEL_KIND_VERTEX) - { - return false; - } - } - } - else - { - cypher_relationship *rel = NULL; + if (node->props) + { + Node *n = NULL; + Node *prop_var = NULL; + Node *prop_expr = NULL; - rel = lfirst(lc); + if (node->name != NULL) + { + prop_var = colNameToVar(pstate, node->name, false, + node->location); + } - if (rel->label) - { - label_cache_data *lcd = - search_label_name_graph_cache(rel->label, - cpstate->graph_oid); + if (prop_var != NULL && + pg_strncasecmp(node->name, AGE_DEFAULT_ALIAS_PREFIX, + sizeof(AGE_DEFAULT_ALIAS_PREFIX) - 1) == 0) + { + prop_expr = prop_var; + } + else if (prop_var != NULL) + { + prop_expr = make_age_properties_func_expr(prop_var); + } - if (lcd == NULL || lcd->kind != LABEL_KIND_EDGE) - { - return false; - } - } + if (is_ag_node(node->props, cypher_map)) + { + ((cypher_map*)node->props)->keep_null = true; } - i++; + n = create_property_constraints(cpstate, entity, node->props, + prop_expr); + + cpstate->property_constraint_quals = + lappend(cpstate->property_constraint_quals, n); } - return true; + return entity; } /* * Iterate through the path and construct all edges and necessary vertices */ static List *transform_match_entities(cypher_parsestate *cpstate, Query *query, - cypher_path *path) + cypher_path *path, bool valid_label) { ParseState *pstate = (ParseState *)cpstate; ListCell *lc = NULL; @@ -4990,10 +5113,10 @@ static List *transform_match_entities(cypher_parsestate *cpstate, Query *query, bool node_declared_in_prev_clause = false; transform_entity *prev_entity = NULL; bool special_VLE_case = false; - bool valid_label = true; + bool skip_pretransformed_node = false; + transform_entity *pretransformed_node = NULL; special_VLE_case = isa_special_VLE_case(path); - valid_label = path_check_valid_label(path, cpstate); /* * Iterate through every node in the path, construct the expr node @@ -5014,6 +5137,15 @@ static List *transform_match_entities(cypher_parsestate *cpstate, Query *query, node = lfirst(lc); + if (skip_pretransformed_node) + { + prev_entity = pretransformed_node; + pretransformed_node = NULL; + skip_pretransformed_node = false; + i++; + continue; + } + /* * The vle needs to know if the start vertex was * created in a previous clause. Check to see if it @@ -5066,90 +5198,84 @@ static List *transform_match_entities(cypher_parsestate *cpstate, Query *query, */ output_node = true; - /* transform vertex */ - expr = transform_cypher_node(cpstate, node, &query->targetList, - output_node, valid_label); - - entity = make_transform_entity(cpstate, ENT_VERTEX, (Node *)node, - expr); - /* - * We want to add transformed entity to entities before transforming props - * so that props referencing currently transformed entity can be resolved. + * A terminal anonymous node after a VLE does not add bindings or + * filters. Keeping it in the join tree forces a full vertex scan + * plus age_match_vle_terminal_edge(), even though age_vle() has + * already generated valid terminal vertices for paths-from + * traversal. */ - cpstate->entities = lappend(cpstate->entities, entity); - entities = lappend(entities, entity); - - /* transform the properties if they exist */ - if (node->props) + if ((path->var_name == NULL || list_length(path->path) == 3) && + i > 0 && i == list_length(path->path) - 1 && + node->name == NULL && node->label == NULL && + node->props == NULL) { - Node *n = NULL; - Node *prop_var = NULL; - Node *prop_expr = NULL; + cypher_relationship *prev_rel = + (cypher_relationship *)list_nth(path->path, i - 1); - /* - * We need to build a transformed properties(prop_var) - * expression IF the properties variable already exists from a - * previous clause. Please note that the "found" prop_var was - * previously transformed. - */ - - /* get the prop_var if it was previously resolved */ - if (node->name != NULL) + if (prev_rel->varlen != NULL) { - prop_var = colNameToVar(pstate, node->name, false, - node->location); - } + FuncCall *vle_func = (FuncCall *)prev_rel->varlen; + Node *start_arg = linitial(vle_func->args); - /* - * If prop_var exists and is an alias, just pass it through by - * assigning the prop_expr the prop_var. - */ - if (prop_var != NULL && - pg_strncasecmp(node->name, AGE_DEFAULT_ALIAS_PREFIX, - strlen(AGE_DEFAULT_ALIAS_PREFIX)) == 0) - { - prop_expr = prop_var; + if (!IsA(start_arg, A_Const)) + { + output_node = false; + } } - /* - * Else, if it exists and is not an alias, create the prop_expr - * as a transformed properties(prop_var) function node. - */ - else if (prop_var != NULL) + } + + /* + * Symmetric case for right-bound VLE: the anonymous start vertex is + * not bound, filtered, or returned. Let age_vle() iterate starts + * internally in paths-to mode. + */ + if (i == 0 && + node->parsed_name == NULL && node->label == NULL && + node->props == NULL && + list_length(path->path) == 3) + { + cypher_relationship *next_rel = + (cypher_relationship *)lfirst(lnext(path->path, lc)); + cypher_node *next_node = + (cypher_node *)lfirst(lnext(path->path, + lnext(path->path, lc))); + + if (next_rel->varlen != NULL) { - /* - * Remember that prop_var is already transformed. We need - * to built the transform manually. - */ - FuncCall *fc = NULL; - List *targs = NIL; - List *fname = NIL; + FuncCall *vle_func = (FuncCall *)next_rel->varlen; + ListCell *start_arg_cell = list_head(vle_func->args); + ListCell *end_arg_cell = lnext(vle_func->args, + start_arg_cell); + Node *start_arg = lfirst(start_arg_cell); + Node *end_arg = lfirst(end_arg_cell); + + if (next_node->parsed_name == NULL && + (next_node->label != NULL || next_node->props != NULL) && + next_node->name != NULL && + !IsA(start_arg, A_Const) && IsA(end_arg, A_Const)) + { + A_Const *null_const = makeNode(A_Const); + ColumnRef *end_ref = makeNode(ColumnRef); - targs = lappend(targs, prop_var); - fname = list_make2(makeString("ag_catalog"), - makeString("age_properties")); - fc = makeFuncCall(fname, targs, COERCE_SQL_SYNTAX, -1); + null_const->isnull = true; + null_const->location = -1; + lfirst(start_arg_cell) = null_const; - /* - * Hand off to ParseFuncOrColumn to create the function - * expression for properties(prop_var) - */ - prop_expr = ParseFuncOrColumn(pstate, fname, targs, - pstate->p_last_srf, fc, false, - -1); - } + end_ref->fields = list_make2(makeString(next_node->name), + makeString("id")); + end_ref->location = next_node->location; + lfirst(end_arg_cell) = end_ref; - if (is_ag_node(node->props, cypher_map)) - { - ((cypher_map*)node->props)->keep_null = true; + output_node = false; + } } - n = create_property_constraints(cpstate, entity, node->props, - prop_expr); - - cpstate->property_constraint_quals = - lappend(cpstate->property_constraint_quals, n); } + entity = transform_match_node_entity(cpstate, query, node, + output_node, valid_label); + entities = lappend(entities, entity); + prev_entity = entity; } /* odd increments of i are edges */ @@ -5239,7 +5365,7 @@ static List *transform_match_entities(cypher_parsestate *cpstate, Query *query, */ if (prop_var != NULL && pg_strncasecmp(rel->name, AGE_DEFAULT_ALIAS_PREFIX, - strlen(AGE_DEFAULT_ALIAS_PREFIX)) == 0) + sizeof(AGE_DEFAULT_ALIAS_PREFIX) - 1) == 0) { prop_expr = prop_var; } @@ -5249,26 +5375,7 @@ static List *transform_match_entities(cypher_parsestate *cpstate, Query *query, */ else if (prop_var != NULL) { - /* - * Remember that prop_var is already transformed. We need - * to built the transform manually. - */ - FuncCall *fc = NULL; - List *targs = NIL; - List *fname = NIL; - - targs = lappend(targs, prop_var); - fname = list_make2(makeString("ag_catalog"), - makeString("age_properties")); - fc = makeFuncCall(fname, targs, COERCE_SQL_SYNTAX, -1); - - /* - * Hand off to ParseFuncOrColumn to create the function - * expression for properties(prop_var) - */ - prop_expr = ParseFuncOrColumn(pstate, fname, targs, - pstate->p_last_srf, fc, - false, -1); + prop_expr = make_age_properties_func_expr(prop_var); } if (is_ag_node(rel->props, cypher_map)) @@ -5288,6 +5395,7 @@ static List *transform_match_entities(cypher_parsestate *cpstate, Query *query, else { transform_entity *vle_entity = NULL; + ListCell *next_lc = lnext(path->path, lc); /* * Check to see if the previous node was originally created @@ -5308,12 +5416,60 @@ static List *transform_match_entities(cypher_parsestate *cpstate, Query *query, cr->fields = list_make1(linitial(cr->fields)); } + if (list_length(path->path) == 3 && + prev_entity != NULL && + next_lc != NULL) + { + cypher_node *next_node = lfirst(next_lc); + FuncCall *func = (FuncCall *)rel->varlen; + ListCell *start_arg_cell = list_head(func->args); + ListCell *end_arg_cell = lnext(func->args, + start_arg_cell); + Node *start_arg = linitial(func->args); + Node *end_arg = lfirst(end_arg_cell); + bool needs_pretransform = false; + + if (next_node->name != NULL && + next_node->parsed_name == NULL && + IsA(end_arg, A_Const) && + !IsA(start_arg, A_Const)) + { + ColumnRef *end_ref = makeNode(ColumnRef); + + end_ref->fields = list_make2(makeString(next_node->name), + makeString("id")); + end_ref->location = next_node->location; + lfirst(end_arg_cell) = end_ref; + needs_pretransform = true; + } + else if (next_node->name != NULL && + next_node->parsed_name == NULL && + !prev_entity->in_join_tree && + !IsA(end_arg, A_Const)) + { + needs_pretransform = true; + } + + if (needs_pretransform) + { + pretransformed_node = + transform_match_node_entity(cpstate, query, + next_node, true, + valid_label); + skip_pretransformed_node = true; + } + } + /* make a transform entity for the vle */ vle_entity = transform_VLE_edge_entity(cpstate, rel, query); /* add the entity in */ cpstate->entities = lappend(cpstate->entities, vle_entity); entities = lappend(entities, vle_entity); + if (pretransformed_node != NULL) + { + entities = lappend(entities, pretransformed_node); + } prev_entity = entity; } @@ -5442,7 +5598,7 @@ transform_match_create_path_variable(cypher_parsestate *cpstate, } /* get the oid for the path creation function */ - build_path_oid = get_ag_func_oid("_agtype_build_path", 1, ANYOID); + build_path_oid = get_build_path_func_oid(); /* * If we have a NULL in the path, there is an invalid label, so there aren't @@ -5481,38 +5637,50 @@ static char *get_accessor_function_name(enum transform_entity_type type, { if (type == ENT_VERTEX) { - /* id */ - if (!strcmp(AG_VERTEX_COLNAME_ID, name)) + switch (name[0]) { - return AG_VERTEX_ACCESS_FUNCTION_ID; - } - /* props */ - else if (!strcmp(AG_VERTEX_COLNAME_PROPERTIES, name)) - { - return AG_VERTEX_ACCESS_FUNCTION_PROPERTIES; + case 'i': + if (strcmp(AG_VERTEX_COLNAME_ID, name) == 0) + { + return AG_VERTEX_ACCESS_FUNCTION_ID; + } + break; + case 'p': + if (strcmp(AG_VERTEX_COLNAME_PROPERTIES, name) == 0) + { + return AG_VERTEX_ACCESS_FUNCTION_PROPERTIES; + } + break; } } if (type == ENT_EDGE) { - /* id */ - if (!strcmp(AG_EDGE_COLNAME_ID, name)) - { - return AG_EDGE_ACCESS_FUNCTION_ID; - } - /* start id */ - else if (!strcmp(AG_EDGE_COLNAME_START_ID, name)) + switch (name[0]) { - return AG_EDGE_ACCESS_FUNCTION_START_ID; - } - /* end id */ - else if (!strcmp(AG_EDGE_COLNAME_END_ID, name)) - { - return AG_EDGE_ACCESS_FUNCTION_END_ID; - } - /* props */ - else if (!strcmp(AG_VERTEX_COLNAME_PROPERTIES, name)) - { - return AG_VERTEX_ACCESS_FUNCTION_PROPERTIES; + case 'e': + if (strcmp(AG_EDGE_COLNAME_END_ID, name) == 0) + { + return AG_EDGE_ACCESS_FUNCTION_END_ID; + } + break; + case 'i': + if (strcmp(AG_EDGE_COLNAME_ID, name) == 0) + { + return AG_EDGE_ACCESS_FUNCTION_ID; + } + break; + case 'p': + if (strcmp(AG_EDGE_COLNAME_PROPERTIES, name) == 0) + { + return AG_EDGE_ACCESS_FUNCTION_PROPERTIES; + } + break; + case 's': + if (strcmp(AG_EDGE_COLNAME_START_ID, name) == 0) + { + return AG_EDGE_ACCESS_FUNCTION_START_ID; + } + break; } } @@ -5581,9 +5749,7 @@ static Expr *transform_cypher_edge(cypher_parsestate *cpstate, bool valid_label) { ParseState *pstate = (ParseState *)cpstate; - char *schema_name = NULL; - char *rel_name = NULL; - RangeVar *label_range_var = NULL; + Relation label_relation = NULL; Alias *alias = NULL; int resno = -1; TargetEntry *te = NULL; @@ -5790,23 +5956,26 @@ static Expr *transform_cypher_edge(cypher_parsestate *cpstate, rel->name = get_next_default_alias(cpstate); } - schema_name = get_graph_namespace_name(cpstate->graph_name); - if (valid_label) { - rel_name = get_label_relation_name(rel->label, cpstate->graph_oid); + label_relation = open_label_relation_with_lock(cpstate, rel->label, + rel->location, + AccessShareLock); } else { - rel_name = AG_DEFAULT_LABEL_EDGE; + label_relation = open_label_relation_with_lock(cpstate, + AG_DEFAULT_LABEL_EDGE, + rel->location, + AccessShareLock); } - label_range_var = makeRangeVar(schema_name, rel_name, -1); alias = makeAlias(rel->name, NIL); - pnsi = addRangeTableEntry(pstate, label_range_var, alias, - label_range_var->inh, true); + pnsi = addRangeTableEntryForRelation(pstate, label_relation, + AccessShareLock, alias, true, true); Assert(pnsi != NULL); + table_close(label_relation, NoLock); /* * relation is visible (r.a in expression works) but attributes in the @@ -5839,9 +6008,7 @@ static Expr *transform_cypher_node(cypher_parsestate *cpstate, bool output_node, bool valid_label) { ParseState *pstate = (ParseState *)cpstate; - char *schema_name = NULL; - char *rel_name = NULL; - RangeVar *label_range_var = NULL; + Relation label_relation = NULL; Alias *alias = NULL; int resno = -1; TargetEntry *te = NULL; @@ -6042,7 +6209,7 @@ static Expr *transform_cypher_node(cypher_parsestate *cpstate, */ is_vle = (strncmp(node->name, AGE_DEFAULT_PREFIX"vle_function_end_var", - strlen(AGE_DEFAULT_PREFIX"vle_function_end_var")) + sizeof(AGE_DEFAULT_PREFIX"vle_function_end_var") - 1) == 0); /* @@ -6077,24 +6244,27 @@ static Expr *transform_cypher_node(cypher_parsestate *cpstate, } /* now build a new vertex */ - schema_name = get_graph_namespace_name(cpstate->graph_name); - if (valid_label) { - rel_name = get_label_relation_name(node->label, cpstate->graph_oid); + label_relation = open_label_relation_with_lock(cpstate, node->label, + node->location, + AccessShareLock); } else { - rel_name = AG_DEFAULT_LABEL_VERTEX; + label_relation = open_label_relation_with_lock(cpstate, + AG_DEFAULT_LABEL_VERTEX, + node->location, + AccessShareLock); } - label_range_var = makeRangeVar(schema_name, rel_name, -1); alias = makeAlias(node->name, NIL); - pnsi = addRangeTableEntry(pstate, label_range_var, alias, - label_range_var->inh, true); + pnsi = addRangeTableEntryForRelation(pstate, label_relation, + AccessShareLock, alias, true, true); Assert(pnsi != NULL); + table_close(label_relation, NoLock); /* * relation is visible (r.a in expression works) but attributes in the @@ -6133,8 +6303,7 @@ static Node *make_edge_expr(cypher_parsestate *cpstate, FuncExpr *func_expr; FuncExpr *label_name_func_expr; - func_oid = get_ag_func_oid("_agtype_build_edge", 5, GRAPHIDOID, GRAPHIDOID, - GRAPHIDOID, CSTRINGOID, AGTYPEOID); + func_oid = get_build_edge_func_oid(); id = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_ID, -1); @@ -6142,8 +6311,7 @@ static Node *make_edge_expr(cypher_parsestate *cpstate, end_id = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_END_ID, -1); - label_name_func_oid = get_ag_func_oid("_label_name", 2, OIDOID, - GRAPHIDOID); + label_name_func_oid = get_label_name_func_oid(); graph_oid_const = makeConst(OIDOID, -1, InvalidOid, sizeof(Oid), ObjectIdGetDatum(cpstate->graph_oid), false, @@ -6182,13 +6350,11 @@ static Node *make_vertex_expr(cypher_parsestate *cpstate, Assert(pnsi != NULL); - func_oid = get_ag_func_oid("_agtype_build_vertex", 3, GRAPHIDOID, - CSTRINGOID, AGTYPEOID); + func_oid = get_build_vertex_func_oid(); id = scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_ID, -1); - label_name_func_oid = get_ag_func_oid("_label_name", 2, OIDOID, - GRAPHIDOID); + label_name_func_oid = get_label_name_func_oid(); graph_oid_const = makeConst(OIDOID, -1, InvalidOid, sizeof(Oid), ObjectIdGetDatum(cpstate->graph_oid), false, @@ -6260,7 +6426,7 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate, } - func_expr = make_clause_func_expr(CREATE_CLAUSE_FUNCTION_NAME, + func_expr = make_clause_func_expr(get_create_clause_func_oid(), (Node *)target_nodes); /* Create the target entry */ @@ -6308,6 +6474,7 @@ transform_cypher_create_path(cypher_parsestate *cpstate, List **target_list, bool in_path = path->var_name != NULL; ccp->path_attr_num = InvalidAttrNumber; + ccp->path_length = 0; if (in_path) { @@ -6347,6 +6514,7 @@ transform_cypher_create_path(cypher_parsestate *cpstate, List **target_list, } transformed_path = lappend(transformed_path, rel); + ccp->path_length++; entity = make_transform_entity(cpstate, ENT_VERTEX, (Node *)node, NULL); @@ -6375,6 +6543,7 @@ transform_cypher_create_path(cypher_parsestate *cpstate, List **target_list, } transformed_path = lappend(transformed_path, rel); + ccp->path_length++; entity = make_transform_entity(cpstate, ENT_EDGE, (Node *)edge, NULL); @@ -6399,7 +6568,7 @@ transform_cypher_create_path(cypher_parsestate *cpstate, List **target_list, { TargetEntry *te; - if (list_length(transformed_path) < 1) + if (ccp->path_length < 1) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -6431,17 +6600,8 @@ transform_create_cypher_edge(cypher_parsestate *cpstate, List **target_list, char *alias; AttrNumber resno; ParseNamespaceItem *pnsi; - - if (edge->label) - { - if (get_label_kind(edge->label, cpstate->graph_oid) == LABEL_KIND_VERTEX) - { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("label %s is for vertices, not edges", edge->label), - parser_errposition(pstate, edge->location))); - } - } + label_cache_data *label_cache = NULL; + Oid created_label_relid = InvalidOid; rel->type = LABEL_KIND_EDGE; rel->flags = CYPHER_TARGET_NODE_FLAG_INSERT; @@ -6497,23 +6657,41 @@ transform_create_cypher_edge(cypher_parsestate *cpstate, List **target_list, parser_errposition(&cpstate->pstate, edge->location))); } + label_cache = get_label_cache_data(cpstate, edge->label); + if (label_cache != NULL && label_cache->kind == LABEL_KIND_VERTEX) + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("label %s is for vertices, not edges", edge->label), + parser_errposition(pstate, edge->location))); + } + /* create the label entry if it does not exist */ - if (!label_exists(edge->label, cpstate->graph_oid)) + if (label_cache == NULL) { List *parent; - rv = get_label_range_var(cpstate->graph_name, cpstate->graph_oid, - AG_DEFAULT_LABEL_EDGE); + rv = make_default_label_range_var(cpstate, AG_DEFAULT_LABEL_EDGE); parent = list_make1(rv); - create_label(cpstate->graph_name, edge->label, LABEL_TYPE_EDGE, - parent); + created_label_relid = create_label_with_graph_oid( + cpstate->graph_name, cpstate->graph_oid, edge->label, + LABEL_TYPE_EDGE, parent); } /* lock the relation of the label */ - rv = makeRangeVar(cpstate->graph_name, edge->label, -1); - label_relation = parserOpenTable(&cpstate->pstate, rv, RowExclusiveLock); + if (OidIsValid(created_label_relid)) + { + label_relation = table_open(created_label_relid, RowExclusiveLock); + } + else + { + label_relation = open_label_relation_with_cache(cpstate, edge->label, + edge->location, + RowExclusiveLock, + label_cache); + } /* Store the relid */ rel->relid = RelationGetRelid(label_relation); @@ -6574,10 +6752,12 @@ transform_create_cypher_node(cypher_parsestate *cpstate, List **target_list, cypher_node *node, bool has_edge) { ParseState *pstate = (ParseState *)cpstate; + label_cache_data *label_cache = NULL; if (node->label) { - if (get_label_kind(node->label, cpstate->graph_oid) == LABEL_KIND_EDGE) + label_cache = get_label_cache_data(cpstate, node->label); + if (label_cache != NULL && label_cache->kind == LABEL_KIND_EDGE) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("label %s is for edges, not vertices", @@ -6754,6 +6934,8 @@ transform_create_cypher_new_node(cypher_parsestate *cpstate, char *alias; int resno; ParseNamespaceItem *pnsi; + label_cache_data *label_cache = NULL; + Oid created_label_relid = InvalidOid; rel->type = LABEL_KIND_VERTEX; rel->tuple_position = InvalidAttrNumber; @@ -6774,24 +6956,35 @@ transform_create_cypher_new_node(cypher_parsestate *cpstate, rel->label_name = node->label; } + label_cache = get_label_cache_data(cpstate, node->label); + /* create the label entry if it does not exist */ - if (!label_exists(node->label, cpstate->graph_oid)) + if (label_cache == NULL) { List *parent; - rv = get_label_range_var(cpstate->graph_name, cpstate->graph_oid, - AG_DEFAULT_LABEL_VERTEX); + rv = make_default_label_range_var(cpstate, AG_DEFAULT_LABEL_VERTEX); parent = list_make1(rv); - create_label(cpstate->graph_name, node->label, LABEL_TYPE_VERTEX, - parent); + created_label_relid = create_label_with_graph_oid( + cpstate->graph_name, cpstate->graph_oid, node->label, + LABEL_TYPE_VERTEX, parent); } rel->flags = CYPHER_TARGET_NODE_FLAG_INSERT; - rv = makeRangeVar(cpstate->graph_name, node->label, -1); - label_relation = parserOpenTable(&cpstate->pstate, rv, RowExclusiveLock); + if (OidIsValid(created_label_relid)) + { + label_relation = table_open(created_label_relid, RowExclusiveLock); + } + else + { + label_relation = open_label_relation_with_cache(cpstate, node->label, + node->location, + RowExclusiveLock, + label_cache); + } /* Store the relid */ rel->relid = RelationGetRelid(label_relation); @@ -6969,25 +7162,25 @@ transform_cypher_clause_as_subquery(cypher_parsestate *cpstate, * NOTE: skip namespace conflicts check if the rte will be the only * RangeTblEntry in pstate */ - if (list_length(pstate->p_rtable) > 1) { - List *namespace = NULL; - int rtindex = 0; - - /* get the index of the last entry */ - rtindex = list_length(pstate->p_rtable); + int rtindex = list_length(pstate->p_rtable); - /* the rte at the end should be the rte just added */ - if (rte != rt_fetch(rtindex, pstate->p_rtable)) + if (rtindex > 1) { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("rte must be last entry in p_rtable"))); - } + List *namespace = NULL; + + /* the rte at the end should be the rte just added */ + if (rte != rt_fetch(rtindex, pstate->p_rtable)) + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("rte must be last entry in p_rtable"))); + } - namespace = list_make1(pnsi); + namespace = list_make1(pnsi); - checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace); + checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace); + } } if (add_rte_to_query) @@ -7160,7 +7353,7 @@ static Expr *add_volatile_wrapper(Expr *node) ereport(ERROR, (errmsg_internal("add_volatile_wrapper: NULL expr"))); } - oid = get_ag_func_oid("agtype_volatile_wrapper", 1, ANYOID); + oid = get_volatile_wrapper_func_oid(); /* if the passed Expr node is already wrapped, just return it */ if (IsA(node, FuncExpr) && oid == ((FuncExpr*)node)->funcid) @@ -7220,8 +7413,10 @@ Query *cypher_parse_sub_analyze(Node *parseTree, pstate->p_locked_from_parent = locked_from_parent; pstate->p_resolve_unknowns = resolve_unknowns; - clause = palloc0(sizeof(cypher_clause)); + clause = palloc(sizeof(cypher_clause)); + clause->next = NULL; clause->self = parseTree; + clause->prev = NULL; query = transform_cypher_clause(cpstate, clause); free_parsestate(pstate); @@ -7367,6 +7562,7 @@ static Query *transform_cypher_merge(cypher_parsestate *cpstate, merge_information->graph_oid = cpstate->graph_oid; merge_information->path = merge_path; + merge_information->path_length = merge_path->path_length; /* Transform ON MATCH SET items, if any */ if (self->on_match != NIL) @@ -7375,10 +7571,13 @@ static Query *transform_cypher_merge(cypher_parsestate *cpstate, transform_cypher_set_item_list(cpstate, self->on_match, query); merge_information->on_match_set_info->clause_name = "MERGE ON MATCH SET"; merge_information->on_match_set_info->graph_name = cpstate->graph_name; + merge_information->on_match_set_info->graph_oid = cpstate->graph_oid; resolve_merge_set_exprs( merge_information->on_match_set_info->set_items, query->targetList, "ON MATCH SET"); + merge_information->on_match_set_item_count = + merge_information->on_match_set_info->set_item_count; } /* Transform ON CREATE SET items, if any */ @@ -7388,10 +7587,13 @@ static Query *transform_cypher_merge(cypher_parsestate *cpstate, transform_cypher_set_item_list(cpstate, self->on_create, query); merge_information->on_create_set_info->clause_name = "MERGE ON CREATE SET"; merge_information->on_create_set_info->graph_name = cpstate->graph_name; + merge_information->on_create_set_info->graph_oid = cpstate->graph_oid; resolve_merge_set_exprs( merge_information->on_create_set_info->set_items, query->targetList, "ON CREATE SET"); + merge_information->on_create_set_item_count = + merge_information->on_create_set_info->set_item_count; } if (!clause->next) @@ -7403,7 +7605,7 @@ static Query *transform_cypher_merge(cypher_parsestate *cpstate, * Creates the function expression that the planner will find and * convert to a MERGE path. */ - func_expr = make_clause_func_expr(MERGE_CLAUSE_FUNCTION_NAME, + func_expr = make_clause_func_expr(get_merge_clause_func_oid(), (Node *)merge_information); /* Create the target entry */ @@ -7612,7 +7814,7 @@ transform_merge_make_lateral_join(cypher_parsestate *cpstate, Query *query, { if (qte->resname != NULL && pg_strncasecmp(qte->resname, AGE_DEFAULT_VARNAME_PREFIX, - strlen(AGE_DEFAULT_VARNAME_PREFIX))) + sizeof(AGE_DEFAULT_VARNAME_PREFIX) - 1)) { qte->expr = add_volatile_wrapper(qte->expr); } @@ -7876,6 +8078,7 @@ transform_cypher_merge_path(cypher_parsestate *cpstate, List **target_list, bool in_path = path->var_name != NULL; ccp->path_attr_num = InvalidAttrNumber; + ccp->path_length = 0; foreach (lc, path->path) { @@ -7915,6 +8118,7 @@ transform_cypher_merge_path(cypher_parsestate *cpstate, List **target_list, } transformed_path = lappend(transformed_path, rel); + ccp->path_length++; } else if (is_ag_node(lfirst(lc), cypher_relationship)) { @@ -7956,6 +8160,7 @@ transform_cypher_merge_path(cypher_parsestate *cpstate, List **target_list, } transformed_path = lappend(transformed_path, rel); + ccp->path_length++; } else { @@ -7991,6 +8196,8 @@ transform_merge_cypher_edge(cypher_parsestate *cpstate, List **target_list, RangeVar *rv; RTEPermissionInfo *rte_pi; ParseNamespaceItem *pnsi; + label_cache_data *label_cache = NULL; + Oid created_label_relid = InvalidOid; if (edge->name != NULL) { @@ -8033,42 +8240,43 @@ transform_merge_cypher_edge(cypher_parsestate *cpstate, List **target_list, } + label_cache = get_label_cache_data(cpstate, edge->label); + if (label_cache != NULL && label_cache->kind == LABEL_KIND_VERTEX) + { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Expecting edge label, found existing vertex label"), + parser_errposition(&cpstate->pstate, edge->location))); + } + /* check to see if the label exists, create the label entry if it does not. */ - if (edge->label && !label_exists(edge->label, cpstate->graph_oid)) + if (label_cache == NULL) { List *parent; /* * setup the default edge table as the parent table, that we * will inherit from. */ - rv = get_label_range_var(cpstate->graph_name, cpstate->graph_oid, - AG_DEFAULT_LABEL_EDGE); + rv = make_default_label_range_var(cpstate, AG_DEFAULT_LABEL_EDGE); parent = list_make1(rv); /* create the label */ - create_label(cpstate->graph_name, edge->label, LABEL_TYPE_EDGE, - parent); + created_label_relid = create_label_with_graph_oid( + cpstate->graph_name, cpstate->graph_oid, edge->label, + LABEL_TYPE_EDGE, parent); } /* lock the relation of the label */ - rv = makeRangeVar(cpstate->graph_name, edge->label, -1); - label_relation = parserOpenTable(&cpstate->pstate, rv, RowExclusiveLock); - - /* - * TODO - * It is possible for a vertex label to be retrieved, instead of an edge, - * due to the above logic. So, we need to check if it is a vertex label. - * This whole section needs to be fixed because it could be a relation that - * isn't either and has the correct number of columns. However, for now, - * we just check the number of columns. - */ - /* TODO temporarily hardcoded */ - if (label_relation->rd_att->natts == 2) + if (OidIsValid(created_label_relid)) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("Expecting edge label, found existing vertex label"), - parser_errposition(&cpstate->pstate, edge->location))); + label_relation = table_open(created_label_relid, RowExclusiveLock); + } + else + { + label_relation = open_label_relation_with_cache(cpstate, edge->label, + edge->location, + RowExclusiveLock, + label_cache); } /* Store the relid */ @@ -8106,6 +8314,8 @@ transform_merge_cypher_node(cypher_parsestate *cpstate, List **target_list, RangeVar *rv; RTEPermissionInfo *rte_pi; ParseNamespaceItem *pnsi; + label_cache_data *label_cache = NULL; + Oid created_label_relid = InvalidOid; if (node->name != NULL) { @@ -8168,8 +8378,16 @@ transform_merge_cypher_node(cypher_parsestate *cpstate, List **target_list, rel->label_name = node->label; } + label_cache = get_label_cache_data(cpstate, node->label); + if (label_cache != NULL && label_cache->kind == LABEL_KIND_EDGE) + { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Expecting vertex label, found existing edge label"), + parser_errposition(&cpstate->pstate, node->location))); + } + /* check to see if the label exists, create the label entry if it does not. */ - if (node->label && !label_exists(node->label, cpstate->graph_oid)) + if (label_cache == NULL) { List *parent; @@ -8177,35 +8395,28 @@ transform_merge_cypher_node(cypher_parsestate *cpstate, List **target_list, * setup the default vertex table as the parent table, that we * will inherit from. */ - rv = get_label_range_var(cpstate->graph_name, cpstate->graph_oid, - AG_DEFAULT_LABEL_VERTEX); + rv = make_default_label_range_var(cpstate, AG_DEFAULT_LABEL_VERTEX); parent = list_make1(rv); /* create the label */ - create_label(cpstate->graph_name, node->label, LABEL_TYPE_VERTEX, - parent); + created_label_relid = create_label_with_graph_oid( + cpstate->graph_name, cpstate->graph_oid, node->label, + LABEL_TYPE_VERTEX, parent); } rel->flags |= CYPHER_TARGET_NODE_FLAG_INSERT; - rv = makeRangeVar(cpstate->graph_name, node->label, -1); - label_relation = parserOpenTable(&cpstate->pstate, rv, RowExclusiveLock); - - /* - * TODO - * It is possible for an edge label to be retrieved, instead of a vertex, - * due to the above logic. So, we need to check if it is an edge label. - * This whole section needs to be fixed because it could be a relation that - * isn't either and has the correct number of columns. However, for now, - * we just check the number of columns. - */ - /* TODO temporarily hardcoded */ - if (label_relation->rd_att->natts == 4) + if (OidIsValid(created_label_relid)) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("Expecting vertex label, found existing edge label"), - parser_errposition(&cpstate->pstate, node->location))); + label_relation = table_open(created_label_relid, RowExclusiveLock); + } + else + { + label_relation = open_label_relation_with_cache(cpstate, node->label, + node->location, + RowExclusiveLock, + label_cache); } /* Store the relid */ @@ -8282,11 +8493,10 @@ static ParseNamespaceItem *get_namespace_item(ParseState *pstate, * extensible node that represents the metadata that the clause needs to * handle the clause in the execution phase. */ -static FuncExpr *make_clause_func_expr(char *function_name, +static FuncExpr *make_clause_func_expr(Oid func_oid, Node *clause_information) { Const *clause_information_const; - Oid func_oid; FuncExpr *func_expr; StringInfo str = makeStringInfo(); @@ -8305,8 +8515,6 @@ static FuncExpr *make_clause_func_expr(char *function_name, clause_information_const = makeConst(INTERNALOID, -1, InvalidOid, str->len, PointerGetDatum(str->data), false, false); - func_oid = get_ag_func_oid(function_name, 1, INTERNALOID); - func_expr = makeFuncExpr(func_oid, AGTYPEOID, list_make1(clause_information_const), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); @@ -8314,6 +8522,199 @@ static FuncExpr *make_clause_func_expr(char *function_name, return func_expr; } +static Oid get_create_clause_func_oid(void) +{ + initialize_clause_function_oid_cache(); + + if (!OidIsValid(create_clause_func_oid)) + { + create_clause_func_oid = + get_ag_func_oid(CREATE_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); + } + + return create_clause_func_oid; +} + +static Oid get_set_clause_func_oid(void) +{ + initialize_clause_function_oid_cache(); + + if (!OidIsValid(set_clause_func_oid)) + { + set_clause_func_oid = + get_ag_func_oid(SET_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); + } + + return set_clause_func_oid; +} + +static Oid get_delete_clause_func_oid(void) +{ + initialize_clause_function_oid_cache(); + + if (!OidIsValid(delete_clause_func_oid)) + { + delete_clause_func_oid = + get_ag_func_oid(DELETE_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); + } + + return delete_clause_func_oid; +} + +static Oid get_merge_clause_func_oid(void) +{ + initialize_clause_function_oid_cache(); + + if (!OidIsValid(merge_clause_func_oid)) + { + merge_clause_func_oid = + get_ag_func_oid(MERGE_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); + } + + return merge_clause_func_oid; +} + +static Oid get_bool_or_func_oid(void) +{ + initialize_clause_function_oid_cache(); + + if (!OidIsValid(bool_or_func_oid)) + { + bool_or_func_oid = get_pg_func_oid("bool_or", 1, BOOLOID); + } + + return bool_or_func_oid; +} + +static Oid get_materialize_vle_edges_func_oid(void) +{ + initialize_clause_function_oid_cache(); + + if (!OidIsValid(materialize_vle_edges_func_oid)) + { + materialize_vle_edges_func_oid = + get_ag_func_oid("age_materialize_vle_edges", 1, AGTYPEOID); + } + + return materialize_vle_edges_func_oid; +} + +static Oid get_build_path_func_oid(void) +{ + initialize_clause_function_oid_cache(); + + if (!OidIsValid(build_path_func_oid)) + { + build_path_func_oid = get_ag_func_oid("_agtype_build_path", 1, ANYOID); + } + + return build_path_func_oid; +} + +static Oid get_build_edge_func_oid(void) +{ + initialize_clause_function_oid_cache(); + + if (!OidIsValid(build_edge_func_oid)) + { + build_edge_func_oid = + get_ag_func_oid("_agtype_build_edge", 5, GRAPHIDOID, GRAPHIDOID, + GRAPHIDOID, CSTRINGOID, AGTYPEOID); + } + + return build_edge_func_oid; +} + +static Oid get_build_vertex_func_oid(void) +{ + initialize_clause_function_oid_cache(); + + if (!OidIsValid(build_vertex_func_oid)) + { + build_vertex_func_oid = + get_ag_func_oid("_agtype_build_vertex", 3, GRAPHIDOID, CSTRINGOID, + AGTYPEOID); + } + + return build_vertex_func_oid; +} + +static Oid get_label_name_func_oid(void) +{ + initialize_clause_function_oid_cache(); + + if (!OidIsValid(label_name_func_oid)) + { + label_name_func_oid = + get_ag_func_oid("_label_name", 2, OIDOID, GRAPHIDOID); + } + + return label_name_func_oid; +} + +static Oid get_volatile_wrapper_func_oid(void) +{ + initialize_clause_function_oid_cache(); + + if (!OidIsValid(volatile_wrapper_func_oid)) + { + volatile_wrapper_func_oid = + get_ag_func_oid("agtype_volatile_wrapper", 1, ANYOID); + } + + return volatile_wrapper_func_oid; +} + +static Oid get_age_properties_func_oid(void) +{ + initialize_clause_function_oid_cache(); + + if (!OidIsValid(age_properties_func_oid)) + { + age_properties_func_oid = + get_ag_func_oid("age_properties", 1, AGTYPEOID); + } + + return age_properties_func_oid; +} + +static Node *make_age_properties_func_expr(Node *arg) +{ + return (Node *)makeFuncExpr(get_age_properties_func_oid(), AGTYPEOID, + list_make1(arg), InvalidOid, InvalidOid, + COERCE_SQL_SYNTAX); +} + +static void initialize_clause_function_oid_cache(void) +{ + if (!clause_func_oid_callback_registered) + { + CacheRegisterSyscacheCallback(PROCOID, invalidate_clause_function_oids, + (Datum)0); + CacheRegisterSyscacheCallback(PROCNAMEARGSNSP, + invalidate_clause_function_oids, + (Datum)0); + clause_func_oid_callback_registered = true; + } +} + +static void invalidate_clause_function_oids(Datum arg, int cache_id, + uint32 hash_value) +{ + create_clause_func_oid = InvalidOid; + set_clause_func_oid = InvalidOid; + delete_clause_func_oid = InvalidOid; + merge_clause_func_oid = InvalidOid; + bool_or_func_oid = InvalidOid; + materialize_vle_edges_func_oid = InvalidOid; + build_path_func_oid = InvalidOid; + build_edge_func_oid = InvalidOid; + build_vertex_func_oid = InvalidOid; + label_name_func_oid = InvalidOid; + volatile_wrapper_func_oid = InvalidOid; + age_properties_func_oid = InvalidOid; +} + /* * This function is borrowed from PG version 16.1. * @@ -8323,6 +8724,7 @@ static FuncExpr *make_clause_func_expr(char *function_name, static void markRelsAsNulledBy(ParseState *pstate, Node *n, int jindex) { int varno; + int nnullingrels; ListCell *lc; /* Note: we can't see FromExpr here */ @@ -8350,9 +8752,11 @@ static void markRelsAsNulledBy(ParseState *pstate, Node *n, int jindex) * maintain the p_nullingrels list lazily, we might need to extend it to * make the varno'th entry exist. */ - while (list_length(pstate->p_nullingrels) < varno) + nnullingrels = list_length(pstate->p_nullingrels); + while (nnullingrels < varno) { pstate->p_nullingrels = lappend(pstate->p_nullingrels, NULL); + nnullingrels++; } lc = list_nth_cell(pstate->p_nullingrels, varno - 1); lfirst(lc) = bms_add_member((Bitmapset *) lfirst(lc), jindex); diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c index 1ed777486..902970bda 100644 --- a/src/backend/parser/cypher_expr.c +++ b/src/backend/parser/cypher_expr.c @@ -40,7 +40,11 @@ #include "utils/builtins.h" #include "utils/catcache.h" #include "utils/float.h" +#include "utils/hsearch.h" +#include "utils/inval.h" #include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/syscache.h" #include "parser/cypher_expr.h" #include "parser/cypher_transform_entity.h" @@ -59,6 +63,83 @@ #define FUNC_AGTYPE_TYPECAST_BOOL "agtype_typecast_bool" #define FUNC_AGTYPE_TYPECAST_PG_TEXT "agtype_to_text" +typedef struct function_exists_cache_key +{ + NameData funcname; + NameData extension; +} function_exists_cache_key; + +typedef struct function_exists_cache_entry +{ + function_exists_cache_key key; + bool exists; +} function_exists_cache_entry; + +typedef struct function_extension_cache_entry +{ + Oid func_oid; + bool has_extension; + NameData extension; +} function_extension_cache_entry; + +static HTAB *function_exists_cache = NULL; +static HTAB *function_extension_cache = NULL; +static bool function_cache_callback_registered = false; +static Oid agtype_access_operator_oid = InvalidOid; +static Oid agtype_access_slice_oid = InvalidOid; +static Oid agtype_in_operator_oid = InvalidOid; +static Oid agtype_add_oid = InvalidOid; +static Oid agtype_build_empty_list_oid = InvalidOid; +static Oid agtype_build_list_oid = InvalidOid; +static Oid agtype_build_empty_map_oid = InvalidOid; +static Oid agtype_build_map_oid = InvalidOid; +static Oid agtype_build_map_nonull_oid = InvalidOid; +static Oid age_properties_oid = InvalidOid; +static Oid agtype_string_match_starts_with_oid = InvalidOid; +static Oid agtype_string_match_ends_with_oid = InvalidOid; +static Oid agtype_string_match_contains_oid = InvalidOid; +static Oid text_to_agtype_oid = InvalidOid; +static Oid age_vle_path_length_oid = InvalidOid; +static Oid age_vle_path_node_count_oid = InvalidOid; +static Oid age_vle_edge_tail_count_oid = InvalidOid; +static Oid age_vle_list_is_empty_oid = InvalidOid; +static Oid age_vle_list_slice_count_oid = InvalidOid; +static Oid age_vle_list_slice_is_empty_oid = InvalidOid; +static Oid age_materialize_vle_list_slice_oid = InvalidOid; +static Oid age_materialize_vle_slice_boundary_oid = InvalidOid; +static Oid age_materialize_vle_edges_oid = InvalidOid; +static Oid age_materialize_vle_nodes_oid = InvalidOid; +static Oid age_materialize_vle_node_at_oid = InvalidOid; +static Oid age_materialize_vle_node_tail_last_oid = InvalidOid; +static Oid age_vle_node_tail_last_id_oid = InvalidOid; +static Oid age_vle_node_id_at_oid = InvalidOid; +static Oid age_vle_node_label_at_oid = InvalidOid; +static Oid age_vle_node_labels_at_oid = InvalidOid; +static Oid age_vle_node_properties_at_oid = InvalidOid; +static Oid age_materialize_vle_nodes_slice_oid = InvalidOid; +static Oid age_materialize_vle_nodes_tail_oid = InvalidOid; +static Oid age_materialize_vle_nodes_reversed_oid = InvalidOid; +static Oid age_materialize_vle_edge_at_oid = InvalidOid; +static Oid age_materialize_vle_edge_reversed_at_oid = InvalidOid; +static Oid age_materialize_vle_edge_tail_last_oid = InvalidOid; +static Oid age_vle_edge_tail_last_id_oid = InvalidOid; +static Oid age_vle_tail_last_field_oid = InvalidOid; +static Oid age_vle_tail_last_edge_endpoint_oid = InvalidOid; +static Oid age_vle_tail_last_endpoint_field_oid = InvalidOid; +static Oid age_vle_edge_id_at_oid = InvalidOid; +static Oid age_vle_edge_index_exists_oid = InvalidOid; +static Oid age_vle_edge_indices_equal_oid = InvalidOid; +static Oid age_vle_edge_reversed_index_equal_oid = InvalidOid; +static Oid age_vle_edge_label_at_oid = InvalidOid; +static Oid age_vle_edge_properties_at_oid = InvalidOid; +static Oid age_vle_edge_start_node_at_oid = InvalidOid; +static Oid age_vle_edge_end_node_at_oid = InvalidOid; +static Oid age_vle_edge_endpoint_field_at_oid = InvalidOid; +static Oid age_vle_edge_start_id_at_oid = InvalidOid; +static Oid age_vle_edge_end_id_at_oid = InvalidOid; +static Oid age_materialize_vle_edges_tail_oid = InvalidOid; +static Oid age_materialize_vle_edges_reversed_oid = InvalidOid; + static Node *transform_cypher_expr_recurse(cypher_parsestate *cpstate, Node *expr); static Node *transform_A_Const(cypher_parsestate *cpstate, A_Const *ac); @@ -75,7 +156,20 @@ static Node *transform_cypher_bool_const(cypher_parsestate *cpstate, cypher_bool_const *bc); static Node *transform_cypher_integer_const(cypher_parsestate *cpstate, cypher_integer_const *ic); +static Const *make_agtype_integer_const(int64 value, int location); static Node *transform_AEXPR_IN(cypher_parsestate *cpstate, A_Expr *a); +static Node *try_transform_vle_edge_self_membership( + cypher_parsestate *cpstate, A_Expr *a); +static Node *try_transform_vle_edge_index_equality( + cypher_parsestate *cpstate, A_Expr *a); +static Node *try_transform_vle_edge_boundary_equality( + cypher_parsestate *cpstate, A_Expr *a); +static Node *try_transform_vle_edge_reversed_equality( + cypher_parsestate *cpstate, A_Expr *a); +static Node *try_transform_vle_edge_nested_transform_equality( + cypher_parsestate *cpstate, A_Expr *a); +static Node *try_transform_vle_edge_normalized_equality( + cypher_parsestate *cpstate, A_Expr *a); static Node *transform_cypher_param(cypher_parsestate *cpstate, cypher_param *cp); static Node *transform_cypher_map(cypher_parsestate *cpstate, cypher_map *cm); @@ -101,7 +195,134 @@ static Node *transform_column_ref_for_indirection(cypher_parsestate *cpstate, static Node *transform_external_ext_FuncCall(cypher_parsestate *cpstate, FuncCall *fn, List *targs, Form_pg_proc procform, - char *extension); + const char *extension); +static Expr *get_current_single_vle_path_expr(cypher_parsestate *cpstate, + Node *arg); +static Expr *get_current_vle_relationship_list_expr( + cypher_parsestate *cpstate, Node *arg); +static Node *try_transform_vle_path_length(cypher_parsestate *cpstate, + FuncCall *fn); +static Node *try_transform_vle_path_id_access(cypher_parsestate *cpstate, + FuncCall *fn); +static Node *try_transform_vle_path_boundary_id_function( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_endpoint_id_access( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_nested_transform_index_endpoint_id_access( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_boundary_endpoint_id_access( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_slice_boundary_endpoint_id_access( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_endpoint_access(cypher_parsestate *cpstate, + FuncCall *fn); +static Node *try_transform_vle_path_nested_transform_index_endpoint_access( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_boundary_endpoint_access( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_slice_boundary_endpoint_access( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_endpoint_field(cypher_parsestate *cpstate, + FuncCall *fn); +static Node *try_transform_vle_path_nested_transform_index_endpoint_field( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_slice_boundary_endpoint_field( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_tail_last_endpoint_field( + cypher_parsestate *cpstate, FuncCall *fn); +static Expr *get_current_vle_edge_expr(cypher_parsestate *cpstate, Node *arg); +static Node *try_transform_vle_edge_variable_field( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_node_field(cypher_parsestate *cpstate, + FuncCall *fn); +static bool parse_vle_path_boundary_list_index(cypher_parsestate *cpstate, + Node *node, Expr **vle_expr, + char **list_name, + int64 *index); +static bool parse_vle_normal_or_boundary_index(cypher_parsestate *cpstate, + Node *node, Expr **vle_expr, + Node **index_expr); +static bool parse_vle_nested_transform_edge_equality_index( + cypher_parsestate *cpstate, Node *node, Expr **vle_expr, + Node **index_expr, bool *reversed_index); +static bool parse_vle_edge_endpoint_index(cypher_parsestate *cpstate, + Node *node, Expr **vle_expr, + Node **index_expr, + bool *start_endpoint); +static bool parse_vle_path_indexed_list_index(cypher_parsestate *cpstate, + A_Indirection *a_ind, + Expr **vle_expr, + char **list_name, + Node **index_expr); +static Node *try_transform_vle_path_boundary_field(cypher_parsestate *cpstate, + FuncCall *fn); +static Node *try_transform_vle_path_slice_boundary_field( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_nested_transform_index_field( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_edge_label(cypher_parsestate *cpstate, + FuncCall *fn); +static Node *try_transform_vle_path_edge_properties( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_slice_size(cypher_parsestate *cpstate, + FuncCall *fn); +static Node *try_transform_vle_path_slice_is_empty(cypher_parsestate *cpstate, + FuncCall *fn); +static Node *transform_vle_path_slice_head_last(cypher_parsestate *cpstate, + FuncCall *fn, + int64 mode_offset); +static Node *try_transform_vle_path_slice_head_last(cypher_parsestate *cpstate, + FuncCall *fn); +static Node *try_transform_vle_path_slice_boundary_id_function( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_nested_transform_index_id_function( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_size(cypher_parsestate *cpstate, + FuncCall *fn); +static Node *try_transform_vle_path_is_empty(cypher_parsestate *cpstate, + FuncCall *fn); +static Node *try_transform_vle_path_head_last(cypher_parsestate *cpstate, + FuncCall *fn); +static Node *transform_vle_path_nested_transform_head_last( + cypher_parsestate *cpstate, FuncCall *fn, int64 mode_offset); +static Node *try_transform_vle_path_nested_transform_head_last( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_list_tail_reverse( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_relationships(cypher_parsestate *cpstate, + FuncCall *fn); +static Node *try_transform_vle_path_nodes(cypher_parsestate *cpstate, + FuncCall *fn); +static bool get_nonnegative_integer_const(Node *node, int64 *value); +static bool parse_vle_path_nested_transform_index(cypher_parsestate *cpstate, + A_Indirection *a_ind, + Expr **vle_expr, + char **list_name, + Const **lower_expr, + Const **upper_expr, + int64 *mode); +static Node *try_transform_vle_path_tail_access(cypher_parsestate *cpstate, + A_Indirection *a_ind); +static Node *try_transform_vle_path_reverse_access(cypher_parsestate *cpstate, + A_Indirection *a_ind); +static Node *try_transform_vle_path_nested_transform_access( + cypher_parsestate *cpstate, A_Indirection *a_ind); +static Node *try_transform_vle_path_nodes_access(cypher_parsestate *cpstate, + A_Indirection *a_ind); +static Node *try_transform_vle_path_nodes_slice(cypher_parsestate *cpstate, + A_Indirection *a_ind); +static Node *try_transform_vle_path_list_slice(cypher_parsestate *cpstate, + A_Indirection *a_ind); +static Node *try_transform_vle_path_boundary_id_access( + cypher_parsestate *cpstate, A_Indirection *a_ind); +static Node *try_transform_vle_path_boundary_property_access( + cypher_parsestate *cpstate, A_Indirection *a_ind); +static Node *try_transform_vle_path_relationships_access( + cypher_parsestate *cpstate, A_Indirection *a_ind); +static Node *try_transform_vle_edge_reverse_access(cypher_parsestate *cpstate, + A_Indirection *a_ind); +static Node *try_transform_vle_path_relationships_slice( + cypher_parsestate *cpstate, A_Indirection *a_ind); static List *cast_agtype_args_to_target_type(cypher_parsestate *cpstate, Form_pg_proc procform, List *fargs, @@ -109,9 +330,67 @@ static List *cast_agtype_args_to_target_type(cypher_parsestate *cpstate, static Node *wrap_text_output_to_agtype(cypher_parsestate *cpstate, FuncExpr *fexpr); static Form_pg_proc get_procform(FuncCall *fn, bool err_not_found); -static char *get_mapped_extension(Oid func_oid); -static bool is_extension_external(char *extension); -static char *construct_age_function_name(char *funcname); +static const char *get_mapped_extension(Oid func_oid); +static bool function_belongs_to_extension(Oid func_oid, const char *extension); +static bool is_extension_external(const char *extension); +static bool function_needs_graph_name_argument(const char *name, int name_len); +static char *construct_age_function_name(char *funcname, int funcname_len); +static void initialize_function_caches(void); +static void invalidate_function_caches(Datum arg, int cache_id, + uint32 hash_value); +static void initialize_function_extension_cache(void); +static void initialize_function_exists_cache(void); +static Oid get_agtype_access_operator_oid(void); +static Oid get_agtype_access_slice_oid(void); +static Oid get_agtype_in_operator_oid(void); +static Oid get_agtype_add_oid(void); +static Oid get_agtype_build_empty_list_oid(void); +static Oid get_agtype_build_list_oid(void); +static Oid get_agtype_build_empty_map_oid(void); +static Oid get_agtype_build_map_oid(void); +static Oid get_agtype_build_map_nonull_oid(void); +static Oid get_age_properties_oid(void); +static Oid get_agtype_string_match_starts_with_oid(void); +static Oid get_agtype_string_match_ends_with_oid(void); +static Oid get_agtype_string_match_contains_oid(void); +static Oid get_text_to_agtype_oid(void); +static Oid get_age_vle_path_length_oid(void); +static Oid get_age_vle_path_node_count_oid(void); +static Oid get_age_vle_edge_tail_count_oid(void); +static Oid get_age_vle_list_is_empty_oid(void); +static Oid get_age_vle_list_slice_count_oid(void); +static Oid get_age_vle_list_slice_is_empty_oid(void); +static Oid get_age_materialize_vle_list_slice_oid(void); +static Oid get_age_materialize_vle_slice_boundary_oid(void); +static Oid get_age_materialize_vle_edges_oid(void); +static Oid get_age_materialize_vle_nodes_oid(void); +static Oid get_age_materialize_vle_node_at_oid(void); +static Oid get_age_materialize_vle_node_tail_last_oid(void); +static Oid get_age_vle_node_tail_last_id_oid(void); +static Oid get_age_vle_node_id_at_oid(void); +static Oid get_age_materialize_vle_nodes_slice_oid(void); +static Oid get_age_materialize_vle_nodes_tail_oid(void); +static Oid get_age_materialize_vle_nodes_reversed_oid(void); +static Oid get_age_materialize_vle_edge_at_oid(void); +static Oid get_age_materialize_vle_edge_reversed_at_oid(void); +static Oid get_age_materialize_vle_edge_tail_last_oid(void); +static Oid get_age_vle_edge_tail_last_id_oid(void); +static Oid get_age_vle_tail_last_field_oid(void); +static Oid get_age_vle_tail_last_edge_endpoint_oid(void); +static Oid get_age_vle_tail_last_endpoint_field_oid(void); +static Oid get_age_vle_edge_id_at_oid(void); +static Oid get_age_vle_edge_index_exists_oid(void); +static Oid get_age_vle_edge_indices_equal_oid(void); +static Oid get_age_vle_edge_reversed_index_equal_oid(void); +static Oid get_age_vle_edge_label_at_oid(void); +static Oid get_age_vle_edge_properties_at_oid(void); +static Oid get_age_vle_edge_start_node_at_oid(void); +static Oid get_age_vle_edge_end_node_at_oid(void); +static Oid get_age_vle_edge_endpoint_field_at_oid(void); +static Oid get_age_vle_edge_start_id_at_oid(void); +static Oid get_age_vle_edge_end_id_at_oid(void); +static Oid get_age_materialize_vle_edges_tail_oid(void); +static Oid get_age_materialize_vle_edges_reversed_oid(void); static bool function_exists(char *funcname, char *extension); static Node *coerce_expr_flexible(ParseState *pstate, Node *expr, Oid source_oid, Oid target_oid, @@ -535,8 +814,38 @@ static Node *transform_AEXPR_OP(cypher_parsestate *cpstate, A_Expr *a) { ParseState *pstate = (ParseState *)cpstate; Node *last_srf = pstate->p_last_srf; - Node *lexpr = transform_cypher_expr_recurse(cpstate, a->lexpr); - Node *rexpr = transform_cypher_expr_recurse(cpstate, a->rexpr); + Node *fast_expr = NULL; + Node *lexpr = NULL; + Node *rexpr = NULL; + + fast_expr = try_transform_vle_edge_index_equality(cpstate, a); + if (fast_expr != NULL) + { + return fast_expr; + } + fast_expr = try_transform_vle_edge_boundary_equality(cpstate, a); + if (fast_expr != NULL) + { + return fast_expr; + } + fast_expr = try_transform_vle_edge_reversed_equality(cpstate, a); + if (fast_expr != NULL) + { + return fast_expr; + } + fast_expr = try_transform_vle_edge_nested_transform_equality(cpstate, a); + if (fast_expr != NULL) + { + return fast_expr; + } + fast_expr = try_transform_vle_edge_normalized_equality(cpstate, a); + if (fast_expr != NULL) + { + return fast_expr; + } + + lexpr = transform_cypher_expr_recurse(cpstate, a->lexpr); + rexpr = transform_cypher_expr_recurse(cpstate, a->rexpr); return (Node *)make_op(pstate, a->name, lexpr, rexpr, last_srf, a->location); @@ -571,6 +880,7 @@ static Node *transform_AEXPR_IN(cypher_parsestate *cpstate, A_Expr *a) List *rvars; List *rnonvars; bool useOr; + bool is_not_in; ListCell *l; if (!is_ag_node(a->rexpr, cypher_list)) @@ -583,12 +893,17 @@ static Node *transform_AEXPR_IN(cypher_parsestate *cpstate, A_Expr *a) FuncExpr *func_in_expr; List *args = NIL; + result = try_transform_vle_edge_self_membership(cpstate, a); + if (result != NULL) + { + return result; + } + args = lappend(args, transform_cypher_expr_recurse(cpstate, a->rexpr)); args = lappend(args, transform_cypher_expr_recurse(cpstate, a->lexpr)); /* get the agtype_in_operator function */ - func_in_oid = get_ag_func_oid("agtype_in_operator", 2, AGTYPEOID, - AGTYPEOID); + func_in_oid = get_agtype_in_operator_oid(); func_in_expr = makeFuncExpr(func_in_oid, AGTYPEOID, args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); @@ -601,19 +916,20 @@ static Node *transform_AEXPR_IN(cypher_parsestate *cpstate, A_Expr *a) Assert(is_ag_node(a->rexpr, cypher_list)); rexpr = (cypher_list *)a->rexpr; + is_not_in = (strcmp(strVal(linitial(a->name)), "<>") == 0); /* * Handle empty list case: x IN [] is always false, x NOT IN [] is always true. * We need to check this before processing to avoid returning NULL result * which causes "cache lookup failed for type 0" error. */ - if (rexpr->elems == NIL || list_length((List *)rexpr->elems) == 0) + if (rexpr->elems == NIL) { Datum bool_value; Const *const_result; /* If operator is <> (NOT IN), result is true; otherwise (IN) result is false */ - if (strcmp(strVal(linitial(a->name)), "<>") == 0) + if (is_not_in) { bool_value = BoolGetDatum(true); } @@ -629,14 +945,7 @@ static Node *transform_AEXPR_IN(cypher_parsestate *cpstate, A_Expr *a) } /* If the operator is <>, combine with AND not OR. */ - if (strcmp(strVal(linitial(a->name)), "<>") == 0) - { - useOr = false; - } - else - { - useOr = true; - } + useOr = !is_not_in; lexpr = transform_cypher_expr_recurse(cpstate, a->lexpr); @@ -662,7 +971,7 @@ static Node *transform_AEXPR_IN(cypher_parsestate *cpstate, A_Expr *a) * ScalarArrayOpExpr is only going to be useful if there's more than one * non-Var righthand item. */ - if (list_length(rnonvars) > 1) + if (rnonvars != NIL && lnext(rnonvars, list_head(rnonvars)) != NULL) { List *allexprs; Oid scalar_type; @@ -731,1267 +1040,7376 @@ static Node *transform_AEXPR_IN(cypher_parsestate *cpstate, A_Expr *a) return result; } -static Node *transform_BoolExpr(cypher_parsestate *cpstate, BoolExpr *expr) +static Node *try_transform_vle_edge_self_membership( + cypher_parsestate *cpstate, A_Expr *a) { - ParseState *pstate = (ParseState *)cpstate; - List *args = NIL; - const char *opname; - ListCell *la; - - switch (expr->boolop) + Expr *left_vle_expr = NULL; + Expr *right_vle_expr = NULL; + Node *index_expr = NULL; + Oid func_oid; + bool reversed_index = false; + + if (list_length(a->name) != 1 || + pg_strcasecmp(strVal(linitial(a->name)), "=") != 0) { - case AND_EXPR: - opname = "AND"; - break; - case OR_EXPR: - opname = "OR"; - break; - case NOT_EXPR: - opname = "NOT"; - break; - default: - ereport(ERROR, (errmsg_internal("unrecognized boolop: %d", - (int)expr->boolop))); return NULL; } - foreach (la, expr->args) + right_vle_expr = get_current_vle_relationship_list_expr(cpstate, + a->rexpr); + if (right_vle_expr == NULL) { - Node *arg = lfirst(la); - - arg = transform_cypher_expr_recurse(cpstate, arg); - arg = coerce_to_boolean(pstate, arg, opname); - - args = lappend(args, arg); + return NULL; + } + if (!parse_vle_normal_or_boundary_index(cpstate, a->lexpr, + &left_vle_expr, &index_expr) && + !parse_vle_nested_transform_edge_equality_index( + cpstate, a->lexpr, &left_vle_expr, &index_expr, &reversed_index)) + { + return NULL; + } + if (left_vle_expr != right_vle_expr) + { + return NULL; } - return (Node *)makeBoolExpr(expr->boolop, args, expr->location); + func_oid = get_age_vle_edge_index_exists_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(left_vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); } -/* - * function for transforming cypher_comparison_boolexpr. Since this node is a - * wrapper to let us know when a comparison occurs in a chained comparison, - * we convert it to a PG BoolExpr and transform it. - */ -static Node *transform_cypher_comparison_boolexpr(cypher_parsestate *cpstate, - cypher_comparison_boolexpr *b) +static Node *try_transform_vle_edge_index_equality( + cypher_parsestate *cpstate, A_Expr *a) { - BoolExpr *n = makeNode(BoolExpr); + A_Indirection *left_ind = NULL; + A_Indirection *right_ind = NULL; + A_Indices *left_indices = NULL; + A_Indices *right_indices = NULL; + Expr *left_vle_expr = NULL; + Expr *right_vle_expr = NULL; + Node *left_index_expr = NULL; + Node *right_index_expr = NULL; + Oid func_oid; + + if (list_length(a->name) != 1 || + pg_strcasecmp(strVal(linitial(a->name)), "=") != 0 || + !IsA(a->lexpr, A_Indirection) || + !IsA(a->rexpr, A_Indirection)) + { + return NULL; + } - n->boolop = b->boolop; - n->args = b->args; - n->location = b->location; + left_ind = (A_Indirection *)a->lexpr; + right_ind = (A_Indirection *)a->rexpr; + if (list_length(left_ind->indirection) != 1 || + list_length(right_ind->indirection) != 1 || + !IsA(linitial(left_ind->indirection), A_Indices) || + !IsA(linitial(right_ind->indirection), A_Indices)) + { + return NULL; + } - return transform_BoolExpr(cpstate, n); -} + left_indices = linitial(left_ind->indirection); + right_indices = linitial(right_ind->indirection); + if (left_indices->is_slice || left_indices->uidx == NULL || + right_indices->is_slice || right_indices->uidx == NULL) + { + return NULL; + } + left_vle_expr = get_current_vle_edge_expr(cpstate, left_ind->arg); + right_vle_expr = get_current_vle_edge_expr(cpstate, right_ind->arg); + if (left_vle_expr == NULL || right_vle_expr == NULL || + left_vle_expr != right_vle_expr) + { + return NULL; + } -static Node *transform_cypher_bool_const(cypher_parsestate *cpstate, - cypher_bool_const *bc) + left_index_expr = transform_cypher_expr_recurse(cpstate, + left_indices->uidx); + right_index_expr = transform_cypher_expr_recurse(cpstate, + right_indices->uidx); + func_oid = get_age_vle_edge_indices_equal_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make3(left_vle_expr, left_index_expr, + right_index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_edge_boundary_equality( + cypher_parsestate *cpstate, A_Expr *a) { - ParseState *pstate = (ParseState *)cpstate; - ParseCallbackState pcbstate; - Datum agt; - Const *c; + Node *boundary_node = NULL; + Node *indexed_node = NULL; + FuncCall *boundary_fn = NULL; + A_Indirection *a_ind = NULL; + A_Indices *indices = NULL; + Expr *boundary_vle_expr = NULL; + Expr *indexed_vle_expr = NULL; + Node *boundary_index_expr = NULL; + Node *indexed_index_expr = NULL; + char *boundary_name = NULL; + Oid func_oid; + + if (list_length(a->name) != 1 || + pg_strcasecmp(strVal(linitial(a->name)), "=") != 0) + { + return NULL; + } - setup_parser_errposition_callback(&pcbstate, pstate, bc->location); - agt = boolean_to_agtype(bc->boolean); - cancel_parser_errposition_callback(&pcbstate); + if (IsA(a->lexpr, FuncCall) && IsA(a->rexpr, A_Indirection)) + { + boundary_node = a->lexpr; + indexed_node = a->rexpr; + } + else if (IsA(a->lexpr, A_Indirection) && IsA(a->rexpr, FuncCall)) + { + boundary_node = a->rexpr; + indexed_node = a->lexpr; + } + else + { + return NULL; + } - /* typtypmod, typcollation, typlen, and typbyval of agtype are hard-coded. */ - c = makeConst(AGTYPEOID, -1, InvalidOid, -1, agt, false, false); - c->location = bc->location; + boundary_fn = (FuncCall *)boundary_node; + if (list_length(boundary_fn->funcname) != 1 || + list_length(boundary_fn->args) != 1) + { + return NULL; + } - return (Node *)c; -} + boundary_name = strVal(linitial(boundary_fn->funcname)); + if (pg_strcasecmp(boundary_name, "head") == 0) + { + boundary_index_expr = (Node *)make_agtype_integer_const( + 0, boundary_fn->location); + } + else if (pg_strcasecmp(boundary_name, "last") == 0) + { + boundary_index_expr = (Node *)make_agtype_integer_const( + -1, boundary_fn->location); + } + else + { + return NULL; + } -static Node *transform_cypher_integer_const(cypher_parsestate *cpstate, - cypher_integer_const *ic) -{ - ParseState *pstate = (ParseState *)cpstate; - ParseCallbackState pcbstate; - Datum agt; - Const *c; + boundary_vle_expr = get_current_vle_edge_expr(cpstate, + linitial(boundary_fn->args)); + if (boundary_vle_expr == NULL) + { + return NULL; + } - setup_parser_errposition_callback(&pcbstate, pstate, ic->location); - agt = integer_to_agtype(ic->integer); - cancel_parser_errposition_callback(&pcbstate); + a_ind = (A_Indirection *)indexed_node; + if (list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) + { + return NULL; + } - /* typtypmod, typcollation, typlen, and typbyval of agtype are hard-coded. */ - c = makeConst(AGTYPEOID, -1, InvalidOid, -1, agt, false, false); - c->location = ic->location; + indices = linitial(a_ind->indirection); + if (indices->is_slice || indices->uidx == NULL) + { + return NULL; + } - return (Node *)c; + indexed_vle_expr = get_current_vle_edge_expr(cpstate, a_ind->arg); + if (indexed_vle_expr == NULL || indexed_vle_expr != boundary_vle_expr) + { + return NULL; + } + + indexed_index_expr = transform_cypher_expr_recurse(cpstate, indices->uidx); + func_oid = get_age_vle_edge_indices_equal_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make3(boundary_vle_expr, + boundary_index_expr, + indexed_index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); } -static Node *transform_cypher_param(cypher_parsestate *cpstate, - cypher_param *cp) +static bool parse_vle_reverse_index(cypher_parsestate *cpstate, Node *node, + Expr **vle_expr, Node **index_expr) { - ParseState *pstate = (ParseState *)cpstate; - Const *const_str; - FuncExpr *func_expr; - Oid func_access_oid; - List *args = NIL; + A_Indirection *a_ind = NULL; + A_Indices *indices = NULL; + FuncCall *reverse_fn = NULL; - if (!cpstate->params) + Assert(vle_expr != NULL); + Assert(index_expr != NULL); + + if (!IsA(node, A_Indirection)) { - ereport( - ERROR, - (errcode(ERRCODE_UNDEFINED_PARAMETER), - errmsg( - "parameters argument is missing from cypher() function call"), - parser_errposition(pstate, cp->location))); + return false; } - /* get the agtype_access_operator function */ - func_access_oid = get_ag_func_oid("agtype_access_operator", 1, - AGTYPEARRAYOID); + a_ind = (A_Indirection *)node; + if (!IsA(a_ind->arg, FuncCall) || + list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) + { + return false; + } - args = lappend(args, copyObject(cpstate->params)); + indices = linitial(a_ind->indirection); + if (indices->is_slice || indices->uidx == NULL) + { + return false; + } - const_str = makeConst(AGTYPEOID, -1, InvalidOid, -1, - string_to_agtype(cp->name), false, false); + reverse_fn = (FuncCall *)a_ind->arg; + if (list_length(reverse_fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(reverse_fn->funcname)), "reverse") != 0 || + list_length(reverse_fn->args) != 1) + { + return false; + } - args = lappend(args, const_str); + *vle_expr = get_current_vle_edge_expr(cpstate, linitial(reverse_fn->args)); + if (*vle_expr == NULL) + { + return false; + } - func_expr = makeFuncExpr(func_access_oid, AGTYPEOID, args, InvalidOid, - InvalidOid, COERCE_EXPLICIT_CALL); - func_expr->location = cp->location; + *index_expr = transform_cypher_expr_recurse(cpstate, indices->uidx); - return (Node *)func_expr; + return true; } -static Node *transform_cypher_map_projection(cypher_parsestate *cpstate, - cypher_map_projection *cmp) +static bool parse_vle_normal_or_boundary_index(cypher_parsestate *cpstate, + Node *node, Expr **vle_expr, + Node **index_expr) { - ParseState *pstate; - ListCell *lc; - List *keyvals; - Oid foid_agtype_build_map; - FuncExpr *fexpr_new_map; - bool has_all_prop_selector; - Node *transformed_map_var; - Oid foid_age_properties; - FuncExpr *fexpr_orig_map; - - pstate = (ParseState *)cpstate; - keyvals = NIL; - has_all_prop_selector = false; - fexpr_new_map = NULL; + A_Indirection *a_ind = NULL; + A_Indices *indices = NULL; + FuncCall *boundary_fn = NULL; + char *list_name = NULL; + char *boundary_name = NULL; - /* - * Builds the original map: `age_properties(cmp->map_var)`. Whether map_var - * is compatible (map, vertex or edge) is checked during the execution of - * age_properties(). - */ - transformed_map_var = transform_cypher_expr_recurse(cpstate, - (Node *)cmp->map_var); - foid_age_properties = get_ag_func_oid("age_properties", 1, AGTYPEOID); - fexpr_orig_map = makeFuncExpr(foid_age_properties, AGTYPEOID, - list_make1(transformed_map_var), InvalidOid, - InvalidOid, COERCE_EXPLICIT_CALL); - fexpr_orig_map->location = cmp->location; + Assert(vle_expr != NULL); + Assert(index_expr != NULL); - /* - * Builds a new map. Each map projection element is transformed into a key - * value pair (except for the ALL_PROPERTIES_SELECTOR type). - */ - foreach (lc, cmp->map_elements) + if (IsA(node, FuncCall)) { - cypher_map_projection_element *elem; - Const *key; - Node *val; + FuncCall *boundary_fn = (FuncCall *)node; + char *boundary_name = NULL; - elem = lfirst(lc); - key = NULL; - val = NULL; + if (list_length(boundary_fn->funcname) == 1 && + list_length(boundary_fn->args) == 1) + { + boundary_name = strVal(linitial(boundary_fn->funcname)); + if ((pg_strcasecmp(boundary_name, "head") == 0 || + pg_strcasecmp(boundary_name, "last") == 0) && + is_ag_node(linitial(boundary_fn->args), cypher_list)) + { + cypher_list *cl = (cypher_list *)linitial(boundary_fn->args); - if (elem->type == ALL_PROPERTIES_SELECTOR) + if (list_length(cl->elems) != 1) + { + return false; + } + + return parse_vle_normal_or_boundary_index( + cpstate, linitial(cl->elems), vle_expr, index_expr); + } + } + } + + if (IsA(node, A_Indirection)) + { + a_ind = (A_Indirection *)node; + + if (parse_vle_path_indexed_list_index(cpstate, a_ind, vle_expr, + &list_name, index_expr)) { - has_all_prop_selector = true; - continue; + return pg_strcasecmp(list_name, "relationships") == 0; } - /* Makes key and val based on elem->type */ - switch (elem->type) + if (list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) { - case PROPERTY_SELECTOR: - { - Oid foid_access_op; - FuncExpr *fexpr_access_op; - ArrayExpr *args_access_op; - Const *key_agtype; + return false; + } - /* Makes key from elem->key */ - key = makeConst(TEXTOID, -1, InvalidOid, -1, - CStringGetTextDatum(elem->key), false, false); + indices = linitial(a_ind->indirection); + if (indices->is_slice || indices->uidx == NULL) + { + return false; + } - /* Makes val from `age_properties(cmp->map_var).key` */ - key_agtype = makeConst(AGTYPEOID, -1, InvalidOid, -1, - string_to_agtype(elem->key), false, - false); - foid_access_op = get_ag_func_oid("agtype_access_operator", 1, - AGTYPEARRAYOID); - args_access_op = make_agtype_array_expr( - list_make2(fexpr_orig_map, key_agtype)); - fexpr_access_op = makeFuncExpr(foid_access_op, AGTYPEOID, - list_make1(args_access_op), - InvalidOid, InvalidOid, - COERCE_EXPLICIT_CALL); - fexpr_access_op->funcvariadic = true; - fexpr_access_op->location = -1; - val = (Node *)fexpr_access_op; - - break; - } - case LITERAL_ENTRY: - { - key = makeConst(TEXTOID, -1, InvalidOid, -1, - CStringGetTextDatum(elem->key), false, false); - val = transform_cypher_expr_recurse(cpstate, elem->value); - break; - } - case VARIABLE_SELECTOR: - { - char *key_str; - List *fields; - - Assert(IsA(elem->value, ColumnRef)); - - /* Makes key from the ColumnRef's field */ - fields = ((ColumnRef *)elem->value)->fields; - key_str = strVal(lfirst(list_head(fields))); - key = makeConst(TEXTOID, -1, InvalidOid, -1, - CStringGetTextDatum(key_str), false, false); - - val = transform_cypher_expr_recurse(cpstate, elem->value); - break; - } - case ALL_PROPERTIES_SELECTOR: - { - /* - * Key value pairs of the original map are added later outside - * the loop. Control never reaches this block. - */ - break; - } - default: - { - elog(ERROR, "unknown map projection element type"); - } + *vle_expr = get_current_vle_edge_expr(cpstate, a_ind->arg); + if (*vle_expr == NULL) + { + return false; } - Assert(key); - Assert(val); - keyvals = lappend(lappend(keyvals, key), val); + *index_expr = transform_cypher_expr_recurse(cpstate, indices->uidx); + return true; } - if (keyvals) + if (!IsA(node, FuncCall)) { - foid_agtype_build_map = get_ag_func_oid("agtype_build_map_nonull", 1, - ANYOID); - fexpr_new_map = makeFuncExpr(foid_agtype_build_map, AGTYPEOID, keyvals, - InvalidOid, InvalidOid, - COERCE_EXPLICIT_CALL); - fexpr_new_map->location = cmp->location; + return false; } - /* - * In case .* is present, returns age_properties(cmp->map_var) + the new - * map. Else, returns the new map. - */ - if (has_all_prop_selector) + boundary_fn = (FuncCall *)node; + if (list_length(boundary_fn->funcname) != 1 || + list_length(boundary_fn->args) != 1) { - if (!keyvals) - { - return (Node *)fexpr_orig_map; - } - else - { - return (Node *)make_op(pstate, list_make1(makeString("+")), - (Node *)fexpr_orig_map, - (Node *)fexpr_new_map, - pstate->p_last_srf, -1); - } + return false; + } + + boundary_name = strVal(linitial(boundary_fn->funcname)); + if (pg_strcasecmp(boundary_name, "head") == 0) + { + *index_expr = (Node *)make_agtype_integer_const( + 0, boundary_fn->location); + } + else if (pg_strcasecmp(boundary_name, "last") == 0) + { + *index_expr = (Node *)make_agtype_integer_const( + -1, boundary_fn->location); } else { - Assert(!has_all_prop_selector && fexpr_new_map); - return (Node *)fexpr_new_map; + return false; } -} -/* - * Helper function to transform a cypher map into an agtype map. The function - * will use agtype_add to concatenate the argument list when the number of - * parameters (keys and values) exceeds 100, a PG limitation. - */ -static Node *transform_cypher_map(cypher_parsestate *cpstate, cypher_map *cm) -{ - ParseState *pstate = (ParseState *)cpstate; - List *newkeyvals_args = NIL; - ListCell *le = NULL; - FuncExpr *fexpr = NULL; - FuncExpr *aa_lhs_arg = NULL; - Oid abm_func_oid = InvalidOid; - Oid aa_func_oid = InvalidOid; - int nkeyvals = 0; - int i = 0; + *vle_expr = get_current_vle_edge_expr(cpstate, linitial(boundary_fn->args)); - /* get the number of keys and values */ - nkeyvals = list_length(cm->keyvals); + return *vle_expr != NULL; +} - /* error out if it isn't even */ - if (nkeyvals % 2 != 0) +static Node *try_transform_vle_edge_normalized_equality( + cypher_parsestate *cpstate, A_Expr *a) +{ + Expr *left_vle_expr = NULL; + Expr *right_vle_expr = NULL; + Node *left_index_expr = NULL; + Node *right_index_expr = NULL; + Oid func_oid; + + if (list_length(a->name) != 1 || + pg_strcasecmp(strVal(linitial(a->name)), "=") != 0) { - ereport(ERROR, - (errcode(ERRCODE_DATA_EXCEPTION), - errmsg("number of keys does not match number of values"))); + return NULL; } - if (nkeyvals == 0) + if (!parse_vle_normal_or_boundary_index(cpstate, a->lexpr, + &left_vle_expr, + &left_index_expr) || + !parse_vle_normal_or_boundary_index(cpstate, a->rexpr, + &right_vle_expr, + &right_index_expr)) { - abm_func_oid = get_ag_func_oid("agtype_build_map", 0); + return NULL; } - else if (!cm->keep_null) + if (left_vle_expr == NULL || left_vle_expr != right_vle_expr) { - abm_func_oid = get_ag_func_oid("agtype_build_map_nonull", 1, ANYOID); + return NULL; } - else + + func_oid = get_age_vle_edge_indices_equal_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make3(left_vle_expr, left_index_expr, + right_index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static bool parse_vle_nested_transform_edge_equality_index( + cypher_parsestate *cpstate, Node *node, Expr **vle_expr, + Node **index_expr, bool *reversed_index) +{ + A_Indirection *a_ind = NULL; + A_Indices *indices = NULL; + Const *lower_expr = NULL; + Const *upper_expr = NULL; + char *list_name = NULL; + int64 index; + int64 mode; + + Assert(vle_expr != NULL); + Assert(index_expr != NULL); + Assert(reversed_index != NULL); + + if (!IsA(node, A_Indirection)) { - abm_func_oid = get_ag_func_oid("agtype_build_map", 1, ANYOID); + return false; } - /* get the concat function oid, if necessary */ - if (nkeyvals > 100) + a_ind = (A_Indirection *)node; + if (list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) { - aa_func_oid = get_ag_func_oid("agtype_add", 2, AGTYPEOID, AGTYPEOID); + return false; } - /* get the key/val list */ - le = list_head(cm->keyvals); - /* while we have key/val to process */ - while (le != NULL) + indices = linitial(a_ind->indirection); + if (indices->is_slice || indices->uidx == NULL || + !get_nonnegative_integer_const(indices->uidx, &index)) { - Node *key = NULL; - Node *val = NULL; - Node *newval = NULL; - ParseCallbackState pcbstate; - Const *newkey = NULL; - - /* get the key */ - key = lfirst(le); - le = lnext(cm->keyvals, le); - /* get the value */ - val = lfirst(le); - le = lnext(cm->keyvals, le); - - /* transform the value */ - newval = transform_cypher_expr_recurse(cpstate, val); - - /* - * If we have more than 50 key/value pairs, 100 elements, we will need - * to add in the list concatenation function. - */ - if (i >= 50) - { - /* build the object for the first 50 pairs for concat */ - fexpr = makeFuncExpr(abm_func_oid, AGTYPEOID, newkeyvals_args, - InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); - fexpr->location = cm->location; - - /* initial case, set up for concatenating 2 lists */ - if (aa_lhs_arg == NULL) - { - aa_lhs_arg = fexpr; - } - /* - * For every other case, concatenate the list on to the previous - * concatenate operation. - */ - else - { - List *aa_args = list_make2(aa_lhs_arg, fexpr); - - fexpr = makeFuncExpr(aa_func_oid, AGTYPEOID, aa_args, - InvalidOid, InvalidOid, - COERCE_EXPLICIT_CALL); - fexpr->location = cm->location; - - /* set the lhs to the concatenation operation */ - aa_lhs_arg = fexpr; - } - - /* reset for the next 50 pairs */ - newkeyvals_args = NIL; - i = 0; - fexpr = NULL; - } - - /* build and append the transformed key/val pair */ - setup_parser_errposition_callback(&pcbstate, pstate, cm->location); - /* typtypmod, typcollation, typlen, and typbyval of agtype are */ - /* hard-coded. */ - newkey = makeConst(TEXTOID, -1, InvalidOid, -1, - CStringGetTextDatum(strVal(key)), false, false); - cancel_parser_errposition_callback(&pcbstate); - - newkeyvals_args = lappend(lappend(newkeyvals_args, newkey), newval); - - i++; + return false; } - /* now build the final map function */ - fexpr = makeFuncExpr(abm_func_oid, AGTYPEOID, newkeyvals_args, InvalidOid, - InvalidOid, COERCE_EXPLICIT_CALL); - fexpr->location = cm->location; - - /* - * If there was a previous concatenation, build a final concatenation - * function node. - */ - if (aa_lhs_arg != NULL) + if (!parse_vle_path_nested_transform_index(cpstate, a_ind, vle_expr, + &list_name, &lower_expr, + &upper_expr, &mode) || + pg_strcasecmp(list_name, "relationships") != 0) { - List *aa_args = list_make2(aa_lhs_arg, fexpr); + return false; + } - fexpr = makeFuncExpr(aa_func_oid, AGTYPEOID, aa_args, InvalidOid, - InvalidOid, COERCE_EXPLICIT_CALL); + if (mode >= 360) + { + *reversed_index = false; + index += 2; + } + else if (mode >= 240) + { + *reversed_index = true; + index += 1; + } + else if (mode >= 120) + { + *reversed_index = true; + } + else + { + return false; } - return (Node *)fexpr; + *index_expr = (Node *)make_agtype_integer_const( + index, exprLocation(indices->uidx)); + + return true; } -/* - * Helper function to transform a cypher list into an agtype list. The function - * will use agtype_add to concatenate argument lists when the number of list - * elements, parameters, exceeds 100, a PG limitation. - */ -static Node *transform_cypher_list(cypher_parsestate *cpstate, cypher_list *cl) +static Node *try_transform_vle_edge_reversed_equality( + cypher_parsestate *cpstate, A_Expr *a) { - List *abl_args = NIL; - ListCell *le = NULL; - FuncExpr *aa_lhs_arg = NULL; - FuncExpr *fexpr = NULL; - Oid abl_func_oid = InvalidOid; - Oid aa_func_oid = InvalidOid; - int nelems = 0; - int i = 0; + Expr *reversed_vle_expr = NULL; + Expr *normal_vle_expr = NULL; + Node *reversed_index_expr = NULL; + Node *normal_index_expr = NULL; + Oid func_oid; + + if (list_length(a->name) != 1 || + pg_strcasecmp(strVal(linitial(a->name)), "=") != 0) + { + return NULL; + } - /* determine which build function we need */ - nelems = list_length(cl->elems); - if (nelems == 0) + if (!parse_vle_reverse_index(cpstate, a->lexpr, &reversed_vle_expr, + &reversed_index_expr)) { - abl_func_oid = get_ag_func_oid("agtype_build_list", 0); + if (!parse_vle_reverse_index(cpstate, a->rexpr, &reversed_vle_expr, + &reversed_index_expr)) + { + return NULL; + } + if (!parse_vle_normal_or_boundary_index(cpstate, a->lexpr, + &normal_vle_expr, + &normal_index_expr)) + { + return NULL; + } } - else + else if (!parse_vle_normal_or_boundary_index(cpstate, a->rexpr, + &normal_vle_expr, + &normal_index_expr)) { - abl_func_oid = get_ag_func_oid("agtype_build_list", 1, ANYOID); + return NULL; } - /* get the concat function oid, if necessary */ - if (nelems > 100) + if (normal_vle_expr == NULL || normal_vle_expr != reversed_vle_expr) { - aa_func_oid = get_ag_func_oid("agtype_add", 2, AGTYPEOID, AGTYPEOID); + return NULL; } - /* iterate through the list of elements */ - foreach (le, cl->elems) + func_oid = get_age_vle_edge_reversed_index_equal_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make3(reversed_vle_expr, + reversed_index_expr, + normal_index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_edge_nested_transform_equality( + cypher_parsestate *cpstate, A_Expr *a) +{ + Expr *nested_vle_expr = NULL; + Expr *normal_vle_expr = NULL; + Expr *other_nested_vle_expr = NULL; + Node *nested_index_expr = NULL; + Node *normal_index_expr = NULL; + Node *other_nested_index_expr = NULL; + bool nested_reversed = false; + bool other_nested_reversed = false; + Oid func_oid; + + if (list_length(a->name) != 1 || + pg_strcasecmp(strVal(linitial(a->name)), "=") != 0) + { + return NULL; + } + + if (!parse_vle_nested_transform_edge_equality_index( + cpstate, a->lexpr, &nested_vle_expr, &nested_index_expr, + &nested_reversed)) + { + if (!parse_vle_nested_transform_edge_equality_index( + cpstate, a->rexpr, &nested_vle_expr, &nested_index_expr, + &nested_reversed)) + { + return NULL; + } + if (!parse_vle_normal_or_boundary_index(cpstate, a->lexpr, + &normal_vle_expr, + &normal_index_expr)) + { + return NULL; + } + } + else if (!parse_vle_normal_or_boundary_index(cpstate, a->rexpr, + &normal_vle_expr, + &normal_index_expr)) + { + if (!parse_vle_nested_transform_edge_equality_index( + cpstate, a->rexpr, &other_nested_vle_expr, + &other_nested_index_expr, &other_nested_reversed) || + other_nested_vle_expr == NULL || + other_nested_vle_expr != nested_vle_expr) + { + return NULL; + } + + if (nested_reversed == other_nested_reversed) + { + func_oid = get_age_vle_edge_indices_equal_oid(); + return (Node *)makeFuncExpr( + func_oid, AGTYPEOID, + list_make3(nested_vle_expr, nested_index_expr, + other_nested_index_expr), + InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); + } + if (nested_reversed) + { + func_oid = get_age_vle_edge_reversed_index_equal_oid(); + return (Node *)makeFuncExpr( + func_oid, AGTYPEOID, + list_make3(nested_vle_expr, nested_index_expr, + other_nested_index_expr), + InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); + } + + func_oid = get_age_vle_edge_reversed_index_equal_oid(); + return (Node *)makeFuncExpr( + func_oid, AGTYPEOID, + list_make3(nested_vle_expr, other_nested_index_expr, + nested_index_expr), + InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); + } + + if (normal_vle_expr == NULL || normal_vle_expr != nested_vle_expr) + { + return NULL; + } + + if (nested_reversed) + { + func_oid = get_age_vle_edge_reversed_index_equal_oid(); + } + else + { + func_oid = get_age_vle_edge_indices_equal_oid(); + } + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make3(nested_vle_expr, + nested_index_expr, + normal_index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *transform_BoolExpr(cypher_parsestate *cpstate, BoolExpr *expr) +{ + ParseState *pstate = (ParseState *)cpstate; + List *args = NIL; + const char *opname; + ListCell *la; + + switch (expr->boolop) + { + case AND_EXPR: + opname = "AND"; + break; + case OR_EXPR: + opname = "OR"; + break; + case NOT_EXPR: + opname = "NOT"; + break; + default: + ereport(ERROR, (errmsg_internal("unrecognized boolop: %d", + (int)expr->boolop))); + return NULL; + } + + foreach (la, expr->args) + { + Node *arg = lfirst(la); + + arg = transform_cypher_expr_recurse(cpstate, arg); + arg = coerce_to_boolean(pstate, arg, opname); + + args = lappend(args, arg); + } + + return (Node *)makeBoolExpr(expr->boolop, args, expr->location); +} + +/* + * function for transforming cypher_comparison_boolexpr. Since this node is a + * wrapper to let us know when a comparison occurs in a chained comparison, + * we convert it to a PG BoolExpr and transform it. + */ +static Node *transform_cypher_comparison_boolexpr(cypher_parsestate *cpstate, + cypher_comparison_boolexpr *b) +{ + BoolExpr *n = makeNode(BoolExpr); + + n->boolop = b->boolop; + n->args = b->args; + n->location = b->location; + + return transform_BoolExpr(cpstate, n); +} + + +static Node *transform_cypher_bool_const(cypher_parsestate *cpstate, + cypher_bool_const *bc) +{ + ParseState *pstate = (ParseState *)cpstate; + ParseCallbackState pcbstate; + Datum agt; + Const *c; + + setup_parser_errposition_callback(&pcbstate, pstate, bc->location); + agt = boolean_to_agtype(bc->boolean); + cancel_parser_errposition_callback(&pcbstate); + + /* typtypmod, typcollation, typlen, and typbyval of agtype are hard-coded. */ + c = makeConst(AGTYPEOID, -1, InvalidOid, -1, agt, false, false); + c->location = bc->location; + + return (Node *)c; +} + +static Node *transform_cypher_integer_const(cypher_parsestate *cpstate, + cypher_integer_const *ic) +{ + ParseState *pstate = (ParseState *)cpstate; + ParseCallbackState pcbstate; + Datum agt; + Const *c; + + setup_parser_errposition_callback(&pcbstate, pstate, ic->location); + agt = integer_to_agtype(ic->integer); + cancel_parser_errposition_callback(&pcbstate); + + /* typtypmod, typcollation, typlen, and typbyval of agtype are hard-coded. */ + c = makeConst(AGTYPEOID, -1, InvalidOid, -1, agt, false, false); + c->location = ic->location; + + return (Node *)c; +} + +static Const *make_agtype_integer_const(int64 value, int location) +{ + Datum agt; + Const *c; + + agt = integer_to_agtype(value); + c = makeConst(AGTYPEOID, -1, InvalidOid, -1, agt, false, false); + c->location = location; + + return c; +} + +static Node *transform_cypher_param(cypher_parsestate *cpstate, + cypher_param *cp) +{ + ParseState *pstate = (ParseState *)cpstate; + Const *const_str; + FuncExpr *func_expr; + Oid func_access_oid; + List *args = NIL; + + if (!cpstate->params) + { + ereport( + ERROR, + (errcode(ERRCODE_UNDEFINED_PARAMETER), + errmsg( + "parameters argument is missing from cypher() function call"), + parser_errposition(pstate, cp->location))); + } + + /* get the agtype_access_operator function */ + func_access_oid = get_agtype_access_operator_oid(); + + args = lappend(args, copyObject(cpstate->params)); + + const_str = makeConst(AGTYPEOID, -1, InvalidOid, -1, + string_to_agtype(cp->name), false, false); + + args = lappend(args, const_str); + + func_expr = makeFuncExpr(func_access_oid, AGTYPEOID, args, InvalidOid, + InvalidOid, COERCE_EXPLICIT_CALL); + func_expr->location = cp->location; + + return (Node *)func_expr; +} + +static Node *transform_cypher_map_projection(cypher_parsestate *cpstate, + cypher_map_projection *cmp) +{ + ParseState *pstate; + ListCell *lc; + List *keyvals; + Oid foid_agtype_build_map; + FuncExpr *fexpr_new_map; + bool has_all_prop_selector; + Node *transformed_map_var; + Oid foid_age_properties; + FuncExpr *fexpr_orig_map; + + pstate = (ParseState *)cpstate; + keyvals = NIL; + has_all_prop_selector = false; + fexpr_new_map = NULL; + + /* + * Builds the original map: `age_properties(cmp->map_var)`. Whether map_var + * is compatible (map, vertex or edge) is checked during the execution of + * age_properties(). + */ + transformed_map_var = transform_cypher_expr_recurse(cpstate, + (Node *)cmp->map_var); + foid_age_properties = get_age_properties_oid(); + fexpr_orig_map = makeFuncExpr(foid_age_properties, AGTYPEOID, + list_make1(transformed_map_var), InvalidOid, + InvalidOid, COERCE_EXPLICIT_CALL); + fexpr_orig_map->location = cmp->location; + + /* + * Builds a new map. Each map projection element is transformed into a key + * value pair (except for the ALL_PROPERTIES_SELECTOR type). + */ + foreach (lc, cmp->map_elements) + { + cypher_map_projection_element *elem; + Const *key; + Node *val; + + elem = lfirst(lc); + key = NULL; + val = NULL; + + if (elem->type == ALL_PROPERTIES_SELECTOR) + { + has_all_prop_selector = true; + continue; + } + + /* Makes key and val based on elem->type */ + switch (elem->type) + { + case PROPERTY_SELECTOR: + { + Oid foid_access_op; + FuncExpr *fexpr_access_op; + ArrayExpr *args_access_op; + Const *key_agtype; + + /* Makes key from elem->key */ + key = makeConst(TEXTOID, -1, InvalidOid, -1, + CStringGetTextDatum(elem->key), false, false); + + /* Makes val from `age_properties(cmp->map_var).key` */ + key_agtype = makeConst(AGTYPEOID, -1, InvalidOid, -1, + string_to_agtype(elem->key), false, + false); + foid_access_op = get_agtype_access_operator_oid(); + args_access_op = make_agtype_array_expr( + list_make2(fexpr_orig_map, key_agtype)); + fexpr_access_op = makeFuncExpr(foid_access_op, AGTYPEOID, + list_make1(args_access_op), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + fexpr_access_op->funcvariadic = true; + fexpr_access_op->location = -1; + val = (Node *)fexpr_access_op; + + break; + } + case LITERAL_ENTRY: + { + key = makeConst(TEXTOID, -1, InvalidOid, -1, + CStringGetTextDatum(elem->key), false, false); + val = transform_cypher_expr_recurse(cpstate, elem->value); + break; + } + case VARIABLE_SELECTOR: + { + char *key_str; + List *fields; + + Assert(IsA(elem->value, ColumnRef)); + + /* Makes key from the ColumnRef's field */ + fields = ((ColumnRef *)elem->value)->fields; + key_str = strVal(lfirst(list_head(fields))); + key = makeConst(TEXTOID, -1, InvalidOid, -1, + CStringGetTextDatum(key_str), false, false); + + val = transform_cypher_expr_recurse(cpstate, elem->value); + break; + } + case ALL_PROPERTIES_SELECTOR: + { + /* + * Key value pairs of the original map are added later outside + * the loop. Control never reaches this block. + */ + break; + } + default: + { + elog(ERROR, "unknown map projection element type"); + } + } + + Assert(key); + Assert(val); + keyvals = lappend(lappend(keyvals, key), val); + } + + if (keyvals) + { + foid_agtype_build_map = get_agtype_build_map_nonull_oid(); + fexpr_new_map = makeFuncExpr(foid_agtype_build_map, AGTYPEOID, keyvals, + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + fexpr_new_map->location = cmp->location; + } + + /* + * In case .* is present, returns age_properties(cmp->map_var) + the new + * map. Else, returns the new map. + */ + if (has_all_prop_selector) + { + if (!keyvals) + { + return (Node *)fexpr_orig_map; + } + else + { + return (Node *)make_op(pstate, list_make1(makeString("+")), + (Node *)fexpr_orig_map, + (Node *)fexpr_new_map, + pstate->p_last_srf, -1); + } + } + else + { + Assert(!has_all_prop_selector && fexpr_new_map); + return (Node *)fexpr_new_map; + } +} + +/* + * Helper function to transform a cypher map into an agtype map. The function + * will use agtype_add to concatenate the argument list when the number of + * parameters (keys and values) exceeds 100, a PG limitation. + */ +static Node *transform_cypher_map(cypher_parsestate *cpstate, cypher_map *cm) +{ + ParseState *pstate = (ParseState *)cpstate; + List *newkeyvals_args = NIL; + ListCell *le = NULL; + FuncExpr *fexpr = NULL; + FuncExpr *aa_lhs_arg = NULL; + Oid abm_func_oid = InvalidOid; + Oid aa_func_oid = InvalidOid; + int nkeyvals = 0; + int i = 0; + + /* get the number of keys and values */ + nkeyvals = list_length(cm->keyvals); + + /* error out if it isn't even */ + if (nkeyvals % 2 != 0) + { + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("number of keys does not match number of values"))); + } + + if (nkeyvals == 0) + { + abm_func_oid = get_agtype_build_empty_map_oid(); + } + else if (!cm->keep_null) + { + abm_func_oid = get_agtype_build_map_nonull_oid(); + } + else + { + abm_func_oid = get_agtype_build_map_oid(); + } + + /* get the concat function oid, if necessary */ + if (nkeyvals > 100) + { + aa_func_oid = get_agtype_add_oid(); + } + + /* get the key/val list */ + le = list_head(cm->keyvals); + /* while we have key/val to process */ + while (le != NULL) + { + Node *key = NULL; + Node *val = NULL; + Node *newval = NULL; + ParseCallbackState pcbstate; + Const *newkey = NULL; + + /* get the key */ + key = lfirst(le); + le = lnext(cm->keyvals, le); + /* get the value */ + val = lfirst(le); + le = lnext(cm->keyvals, le); + + /* transform the value */ + newval = transform_cypher_expr_recurse(cpstate, val); + + /* + * If we have more than 50 key/value pairs, 100 elements, we will need + * to add in the list concatenation function. + */ + if (i >= 50) + { + /* build the object for the first 50 pairs for concat */ + fexpr = makeFuncExpr(abm_func_oid, AGTYPEOID, newkeyvals_args, + InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); + fexpr->location = cm->location; + + /* initial case, set up for concatenating 2 lists */ + if (aa_lhs_arg == NULL) + { + aa_lhs_arg = fexpr; + } + /* + * For every other case, concatenate the list on to the previous + * concatenate operation. + */ + else + { + List *aa_args = list_make2(aa_lhs_arg, fexpr); + + fexpr = makeFuncExpr(aa_func_oid, AGTYPEOID, aa_args, + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + fexpr->location = cm->location; + + /* set the lhs to the concatenation operation */ + aa_lhs_arg = fexpr; + } + + /* reset for the next 50 pairs */ + newkeyvals_args = NIL; + i = 0; + fexpr = NULL; + } + + /* build and append the transformed key/val pair */ + setup_parser_errposition_callback(&pcbstate, pstate, cm->location); + /* typtypmod, typcollation, typlen, and typbyval of agtype are */ + /* hard-coded. */ + newkey = makeConst(TEXTOID, -1, InvalidOid, -1, + CStringGetTextDatum(strVal(key)), false, false); + cancel_parser_errposition_callback(&pcbstate); + + newkeyvals_args = lappend(lappend(newkeyvals_args, newkey), newval); + + i++; + } + + /* now build the final map function */ + fexpr = makeFuncExpr(abm_func_oid, AGTYPEOID, newkeyvals_args, InvalidOid, + InvalidOid, COERCE_EXPLICIT_CALL); + fexpr->location = cm->location; + + /* + * If there was a previous concatenation, build a final concatenation + * function node. + */ + if (aa_lhs_arg != NULL) + { + List *aa_args = list_make2(aa_lhs_arg, fexpr); + + fexpr = makeFuncExpr(aa_func_oid, AGTYPEOID, aa_args, InvalidOid, + InvalidOid, COERCE_EXPLICIT_CALL); + } + + return (Node *)fexpr; +} + +/* + * Helper function to transform a cypher list into an agtype list. The function + * will use agtype_add to concatenate argument lists when the number of list + * elements, parameters, exceeds 100, a PG limitation. + */ +static Node *transform_cypher_list(cypher_parsestate *cpstate, cypher_list *cl) +{ + List *abl_args = NIL; + ListCell *le = NULL; + FuncExpr *aa_lhs_arg = NULL; + FuncExpr *fexpr = NULL; + Oid abl_func_oid = InvalidOid; + Oid aa_func_oid = InvalidOid; + int nelems = 0; + int i = 0; + + /* determine which build function we need */ + nelems = list_length(cl->elems); + if (nelems == 0) + { + abl_func_oid = get_agtype_build_empty_list_oid(); + } + else + { + abl_func_oid = get_agtype_build_list_oid(); + } + + /* get the concat function oid, if necessary */ + if (nelems > 100) + { + aa_func_oid = get_agtype_add_oid(); + } + + /* iterate through the list of elements */ + foreach (le, cl->elems) + { + Node *texpr = NULL; + + /* transform the argument */ + texpr = transform_cypher_expr_recurse(cpstate, lfirst(le)); + + /* + * If we have more than 100 elements we will need to add in the list + * concatenation function. + */ + if (i >= 100) + { + /* build the list function node argument for concatenate */ + fexpr = makeFuncExpr(abl_func_oid, AGTYPEOID, abl_args, InvalidOid, + InvalidOid, COERCE_EXPLICIT_CALL); + fexpr->location = cl->location; + + /* initial case, set up for concatenating 2 lists */ + if (aa_lhs_arg == NULL) + { + aa_lhs_arg = fexpr; + } + /* + * For every other case, concatenate the list on to the previous + * concatenate operation. + */ + else + { + List *aa_args = list_make2(aa_lhs_arg, fexpr); + + fexpr = makeFuncExpr(aa_func_oid, AGTYPEOID, aa_args, + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + fexpr->location = cl->location; + + /* set the lhs to the concatenation operation */ + aa_lhs_arg = fexpr; + } + + /* reset */ + abl_args = NIL; + i = 0; + fexpr = NULL; + } + + /* now add the latest transformed expression to the list */ + abl_args = lappend(abl_args, texpr); + i++; + } + + /* now build the final list function */ + fexpr = makeFuncExpr(abl_func_oid, AGTYPEOID, abl_args, InvalidOid, + InvalidOid, COERCE_EXPLICIT_CALL); + fexpr->location = cl->location; + + /* + * If there was a previous concatenation or list function, build a final + * concatenation function node + */ + if (aa_lhs_arg != NULL) + { + List *aa_args = list_make2(aa_lhs_arg, fexpr); + + fexpr = makeFuncExpr(aa_func_oid, AGTYPEOID, aa_args, InvalidOid, + InvalidOid, COERCE_EXPLICIT_CALL); + } + + return (Node *)fexpr; +} + +/* makes a VARIADIC agtype array */ +static ArrayExpr *make_agtype_array_expr(List *args) +{ + ArrayExpr *newa = makeNode(ArrayExpr); + + newa->elements = args; + + /* assume all the variadic arguments were coerced to the same type */ + newa->element_typeid = AGTYPEOID; + newa->array_typeid = AGTYPEARRAYOID; + + if (!OidIsValid(newa->array_typeid)) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("could not find array type for data type %s", + format_type_be(newa->element_typeid)))); + } + + /* array_collid will be set by parse_collate.c */ + newa->multidims = false; + + return newa; +} + +/* + * Transform a ColumnRef for indirection. Try to find the rte that the ColumnRef + * references and pass the properties of that rte as what the ColumnRef is + * referencing. Otherwise, reference the Var. + */ +static Node *transform_column_ref_for_indirection(cypher_parsestate *cpstate, + ColumnRef *cr) +{ + ParseState *pstate = (ParseState *)cpstate; + ParseNamespaceItem *pnsi = NULL; + Node *field1 = linitial(cr->fields); + char *relname = NULL; + Node *node = NULL; + int levels_up = 0; + + Assert(IsA(field1, String)); + relname = strVal(field1); + + /* locate the referenced RTE (used to be find_rte(cpstate, relname)) */ + pnsi = refnameNamespaceItem(pstate, NULL, relname, cr->location, + &levels_up); + + /* + * If we didn't find anything, try looking for a previous variable + * reference. Otherwise, return NULL (colNameToVar will return NULL + * if nothing is found). + */ + if (!pnsi) + { + Node *prev_var = colNameToVar(pstate, relname, false, cr->location); + + return prev_var; + } + + /* find the properties column of the NSI and return a var for it */ + node = scanNSItemForColumn(pstate, pnsi, levels_up, "properties", + cr->location); + + /* + * If there's no "properties" column, continue transforming the + * ColumnRef as an agtype value and try to apply the indirection via + * agtype_access_operator. + */ + return node; +} + +static Node *transform_A_Indirection(cypher_parsestate *cpstate, + A_Indirection *a_ind) +{ + ParseState *pstate = &cpstate->pstate; + int location; + ListCell *lc = NULL; + Node *ind_arg_expr = NULL; + FuncExpr *func_expr = NULL; + Oid func_access_oid = InvalidOid; + Oid func_slice_oid = InvalidOid; + List *args = NIL; + bool is_access = false; + Node *fast_expr = NULL; + + /* validate that we have an indirection with at least 1 entry */ + Assert(a_ind != NULL && list_length(a_ind->indirection)); + + fast_expr = try_transform_vle_path_tail_access(cpstate, a_ind); + if (fast_expr != NULL) + { + return fast_expr; + } + fast_expr = try_transform_vle_path_reverse_access(cpstate, a_ind); + if (fast_expr != NULL) + { + return fast_expr; + } + fast_expr = try_transform_vle_path_nested_transform_access(cpstate, a_ind); + if (fast_expr != NULL) + { + return fast_expr; + } + fast_expr = try_transform_vle_path_nodes_access(cpstate, a_ind); + if (fast_expr != NULL) + { + return fast_expr; + } + fast_expr = try_transform_vle_path_nodes_slice(cpstate, a_ind); + if (fast_expr != NULL) + { + return fast_expr; + } + fast_expr = try_transform_vle_path_list_slice(cpstate, a_ind); + if (fast_expr != NULL) + { + return fast_expr; + } + fast_expr = try_transform_vle_path_boundary_id_access(cpstate, a_ind); + if (fast_expr != NULL) + { + return fast_expr; + } + fast_expr = try_transform_vle_path_boundary_property_access(cpstate, a_ind); + if (fast_expr != NULL) + { + return fast_expr; + } + fast_expr = try_transform_vle_path_relationships_access(cpstate, a_ind); + if (fast_expr != NULL) + { + return fast_expr; + } + fast_expr = try_transform_vle_edge_reverse_access(cpstate, a_ind); + if (fast_expr != NULL) + { + return fast_expr; + } + fast_expr = try_transform_vle_path_relationships_slice(cpstate, a_ind); + if (fast_expr != NULL) + { + return fast_expr; + } + + /* get the agtype_access_operator function */ + func_access_oid = get_agtype_access_operator_oid(); + /* get the agtype_access_slice function */ + func_slice_oid = get_agtype_access_slice_oid(); + + /* + * If the indirection argument is a ColumnRef, we want to pull out the + * properties, as a var node, if possible. + */ + if (IsA(a_ind->arg, ColumnRef)) + { + ColumnRef *cr = (ColumnRef *)a_ind->arg; + + ind_arg_expr = transform_column_ref_for_indirection(cpstate, cr); + } + + /* + * If we didn't get the properties from a ColumnRef, just transform the + * indirection argument. + */ + if (ind_arg_expr == NULL) + { + ind_arg_expr = transform_cypher_expr_recurse(cpstate, a_ind->arg); + } + + ind_arg_expr = coerce_to_common_type(pstate, ind_arg_expr, AGTYPEOID, + "A_indirection"); + + /* get the location of the expression */ + location = exprLocation(ind_arg_expr); + + /* add the expression as the first entry */ + args = lappend(args, ind_arg_expr); + + /* iterate through the indirections */ + foreach (lc, a_ind->indirection) + { + Node *node = lfirst(lc); + + /* is this a slice? */ + if (IsA(node, A_Indices) && ((A_Indices *)node)->is_slice) + { + A_Indices *indices = (A_Indices *)node; + + /* were we working on an access? if so, wrap and close it */ + if (is_access) + { + ArrayExpr *newa = make_agtype_array_expr(args); + + func_expr = makeFuncExpr(func_access_oid, AGTYPEOID, + list_make1(newa), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + + func_expr->funcvariadic = true; + func_expr->location = location; + + /* + * The result of this function is the input to the next access + * or slice operator. So we need to start out with a new arg + * list with this function expression. + */ + args = lappend(NIL, func_expr); + + /* we are no longer working on an access */ + is_access = false; + + } + + /* add slice bounds to args */ + if (!indices->lidx) + { + A_Const *n = makeNode(A_Const); + n->isnull = true; + n->location = -1; + node = transform_cypher_expr_recurse(cpstate, (Node *)n); + } + else + { + node = transform_cypher_expr_recurse(cpstate, indices->lidx); + } + + args = lappend(args, node); + + if (!indices->uidx) + { + A_Const *n = makeNode(A_Const); + n->isnull = true; + n->location = -1; + node = transform_cypher_expr_recurse(cpstate, (Node *)n); + } + else + { + node = transform_cypher_expr_recurse(cpstate, indices->uidx); + } + args = lappend(args, node); + + /* wrap and close it */ + func_expr = makeFuncExpr(func_slice_oid, AGTYPEOID, args, + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + func_expr->location = location; + + /* + * The result of this function is the input to the next access + * or slice operator. So we need to start out with a new arg + * list with this function expression. + */ + args = lappend(NIL, func_expr); + } + /* is this a string or index?*/ + else if (IsA(node, String) || IsA(node, A_Indices)) + { + /* we are working on an access */ + is_access = true; + + /* is this an index? */ + if (IsA(node, A_Indices)) + { + A_Indices *indices = (A_Indices *)node; + + node = transform_cypher_expr_recurse(cpstate, indices->uidx); + args = lappend(args, node); + } + /* it must be a string */ + else + { + Const *const_str = makeConst(AGTYPEOID, -1, InvalidOid, -1, + string_to_agtype(strVal(node)), + false, false); + args = lappend(args, const_str); + } + } + /* not an indirection we understand */ + else + { + ereport(ERROR, + (errmsg("invalid indirection node %d", nodeTag(node)))); + } + } + + /* if we were doing an access, we need wrap the args with access func. */ + if (is_access) + { + ArrayExpr *newa = make_agtype_array_expr(args); + + func_expr = makeFuncExpr(func_access_oid, AGTYPEOID, list_make1(newa), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + func_expr->funcvariadic = true; + } + + Assert(func_expr != NULL); + func_expr->location = location; + + return (Node *)func_expr; +} + +static Node *transform_cypher_string_match(cypher_parsestate *cpstate, + cypher_string_match *csm_node) +{ + Node *expr; + FuncExpr *func_expr; + Oid func_access_oid; + List *args = NIL; + + switch (csm_node->operation) + { + case CSMO_STARTS_WITH: + func_access_oid = get_agtype_string_match_starts_with_oid(); + break; + case CSMO_ENDS_WITH: + func_access_oid = get_agtype_string_match_ends_with_oid(); + break; + case CSMO_CONTAINS: + func_access_oid = get_agtype_string_match_contains_oid(); + break; + + default: + ereport(ERROR, + (errmsg_internal("unknown Cypher string match operation"))); + } + + expr = transform_cypher_expr_recurse(cpstate, csm_node->lhs); + args = lappend(args, expr); + expr = transform_cypher_expr_recurse(cpstate, csm_node->rhs); + args = lappend(args, expr); + + func_expr = makeFuncExpr(func_access_oid, AGTYPEOID, args, InvalidOid, + InvalidOid, COERCE_EXPLICIT_CALL); + func_expr->location = csm_node->location; + + return (Node *)func_expr; +} + +/* + * Function to create a typecasting node + */ +static Node *transform_cypher_typecast(cypher_parsestate *cpstate, + cypher_typecast *ctypecast) +{ + List *fname; + FuncCall *fnode; + ParseState *pstate; + TypeName *target_typ; + + /* verify input parameter */ + Assert (cpstate != NULL); + Assert (ctypecast != NULL); + + /* create the qualified function name, schema first */ + fname = list_make1(makeString("ag_catalog")); + pstate = &cpstate->pstate; + target_typ = ctypecast->typname; + + if (list_length(target_typ->names) == 1) + { + char *typecast = strVal(linitial(target_typ->names)); + int typecast_len = strlen(typecast); + + /* append the name of the requested typecast function */ + if (typecast_len == 4 && pg_strcasecmp(typecast, "edge") == 0) + { + fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_EDGE)); + } + else if (typecast_len == 4 && pg_strcasecmp(typecast, "path") == 0) + { + fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_PATH)); + } + else if (typecast_len == 6 && pg_strcasecmp(typecast, "vertex") == 0) + { + fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_VERTEX)); + } + else if (typecast_len == 7 && pg_strcasecmp(typecast, "numeric") == 0) + { + fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_NUMERIC)); + } + else if (typecast_len == 5 && pg_strcasecmp(typecast, "float") == 0) + { + fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_FLOAT)); + } + else if ((typecast_len == 3 && + pg_strcasecmp(typecast, "int") == 0) || + (typecast_len == 7 && + pg_strcasecmp(typecast, "integer") == 0)) + { + fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_INT)); + } + else if (typecast_len == 9 && + pg_strcasecmp(typecast, "pg_float8") == 0) + { + fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_PG_FLOAT8)); + } + else if (typecast_len == 9 && + pg_strcasecmp(typecast, "pg_bigint") == 0) + { + fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_PG_BIGINT)); + } + else if ((typecast_len == 4 && + pg_strcasecmp(typecast, "bool") == 0) || + (typecast_len == 7 && + pg_strcasecmp(typecast, "boolean") == 0)) + { + fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_BOOL)); + } + else if (typecast_len == 7 && + pg_strcasecmp(typecast, "pg_text") == 0) + { + fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_PG_TEXT)); + } + else + { + goto fallback_coercion; + } + + /* make a function call node */ + fnode = makeFuncCall(fname, list_make1(ctypecast->expr), COERCE_SQL_SYNTAX, + ctypecast->location); + + /* return the transformed function */ + return transform_FuncCall(cpstate, fnode); + } + +fallback_coercion: + { + Oid source_oid; + Oid target_oid; + int32 t_typmod = -1; + Node *expr; + + /* transform the expr before casting */ + expr = transform_cypher_expr_recurse(cpstate, + ctypecast->expr); + + typenameTypeIdAndMod(pstate, target_typ, &target_oid, &t_typmod); + source_oid = exprType(expr); + + /* errors out if cast not possible */ + expr = coerce_expr_flexible(pstate, expr, source_oid, target_oid, + t_typmod, true); + + return expr; + } +} + +/* + * Helper function to coerce an expression to the target type. If + * no direct cast exists, it attempts to cast through text if the + * source or target type is agtype. This improves interoperability + * with types from other extensions. + */ +static Node *coerce_expr_flexible(ParseState *pstate, Node *expr, + Oid source_oid, Oid target_oid, + int32 t_typmod, bool error_out) +{ + const Oid text_oid = TEXTOID; + Node *result; + + if (expr == NULL) + return NULL; + + /* Try a direct cast */ + result = coerce_to_target_type(pstate, expr, source_oid, target_oid, + t_typmod, COERCION_EXPLICIT, + COERCE_EXPLICIT_CAST, -1); + if (result != NULL) + return result; + + /* Try cast via TEXT if either side is AGTYPE */ + if (source_oid == AGTYPEOID || target_oid == AGTYPEOID) + { + Node *to_text = coerce_to_target_type(pstate, expr, source_oid, text_oid, + -1, COERCION_EXPLICIT, + COERCE_EXPLICIT_CAST, -1); + if (to_text != NULL) + { + result = coerce_to_target_type(pstate, to_text, text_oid, target_oid, + t_typmod, COERCION_EXPLICIT, + COERCE_EXPLICIT_CAST, -1); + if (result != NULL) + return result; + } + } + + if (error_out) + { + ereport(ERROR, + (errmsg_internal("typecast \'%s\' not supported", + format_type_be(target_oid)))); + } + + return NULL; +} + +static Node *transform_external_ext_FuncCall(cypher_parsestate *cpstate, + FuncCall *fn, List *targs, + Form_pg_proc procform, + const char *extension) +{ + ParseState *pstate = &cpstate->pstate; + FuncExpr *fexpr = NULL; + Node *retval = NULL; + Node *last_srf = pstate->p_last_srf; + Oid *proargtypes; + + /* make sure procform in not NULL */ + Assert(procform != NULL); + proargtypes = procform->proargtypes.values; + + /* cast the agtype arguments to the types accepted by function */ + targs = cast_agtype_args_to_target_type(cpstate, procform, targs, proargtypes); + + /* now get the function node for the external function */ + fexpr = (FuncExpr *)ParseFuncOrColumn(pstate, fn->funcname, targs, + last_srf, fn, false, + fn->location); + pfree(procform); + + /* + * This will cast TEXT output to AGTYPE. It will error out if this is + * not possible to do. For TEXT to AGTYPE we need to wrap the output + * due to issues with creating a cast from TEXT to AGTYPE. + */ + if (fexpr->funcresulttype == TEXTOID) + { + retval = wrap_text_output_to_agtype(cpstate, fexpr); + } + else + { + retval = (Node *)fexpr; + } + + /* additional casts or wraps can be done here for other types */ + + /* flag that an aggregate was found during a transform */ + if (retval != NULL && retval->type == T_Aggref) + { + cpstate->exprHasAgg = true; + } + + /* we can just return it here */ + return retval; +} + +/* + * Cast a function's input parameter list from agtype to that function's input + * type. This is used for functions that don't take agtype as input and where + * there isn't an implicit cast to do this for us. + */ +static List *cast_agtype_args_to_target_type(cypher_parsestate *cpstate, + Form_pg_proc procform, + List *fargs, + Oid *target_types) +{ + char *funcname = NameStr(procform->proname); + int nargs = procform->pronargs; + int given_nargs = list_length(fargs); + ListCell *lc = NULL; + + /* verify the length of args are same */ + if (given_nargs != nargs) + { + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("function %s requires %d arguments, %d given", + funcname, nargs, given_nargs))); + } + + /* iterate through the function's args */ + foreach (lc, fargs) + { + Node *expr = lfirst(lc); + Oid source_oid = exprType(expr); + Oid target_oid = target_types[foreach_current_index(lc)]; + + if (source_oid == target_oid) + { + continue; + } + + /* errors out if cast not possible */ + expr = coerce_expr_flexible(&cpstate->pstate, expr, source_oid, + target_oid, -1, true); + + lfirst(lc) = expr; + } + + return fargs; +} + +/* + * Due to issues with creating a cast from text to agtype, we need to wrap a + * function that outputs text with text_to_agtype. + */ +static Node *wrap_text_output_to_agtype(cypher_parsestate *cpstate, + FuncExpr *fexpr) +{ + Oid func_oid; + FuncExpr *retval; + + if (fexpr->funcresulttype != TEXTOID) + { + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("can only wrap text to agtype"))); + } + + func_oid = get_text_to_agtype_oid(); + retval = makeFuncExpr(func_oid, AGTYPEOID, list_make1(fexpr), InvalidOid, + InvalidOid, COERCE_SQL_SYNTAX); + retval->location = fexpr->location; + + /* return the wrapped function */ + return (Node *)retval; +} + +/* + * Returns Form_pg_proc struct for given function, if the function + * is not in search path, it is not considered. + */ +static Form_pg_proc get_procform(FuncCall *fn, bool err_not_found) +{ + CatCList *catlist = NULL; + Form_pg_proc procform = NULL; + Form_pg_proc result = NULL; + int nargs; + int i = 0; + List *asp = NIL; + bool asp_fetched = false; + bool found = false; + char *funcname = (((String*)linitial(fn->funcname))->sval); + int funcname_len = strlen(funcname); + + /* get a list of matching functions */ + catlist = SearchSysCacheList1(PROCNAMEARGSNSP, CStringGetDatum(funcname)); + + if (catlist->n_members == 0) + { + ReleaseSysCacheList(catlist); + return NULL; + } + + nargs = list_length(fn->args); + + /* iterate through them and verify that they are in the search path */ + for (i = 0; i < catlist->n_members; i++) + { + HeapTuple proctup = &catlist->members[i]->tuple; + procform = (Form_pg_proc) GETSTRUCT(proctup); + + /* + * Check if the function name, number of arguments, and + * variadic match before checking if it is in the search + * path. + */ + if (nargs == procform->pronargs && + fn->func_variadic == procform->provariadic && + strnlen(NameStr(procform->proname), NAMEDATALEN) == funcname_len && + pg_strcasecmp(funcname, procform->proname.data) == 0) + { + if (!asp_fetched) + { + asp = fetch_search_path(false); + asp_fetched = true; + } + + if (list_member_oid(asp, procform->pronamespace) && + !isTempNamespace(procform->pronamespace)) + { + found = true; + } + } + + if (found) + { + result = palloc(sizeof(FormData_pg_proc)); + memcpy(result, procform, sizeof(FormData_pg_proc)); + break; + } + + /* reset procform */ + procform = NULL; + } + + /* Error out if function not found */ + if (err_not_found && (result == NULL)) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("function %s does not exist", funcname), + errhint("If the function is from an external extension, " + "make sure the extension is installed and the " + "function is in the search path."))); + } + + /* we need to release the cache list */ + ReleaseSysCacheList(catlist); + list_free(asp); + + return result; +} + +static const char *get_mapped_extension(Oid func_oid) +{ + Oid extension_oid; + char *extension = NULL; + function_extension_cache_entry *entry; + bool found; + + initialize_function_extension_cache(); + + entry = hash_search(function_extension_cache, &func_oid, HASH_FIND, NULL); + if (entry != NULL) + { + if (!entry->has_extension) + { + return NULL; + } + return NameStr(entry->extension); + } + + extension_oid = getExtensionOfObject(ProcedureRelationId, func_oid); + extension = get_extension_name(extension_oid); + + if (function_extension_cache == NULL) + { + initialize_function_extension_cache(); + } + + entry = hash_search(function_extension_cache, &func_oid, HASH_ENTER, + &found); + if (!found) + { + entry->has_extension = (extension != NULL); + if (entry->has_extension) + { + namestrcpy(&entry->extension, extension); + } + } + + if (extension != NULL) + { + pfree(extension); + } + + return entry->has_extension ? NameStr(entry->extension) : NULL; +} + +static bool function_belongs_to_extension(Oid func_oid, const char *extension) +{ + Oid extension_oid; + char *extension_name = NULL; + function_extension_cache_entry *entry; + bool belongs; + bool found; + + initialize_function_extension_cache(); + + entry = hash_search(function_extension_cache, &func_oid, HASH_FIND, NULL); + if (entry != NULL) + { + return entry->has_extension && + pg_strcasecmp(NameStr(entry->extension), extension) == 0; + } + + extension_oid = getExtensionOfObject(ProcedureRelationId, func_oid); + extension_name = get_extension_name(extension_oid); + + if (function_extension_cache == NULL) + { + initialize_function_extension_cache(); + } + + entry = hash_search(function_extension_cache, &func_oid, HASH_ENTER, + &found); + if (!found) + { + entry->has_extension = (extension_name != NULL); + if (entry->has_extension) + { + namestrcpy(&entry->extension, extension_name); + } + } + + belongs = (extension_name != NULL && + pg_strcasecmp(extension_name, extension) == 0); + + if (extension_name != NULL) + { + pfree(extension_name); + } + + return belongs; +} + +static bool is_extension_external(const char *extension) +{ + return ((extension != NULL) && + (pg_strcasecmp(extension, "age") != 0)); +} + +static bool function_needs_graph_name_argument(const char *name, int name_len) +{ + switch (name[0]) + { + case 'e': + return name_len == 7 && memcmp(name, "endNode", 7) == 0; + case 's': + return name_len == 9 && memcmp(name, "startNode", 9) == 0; + case 'v': + return (name_len == 3 && memcmp(name, "vle", 3) == 0) || + (name_len == 12 && + memcmp(name, "vertex_stats", 12) == 0); + default: + return false; + } +} + +/* Returns age_ prefiexed lower case function name */ +static char *construct_age_function_name(char *funcname, int funcname_len) +{ + char *ag_name = palloc(funcname_len + 5); + int i; + + /* copy in the prefix - all AGE functions are prefixed with age_ */ + memcpy(ag_name, "age_", 4); + + /* + * All AGE function names are in lower case. So, copy in the funcname + * in lower case. + */ + for (i = 0; i < funcname_len; i++) + { + ag_name[i + 4] = tolower(funcname[i]); + } + + /* terminate it with 0 */ + ag_name[i + 4] = 0; + + return ag_name; +} + + +/* + * Checks if a function exists. If the extension name is given, + * then it checks if the function exists in that extension. + */ +static bool function_exists(char *funcname, char *extension) +{ + CatCList *catlist = NULL; + bool found = false; + function_exists_cache_key key; + function_exists_cache_entry *entry; + bool cache_found; + int i = 0; + + initialize_function_exists_cache(); + MemSet(&key, 0, sizeof(key)); + namestrcpy(&key.funcname, funcname); + if (extension != NULL) + { + namestrcpy(&key.extension, extension); + } + + entry = hash_search(function_exists_cache, &key, HASH_FIND, NULL); + if (entry != NULL) + { + return entry->exists; + } + + /* get a list of matching functions */ + catlist = SearchSysCacheList1(PROCNAMEARGSNSP, CStringGetDatum(funcname)); + + if (catlist->n_members == 0) + { + ReleaseSysCacheList(catlist); + entry = hash_search(function_exists_cache, &key, HASH_ENTER, + &cache_found); + entry->exists = false; + return false; + } + else if (extension == NULL) + { + ReleaseSysCacheList(catlist); + entry = hash_search(function_exists_cache, &key, HASH_ENTER, + &cache_found); + entry->exists = true; + return true; + } + + for (i = 0; i < catlist->n_members; i++) + { + HeapTuple proctup = &catlist->members[i]->tuple; + Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup); + + if (function_belongs_to_extension(procform->oid, extension)) + { + found = true; + break; + } + } + + /* we need to release the cache list */ + ReleaseSysCacheList(catlist); + + entry = hash_search(function_exists_cache, &key, HASH_ENTER, + &cache_found); + entry->exists = found; + + return found; +} + +static void initialize_function_exists_cache(void) +{ + HASHCTL hash_ctl; + + if (function_exists_cache != NULL) + { + return; + } + + initialize_function_caches(); + + MemSet(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(function_exists_cache_key); + hash_ctl.entrysize = sizeof(function_exists_cache_entry); + hash_ctl.hcxt = CacheMemoryContext; + + function_exists_cache = hash_create("cypher function existence cache", 16, + &hash_ctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); +} + +static void initialize_function_extension_cache(void) +{ + HASHCTL hash_ctl; + + if (function_extension_cache != NULL) + { + return; + } + + initialize_function_caches(); + + MemSet(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(Oid); + hash_ctl.entrysize = sizeof(function_extension_cache_entry); + hash_ctl.hcxt = CacheMemoryContext; + + function_extension_cache = + hash_create("cypher function extension cache", 16, &hash_ctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); +} + +static void initialize_function_caches(void) +{ + if (!CacheMemoryContext) + { + CreateCacheMemoryContext(); + } + + if (!function_cache_callback_registered) + { + CacheRegisterSyscacheCallback(PROCOID, invalidate_function_caches, + (Datum)0); + CacheRegisterSyscacheCallback(PROCNAMEARGSNSP, + invalidate_function_caches, + (Datum)0); + function_cache_callback_registered = true; + } +} + +static void invalidate_function_caches(Datum arg, int cache_id, + uint32 hash_value) +{ + if (function_exists_cache != NULL) + { + hash_destroy(function_exists_cache); + function_exists_cache = NULL; + } + + if (function_extension_cache != NULL) + { + hash_destroy(function_extension_cache); + function_extension_cache = NULL; + } + + agtype_access_operator_oid = InvalidOid; + agtype_access_slice_oid = InvalidOid; + agtype_in_operator_oid = InvalidOid; + agtype_add_oid = InvalidOid; + agtype_build_empty_list_oid = InvalidOid; + agtype_build_list_oid = InvalidOid; + agtype_build_empty_map_oid = InvalidOid; + agtype_build_map_oid = InvalidOid; + agtype_build_map_nonull_oid = InvalidOid; + age_properties_oid = InvalidOid; + agtype_string_match_starts_with_oid = InvalidOid; + agtype_string_match_ends_with_oid = InvalidOid; + agtype_string_match_contains_oid = InvalidOid; + text_to_agtype_oid = InvalidOid; + age_vle_path_length_oid = InvalidOid; + age_vle_path_node_count_oid = InvalidOid; + age_vle_edge_tail_count_oid = InvalidOid; + age_vle_list_is_empty_oid = InvalidOid; + age_vle_list_slice_count_oid = InvalidOid; + age_vle_list_slice_is_empty_oid = InvalidOid; + age_materialize_vle_list_slice_oid = InvalidOid; + age_materialize_vle_slice_boundary_oid = InvalidOid; + age_materialize_vle_edges_oid = InvalidOid; + age_materialize_vle_nodes_oid = InvalidOid; + age_materialize_vle_node_at_oid = InvalidOid; + age_materialize_vle_node_tail_last_oid = InvalidOid; + age_vle_node_tail_last_id_oid = InvalidOid; + age_vle_node_id_at_oid = InvalidOid; + age_vle_node_label_at_oid = InvalidOid; + age_vle_node_labels_at_oid = InvalidOid; + age_vle_node_properties_at_oid = InvalidOid; + age_materialize_vle_nodes_slice_oid = InvalidOid; + age_materialize_vle_nodes_tail_oid = InvalidOid; + age_materialize_vle_nodes_reversed_oid = InvalidOid; + age_materialize_vle_edge_at_oid = InvalidOid; + age_materialize_vle_edge_reversed_at_oid = InvalidOid; + age_materialize_vle_edge_tail_last_oid = InvalidOid; + age_vle_edge_tail_last_id_oid = InvalidOid; + age_vle_tail_last_field_oid = InvalidOid; + age_vle_tail_last_edge_endpoint_oid = InvalidOid; + age_vle_tail_last_endpoint_field_oid = InvalidOid; + age_vle_edge_id_at_oid = InvalidOid; + age_vle_edge_index_exists_oid = InvalidOid; + age_vle_edge_indices_equal_oid = InvalidOid; + age_vle_edge_reversed_index_equal_oid = InvalidOid; + age_vle_edge_label_at_oid = InvalidOid; + age_vle_edge_properties_at_oid = InvalidOid; + age_vle_edge_start_node_at_oid = InvalidOid; + age_vle_edge_end_node_at_oid = InvalidOid; + age_vle_edge_endpoint_field_at_oid = InvalidOid; + age_vle_edge_start_id_at_oid = InvalidOid; + age_vle_edge_end_id_at_oid = InvalidOid; + age_materialize_vle_edges_tail_oid = InvalidOid; + age_materialize_vle_edges_reversed_oid = InvalidOid; +} + +static Oid get_agtype_access_operator_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_access_operator_oid)) + { + agtype_access_operator_oid = + get_ag_func_oid("agtype_access_operator", 1, AGTYPEARRAYOID); + } + + return agtype_access_operator_oid; +} + +static Oid get_agtype_access_slice_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_access_slice_oid)) + { + agtype_access_slice_oid = + get_ag_func_oid("agtype_access_slice", 3, AGTYPEOID, AGTYPEOID, + AGTYPEOID); + } + + return agtype_access_slice_oid; +} + +static Oid get_agtype_in_operator_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_in_operator_oid)) + { + agtype_in_operator_oid = + get_ag_func_oid("agtype_in_operator", 2, AGTYPEOID, AGTYPEOID); + } + + return agtype_in_operator_oid; +} + +static Oid get_agtype_add_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_add_oid)) + { + agtype_add_oid = + get_ag_func_oid("agtype_add", 2, AGTYPEOID, AGTYPEOID); + } + + return agtype_add_oid; +} + +static Oid get_agtype_build_empty_list_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_build_empty_list_oid)) + { + agtype_build_empty_list_oid = + get_ag_func_oid("agtype_build_list", 0); + } + + return agtype_build_empty_list_oid; +} + +static Oid get_agtype_build_list_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_build_list_oid)) + { + agtype_build_list_oid = + get_ag_func_oid("agtype_build_list", 1, ANYOID); + } + + return agtype_build_list_oid; +} + +static Oid get_agtype_build_empty_map_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_build_empty_map_oid)) + { + agtype_build_empty_map_oid = get_ag_func_oid("agtype_build_map", 0); + } + + return agtype_build_empty_map_oid; +} + +static Oid get_agtype_build_map_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_build_map_oid)) + { + agtype_build_map_oid = + get_ag_func_oid("agtype_build_map", 1, ANYOID); + } + + return agtype_build_map_oid; +} + +static Oid get_agtype_build_map_nonull_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_build_map_nonull_oid)) + { + agtype_build_map_nonull_oid = + get_ag_func_oid("agtype_build_map_nonull", 1, ANYOID); + } + + return agtype_build_map_nonull_oid; +} + +static Oid get_age_properties_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_properties_oid)) + { + age_properties_oid = get_ag_func_oid("age_properties", 1, AGTYPEOID); + } + + return age_properties_oid; +} + +static Oid get_agtype_string_match_starts_with_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_string_match_starts_with_oid)) + { + agtype_string_match_starts_with_oid = + get_ag_func_oid("agtype_string_match_starts_with", 2, AGTYPEOID, + AGTYPEOID); + } + + return agtype_string_match_starts_with_oid; +} + +static Oid get_agtype_string_match_ends_with_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_string_match_ends_with_oid)) + { + agtype_string_match_ends_with_oid = + get_ag_func_oid("agtype_string_match_ends_with", 2, AGTYPEOID, + AGTYPEOID); + } + + return agtype_string_match_ends_with_oid; +} + +static Oid get_agtype_string_match_contains_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_string_match_contains_oid)) + { + agtype_string_match_contains_oid = + get_ag_func_oid("agtype_string_match_contains", 2, AGTYPEOID, + AGTYPEOID); + } + + return agtype_string_match_contains_oid; +} + +static Oid get_text_to_agtype_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(text_to_agtype_oid)) + { + text_to_agtype_oid = get_ag_func_oid("text_to_agtype", 1, TEXTOID); + } + + return text_to_agtype_oid; +} + +static Oid get_age_vle_path_length_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_path_length_oid)) + { + age_vle_path_length_oid = + get_ag_func_oid("age_vle_path_length", 1, AGTYPEOID); + } + + return age_vle_path_length_oid; +} + +static Oid get_age_vle_path_node_count_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_path_node_count_oid)) + { + age_vle_path_node_count_oid = + get_ag_func_oid("age_vle_path_node_count", 1, AGTYPEOID); + } + + return age_vle_path_node_count_oid; +} + +static Oid get_age_vle_edge_tail_count_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_edge_tail_count_oid)) + { + age_vle_edge_tail_count_oid = + get_ag_func_oid("age_vle_edge_tail_count", 1, AGTYPEOID); + } + + return age_vle_edge_tail_count_oid; +} + +static Oid get_age_vle_list_is_empty_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_list_is_empty_oid)) + { + age_vle_list_is_empty_oid = + get_ag_func_oid("age_vle_list_is_empty", 2, AGTYPEOID, + AGTYPEOID); + } + + return age_vle_list_is_empty_oid; +} + +static Oid get_age_vle_list_slice_count_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_list_slice_count_oid)) + { + age_vle_list_slice_count_oid = + get_ag_func_oid("age_vle_list_slice_count", 4, AGTYPEOID, + AGTYPEOID, AGTYPEOID, AGTYPEOID); + } + + return age_vle_list_slice_count_oid; +} + +static Oid get_age_vle_list_slice_is_empty_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_list_slice_is_empty_oid)) + { + age_vle_list_slice_is_empty_oid = + get_ag_func_oid("age_vle_list_slice_is_empty", 4, AGTYPEOID, + AGTYPEOID, AGTYPEOID, AGTYPEOID); + } + + return age_vle_list_slice_is_empty_oid; +} + +static Oid get_age_materialize_vle_list_slice_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_materialize_vle_list_slice_oid)) + { + age_materialize_vle_list_slice_oid = + get_ag_func_oid("age_materialize_vle_list_slice", 4, AGTYPEOID, + AGTYPEOID, AGTYPEOID, AGTYPEOID); + } + + return age_materialize_vle_list_slice_oid; +} + +static Oid get_age_materialize_vle_slice_boundary_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_materialize_vle_slice_boundary_oid)) + { + age_materialize_vle_slice_boundary_oid = + get_ag_func_oid("age_materialize_vle_slice_boundary", 4, + AGTYPEOID, AGTYPEOID, AGTYPEOID, AGTYPEOID); + } + + return age_materialize_vle_slice_boundary_oid; +} + +static Oid get_age_materialize_vle_edges_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_materialize_vle_edges_oid)) + { + age_materialize_vle_edges_oid = + get_ag_func_oid("age_materialize_vle_edges", 1, AGTYPEOID); + } + + return age_materialize_vle_edges_oid; +} + +static Oid get_age_materialize_vle_nodes_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_materialize_vle_nodes_oid)) + { + age_materialize_vle_nodes_oid = + get_ag_func_oid("age_materialize_vle_nodes", 1, AGTYPEOID); + } + + return age_materialize_vle_nodes_oid; +} + +static Oid get_age_materialize_vle_node_at_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_materialize_vle_node_at_oid)) + { + age_materialize_vle_node_at_oid = + get_ag_func_oid("age_materialize_vle_node_at", 2, AGTYPEOID, + AGTYPEOID); + } + + return age_materialize_vle_node_at_oid; +} + +static Oid get_age_materialize_vle_node_tail_last_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_materialize_vle_node_tail_last_oid)) + { + age_materialize_vle_node_tail_last_oid = + get_ag_func_oid("age_materialize_vle_node_tail_last", 1, + AGTYPEOID); + } + + return age_materialize_vle_node_tail_last_oid; +} + +static Oid get_age_vle_node_tail_last_id_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_node_tail_last_id_oid)) + { + age_vle_node_tail_last_id_oid = + get_ag_func_oid("age_vle_node_tail_last_id", 1, AGTYPEOID); + } + + return age_vle_node_tail_last_id_oid; +} + +static Oid get_age_vle_node_id_at_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_node_id_at_oid)) + { + age_vle_node_id_at_oid = + get_ag_func_oid("age_vle_node_id_at", 2, AGTYPEOID, AGTYPEOID); + } + + return age_vle_node_id_at_oid; +} + +static Oid get_age_vle_node_label_at_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_node_label_at_oid)) + { + age_vle_node_label_at_oid = + get_ag_func_oid("age_vle_node_label_at", 2, AGTYPEOID, + AGTYPEOID); + } + + return age_vle_node_label_at_oid; +} + +static Oid get_age_vle_node_labels_at_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_node_labels_at_oid)) + { + age_vle_node_labels_at_oid = + get_ag_func_oid("age_vle_node_labels_at", 2, AGTYPEOID, + AGTYPEOID); + } + + return age_vle_node_labels_at_oid; +} + +static Oid get_age_vle_node_properties_at_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_node_properties_at_oid)) + { + age_vle_node_properties_at_oid = + get_ag_func_oid("age_vle_node_properties_at", 2, AGTYPEOID, + AGTYPEOID); + } + + return age_vle_node_properties_at_oid; +} + +static Oid get_age_materialize_vle_nodes_slice_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_materialize_vle_nodes_slice_oid)) + { + age_materialize_vle_nodes_slice_oid = + get_ag_func_oid("age_materialize_vle_nodes_slice", 3, AGTYPEOID, + AGTYPEOID, AGTYPEOID); + } + + return age_materialize_vle_nodes_slice_oid; +} + +static Oid get_age_materialize_vle_nodes_tail_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_materialize_vle_nodes_tail_oid)) + { + age_materialize_vle_nodes_tail_oid = + get_ag_func_oid("age_materialize_vle_nodes_tail", 1, AGTYPEOID); + } + + return age_materialize_vle_nodes_tail_oid; +} + +static Oid get_age_materialize_vle_nodes_reversed_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_materialize_vle_nodes_reversed_oid)) + { + age_materialize_vle_nodes_reversed_oid = get_ag_func_oid( + "age_materialize_vle_nodes_reversed", 1, AGTYPEOID); + } + + return age_materialize_vle_nodes_reversed_oid; +} + +static Oid get_age_materialize_vle_edge_at_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_materialize_vle_edge_at_oid)) + { + age_materialize_vle_edge_at_oid = + get_ag_func_oid("age_materialize_vle_edge_at", 2, AGTYPEOID, + AGTYPEOID); + } + + return age_materialize_vle_edge_at_oid; +} + +static Oid get_age_materialize_vle_edge_reversed_at_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_materialize_vle_edge_reversed_at_oid)) + { + age_materialize_vle_edge_reversed_at_oid = + get_ag_func_oid("age_materialize_vle_edge_reversed_at", 2, + AGTYPEOID, AGTYPEOID); + } + + return age_materialize_vle_edge_reversed_at_oid; +} + +static Oid get_age_materialize_vle_edge_tail_last_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_materialize_vle_edge_tail_last_oid)) + { + age_materialize_vle_edge_tail_last_oid = + get_ag_func_oid("age_materialize_vle_edge_tail_last", 1, + AGTYPEOID); + } + + return age_materialize_vle_edge_tail_last_oid; +} + +static Oid get_age_vle_edge_tail_last_id_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_edge_tail_last_id_oid)) + { + age_vle_edge_tail_last_id_oid = + get_ag_func_oid("age_vle_edge_tail_last_id", 1, AGTYPEOID); + } + + return age_vle_edge_tail_last_id_oid; +} + +static Oid get_age_vle_tail_last_field_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_tail_last_field_oid)) + { + age_vle_tail_last_field_oid = + get_ag_func_oid("age_vle_tail_last_field", 2, AGTYPEOID, + AGTYPEOID); + } + + return age_vle_tail_last_field_oid; +} + +static Oid get_age_vle_tail_last_edge_endpoint_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_tail_last_edge_endpoint_oid)) + { + age_vle_tail_last_edge_endpoint_oid = + get_ag_func_oid("age_vle_tail_last_edge_endpoint", 2, + AGTYPEOID, AGTYPEOID); + } + + return age_vle_tail_last_edge_endpoint_oid; +} + +static Oid get_age_vle_tail_last_endpoint_field_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_tail_last_endpoint_field_oid)) + { + age_vle_tail_last_endpoint_field_oid = + get_ag_func_oid("age_vle_tail_last_endpoint_field", 2, + AGTYPEOID, AGTYPEOID); + } + + return age_vle_tail_last_endpoint_field_oid; +} + +static Oid get_age_vle_edge_id_at_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_edge_id_at_oid)) + { + age_vle_edge_id_at_oid = + get_ag_func_oid("age_vle_edge_id_at", 2, AGTYPEOID, AGTYPEOID); + } + + return age_vle_edge_id_at_oid; +} + +static Oid get_age_vle_edge_index_exists_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_edge_index_exists_oid)) + { + age_vle_edge_index_exists_oid = + get_ag_func_oid("age_vle_edge_index_exists", 2, AGTYPEOID, + AGTYPEOID); + } + + return age_vle_edge_index_exists_oid; +} + +static Oid get_age_vle_edge_indices_equal_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_edge_indices_equal_oid)) + { + age_vle_edge_indices_equal_oid = + get_ag_func_oid("age_vle_edge_indices_equal", 3, AGTYPEOID, + AGTYPEOID, AGTYPEOID); + } + + return age_vle_edge_indices_equal_oid; +} + +static Oid get_age_vle_edge_reversed_index_equal_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_edge_reversed_index_equal_oid)) + { + age_vle_edge_reversed_index_equal_oid = + get_ag_func_oid("age_vle_edge_reversed_index_equal", 3, + AGTYPEOID, AGTYPEOID, AGTYPEOID); + } + + return age_vle_edge_reversed_index_equal_oid; +} + +static Oid get_age_vle_edge_label_at_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_edge_label_at_oid)) + { + age_vle_edge_label_at_oid = + get_ag_func_oid("age_vle_edge_label_at", 2, AGTYPEOID, + AGTYPEOID); + } + + return age_vle_edge_label_at_oid; +} + +static Oid get_age_vle_edge_properties_at_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_edge_properties_at_oid)) + { + age_vle_edge_properties_at_oid = + get_ag_func_oid("age_vle_edge_properties_at", 2, AGTYPEOID, + AGTYPEOID); + } + + return age_vle_edge_properties_at_oid; +} + +static Oid get_age_vle_edge_start_node_at_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_edge_start_node_at_oid)) + { + age_vle_edge_start_node_at_oid = + get_ag_func_oid("age_vle_edge_start_node_at", 2, AGTYPEOID, + AGTYPEOID); + } + + return age_vle_edge_start_node_at_oid; +} + +static Oid get_age_vle_edge_end_node_at_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_edge_end_node_at_oid)) + { + age_vle_edge_end_node_at_oid = + get_ag_func_oid("age_vle_edge_end_node_at", 2, AGTYPEOID, + AGTYPEOID); + } + + return age_vle_edge_end_node_at_oid; +} + +static Oid get_age_vle_edge_endpoint_field_at_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_edge_endpoint_field_at_oid)) + { + age_vle_edge_endpoint_field_at_oid = + get_ag_func_oid("age_vle_edge_endpoint_field_at", 3, + AGTYPEOID, AGTYPEOID, AGTYPEOID); + } + + return age_vle_edge_endpoint_field_at_oid; +} + +static Oid get_age_vle_edge_start_id_at_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_edge_start_id_at_oid)) + { + age_vle_edge_start_id_at_oid = + get_ag_func_oid("age_vle_edge_start_id_at", 2, AGTYPEOID, + AGTYPEOID); + } + + return age_vle_edge_start_id_at_oid; +} + +static Oid get_age_vle_edge_end_id_at_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_edge_end_id_at_oid)) + { + age_vle_edge_end_id_at_oid = + get_ag_func_oid("age_vle_edge_end_id_at", 2, AGTYPEOID, + AGTYPEOID); + } + + return age_vle_edge_end_id_at_oid; +} + +static Oid get_age_materialize_vle_edges_tail_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_materialize_vle_edges_tail_oid)) + { + age_materialize_vle_edges_tail_oid = + get_ag_func_oid("age_materialize_vle_edges_tail", 1, AGTYPEOID); + } + + return age_materialize_vle_edges_tail_oid; +} + +static Oid get_age_materialize_vle_edges_reversed_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_materialize_vle_edges_reversed_oid)) + { + age_materialize_vle_edges_reversed_oid = get_ag_func_oid( + "age_materialize_vle_edges_reversed", 1, AGTYPEOID); + } + + return age_materialize_vle_edges_reversed_oid; +} + +static Expr *get_current_single_vle_path_expr(cypher_parsestate *cpstate, + Node *arg) +{ + ColumnRef *cr = NULL; + String *field = NULL; + transform_entity *entity = NULL; + cypher_path *path = NULL; + cypher_relationship *rel = NULL; + FuncExpr *path_expr = NULL; + + if (!IsA(arg, ColumnRef)) + { + return NULL; + } + + cr = (ColumnRef *)arg; + if (list_length(cr->fields) != 1 || !IsA(linitial(cr->fields), String)) + { + return NULL; + } + + field = linitial(cr->fields); + entity = find_variable(cpstate, strVal(field)); + if (entity == NULL || entity->type != ENT_PATH || + !entity->declared_in_current_clause || + entity->expr == NULL || !IsA(entity->expr, FuncExpr)) + { + return NULL; + } + + path = entity->entity.path; + if (path == NULL || list_length(path->path) != 3) + { + return NULL; + } + + rel = (cypher_relationship *)lfirst(lnext(path->path, + list_head(path->path))); + if (rel == NULL || rel->varlen == NULL) + { + return NULL; + } + + path_expr = (FuncExpr *)entity->expr; + if (list_length(path_expr->args) == 1) + { + return linitial(path_expr->args); + } + else if (list_length(path_expr->args) == 3) + { + return lsecond(path_expr->args); + } + + return NULL; +} + +static Expr *get_current_vle_edge_expr(cypher_parsestate *cpstate, Node *arg) +{ + ColumnRef *cr = NULL; + String *field = NULL; + transform_entity *entity = NULL; + + if (!IsA(arg, ColumnRef)) + { + return NULL; + } + + cr = (ColumnRef *)arg; + if (list_length(cr->fields) != 1 || !IsA(linitial(cr->fields), String)) + { + return NULL; + } + + field = linitial(cr->fields); + entity = find_variable(cpstate, strVal(field)); + if (entity == NULL || entity->type != ENT_VLE_EDGE || + !entity->declared_in_current_clause || entity->expr == NULL) + { + return NULL; + } + + return entity->expr; +} + +static Expr *get_current_vle_relationship_list_expr(cypher_parsestate *cpstate, + Node *arg) +{ + FuncCall *fn = NULL; + + if (!IsA(arg, FuncCall)) + { + return get_current_vle_edge_expr(cpstate, arg); + } + + fn = (FuncCall *)arg; + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "relationships") != 0 || + list_length(fn->args) != 1) + { + return NULL; + } + + return get_current_single_vle_path_expr(cpstate, linitial(fn->args)); +} + +static Node *try_transform_vle_path_length(cypher_parsestate *cpstate, + FuncCall *fn) +{ + Expr *vle_expr = NULL; + Oid func_oid; + + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "length") != 0 || + list_length(fn->args) != 1) + { + return NULL; + } + + vle_expr = get_current_single_vle_path_expr(cpstate, linitial(fn->args)); + if (vle_expr == NULL) + { + return NULL; + } + + func_oid = get_age_vle_path_length_oid(); + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, list_make1(vle_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_id_access(cypher_parsestate *cpstate, + FuncCall *fn) +{ + A_Indirection *a_ind = NULL; + char *list_name = NULL; + Expr *vle_expr = NULL; + Node *index_expr = NULL; + Oid func_oid; + + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "id") != 0 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), A_Indirection)) + { + return NULL; + } + + a_ind = (A_Indirection *)linitial(fn->args); + if (!parse_vle_path_indexed_list_index(cpstate, a_ind, &vle_expr, + &list_name, &index_expr)) + { + return NULL; + } + + if (pg_strcasecmp(list_name, "nodes") == 0) + { + func_oid = get_age_vle_node_id_at_oid(); + } + else + { + func_oid = get_age_vle_edge_id_at_oid(); + } + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static bool parse_vle_path_tail_last_list(cypher_parsestate *cpstate, + Node *node, Expr **vle_expr, + char **list_name) +{ + FuncCall *last_fn = NULL; + FuncCall *tail_fn = NULL; + FuncCall *list_fn = NULL; + + if (!IsA(node, FuncCall)) + { + return false; + } + + last_fn = (FuncCall *)node; + if (list_length(last_fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(last_fn->funcname)), "last") != 0 || + list_length(last_fn->args) != 1 || + !IsA(linitial(last_fn->args), FuncCall)) + { + return false; + } + + tail_fn = (FuncCall *)linitial(last_fn->args); + if (list_length(tail_fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(tail_fn->funcname)), "tail") != 0 || + list_length(tail_fn->args) != 1) + { + return false; + } + + if (IsA(linitial(tail_fn->args), FuncCall)) + { + list_fn = (FuncCall *)linitial(tail_fn->args); + if (list_length(list_fn->funcname) != 1 || + list_length(list_fn->args) != 1) + { + return false; + } + + *list_name = strVal(linitial(list_fn->funcname)); + if (pg_strcasecmp(*list_name, "nodes") != 0 && + pg_strcasecmp(*list_name, "relationships") != 0) + { + return false; + } + + *vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(list_fn->args)); + } + else + { + *vle_expr = get_current_vle_edge_expr(cpstate, + linitial(tail_fn->args)); + if (*vle_expr != NULL) + { + *list_name = "relationships"; + } + } + + return *vle_expr != NULL; +} + +static bool parse_vle_tail_last_endpoint(cypher_parsestate *cpstate, + Node *node, Expr **vle_expr, + bool *start_endpoint) +{ + FuncCall *endpoint_fn = NULL; + char *endpoint_name = NULL; + char *list_name = NULL; + + if (!IsA(node, FuncCall)) + { + return false; + } + + endpoint_fn = (FuncCall *)node; + if (list_length(endpoint_fn->funcname) != 1 || + list_length(endpoint_fn->args) != 1) + { + return false; + } + + endpoint_name = strVal(linitial(endpoint_fn->funcname)); + if (pg_strcasecmp(endpoint_name, "startNode") == 0) + { + *start_endpoint = true; + } + else if (pg_strcasecmp(endpoint_name, "endNode") == 0) + { + *start_endpoint = false; + } + else + { + return false; + } + + return parse_vle_path_tail_last_list(cpstate, + linitial(endpoint_fn->args), + vle_expr, &list_name) && + pg_strcasecmp(list_name, "relationships") == 0; +} + +static Node *try_transform_vle_path_boundary_id_function( + cypher_parsestate *cpstate, FuncCall *fn) +{ + char *list_name = NULL; + Expr *vle_expr = NULL; + Const *index_expr = NULL; + Node *nested_expr = NULL; + Oid func_oid; + int64 index; + + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "id") != 0 || + list_length(fn->args) != 1) + { + return NULL; + } + + if (parse_vle_path_tail_last_list(cpstate, linitial(fn->args), &vle_expr, + &list_name)) + { + if (pg_strcasecmp(list_name, "nodes") == 0) + { + func_oid = get_age_vle_node_tail_last_id_oid(); + } + else + { + func_oid = get_age_vle_edge_tail_last_id_oid(); + } + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, list_make1(vle_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + } + + if (IsA(linitial(fn->args), FuncCall)) + { + nested_expr = transform_vle_path_nested_transform_head_last( + cpstate, (FuncCall *)linitial(fn->args), 8); + if (nested_expr != NULL) + { + return nested_expr; + } + } + + if (!parse_vle_path_boundary_list_index(cpstate, linitial(fn->args), + &vle_expr, &list_name, &index)) + { + return NULL; + } + + index_expr = make_agtype_integer_const(index, fn->location); + if (pg_strcasecmp(list_name, "nodes") == 0) + { + func_oid = get_age_vle_node_id_at_oid(); + } + else + { + func_oid = get_age_vle_edge_id_at_oid(); + } + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_endpoint_id_access( + cypher_parsestate *cpstate, FuncCall *fn) +{ + Expr *vle_expr = NULL; + Node *index_expr = NULL; + Oid func_oid; + bool start_endpoint; + + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "id") != 0 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), FuncCall)) + { + return NULL; + } + + if (!parse_vle_edge_endpoint_index(cpstate, linitial(fn->args), &vle_expr, + &index_expr, &start_endpoint)) + { + return NULL; + } + + if (start_endpoint) + { + func_oid = get_age_vle_edge_start_id_at_oid(); + } + else + { + func_oid = get_age_vle_edge_end_id_at_oid(); + } + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_nested_transform_index_endpoint_id_access( + cypher_parsestate *cpstate, FuncCall *fn) +{ + FuncCall *endpoint_fn = NULL; + A_Indirection *a_ind = NULL; + Expr *vle_expr = NULL; + char *list_name = NULL; + Const *lower_expr = NULL; + Const *upper_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + char *endpoint_name = NULL; + int64 mode; + int64 mode_offset; + + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "id") != 0 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), FuncCall)) + { + return NULL; + } + + endpoint_fn = (FuncCall *)linitial(fn->args); + if (list_length(endpoint_fn->funcname) != 1 || + list_length(endpoint_fn->args) != 1 || + !IsA(linitial(endpoint_fn->args), A_Indirection)) + { + return NULL; + } + + endpoint_name = strVal(linitial(endpoint_fn->funcname)); + if (pg_strcasecmp(endpoint_name, "startNode") == 0) + { + mode_offset = 56; + } + else if (pg_strcasecmp(endpoint_name, "endNode") == 0) + { + mode_offset = 64; + } + else + { + return NULL; + } + + a_ind = (A_Indirection *)linitial(endpoint_fn->args); + if (!parse_vle_path_nested_transform_index(cpstate, a_ind, &vle_expr, + &list_name, &lower_expr, + &upper_expr, &mode) || + pg_strcasecmp(list_name, "relationships") != 0) + { + return NULL; + } + + mode_expr = make_agtype_integer_const(mode + mode_offset, fn->location); + func_oid = get_age_materialize_vle_slice_boundary_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make4(vle_expr, lower_expr, upper_expr, + mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_boundary_endpoint_id_access( + cypher_parsestate *cpstate, FuncCall *fn) +{ + FuncCall *endpoint_fn = NULL; + char *endpoint_name = NULL; + char *list_name = NULL; + Expr *vle_expr = NULL; + Const *index_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + int64 index; + int64 mode; + + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "id") != 0 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), FuncCall)) + { + return NULL; + } + + endpoint_fn = (FuncCall *)linitial(fn->args); + if (list_length(endpoint_fn->funcname) != 1 || + list_length(endpoint_fn->args) != 1) + { + return NULL; + } + + endpoint_name = strVal(linitial(endpoint_fn->funcname)); + if (pg_strcasecmp(endpoint_name, "startNode") != 0 && + pg_strcasecmp(endpoint_name, "endNode") != 0) + { + return NULL; + } + + if (parse_vle_path_tail_last_list(cpstate, linitial(endpoint_fn->args), + &vle_expr, &list_name) && + pg_strcasecmp(list_name, "relationships") == 0) + { + mode = pg_strcasecmp(endpoint_name, "startNode") == 0 ? 2 : 3; + mode_expr = make_agtype_integer_const(mode, fn->location); + func_oid = get_age_vle_tail_last_edge_endpoint_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + } + + if (!parse_vle_path_boundary_list_index(cpstate, + linitial(endpoint_fn->args), + &vle_expr, &list_name, &index) || + pg_strcasecmp(list_name, "relationships") != 0) + { + return NULL; + } + + index_expr = make_agtype_integer_const(index, fn->location); + if (pg_strcasecmp(endpoint_name, "startNode") == 0) + { + func_oid = get_age_vle_edge_start_id_at_oid(); + } + else + { + func_oid = get_age_vle_edge_end_id_at_oid(); + } + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_slice_boundary_endpoint_id_access( + cypher_parsestate *cpstate, FuncCall *fn) +{ + FuncCall *endpoint_fn = NULL; + FuncCall *boundary_fn = NULL; + char *endpoint_name = NULL; + Node *retval = NULL; + int64 mode_offset; + + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "id") != 0 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), FuncCall)) + { + return NULL; + } + + endpoint_fn = (FuncCall *)linitial(fn->args); + if (list_length(endpoint_fn->funcname) != 1 || + list_length(endpoint_fn->args) != 1 || + !IsA(linitial(endpoint_fn->args), FuncCall)) + { + return NULL; + } + + endpoint_name = strVal(linitial(endpoint_fn->funcname)); + if (pg_strcasecmp(endpoint_name, "startNode") == 0) + { + mode_offset = 56; + } + else if (pg_strcasecmp(endpoint_name, "endNode") == 0) + { + mode_offset = 64; + } + else + { + return NULL; + } + + boundary_fn = (FuncCall *)linitial(endpoint_fn->args); + + retval = transform_vle_path_nested_transform_head_last( + cpstate, boundary_fn, mode_offset); + if (retval != NULL) + { + return retval; + } + + return transform_vle_path_slice_head_last(cpstate, boundary_fn, + mode_offset); +} + +static Node *try_transform_vle_path_endpoint_access(cypher_parsestate *cpstate, + FuncCall *fn) +{ + Expr *vle_expr = NULL; + Node *index_expr = NULL; + Oid func_oid; + bool start_endpoint; + + if (!parse_vle_edge_endpoint_index(cpstate, (Node *)fn, &vle_expr, + &index_expr, &start_endpoint)) + { + return NULL; + } + + if (start_endpoint) + { + func_oid = get_age_vle_edge_start_node_at_oid(); + } + else + { + func_oid = get_age_vle_edge_end_node_at_oid(); + } + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_nested_transform_index_endpoint_access( + cypher_parsestate *cpstate, FuncCall *fn) +{ + A_Indirection *a_ind = NULL; + Expr *vle_expr = NULL; + char *list_name = NULL; + Const *lower_expr = NULL; + Const *upper_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + char *endpoint_name = NULL; + int64 mode; + int64 mode_offset; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), A_Indirection)) + { + return NULL; + } + + endpoint_name = strVal(linitial(fn->funcname)); + if (pg_strcasecmp(endpoint_name, "startNode") == 0) + { + mode_offset = 40; + } + else if (pg_strcasecmp(endpoint_name, "endNode") == 0) + { + mode_offset = 48; + } + else + { + return NULL; + } + + a_ind = (A_Indirection *)linitial(fn->args); + if (!parse_vle_path_nested_transform_index(cpstate, a_ind, &vle_expr, + &list_name, &lower_expr, + &upper_expr, &mode) || + pg_strcasecmp(list_name, "relationships") != 0) + { + return NULL; + } + + mode_expr = make_agtype_integer_const(mode + mode_offset, fn->location); + func_oid = get_age_materialize_vle_slice_boundary_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make4(vle_expr, lower_expr, upper_expr, + mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_boundary_endpoint_access( + cypher_parsestate *cpstate, FuncCall *fn) +{ + char *endpoint_name = NULL; + char *list_name = NULL; + Expr *vle_expr = NULL; + Const *index_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + int64 index; + int64 mode; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1) + { + return NULL; + } + + endpoint_name = strVal(linitial(fn->funcname)); + if (pg_strcasecmp(endpoint_name, "startNode") != 0 && + pg_strcasecmp(endpoint_name, "endNode") != 0) + { + return NULL; + } + + if (parse_vle_path_tail_last_list(cpstate, linitial(fn->args), &vle_expr, + &list_name) && + pg_strcasecmp(list_name, "relationships") == 0) + { + mode = pg_strcasecmp(endpoint_name, "startNode") == 0 ? 0 : 1; + mode_expr = make_agtype_integer_const(mode, fn->location); + func_oid = get_age_vle_tail_last_edge_endpoint_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + } + + if (!parse_vle_path_boundary_list_index(cpstate, linitial(fn->args), + &vle_expr, &list_name, &index) || + pg_strcasecmp(list_name, "relationships") != 0) + { + return NULL; + } + + index_expr = make_agtype_integer_const(index, fn->location); + if (pg_strcasecmp(endpoint_name, "startNode") == 0) + { + func_oid = get_age_vle_edge_start_node_at_oid(); + } + else + { + func_oid = get_age_vle_edge_end_node_at_oid(); + } + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_slice_boundary_endpoint_access( + cypher_parsestate *cpstate, FuncCall *fn) +{ + char *endpoint_name = NULL; + Node *retval = NULL; + int64 mode_offset; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), FuncCall)) + { + return NULL; + } + + endpoint_name = strVal(linitial(fn->funcname)); + if (pg_strcasecmp(endpoint_name, "startNode") == 0) + { + mode_offset = 40; + } + else if (pg_strcasecmp(endpoint_name, "endNode") == 0) + { + mode_offset = 48; + } + else + { + return NULL; + } + + retval = transform_vle_path_nested_transform_head_last( + cpstate, (FuncCall *)linitial(fn->args), mode_offset); + if (retval != NULL) + { + return retval; + } + + return transform_vle_path_slice_head_last(cpstate, + (FuncCall *)linitial(fn->args), + mode_offset); +} + +static Node *try_transform_vle_path_endpoint_field(cypher_parsestate *cpstate, + FuncCall *fn) +{ + char *field_name = NULL; + Expr *vle_expr = NULL; + Node *index_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + bool start_endpoint; + int64 mode; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1) + { + return NULL; + } + + field_name = strVal(linitial(fn->funcname)); + if (pg_strcasecmp(field_name, "label") != 0 && + pg_strcasecmp(field_name, "labels") != 0 && + pg_strcasecmp(field_name, "properties") != 0) + { + return NULL; + } + + if (!parse_vle_edge_endpoint_index(cpstate, linitial(fn->args), &vle_expr, + &index_expr, &start_endpoint)) + { + return NULL; + } + + if (pg_strcasecmp(field_name, "label") == 0) + { + mode = start_endpoint ? 0 : 1; + } + else if (pg_strcasecmp(field_name, "labels") == 0) + { + mode = start_endpoint ? 2 : 3; + } + else + { + mode = start_endpoint ? 4 : 5; + } + + mode_expr = make_agtype_integer_const(mode, fn->location); + func_oid = get_age_vle_edge_endpoint_field_at_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make3(vle_expr, index_expr, mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_nested_transform_index_endpoint_field( + cypher_parsestate *cpstate, FuncCall *fn) +{ + FuncCall *endpoint_fn = NULL; + A_Indirection *a_ind = NULL; + Expr *vle_expr = NULL; + char *list_name = NULL; + Const *lower_expr = NULL; + Const *upper_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + char *field_name = NULL; + char *endpoint_name = NULL; + int64 mode; + int64 mode_offset; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), FuncCall)) + { + return NULL; + } + + field_name = strVal(linitial(fn->funcname)); + endpoint_fn = (FuncCall *)linitial(fn->args); + if (list_length(endpoint_fn->funcname) != 1 || + list_length(endpoint_fn->args) != 1 || + !IsA(linitial(endpoint_fn->args), A_Indirection)) + { + return NULL; + } + + endpoint_name = strVal(linitial(endpoint_fn->funcname)); + if (pg_strcasecmp(field_name, "label") == 0) + { + mode_offset = pg_strcasecmp(endpoint_name, "startNode") == 0 ? 72 : 80; + } + else if (pg_strcasecmp(field_name, "labels") == 0) + { + mode_offset = pg_strcasecmp(endpoint_name, "startNode") == 0 ? 88 : 96; + } + else if (pg_strcasecmp(field_name, "properties") == 0) + { + mode_offset = pg_strcasecmp(endpoint_name, "startNode") == 0 ? + 104 : 112; + } + else + { + return NULL; + } + + if (pg_strcasecmp(endpoint_name, "startNode") != 0 && + pg_strcasecmp(endpoint_name, "endNode") != 0) + { + return NULL; + } + + a_ind = (A_Indirection *)linitial(endpoint_fn->args); + if (!parse_vle_path_nested_transform_index(cpstate, a_ind, &vle_expr, + &list_name, &lower_expr, + &upper_expr, &mode) || + pg_strcasecmp(list_name, "relationships") != 0) + { + return NULL; + } + + mode_expr = make_agtype_integer_const(mode + mode_offset, fn->location); + func_oid = get_age_materialize_vle_slice_boundary_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make4(vle_expr, lower_expr, upper_expr, + mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_slice_boundary_endpoint_field( + cypher_parsestate *cpstate, FuncCall *fn) +{ + FuncCall *endpoint_fn = NULL; + FuncCall *boundary_fn = NULL; + char *field_name = NULL; + char *endpoint_name = NULL; + Node *retval = NULL; + int64 mode_offset; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), FuncCall)) + { + return NULL; + } + + field_name = strVal(linitial(fn->funcname)); + endpoint_fn = (FuncCall *)linitial(fn->args); + if (list_length(endpoint_fn->funcname) != 1 || + list_length(endpoint_fn->args) != 1 || + !IsA(linitial(endpoint_fn->args), FuncCall)) + { + return NULL; + } + + endpoint_name = strVal(linitial(endpoint_fn->funcname)); + if (pg_strcasecmp(field_name, "label") == 0) + { + mode_offset = pg_strcasecmp(endpoint_name, "startNode") == 0 ? 72 : 80; + } + else if (pg_strcasecmp(field_name, "labels") == 0) + { + mode_offset = pg_strcasecmp(endpoint_name, "startNode") == 0 ? 88 : 96; + } + else if (pg_strcasecmp(field_name, "properties") == 0) + { + mode_offset = pg_strcasecmp(endpoint_name, "startNode") == 0 ? + 104 : 112; + } + else + { + return NULL; + } + + if (pg_strcasecmp(endpoint_name, "startNode") != 0 && + pg_strcasecmp(endpoint_name, "endNode") != 0) + { + return NULL; + } + + boundary_fn = (FuncCall *)linitial(endpoint_fn->args); + + retval = transform_vle_path_nested_transform_head_last( + cpstate, boundary_fn, mode_offset); + if (retval != NULL) + { + return retval; + } + + return transform_vle_path_slice_head_last(cpstate, boundary_fn, + mode_offset); +} + +static Node *try_transform_vle_tail_last_endpoint_field( + cypher_parsestate *cpstate, FuncCall *fn) +{ + char *field_name = NULL; + Expr *vle_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + bool start_endpoint; + int64 mode; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1) + { + return NULL; + } + + field_name = strVal(linitial(fn->funcname)); + if (pg_strcasecmp(field_name, "label") != 0 && + pg_strcasecmp(field_name, "labels") != 0 && + pg_strcasecmp(field_name, "properties") != 0) + { + return NULL; + } + + if (!parse_vle_tail_last_endpoint(cpstate, linitial(fn->args), &vle_expr, + &start_endpoint)) + { + return NULL; + } + + if (pg_strcasecmp(field_name, "label") == 0) + { + mode = start_endpoint ? 0 : 1; + } + else if (pg_strcasecmp(field_name, "labels") == 0) + { + mode = start_endpoint ? 2 : 3; + } + else + { + mode = start_endpoint ? 4 : 5; + } + + mode_expr = make_agtype_integer_const(mode, fn->location); + func_oid = get_age_vle_tail_last_endpoint_field_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_edge_variable_field( + cypher_parsestate *cpstate, FuncCall *fn) +{ + A_Indirection *a_ind = NULL; + A_Indices *indices = NULL; + char *field_name = NULL; + Expr *vle_expr = NULL; + Node *index_expr = NULL; + Oid func_oid; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), A_Indirection)) + { + return NULL; + } + + field_name = strVal(linitial(fn->funcname)); + if (pg_strcasecmp(field_name, "properties") != 0 && + pg_strcasecmp(field_name, "type") != 0 && + pg_strcasecmp(field_name, "label") != 0) + { + return NULL; + } + + a_ind = (A_Indirection *)linitial(fn->args); + if (list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) + { + return NULL; + } + + indices = linitial(a_ind->indirection); + if (indices->is_slice || indices->uidx == NULL) + { + return NULL; + } + + vle_expr = get_current_vle_edge_expr(cpstate, a_ind->arg); + if (vle_expr == NULL) + { + return NULL; + } + + index_expr = transform_cypher_expr_recurse(cpstate, indices->uidx); + if (pg_strcasecmp(field_name, "properties") == 0) + { + func_oid = get_age_vle_edge_properties_at_oid(); + } + else + { + func_oid = get_age_vle_edge_label_at_oid(); + } + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_node_field(cypher_parsestate *cpstate, + FuncCall *fn) +{ + A_Indirection *a_ind = NULL; + char *field_name = NULL; + char *list_name = NULL; + Expr *vle_expr = NULL; + Node *index_expr = NULL; + Oid func_oid; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), A_Indirection)) + { + return NULL; + } + + field_name = strVal(linitial(fn->funcname)); + if (pg_strcasecmp(field_name, "label") != 0 && + pg_strcasecmp(field_name, "labels") != 0 && + pg_strcasecmp(field_name, "properties") != 0) + { + return NULL; + } + + a_ind = (A_Indirection *)linitial(fn->args); + if (!parse_vle_path_indexed_list_index(cpstate, a_ind, &vle_expr, + &list_name, &index_expr) || + pg_strcasecmp(list_name, "nodes") != 0) + { + return NULL; + } + + if (pg_strcasecmp(field_name, "label") == 0) + { + func_oid = get_age_vle_node_label_at_oid(); + } + else if (pg_strcasecmp(field_name, "labels") == 0) + { + func_oid = get_age_vle_node_labels_at_oid(); + } + else + { + func_oid = get_age_vle_node_properties_at_oid(); + } + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static bool parse_vle_path_boundary_list_index(cypher_parsestate *cpstate, + Node *node, Expr **vle_expr, + char **list_name, + int64 *index) +{ + FuncCall *outer_fn = NULL; + FuncCall *inner_fn = NULL; + FuncCall *list_fn = NULL; + char *outer_name = NULL; + char *inner_name = NULL; + + if (!IsA(node, FuncCall)) + { + return false; + } + + outer_fn = (FuncCall *)node; + if (list_length(outer_fn->funcname) != 1 || + list_length(outer_fn->args) != 1) + { + return false; + } + + outer_name = strVal(linitial(outer_fn->funcname)); + if (pg_strcasecmp(outer_name, "head") != 0 && + pg_strcasecmp(outer_name, "last") != 0) + { + return false; + } + + if (!IsA(linitial(outer_fn->args), FuncCall)) + { + *vle_expr = get_current_vle_edge_expr(cpstate, + linitial(outer_fn->args)); + if (*vle_expr == NULL) + { + return false; + } + + *list_name = "relationships"; + *index = pg_strcasecmp(outer_name, "head") == 0 ? 0 : -1; + return true; + } + + inner_fn = (FuncCall *)linitial(outer_fn->args); + if (list_length(inner_fn->funcname) != 1 || + list_length(inner_fn->args) != 1) + { + return false; + } + + inner_name = strVal(linitial(inner_fn->funcname)); + if (pg_strcasecmp(inner_name, "nodes") == 0 || + pg_strcasecmp(inner_name, "relationships") == 0) + { + *list_name = inner_name; + *vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(inner_fn->args)); + *index = pg_strcasecmp(outer_name, "head") == 0 ? 0 : -1; + return *vle_expr != NULL; + } + + if ((pg_strcasecmp(inner_name, "tail") != 0 && + pg_strcasecmp(inner_name, "reverse") != 0) || + !IsA(linitial(inner_fn->args), FuncCall)) + { + return false; + } + + list_fn = (FuncCall *)linitial(inner_fn->args); + if (list_length(list_fn->funcname) != 1 || + list_length(list_fn->args) != 1) + { + return false; + } + + *list_name = strVal(linitial(list_fn->funcname)); + if (pg_strcasecmp(*list_name, "nodes") != 0 && + pg_strcasecmp(*list_name, "relationships") != 0) + { + return false; + } + + if (pg_strcasecmp(inner_name, "tail") == 0) + { + if (pg_strcasecmp(outer_name, "head") != 0) + { + return false; + } + *index = 1; + } + else + { + *index = pg_strcasecmp(outer_name, "head") == 0 ? -1 : 0; + } + + *vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(list_fn->args)); + return *vle_expr != NULL; +} + +static bool parse_vle_edge_endpoint_index(cypher_parsestate *cpstate, + Node *node, Expr **vle_expr, + Node **index_expr, + bool *start_endpoint) +{ + FuncCall *endpoint_fn = NULL; + char *endpoint_name = NULL; + char *list_name = NULL; + int64 boundary_index; + + if (!IsA(node, FuncCall)) + { + return false; + } + + endpoint_fn = (FuncCall *)node; + if (list_length(endpoint_fn->funcname) != 1 || + list_length(endpoint_fn->args) != 1) + { + return false; + } + + endpoint_name = strVal(linitial(endpoint_fn->funcname)); + if (pg_strcasecmp(endpoint_name, "startNode") == 0) + { + *start_endpoint = true; + } + else if (pg_strcasecmp(endpoint_name, "endNode") == 0) + { + *start_endpoint = false; + } + else + { + return false; + } + + if (IsA(linitial(endpoint_fn->args), A_Indirection)) + { + A_Indirection *a_ind = linitial(endpoint_fn->args); + + if (parse_vle_path_indexed_list_index(cpstate, a_ind, vle_expr, + &list_name, index_expr)) + { + return pg_strcasecmp(list_name, "relationships") == 0; + } + + if (list_length(a_ind->indirection) == 1 && + IsA(linitial(a_ind->indirection), A_Indices)) + { + A_Indices *indices = linitial(a_ind->indirection); + + if (!indices->is_slice && indices->uidx != NULL) + { + *vle_expr = get_current_vle_edge_expr(cpstate, a_ind->arg); + if (*vle_expr != NULL) + { + *index_expr = transform_cypher_expr_recurse(cpstate, + indices->uidx); + return true; + } + } + } + + return false; + } + + if (!parse_vle_path_boundary_list_index(cpstate, linitial(endpoint_fn->args), + vle_expr, &list_name, + &boundary_index) || + pg_strcasecmp(list_name, "relationships") != 0) + { + return false; + } + + *index_expr = (Node *)make_agtype_integer_const( + boundary_index, exprLocation(linitial(endpoint_fn->args))); + + return true; +} + +static bool parse_vle_path_indexed_list_index(cypher_parsestate *cpstate, + A_Indirection *a_ind, + Expr **vle_expr, + char **list_name, + Node **index_expr) +{ + FuncCall *outer_fn = NULL; + FuncCall *list_fn = NULL; + A_Indices *indices = NULL; + char *outer_name = NULL; + int64 constant_index; + + if (!IsA(a_ind->arg, FuncCall) || + list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) + { + return false; + } + + indices = linitial(a_ind->indirection); + if (indices->is_slice || indices->uidx == NULL) + { + return false; + } + + outer_fn = (FuncCall *)a_ind->arg; + if (list_length(outer_fn->funcname) != 1 || + list_length(outer_fn->args) != 1) + { + return false; + } + + outer_name = strVal(linitial(outer_fn->funcname)); + if (pg_strcasecmp(outer_name, "nodes") == 0 || + pg_strcasecmp(outer_name, "relationships") == 0) + { + *list_name = outer_name; + *vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(outer_fn->args)); + if (*vle_expr == NULL) + { + return false; + } + + *index_expr = transform_cypher_expr_recurse(cpstate, indices->uidx); + return true; + } + + if ((pg_strcasecmp(outer_name, "tail") != 0 && + pg_strcasecmp(outer_name, "reverse") != 0) || + !get_nonnegative_integer_const(indices->uidx, &constant_index)) + { + return false; + } + + if (IsA(linitial(outer_fn->args), FuncCall)) + { + list_fn = (FuncCall *)linitial(outer_fn->args); + if (list_length(list_fn->funcname) != 1 || + list_length(list_fn->args) != 1) + { + return false; + } + + *list_name = strVal(linitial(list_fn->funcname)); + if (pg_strcasecmp(*list_name, "nodes") != 0 && + pg_strcasecmp(*list_name, "relationships") != 0) + { + return false; + } + + *vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(list_fn->args)); + } + else + { + *vle_expr = get_current_vle_edge_expr(cpstate, + linitial(outer_fn->args)); + if (*vle_expr != NULL) + { + *list_name = "relationships"; + } + } + + if (*vle_expr == NULL) + { + return false; + } + + if (pg_strcasecmp(outer_name, "tail") == 0) + { + *index_expr = (Node *)make_agtype_integer_const( + constant_index + 1, exprLocation(indices->uidx)); + } + else + { + *index_expr = (Node *)make_agtype_integer_const( + -constant_index - 1, exprLocation(indices->uidx)); + } + + return true; +} + +static Node *try_transform_vle_path_boundary_field(cypher_parsestate *cpstate, + FuncCall *fn) +{ + char *field_name = NULL; + char *list_name = NULL; + Expr *vle_expr = NULL; + Const *index_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + int64 index; + int64 mode; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1) + { + return NULL; + } + + field_name = strVal(linitial(fn->funcname)); + if (pg_strcasecmp(field_name, "label") != 0 && + pg_strcasecmp(field_name, "labels") != 0 && + pg_strcasecmp(field_name, "properties") != 0 && + pg_strcasecmp(field_name, "type") != 0) + { + return NULL; + } + + if (parse_vle_path_tail_last_list(cpstate, linitial(fn->args), &vle_expr, + &list_name)) + { + if (pg_strcasecmp(list_name, "nodes") == 0) + { + if (pg_strcasecmp(field_name, "label") == 0) + { + mode = 0; + } + else if (pg_strcasecmp(field_name, "labels") == 0) + { + mode = 1; + } + else if (pg_strcasecmp(field_name, "properties") == 0) + { + mode = 2; + } + else + { + return NULL; + } + } + else + { + if (pg_strcasecmp(field_name, "label") == 0 || + pg_strcasecmp(field_name, "type") == 0) + { + mode = 3; + } + else if (pg_strcasecmp(field_name, "properties") == 0) + { + mode = 4; + } + else + { + return NULL; + } + } + + mode_expr = make_agtype_integer_const(mode, fn->location); + func_oid = get_age_vle_tail_last_field_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + } + + if (!parse_vle_path_boundary_list_index(cpstate, linitial(fn->args), + &vle_expr, &list_name, &index)) + { + return NULL; + } + + if (pg_strcasecmp(list_name, "nodes") == 0) + { + if (pg_strcasecmp(field_name, "label") == 0) + { + func_oid = get_age_vle_node_label_at_oid(); + } + else if (pg_strcasecmp(field_name, "labels") == 0) + { + func_oid = get_age_vle_node_labels_at_oid(); + } + else if (pg_strcasecmp(field_name, "properties") == 0) + { + func_oid = get_age_vle_node_properties_at_oid(); + } + else + { + return NULL; + } + } + else + { + if (pg_strcasecmp(field_name, "properties") == 0) + { + func_oid = get_age_vle_edge_properties_at_oid(); + } + else if (pg_strcasecmp(field_name, "label") == 0 || + pg_strcasecmp(field_name, "type") == 0) + { + func_oid = get_age_vle_edge_label_at_oid(); + } + else + { + return NULL; + } + } + + index_expr = make_agtype_integer_const(index, fn->location); + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_slice_boundary_field( + cypher_parsestate *cpstate, FuncCall *fn) +{ + FuncCall *boundary_fn = NULL; + char *field_name = NULL; + Node *retval = NULL; + int64 mode_offset; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), FuncCall)) + { + return NULL; + } + + field_name = strVal(linitial(fn->funcname)); + if (pg_strcasecmp(field_name, "label") == 0 || + pg_strcasecmp(field_name, "type") == 0) + { + mode_offset = 16; + } + else if (pg_strcasecmp(field_name, "labels") == 0) + { + mode_offset = 24; + } + else if (pg_strcasecmp(field_name, "properties") == 0) + { + mode_offset = 32; + } + else + { + return NULL; + } + + boundary_fn = (FuncCall *)linitial(fn->args); + + retval = transform_vle_path_nested_transform_head_last( + cpstate, boundary_fn, mode_offset); + if (retval != NULL) + { + return retval; + } + + return transform_vle_path_slice_head_last(cpstate, boundary_fn, + mode_offset); +} + +static Node *try_transform_vle_path_nested_transform_index_field( + cypher_parsestate *cpstate, FuncCall *fn) +{ + A_Indirection *a_ind = NULL; + Expr *vle_expr = NULL; + char *list_name = NULL; + Const *lower_expr = NULL; + Const *upper_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + char *field_name = NULL; + int64 mode; + int64 mode_offset; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), A_Indirection)) + { + return NULL; + } + + field_name = strVal(linitial(fn->funcname)); + if (pg_strcasecmp(field_name, "label") == 0 || + pg_strcasecmp(field_name, "type") == 0) + { + mode_offset = 16; + } + else if (pg_strcasecmp(field_name, "labels") == 0) + { + mode_offset = 24; + } + else if (pg_strcasecmp(field_name, "properties") == 0) + { + mode_offset = 32; + } + else + { + return NULL; + } + + a_ind = (A_Indirection *)linitial(fn->args); + if (!parse_vle_path_nested_transform_index(cpstate, a_ind, &vle_expr, + &list_name, &lower_expr, + &upper_expr, &mode)) + { + return NULL; + } + + if (pg_strcasecmp(list_name, "nodes") == 0) + { + if (pg_strcasecmp(field_name, "type") == 0) + { + return NULL; + } + } + else if (pg_strcasecmp(field_name, "labels") == 0) + { + return NULL; + } + + mode_expr = make_agtype_integer_const(mode + mode_offset, fn->location); + func_oid = get_age_materialize_vle_slice_boundary_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make4(vle_expr, lower_expr, upper_expr, + mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_edge_label(cypher_parsestate *cpstate, + FuncCall *fn) +{ + A_Indirection *a_ind = NULL; + char *field_name = NULL; + char *list_name = NULL; + Expr *vle_expr = NULL; + Node *index_expr = NULL; + Oid func_oid; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), A_Indirection)) + { + return NULL; + } + + field_name = strVal(linitial(fn->funcname)); + if (pg_strcasecmp(field_name, "label") != 0 && + pg_strcasecmp(field_name, "type") != 0) + { + return NULL; + } + + a_ind = (A_Indirection *)linitial(fn->args); + if (!parse_vle_path_indexed_list_index(cpstate, a_ind, &vle_expr, + &list_name, &index_expr) || + pg_strcasecmp(list_name, "relationships") != 0) + { + return NULL; + } + + func_oid = get_age_vle_edge_label_at_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_edge_properties( + cypher_parsestate *cpstate, FuncCall *fn) +{ + A_Indirection *a_ind = NULL; + char *list_name = NULL; + Expr *vle_expr = NULL; + Node *index_expr = NULL; + Oid func_oid; + + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "properties") != 0 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), A_Indirection)) + { + return NULL; + } + + a_ind = (A_Indirection *)linitial(fn->args); + if (!parse_vle_path_indexed_list_index(cpstate, a_ind, &vle_expr, + &list_name, &index_expr) || + pg_strcasecmp(list_name, "relationships") != 0) + { + return NULL; + } + + func_oid = get_age_vle_edge_properties_at_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_slice_size(cypher_parsestate *cpstate, + FuncCall *fn) +{ + A_Indirection *a_ind = NULL; + A_Indices *indices = NULL; + FuncCall *outer_fn = NULL; + FuncCall *list_fn = NULL; + Expr *vle_expr = NULL; + Node *lower_expr = NULL; + Node *upper_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + char *outer_name = NULL; + char *list_name = NULL; + int64 mode = 0; + + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "size") != 0 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), A_Indirection)) + { + return NULL; + } + + a_ind = (A_Indirection *)linitial(fn->args); + if (list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) + { + return NULL; + } + + indices = linitial(a_ind->indirection); + if (!indices->is_slice) + { + return NULL; + } + + if (IsA(a_ind->arg, FuncCall)) + { + outer_fn = (FuncCall *)a_ind->arg; + if (list_length(outer_fn->funcname) != 1 || + list_length(outer_fn->args) != 1) + { + return NULL; + } + + outer_name = strVal(linitial(outer_fn->funcname)); + if (pg_strcasecmp(outer_name, "nodes") == 0 || + pg_strcasecmp(outer_name, "relationships") == 0) + { + list_name = outer_name; + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(outer_fn->args)); + mode = pg_strcasecmp(list_name, "nodes") == 0 ? 1 : 0; + } + else if ((pg_strcasecmp(outer_name, "tail") == 0 || + pg_strcasecmp(outer_name, "reverse") == 0) && + IsA(linitial(outer_fn->args), FuncCall)) + { + list_fn = (FuncCall *)linitial(outer_fn->args); + if (list_length(list_fn->funcname) != 1 || + list_length(list_fn->args) != 1) + { + return NULL; + } + + list_name = strVal(linitial(list_fn->funcname)); + if (pg_strcasecmp(outer_name, "tail") == 0 && + pg_strcasecmp(list_name, "tail") == 0) + { + if (IsA(linitial(list_fn->args), FuncCall)) + { + FuncCall *base_fn = (FuncCall *)linitial(list_fn->args); + char *base_name = NULL; + + if (list_length(base_fn->funcname) != 1 || + list_length(base_fn->args) != 1) + { + return NULL; + } + + base_name = strVal(linitial(base_fn->funcname)); + if (pg_strcasecmp(base_name, "nodes") != 0 && + pg_strcasecmp(base_name, "relationships") != 0) + { + return NULL; + } + + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(base_fn->args)); + mode = pg_strcasecmp(base_name, "nodes") == 0 ? 7 : 6; + } + else + { + vle_expr = get_current_vle_edge_expr( + cpstate, linitial(list_fn->args)); + mode = 6; + } + + goto check_vle_expr; + } + + if ((pg_strcasecmp(outer_name, "tail") == 0 && + pg_strcasecmp(list_name, "reverse") == 0) || + (pg_strcasecmp(outer_name, "reverse") == 0 && + pg_strcasecmp(list_name, "tail") == 0)) + { + bool tail_reverse = pg_strcasecmp(outer_name, "tail") == 0; + + if (IsA(linitial(list_fn->args), FuncCall)) + { + FuncCall *base_fn = (FuncCall *)linitial(list_fn->args); + char *base_name = NULL; + bool node_list = false; + + if (list_length(base_fn->funcname) != 1 || + list_length(base_fn->args) != 1) + { + return NULL; + } + + base_name = strVal(linitial(base_fn->funcname)); + if (pg_strcasecmp(base_name, "nodes") != 0 && + pg_strcasecmp(base_name, "relationships") != 0) + { + return NULL; + } + + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(base_fn->args)); + node_list = pg_strcasecmp(base_name, "nodes") == 0; + mode = tail_reverse ? (node_list ? 9 : 8) : + (node_list ? 11 : 10); + } + else + { + vle_expr = get_current_vle_edge_expr( + cpstate, linitial(list_fn->args)); + mode = tail_reverse ? 8 : 10; + } + + goto check_vle_expr; + } + + if (pg_strcasecmp(list_name, "nodes") != 0 && + pg_strcasecmp(list_name, "relationships") != 0) + { + return NULL; + } + + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(list_fn->args)); + if (pg_strcasecmp(list_name, "nodes") == 0) + { + mode = pg_strcasecmp(outer_name, "tail") == 0 ? 3 : 1; + } + else + { + mode = pg_strcasecmp(outer_name, "tail") == 0 ? 2 : 0; + } + } + else if (pg_strcasecmp(outer_name, "tail") == 0 || + pg_strcasecmp(outer_name, "reverse") == 0) + { + vle_expr = get_current_vle_edge_expr(cpstate, + linitial(outer_fn->args)); + if (vle_expr != NULL) + { + mode = pg_strcasecmp(outer_name, "tail") == 0 ? 2 : 0; + } + } + } + else + { + vle_expr = get_current_vle_edge_expr(cpstate, a_ind->arg); + mode = 0; + } + +check_vle_expr: + if (vle_expr == NULL) + { + return NULL; + } + + if (indices->lidx == NULL) + { + lower_expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); + } + else + { + lower_expr = transform_cypher_expr_recurse(cpstate, indices->lidx); + } + + if (indices->uidx == NULL) + { + upper_expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); + } + else + { + upper_expr = transform_cypher_expr_recurse(cpstate, indices->uidx); + } + + mode_expr = make_agtype_integer_const(mode, fn->location); + func_oid = get_age_vle_list_slice_count_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make4(vle_expr, lower_expr, upper_expr, + mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_slice_is_empty(cypher_parsestate *cpstate, + FuncCall *fn) +{ + A_Indirection *a_ind = NULL; + A_Indices *indices = NULL; + FuncCall *outer_fn = NULL; + FuncCall *list_fn = NULL; + Expr *vle_expr = NULL; + Node *lower_expr = NULL; + Node *upper_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + char *outer_name = NULL; + char *list_name = NULL; + int64 mode = 0; + + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "isEmpty") != 0 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), A_Indirection)) + { + return NULL; + } + + a_ind = (A_Indirection *)linitial(fn->args); + if (list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) + { + return NULL; + } + + indices = linitial(a_ind->indirection); + if (!indices->is_slice) + { + return NULL; + } + + if (IsA(a_ind->arg, FuncCall)) + { + outer_fn = (FuncCall *)a_ind->arg; + if (list_length(outer_fn->funcname) != 1 || + list_length(outer_fn->args) != 1) + { + return NULL; + } + + outer_name = strVal(linitial(outer_fn->funcname)); + if (pg_strcasecmp(outer_name, "nodes") == 0 || + pg_strcasecmp(outer_name, "relationships") == 0) + { + list_name = outer_name; + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(outer_fn->args)); + mode = pg_strcasecmp(list_name, "nodes") == 0 ? 1 : 0; + } + else if ((pg_strcasecmp(outer_name, "tail") == 0 || + pg_strcasecmp(outer_name, "reverse") == 0) && + IsA(linitial(outer_fn->args), FuncCall)) + { + list_fn = (FuncCall *)linitial(outer_fn->args); + if (list_length(list_fn->funcname) != 1 || + list_length(list_fn->args) != 1) + { + return NULL; + } + + list_name = strVal(linitial(list_fn->funcname)); + if (pg_strcasecmp(outer_name, "tail") == 0 && + pg_strcasecmp(list_name, "tail") == 0) + { + if (IsA(linitial(list_fn->args), FuncCall)) + { + FuncCall *base_fn = (FuncCall *)linitial(list_fn->args); + char *base_name = NULL; + + if (list_length(base_fn->funcname) != 1 || + list_length(base_fn->args) != 1) + { + return NULL; + } + + base_name = strVal(linitial(base_fn->funcname)); + if (pg_strcasecmp(base_name, "nodes") != 0 && + pg_strcasecmp(base_name, "relationships") != 0) + { + return NULL; + } + + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(base_fn->args)); + mode = pg_strcasecmp(base_name, "nodes") == 0 ? 7 : 6; + } + else + { + vle_expr = get_current_vle_edge_expr( + cpstate, linitial(list_fn->args)); + mode = 6; + } + + goto check_vle_expr; + } + + if ((pg_strcasecmp(outer_name, "tail") == 0 && + pg_strcasecmp(list_name, "reverse") == 0) || + (pg_strcasecmp(outer_name, "reverse") == 0 && + pg_strcasecmp(list_name, "tail") == 0)) + { + bool tail_reverse = pg_strcasecmp(outer_name, "tail") == 0; + + if (IsA(linitial(list_fn->args), FuncCall)) + { + FuncCall *base_fn = (FuncCall *)linitial(list_fn->args); + char *base_name = NULL; + bool node_list = false; + + if (list_length(base_fn->funcname) != 1 || + list_length(base_fn->args) != 1) + { + return NULL; + } + + base_name = strVal(linitial(base_fn->funcname)); + if (pg_strcasecmp(base_name, "nodes") != 0 && + pg_strcasecmp(base_name, "relationships") != 0) + { + return NULL; + } + + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(base_fn->args)); + node_list = pg_strcasecmp(base_name, "nodes") == 0; + mode = tail_reverse ? (node_list ? 9 : 8) : + (node_list ? 11 : 10); + } + else + { + vle_expr = get_current_vle_edge_expr( + cpstate, linitial(list_fn->args)); + mode = tail_reverse ? 8 : 10; + } + + goto check_vle_expr; + } + + if (pg_strcasecmp(list_name, "nodes") != 0 && + pg_strcasecmp(list_name, "relationships") != 0) + { + return NULL; + } + + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(list_fn->args)); + if (pg_strcasecmp(list_name, "nodes") == 0) + { + mode = pg_strcasecmp(outer_name, "tail") == 0 ? 3 : 1; + } + else + { + mode = pg_strcasecmp(outer_name, "tail") == 0 ? 2 : 0; + } + } + else if (pg_strcasecmp(outer_name, "tail") == 0 || + pg_strcasecmp(outer_name, "reverse") == 0) + { + vle_expr = get_current_vle_edge_expr(cpstate, + linitial(outer_fn->args)); + if (vle_expr != NULL) + { + mode = pg_strcasecmp(outer_name, "tail") == 0 ? 2 : 0; + } + } + } + else + { + vle_expr = get_current_vle_edge_expr(cpstate, a_ind->arg); + mode = 0; + } + +check_vle_expr: + if (vle_expr == NULL) + { + return NULL; + } + + if (indices->lidx == NULL) + { + lower_expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); + } + else + { + lower_expr = transform_cypher_expr_recurse(cpstate, indices->lidx); + } + + if (indices->uidx == NULL) + { + upper_expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); + } + else + { + upper_expr = transform_cypher_expr_recurse(cpstate, indices->uidx); + } + + mode_expr = make_agtype_integer_const(mode, fn->location); + func_oid = get_age_vle_list_slice_is_empty_oid(); + + return (Node *)makeFuncExpr(func_oid, BOOLOID, + list_make4(vle_expr, lower_expr, upper_expr, + mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *transform_vle_path_slice_head_last(cypher_parsestate *cpstate, + FuncCall *fn, + int64 mode_offset) +{ + A_Indirection *a_ind = NULL; + A_Indices *indices = NULL; + FuncCall *outer_fn = NULL; + FuncCall *list_fn = NULL; + Expr *vle_expr = NULL; + Node *lower_expr = NULL; + Node *upper_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + char *head_last_name = NULL; + char *outer_name = NULL; + char *list_name = NULL; + int64 mode = 0; + bool last = false; + bool reverse = false; + bool tail_reverse = false; + bool double_tail = false; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), A_Indirection)) + { + return NULL; + } + + head_last_name = strVal(linitial(fn->funcname)); + if (pg_strcasecmp(head_last_name, "head") != 0 && + pg_strcasecmp(head_last_name, "last") != 0) + { + return NULL; + } + last = pg_strcasecmp(head_last_name, "last") == 0; + + a_ind = (A_Indirection *)linitial(fn->args); + if (list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) + { + return NULL; + } + + indices = linitial(a_ind->indirection); + if (!indices->is_slice) + { + return NULL; + } + + if (IsA(a_ind->arg, FuncCall)) + { + outer_fn = (FuncCall *)a_ind->arg; + if (list_length(outer_fn->funcname) != 1 || + list_length(outer_fn->args) != 1) + { + return NULL; + } + + outer_name = strVal(linitial(outer_fn->funcname)); + if (pg_strcasecmp(outer_name, "nodes") == 0 || + pg_strcasecmp(outer_name, "relationships") == 0) + { + list_name = outer_name; + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(outer_fn->args)); + mode = pg_strcasecmp(list_name, "nodes") == 0 ? 2 : 0; + } + else if ((pg_strcasecmp(outer_name, "tail") == 0 || + pg_strcasecmp(outer_name, "reverse") == 0) && + IsA(linitial(outer_fn->args), FuncCall)) + { + list_fn = (FuncCall *)linitial(outer_fn->args); + if (list_length(list_fn->funcname) != 1 || + list_length(list_fn->args) != 1) + { + return NULL; + } + + list_name = strVal(linitial(list_fn->funcname)); + if (pg_strcasecmp(outer_name, "tail") == 0 && + pg_strcasecmp(list_name, "tail") == 0) + { + if (IsA(linitial(list_fn->args), FuncCall)) + { + FuncCall *base_fn = (FuncCall *)linitial(list_fn->args); + char *base_name = NULL; + + if (list_length(base_fn->funcname) != 1 || + list_length(base_fn->args) != 1) + { + return NULL; + } + + base_name = strVal(linitial(base_fn->funcname)); + if (pg_strcasecmp(base_name, "nodes") != 0 && + pg_strcasecmp(base_name, "relationships") != 0) + { + return NULL; + } + + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(base_fn->args)); + mode = pg_strcasecmp(base_name, "nodes") == 0 ? 6 : 4; + } + else + { + vle_expr = get_current_vle_edge_expr( + cpstate, linitial(list_fn->args)); + mode = 4; + } + + double_tail = true; + goto check_vle_expr; + } + + if ((pg_strcasecmp(outer_name, "tail") == 0 && + pg_strcasecmp(list_name, "reverse") == 0) || + (pg_strcasecmp(outer_name, "reverse") == 0 && + pg_strcasecmp(list_name, "tail") == 0)) + { + bool node_list = false; + + if (IsA(linitial(list_fn->args), FuncCall)) + { + FuncCall *base_fn = (FuncCall *)linitial(list_fn->args); + char *base_name = NULL; + + if (list_length(base_fn->funcname) != 1 || + list_length(base_fn->args) != 1) + { + return NULL; + } + + base_name = strVal(linitial(base_fn->funcname)); + if (pg_strcasecmp(base_name, "nodes") != 0 && + pg_strcasecmp(base_name, "relationships") != 0) + { + return NULL; + } + + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(base_fn->args)); + node_list = pg_strcasecmp(base_name, "nodes") == 0; + } + else + { + vle_expr = get_current_vle_edge_expr( + cpstate, linitial(list_fn->args)); + } + + mode = node_list ? 6 : 4; + if (pg_strcasecmp(outer_name, "tail") == 0) + { + tail_reverse = true; + } + else + { + reverse = true; + } + goto check_vle_expr; + } + + if (pg_strcasecmp(list_name, "nodes") != 0 && + pg_strcasecmp(list_name, "relationships") != 0) + { + return NULL; + } + + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(list_fn->args)); + if (pg_strcasecmp(outer_name, "tail") == 0) + { + mode = pg_strcasecmp(list_name, "nodes") == 0 ? 6 : 4; + } + else + { + mode = pg_strcasecmp(list_name, "nodes") == 0 ? 2 : 0; + reverse = true; + } + } + else if (pg_strcasecmp(outer_name, "tail") == 0 || + pg_strcasecmp(outer_name, "reverse") == 0) + { + vle_expr = get_current_vle_edge_expr(cpstate, + linitial(outer_fn->args)); + if (vle_expr != NULL) + { + if (pg_strcasecmp(outer_name, "tail") == 0) + { + mode = 4; + } + else + { + mode = 0; + reverse = true; + } + } + } + } + else + { + vle_expr = get_current_vle_edge_expr(cpstate, a_ind->arg); + mode = 0; + } + +check_vle_expr: + if (vle_expr == NULL) + { + return NULL; + } + + if (last) + { + mode++; + } + + if (indices->lidx == NULL) + { + lower_expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); + } + else + { + lower_expr = transform_cypher_expr_recurse(cpstate, indices->lidx); + } + + if (indices->uidx == NULL) + { + upper_expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); + } + else + { + upper_expr = transform_cypher_expr_recurse(cpstate, indices->uidx); + } + + mode_expr = make_agtype_integer_const(mode + mode_offset + + (double_tail ? 360 : 0) + + (tail_reverse ? 240 : 0) + + (reverse ? 120 : 0), + fn->location); + func_oid = get_age_materialize_vle_slice_boundary_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make4(vle_expr, lower_expr, upper_expr, + mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_slice_head_last(cypher_parsestate *cpstate, + FuncCall *fn) +{ + return transform_vle_path_slice_head_last(cpstate, fn, 0); +} + +static Node *try_transform_vle_path_slice_boundary_id_function( + cypher_parsestate *cpstate, FuncCall *fn) +{ + FuncCall *boundary_fn = NULL; + + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "id") != 0 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), FuncCall)) + { + return NULL; + } + + boundary_fn = (FuncCall *)linitial(fn->args); + + return transform_vle_path_slice_head_last(cpstate, boundary_fn, 8); +} + +static Node *try_transform_vle_path_nested_transform_index_id_function( + cypher_parsestate *cpstate, FuncCall *fn) +{ + A_Indirection *a_ind = NULL; + Expr *vle_expr = NULL; + char *list_name = NULL; + Const *lower_expr = NULL; + Const *upper_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + int64 mode; + + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "id") != 0 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), A_Indirection)) + { + return NULL; + } + + a_ind = (A_Indirection *)linitial(fn->args); + if (!parse_vle_path_nested_transform_index(cpstate, a_ind, &vle_expr, + &list_name, &lower_expr, + &upper_expr, &mode)) + { + return NULL; + } + + mode_expr = make_agtype_integer_const(mode + 8, fn->location); + func_oid = get_age_materialize_vle_slice_boundary_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make4(vle_expr, lower_expr, upper_expr, + mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_size(cypher_parsestate *cpstate, + FuncCall *fn) +{ + FuncCall *inner_fn = NULL; + FuncCall *list_fn = NULL; + char *inner_name = NULL; + char *list_name = NULL; + Expr *vle_expr = NULL; + Oid func_oid; + bool tail_mode = false; + bool double_tail = false; + + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "size") != 0 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), FuncCall)) { - Node *texpr = NULL; + return NULL; + } + + inner_fn = (FuncCall *)linitial(fn->args); + if (list_length(inner_fn->funcname) != 1 || + list_length(inner_fn->args) != 1) + { + return NULL; + } + + inner_name = strVal(linitial(inner_fn->funcname)); + if (pg_strcasecmp(inner_name, "nodes") == 0 || + pg_strcasecmp(inner_name, "relationships") == 0) + { + list_name = inner_name; + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(inner_fn->args)); + } + else if ((pg_strcasecmp(inner_name, "tail") == 0 || + pg_strcasecmp(inner_name, "reverse") == 0) && + IsA(linitial(inner_fn->args), FuncCall)) + { + list_fn = (FuncCall *)linitial(inner_fn->args); + if (list_length(list_fn->funcname) != 1 || + list_length(list_fn->args) != 1) + { + return NULL; + } + + list_name = strVal(linitial(list_fn->funcname)); + if (pg_strcasecmp(list_name, "nodes") == 0 || + pg_strcasecmp(list_name, "relationships") == 0) + { + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(list_fn->args)); + tail_mode = pg_strcasecmp(inner_name, "tail") == 0; + } + else if ((pg_strcasecmp(list_name, "tail") == 0 || + pg_strcasecmp(list_name, "reverse") == 0) && + list_length(list_fn->args) == 1) + { + FuncCall *base_fn = NULL; + char *base_name = NULL; + + double_tail = pg_strcasecmp(inner_name, "tail") == 0 && + pg_strcasecmp(list_name, "tail") == 0; + tail_mode = pg_strcasecmp(inner_name, "tail") == 0 || + pg_strcasecmp(list_name, "tail") == 0; + + if (IsA(linitial(list_fn->args), FuncCall)) + { + base_fn = (FuncCall *)linitial(list_fn->args); + if (list_length(base_fn->funcname) != 1 || + list_length(base_fn->args) != 1) + { + return NULL; + } + + base_name = strVal(linitial(base_fn->funcname)); + if (pg_strcasecmp(base_name, "nodes") != 0 && + pg_strcasecmp(base_name, "relationships") != 0) + { + return NULL; + } + + list_name = base_name; + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(base_fn->args)); + } + else + { + vle_expr = get_current_vle_edge_expr( + cpstate, linitial(list_fn->args)); + if (vle_expr != NULL) + { + list_name = "relationships"; + } + } + } + else + { + return NULL; + } + } + else if (pg_strcasecmp(inner_name, "tail") == 0 || + pg_strcasecmp(inner_name, "reverse") == 0) + { + vle_expr = get_current_vle_edge_expr(cpstate, linitial(inner_fn->args)); + if (vle_expr != NULL) + { + list_name = "relationships"; + tail_mode = pg_strcasecmp(inner_name, "tail") == 0; + } + } + else + { + return NULL; + } + + if (vle_expr == NULL) + { + return NULL; + } + + if (double_tail) + { + Node *lower_expr = (Node *)make_agtype_integer_const(2, fn->location); + Node *upper_expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); + Const *mode_expr = make_agtype_integer_const( + pg_strcasecmp(list_name, "nodes") == 0 ? 1 : 0, fn->location); + + func_oid = get_age_vle_list_slice_count_oid(); + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make4(vle_expr, lower_expr, + upper_expr, mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + } + + if (tail_mode && pg_strcasecmp(list_name, "relationships") == 0) + { + func_oid = get_age_vle_edge_tail_count_oid(); + } + else if (pg_strcasecmp(list_name, "nodes") == 0) + { + if (tail_mode) + { + func_oid = get_age_vle_path_length_oid(); + } + else + { + func_oid = get_age_vle_path_node_count_oid(); + } + } + else + { + func_oid = get_age_vle_path_length_oid(); + } + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, list_make1(vle_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_is_empty(cypher_parsestate *cpstate, + FuncCall *fn) +{ + FuncCall *inner_fn = NULL; + FuncCall *list_fn = NULL; + char *inner_name = NULL; + char *list_name = NULL; + Expr *vle_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + int64 mode; + bool tail_mode = false; + bool double_tail = false; + + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "isEmpty") != 0 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), FuncCall)) + { + return NULL; + } + + inner_fn = (FuncCall *)linitial(fn->args); + if (list_length(inner_fn->funcname) != 1 || + list_length(inner_fn->args) != 1) + { + return NULL; + } + + inner_name = strVal(linitial(inner_fn->funcname)); + if (pg_strcasecmp(inner_name, "nodes") == 0 || + pg_strcasecmp(inner_name, "relationships") == 0) + { + list_name = inner_name; + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(inner_fn->args)); + mode = pg_strcasecmp(list_name, "nodes") == 0 ? 0 : 1; + } + else if ((pg_strcasecmp(inner_name, "tail") == 0 || + pg_strcasecmp(inner_name, "reverse") == 0) && + IsA(linitial(inner_fn->args), FuncCall)) + { + list_fn = (FuncCall *)linitial(inner_fn->args); + if (list_length(list_fn->funcname) != 1 || + list_length(list_fn->args) != 1) + { + return NULL; + } + + list_name = strVal(linitial(list_fn->funcname)); + if (pg_strcasecmp(list_name, "nodes") == 0 || + pg_strcasecmp(list_name, "relationships") == 0) + { + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(list_fn->args)); + tail_mode = pg_strcasecmp(inner_name, "tail") == 0; + } + else if ((pg_strcasecmp(list_name, "tail") == 0 || + pg_strcasecmp(list_name, "reverse") == 0) && + list_length(list_fn->args) == 1) + { + FuncCall *base_fn = NULL; + char *base_name = NULL; + + double_tail = pg_strcasecmp(inner_name, "tail") == 0 && + pg_strcasecmp(list_name, "tail") == 0; + tail_mode = pg_strcasecmp(inner_name, "tail") == 0 || + pg_strcasecmp(list_name, "tail") == 0; + + if (IsA(linitial(list_fn->args), FuncCall)) + { + base_fn = (FuncCall *)linitial(list_fn->args); + if (list_length(base_fn->funcname) != 1 || + list_length(base_fn->args) != 1) + { + return NULL; + } + + base_name = strVal(linitial(base_fn->funcname)); + if (pg_strcasecmp(base_name, "nodes") != 0 && + pg_strcasecmp(base_name, "relationships") != 0) + { + return NULL; + } + + list_name = base_name; + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(base_fn->args)); + } + else + { + vle_expr = get_current_vle_edge_expr( + cpstate, linitial(list_fn->args)); + if (vle_expr != NULL) + { + list_name = "relationships"; + } + } + } + else + { + return NULL; + } + + if (tail_mode) + { + mode = pg_strcasecmp(list_name, "nodes") == 0 ? 2 : 3; + } + else + { + mode = pg_strcasecmp(list_name, "nodes") == 0 ? 0 : 1; + } + } + else if (pg_strcasecmp(inner_name, "tail") == 0 || + pg_strcasecmp(inner_name, "reverse") == 0) + { + vle_expr = get_current_vle_edge_expr(cpstate, linitial(inner_fn->args)); + if (vle_expr == NULL) + { + return NULL; + } + tail_mode = pg_strcasecmp(inner_name, "tail") == 0; + mode = tail_mode ? 3 : 1; + } + else + { + return NULL; + } + + if (vle_expr == NULL) + { + return NULL; + } + + if (double_tail) + { + Node *lower_expr = (Node *)make_agtype_integer_const(2, fn->location); + Node *upper_expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); + + mode_expr = make_agtype_integer_const( + pg_strcasecmp(list_name, "nodes") == 0 ? 1 : 0, fn->location); + func_oid = get_age_vle_list_slice_is_empty_oid(); + return (Node *)makeFuncExpr(func_oid, BOOLOID, + list_make4(vle_expr, lower_expr, + upper_expr, mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + } + + mode_expr = make_agtype_integer_const(mode, fn->location); + func_oid = get_age_vle_list_is_empty_oid(); + + return (Node *)makeFuncExpr(func_oid, BOOLOID, + list_make2(vle_expr, mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_head_last(cypher_parsestate *cpstate, + FuncCall *fn) +{ + FuncCall *inner_fn = NULL; + FuncCall *list_fn = NULL; + char *outer_name = NULL; + char *inner_name = NULL; + char *list_name = NULL; + Expr *vle_expr = NULL; + Const *index_expr = NULL; + Oid func_oid; + int64 index; + bool tail_last = false; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1) + { + return NULL; + } + + outer_name = strVal(linitial(fn->funcname)); + if (pg_strcasecmp(outer_name, "head") != 0 && + pg_strcasecmp(outer_name, "last") != 0) + { + return NULL; + } + + if (!IsA(linitial(fn->args), FuncCall)) + { + vle_expr = get_current_vle_edge_expr(cpstate, linitial(fn->args)); + if (vle_expr == NULL) + { + return NULL; + } + + list_name = "relationships"; + index = pg_strcasecmp(outer_name, "head") == 0 ? 0 : -1; + goto build_index_access; + } + + inner_fn = (FuncCall *)linitial(fn->args); + if (list_length(inner_fn->funcname) != 1 || + list_length(inner_fn->args) != 1) + { + return NULL; + } + + inner_name = strVal(linitial(inner_fn->funcname)); + if (pg_strcasecmp(inner_name, "nodes") == 0 || + pg_strcasecmp(inner_name, "relationships") == 0) + { + list_name = inner_name; + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(inner_fn->args)); + index = pg_strcasecmp(outer_name, "head") == 0 ? 0 : -1; + } + else if ((pg_strcasecmp(inner_name, "tail") == 0 || + pg_strcasecmp(inner_name, "reverse") == 0) && + IsA(linitial(inner_fn->args), FuncCall)) + { + list_fn = (FuncCall *)linitial(inner_fn->args); + + if (list_length(list_fn->funcname) != 1 || + list_length(list_fn->args) != 1) + { + return NULL; + } + + list_name = strVal(linitial(list_fn->funcname)); + if (pg_strcasecmp(list_name, "nodes") != 0 && + pg_strcasecmp(list_name, "relationships") != 0) + { + return NULL; + } + + /* + * head(tail(list)) maps to original index 1. last(tail(list)) needs a + * tail-aware helper because it must return null when the tail is empty. + */ + if (pg_strcasecmp(inner_name, "tail") == 0) + { + if (pg_strcasecmp(outer_name, "head") == 0) + { + index = 1; + } + else + { + tail_last = true; + index = -1; + } + } + else + { + index = pg_strcasecmp(outer_name, "head") == 0 ? -1 : 0; + } + + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(list_fn->args)); + } + else if (pg_strcasecmp(inner_name, "tail") == 0 || + pg_strcasecmp(inner_name, "reverse") == 0) + { + vle_expr = get_current_vle_edge_expr(cpstate, linitial(inner_fn->args)); + if (vle_expr == NULL) + { + return NULL; + } + + list_name = "relationships"; + if (pg_strcasecmp(inner_name, "tail") == 0) + { + if (pg_strcasecmp(outer_name, "head") == 0) + { + index = 1; + } + else + { + tail_last = true; + index = -1; + } + } + else + { + index = pg_strcasecmp(outer_name, "head") == 0 ? -1 : 0; + } + } + else + { + return NULL; + } + + if (vle_expr == NULL) + { + return NULL; + } + + if (tail_last) + { + if (pg_strcasecmp(list_name, "nodes") == 0) + { + func_oid = get_age_materialize_vle_node_tail_last_oid(); + } + else + { + func_oid = get_age_materialize_vle_edge_tail_last_oid(); + } + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, list_make1(vle_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + } + +build_index_access: + index_expr = make_agtype_integer_const(index, fn->location); + + if (pg_strcasecmp(list_name, "nodes") == 0) + { + func_oid = get_age_materialize_vle_node_at_oid(); + } + else + { + func_oid = get_age_materialize_vle_edge_at_oid(); + } + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *transform_vle_path_nested_transform_head_last( + cypher_parsestate *cpstate, FuncCall *fn, int64 mode_offset) +{ + FuncCall *outer_fn = NULL; + FuncCall *inner_fn = NULL; + FuncCall *base_fn = NULL; + Expr *vle_expr = NULL; + Node *lower_expr = NULL; + Node *upper_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + char *head_last_name = NULL; + char *outer_name = NULL; + char *inner_name = NULL; + char *base_name = NULL; + int64 mode_flag = 0; + int64 mode; + bool last; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), FuncCall)) + { + return NULL; + } + + head_last_name = strVal(linitial(fn->funcname)); + if (pg_strcasecmp(head_last_name, "head") != 0 && + pg_strcasecmp(head_last_name, "last") != 0) + { + return NULL; + } + last = pg_strcasecmp(head_last_name, "last") == 0; + + outer_fn = (FuncCall *)linitial(fn->args); + if (list_length(outer_fn->funcname) != 1 || + list_length(outer_fn->args) != 1 || + !IsA(linitial(outer_fn->args), FuncCall)) + { + return NULL; + } + + outer_name = strVal(linitial(outer_fn->funcname)); + inner_fn = (FuncCall *)linitial(outer_fn->args); + if (list_length(inner_fn->funcname) != 1 || + list_length(inner_fn->args) != 1) + { + return NULL; + } + + inner_name = strVal(linitial(inner_fn->funcname)); + if (pg_strcasecmp(outer_name, "tail") == 0 && + pg_strcasecmp(inner_name, "tail") == 0) + { + mode_flag = 360; + } + else if (pg_strcasecmp(outer_name, "tail") == 0 && + pg_strcasecmp(inner_name, "reverse") == 0) + { + mode_flag = 240; + } + else if (pg_strcasecmp(outer_name, "reverse") == 0 && + pg_strcasecmp(inner_name, "tail") == 0) + { + mode_flag = 120; + } + else + { + return NULL; + } + + if (IsA(linitial(inner_fn->args), FuncCall)) + { + base_fn = (FuncCall *)linitial(inner_fn->args); + if (list_length(base_fn->funcname) != 1 || + list_length(base_fn->args) != 1) + { + return NULL; + } + + base_name = strVal(linitial(base_fn->funcname)); + if (pg_strcasecmp(base_name, "nodes") != 0 && + pg_strcasecmp(base_name, "relationships") != 0) + { + return NULL; + } + + vle_expr = get_current_single_vle_path_expr(cpstate, + linitial(base_fn->args)); + mode = (pg_strcasecmp(base_name, "nodes") == 0 ? 6 : 4) + mode_flag; + } + else + { + vle_expr = get_current_vle_edge_expr(cpstate, + linitial(inner_fn->args)); + if (vle_expr == NULL) + { + return NULL; + } + mode = 4 + mode_flag; + } - /* transform the argument */ - texpr = transform_cypher_expr_recurse(cpstate, lfirst(le)); + if (vle_expr == NULL) + { + return NULL; + } - /* - * If we have more than 100 elements we will need to add in the list - * concatenation function. - */ - if (i >= 100) - { - /* build the list function node argument for concatenate */ - fexpr = makeFuncExpr(abl_func_oid, AGTYPEOID, abl_args, InvalidOid, - InvalidOid, COERCE_EXPLICIT_CALL); - fexpr->location = cl->location; + lower_expr = (Node *)make_agtype_integer_const(0, fn->location); + upper_expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); + mode_expr = make_agtype_integer_const(mode + mode_offset + + (last ? 1 : 0), + fn->location); + func_oid = get_age_materialize_vle_slice_boundary_oid(); - /* initial case, set up for concatenating 2 lists */ - if (aa_lhs_arg == NULL) - { - aa_lhs_arg = fexpr; - } - /* - * For every other case, concatenate the list on to the previous - * concatenate operation. - */ - else - { - List *aa_args = list_make2(aa_lhs_arg, fexpr); + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make4(vle_expr, lower_expr, upper_expr, + mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} - fexpr = makeFuncExpr(aa_func_oid, AGTYPEOID, aa_args, - InvalidOid, InvalidOid, - COERCE_EXPLICIT_CALL); - fexpr->location = cl->location; +static Node *try_transform_vle_path_nested_transform_head_last( + cypher_parsestate *cpstate, FuncCall *fn) +{ + return transform_vle_path_nested_transform_head_last(cpstate, fn, 0); +} - /* set the lhs to the concatenation operation */ - aa_lhs_arg = fexpr; - } +static Node *try_transform_vle_path_list_tail_reverse( + cypher_parsestate *cpstate, FuncCall *fn) +{ + FuncCall *inner_fn = NULL; + char *outer_name = NULL; + char *inner_name = NULL; + Expr *vle_expr = NULL; + Oid func_oid; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), FuncCall)) + { + return NULL; + } - /* reset */ - abl_args = NIL; - i = 0; - fexpr = NULL; - } + outer_name = strVal(linitial(fn->funcname)); + if (pg_strcasecmp(outer_name, "tail") != 0 && + pg_strcasecmp(outer_name, "reverse") != 0) + { + return NULL; + } - /* now add the latest transformed expression to the list */ - abl_args = lappend(abl_args, texpr); - i++; + inner_fn = (FuncCall *)linitial(fn->args); + if (list_length(inner_fn->funcname) != 1 || + list_length(inner_fn->args) != 1) + { + return NULL; } - /* now build the final list function */ - fexpr = makeFuncExpr(abl_func_oid, AGTYPEOID, abl_args, InvalidOid, - InvalidOid, COERCE_EXPLICIT_CALL); - fexpr->location = cl->location; + inner_name = strVal(linitial(inner_fn->funcname)); + if (pg_strcasecmp(inner_name, "nodes") != 0 && + pg_strcasecmp(inner_name, "relationships") != 0) + { + return NULL; + } - /* - * If there was a previous concatenation or list function, build a final - * concatenation function node - */ - if (aa_lhs_arg != NULL) + vle_expr = get_current_single_vle_path_expr(cpstate, + linitial(inner_fn->args)); + if (vle_expr == NULL) { - List *aa_args = list_make2(aa_lhs_arg, fexpr); + return NULL; + } - fexpr = makeFuncExpr(aa_func_oid, AGTYPEOID, aa_args, InvalidOid, - InvalidOid, COERCE_EXPLICIT_CALL); + if (pg_strcasecmp(inner_name, "nodes") == 0) + { + if (pg_strcasecmp(outer_name, "tail") == 0) + { + func_oid = get_age_materialize_vle_nodes_tail_oid(); + } + else + { + func_oid = get_age_materialize_vle_nodes_reversed_oid(); + } + } + else + { + if (pg_strcasecmp(outer_name, "tail") == 0) + { + func_oid = get_age_materialize_vle_edges_tail_oid(); + } + else + { + func_oid = get_age_materialize_vle_edges_reversed_oid(); + } } - return (Node *)fexpr; + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, list_make1(vle_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); } -/* makes a VARIADIC agtype array */ -static ArrayExpr *make_agtype_array_expr(List *args) +static Node *try_transform_vle_path_relationships(cypher_parsestate *cpstate, + FuncCall *fn) { - ArrayExpr *newa = makeNode(ArrayExpr); + Expr *vle_expr = NULL; + Oid func_oid; - newa->elements = args; + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "relationships") != 0 || + list_length(fn->args) != 1) + { + return NULL; + } - /* assume all the variadic arguments were coerced to the same type */ - newa->element_typeid = AGTYPEOID; - newa->array_typeid = AGTYPEARRAYOID; + vle_expr = get_current_single_vle_path_expr(cpstate, linitial(fn->args)); + if (vle_expr == NULL) + { + return NULL; + } - if (!OidIsValid(newa->array_typeid)) + func_oid = get_age_materialize_vle_edges_oid(); + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, list_make1(vle_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_nodes(cypher_parsestate *cpstate, + FuncCall *fn) +{ + Expr *vle_expr = NULL; + Oid func_oid; + + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "nodes") != 0 || + list_length(fn->args) != 1) { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("could not find array type for data type %s", - format_type_be(newa->element_typeid)))); + return NULL; } - /* array_collid will be set by parse_collate.c */ - newa->multidims = false; + vle_expr = get_current_single_vle_path_expr(cpstate, linitial(fn->args)); + if (vle_expr == NULL) + { + return NULL; + } - return newa; + func_oid = get_age_materialize_vle_nodes_oid(); + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, list_make1(vle_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); } -/* - * Transform a ColumnRef for indirection. Try to find the rte that the ColumnRef - * references and pass the properties of that rte as what the ColumnRef is - * referencing. Otherwise, reference the Var. - */ -static Node *transform_column_ref_for_indirection(cypher_parsestate *cpstate, - ColumnRef *cr) +static bool get_nonnegative_integer_const(Node *node, int64 *value) { - ParseState *pstate = (ParseState *)cpstate; - ParseNamespaceItem *pnsi = NULL; - Node *field1 = linitial(cr->fields); - char *relname = NULL; - Node *node = NULL; - int levels_up = 0; + if (!is_ag_node(node, cypher_integer_const)) + { + return false; + } - Assert(IsA(field1, String)); - relname = strVal(field1); + *value = ((cypher_integer_const *)node)->integer; + return *value >= 0; +} - /* locate the referenced RTE (used to be find_rte(cpstate, relname)) */ - pnsi = refnameNamespaceItem(pstate, NULL, relname, cr->location, - &levels_up); +static bool parse_vle_path_nested_transform_index(cypher_parsestate *cpstate, + A_Indirection *a_ind, + Expr **vle_expr, + char **list_name, + Const **lower_expr, + Const **upper_expr, + int64 *mode) +{ + FuncCall *outer_fn = NULL; + FuncCall *inner_fn = NULL; + FuncCall *base_fn = NULL; + A_Indices *indices = NULL; + char *outer_name = NULL; + char *inner_name = NULL; + char *base_name = NULL; + int64 index; + int64 mode_flag = 0; + + if (!IsA(a_ind->arg, FuncCall) || + list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) + { + return false; + } - /* - * If we didn't find anything, try looking for a previous variable - * reference. Otherwise, return NULL (colNameToVar will return NULL - * if nothing is found). - */ - if (!pnsi) + indices = linitial(a_ind->indirection); + if (indices->is_slice || indices->uidx == NULL || + !get_nonnegative_integer_const(indices->uidx, &index)) { - Node *prev_var = colNameToVar(pstate, relname, false, cr->location); + return false; + } - return prev_var; + outer_fn = (FuncCall *)a_ind->arg; + if (list_length(outer_fn->funcname) != 1 || + list_length(outer_fn->args) != 1 || + !IsA(linitial(outer_fn->args), FuncCall)) + { + return false; } - /* find the properties column of the NSI and return a var for it */ - node = scanNSItemForColumn(pstate, pnsi, levels_up, "properties", - cr->location); + outer_name = strVal(linitial(outer_fn->funcname)); + if (pg_strcasecmp(outer_name, "tail") != 0 && + pg_strcasecmp(outer_name, "reverse") != 0) + { + return false; + } - /* - * If there's no "properties" column, continue transforming the - * ColumnRef as an agtype value and try to apply the indirection via - * agtype_access_operator. - */ - return node; + inner_fn = (FuncCall *)linitial(outer_fn->args); + if (list_length(inner_fn->funcname) != 1 || + list_length(inner_fn->args) != 1) + { + return false; + } + + inner_name = strVal(linitial(inner_fn->funcname)); + if (pg_strcasecmp(outer_name, "tail") == 0 && + pg_strcasecmp(inner_name, "tail") == 0) + { + mode_flag = 360; + } + else if (pg_strcasecmp(outer_name, "tail") == 0 && + pg_strcasecmp(inner_name, "reverse") == 0) + { + mode_flag = 240; + } + else if (pg_strcasecmp(outer_name, "reverse") == 0 && + pg_strcasecmp(inner_name, "tail") == 0) + { + mode_flag = 120; + } + else + { + return false; + } + + if (IsA(linitial(inner_fn->args), FuncCall)) + { + base_fn = (FuncCall *)linitial(inner_fn->args); + if (list_length(base_fn->funcname) != 1 || + list_length(base_fn->args) != 1) + { + return false; + } + + base_name = strVal(linitial(base_fn->funcname)); + if (pg_strcasecmp(base_name, "nodes") == 0 || + pg_strcasecmp(base_name, "relationships") == 0) + { + *list_name = base_name; + *vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(base_fn->args)); + *mode = (pg_strcasecmp(base_name, "nodes") == 0 ? 6 : 4) + + mode_flag; + } + else + { + return false; + } + } + else + { + *vle_expr = get_current_vle_edge_expr(cpstate, + linitial(inner_fn->args)); + if (*vle_expr != NULL) + { + *list_name = "relationships"; + *mode = 4 + mode_flag; + } + } + + if (*vle_expr == NULL) + { + return false; + } + + *lower_expr = make_agtype_integer_const(index, exprLocation(indices->uidx)); + *upper_expr = make_agtype_integer_const(index + 1, + exprLocation(indices->uidx)); + + return true; } -static Node *transform_A_Indirection(cypher_parsestate *cpstate, - A_Indirection *a_ind) +static Node *try_transform_vle_path_tail_access(cypher_parsestate *cpstate, + A_Indirection *a_ind) { - ParseState *pstate = &cpstate->pstate; - int location; - ListCell *lc = NULL; - Node *ind_arg_expr = NULL; - FuncExpr *func_expr = NULL; - Oid func_access_oid = InvalidOid; - Oid func_slice_oid = InvalidOid; - List *args = NIL; - bool is_access = false; + FuncCall *tail_fn = NULL; + FuncCall *list_fn = NULL; + A_Indices *indices = NULL; + Expr *vle_expr = NULL; + Const *index_expr = NULL; + Oid func_oid; + char *list_name = NULL; + int64 tail_index; + + if (!IsA(a_ind->arg, FuncCall) || + list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) + { + return NULL; + } - /* validate that we have an indirection with at least 1 entry */ - Assert(a_ind != NULL && list_length(a_ind->indirection)); - /* get the agtype_access_operator function */ - func_access_oid = get_ag_func_oid("agtype_access_operator", 1, - AGTYPEARRAYOID); - /* get the agtype_access_slice function */ - func_slice_oid = get_ag_func_oid("agtype_access_slice", 3, AGTYPEOID, - AGTYPEOID, AGTYPEOID); + tail_fn = (FuncCall *)a_ind->arg; + if (list_length(tail_fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(tail_fn->funcname)), "tail") != 0 || + list_length(tail_fn->args) != 1) + { + return NULL; + } - /* - * If the indirection argument is a ColumnRef, we want to pull out the - * properties, as a var node, if possible. - */ - if (IsA(a_ind->arg, ColumnRef)) + indices = linitial(a_ind->indirection); + if (indices->is_slice || indices->uidx == NULL || + !get_nonnegative_integer_const(indices->uidx, &tail_index)) { - ColumnRef *cr = (ColumnRef *)a_ind->arg; + return NULL; + } - ind_arg_expr = transform_column_ref_for_indirection(cpstate, cr); + if (IsA(linitial(tail_fn->args), FuncCall)) + { + list_fn = (FuncCall *)linitial(tail_fn->args); + if (list_length(list_fn->funcname) != 1 || + list_length(list_fn->args) != 1) + { + return NULL; + } + + list_name = strVal(linitial(list_fn->funcname)); + if (pg_strcasecmp(list_name, "nodes") == 0 || + pg_strcasecmp(list_name, "relationships") == 0) + { + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(list_fn->args)); + } + else + { + return NULL; + } + } + else + { + vle_expr = get_current_vle_edge_expr(cpstate, linitial(tail_fn->args)); + if (vle_expr != NULL) + { + list_name = "relationships"; + } } - /* - * If we didn't get the properties from a ColumnRef, just transform the - * indirection argument. - */ - if (ind_arg_expr == NULL) + if (vle_expr == NULL) + { + return NULL; + } + + index_expr = make_agtype_integer_const(tail_index + 1, + exprLocation(indices->uidx)); + if (pg_strcasecmp(list_name, "nodes") == 0) { - ind_arg_expr = transform_cypher_expr_recurse(cpstate, a_ind->arg); + func_oid = get_age_materialize_vle_node_at_oid(); + } + else + { + func_oid = get_age_materialize_vle_edge_at_oid(); } - ind_arg_expr = coerce_to_common_type(pstate, ind_arg_expr, AGTYPEOID, - "A_indirection"); + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} - /* get the location of the expression */ - location = exprLocation(ind_arg_expr); +static Node *try_transform_vle_path_reverse_access(cypher_parsestate *cpstate, + A_Indirection *a_ind) +{ + FuncCall *reverse_fn = NULL; + FuncCall *list_fn = NULL; + A_Indices *indices = NULL; + Expr *vle_expr = NULL; + Const *index_expr = NULL; + Oid func_oid; + char *list_name = NULL; + int64 reverse_index; + + if (!IsA(a_ind->arg, FuncCall) || + list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) + { + return NULL; + } - /* add the expression as the first entry */ - args = lappend(args, ind_arg_expr); + reverse_fn = (FuncCall *)a_ind->arg; + if (list_length(reverse_fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(reverse_fn->funcname)), "reverse") != 0 || + list_length(reverse_fn->args) != 1) + { + return NULL; + } - /* iterate through the indirections */ - foreach (lc, a_ind->indirection) + indices = linitial(a_ind->indirection); + if (indices->is_slice || indices->uidx == NULL || + !get_nonnegative_integer_const(indices->uidx, &reverse_index)) { - Node *node = lfirst(lc); + return NULL; + } - /* is this a slice? */ - if (IsA(node, A_Indices) && ((A_Indices *)node)->is_slice) + if (IsA(linitial(reverse_fn->args), FuncCall)) + { + list_fn = (FuncCall *)linitial(reverse_fn->args); + if (list_length(list_fn->funcname) != 1 || + list_length(list_fn->args) != 1) { - A_Indices *indices = (A_Indices *)node; - - /* were we working on an access? if so, wrap and close it */ - if (is_access) - { - ArrayExpr *newa = make_agtype_array_expr(args); - - func_expr = makeFuncExpr(func_access_oid, AGTYPEOID, - list_make1(newa), - InvalidOid, InvalidOid, - COERCE_EXPLICIT_CALL); - - func_expr->funcvariadic = true; - func_expr->location = location; - - /* - * The result of this function is the input to the next access - * or slice operator. So we need to start out with a new arg - * list with this function expression. - */ - args = lappend(NIL, func_expr); + return NULL; + } - /* we are no longer working on an access */ - is_access = false; + list_name = strVal(linitial(list_fn->funcname)); + if (pg_strcasecmp(list_name, "nodes") == 0 || + pg_strcasecmp(list_name, "relationships") == 0) + { + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(list_fn->args)); + } + else + { + return NULL; + } + } + else + { + vle_expr = get_current_vle_edge_expr(cpstate, + linitial(reverse_fn->args)); + if (vle_expr != NULL) + { + list_name = "relationships"; + } + } - } + if (vle_expr == NULL) + { + return NULL; + } - /* add slice bounds to args */ - if (!indices->lidx) - { - A_Const *n = makeNode(A_Const); - n->isnull = true; - n->location = -1; - node = transform_cypher_expr_recurse(cpstate, (Node *)n); - } - else - { - node = transform_cypher_expr_recurse(cpstate, indices->lidx); - } + index_expr = make_agtype_integer_const(-reverse_index - 1, + exprLocation(indices->uidx)); + if (pg_strcasecmp(list_name, "nodes") == 0) + { + func_oid = get_age_materialize_vle_node_at_oid(); + } + else + { + func_oid = get_age_materialize_vle_edge_at_oid(); + } - args = lappend(args, node); + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} - if (!indices->uidx) - { - A_Const *n = makeNode(A_Const); - n->isnull = true; - n->location = -1; - node = transform_cypher_expr_recurse(cpstate, (Node *)n); - } - else - { - node = transform_cypher_expr_recurse(cpstate, indices->uidx); - } - args = lappend(args, node); +static Node *try_transform_vle_path_nested_transform_access( + cypher_parsestate *cpstate, A_Indirection *a_ind) +{ + Expr *vle_expr = NULL; + char *list_name = NULL; + Const *lower_expr = NULL; + Const *upper_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + int64 mode; + + if (!parse_vle_path_nested_transform_index(cpstate, a_ind, &vle_expr, + &list_name, &lower_expr, + &upper_expr, &mode)) + { + return NULL; + } - /* wrap and close it */ - func_expr = makeFuncExpr(func_slice_oid, AGTYPEOID, args, - InvalidOid, InvalidOid, - COERCE_EXPLICIT_CALL); - func_expr->location = location; + mode_expr = make_agtype_integer_const(mode, exprLocation((Node *)a_ind)); + func_oid = get_age_materialize_vle_slice_boundary_oid(); - /* - * The result of this function is the input to the next access - * or slice operator. So we need to start out with a new arg - * list with this function expression. - */ - args = lappend(NIL, func_expr); - } - /* is this a string or index?*/ - else if (IsA(node, String) || IsA(node, A_Indices)) - { - /* we are working on an access */ - is_access = true; + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make4(vle_expr, lower_expr, upper_expr, + mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} - /* is this an index? */ - if (IsA(node, A_Indices)) - { - A_Indices *indices = (A_Indices *)node; +static Node *try_transform_vle_path_nodes_access(cypher_parsestate *cpstate, + A_Indirection *a_ind) +{ + FuncCall *nodes_func = NULL; + A_Indices *indices = NULL; + Expr *vle_expr = NULL; + Node *index_expr = NULL; + Oid func_oid; + + if (!IsA(a_ind->arg, FuncCall) || + list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) + { + return NULL; + } - node = transform_cypher_expr_recurse(cpstate, indices->uidx); - args = lappend(args, node); - } - /* it must be a string */ - else - { - Const *const_str = makeConst(AGTYPEOID, -1, InvalidOid, -1, - string_to_agtype(strVal(node)), - false, false); - args = lappend(args, const_str); - } - } - /* not an indirection we understand */ - else - { - ereport(ERROR, - (errmsg("invalid indirection node %d", nodeTag(node)))); - } + indices = linitial(a_ind->indirection); + if (indices->is_slice || indices->uidx == NULL) + { + return NULL; } - /* if we were doing an access, we need wrap the args with access func. */ - if (is_access) + nodes_func = (FuncCall *)a_ind->arg; + if (list_length(nodes_func->funcname) != 1 || + pg_strcasecmp(strVal(linitial(nodes_func->funcname)), "nodes") != 0 || + list_length(nodes_func->args) != 1) { - ArrayExpr *newa = make_agtype_array_expr(args); + return NULL; + } - func_expr = makeFuncExpr(func_access_oid, AGTYPEOID, list_make1(newa), - InvalidOid, InvalidOid, - COERCE_EXPLICIT_CALL); - func_expr->funcvariadic = true; + vle_expr = get_current_single_vle_path_expr(cpstate, + linitial(nodes_func->args)); + if (vle_expr == NULL) + { + return NULL; } - Assert(func_expr != NULL); - func_expr->location = location; + index_expr = transform_cypher_expr_recurse(cpstate, indices->uidx); + func_oid = get_age_materialize_vle_node_at_oid(); - return (Node *)func_expr; + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); } -static Node *transform_cypher_string_match(cypher_parsestate *cpstate, - cypher_string_match *csm_node) +static Node *try_transform_vle_path_nodes_slice(cypher_parsestate *cpstate, + A_Indirection *a_ind) { - Node *expr; - FuncExpr *func_expr; - Oid func_access_oid; - List *args = NIL; - const char *func_name; + FuncCall *nodes_func = NULL; + A_Indices *indices = NULL; + Expr *vle_expr = NULL; + Node *lower_expr = NULL; + Node *upper_expr = NULL; + Oid func_oid; + + if (!IsA(a_ind->arg, FuncCall) || + list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) + { + return NULL; + } - switch (csm_node->operation) + indices = linitial(a_ind->indirection); + if (!indices->is_slice) { - case CSMO_STARTS_WITH: - func_name = "agtype_string_match_starts_with"; - break; - case CSMO_ENDS_WITH: - func_name = "agtype_string_match_ends_with"; - break; - case CSMO_CONTAINS: - func_name = "agtype_string_match_contains"; - break; + return NULL; + } - default: - ereport(ERROR, - (errmsg_internal("unknown Cypher string match operation"))); + nodes_func = (FuncCall *)a_ind->arg; + if (list_length(nodes_func->funcname) != 1 || + pg_strcasecmp(strVal(linitial(nodes_func->funcname)), "nodes") != 0 || + list_length(nodes_func->args) != 1) + { + return NULL; } - func_access_oid = get_ag_func_oid(func_name, 2, AGTYPEOID, AGTYPEOID); + vle_expr = get_current_single_vle_path_expr(cpstate, + linitial(nodes_func->args)); + if (vle_expr == NULL) + { + return NULL; + } - expr = transform_cypher_expr_recurse(cpstate, csm_node->lhs); - args = lappend(args, expr); - expr = transform_cypher_expr_recurse(cpstate, csm_node->rhs); - args = lappend(args, expr); + if (indices->lidx == NULL) + { + lower_expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); + } + else + { + lower_expr = transform_cypher_expr_recurse(cpstate, indices->lidx); + } - func_expr = makeFuncExpr(func_access_oid, AGTYPEOID, args, InvalidOid, - InvalidOid, COERCE_EXPLICIT_CALL); - func_expr->location = csm_node->location; + if (indices->uidx == NULL) + { + upper_expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); + } + else + { + upper_expr = transform_cypher_expr_recurse(cpstate, indices->uidx); + } - return (Node *)func_expr; + func_oid = get_age_materialize_vle_nodes_slice_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make3(vle_expr, lower_expr, upper_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); } -/* - * Function to create a typecasting node - */ -static Node *transform_cypher_typecast(cypher_parsestate *cpstate, - cypher_typecast *ctypecast) +static Node *try_transform_vle_path_list_slice(cypher_parsestate *cpstate, + A_Indirection *a_ind) { - List *fname; - FuncCall *fnode; - ParseState *pstate; - TypeName *target_typ; - - /* verify input parameter */ - Assert (cpstate != NULL); - Assert (ctypecast != NULL); + A_Indices *indices = NULL; + FuncCall *outer_fn = NULL; + FuncCall *list_fn = NULL; + Expr *vle_expr = NULL; + Node *lower_expr = NULL; + Node *upper_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + char *outer_name = NULL; + char *list_name = NULL; + int64 mode = 0; + + if (list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) + { + return NULL; + } - /* create the qualified function name, schema first */ - fname = list_make1(makeString("ag_catalog")); - pstate = &cpstate->pstate; - target_typ = ctypecast->typname; - - if (list_length(target_typ->names) == 1) + indices = linitial(a_ind->indirection); + if (!indices->is_slice) { - char *typecast = strVal(linitial(target_typ->names)); + return NULL; + } - /* append the name of the requested typecast function */ - if (pg_strcasecmp(typecast, "edge") == 0) - { - fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_EDGE)); - } - else if (pg_strcasecmp(typecast, "path") == 0) - { - fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_PATH)); - } - else if (pg_strcasecmp(typecast, "vertex") == 0) - { - fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_VERTEX)); - } - else if (pg_strcasecmp(typecast, "numeric") == 0) - { - fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_NUMERIC)); - } - else if (pg_strcasecmp(typecast, "float") == 0) + if (!IsA(a_ind->arg, FuncCall)) + { + vle_expr = get_current_vle_edge_expr(cpstate, a_ind->arg); + if (vle_expr == NULL) { - fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_FLOAT)); + return NULL; } - else if (pg_strcasecmp(typecast, "int") == 0 || - pg_strcasecmp(typecast, "integer") == 0) + + mode = 0; + goto build_slice; + } + + outer_fn = (FuncCall *)a_ind->arg; + if (list_length(outer_fn->funcname) != 1 || + list_length(outer_fn->args) != 1) + { + return NULL; + } + + outer_name = strVal(linitial(outer_fn->funcname)); + if ((pg_strcasecmp(outer_name, "tail") != 0 && + pg_strcasecmp(outer_name, "reverse") != 0)) + { + return NULL; + } + + if (IsA(linitial(outer_fn->args), FuncCall)) + { + list_fn = (FuncCall *)linitial(outer_fn->args); + if (list_length(list_fn->funcname) != 1 || + list_length(list_fn->args) != 1) { - fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_INT)); + return NULL; } - else if (pg_strcasecmp(typecast, "pg_float8") == 0) + + list_name = strVal(linitial(list_fn->funcname)); + if (pg_strcasecmp(outer_name, "tail") == 0 && + pg_strcasecmp(list_name, "tail") == 0) { - fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_PG_FLOAT8)); + if (IsA(linitial(list_fn->args), FuncCall)) + { + FuncCall *base_fn = (FuncCall *)linitial(list_fn->args); + char *base_name = NULL; + + if (list_length(base_fn->funcname) != 1 || + list_length(base_fn->args) != 1) + { + return NULL; + } + + base_name = strVal(linitial(base_fn->funcname)); + if (pg_strcasecmp(base_name, "nodes") != 0 && + pg_strcasecmp(base_name, "relationships") != 0) + { + return NULL; + } + + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(base_fn->args)); + mode = pg_strcasecmp(base_name, "nodes") == 0 ? 7 : 6; + } + else + { + vle_expr = get_current_vle_edge_expr( + cpstate, linitial(list_fn->args)); + mode = 6; + } + + goto check_vle_expr; } - else if (pg_strcasecmp(typecast, "pg_bigint") == 0) + + if ((pg_strcasecmp(outer_name, "tail") == 0 && + pg_strcasecmp(list_name, "reverse") == 0) || + (pg_strcasecmp(outer_name, "reverse") == 0 && + pg_strcasecmp(list_name, "tail") == 0)) { - fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_PG_BIGINT)); + bool tail_reverse = pg_strcasecmp(outer_name, "tail") == 0; + + if (IsA(linitial(list_fn->args), FuncCall)) + { + FuncCall *base_fn = (FuncCall *)linitial(list_fn->args); + char *base_name = NULL; + bool node_list = false; + + if (list_length(base_fn->funcname) != 1 || + list_length(base_fn->args) != 1) + { + return NULL; + } + + base_name = strVal(linitial(base_fn->funcname)); + if (pg_strcasecmp(base_name, "nodes") != 0 && + pg_strcasecmp(base_name, "relationships") != 0) + { + return NULL; + } + + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(base_fn->args)); + node_list = pg_strcasecmp(base_name, "nodes") == 0; + mode = tail_reverse ? (node_list ? 9 : 8) : + (node_list ? 11 : 10); + } + else + { + vle_expr = get_current_vle_edge_expr( + cpstate, linitial(list_fn->args)); + mode = tail_reverse ? 8 : 10; + } + + goto check_vle_expr; } - else if ((pg_strcasecmp(typecast, "bool") == 0 || - pg_strcasecmp(typecast, "boolean") == 0)) + + if (pg_strcasecmp(list_name, "nodes") != 0 && + pg_strcasecmp(list_name, "relationships") != 0) { - fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_BOOL)); + return NULL; } - else if (pg_strcasecmp(typecast, "pg_text") == 0) + + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(list_fn->args)); + if (pg_strcasecmp(outer_name, "tail") == 0) { - fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_PG_TEXT)); + mode = pg_strcasecmp(list_name, "nodes") == 0 ? 3 : 2; } else { - goto fallback_coercion; + mode = pg_strcasecmp(list_name, "nodes") == 0 ? 5 : 4; + } + } + else + { + vle_expr = get_current_vle_edge_expr(cpstate, + linitial(outer_fn->args)); + if (vle_expr == NULL) + { + return NULL; } - /* make a function call node */ - fnode = makeFuncCall(fname, list_make1(ctypecast->expr), COERCE_SQL_SYNTAX, - ctypecast->location); - - /* return the transformed function */ - return transform_FuncCall(cpstate, fnode); + mode = pg_strcasecmp(outer_name, "tail") == 0 ? 2 : 4; } -fallback_coercion: +check_vle_expr: + if (vle_expr == NULL) { - Oid source_oid; - Oid target_oid; - int32 t_typmod = -1; - Node *expr; + return NULL; + } - /* transform the expr before casting */ - expr = transform_cypher_expr_recurse(cpstate, - ctypecast->expr); +build_slice: + if (indices->lidx == NULL) + { + lower_expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); + } + else + { + lower_expr = transform_cypher_expr_recurse(cpstate, indices->lidx); + } - typenameTypeIdAndMod(pstate, target_typ, &target_oid, &t_typmod); - source_oid = exprType(expr); + if (indices->uidx == NULL) + { + upper_expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); + } + else + { + upper_expr = transform_cypher_expr_recurse(cpstate, indices->uidx); + } - /* errors out if cast not possible */ - expr = coerce_expr_flexible(pstate, expr, source_oid, target_oid, - t_typmod, true); + mode_expr = make_agtype_integer_const(mode, exprLocation((Node *)a_ind)); + func_oid = get_age_materialize_vle_list_slice_oid(); - return expr; - } + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make4(vle_expr, lower_expr, upper_expr, + mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); } -/* - * Helper function to coerce an expression to the target type. If - * no direct cast exists, it attempts to cast through text if the - * source or target type is agtype. This improves interoperability - * with types from other extensions. - */ -static Node *coerce_expr_flexible(ParseState *pstate, Node *expr, - Oid source_oid, Oid target_oid, - int32 t_typmod, bool error_out) +static Node *try_transform_vle_path_boundary_id_access( + cypher_parsestate *cpstate, A_Indirection *a_ind) { - const Oid text_oid = TEXTOID; - Node *result; - - if (expr == NULL) + FuncCall *outer_fn = NULL; + FuncCall *inner_fn = NULL; + FuncCall *list_fn = NULL; + char *outer_name = NULL; + char *inner_name = NULL; + char *list_name = NULL; + Expr *vle_expr = NULL; + Const *index_expr = NULL; + Oid func_oid; + int64 index; + + if (!IsA(a_ind->arg, FuncCall) || + list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), String) || + pg_strcasecmp(strVal(linitial(a_ind->indirection)), "id") != 0) + { return NULL; + } - /* Try a direct cast */ - result = coerce_to_target_type(pstate, expr, source_oid, target_oid, - t_typmod, COERCION_EXPLICIT, - COERCE_EXPLICIT_CAST, -1); - if (result != NULL) - return result; - - /* Try cast via TEXT if either side is AGTYPE */ - if (source_oid == AGTYPEOID || target_oid == AGTYPEOID) + outer_fn = (FuncCall *)a_ind->arg; + if (list_length(outer_fn->funcname) != 1 || + list_length(outer_fn->args) != 1 || + !IsA(linitial(outer_fn->args), FuncCall)) { - Node *to_text = coerce_to_target_type(pstate, expr, source_oid, text_oid, - -1, COERCION_EXPLICIT, - COERCE_EXPLICIT_CAST, -1); - if (to_text != NULL) - { - result = coerce_to_target_type(pstate, to_text, text_oid, target_oid, - t_typmod, COERCION_EXPLICIT, - COERCE_EXPLICIT_CAST, -1); - if (result != NULL) - return result; - } + return NULL; } - if (error_out) + outer_name = strVal(linitial(outer_fn->funcname)); + if (pg_strcasecmp(outer_name, "head") != 0 && + pg_strcasecmp(outer_name, "last") != 0) { - ereport(ERROR, - (errmsg_internal("typecast \'%s\' not supported", - format_type_be(target_oid)))); + return NULL; } - return NULL; -} - -static Node *transform_external_ext_FuncCall(cypher_parsestate *cpstate, - FuncCall *fn, List *targs, - Form_pg_proc procform, - char *extension) -{ - ParseState *pstate = &cpstate->pstate; - FuncExpr *fexpr = NULL; - Node *retval = NULL; - Node *last_srf = pstate->p_last_srf; - Oid *proargtypes; + inner_fn = (FuncCall *)linitial(outer_fn->args); + if (list_length(inner_fn->funcname) != 1 || + list_length(inner_fn->args) != 1) + { + return NULL; + } - /* make sure procform in not NULL */ - Assert(procform != NULL); - proargtypes = procform->proargtypes.values; + inner_name = strVal(linitial(inner_fn->funcname)); + if (pg_strcasecmp(inner_name, "nodes") == 0 || + pg_strcasecmp(inner_name, "relationships") == 0) + { + list_name = inner_name; + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(inner_fn->args)); + index = pg_strcasecmp(outer_name, "head") == 0 ? 0 : -1; + } + else if ((pg_strcasecmp(inner_name, "tail") == 0 || + pg_strcasecmp(inner_name, "reverse") == 0) && + IsA(linitial(inner_fn->args), FuncCall)) + { + list_fn = (FuncCall *)linitial(inner_fn->args); + if (list_length(list_fn->funcname) != 1 || + list_length(list_fn->args) != 1) + { + return NULL; + } - /* cast the agtype arguments to the types accepted by function */ - targs = cast_agtype_args_to_target_type(cpstate, procform, targs, proargtypes); + list_name = strVal(linitial(list_fn->funcname)); + if (pg_strcasecmp(list_name, "nodes") != 0 && + pg_strcasecmp(list_name, "relationships") != 0) + { + return NULL; + } - /* now get the function node for the external function */ - fexpr = (FuncExpr *)ParseFuncOrColumn(pstate, fn->funcname, targs, - last_srf, fn, false, - fn->location); + if (pg_strcasecmp(inner_name, "tail") == 0) + { + if (pg_strcasecmp(outer_name, "head") != 0) + { + return NULL; + } + index = 1; + } + else + { + index = pg_strcasecmp(outer_name, "head") == 0 ? -1 : 0; + } - /* - * This will cast TEXT output to AGTYPE. It will error out if this is - * not possible to do. For TEXT to AGTYPE we need to wrap the output - * due to issues with creating a cast from TEXT to AGTYPE. - */ - if (fexpr->funcresulttype == TEXTOID) - { - retval = wrap_text_output_to_agtype(cpstate, fexpr); + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(list_fn->args)); } else { - retval = (Node *)fexpr; + return NULL; } - /* additional casts or wraps can be done here for other types */ + if (vle_expr == NULL) + { + return NULL; + } - /* flag that an aggregate was found during a transform */ - if (retval != NULL && retval->type == T_Aggref) + index_expr = make_agtype_integer_const(index, outer_fn->location); + if (pg_strcasecmp(list_name, "nodes") == 0) { - cpstate->exprHasAgg = true; + func_oid = get_age_vle_node_id_at_oid(); + } + else + { + func_oid = get_age_vle_edge_id_at_oid(); } - /* we can just return it here */ - return retval; + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); } -/* - * Cast a function's input parameter list from agtype to that function's input - * type. This is used for functions that don't take agtype as input and where - * there isn't an implicit cast to do this for us. - */ -static List *cast_agtype_args_to_target_type(cypher_parsestate *cpstate, - Form_pg_proc procform, - List *fargs, - Oid *target_types) +static FuncExpr *make_vle_index_properties_expr(cypher_parsestate *cpstate, + A_Indirection *indexed_arg, + int location) { - char *funcname = NameStr(procform->proname); - int nargs = procform->pronargs; - ListCell *lc = NULL; + A_Indices *indices = NULL; + Expr *vle_expr = NULL; + Node *index_expr = NULL; + Const *lower_expr = NULL; + Const *upper_expr = NULL; + Const *mode_expr = NULL; + char *list_name = NULL; + Oid func_oid; + int64 mode; + + if (list_length(indexed_arg->indirection) != 1 || + !IsA(linitial(indexed_arg->indirection), A_Indices)) + { + return NULL; + } - /* verify the length of args are same */ - if (list_length(fargs) != nargs) + indices = linitial(indexed_arg->indirection); + if (indices->is_slice || indices->uidx == NULL) { - ereport(ERROR, - (errcode(ERRCODE_DATA_EXCEPTION), - errmsg("function %s requires %d arguments, %d given", - funcname, nargs, list_length(fargs)))); + return NULL; } - /* iterate through the function's args */ - foreach (lc, fargs) + if (parse_vle_path_nested_transform_index(cpstate, indexed_arg, &vle_expr, + &list_name, &lower_expr, + &upper_expr, &mode)) { - Node *expr = lfirst(lc); - Oid source_oid = exprType(expr); - Oid target_oid = target_types[foreach_current_index(lc)]; + mode_expr = make_agtype_integer_const(mode + 32, location); + func_oid = get_age_materialize_vle_slice_boundary_oid(); + return makeFuncExpr(func_oid, AGTYPEOID, + list_make4(vle_expr, lower_expr, upper_expr, + mode_expr), + InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); + } - /* errors out if cast not possible */ - expr = coerce_expr_flexible(&cpstate->pstate, expr, source_oid, - target_oid, -1, true); + vle_expr = get_current_vle_edge_expr(cpstate, indexed_arg->arg); + if (vle_expr != NULL) + { + index_expr = transform_cypher_expr_recurse(cpstate, indices->uidx); + func_oid = get_age_vle_edge_properties_at_oid(); + return makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); + } - lfirst(lc) = expr; + if (!parse_vle_path_indexed_list_index(cpstate, indexed_arg, &vle_expr, + &list_name, &index_expr)) + { + return NULL; } - return fargs; + if (pg_strcasecmp(list_name, "nodes") == 0) + { + func_oid = get_age_vle_node_properties_at_oid(); + } + else + { + func_oid = get_age_vle_edge_properties_at_oid(); + } + + return makeFuncExpr(func_oid, AGTYPEOID, list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); } -/* - * Due to issues with creating a cast from text to agtype, we need to wrap a - * function that outputs text with text_to_agtype. - */ -static Node *wrap_text_output_to_agtype(cypher_parsestate *cpstate, - FuncExpr *fexpr) +static Node *try_transform_vle_path_boundary_property_access( + cypher_parsestate *cpstate, A_Indirection *a_ind) { - ParseState *pstate = &cpstate->pstate; - Node *last_srf = pstate->p_last_srf; - Node *retval = NULL; - List *fname = NIL; - FuncCall *fnode = NULL; - - if (fexpr->funcresulttype != TEXTOID) + FuncExpr *properties_expr = NULL; + FuncExpr *access_expr = NULL; + List *args = NIL; + ListCell *lc = NULL; + Expr *vle_expr = NULL; + Node *index_expr = NULL; + Const *mode_expr = NULL; + Oid properties_func_oid; + Oid access_func_oid; + char *list_name = NULL; + int64 index; + bool start_endpoint = false; + bool endpoint_index = false; + bool tail_last = false; + bool tail_last_endpoint = false; + bool skip_first_indirection = false; + + if (list_length(a_ind->indirection) < 1) { - ereport(ERROR, - (errcode(ERRCODE_DATA_EXCEPTION), - errmsg("can only wrap text to agtype"))); + return NULL; } - /* make a function call node to cast text to agtype */ - fname = list_make2(makeString("ag_catalog"), makeString("text_to_agtype")); - - /* the input function is the arg to the new function (wrapper) */ - fnode = makeFuncCall(fname, list_make1(fexpr), COERCE_SQL_SYNTAX, -1); + if (IsA(a_ind->arg, A_Indirection)) + { + A_Indirection *nested_arg = (A_Indirection *)a_ind->arg; - /* ... and hand off to ParseFuncOrColumn to create it */ - retval = ParseFuncOrColumn(pstate, fname, list_make1(fexpr), last_srf, - fnode, false, -1); + properties_expr = make_vle_index_properties_expr( + cpstate, nested_arg, exprLocation((Node *)nested_arg)); + if (properties_expr != NULL) + { + goto build_access; + } - /* return the wrapped function */ - return retval; -} + return NULL; + } -/* - * Returns Form_pg_proc struct for given function, if the function - * is not in search path, it is not considered. - */ -static Form_pg_proc get_procform(FuncCall *fn, bool err_not_found) -{ - CatCList *catlist = NULL; - Form_pg_proc procform = NULL; - int nargs; - int i = 0; - List *asp; - bool found = false; - char *funcname = (((String*)linitial(fn->funcname))->sval); + if (list_length(a_ind->indirection) > 1 && + IsA(linitial(a_ind->indirection), A_Indices)) + { + A_Indirection indexed_arg; - /* get a list of matching functions */ - catlist = SearchSysCacheList1(PROCNAMEARGSNSP, CStringGetDatum(funcname)); + MemSet(&indexed_arg, 0, sizeof(A_Indirection)); + indexed_arg.type = T_A_Indirection; + indexed_arg.arg = a_ind->arg; + indexed_arg.indirection = list_make1(linitial(a_ind->indirection)); - if (catlist->n_members == 0) + properties_expr = make_vle_index_properties_expr( + cpstate, &indexed_arg, exprLocation((Node *)a_ind)); + if (properties_expr != NULL) + { + skip_first_indirection = true; + goto build_access; + } + } + + if (!IsA(a_ind->arg, FuncCall)) { - ReleaseSysCacheList(catlist); return NULL; } - asp = fetch_search_path(false); - nargs = list_length(fn->args); - - /* iterate through them and verify that they are in the search path */ - for (i = 0; i < catlist->n_members; i++) + if (IsA(a_ind->arg, FuncCall)) { - ListCell *nsp; - HeapTuple proctup = &catlist->members[i]->tuple; - procform = (Form_pg_proc) GETSTRUCT(proctup); + FuncCall *arg_fn = (FuncCall *)a_ind->arg; - /* - * Check if the function name, number of arguments, and - * variadic match before checking if it is in the search - * path. - */ - if (pg_strcasecmp(funcname, procform->proname.data) == 0 && - nargs == procform->pronargs && - fn->func_variadic == procform->provariadic) + if (list_length(arg_fn->funcname) == 1 && + list_length(arg_fn->args) == 1 && + (IsA(linitial(arg_fn->args), FuncCall) || + IsA(linitial(arg_fn->args), A_Indirection))) { - foreach(nsp, asp) + char *endpoint_name = strVal(linitial(arg_fn->funcname)); + + if (pg_strcasecmp(endpoint_name, "startNode") == 0 || + pg_strcasecmp(endpoint_name, "endNode") == 0) { - Oid oid = lfirst_oid(nsp); + int64 mode_offset = + pg_strcasecmp(endpoint_name, "startNode") == 0 ? 104 : 112; - if (procform->pronamespace == oid && - isTempNamespace(procform->pronamespace) == false) + if (IsA(linitial(arg_fn->args), FuncCall)) { - found = true; - break; + properties_expr = + (FuncExpr *)transform_vle_path_nested_transform_head_last( + cpstate, (FuncCall *)linitial(arg_fn->args), + mode_offset); + if (properties_expr != NULL) + { + goto build_access; + } + + properties_expr = + (FuncExpr *)transform_vle_path_slice_head_last( + cpstate, (FuncCall *)linitial(arg_fn->args), + mode_offset); + if (properties_expr != NULL) + { + goto build_access; + } + } + else + { + A_Indirection *endpoint_arg = linitial(arg_fn->args); + Const *lower_expr = NULL; + Const *upper_expr = NULL; + Const *nested_mode_expr = NULL; + int64 nested_mode; + + if (parse_vle_path_nested_transform_index( + cpstate, endpoint_arg, &vle_expr, &list_name, + &lower_expr, &upper_expr, &nested_mode) && + pg_strcasecmp(list_name, "relationships") == 0) + { + nested_mode_expr = make_agtype_integer_const( + nested_mode + mode_offset, + exprLocation((Node *)endpoint_arg)); + properties_func_oid = + get_age_materialize_vle_slice_boundary_oid(); + properties_expr = makeFuncExpr( + properties_func_oid, AGTYPEOID, + list_make4(vle_expr, lower_expr, upper_expr, + nested_mode_expr), + InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); + goto build_access; + } } } } + } - if (found) - { - break; - } + properties_expr = + (FuncExpr *)transform_vle_path_nested_transform_head_last( + cpstate, (FuncCall *)a_ind->arg, 32); + if (properties_expr != NULL) + { + goto build_access; + } - /* reset procform */ - procform = NULL; + properties_expr = (FuncExpr *)transform_vle_path_slice_head_last( + cpstate, (FuncCall *)a_ind->arg, 32); + if (properties_expr != NULL) + { + goto build_access; } - /* Error out if function not found */ - if (err_not_found && (procform == NULL)) + tail_last_endpoint = parse_vle_tail_last_endpoint(cpstate, a_ind->arg, + &vle_expr, + &start_endpoint); + if (!tail_last_endpoint) { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_FUNCTION), - errmsg("function %s does not exist", funcname), - errhint("If the function is from an external extension, " - "make sure the extension is installed and the " - "function is in the search path."))); + endpoint_index = parse_vle_edge_endpoint_index(cpstate, a_ind->arg, + &vle_expr, &index_expr, + &start_endpoint); + } + if (!tail_last_endpoint && !endpoint_index) + { + tail_last = parse_vle_path_tail_last_list(cpstate, a_ind->arg, + &vle_expr, &list_name); + } + if (!tail_last_endpoint && !endpoint_index && !tail_last && + !parse_vle_path_boundary_list_index(cpstate, a_ind->arg, &vle_expr, + &list_name, &index)) + { + return NULL; } - /* we need to release the cache list */ - ReleaseSysCacheList(catlist); - pfree_if_not_null(asp); + if (tail_last_endpoint) + { + int64 mode = start_endpoint ? 4 : 5; + + mode_expr = make_agtype_integer_const(mode, exprLocation(a_ind->arg)); + properties_func_oid = get_age_vle_tail_last_endpoint_field_oid(); + properties_expr = makeFuncExpr(properties_func_oid, AGTYPEOID, + list_make2(vle_expr, mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + } + else if (endpoint_index) + { + int64 mode = start_endpoint ? 4 : 5; + + mode_expr = make_agtype_integer_const(mode, exprLocation(a_ind->arg)); + properties_func_oid = get_age_vle_edge_endpoint_field_at_oid(); + properties_expr = makeFuncExpr(properties_func_oid, AGTYPEOID, + list_make3(vle_expr, index_expr, + mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + } + else if (tail_last) + { + int64 mode = pg_strcasecmp(list_name, "nodes") == 0 ? 2 : 4; + + mode_expr = make_agtype_integer_const(mode, exprLocation(a_ind->arg)); + properties_func_oid = get_age_vle_tail_last_field_oid(); + properties_expr = makeFuncExpr(properties_func_oid, AGTYPEOID, + list_make2(vle_expr, mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + } + else if (pg_strcasecmp(list_name, "nodes") == 0) + { + index_expr = (Node *)make_agtype_integer_const( + index, exprLocation(a_ind->arg)); + properties_func_oid = get_age_vle_node_properties_at_oid(); + properties_expr = makeFuncExpr(properties_func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + } + else + { + index_expr = (Node *)make_agtype_integer_const( + index, exprLocation(a_ind->arg)); + properties_func_oid = get_age_vle_edge_properties_at_oid(); + properties_expr = makeFuncExpr(properties_func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + } - return procform; -} +build_access: + properties_expr->location = exprLocation(a_ind->arg); -static char *get_mapped_extension(Oid func_oid) -{ - Oid extension_oid; - char *extension = NULL; + args = lappend(args, properties_expr); + foreach (lc, a_ind->indirection) + { + Node *node = lfirst(lc); - extension_oid = getExtensionOfObject(ProcedureRelationId, func_oid); - extension = get_extension_name(extension_oid); + if (skip_first_indirection) + { + skip_first_indirection = false; + continue; + } + + if (IsA(node, String)) + { + Const *const_str = makeConst(AGTYPEOID, -1, InvalidOid, -1, + string_to_agtype(strVal(node)), + false, false); + + args = lappend(args, const_str); + } + else if (IsA(node, A_Indices)) + { + A_Indices *indices = (A_Indices *)node; + + if (indices->is_slice || indices->uidx == NULL) + { + return NULL; + } + args = lappend(args, + transform_cypher_expr_recurse(cpstate, + indices->uidx)); + } + else + { + return NULL; + } + } + + access_func_oid = get_agtype_access_operator_oid(); + access_expr = makeFuncExpr(access_func_oid, AGTYPEOID, + list_make1(make_agtype_array_expr(args)), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + access_expr->funcvariadic = true; + access_expr->location = exprLocation(a_ind->arg); - return extension; + return (Node *)access_expr; } -static bool is_extension_external(char *extension) +static Node *try_transform_vle_path_relationships_access( + cypher_parsestate *cpstate, A_Indirection *a_ind) { - return ((extension != NULL) && - (pg_strcasecmp(extension, "age") != 0)); + FuncCall *relationships_func = NULL; + A_Indices *indices = NULL; + Expr *vle_expr = NULL; + Node *index_expr = NULL; + Oid func_oid; + + if (!IsA(a_ind->arg, FuncCall) || + list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) + { + return NULL; + } + + indices = linitial(a_ind->indirection); + if (indices->is_slice || indices->uidx == NULL) + { + return NULL; + } + + relationships_func = (FuncCall *)a_ind->arg; + if (list_length(relationships_func->funcname) != 1 || + pg_strcasecmp(strVal(linitial(relationships_func->funcname)), + "relationships") != 0 || + list_length(relationships_func->args) != 1) + { + return NULL; + } + + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(relationships_func->args)); + if (vle_expr == NULL) + { + return NULL; + } + + index_expr = transform_cypher_expr_recurse(cpstate, indices->uidx); + func_oid = get_age_materialize_vle_edge_at_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); } -/* Returns age_ prefiexed lower case function name */ -static char *construct_age_function_name(char *funcname) +static Node *try_transform_vle_edge_reverse_access(cypher_parsestate *cpstate, + A_Indirection *a_ind) { - int pnlen = strlen(funcname); - char *ag_name = palloc(pnlen + 5); - int i; + FuncCall *reverse_fn = NULL; + A_Indices *indices = NULL; + Expr *vle_expr = NULL; + Node *index_expr = NULL; + Oid func_oid; + int64 reverse_index; + + if (!IsA(a_ind->arg, FuncCall) || + list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) + { + return NULL; + } - /* copy in the prefix - all AGE functions are prefixed with age_ */ - strncpy(ag_name, "age_", 4); + indices = linitial(a_ind->indirection); + if (indices->is_slice || indices->uidx == NULL) + { + return NULL; + } - /* - * All AGE function names are in lower case. So, copy in the funcname - * in lower case. - */ - for (i = 0; i < pnlen; i++) + reverse_fn = (FuncCall *)a_ind->arg; + if (list_length(reverse_fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(reverse_fn->funcname)), "reverse") != 0 || + list_length(reverse_fn->args) != 1) { - ag_name[i + 4] = tolower(funcname[i]); + return NULL; } - /* terminate it with 0 */ - ag_name[i + 4] = 0; + vle_expr = get_current_vle_edge_expr(cpstate, linitial(reverse_fn->args)); + if (vle_expr == NULL) + { + return NULL; + } - return ag_name; -} + if (get_nonnegative_integer_const(indices->uidx, &reverse_index)) + { + index_expr = (Node *)make_agtype_integer_const( + -reverse_index - 1, exprLocation(indices->uidx)); + func_oid = get_age_materialize_vle_edge_at_oid(); + } + else + { + index_expr = transform_cypher_expr_recurse(cpstate, indices->uidx); + func_oid = get_age_materialize_vle_edge_reversed_at_oid(); + } + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} -/* - * Checks if a function exists. If the extension name is given, - * then it checks if the function exists in that extension. - */ -static bool function_exists(char *funcname, char *extension) +static Node *try_transform_vle_path_relationships_slice( + cypher_parsestate *cpstate, A_Indirection *a_ind) { - CatCList *catlist = NULL; - bool found = false; - int i = 0; + FuncCall *relationships_func = NULL; + A_Indices *indices = NULL; + Expr *vle_expr = NULL; + Node *lower_expr = NULL; + Node *upper_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + + if (!IsA(a_ind->arg, FuncCall) || + list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) + { + return NULL; + } - /* get a list of matching functions */ - catlist = SearchSysCacheList1(PROCNAMEARGSNSP, CStringGetDatum(funcname)); + indices = linitial(a_ind->indirection); + if (!indices->is_slice) + { + return NULL; + } - if (catlist->n_members == 0) + relationships_func = (FuncCall *)a_ind->arg; + if (list_length(relationships_func->funcname) != 1 || + pg_strcasecmp(strVal(linitial(relationships_func->funcname)), + "relationships") != 0 || + list_length(relationships_func->args) != 1) { - ReleaseSysCacheList(catlist); - return false; + return NULL; } - else if (extension == NULL) + + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(relationships_func->args)); + if (vle_expr == NULL) { - ReleaseSysCacheList(catlist); - return true; + return NULL; } - for (i = 0; i < catlist->n_members; i++) + if (indices->lidx == NULL) { - HeapTuple proctup = &catlist->members[i]->tuple; - Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup); - char *ext = get_mapped_extension(procform->oid); + lower_expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); + } + else + { + lower_expr = transform_cypher_expr_recurse(cpstate, indices->lidx); + } - if (ext != NULL && pg_strcasecmp(ext, extension) == 0) - { - found = true; - break; - } + if (indices->uidx == NULL) + { + upper_expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); + } + else + { + upper_expr = transform_cypher_expr_recurse(cpstate, indices->uidx); } - /* we need to release the cache list */ - ReleaseSysCacheList(catlist); + mode_expr = make_agtype_integer_const(0, exprLocation((Node *)a_ind)); + func_oid = get_age_materialize_vle_list_slice_oid(); - return found; + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make4(vle_expr, lower_expr, upper_expr, + mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); } /* @@ -2006,6 +8424,182 @@ static Node *transform_FuncCall(cypher_parsestate *cpstate, FuncCall *fn) ListCell *arg; Node *retval = NULL; + retval = try_transform_vle_path_endpoint_id_access(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_nested_transform_index_endpoint_id_access( + cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_boundary_endpoint_id_access(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_slice_boundary_endpoint_id_access(cpstate, + fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_edge_variable_field(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_endpoint_field(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_nested_transform_index_endpoint_field( + cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_slice_boundary_endpoint_field(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_tail_last_endpoint_field(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_node_field(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_boundary_field(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_slice_boundary_field(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_nested_transform_index_field(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_edge_label(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_edge_properties(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_endpoint_access(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_nested_transform_index_endpoint_access( + cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_boundary_endpoint_access(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_slice_boundary_endpoint_access(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_slice_boundary_id_function(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_nested_transform_index_id_function(cpstate, + fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_boundary_id_function(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_id_access(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_length(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_slice_size(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_size(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_slice_is_empty(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_is_empty(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_slice_head_last(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_head_last(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_nested_transform_head_last(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_list_tail_reverse(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_relationships(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_nodes(cpstate, fn); + if (retval != NULL) + { + return retval; + } + /* Transform the list of arguments ... */ foreach(arg, fn->args) { @@ -2019,7 +8613,7 @@ static Node *transform_FuncCall(cypher_parsestate *cpstate, FuncCall *fn) Assert(!fn->agg_within_group); /* If it is a qualified function call, let it through. */ - if (list_length(fn->funcname) > 1) + if (lnext(fn->funcname, list_head(fn->funcname)) != NULL) { fname = fn->funcname; } @@ -2030,7 +8624,8 @@ static Node *transform_FuncCall(cypher_parsestate *cpstate, FuncCall *fn) else { char *name = strVal(linitial(fn->funcname)); - char *ag_name = construct_age_function_name(name); + int name_len = strlen(name); + char *ag_name = construct_age_function_name(name, name_len); if (function_exists(ag_name, "age")) { @@ -2043,11 +8638,8 @@ static Node *transform_FuncCall(cypher_parsestate *cpstate, FuncCall *fn) * and vle. So, check for those 3 functions here and that the arg list * is not empty. Then prepend the graph name if necessary. */ - if ((list_length(targs) != 0) && - (strcmp("startNode", name) == 0 || - strcmp("endNode", name) == 0 || - strcmp("vle", name) == 0 || - strcmp("vertex_stats", name) == 0)) + if ((targs != NIL) && + function_needs_graph_name_argument(name, name_len)) { char *graph_name = cpstate->graph_name; Datum d = string_to_agtype(graph_name); @@ -2061,10 +8653,24 @@ static Node *transform_FuncCall(cypher_parsestate *cpstate, FuncCall *fn) * If it's not in age, check if it's a potential call to some function * in another installed extension. */ - else if(function_exists(name, NULL)) + else { - Form_pg_proc procform = get_procform(fn, true); - char *extension = get_mapped_extension(procform->oid); + Form_pg_proc procform = get_procform(fn, false); + const char *extension; + + pfree(ag_name); + + if (procform == NULL) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("function %s does not exist", name), + errhint("If the function is from an external extension, " + "make sure the extension is installed and the " + "function is in the search path."))); + } + + extension = get_mapped_extension(procform->oid); /* * If the function is from another extension, transform @@ -2082,19 +8688,10 @@ static Node *transform_FuncCall(cypher_parsestate *cpstate, FuncCall *fn) */ else { + pfree(procform); fname = fn->funcname; } } - /* no function found */ - else - { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_FUNCTION), - errmsg("function %s does not exist", name), - errhint("If the function is from an external extension, " - "make sure the extension is installed and the " - "function is in the search path."))); - } } /* ... and hand off to ParseFuncOrColumn */ diff --git a/src/backend/parser/cypher_gram.y b/src/backend/parser/cypher_gram.y index c614e1dbe..e76251fcb 100644 --- a/src/backend/parser/cypher_gram.y +++ b/src/backend/parser/cypher_gram.y @@ -257,6 +257,7 @@ static Node *make_typecast_expr(Node *expr, Node *typname, int location); static Node *make_function_expr(List *func_name, List *exprs, int location); static Node *make_star_function_expr(List *func_name, List *exprs, int location); static Node *make_distinct_function_expr(List *func_name, List *exprs, int location); +static bool is_count_function_name(const char *name); static FuncCall *node_to_agtype(Node* fnode, char *type, int location); /* setops */ @@ -2823,7 +2824,7 @@ static Node *make_function_expr(List *func_name, List *exprs, int location) * functions. We may want to find a better way to do this, as there * could be many. */ - if (pg_strcasecmp(name, "count") == 0) + if (is_count_function_name(name)) { funcname = SystemFuncName("count"); @@ -2878,7 +2879,7 @@ static Node *make_star_function_expr(List *func_name, List *exprs, int location) * functions. We may want to find a better way to do this, as there * could be many. */ - if (pg_strcasecmp(name, "count") == 0) + if (is_count_function_name(name)) { funcname = SystemFuncName("count"); @@ -2935,7 +2936,7 @@ static Node *make_distinct_function_expr(List *func_name, List *exprs, int locat * functions. We may want to find a better way to do this, as there * could be many. */ - if (pg_strcasecmp(name, "count") == 0) + if (is_count_function_name(name)) { funcname = SystemFuncName("count"); @@ -2972,6 +2973,11 @@ static Node *make_distinct_function_expr(List *func_name, List *exprs, int locat return (Node *)fnode; } +static bool is_count_function_name(const char *name) +{ + return strlen(name) == 5 && pg_strcasecmp(name, "count") == 0; +} + /* * helper function to wrap pg_function in the appropiate typecast function to * interface with AGE components @@ -3012,7 +3018,7 @@ static FuncCall *node_to_agtype(Node * fnode, char *type, int location) static char *create_unique_name(char *prefix_name) { char *name = NULL; - char *prefix = NULL; + char *prefix; uint nlen = 0; unsigned long unique_number = 0; @@ -3022,8 +3028,7 @@ static char *create_unique_name(char *prefix_name) /* was a valid prefix supplied */ if (prefix_name == NULL || strlen(prefix_name) <= 0) { - prefix = pnstrdup(UNIQUE_NAME_NULL_PREFIX, - strlen(UNIQUE_NAME_NULL_PREFIX)); + prefix = UNIQUE_NAME_NULL_PREFIX; } else { @@ -3034,24 +3039,19 @@ static char *create_unique_name(char *prefix_name) nlen = snprintf(NULL, 0, "%s_%lu", prefix, unique_number); /* allocate the space */ - name = palloc0(nlen + 1); + name = palloc(nlen + 1); /* create the name */ snprintf(name, nlen + 1, "%s_%lu", prefix, unique_number); - /* if we created the prefix, we need to free it */ - if (prefix_name == NULL || strlen(prefix_name) <= 0) - { - pfree_if_not_null(prefix); - } - return name; } /* function to check if given string has internal alias as prefix */ static bool has_internal_default_prefix(char *str) { - return strncmp(AGE_DEFAULT_PREFIX, str, strlen(AGE_DEFAULT_PREFIX)) == 0; + return strncmp(AGE_DEFAULT_PREFIX, str, + sizeof(AGE_DEFAULT_PREFIX) - 1) == 0; } /* function to return a unique unsigned long number */ diff --git a/src/backend/parser/cypher_item.c b/src/backend/parser/cypher_item.c index c2feb2720..a229833b2 100644 --- a/src/backend/parser/cypher_item.c +++ b/src/backend/parser/cypher_item.c @@ -181,8 +181,8 @@ static List *expand_pnsi_attrs(ParseState *pstate, ParseNamespaceItem *pnsi, List *names, *vars; ListCell *name, *var; List *te_list = NIL; - int var_prefix_len = strlen(AGE_DEFAULT_VARNAME_PREFIX); - int alias_prefix_len = strlen(AGE_DEFAULT_ALIAS_PREFIX); + int var_prefix_len = sizeof(AGE_DEFAULT_VARNAME_PREFIX) - 1; + int alias_prefix_len = sizeof(AGE_DEFAULT_ALIAS_PREFIX) - 1; vars = expandNSItemVars(pstate, pnsi, sublevels_up, location, &names); diff --git a/src/backend/parser/cypher_parse_node.c b/src/backend/parser/cypher_parse_node.c index 48c50d4fd..3acbd6214 100644 --- a/src/backend/parser/cypher_parse_node.c +++ b/src/backend/parser/cypher_parse_node.c @@ -29,6 +29,16 @@ #include "parser/cypher_parse_node.h" static void errpos_ecb(void *arg); +static cypher_parsestate *get_root_cypher_parsestate( + cypher_parsestate *cpstate); + +typedef struct label_cache_entry +{ + const char *label_name; + Oid graph_oid; + uint64 generation; + label_cache_data *label_cache; +} label_cache_entry; /* NOTE: sync the logic with make_parsestate() */ cypher_parsestate *make_cypher_parsestate(cypher_parsestate *parent_cpstate) @@ -132,7 +142,7 @@ char *get_next_default_alias(cypher_parsestate *cpstate) cpstate->default_alias_num); /* allocate the space */ - alias_name = palloc0(nlen + 1); + alias_name = palloc(nlen + 1); /* create the name */ snprintf(alias_name, nlen + 1, "%s%d", AGE_DEFAULT_ALIAS_PREFIX, @@ -143,3 +153,71 @@ char *get_next_default_alias(cypher_parsestate *cpstate) return alias_name; } + +label_cache_data *get_label_cache_data(cypher_parsestate *cpstate, + const char *label_name) +{ + cypher_parsestate *root_cpstate = get_root_cypher_parsestate(cpstate); + ListCell *lc; + uint64 generation = get_label_cache_generation(); + label_cache_data *label_cache; + label_cache_entry *entry; + + if (root_cpstate->last_label_cache_data != NULL && + root_cpstate->last_label_cache_graph_oid == cpstate->graph_oid && + root_cpstate->last_label_cache_generation == generation && + strcmp(root_cpstate->last_label_cache_name, label_name) == 0) + { + return root_cpstate->last_label_cache_data; + } + + foreach (lc, root_cpstate->label_cache_entries) + { + entry = lfirst(lc); + + if (entry->graph_oid == cpstate->graph_oid && + entry->generation == generation && + strcmp(entry->label_name, label_name) == 0) + { + root_cpstate->last_label_cache_name = entry->label_name; + root_cpstate->last_label_cache_graph_oid = entry->graph_oid; + root_cpstate->last_label_cache_generation = entry->generation; + root_cpstate->last_label_cache_data = entry->label_cache; + return entry->label_cache; + } + } + + label_cache = search_label_name_graph_cache_cached(label_name, + root_cpstate->graph_oid); + if (label_cache == NULL) + { + return NULL; + } + + entry = palloc(sizeof(*entry)); + entry->label_name = pstrdup(label_name); + entry->graph_oid = root_cpstate->graph_oid; + entry->generation = generation; + entry->label_cache = label_cache; + root_cpstate->label_cache_entries = + lappend(root_cpstate->label_cache_entries, entry); + root_cpstate->last_label_cache_name = entry->label_name; + root_cpstate->last_label_cache_graph_oid = entry->graph_oid; + root_cpstate->last_label_cache_generation = entry->generation; + root_cpstate->last_label_cache_data = entry->label_cache; + + return label_cache; +} + +static cypher_parsestate *get_root_cypher_parsestate( + cypher_parsestate *cpstate) +{ + ParseState *pstate = (ParseState *)cpstate; + + while (pstate->parentParseState != NULL) + { + pstate = pstate->parentParseState; + } + + return (cypher_parsestate *)pstate; +} diff --git a/src/backend/utils/adt/age_global_graph.c b/src/backend/utils/adt/age_global_graph.c index a9b9b7111..956d2a39b 100644 --- a/src/backend/utils/adt/age_global_graph.c +++ b/src/backend/utils/adt/age_global_graph.c @@ -19,6 +19,7 @@ #include "postgres.h" +#include "access/genam.h" #include "access/heapam.h" #include "catalog/namespace.h" #include "commands/trigger.h" @@ -49,8 +50,8 @@ /* defines */ #define VERTEX_HTAB_NAME "Vertex to edge lists " /* added a space at end for */ #define EDGE_HTAB_NAME "Edge to vertex mapping " /* the graph name to follow */ -#define VERTEX_HTAB_INITIAL_SIZE 10000 -#define EDGE_HTAB_INITIAL_SIZE 10000 +#define VERTEX_HTAB_INITIAL_SIZE 1024 +#define EDGE_HTAB_INITIAL_SIZE 1024 /* Maximum number of graphs tracked for version counting */ #define AGE_MAX_GRAPHS 128 @@ -79,6 +80,12 @@ typedef struct GraphVersionState GraphVersionEntry entries[AGE_MAX_GRAPHS]; } GraphVersionState; +typedef struct EntryPropertyRelationCacheEntry +{ + Oid relid; + Relation rel; +} EntryPropertyRelationCacheEntry; + /* * Version mode detection — determined once per backend on first use. * DSM: PG 17+ GetNamedDSMSegment (no shared_preload_libraries needed) @@ -94,12 +101,21 @@ typedef enum } VersionMode; static VersionMode version_mode = VERSION_MODE_UNKNOWN; +static Oid cached_version_graph_oid = InvalidOid; +static int cached_version_graph_index = -1; /* For PG < 17 shmem path */ static GraphVersionState *shmem_version_state = NULL; /* internal data structures implementation */ +typedef struct edge_label_adj_entry +{ + Oid edge_label_table_oid; + ListGraphId *edges; + struct edge_label_adj_entry *next; +} edge_label_adj_entry; + /* vertex entry for the vertex_hashtable */ typedef struct vertex_entry { @@ -107,7 +123,11 @@ typedef struct vertex_entry ListGraphId *edges_in; /* List of entering edges graphids (int64) */ ListGraphId *edges_out; /* List of exiting edges graphids (int64) */ ListGraphId *edges_self; /* List of selfloop edges graphids (int64) */ + edge_label_adj_entry *edges_in_by_label; + edge_label_adj_entry *edges_out_by_label; + edge_label_adj_entry *edges_self_by_label; Oid vertex_label_table_oid; /* the label table oid */ + char *vertex_label_name; /* the label name */ ItemPointerData tid; /* physical tuple location for lazy fetch */ } vertex_entry; @@ -116,11 +136,25 @@ typedef struct edge_entry { graphid edge_id; /* edge id, it is also the hash key */ Oid edge_label_table_oid; /* the label table oid */ + char *edge_label_name; /* the label name */ ItemPointerData tid; /* physical tuple location for lazy fetch */ graphid start_vertex_id; /* start vertex */ graphid end_vertex_id; /* end vertex */ } edge_entry; +typedef struct graph_label_entry +{ + NameData name; + Oid relation; +} graph_label_entry; + +typedef struct graph_label_list +{ + graph_label_entry *entries; + int count; + int capacity; +} graph_label_list; + /* * GRAPH global context per graph. They are chained together via next. * Be aware that the global pointer will point to the root BUT that @@ -138,6 +172,8 @@ typedef struct GRAPH_global_context CommandId curcid; /* snapshot fallback: command id */ int64 num_loaded_vertices; /* number of loaded vertices in this graph */ int64 num_loaded_edges; /* number of loaded edges in this graph */ + graph_label_list vertex_labels; /* loaded vertex labels for shared names */ + graph_label_list edge_labels; /* loaded edge labels for shared names */ ListGraphId *vertices; /* vertices for vertex hashtable cleanup */ struct GRAPH_global_context *next; /* next graph */ } GRAPH_global_context; @@ -158,24 +194,60 @@ static GRAPH_global_context_container global_graph_contexts_container = {0}; /* declarations */ /* GRAPH global context functions */ static bool free_specific_GRAPH_global_context(GRAPH_global_context *ggctx); -static bool delete_specific_GRAPH_global_contexts(char *graph_name); +static GRAPH_global_context *manage_GRAPH_global_contexts_internal( + const char *graph_name, int graph_name_len, Oid graph_oid); +static bool delete_specific_GRAPH_global_context_by_oid(Oid graph_oid); +static bool delete_specific_GRAPH_global_contexts_len(const char *graph_name, + int graph_name_len); static bool delete_GRAPH_global_contexts(void); +static Oid get_cached_global_graph_oid_len(const char *graph_name, + int graph_name_len); +static void get_global_graph_scalar_arg_no_copy(char *funcname, + agtype *agt_arg, + enum agtype_value_type type, + bool error, + agtype_value *result, + bool *needs_free); static void create_GRAPH_global_hashtables(GRAPH_global_context *ggctx); static void load_GRAPH_global_hashtables(GRAPH_global_context *ggctx); -static void load_vertex_hashtable(GRAPH_global_context *ggctx); -static void load_edge_hashtable(GRAPH_global_context *ggctx); +static void load_vertex_hashtable(GRAPH_global_context *ggctx, + Snapshot snapshot, + graph_label_list *vertex_labels); +static void load_edge_hashtable(GRAPH_global_context *ggctx, + Snapshot snapshot, + graph_label_list *edge_labels); 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 void get_ag_labels_names(Snapshot snapshot, Oid graph_oid, + graph_label_list *vertex_labels, + graph_label_list *edge_labels); +static void append_graph_label(graph_label_list *labels, Name label_name, + Oid relation); +static void free_ag_label_names(graph_label_list *labels); static bool insert_edge_entry(GRAPH_global_context *ggctx, graphid edge_id, ItemPointerData tid, graphid start_vertex_id, - graphid end_vertex_id, Oid edge_label_table_oid); + graphid end_vertex_id, Oid edge_label_table_oid, + const char *edge_label_name); static bool insert_vertex_edge(GRAPH_global_context *ggctx, graphid start_vertex_id, graphid end_vertex_id, - graphid edge_id, char *edge_label_name); + graphid edge_id, Oid edge_label_table_oid, + const char *edge_label_name); +static void append_labeled_vertex_edge(edge_label_adj_entry **head, + Oid edge_label_table_oid, + graphid edge_id); +static ListGraphId *get_labeled_vertex_edges(edge_label_adj_entry *head, + Oid edge_label_table_oid); +static void free_labeled_vertex_edges(edge_label_adj_entry *head); static bool insert_vertex_entry(GRAPH_global_context *ggctx, graphid vertex_id, Oid vertex_label_table_oid, + const char *vertex_label_name, ItemPointerData tid); +static Datum fetch_entry_properties(Oid relid, ItemPointerData tid, + AttrNumber property_attr, + HTAB *relation_cache, + const char *stale_msg); +static Relation get_entry_property_relation(Oid relid, HTAB *relation_cache); +static bool is_ggctx_invalid_with_snapshot(GRAPH_global_context *ggctx, + Snapshot *snapshot); /* definitions */ /* @@ -190,6 +262,14 @@ static bool insert_vertex_entry(GRAPH_global_context *ggctx, graphid vertex_id, * invalidation from unrelated transactions on the server. */ bool is_ggctx_invalid(GRAPH_global_context *ggctx) +{ + Snapshot snapshot = NULL; + + return is_ggctx_invalid_with_snapshot(ggctx, &snapshot); +} + +static bool is_ggctx_invalid_with_snapshot(GRAPH_global_context *ggctx, + Snapshot *snapshot) { /* use version counter if DSM or SHMEM mode is active */ if (version_mode == VERSION_MODE_DSM || version_mode == VERSION_MODE_SHMEM) @@ -214,13 +294,14 @@ bool is_ggctx_invalid(GRAPH_global_context *ggctx) } /* SNAPSHOT fallback: original behavior — check snapshot ids */ + if (*snapshot == NULL) { - Snapshot snap = GetActiveSnapshot(); - - return (ggctx->xmin != snap->xmin || - ggctx->xmax != snap->xmax || - ggctx->curcid != snap->curcid); + *snapshot = GetActiveSnapshot(); } + + return (ggctx->xmin != (*snapshot)->xmin || + ggctx->xmax != (*snapshot)->xmax || + ggctx->curcid != (*snapshot)->curcid); } /* * Helper function to create the global vertex and edge hashtables. One @@ -235,26 +316,11 @@ static void create_GRAPH_global_hashtables(GRAPH_global_context *ggctx) char *graph_name = NULL; char *vhn = NULL; char *ehn = NULL; - int glen; - int vlen; - int elen; - /* get the graph name and length */ + /* get the graph name */ graph_name = ggctx->graph_name; - glen = strlen(graph_name); - /* get the vertex htab name length */ - vlen = strlen(VERTEX_HTAB_NAME); - /* get the edge htab name length */ - elen = strlen(EDGE_HTAB_NAME); - /* allocate the space and build the names */ - vhn = palloc0(vlen + glen + 1); - ehn = palloc0(elen + glen + 1); - /* copy in the names */ - strcpy(vhn, VERTEX_HTAB_NAME); - strcpy(ehn, EDGE_HTAB_NAME); - /* add in the graph name */ - vhn = strncat(vhn, graph_name, glen); - ehn = strncat(ehn, graph_name, glen); + vhn = psprintf("%s%s", VERTEX_HTAB_NAME, graph_name); + ehn = psprintf("%s%s", EDGE_HTAB_NAME, graph_name); /* initialize the vertex hashtable */ MemSet(&vertex_ctl, 0, sizeof(vertex_ctl)); @@ -276,17 +342,16 @@ static void create_GRAPH_global_hashtables(GRAPH_global_context *ggctx) pfree_if_not_null(ehn); } -/* helper function to get a List of all label names for the specified graph */ -static List *get_ag_labels_names(Snapshot snapshot, Oid graph_oid, - char label_type) +/* helper function to get label names for the specified graph */ +static void get_ag_labels_names(Snapshot snapshot, Oid graph_oid, + graph_label_list *vertex_labels, + graph_label_list *edge_labels) { - List *labels = NIL; - ScanKeyData scan_keys[2]; + ScanKeyData scan_key; Relation ag_label; - TableScanDesc scan_desc; + SysScanDesc scan_desc; HeapTuple tuple; TupleDesc tupdesc; - Oid index_oid = InvalidOid; /* we need a valid snapshot */ Assert(snapshot != NULL); @@ -299,113 +364,91 @@ static List *get_ag_labels_names(Snapshot snapshot, Oid graph_oid, /* bail if the number of columns differs - this table has 5 */ Assert(tupdesc->natts == Natts_ag_label); - /* - * Find a usable index whose first key column is ag_label.graph - * (Anum_ag_label_graph) - */ - index_oid = find_usable_btree_index_for_attr(ag_label, Anum_ag_label_graph); + ScanKeyInit(&scan_key, Anum_ag_label_graph, BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(graph_oid)); - if (OidIsValid(index_oid)) - { - Relation index_rel; - IndexScanDesc idx_scan_desc; - ScanKeyData key; - TupleTableSlot *slot; + scan_desc = systable_beginscan(ag_label, ag_label_graph_oid_index_id(), + true, snapshot, 1, &scan_key); - index_rel = index_open(index_oid, AccessShareLock); - slot = table_slot_create(ag_label, NULL); + while (HeapTupleIsValid(tuple = systable_getnext(scan_desc))) + { + bool is_null; + Datum datum; + char label_type; + Name label_name; + Oid label_relation; + + datum = heap_getattr(tuple, Anum_ag_label_kind, tupdesc, &is_null); + if (is_null) + { + continue; + } - /* - * Setup ScanKey: ag_label.graph = graph_oid - * Note: We CANNOT filter by 'kind' here because it is not in the index. - */ - ScanKeyInit(&key, 1, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(graph_oid)); + label_type = DatumGetChar(datum); + if (label_type == LABEL_TYPE_VERTEX || label_type == LABEL_TYPE_EDGE) + { + datum = heap_getattr(tuple, Anum_ag_label_name, tupdesc, &is_null); + if (is_null) + { + continue; + } - idx_scan_desc = index_beginscan(ag_label, index_rel, snapshot, NULL, 1, 0); - index_rescan(idx_scan_desc, &key, 1, NULL, 0); + label_name = DatumGetName(datum); - while (index_getnext_slot(idx_scan_desc, ForwardScanDirection, slot)) - { - bool shouldFree; - - tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree); + datum = heap_getattr(tuple, Anum_ag_label_relation, tupdesc, + &is_null); + Assert(!is_null); + label_relation = DatumGetObjectId(datum); - if (HeapTupleIsValid(tuple)) + if (label_type == LABEL_TYPE_VERTEX) { - bool is_null; - Datum kind_datum; - - /* - * Since the index only gave us rows for the correct graph, - * we must now check if the label 'kind' matches (vertex 'v' or edge 'e'). - */ - kind_datum = heap_getattr(tuple, Anum_ag_label_kind, tupdesc, &is_null); - - if (!is_null && DatumGetChar(kind_datum) == label_type) - { - Datum name_datum = heap_getattr(tuple, Anum_ag_label_name, tupdesc, &is_null); - if (!is_null) - { - Name label_name_ptr; - Name lval; - - label_name_ptr = DatumGetName(name_datum); - lval = (Name) palloc(NAMEDATALEN); - namestrcpy(lval, NameStr(*label_name_ptr)); - labels = lappend(labels, lval); - } - } + append_graph_label(vertex_labels, label_name, label_relation); } - - if (shouldFree) + else { - heap_freetuple(tuple); + append_graph_label(edge_labels, label_name, label_relation); } - ExecClearTuple(slot); } + } - ExecDropSingleTupleTableSlot(slot); - index_endscan(idx_scan_desc); - index_close(index_rel, AccessShareLock); - } - else - { - /* setup scan keys to get all edges for the given graph oid */ - ScanKeyInit(&scan_keys[1], Anum_ag_label_graph, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(graph_oid)); - ScanKeyInit(&scan_keys[0], Anum_ag_label_kind, BTEqualStrategyNumber, - F_CHAREQ, CharGetDatum(label_type)); - - scan_desc = table_beginscan(ag_label, snapshot, 2, scan_keys); + systable_endscan(scan_desc); + table_close(ag_label, AccessShareLock); +} - /* get all of the label names */ - while((tuple = heap_getnext(scan_desc, ForwardScanDirection)) != NULL) - { - Name label; - Name lval; - bool is_null = false; +static void append_graph_label(graph_label_list *labels, Name label_name, + Oid relation) +{ + graph_label_entry *label; - /* something is wrong if this tuple isn't valid */ - Assert(HeapTupleIsValid(tuple)); - /* get the label name */ - label = DatumGetName(heap_getattr(tuple, Anum_ag_label_name, tupdesc, - &is_null)); + if (labels->count >= labels->capacity) + { + int new_capacity = labels->capacity == 0 ? 8 : labels->capacity * 2; - Assert(!is_null); - /* add it to our list */ - lval = (Name) palloc(NAMEDATALEN); - namestrcpy(lval, NameStr(*label)); - labels = lappend(labels, lval); + if (labels->entries == NULL) + { + labels->entries = palloc(sizeof(*labels->entries) * new_capacity); + } + else + { + labels->entries = repalloc(labels->entries, + sizeof(*labels->entries) * + new_capacity); } - /* close up scan */ - table_endscan(scan_desc); + labels->capacity = new_capacity; } - table_close(ag_label, AccessShareLock); + label = &labels->entries[labels->count++]; + namestrcpy(&label->name, NameStr(*label_name)); + label->relation = relation; +} - return labels; +static void free_ag_label_names(graph_label_list *labels) +{ + pfree_if_not_null(labels->entries); + labels->entries = NULL; + labels->count = 0; + labels->capacity = 0; } /* @@ -414,7 +457,8 @@ static List *get_ag_labels_names(Snapshot snapshot, Oid graph_oid, */ static bool insert_edge_entry(GRAPH_global_context *ggctx, graphid edge_id, ItemPointerData tid, graphid start_vertex_id, - graphid end_vertex_id, Oid edge_label_table_oid) + graphid end_vertex_id, Oid edge_label_table_oid, + const char *edge_label_name) { edge_entry *ee = NULL; bool found = false; @@ -463,6 +507,7 @@ static bool insert_edge_entry(GRAPH_global_context *ggctx, graphid edge_id, ee->start_vertex_id = start_vertex_id; ee->end_vertex_id = end_vertex_id; ee->edge_label_table_oid = edge_label_table_oid; + ee->edge_label_name = (char *)edge_label_name; /* increment the number of loaded edges */ ggctx->num_loaded_edges++; @@ -476,6 +521,7 @@ static bool insert_edge_entry(GRAPH_global_context *ggctx, graphid edge_id, */ static bool insert_vertex_entry(GRAPH_global_context *ggctx, graphid vertex_id, Oid vertex_label_table_oid, + const char *vertex_label_name, ItemPointerData tid) { vertex_entry *ve = NULL; @@ -518,12 +564,16 @@ static bool insert_vertex_entry(GRAPH_global_context *ggctx, graphid vertex_id, ve->vertex_id = vertex_id; /* set the label table oid for this vertex */ ve->vertex_label_table_oid = vertex_label_table_oid; + ve->vertex_label_name = (char *)vertex_label_name; /* set the TID for lazy property fetch */ ve->tid = tid; /* set the NIL edge list */ ve->edges_in = NULL; ve->edges_out = NULL; ve->edges_self = NULL; + ve->edges_in_by_label = NULL; + ve->edges_out_by_label = NULL; + ve->edges_self_by_label = NULL; /* we also need to store the vertex id for clean up of vertex lists */ ggctx->vertices = append_graphid(ggctx->vertices, vertex_id); @@ -540,7 +590,8 @@ static bool insert_vertex_entry(GRAPH_global_context *ggctx, graphid vertex_id, */ static bool insert_vertex_edge(GRAPH_global_context *ggctx, graphid start_vertex_id, graphid end_vertex_id, - graphid edge_id, char *edge_label_name) + graphid edge_id, Oid edge_label_table_oid, + const char *edge_label_name) { vertex_entry *value = NULL; bool start_found = false; @@ -562,6 +613,8 @@ static bool insert_vertex_edge(GRAPH_global_context *ggctx, if (start_found && is_selfloop) { value->edges_self = append_graphid(value->edges_self, edge_id); + append_labeled_vertex_edge(&value->edges_self_by_label, + edge_label_table_oid, edge_id); return true; } /* @@ -571,6 +624,8 @@ static bool insert_vertex_edge(GRAPH_global_context *ggctx, else if (start_found) { value->edges_out = append_graphid(value->edges_out, edge_id); + append_labeled_vertex_edge(&value->edges_out_by_label, + edge_label_table_oid, edge_id); } /* search for the end vertex of the edge */ @@ -585,6 +640,8 @@ static bool insert_vertex_edge(GRAPH_global_context *ggctx, if (start_found && end_found) { value->edges_in = append_graphid(value->edges_in, edge_id); + append_labeled_vertex_edge(&value->edges_in_by_label, + edge_label_table_oid, edge_id); return true; } /* @@ -619,38 +676,79 @@ static bool insert_vertex_edge(GRAPH_global_context *ggctx, return false; } +static void append_labeled_vertex_edge(edge_label_adj_entry **head, + Oid edge_label_table_oid, + graphid edge_id) +{ + edge_label_adj_entry *entry = NULL; + + for (entry = *head; entry != NULL; entry = entry->next) + { + if (entry->edge_label_table_oid == edge_label_table_oid) + { + entry->edges = append_graphid(entry->edges, edge_id); + return; + } + } + + entry = palloc0(sizeof(*entry)); + entry->edge_label_table_oid = edge_label_table_oid; + entry->edges = append_graphid(NULL, edge_id); + entry->next = *head; + *head = entry; +} + +static ListGraphId *get_labeled_vertex_edges(edge_label_adj_entry *head, + Oid edge_label_table_oid) +{ + edge_label_adj_entry *entry = NULL; + + for (entry = head; entry != NULL; entry = entry->next) + { + if (entry->edge_label_table_oid == edge_label_table_oid) + { + return entry->edges; + } + } + + return NULL; +} + +static void free_labeled_vertex_edges(edge_label_adj_entry *head) +{ + edge_label_adj_entry *entry = head; + + while (entry != NULL) + { + edge_label_adj_entry *next = entry->next; + + free_ListGraphId(entry->edges); + pfree(entry); + entry = next; + } +} + /* helper routine to load all vertices into the GRAPH global vertex hashtable */ -static void load_vertex_hashtable(GRAPH_global_context *ggctx) +static void load_vertex_hashtable(GRAPH_global_context *ggctx, + Snapshot snapshot, + graph_label_list *vertex_labels) { - Oid graph_oid; - Oid graph_namespace_oid; - Snapshot snapshot; - List *vertex_label_names = NIL; - ListCell *lc; - - /* get the specific graph OID and namespace (schema) OID */ - graph_oid = ggctx->graph_oid; - graph_namespace_oid = get_namespace_oid(ggctx->graph_name, false); - /* get the active snapshot */ - snapshot = GetActiveSnapshot(); - /* get the names of all of the vertex label tables */ - vertex_label_names = get_ag_labels_names(snapshot, graph_oid, - LABEL_TYPE_VERTEX); + int i; + /* go through all vertex label tables in list */ - foreach (lc, vertex_label_names) + for (i = 0; i < vertex_labels->count; i++) { Relation graph_vertex_label; TableScanDesc scan_desc; HeapTuple tuple; + graph_label_entry *label_entry; char *vertex_label_name; Oid vertex_label_table_oid; TupleDesc tupdesc; - /* get the vertex label name */ - vertex_label_name = lfirst(lc); - /* get the vertex label name's OID */ - vertex_label_table_oid = get_relname_relid(vertex_label_name, - graph_namespace_oid); + label_entry = &vertex_labels->entries[i]; + vertex_label_name = NameStr(label_entry->name); + vertex_label_table_oid = label_entry->relation; /* open the relation (table) and begin the scan */ graph_vertex_label = table_open(vertex_label_table_oid, AccessShareLock); scan_desc = table_beginscan(graph_vertex_label, snapshot, 0, NULL); @@ -668,22 +766,24 @@ static void load_vertex_hashtable(GRAPH_global_context *ggctx) while((tuple = heap_getnext(scan_desc, ForwardScanDirection)) != NULL) { graphid vertex_id; + bool isnull; bool inserted = false; - /* something is wrong if this isn't true */ - if (!HeapTupleIsValid(tuple)) + vertex_id = DatumGetInt64(heap_getattr(tuple, + Anum_ag_label_vertex_table_id, + tupdesc, &isnull)); + if (isnull) { - elog(ERROR, "load_vertex_hashtable: !HeapTupleIsValid"); + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("vertex id is null for %s.%s", + ggctx->graph_name, vertex_label_name))); } - Assert(HeapTupleIsValid(tuple)); - - /* get the vertex id */ - vertex_id = DatumGetInt64(column_get_datum(tupdesc, tuple, 0, "id", - GRAPHIDOID, true)); /* insert vertex into vertex hashtable with TID (no property copy) */ inserted = insert_vertex_entry(ggctx, vertex_id, vertex_label_table_oid, + vertex_label_name, tuple->t_self); /* warn if there is a duplicate */ @@ -707,52 +807,46 @@ static void load_vertex_hashtable(GRAPH_global_context *ggctx) */ static void load_GRAPH_global_hashtables(GRAPH_global_context *ggctx) { + Snapshot snapshot = GetActiveSnapshot(); + /* initialize statistics */ ggctx->num_loaded_vertices = 0; ggctx->num_loaded_edges = 0; + get_ag_labels_names(snapshot, ggctx->graph_oid, &ggctx->vertex_labels, + &ggctx->edge_labels); + /* insert all of our vertices */ - load_vertex_hashtable(ggctx); + load_vertex_hashtable(ggctx, snapshot, &ggctx->vertex_labels); /* insert all of our edges */ - load_edge_hashtable(ggctx); + load_edge_hashtable(ggctx, snapshot, &ggctx->edge_labels); } /* * Helper routine to load all edges into the GRAPH global edge and vertex * hashtables. */ -static void load_edge_hashtable(GRAPH_global_context *ggctx) +static void load_edge_hashtable(GRAPH_global_context *ggctx, + Snapshot snapshot, + graph_label_list *edge_labels) { - Oid graph_oid; - Oid graph_namespace_oid; - Snapshot snapshot; - List *edge_label_names = NIL; - ListCell *lc; - - /* get the specific graph OID and namespace (schema) OID */ - graph_oid = ggctx->graph_oid; - graph_namespace_oid = get_namespace_oid(ggctx->graph_name, false); - /* get the active snapshot */ - snapshot = GetActiveSnapshot(); - /* get the names of all of the edge label tables */ - edge_label_names = get_ag_labels_names(snapshot, graph_oid, - LABEL_TYPE_EDGE); + int i; + /* go through all edge label tables in list */ - foreach (lc, edge_label_names) + for (i = 0; i < edge_labels->count; i++) { Relation graph_edge_label; TableScanDesc scan_desc; HeapTuple tuple; + graph_label_entry *label_entry; char *edge_label_name; Oid edge_label_table_oid; TupleDesc tupdesc; - /* get the edge label name */ - edge_label_name = lfirst(lc); - /* get the edge label name's OID */ - edge_label_table_oid = get_relname_relid(edge_label_name, - graph_namespace_oid); + label_entry = &edge_labels->entries[i]; + edge_label_name = NameStr(label_entry->name); + edge_label_table_oid = label_entry->relation; /* open the relation (table) and begin the scan */ graph_edge_label = table_open(edge_label_table_oid, AccessShareLock); scan_desc = table_beginscan(graph_edge_label, snapshot, 0, NULL); @@ -772,35 +866,46 @@ static void load_edge_hashtable(GRAPH_global_context *ggctx) graphid edge_id; graphid edge_vertex_start_id; graphid edge_vertex_end_id; + bool isnull; bool inserted = false; - /* something is wrong if this isn't true */ - if (!HeapTupleIsValid(tuple)) + edge_id = DatumGetInt64(heap_getattr(tuple, + Anum_ag_label_edge_table_id, + tupdesc, &isnull)); + if (isnull) { - elog(ERROR, "load_edge_hashtable: !HeapTupleIsValid"); + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("edge id is null for %s.%s", + ggctx->graph_name, edge_label_name))); + } + + edge_vertex_start_id = DatumGetInt64(heap_getattr( + tuple, Anum_ag_label_edge_table_start_id, tupdesc, &isnull)); + if (isnull) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("edge start_id is null for %s.%s", + ggctx->graph_name, edge_label_name))); + } + + edge_vertex_end_id = DatumGetInt64(heap_getattr( + tuple, Anum_ag_label_edge_table_end_id, tupdesc, &isnull)); + if (isnull) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("edge end_id is null for %s.%s", + ggctx->graph_name, edge_label_name))); } - Assert(HeapTupleIsValid(tuple)); - - /* get the edge id */ - edge_id = DatumGetInt64(column_get_datum(tupdesc, tuple, 0, "id", - GRAPHIDOID, true)); - /* get the edge start_id (start vertex id) */ - edge_vertex_start_id = DatumGetInt64(column_get_datum(tupdesc, - tuple, 1, - "start_id", - GRAPHIDOID, - true)); - /* get the edge end_id (end vertex id)*/ - edge_vertex_end_id = DatumGetInt64(column_get_datum(tupdesc, tuple, - 2, "end_id", - GRAPHIDOID, - true)); /* insert edge into edge hashtable with TID (no property copy) */ inserted = insert_edge_entry(ggctx, edge_id, tuple->t_self, edge_vertex_start_id, edge_vertex_end_id, - edge_label_table_oid); + edge_label_table_oid, + edge_label_name); /* warn if there is a duplicate */ if (!inserted) @@ -808,11 +913,13 @@ static void load_edge_hashtable(GRAPH_global_context *ggctx) ereport(WARNING, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("ignored duplicate edge"))); + continue; } /* insert the edge into the start and end vertices edge lists */ inserted = insert_vertex_edge(ggctx, edge_vertex_start_id, edge_vertex_end_id, edge_id, + edge_label_table_oid, edge_label_name); if (!inserted) { @@ -889,10 +996,16 @@ static bool free_specific_GRAPH_global_context(GRAPH_global_context *ggctx) free_ListGraphId(value->edges_in); free_ListGraphId(value->edges_out); free_ListGraphId(value->edges_self); + free_labeled_vertex_edges(value->edges_in_by_label); + free_labeled_vertex_edges(value->edges_out_by_label); + free_labeled_vertex_edges(value->edges_self_by_label); value->edges_in = NULL; value->edges_out = NULL; value->edges_self = NULL; + value->edges_in_by_label = NULL; + value->edges_out_by_label = NULL; + value->edges_self_by_label = NULL; /* move to the next vertex */ curr_vertex = next_vertex; @@ -902,6 +1015,9 @@ static bool free_specific_GRAPH_global_context(GRAPH_global_context *ggctx) free_ListGraphId(ggctx->vertices); ggctx->vertices = NULL; + free_ag_label_names(&ggctx->vertex_labels); + free_ag_label_names(&ggctx->edge_labels); + /* free the hashtables */ hash_destroy(ggctx->vertex_hashtable); hash_destroy(ggctx->edge_hashtable); @@ -927,10 +1043,27 @@ static bool free_specific_GRAPH_global_context(GRAPH_global_context *ggctx) */ GRAPH_global_context *manage_GRAPH_global_contexts(char *graph_name, Oid graph_oid) +{ + return manage_GRAPH_global_contexts_internal(graph_name, strlen(graph_name), + graph_oid); +} + +GRAPH_global_context *manage_GRAPH_global_contexts_len(const char *graph_name, + int graph_name_len, + Oid graph_oid) +{ + return manage_GRAPH_global_contexts_internal(graph_name, graph_name_len, + graph_oid); +} + +static GRAPH_global_context *manage_GRAPH_global_contexts_internal( + const char *graph_name, int graph_name_len, Oid graph_oid) { GRAPH_global_context *new_ggctx = NULL; GRAPH_global_context *curr_ggctx = NULL; GRAPH_global_context *prev_ggctx = NULL; + GRAPH_global_context *found_ggctx = NULL; + Snapshot invalidation_snapshot = NULL; MemoryContext oldctx = NULL; /* we need a higher context, or one that isn't destroyed by SRF exit */ @@ -958,7 +1091,8 @@ GRAPH_global_context *manage_GRAPH_global_contexts(char *graph_name, GRAPH_global_context *next_ggctx = curr_ggctx->next; /* if the transaction ids have changed, we have an invalid graph */ - if (is_ggctx_invalid(curr_ggctx)) + if (is_ggctx_invalid_with_snapshot(curr_ggctx, + &invalidation_snapshot)) { bool success = false; @@ -991,6 +1125,10 @@ GRAPH_global_context *manage_GRAPH_global_contexts(char *graph_name, } else { + if (found_ggctx == NULL && curr_ggctx->graph_oid == graph_oid) + { + found_ggctx = curr_ggctx; + } prev_ggctx = curr_ggctx; } @@ -998,25 +1136,20 @@ GRAPH_global_context *manage_GRAPH_global_contexts(char *graph_name, curr_ggctx = next_ggctx; } - /* find our graph's context. if it exists, we are done */ - curr_ggctx = global_graph_contexts_container.contexts; - while (curr_ggctx != NULL) + if (found_ggctx != NULL) { - if (curr_ggctx->graph_oid == graph_oid) - { - /* switch our context back */ - MemoryContextSwitchTo(oldctx); + /* switch our context back */ + MemoryContextSwitchTo(oldctx); - /* we are done unlock the global contexts list */ - pthread_mutex_unlock(&global_graph_contexts_container.mutex_lock); + /* we are done unlock the global contexts list */ + pthread_mutex_unlock(&global_graph_contexts_container.mutex_lock); - return curr_ggctx; - } - curr_ggctx = curr_ggctx->next; + return found_ggctx; } /* otherwise, we need to create one and possibly attach it */ - new_ggctx = palloc0(sizeof(GRAPH_global_context)); + new_ggctx = palloc(sizeof(GRAPH_global_context)); + MemSet(new_ggctx, 0, sizeof(GRAPH_global_context)); if (global_graph_contexts_container.contexts != NULL) { @@ -1031,16 +1164,20 @@ GRAPH_global_context *manage_GRAPH_global_contexts(char *graph_name, global_graph_contexts_container.contexts = new_ggctx; /* set the graph name and oid */ - new_ggctx->graph_name = pstrdup(graph_name); + new_ggctx->graph_name = pnstrdup(graph_name, graph_name_len); new_ggctx->graph_oid = graph_oid; /* set the graph version counter for cache invalidation */ new_ggctx->graph_version = get_graph_version(graph_oid); /* set snapshot fields for SNAPSHOT fallback mode */ - new_ggctx->xmin = GetActiveSnapshot()->xmin; - new_ggctx->xmax = GetActiveSnapshot()->xmax; - new_ggctx->curcid = GetActiveSnapshot()->curcid; + { + Snapshot snap = GetActiveSnapshot(); + + new_ggctx->xmin = snap->xmin; + new_ggctx->xmax = snap->xmax; + new_ggctx->curcid = snap->curcid; + } /* initialize our vertices list */ new_ggctx->vertices = NULL; @@ -1114,10 +1251,9 @@ static bool delete_GRAPH_global_contexts(void) * Helper function to delete a specific global graph context used by the * process. */ -static bool delete_specific_GRAPH_global_contexts(char *graph_name) +static bool delete_specific_GRAPH_global_contexts_len(const char *graph_name, + int graph_name_len) { - GRAPH_global_context *prev_ggctx = NULL; - GRAPH_global_context *curr_ggctx = NULL; Oid graph_oid = InvalidOid; if (graph_name == NULL) @@ -1125,8 +1261,122 @@ static bool delete_specific_GRAPH_global_contexts(char *graph_name) return false; } - /* get the graph oid */ - graph_oid = get_graph_oid(graph_name); + graph_oid = get_cached_global_graph_oid_len(graph_name, graph_name_len); + + return delete_specific_GRAPH_global_context_by_oid(graph_oid); +} + +static Oid get_cached_global_graph_oid_len(const char *graph_name, + int graph_name_len) +{ + static NameData cached_graph_name; + static Oid cached_graph_oid = InvalidOid; + static uint64 cached_generation = 0; + uint64 current_generation = get_graph_cache_generation(); + char *graph_name_cstr; + NameData graph_name_buf; + bool free_graph_name = false; + + if (OidIsValid(cached_graph_oid) && + cached_generation == current_generation && + graph_name_len < NAMEDATALEN && + strncmp(NameStr(cached_graph_name), graph_name, graph_name_len) == 0 && + NameStr(cached_graph_name)[graph_name_len] == '\0') + { + return cached_graph_oid; + } + + if (graph_name_len < NAMEDATALEN) + { + memcpy(NameStr(graph_name_buf), graph_name, graph_name_len); + NameStr(graph_name_buf)[graph_name_len] = '\0'; + graph_name_cstr = NameStr(graph_name_buf); + } + else + { + graph_name_cstr = pnstrdup(graph_name, graph_name_len); + free_graph_name = true; + } + + cached_graph_oid = get_graph_oid(graph_name_cstr); + if (OidIsValid(cached_graph_oid)) + { + if (graph_name_len < NAMEDATALEN) + { + cached_graph_name = graph_name_buf; + } + else + { + namestrcpy(&cached_graph_name, graph_name_cstr); + } + cached_generation = current_generation; + } + if (free_graph_name) + { + pfree(graph_name_cstr); + } + + return cached_graph_oid; +} + +static void get_global_graph_scalar_arg_no_copy(char *funcname, + agtype *agt_arg, + enum agtype_value_type type, + bool error, + agtype_value *result, + bool *needs_free) +{ + bool found; + + Assert(funcname != NULL); + Assert(agt_arg != NULL); + Assert(result != NULL); + Assert(needs_free != NULL); + + if (!AGTYPE_CONTAINER_IS_SCALAR(&agt_arg->root)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s: agtype argument must be a scalar", funcname))); + } + + found = get_ith_agtype_value_from_container_no_copy(&agt_arg->root, 0, + result, needs_free); + Assert(found); + + if (error && result->type == AGTV_NULL) + { + if (*needs_free) + { + pfree_agtype_value_content(result); + } + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s: agtype argument must not be AGTV_NULL", + funcname))); + } + + if (error && result->type != type) + { + if (*needs_free) + { + pfree_agtype_value_content(result); + } + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s: agtype argument of wrong type", funcname))); + } +} + +static bool delete_specific_GRAPH_global_context_by_oid(Oid graph_oid) +{ + GRAPH_global_context *prev_ggctx = NULL; + GRAPH_global_context *curr_ggctx = NULL; + + if (!OidIsValid(graph_oid)) + { + return false; + } /* lock the global contexts list */ pthread_mutex_lock(&global_graph_contexts_container.mutex_lock); @@ -1258,6 +1508,11 @@ ListGraphId *get_graph_vertices(GRAPH_global_context *ggctx) return ggctx->vertices; } +int64 get_graph_num_loaded_edges(GRAPH_global_context *ggctx) +{ + return ggctx->num_loaded_edges; +} + /* vertex_entry accessor functions */ graphid get_vertex_entry_id(vertex_entry *ve) { @@ -1279,12 +1534,37 @@ ListGraphId *get_vertex_entry_edges_self(vertex_entry *ve) return ve->edges_self; } +ListGraphId *get_vertex_entry_edges_in_for_label(vertex_entry *ve, + Oid edge_label_table_oid) +{ + return get_labeled_vertex_edges(ve->edges_in_by_label, + edge_label_table_oid); +} + +ListGraphId *get_vertex_entry_edges_out_for_label(vertex_entry *ve, + Oid edge_label_table_oid) +{ + return get_labeled_vertex_edges(ve->edges_out_by_label, + edge_label_table_oid); +} + +ListGraphId *get_vertex_entry_edges_self_for_label(vertex_entry *ve, + Oid edge_label_table_oid) +{ + return get_labeled_vertex_edges(ve->edges_self_by_label, + edge_label_table_oid); +} Oid get_vertex_entry_label_table_oid(vertex_entry *ve) { return ve->vertex_label_table_oid; } +char *get_vertex_entry_label_name(vertex_entry *ve) +{ + return ve->vertex_label_name; +} + /* * Fetch vertex properties on demand from the heap via stored TID. * @@ -1299,14 +1579,33 @@ Oid get_vertex_entry_label_table_oid(vertex_entry *ve) * the invalidation logic. */ Datum get_vertex_entry_properties(vertex_entry *ve) +{ + return get_vertex_entry_properties_with_cache(ve, NULL); +} + +Datum get_vertex_entry_properties_with_cache(vertex_entry *ve, + HTAB *relation_cache) +{ + return fetch_entry_properties( + ve->vertex_label_table_oid, ve->tid, 2, relation_cache, + "get_vertex_entry_properties: stale TID - " + "vertex entry references a tuple that is no longer visible"); +} + +static Datum fetch_entry_properties(Oid relid, ItemPointerData tid, + AttrNumber property_attr, + HTAB *relation_cache, + const char *stale_msg) { Relation rel; HeapTupleData tuple; Buffer buffer; Datum result = (Datum) 0; + bool close_rel = false; - rel = table_open(ve->vertex_label_table_oid, AccessShareLock); - tuple.t_self = ve->tid; + rel = get_entry_property_relation(relid, relation_cache); + close_rel = relation_cache == NULL; + tuple.t_self = tid; if (heap_fetch(rel, GetActiveSnapshot(), &tuple, &buffer, true)) { @@ -1314,8 +1613,7 @@ Datum get_vertex_entry_properties(vertex_entry *ve) bool isnull; Datum props; - /* properties is column 2 (1-indexed) */ - props = heap_getattr(&tuple, 2, tupdesc, &isnull); + props = heap_getattr(&tuple, property_attr, tupdesc, &isnull); if (!isnull) { result = datumCopy(props, false, -1); @@ -1324,7 +1622,10 @@ Datum get_vertex_entry_properties(vertex_entry *ve) ReleaseBuffer(buffer); } - table_close(rel, AccessShareLock); + if (close_rel) + { + table_close(rel, AccessShareLock); + } /* * If heap_fetch failed, the tuple is no longer visible. This should @@ -1333,8 +1634,7 @@ Datum get_vertex_entry_properties(vertex_entry *ve) */ if (result == (Datum) 0) { - elog(ERROR, "get_vertex_entry_properties: stale TID - " - "vertex entry references a tuple that is no longer visible"); + elog(ERROR, "%s", stale_msg); } return result; @@ -1351,45 +1651,82 @@ Oid get_edge_entry_label_table_oid(edge_entry *ee) return ee->edge_label_table_oid; } +char *get_edge_entry_label_name(edge_entry *ee) +{ + return ee->edge_label_name; +} + /* * 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) { - Relation rel; - HeapTupleData tuple; - Buffer buffer; - Datum result = (Datum) 0; + return get_edge_entry_properties_with_cache(ee, NULL); +} - rel = table_open(ee->edge_label_table_oid, AccessShareLock); - tuple.t_self = ee->tid; +Datum get_edge_entry_properties_with_cache(edge_entry *ee, + HTAB *relation_cache) +{ + return fetch_entry_properties( + ee->edge_label_table_oid, ee->tid, 4, relation_cache, + "get_edge_entry_properties: stale TID - " + "edge entry references a tuple that is no longer visible"); +} - if (heap_fetch(rel, GetActiveSnapshot(), &tuple, &buffer, true)) +HTAB *create_entry_property_relation_cache(const char *name) +{ + HASHCTL hash_ctl; + + MemSet(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(Oid); + hash_ctl.entrysize = sizeof(EntryPropertyRelationCacheEntry); + hash_ctl.hcxt = CurrentMemoryContext; + + return hash_create(name, 8, &hash_ctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); +} + +void destroy_entry_property_relation_cache(HTAB *relation_cache) +{ + HASH_SEQ_STATUS hash_seq; + EntryPropertyRelationCacheEntry *entry; + + if (relation_cache == NULL) { - TupleDesc tupdesc = RelationGetDescr(rel); - bool isnull; - Datum props; + return; + } - /* properties is column 4 (1-indexed) */ - props = heap_getattr(&tuple, 4, tupdesc, &isnull); - if (!isnull) + hash_seq_init(&hash_seq, relation_cache); + while ((entry = hash_seq_search(&hash_seq)) != NULL) + { + if (entry->rel != NULL) { - result = datumCopy(props, false, -1); + table_close(entry->rel, AccessShareLock); } - - ReleaseBuffer(buffer); } - table_close(rel, AccessShareLock); + hash_destroy(relation_cache); +} + +static Relation get_entry_property_relation(Oid relid, HTAB *relation_cache) +{ + EntryPropertyRelationCacheEntry *entry; + bool found; - if (result == (Datum) 0) + if (relation_cache == NULL) { - elog(ERROR, "get_edge_entry_properties: stale TID - " - "edge entry references a tuple that is no longer visible"); + return table_open(relid, AccessShareLock); } - return result; + entry = hash_search(relation_cache, &relid, HASH_ENTER, &found); + if (!found) + { + entry->relid = relid; + entry->rel = table_open(relid, AccessShareLock); + } + + return entry->rel; } graphid get_edge_entry_start_vertex_id(edge_entry *ee) @@ -1409,29 +1746,30 @@ PG_FUNCTION_INFO_V1(age_delete_global_graphs); Datum age_delete_global_graphs(PG_FUNCTION_ARGS) { - agtype_value *agtv_temp = NULL; + agtype_value agtv_temp; + agtype_value *agtv_temp_ptr = NULL; + bool agtv_temp_needs_free = false; bool success = false; /* get the graph name if supplied */ if (!PG_ARGISNULL(0)) { - agtv_temp = get_agtype_value("delete_global_graphs", - AG_GET_ARG_AGTYPE_P(0), - AGTV_STRING, false); + get_global_graph_scalar_arg_no_copy("delete_global_graphs", + AG_GET_ARG_AGTYPE_P(0), + AGTV_STRING, false, + &agtv_temp, + &agtv_temp_needs_free); + agtv_temp_ptr = &agtv_temp; } - if (agtv_temp == NULL || agtv_temp->type == AGTV_NULL) + if (agtv_temp_ptr == NULL || agtv_temp_ptr->type == AGTV_NULL) { success = delete_GRAPH_global_contexts(); } - else if (agtv_temp->type == AGTV_STRING) + else if (agtv_temp_ptr->type == AGTV_STRING) { - char *graph_name = NULL; - - graph_name = pnstrdup(agtv_temp->val.string.val, - agtv_temp->val.string.len); - - success = delete_specific_GRAPH_global_contexts(graph_name); + success = delete_specific_GRAPH_global_contexts_len( + agtv_temp_ptr->val.string.val, agtv_temp_ptr->val.string.len); } else { @@ -1440,6 +1778,11 @@ Datum age_delete_global_graphs(PG_FUNCTION_ARGS) errmsg("delete_global_graphs: invalid graph name type"))); } + if (agtv_temp_needs_free) + { + pfree_agtype_value_content(&agtv_temp); + } + PG_RETURN_BOOL(success); } @@ -1451,11 +1794,14 @@ Datum age_vertex_stats(PG_FUNCTION_ARGS) GRAPH_global_context *ggctx = NULL; vertex_entry *ve = NULL; ListGraphId *edges = NULL; - agtype_value *agtv_vertex = NULL; + agtype_value agtv_vertex; agtype_value *agtv_temp = NULL; + agtype_value graph_name_value; + bool graph_name_needs_free = false; + bool vertex_needs_free = false; agtype_value agtv_integer; agtype_in_state result; - char *graph_name = NULL; + agtype *agt_result; Oid graph_oid = InvalidOid; graphid vid = 0; int64 self_loops = 0; @@ -1470,8 +1816,9 @@ Datum age_vertex_stats(PG_FUNCTION_ARGS) } /* get the graph name */ - agtv_temp = get_agtype_value("vertex_stats", AG_GET_ARG_AGTYPE_P(0), - AGTV_STRING, true); + get_global_graph_scalar_arg_no_copy("vertex_stats", AG_GET_ARG_AGTYPE_P(0), + AGTV_STRING, true, &graph_name_value, + &graph_name_needs_free); /* we need the vertex */ if (PG_ARGISNULL(1)) @@ -1482,26 +1829,24 @@ Datum age_vertex_stats(PG_FUNCTION_ARGS) } /* get the vertex */ - agtv_vertex = get_agtype_value("vertex_stats", AG_GET_ARG_AGTYPE_P(1), - AGTV_VERTEX, true); - - graph_name = pnstrdup(agtv_temp->val.string.val, - agtv_temp->val.string.len); + get_global_graph_scalar_arg_no_copy("vertex_stats", AG_GET_ARG_AGTYPE_P(1), + AGTV_VERTEX, true, &agtv_vertex, + &vertex_needs_free); /* get the graph oid */ - graph_oid = get_graph_oid(graph_name); + graph_oid = get_cached_global_graph_oid_len(graph_name_value.val.string.val, + graph_name_value.val.string.len); /* * 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_if_not_null(graph_name); + ggctx = manage_GRAPH_global_contexts_len(graph_name_value.val.string.val, + graph_name_value.val.string.len, + graph_oid); /* get the id */ - agtv_temp = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_vertex, "id"); + agtv_temp = AGTYPE_VERTEX_GET_ID(&agtv_vertex); vid = agtv_temp->val.int_value; /* get the vertex entry */ @@ -1519,7 +1864,7 @@ Datum age_vertex_stats(PG_FUNCTION_ARGS) result.res = push_agtype_value(&result.parse_state, WAGT_VALUE, agtv_temp); /* store the label */ - agtv_temp = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_vertex, "label"); + agtv_temp = AGTYPE_VERTEX_GET_LABEL(&agtv_vertex); result.res = push_agtype_value(&result.parse_state, WAGT_KEY, string_to_agtype_value("label")); result.res = push_agtype_value(&result.parse_state, WAGT_VALUE, agtv_temp); @@ -1558,7 +1903,17 @@ Datum age_vertex_stats(PG_FUNCTION_ARGS) result.res->type = AGTV_OBJECT; - PG_RETURN_POINTER(agtype_value_to_agtype(result.res)); + agt_result = agtype_value_to_agtype(result.res); + if (graph_name_needs_free) + { + pfree_agtype_value_content(&graph_name_value); + } + if (vertex_needs_free) + { + pfree_agtype_value_content(&agtv_vertex); + } + + PG_RETURN_POINTER(agt_result); } /* PG wrapper function for age_graph_stats */ @@ -1568,9 +1923,11 @@ Datum age_graph_stats(PG_FUNCTION_ARGS) { GRAPH_global_context *ggctx = NULL; agtype_value *agtv_temp = NULL; + agtype_value graph_name_value; + bool graph_name_needs_free = false; agtype_value agtv_integer; agtype_in_state result; - char *graph_name = NULL; + agtype *agt_result; Oid graph_oid = InvalidOid; /* the graph name is required, but this generally isn't user supplied */ @@ -1582,29 +1939,27 @@ Datum age_graph_stats(PG_FUNCTION_ARGS) } /* get the graph name */ - agtv_temp = get_agtype_value("graph_stats", AG_GET_ARG_AGTYPE_P(0), - AGTV_STRING, true); + get_global_graph_scalar_arg_no_copy("graph_stats", AG_GET_ARG_AGTYPE_P(0), + AGTV_STRING, true, &graph_name_value, + &graph_name_needs_free); - graph_name = pnstrdup(agtv_temp->val.string.val, - agtv_temp->val.string.len); + /* get the graph oid */ + graph_oid = get_cached_global_graph_oid_len(graph_name_value.val.string.val, + graph_name_value.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); + delete_specific_GRAPH_global_context_by_oid(graph_oid); /* * 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_if_not_null(graph_name); + ggctx = manage_GRAPH_global_contexts_len(graph_name_value.val.string.val, + graph_name_value.val.string.len, + graph_oid); /* zero the state */ memset(&result, 0, sizeof(agtype_in_state)); @@ -1615,7 +1970,8 @@ Datum age_graph_stats(PG_FUNCTION_ARGS) /* 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); + result.res = push_agtype_value(&result.parse_state, WAGT_VALUE, + &graph_name_value); /* set up an integer for returning values */ agtv_temp = &agtv_integer; @@ -1639,7 +1995,13 @@ Datum age_graph_stats(PG_FUNCTION_ARGS) result.res->type = AGTV_OBJECT; - PG_RETURN_POINTER(agtype_value_to_agtype(result.res)); + agt_result = agtype_value_to_agtype(result.res); + if (graph_name_needs_free) + { + pfree_agtype_value_content(&graph_name_value); + } + + PG_RETURN_POINTER(agt_result); } /* @@ -1788,15 +2150,30 @@ uint64 get_graph_version(Oid graph_oid) return 0; } + if (cached_version_graph_oid == graph_oid && + cached_version_graph_index >= 0 && + cached_version_graph_index < state->num_entries && + state->entries[cached_version_graph_index].graph_oid == graph_oid) + { + return pg_atomic_read_u64( + &state->entries[cached_version_graph_index].version); + } + /* lock-free scan of the array */ for (i = 0; i < state->num_entries; i++) { if (state->entries[i].graph_oid == graph_oid) { + cached_version_graph_oid = graph_oid; + cached_version_graph_index = i; + return pg_atomic_read_u64(&state->entries[i].version); } } + cached_version_graph_oid = InvalidOid; + cached_version_graph_index = -1; + return 0; } @@ -1815,11 +2192,24 @@ void increment_graph_version(Oid graph_oid) return; } + if (cached_version_graph_oid == graph_oid && + cached_version_graph_index >= 0 && + cached_version_graph_index < state->num_entries && + state->entries[cached_version_graph_index].graph_oid == graph_oid) + { + pg_atomic_fetch_add_u64( + &state->entries[cached_version_graph_index].version, 1); + return; + } + /* try to find existing entry (lock-free) */ for (i = 0; i < state->num_entries; i++) { if (state->entries[i].graph_oid == graph_oid) { + cached_version_graph_oid = graph_oid; + cached_version_graph_index = i; + pg_atomic_fetch_add_u64(&state->entries[i].version, 1); return; } @@ -1834,6 +2224,9 @@ void increment_graph_version(Oid graph_oid) if (state->entries[i].graph_oid == graph_oid) { LWLockRelease(&state->lock); + cached_version_graph_oid = graph_oid; + cached_version_graph_index = i; + pg_atomic_fetch_add_u64(&state->entries[i].version, 1); return; } @@ -1846,6 +2239,8 @@ void increment_graph_version(Oid graph_oid) state->entries[idx].graph_oid = graph_oid; pg_atomic_init_u64(&state->entries[idx].version, 1); + cached_version_graph_oid = graph_oid; + cached_version_graph_index = idx; /* * Write barrier ensures the entry fields are fully visible to @@ -1874,7 +2269,7 @@ Oid get_graph_oid_for_table(Oid table_oid) { label_cache_data *lcd = NULL; - lcd = search_label_relation_cache(table_oid); + lcd = search_label_relation_cache_cached(table_oid); if (lcd != NULL) { diff --git a/src/backend/utils/adt/age_graphid_ds.c b/src/backend/utils/adt/age_graphid_ds.c index cee53bd83..2e6d4612b 100644 --- a/src/backend/utils/adt/age_graphid_ds.c +++ b/src/backend/utils/adt/age_graphid_ds.c @@ -100,7 +100,7 @@ ListGraphId *append_graphid(ListGraphId *container, graphid id) GraphIdNode *new_node = NULL; /* create the new link */ - new_node = palloc0(sizeof(GraphIdNode)); + new_node = palloc(sizeof(GraphIdNode)); new_node->id = id; new_node->next = NULL; @@ -110,7 +110,7 @@ ListGraphId *append_graphid(ListGraphId *container, graphid id) */ if (container == NULL) { - container = palloc0(sizeof(ListGraphId)); + container = palloc(sizeof(ListGraphId)); container->head = new_node; container->tail = new_node; container->size = 1; @@ -160,7 +160,7 @@ ListGraphId *new_graphid_stack(void) ListGraphId *stack = NULL; /* allocate the container for the stack */ - stack = palloc0(sizeof(ListGraphId)); + stack = palloc(sizeof(ListGraphId)); /* set it to its initial values */ stack->head = NULL; @@ -214,7 +214,7 @@ void push_graphid_stack(ListGraphId *stack, graphid id) } /* create the new element */ - new_node = palloc0(sizeof(GraphIdNode)); + new_node = palloc(sizeof(GraphIdNode)); new_node->id = id; /* insert (push) the new element on the top */ diff --git a/src/backend/utils/adt/age_session_info.c b/src/backend/utils/adt/age_session_info.c index f224d4064..71322389d 100644 --- a/src/backend/utils/adt/age_session_info.c +++ b/src/backend/utils/adt/age_session_info.c @@ -19,152 +19,20 @@ #include "postgres.h" -#include "funcapi.h" - -#include -#include "utils/age_session_info.h" - -/* - * static/global session info variables for use with the driver interface. - */ -static int session_info_pid = -1; -static char *session_info_graph_name = NULL; -static char *session_info_cypher_statement = NULL; -static bool session_info_prepared = false; - -static void set_session_info(char *graph_name, char *cypher_statement); - -/* function to set the session info. it will clean it, if necessary. */ -static void set_session_info(char *graph_name, char *cypher_statement) -{ - MemoryContext oldctx = NULL; - - if (is_session_info_prepared()) - { - reset_session_info(); - } - - /* we need to use a higher memory context for the data pointed to. */ - oldctx = MemoryContextSwitchTo(TopMemoryContext); - - if (graph_name != NULL) - { - session_info_graph_name = pstrdup(graph_name); - } - else - { - session_info_graph_name = NULL; - } - - if (cypher_statement != NULL) - { - session_info_cypher_statement = pstrdup(cypher_statement); - } - else - { - session_info_cypher_statement = NULL; - } - - /* switch back to the original context */ - MemoryContextSwitchTo(oldctx); - - session_info_pid = getpid(); - session_info_prepared = true; -} - -/* - * Helper function to return the value of session_info_cypher_statement or NULL - * if the value isn't set. The value returned is a copy, so please free it when - * done. - */ -char *get_session_info_graph_name(void) -{ - if (is_session_info_prepared() && - session_info_graph_name != NULL) - { - return pstrdup(session_info_graph_name); - } - - return NULL; -} +#include "fmgr.h" /* - * Helper function to return the value of session_info_cypher_statement or NULL - * if the value isn't set. The value returned is a copy, so please free it when - * done. + * Compatibility stub for the old driver-facing two-step execution path. + * + * The previous implementation stored graph/query text in backend-global state + * and then let a later cypher(NULL, NULL) call consume that state. The parser + * now requires cypher() to receive its graph and query text directly, keeping + * cypher query execution on PostgreSQL's normal parse/plan lifecycle instead + * of an AGE-managed side channel. */ -char *get_session_info_cypher_statement(void) -{ - if (is_session_info_prepared() && - session_info_cypher_statement != NULL) - { - return pstrdup(session_info_cypher_statement); - } - - return NULL; -} - -/* function to return the state of the session info data */ -bool is_session_info_prepared(void) -{ - /* is the session info prepared AND is the pid the same pid */ - if (session_info_prepared == true && - session_info_pid == getpid()) - { - return true; - } - - return false; -} - -/* function to clean and reset the session info back to default values */ -void reset_session_info(void) -{ - /* if the session info is prepared, free the strings */ - if (session_info_prepared == true) - { - if (session_info_graph_name != NULL) - { - pfree_if_not_null(session_info_graph_name); - } - - if (session_info_cypher_statement != NULL) - { - pfree_if_not_null(session_info_cypher_statement); - } - } - - /* reset the session info back to default unused values */ - session_info_graph_name = NULL; - session_info_cypher_statement = NULL; - session_info_prepared = false; - session_info_pid = -1; -} - -/* AGE SQL function to prepare session info */ PG_FUNCTION_INFO_V1(age_prepare_cypher); Datum age_prepare_cypher(PG_FUNCTION_ARGS) { - char *graph_name_str = NULL; - char *cypher_statement_str = NULL; - - /* both arguments must be non-NULL */ - if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) - { - PG_RETURN_BOOL(false); - } - - graph_name_str = PG_GETARG_CSTRING(0); - cypher_statement_str = PG_GETARG_CSTRING(1); - - /* both strings must be non-NULL */ - if (graph_name_str == NULL || cypher_statement_str == NULL) - { - PG_RETURN_BOOL(false); - } - - set_session_info(graph_name_str, cypher_statement_str); - - PG_RETURN_BOOL(true); + PG_RETURN_BOOL(false); } diff --git a/src/backend/utils/adt/age_vle.c b/src/backend/utils/adt/age_vle.c index 22c268cdf..ccb4a8dd0 100644 --- a/src/backend/utils/adt/age_vle.c +++ b/src/backend/utils/adt/age_vle.c @@ -65,6 +65,7 @@ #include "common/hashfn.h" #include "funcapi.h" +#include "utils/builtins.h" #include "utils/datum.h" #include "utils/lsyscache.h" @@ -72,14 +73,18 @@ #include "catalog/ag_graph.h" #include "catalog/ag_label.h" #include "nodes/cypher_nodes.h" +#include "utils/ag_cache.h" /* defines */ #define GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc) \ (graphid *) (&vpc->graphid_array_data) #define EDGE_STATE_HTAB_NAME "Edge state " -#define EDGE_STATE_HTAB_INITIAL_SIZE 100000 +#define EDGE_STATE_HTAB_MIN_SIZE 16 #define EXISTS_HTAB_NAME "known edges" -#define EXISTS_HTAB_NAME_INITIAL_SIZE 1000 +#define EXISTS_HTAB_NAME_MIN_SIZE 16 +#define EDGE_UNIQUENESS_FAST_ARGS 16 +#define EDGE_UNIQUENESS_NESTED_SCAN_LIMIT 64 +#define VLE_NEXT_VERTEX_UNKNOWN ((graphid)0) #define MAXIMUM_NUMBER_OF_CACHED_LOCAL_CONTEXTS 5 /* edge state entry for the edge_state_hashtable */ @@ -116,17 +121,26 @@ typedef struct VLE_local_context char *edge_label_name; /* edge label name for match */ Oid edge_label_name_oid; /* edge label name oid for match */ agtype *edge_property_constraint; /* edge property constraint as agtype */ + int num_edge_property_constraints; /* number of edge property constraints */ Datum edge_property_constraint_datum; /* edge property constraint as Datum */ uint32 edge_property_constraint_hash; /* edge property constraint hash */ + agtype_pair *edge_property_constraint_pairs; /* cached constraints */ + int num_cached_edge_property_constraints; /* cached constraint count */ + HTAB *edge_property_relation_cache; /* relation cache for property fetch */ int64 lidx; /* lower (start) bound index */ int64 uidx; /* upper (end) bound index */ bool uidx_infinite; /* flag if the upper bound is omitted */ cypher_rel_dir edge_direction; /* the direction of the edge */ HTAB *edge_state_hashtable; /* local state hashtable for our edges */ + int64 edge_state_initial_size; /* initial edge-state hash size estimate */ GraphIdStack *dfs_vertex_stack; /* dfs stack for vertices (array-based) */ GraphIdStack *dfs_edge_stack; /* dfs stack for edges (array-based) */ + GraphIdStack *dfs_next_vertex_stack; /* cached terminal vertices */ GraphIdStack *dfs_path_stack; /* dfs stack containing the path (array-based) */ + GraphIdStack *dfs_path_vertex_stack; /* vertices in current path order */ VLE_path_function path_function; /* which path function to use */ + bool reverse_paths_to; /* traverse paths-to from the bound end */ + bool reverse_output_path; /* reverse traversal result before return */ GraphIdNode *next_vertex; /* for VLE_FUNCTION_PATHS_TO */ int64 vle_grammar_node_id; /* the unique VLE grammar assigned node id */ bool use_cache; /* are we using VLE_local_context cache */ @@ -150,16 +164,46 @@ typedef struct VLE_path_container graphid graphid_array_data; } VLE_path_container; +typedef struct edge_uniqueness_argtype_cache +{ + int nargs; + Oid types[FLEXIBLE_ARRAY_MEMBER]; +} edge_uniqueness_argtype_cache; + /* declarations */ /* global variable to hold the per process global cached VLE_local contexts */ static VLE_local_context *global_vle_local_contexts = NULL; /* agtype functions */ -static bool is_an_edge_match(VLE_local_context *vlelctx, edge_entry *ee); +static bool get_vle_vertex_or_id_arg(FunctionCallInfo fcinfo, int argno, + const char *type_error_msg, + graphid *result); +static void get_vle_scalar_arg_no_copy(char *funcname, agtype *agt_arg, + enum agtype_value_type type, bool error, + agtype_value *result, + bool *needs_free); +static graphid get_agtype_scalar_graphid_arg(agtype *agt_arg, + const char *type_error_msg); +static bool get_agtype_scalar_bool_arg(agtype *agt_arg, + const char *type_error_msg); +static bool is_an_edge_match(VLE_local_context *vlelctx, edge_entry *ee, + HTAB *relation_cache); +static void cache_edge_property_constraints(VLE_local_context *vlelctx); +static void free_edge_property_constraint_cache(VLE_local_context *vlelctx); +static bool cached_edge_property_constraints_match( + VLE_local_context *vlelctx, agtype_container *edge_properties); +static void free_cached_property_value(agtype_value *value); +static bool agtype_value_satisfies_property_constraint( + agtype_value *property_value, agtype_value *constraint_value); /* VLE local context functions */ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, FuncCallContext *funcctx); +static Oid get_cached_vle_graph_oid(const char *graph_name, + int graph_name_len); +static Oid get_cached_vle_label_relation(Oid graph_oid, + const char *label_name, + int label_name_len); static void create_VLE_local_state_hashtable(VLE_local_context *vlelctx); static void free_VLE_local_context(VLE_local_context *vlelctx); /* VLE graph traversal functions */ @@ -169,17 +213,55 @@ static edge_state_entry *get_edge_state(VLE_local_context *vlelctx, static void load_initial_dfs_stacks(VLE_local_context *vlelctx); static bool dfs_find_a_path_between(VLE_local_context *vlelctx); static bool dfs_find_a_path_from(VLE_local_context *vlelctx); -static bool do_vsid_and_veid_exist(VLE_local_context *vlelctx); +static int get_edge_uniqueness_args_fast(FunctionCallInfo fcinfo, + Datum **args, Oid **types, + bool **nulls, Datum *fast_args, + Oid *fast_types, bool *fast_nulls); +static Oid get_cached_edge_uniqueness_argtype(FunctionCallInfo fcinfo, + int argno, int nargs); static void add_valid_vertex_edges(VLE_local_context *vlelctx, graphid vertex_id); +static void add_valid_vertex_edges_for_entry(VLE_local_context *vlelctx, + vertex_entry *ve); static graphid get_next_vertex(VLE_local_context *vlelctx, edge_entry *ee); -static bool is_edge_in_path(VLE_local_context *vlelctx, graphid edge_id); +static graphid get_next_vertex_from_source(VLE_local_context *vlelctx, + edge_entry *ee, + graphid source_vertex_id); +static cypher_rel_dir reverse_edge_direction(cypher_rel_dir edge_direction); +static bool is_zero_length_only(VLE_local_context *vlelctx); +static bool is_empty_length_range(VLE_local_context *vlelctx); +static bool should_emit_zero_bound(VLE_local_context *vlelctx); +static int64 get_initial_edge_count(VLE_local_context *vlelctx, + graphid vertex_id, + cypher_rel_dir edge_direction); +static int64 get_matching_initial_edge_count(ListGraphId *edges); +static bool is_edge_in_path(VLE_local_context *vlelctx, graphid edge_id, + int64 stack_size); /* VLE path and edge building functions */ static VLE_path_container *create_VLE_path_container(int64 path_size); static VLE_path_container *build_VLE_path_container(VLE_local_context *vlelctx); +static VLE_path_container *build_reversed_VLE_path_container( + VLE_local_context *vlelctx); static VLE_path_container *build_VLE_zero_container(VLE_local_context *vlelctx); +static agtype_value *build_vle_edge_value(GRAPH_global_context *ggctx, + graphid edge_id, + HTAB *relation_cache); +static agtype_value *build_vle_vertex_value(GRAPH_global_context *ggctx, + graphid vertex_id, + HTAB *relation_cache); static agtype_value *build_path(VLE_path_container *vpc); static agtype_value *build_edge_list(VLE_path_container *vpc); +static agtype_value *build_empty_agtype_value_array(void); +static int64 agtv_vle_node_count(agtype *agt_arg_vpc); +static bool get_vle_edge_id_at_index(agtype *agt_arg_vpc, + int64 edge_index, graphid *edge_id); +static agtype_value *agtv_materialize_vle_vertex_at(agtype *agt_arg_vpc, + int64 node_index); +static agtype_value *agtv_materialize_vle_edge_endpoint_at( + agtype *agt_arg_vpc, int64 edge_index, bool start_endpoint); +static Datum age_vle_edge_endpoint_id_at(FunctionCallInfo fcinfo, + bool start_endpoint); +static agtype *build_empty_agtype_array(void); /* VLE_local_context cache management */ static VLE_local_context *get_cached_VLE_local_context(int64 vle_node_id); static void cache_VLE_local_context(VLE_local_context *vlelctx); @@ -339,28 +421,22 @@ static void create_VLE_local_state_hashtable(VLE_local_context *vlelctx) HASHCTL edge_state_ctl; char *graph_name = NULL; char *eshn = NULL; - int glen; - int elen; + long initial_size; - /* get the graph name and length */ + /* get the graph name */ graph_name = vlelctx->graph_name; - glen = strlen(graph_name); - /* get the edge state htab name length */ - elen = strlen(EDGE_STATE_HTAB_NAME); - /* allocate the space and build the name */ - eshn = palloc0(elen + glen + 1); - /* copy in the name */ - strcpy(eshn, EDGE_STATE_HTAB_NAME); - /* add in the graph name */ - eshn = strncat(eshn, graph_name, glen); + eshn = psprintf("%s%s", EDGE_STATE_HTAB_NAME, graph_name); /* initialize the edge state hashtable */ MemSet(&edge_state_ctl, 0, sizeof(edge_state_ctl)); edge_state_ctl.keysize = sizeof(int64); edge_state_ctl.entrysize = sizeof(edge_state_entry); edge_state_ctl.hash = tag_hash; - vlelctx->edge_state_hashtable = hash_create(eshn, - EDGE_STATE_HTAB_INITIAL_SIZE, + + initial_size = Max(vlelctx->edge_state_initial_size, + EDGE_STATE_HTAB_MIN_SIZE); + initial_size = Min(initial_size, get_graph_num_loaded_edges(vlelctx->ggctx)); + vlelctx->edge_state_hashtable = hash_create(eshn, initial_size, &edge_state_ctl, HASH_ELEM | HASH_FUNCTION); pfree_if_not_null(eshn); @@ -370,49 +446,25 @@ static void create_VLE_local_state_hashtable(VLE_local_context *vlelctx) * Helper function to compare the edge constraint (properties we are looking * for in a matching edge) against an edge entry's property. */ -static bool is_an_edge_match(VLE_local_context *vlelctx, edge_entry *ee) +static bool is_an_edge_match(VLE_local_context *vlelctx, edge_entry *ee, + HTAB *relation_cache) { agtype *edge_property = NULL; agtype_container *agtc_edge_property = NULL; agtype_container *agtc_edge_property_constraint = NULL; agtype_iterator *constraint_it = NULL; agtype_iterator *property_it = NULL; - Oid edge_label_name_oid = InvalidOid; int num_edge_property_constraints = 0; int num_edge_properties = 0; /* get the number of conditions from the prototype edge */ - num_edge_property_constraints = AGT_ROOT_COUNT(vlelctx->edge_property_constraint); - - /* - * We only care about verifying that we have all of the property conditions. - * We don't care about extra unmatched properties. If there aren't any edge - * constraints, then the edge passes by default. - */ - if (vlelctx->edge_label_name_oid == InvalidOid && - num_edge_property_constraints == 0) - { - return true; - } - - /* get the edge label oid */ - edge_label_name_oid = get_edge_entry_label_table_oid(ee); - - /* - * Check for a label constraint. Remember, if the constraint label oid is - * InvalidOid, there isn't one. If there is one, they need to match. - */ - if (vlelctx->edge_label_name_oid != InvalidOid && - vlelctx->edge_label_name_oid != edge_label_name_oid) - { - return false; - } + num_edge_property_constraints = vlelctx->num_edge_property_constraints; /* - * Fast path: if the label matched (or wasn't constrained) and there - * are no property constraints, the edge is a match. This avoids - * accessing edge properties entirely for label-only VLE patterns - * like [:KNOWS*1..3] which are the common case. + * add_valid_vertex_edges() handles direction selection, label selection, + * and cycle pruning before consulting edge-state match status. This helper + * only verifies property constraints against an already label-compatible + * candidate. */ if (num_edge_property_constraints == 0) { @@ -425,7 +477,8 @@ static bool is_an_edge_match(VLE_local_context *vlelctx, edge_entry *ee) * it multiple times for the same edge. */ { - Datum edge_props_datum = get_edge_entry_properties(ee); + Datum edge_props_datum = get_edge_entry_properties_with_cache( + ee, relation_cache); edge_property = DATUM_GET_AGTYPE_P(edge_props_datum); agtc_edge_property_constraint = &vlelctx->edge_property_constraint->root; @@ -448,8 +501,16 @@ static bool is_an_edge_match(VLE_local_context *vlelctx, edge_entry *ee) */ if (num_edge_property_constraints == num_edge_properties) { - uint32 edge_props_hash = datum_image_hash(edge_props_datum, - false, -1); + uint32 edge_props_hash = 0; + + if (VARSIZE_ANY(edge_property) != + VARSIZE_ANY(vlelctx->edge_property_constraint)) + { + return false; + } + + edge_props_hash = datum_image_hash(edge_props_datum, false, -1); + /* check the hash first */ if (vlelctx->edge_property_constraint_hash == edge_props_hash) { @@ -465,6 +526,12 @@ static bool is_an_edge_match(VLE_local_context *vlelctx, edge_entry *ee) return false; } + if (vlelctx->num_cached_edge_property_constraints > 0) + { + return cached_edge_property_constraints_match( + vlelctx, agtc_edge_property); + } + /* get the iterators */ constraint_it = agtype_iterator_init(agtc_edge_property_constraint); property_it = agtype_iterator_init(agtc_edge_property); @@ -474,6 +541,353 @@ static bool is_an_edge_match(VLE_local_context *vlelctx, edge_entry *ee) } } +static void cache_edge_property_constraints(VLE_local_context *vlelctx) +{ + agtype_iterator *constraint_it = NULL; + agtype_iterator_token token; + agtype_pair *pairs = NULL; + agtype_value agtv; + int index; + + vlelctx->edge_property_constraint_pairs = NULL; + vlelctx->num_cached_edge_property_constraints = 0; + + if (vlelctx->num_edge_property_constraints <= 0) + { + return; + } + + pairs = palloc0(sizeof(agtype_pair) * + vlelctx->num_edge_property_constraints); + + constraint_it = agtype_iterator_init( + &vlelctx->edge_property_constraint->root); + token = agtype_iterator_next(&constraint_it, &agtv, true); + Assert(token == WAGT_BEGIN_OBJECT); + + for (index = 0; index < vlelctx->num_edge_property_constraints; index++) + { + token = agtype_iterator_next(&constraint_it, &pairs[index].key, true); + Assert(token == WAGT_KEY); + + token = agtype_iterator_next(&constraint_it, &pairs[index].value, true); + Assert(token == WAGT_VALUE); + } + + token = agtype_iterator_next(&constraint_it, &agtv, true); + Assert(token == WAGT_END_OBJECT); + + vlelctx->edge_property_constraint_pairs = pairs; + vlelctx->num_cached_edge_property_constraints = + vlelctx->num_edge_property_constraints; + +} + +static void free_edge_property_constraint_cache(VLE_local_context *vlelctx) +{ + int index; + + if (vlelctx->edge_property_constraint_pairs == NULL) + { + vlelctx->num_cached_edge_property_constraints = 0; + return; + } + + for (index = 0; index < vlelctx->num_cached_edge_property_constraints; + index++) + { + agtype_pair *pair = &vlelctx->edge_property_constraint_pairs[index]; + + pfree_if_not_null(pair->key.val.string.val); + free_cached_property_value(&pair->value); + } + + pfree(vlelctx->edge_property_constraint_pairs); + vlelctx->edge_property_constraint_pairs = NULL; + vlelctx->num_cached_edge_property_constraints = 0; +} + +static bool cached_edge_property_constraints_match( + VLE_local_context *vlelctx, agtype_container *edge_properties) +{ + int index; + + for (index = 0; index < vlelctx->num_cached_edge_property_constraints; + index++) + { + agtype_pair *constraint = + &vlelctx->edge_property_constraint_pairs[index]; + agtype_value property_value; + bool property_value_needs_free = false; + bool found; + bool matches; + + found = find_agtype_value_from_container_no_copy( + edge_properties, AGT_FOBJECT, &constraint->key, &property_value, + &property_value_needs_free); + if (!found) + { + return false; + } + + matches = agtype_value_satisfies_property_constraint( + &property_value, &constraint->value); + + if (property_value_needs_free) + { + pfree_agtype_value_content(&property_value); + } + + if (!matches) + { + return false; + } + } + + return true; +} + +static void free_cached_property_value(agtype_value *value) +{ + switch (value->type) + { + case AGTV_STRING: + pfree_if_not_null(value->val.string.val); + break; + + case AGTV_NUMERIC: + pfree_if_not_null(value->val.numeric); + break; + + case AGTV_VERTEX: + case AGTV_EDGE: + case AGTV_PATH: + case AGTV_ARRAY: + case AGTV_OBJECT: + pfree_agtype_value_content(value); + break; + + default: + break; + } +} + +static bool agtype_value_satisfies_property_constraint( + agtype_value *property_value, agtype_value *constraint_value) +{ + if (property_value->type != constraint_value->type) + { + return false; + } + + if (IS_A_AGTYPE_SCALAR(property_value)) + { + return compare_agtype_scalar_values(property_value, + constraint_value) == 0; + } + + Assert(property_value->type == AGTV_BINARY); + Assert(constraint_value->type == AGTV_BINARY); + + { + agtype_iterator *property_it = NULL; + agtype_iterator *constraint_it = NULL; + + property_it = agtype_iterator_init(property_value->val.binary.data); + constraint_it = agtype_iterator_init( + constraint_value->val.binary.data); + + return agtype_deep_contains(&property_it, &constraint_it, false); + } +} + +static bool get_vle_vertex_or_id_arg(FunctionCallInfo fcinfo, int argno, + const char *type_error_msg, + graphid *result) +{ + agtype *agt_arg; + agtype_value agtv_value; + agtype_value *id; + bool value_needs_free = false; + + if (PG_ARGISNULL(argno)) + { + return false; + } + + agt_arg = AG_GET_ARG_AGTYPE_P(argno); + + if (!AGTYPE_CONTAINER_IS_SCALAR(&agt_arg->root)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("age_vle: agtype argument must be a scalar"))); + } + + (void)get_ith_agtype_value_from_container_no_copy(&agt_arg->root, 0, + &agtv_value, + &value_needs_free); + if (agtv_value.type == AGTV_NULL) + { + if (value_needs_free) + pfree_agtype_value_content(&agtv_value); + return false; + } + + if (agtv_value.type == AGTV_VERTEX) + { + id = AGTYPE_VERTEX_GET_ID(&agtv_value); + } + else if (agtv_value.type == AGTV_INTEGER) + { + id = &agtv_value; + } + else + { + if (value_needs_free) + pfree_agtype_value_content(&agtv_value); + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s", type_error_msg))); + } + + *result = id->val.int_value; + + if (value_needs_free) + pfree_agtype_value_content(&agtv_value); + + return true; +} + +static void get_vle_scalar_arg_no_copy(char *funcname, agtype *agt_arg, + enum agtype_value_type type, bool error, + agtype_value *result, + bool *needs_free) +{ + bool found; + + Assert(funcname != NULL); + Assert(agt_arg != NULL); + Assert(result != NULL); + Assert(needs_free != NULL); + + if (!AGTYPE_CONTAINER_IS_SCALAR(&agt_arg->root)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s: agtype argument must be a scalar", funcname))); + } + + found = get_ith_agtype_value_from_container_no_copy(&agt_arg->root, 0, + result, needs_free); + Assert(found); + + if (error && result->type == AGTV_NULL) + { + if (*needs_free) + { + pfree_agtype_value_content(result); + } + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s: agtype argument must not be AGTV_NULL", + funcname))); + } + + if (error && result->type != type) + { + if (*needs_free) + { + pfree_agtype_value_content(result); + } + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s: agtype argument of wrong type", funcname))); + } +} + +static graphid get_agtype_scalar_graphid_arg(agtype *agt_arg, + const char *type_error_msg) +{ + agtype_value agtv_value; + bool value_needs_free = false; + graphid result; + bool found; + + if (!AGT_ROOT_IS_SCALAR(agt_arg)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s", type_error_msg))); + } + + found = get_ith_agtype_value_from_container_no_copy(&agt_arg->root, 0, + &agtv_value, + &value_needs_free); + Assert(found); + + if (agtv_value.type != AGTV_INTEGER) + { + if (value_needs_free) + { + pfree_agtype_value_content(&agtv_value); + } + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s", type_error_msg))); + } + + result = agtv_value.val.int_value; + + if (value_needs_free) + { + pfree_agtype_value_content(&agtv_value); + } + + return result; +} + +static bool get_agtype_scalar_bool_arg(agtype *agt_arg, + const char *type_error_msg) +{ + agtype_value agtv_value; + bool value_needs_free = false; + bool result; + bool found; + + if (!AGT_ROOT_IS_SCALAR(agt_arg)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s", type_error_msg))); + } + + found = get_ith_agtype_value_from_container_no_copy(&agt_arg->root, 0, + &agtv_value, + &value_needs_free); + Assert(found); + + if (agtv_value.type != AGTV_BOOL) + { + if (value_needs_free) + { + pfree_agtype_value_content(&agtv_value); + } + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s", type_error_msg))); + } + + result = agtv_value.val.boolean; + + if (value_needs_free) + { + pfree_agtype_value_content(&agtv_value); + } + + return result; +} + /* * Helper function to free up the memory used by the VLE_local_context. * @@ -504,9 +918,17 @@ static void free_VLE_local_context(VLE_local_context *vlelctx) vlelctx->edge_label_name = NULL; } - /* we need to free our state hashtable */ - hash_destroy(vlelctx->edge_state_hashtable); - vlelctx->edge_state_hashtable = NULL; + /* zero-length-only VLE never creates edge-state entries */ + if (vlelctx->edge_state_hashtable != NULL) + { + hash_destroy(vlelctx->edge_state_hashtable); + vlelctx->edge_state_hashtable = NULL; + } + + destroy_entry_property_relation_cache( + vlelctx->edge_property_relation_cache); + vlelctx->edge_property_relation_cache = NULL; + free_edge_property_constraint_cache(vlelctx); /* * Free the DFS stacks. When is_dirty is false, the stacks are in the @@ -522,54 +944,64 @@ static void free_VLE_local_context(VLE_local_context *vlelctx) { free_gid_stack(vlelctx->dfs_edge_stack); } + if (vlelctx->dfs_next_vertex_stack != NULL) + { + free_gid_stack(vlelctx->dfs_next_vertex_stack); + } if (vlelctx->dfs_path_stack != NULL) { free_gid_stack(vlelctx->dfs_path_stack); } + if (vlelctx->dfs_path_vertex_stack != NULL) + { + free_gid_stack(vlelctx->dfs_path_vertex_stack); + } vlelctx->dfs_vertex_stack = NULL; vlelctx->dfs_edge_stack = NULL; + vlelctx->dfs_next_vertex_stack = NULL; vlelctx->dfs_path_stack = NULL; + vlelctx->dfs_path_vertex_stack = NULL; /* and finally the context itself */ pfree_if_not_null(vlelctx); vlelctx = NULL; } -/* helper function to check if our start and end vertices exist */ -static bool do_vsid_and_veid_exist(VLE_local_context *vlelctx) -{ - /* if we are only using the starting vertex */ - if (vlelctx->path_function == VLE_FUNCTION_PATHS_FROM || - vlelctx->path_function == VLE_FUNCTION_PATHS_ALL) - { - return (get_vertex_entry(vlelctx->ggctx, vlelctx->vsid) != NULL); - } - - /* if we are only using the ending vertex */ - if (vlelctx->path_function == VLE_FUNCTION_PATHS_TO) - { - return (get_vertex_entry(vlelctx->ggctx, vlelctx->veid) != NULL); - } - - /* if we are using both start and end */ - return ((get_vertex_entry(vlelctx->ggctx, vlelctx->vsid) != NULL) && - (get_vertex_entry(vlelctx->ggctx, vlelctx->veid) != NULL)); -} - /* load the initial edges into the dfs_edge_stack */ static void load_initial_dfs_stacks(VLE_local_context *vlelctx) { + vertex_entry *start_vertex = NULL; + + vlelctx->dfs_path_vertex_stack->size = 0; + gid_stack_push(vlelctx->dfs_path_vertex_stack, vlelctx->vsid); + /* * If either the vsid or veid don't exist - don't load anything because * there won't be anything to find. */ - if (!do_vsid_and_veid_exist(vlelctx)) + if (is_empty_length_range(vlelctx) || + is_zero_length_only(vlelctx)) + { + return; + } + + start_vertex = get_vertex_entry(vlelctx->ggctx, vlelctx->vsid); + if (start_vertex == NULL) + { + return; + } + + if ((vlelctx->path_function == VLE_FUNCTION_PATHS_BETWEEN || + (vlelctx->path_function == VLE_FUNCTION_PATHS_TO && + !vlelctx->reverse_paths_to)) && + vlelctx->veid != vlelctx->vsid && + get_vertex_entry(vlelctx->ggctx, vlelctx->veid) == NULL) { return; } /* add in the edges for the start vertex */ - add_valid_vertex_edges(vlelctx, vlelctx->vsid); + add_valid_vertex_edges_for_entry(vlelctx, start_vertex); } /* @@ -582,14 +1014,18 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, MemoryContext oldctx = NULL; GRAPH_global_context *ggctx = NULL; VLE_local_context *vlelctx = NULL; - agtype_value *agtv_temp = NULL; + agtype_value agtv_temp; + bool agtv_temp_needs_free = false; agtype_value *agtv_object = NULL; agtype *agt_edge_property_constraint = NULL; + agtype *agt_arg = NULL; Datum d_edge_property_constraint = 0; char *graph_name = NULL; Oid graph_oid = InvalidOid; int64 vle_grammar_node_id = 0; bool use_cache = false; + graphid vertex_id_arg; + int64 selected_start_edge_count = 0; /* * Get the VLE grammar node id, if it exists. Remember, we overload the @@ -598,16 +1034,20 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, if (PG_NARGS() == 8) { /* get the VLE grammar node id */ - agtv_temp = get_agtype_value("age_vle", AG_GET_ARG_AGTYPE_P(7), - AGTV_INTEGER, true); - vle_grammar_node_id = agtv_temp->val.int_value; + get_vle_scalar_arg_no_copy("age_vle", AG_GET_ARG_AGTYPE_P(7), + AGTV_INTEGER, true, &agtv_temp, + &agtv_temp_needs_free); + vle_grammar_node_id = agtv_temp.val.int_value; /* we are using the VLE local context cache, so set it */ use_cache = true; } - /* fetch the VLE_local_context if it is cached */ - vlelctx = get_cached_VLE_local_context(vle_grammar_node_id); + /* fetch the VLE_local_context only when this call can reuse one */ + if (use_cache) + { + vlelctx = get_cached_VLE_local_context(vle_grammar_node_id); + } /* if we are caching VLE_local_contexts and this grammar node is cached */ if (use_cache && vlelctx != NULL) @@ -620,62 +1060,62 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, */ /* get and update the start vertex id */ - if (PG_ARGISNULL(1) || is_agtype_null(AG_GET_ARG_AGTYPE_P(1))) + if (!get_vle_vertex_or_id_arg( + fcinfo, 1, + "start vertex argument must be a vertex or the integer id", + &vertex_id_arg)) { - /* if there are no more vertices to process, return NULL */ - if (vlelctx->next_vertex == NULL) + if (!vlelctx->reverse_paths_to) { - return NULL; + /* if there are no more vertices to process, return NULL */ + if (vlelctx->next_vertex == NULL) + { + return NULL; + } + vlelctx->vsid = get_graphid(vlelctx->next_vertex); + /* increment to the next vertex */ + vlelctx->next_vertex = next_GraphIdNode(vlelctx->next_vertex); } - vlelctx->vsid = get_graphid(vlelctx->next_vertex); - /* increment to the next vertex */ - vlelctx->next_vertex = next_GraphIdNode(vlelctx->next_vertex); } else { - agtv_temp = get_agtype_value("age_vle", AG_GET_ARG_AGTYPE_P(1), - AGTV_VERTEX, false); - if (agtv_temp != NULL && agtv_temp->type == AGTV_VERTEX) - { - agtv_temp = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_temp, "id"); - } - else if (agtv_temp == NULL || agtv_temp->type != AGTV_INTEGER) - { - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("start vertex argument must be a vertex or the integer id"))); - } - vlelctx->vsid = agtv_temp->val.int_value; + vlelctx->vsid = vertex_id_arg; } /* get and update the end vertex id */ - if (PG_ARGISNULL(2) || is_agtype_null(AG_GET_ARG_AGTYPE_P(2))) + if (!get_vle_vertex_or_id_arg( + fcinfo, 2, + "end vertex argument must be a vertex or the integer id", + &vertex_id_arg)) { vlelctx->veid = 0; } else { - agtv_temp = get_agtype_value("age_vle", AG_GET_ARG_AGTYPE_P(2), - AGTV_VERTEX, false); - if (agtv_temp != NULL && agtv_temp->type == AGTV_VERTEX) - { - agtv_temp = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_temp, "id"); - } - else if (agtv_temp == NULL || agtv_temp->type != AGTV_INTEGER) + vlelctx->veid = vertex_id_arg; + if (vlelctx->reverse_paths_to) { - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("end vertex argument must be a vertex or the integer id"))); + vlelctx->vsid = vertex_id_arg; + vlelctx->next_vertex = NULL; } - vlelctx->veid = agtv_temp->val.int_value; + } + if (!vlelctx->reverse_paths_to && vlelctx->reverse_output_path) + { + graphid original_vsid = vlelctx->vsid; + + vlelctx->vsid = vlelctx->veid; + vlelctx->veid = original_vsid; } vlelctx->is_dirty = true; /* we need the SRF context to add in the edges to the stacks */ oldctx = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); - /* load the initial edges into the dfs stacks */ - load_initial_dfs_stacks(vlelctx); + if (!is_empty_length_range(vlelctx)) + { + /* load the initial edges into the dfs stacks */ + load_initial_dfs_stacks(vlelctx); + } /* switch back to the original context */ MemoryContextSwitchTo(oldctx); @@ -701,18 +1141,23 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, } /* get the graph name - this is a required argument */ - agtv_temp = get_agtype_value("age_vle", AG_GET_ARG_AGTYPE_P(0), - AGTV_STRING, true); - graph_name = pnstrdup(agtv_temp->val.string.val, - agtv_temp->val.string.len); - /* get the graph oid */ - graph_oid = get_graph_oid(graph_name); + get_vle_scalar_arg_no_copy("age_vle", AG_GET_ARG_AGTYPE_P(0), + AGTV_STRING, true, &agtv_temp, + &agtv_temp_needs_free); + /* get the graph oid before copying the graph name into VLE state */ + graph_oid = get_cached_vle_graph_oid(agtv_temp.val.string.val, + agtv_temp.val.string.len); + + graph_name = pnstrdup(agtv_temp.val.string.val, + agtv_temp.val.string.len); /* * 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); + ggctx = manage_GRAPH_global_contexts_len(agtv_temp.val.string.val, + agtv_temp.val.string.len, + graph_oid); /* allocate and initialize local VLE context */ vlelctx = palloc0(sizeof(VLE_local_context)); @@ -732,6 +1177,8 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, /* initialize the path function */ vlelctx->path_function = VLE_FUNCTION_PATHS_BETWEEN; + vlelctx->reverse_paths_to = false; + vlelctx->reverse_output_path = false; /* initialize the next vertex, in this case the first */ vlelctx->next_vertex = peek_stack_head(get_graph_vertices(ggctx)); @@ -746,7 +1193,10 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, * which path function is used. If a start vertex isn't provided, we * retrieve them incrementally from the vertices list. */ - if (PG_ARGISNULL(1) || is_agtype_null(AG_GET_ARG_AGTYPE_P(1))) + if (!get_vle_vertex_or_id_arg( + fcinfo, 1, + "start vertex argument must be a vertex or the integer id", + &vertex_id_arg)) { /* set _TO */ vlelctx->path_function = VLE_FUNCTION_PATHS_TO; @@ -758,26 +1208,17 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, } else { - agtv_temp = get_agtype_value("age_vle", AG_GET_ARG_AGTYPE_P(1), - AGTV_VERTEX, false); - if (agtv_temp != NULL && agtv_temp->type == AGTV_VERTEX) - { - agtv_temp = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_temp, "id"); - } - else if (agtv_temp == NULL || agtv_temp->type != AGTV_INTEGER) - { - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("start vertex argument must be a vertex or the integer id"))); - } - vlelctx->vsid = agtv_temp->val.int_value; + vlelctx->vsid = vertex_id_arg; } /* * Get the end vertex id - this is an optional parameter and determines * which path function is used. */ - if (PG_ARGISNULL(2) || is_agtype_null(AG_GET_ARG_AGTYPE_P(2))) + if (!get_vle_vertex_or_id_arg( + fcinfo, 2, + "end vertex argument must be a vertex or the integer id", + &vertex_id_arg)) { if (vlelctx->path_function == VLE_FUNCTION_PATHS_TO) { @@ -791,47 +1232,54 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, } else { - agtv_temp = get_agtype_value("age_vle", AG_GET_ARG_AGTYPE_P(2), - AGTV_VERTEX, false); - if (agtv_temp != NULL && agtv_temp->type == AGTV_VERTEX) + if (vlelctx->path_function != VLE_FUNCTION_PATHS_TO) { - agtv_temp = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_temp, "id"); + vlelctx->path_function = VLE_FUNCTION_PATHS_BETWEEN; } - else if (agtv_temp == NULL || agtv_temp->type != AGTV_INTEGER) - { - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("end vertex argument must be a vertex or the integer id"))); - } - vlelctx->path_function = VLE_FUNCTION_PATHS_BETWEEN; - vlelctx->veid = agtv_temp->val.int_value; + vlelctx->veid = vertex_id_arg; } /* get the VLE edge prototype */ - agtv_temp = get_agtype_value("age_vle", AG_GET_ARG_AGTYPE_P(3), - AGTV_EDGE, true); + get_vle_scalar_arg_no_copy("age_vle", AG_GET_ARG_AGTYPE_P(3), + AGTV_EDGE, true, &agtv_temp, + &agtv_temp_needs_free); /* get the edge prototype's property conditions */ - agtv_object = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_temp, "properties"); - agt_edge_property_constraint = agtype_value_to_agtype(agtv_object); - - /* store the properties as an agtype */ - vlelctx->edge_property_constraint = agt_edge_property_constraint; + agtv_object = AGTYPE_EDGE_GET_PROPERTIES(&agtv_temp); - d_edge_property_constraint = AGTYPE_P_GET_DATUM(agt_edge_property_constraint); - vlelctx->edge_property_constraint_datum = d_edge_property_constraint; - vlelctx->edge_property_constraint_hash = datum_image_hash(d_edge_property_constraint, false, -1); + vlelctx->num_edge_property_constraints = + agtv_object->val.object.num_pairs; + vlelctx->edge_property_relation_cache = NULL; + if (vlelctx->num_edge_property_constraints > 0) + { + agt_edge_property_constraint = agtype_value_to_agtype(agtv_object); + vlelctx->edge_property_constraint = agt_edge_property_constraint; + d_edge_property_constraint = + AGTYPE_P_GET_DATUM(agt_edge_property_constraint); + vlelctx->edge_property_constraint_datum = + d_edge_property_constraint; + vlelctx->edge_property_constraint_hash = + datum_image_hash(d_edge_property_constraint, false, -1); + cache_edge_property_constraints(vlelctx); + } + else + { + vlelctx->edge_property_constraint = NULL; + vlelctx->edge_property_constraint_datum = 0; + vlelctx->edge_property_constraint_hash = 0; + vlelctx->edge_property_constraint_pairs = NULL; + vlelctx->num_cached_edge_property_constraints = 0; + } /* get the edge prototype's label name */ - agtv_temp = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_temp, "label"); - if (agtv_temp->type == AGTV_STRING && - agtv_temp->val.string.len != 0) + agtv_object = AGTYPE_EDGE_GET_LABEL(&agtv_temp); + if (agtv_object->type == AGTV_STRING && + agtv_object->val.string.len != 0) { - vlelctx->edge_label_name = pnstrdup(agtv_temp->val.string.val, - agtv_temp->val.string.len); - - vlelctx->edge_label_name_oid = get_label_relation(vlelctx->edge_label_name, - graph_oid); + vlelctx->edge_label_name_oid = get_cached_vle_label_relation( + graph_oid, agtv_object->val.string.val, + agtv_object->val.string.len); + vlelctx->edge_label_name = NULL; } else { @@ -839,46 +1287,149 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, vlelctx->edge_label_name_oid = InvalidOid; } + if (agtv_temp_needs_free) + { + pfree_agtype_value_content(&agtv_temp); + agtv_temp_needs_free = false; + } + /* get the left range index */ - if (PG_ARGISNULL(4) || is_agtype_null(AG_GET_ARG_AGTYPE_P(4))) + if (PG_ARGISNULL(4)) { vlelctx->lidx = 1; } else { - agtv_temp = get_agtype_value("age_vle", AG_GET_ARG_AGTYPE_P(4), - AGTV_INTEGER, true); - vlelctx->lidx = agtv_temp->val.int_value; + agt_arg = AG_GET_ARG_AGTYPE_P(4); + if (is_agtype_null(agt_arg)) + { + vlelctx->lidx = 1; + } + else + { + get_vle_scalar_arg_no_copy("age_vle", agt_arg, AGTV_INTEGER, + true, &agtv_temp, + &agtv_temp_needs_free); + vlelctx->lidx = agtv_temp.val.int_value; + } } /* get the right range index. NULL means infinite */ - if (PG_ARGISNULL(5) || is_agtype_null(AG_GET_ARG_AGTYPE_P(5))) + if (PG_ARGISNULL(5)) { vlelctx->uidx_infinite = true; vlelctx->uidx = 0; } else { - agtv_temp = get_agtype_value("age_vle", AG_GET_ARG_AGTYPE_P(5), - AGTV_INTEGER, true); - vlelctx->uidx = agtv_temp->val.int_value; - vlelctx->uidx_infinite = false; + agt_arg = AG_GET_ARG_AGTYPE_P(5); + if (is_agtype_null(agt_arg)) + { + vlelctx->uidx_infinite = true; + vlelctx->uidx = 0; + } + else + { + get_vle_scalar_arg_no_copy("age_vle", agt_arg, AGTV_INTEGER, + true, &agtv_temp, + &agtv_temp_needs_free); + vlelctx->uidx = agtv_temp.val.int_value; + vlelctx->uidx_infinite = false; + } } /* get edge direction */ - agtv_temp = get_agtype_value("age_vle", AG_GET_ARG_AGTYPE_P(6), - AGTV_INTEGER, true); - vlelctx->edge_direction = agtv_temp->val.int_value; + get_vle_scalar_arg_no_copy("age_vle", AG_GET_ARG_AGTYPE_P(6), + AGTV_INTEGER, true, &agtv_temp, + &agtv_temp_needs_free); + vlelctx->edge_direction = agtv_temp.val.int_value; + + /* + * A paths-to query has a selective terminal vertex but no useful start + * vertex. Traverse from the bound end with the edge direction reversed and + * later reverse the emitted path container back to Cypher's left-to-right + * order. This avoids repeatedly launching DFS from every graph vertex. + */ + if (vlelctx->path_function == VLE_FUNCTION_PATHS_TO) + { + vlelctx->reverse_paths_to = true; + vlelctx->reverse_output_path = true; + vlelctx->vsid = vlelctx->veid; + vlelctx->next_vertex = NULL; + vlelctx->edge_direction = + reverse_edge_direction(vlelctx->edge_direction); + if (!is_empty_length_range(vlelctx) && + !is_zero_length_only(vlelctx)) + { + selected_start_edge_count = get_initial_edge_count( + vlelctx, vlelctx->vsid, vlelctx->edge_direction); + } + } + /* + * For two-bound VLE, choose the cheaper endpoint as the traversal root. + * Lower-zero queries are safe here because zero-path endpoint semantics are + * handled inside should_emit_zero_bound(). + */ + else if (!is_empty_length_range(vlelctx) && + !is_zero_length_only(vlelctx) && + vlelctx->path_function == VLE_FUNCTION_PATHS_BETWEEN) + { + int64 start_edge_count; + int64 end_edge_count; + + start_edge_count = get_initial_edge_count( + vlelctx, vlelctx->vsid, vlelctx->edge_direction); + end_edge_count = get_initial_edge_count( + vlelctx, vlelctx->veid, + reverse_edge_direction(vlelctx->edge_direction)); + + if (end_edge_count < start_edge_count) + { + graphid original_vsid = vlelctx->vsid; + + vlelctx->vsid = vlelctx->veid; + vlelctx->veid = original_vsid; + vlelctx->edge_direction = + reverse_edge_direction(vlelctx->edge_direction); + vlelctx->reverse_output_path = true; + selected_start_edge_count = end_edge_count; + } + else + { + selected_start_edge_count = start_edge_count; + } + } + else if (!is_empty_length_range(vlelctx) && + !is_zero_length_only(vlelctx)) + { + selected_start_edge_count = get_initial_edge_count( + vlelctx, vlelctx->vsid, vlelctx->edge_direction); + } - /* create the local state hashtable */ - create_VLE_local_state_hashtable(vlelctx); + if ((!is_empty_length_range(vlelctx) && + !is_zero_length_only(vlelctx)) || + use_cache) + { + vlelctx->edge_state_initial_size = Max( + selected_start_edge_count * Max(vlelctx->uidx_infinite ? 1 : + vlelctx->uidx, 1), + EDGE_STATE_HTAB_MIN_SIZE); - /* initialize the dfs stacks */ - vlelctx->dfs_vertex_stack = new_gid_stack(); - vlelctx->dfs_edge_stack = new_gid_stack(); - vlelctx->dfs_path_stack = new_gid_stack(); + /* create the local state hashtable */ + create_VLE_local_state_hashtable(vlelctx); + } - /* load in the starting edge(s) */ - load_initial_dfs_stacks(vlelctx); + if (!is_empty_length_range(vlelctx)) + { + /* initialize the dfs stacks */ + vlelctx->dfs_vertex_stack = new_gid_stack(); + vlelctx->dfs_edge_stack = new_gid_stack(); + vlelctx->dfs_next_vertex_stack = new_gid_stack(); + vlelctx->dfs_path_stack = new_gid_stack(); + vlelctx->dfs_path_vertex_stack = new_gid_stack(); + + /* load in the starting edge(s) */ + load_initial_dfs_stacks(vlelctx); + } /* this is a new one so nothing follows it */ vlelctx->next = NULL; @@ -899,6 +1450,117 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, return vlelctx; } +static Oid get_cached_vle_graph_oid(const char *graph_name, + int graph_name_len) +{ + static NameData cached_graph_name; + static Oid cached_graph_oid = InvalidOid; + static uint64 cached_generation = 0; + uint64 current_generation = get_graph_cache_generation(); + char *graph_name_cstr; + NameData graph_name_buf; + bool free_graph_name = false; + + if (OidIsValid(cached_graph_oid) && + cached_generation == current_generation && + graph_name_len < NAMEDATALEN && + strncmp(NameStr(cached_graph_name), graph_name, graph_name_len) == 0 && + NameStr(cached_graph_name)[graph_name_len] == '\0') + { + return cached_graph_oid; + } + + if (graph_name_len < NAMEDATALEN) + { + memcpy(NameStr(graph_name_buf), graph_name, graph_name_len); + NameStr(graph_name_buf)[graph_name_len] = '\0'; + graph_name_cstr = NameStr(graph_name_buf); + } + else + { + graph_name_cstr = pnstrdup(graph_name, graph_name_len); + free_graph_name = true; + } + + cached_graph_oid = get_graph_oid(graph_name_cstr); + if (OidIsValid(cached_graph_oid)) + { + if (graph_name_len < NAMEDATALEN) + { + cached_graph_name = graph_name_buf; + } + else + { + namestrcpy(&cached_graph_name, graph_name_cstr); + } + cached_generation = current_generation; + } + if (free_graph_name) + { + pfree(graph_name_cstr); + } + + return cached_graph_oid; +} + +static Oid get_cached_vle_label_relation(Oid graph_oid, + const char *label_name, + int label_name_len) +{ + static NameData cached_label_name; + static Oid cached_graph_oid = InvalidOid; + static Oid cached_relation_oid = InvalidOid; + static uint64 cached_generation = 0; + uint64 current_generation = get_label_cache_generation(); + char *label_name_cstr; + NameData label_name_buf; + bool free_label_name = false; + label_cache_data *label_cache; + + if (OidIsValid(cached_relation_oid) && + cached_graph_oid == graph_oid && + cached_generation == current_generation && + label_name_len < NAMEDATALEN && + strncmp(NameStr(cached_label_name), label_name, label_name_len) == 0 && + NameStr(cached_label_name)[label_name_len] == '\0') + { + return cached_relation_oid; + } + + if (label_name_len < NAMEDATALEN) + { + memcpy(NameStr(label_name_buf), label_name, label_name_len); + NameStr(label_name_buf)[label_name_len] = '\0'; + label_name_cstr = NameStr(label_name_buf); + } + else + { + label_name_cstr = pnstrdup(label_name, label_name_len); + free_label_name = true; + } + + label_cache = search_label_name_graph_cache_cached(label_name_cstr, + graph_oid); + cached_graph_oid = graph_oid; + cached_relation_oid = label_cache != NULL ? + label_cache->relation : InvalidOid; + cached_generation = current_generation; + if (label_name_len < NAMEDATALEN) + { + cached_label_name = label_name_buf; + } + else + { + namestrcpy(&cached_label_name, label_name_cstr); + } + if (free_label_name) + { + pfree(label_name_cstr); + } + + return cached_relation_oid; +} + /* * Helper function to get the specified edge's state. If it does not find it, it * creates and initializes it. @@ -980,6 +1642,155 @@ static graphid get_next_vertex(VLE_local_context *vlelctx, edge_entry *ee) return terminal_vertex_id; } +static graphid get_next_vertex_from_source(VLE_local_context *vlelctx, + edge_entry *ee, + graphid source_vertex_id) +{ + switch (vlelctx->edge_direction) + { + case CYPHER_REL_DIR_RIGHT: + return get_edge_entry_end_vertex_id(ee); + + case CYPHER_REL_DIR_LEFT: + return get_edge_entry_start_vertex_id(ee); + + case CYPHER_REL_DIR_NONE: + if (get_edge_entry_start_vertex_id(ee) == source_vertex_id) + { + return get_edge_entry_end_vertex_id(ee); + } + else if (get_edge_entry_end_vertex_id(ee) == source_vertex_id) + { + return get_edge_entry_start_vertex_id(ee); + } + elog(ERROR, "get_next_vertex_from_source: no parent match"); + + default: + elog(ERROR, "get_next_vertex_from_source: unknown edge direction"); + } +} + +static cypher_rel_dir reverse_edge_direction(cypher_rel_dir edge_direction) +{ + switch (edge_direction) + { + case CYPHER_REL_DIR_RIGHT: + return CYPHER_REL_DIR_LEFT; + + case CYPHER_REL_DIR_LEFT: + return CYPHER_REL_DIR_RIGHT; + + case CYPHER_REL_DIR_NONE: + return CYPHER_REL_DIR_NONE; + + default: + elog(ERROR, "reverse_edge_direction: unknown edge direction"); + } + + return CYPHER_REL_DIR_NONE; +} + +static bool is_zero_length_only(VLE_local_context *vlelctx) +{ + return vlelctx->lidx == 0 && + !vlelctx->uidx_infinite && + vlelctx->uidx == 0; +} + +static bool is_empty_length_range(VLE_local_context *vlelctx) +{ + return !vlelctx->uidx_infinite && + vlelctx->lidx > vlelctx->uidx; +} + +static bool should_emit_zero_bound(VLE_local_context *vlelctx) +{ + /* + * For two-bound VLE, zero length is valid only when both endpoints are the + * same vertex. When they differ, skip the zero path inside age_vle() so the + * parser does not need a terminal-edge post-filter just to discard it. + */ + if (vlelctx->path_function == VLE_FUNCTION_PATHS_BETWEEN) + { + return vlelctx->vsid == vlelctx->veid; + } + + return true; +} + +static int64 get_initial_edge_count(VLE_local_context *vlelctx, + graphid vertex_id, + cypher_rel_dir edge_direction) +{ + vertex_entry *ve = NULL; + int64 count = 0; + ListGraphId *edges = NULL; + + ve = get_vertex_entry(vlelctx->ggctx, vertex_id); + if (ve == NULL) + { + return 0; + } + + if (edge_direction == CYPHER_REL_DIR_RIGHT || + edge_direction == CYPHER_REL_DIR_NONE) + { + if (vlelctx->edge_label_name_oid != InvalidOid) + { + edges = get_vertex_entry_edges_out_for_label( + ve, vlelctx->edge_label_name_oid); + } + else + { + edges = get_vertex_entry_edges_out(ve); + } + count += get_matching_initial_edge_count(edges); + } + if (edge_direction == CYPHER_REL_DIR_LEFT || + edge_direction == CYPHER_REL_DIR_NONE) + { + if (vlelctx->edge_label_name_oid != InvalidOid) + { + edges = get_vertex_entry_edges_in_for_label( + ve, vlelctx->edge_label_name_oid); + } + else + { + edges = get_vertex_entry_edges_in(ve); + } + count += get_matching_initial_edge_count(edges); + } + + if (vlelctx->edge_label_name_oid != InvalidOid) + { + edges = get_vertex_entry_edges_self_for_label( + ve, vlelctx->edge_label_name_oid); + } + else + { + edges = get_vertex_entry_edges_self(ve); + } + count += get_matching_initial_edge_count(edges); + + return count; +} + +static int64 get_matching_initial_edge_count(ListGraphId *edges) +{ + if (edges == NULL) + { + return 0; + } + + /* + * Label-constrained callers pass a label-partitioned adjacency list, so + * every edge in the list already matches. Unlabeled callers need the raw + * adjacency size. In both cases ListGraphId tracks size, so avoid scanning + * the linked list during root-choice setup. + */ + return get_list_size(edges); +} + /* * Helper function to find one path BETWEEN two vertices. * @@ -996,7 +1807,9 @@ static bool dfs_find_a_path_between(VLE_local_context *vlelctx) { GraphIdStack *vertex_stack = NULL; GraphIdStack *edge_stack = NULL; + GraphIdStack *next_vertex_stack = NULL; GraphIdStack *path_stack = NULL; + GraphIdStack *path_vertex_stack = NULL; graphid end_vertex_id; Assert(vlelctx != NULL); @@ -1004,7 +1817,9 @@ static bool dfs_find_a_path_between(VLE_local_context *vlelctx) /* for ease of reading */ vertex_stack = vlelctx->dfs_vertex_stack; edge_stack = vlelctx->dfs_edge_stack; + next_vertex_stack = vlelctx->dfs_next_vertex_stack; path_stack = vlelctx->dfs_path_stack; + path_vertex_stack = vlelctx->dfs_path_vertex_stack; end_vertex_id = vlelctx->veid; /* while we have edges to process */ @@ -1014,10 +1829,15 @@ static bool dfs_find_a_path_between(VLE_local_context *vlelctx) graphid next_vertex_id; edge_state_entry *ese = NULL; edge_entry *ee = NULL; + graphid cached_next_vertex_id; bool found = false; + int64 path_length; + + Assert(gid_stack_size(edge_stack) == gid_stack_size(next_vertex_stack)); /* get an edge, but leave it on the stack for now */ edge_id = gid_stack_peek(edge_stack); + cached_next_vertex_id = gid_stack_peek(next_vertex_stack); /* get the edge's state */ ese = get_edge_state(vlelctx, edge_id); /* @@ -1041,10 +1861,12 @@ static bool dfs_find_a_path_between(VLE_local_context *vlelctx) if (edge_id == path_edge_id) { gid_stack_pop(path_stack); + gid_stack_pop(path_vertex_stack); ese->used_in_path = false; } /* now remove it from the edge stack */ gid_stack_pop(edge_stack); + gid_stack_pop(next_vertex_stack); /* * Remove its source vertex, if we are looking at edges as * un-directional. We only maintain the vertex stack when the @@ -1065,19 +1887,28 @@ static bool dfs_find_a_path_between(VLE_local_context *vlelctx) */ ese->used_in_path = true; gid_stack_push(path_stack, edge_id); + path_length = gid_stack_size(path_stack); - /* now get the edge entry so we can get the next vertex to move to */ - ee = get_edge_entry(vlelctx->ggctx, edge_id); - next_vertex_id = get_next_vertex(vlelctx, ee); + if (cached_next_vertex_id != VLE_NEXT_VERTEX_UNKNOWN) + { + next_vertex_id = cached_next_vertex_id; + } + else + { + /* get the edge entry so we can get the next vertex to move to */ + ee = get_edge_entry(vlelctx->ggctx, edge_id); + next_vertex_id = get_next_vertex(vlelctx, ee); + } + gid_stack_push(path_vertex_stack, next_vertex_id); /* * Is this the end of a path that meets our requirements? Is its length * within the bounds specified? */ if (next_vertex_id == end_vertex_id && - gid_stack_size(path_stack) >= vlelctx->lidx && + path_length >= vlelctx->lidx && (vlelctx->uidx_infinite || - gid_stack_size(path_stack) <= vlelctx->uidx)) + path_length <= vlelctx->uidx)) { /* we found one */ found = true; @@ -1089,14 +1920,14 @@ static bool dfs_find_a_path_between(VLE_local_context *vlelctx) */ if (next_vertex_id == end_vertex_id && !vlelctx->uidx_infinite && - gid_stack_size(path_stack) > vlelctx->uidx) + path_length > vlelctx->uidx) { continue; } /* add in the edges for the next vertex if we won't exceed the bounds */ if (vlelctx->uidx_infinite || - gid_stack_size(path_stack) < vlelctx->uidx) + path_length < vlelctx->uidx) { add_valid_vertex_edges(vlelctx, next_vertex_id); } @@ -1126,14 +1957,18 @@ static bool dfs_find_a_path_from(VLE_local_context *vlelctx) { GraphIdStack *vertex_stack = NULL; GraphIdStack *edge_stack = NULL; + GraphIdStack *next_vertex_stack = NULL; GraphIdStack *path_stack = NULL; + GraphIdStack *path_vertex_stack = NULL; Assert(vlelctx != NULL); /* for ease of reading */ vertex_stack = vlelctx->dfs_vertex_stack; edge_stack = vlelctx->dfs_edge_stack; + next_vertex_stack = vlelctx->dfs_next_vertex_stack; path_stack = vlelctx->dfs_path_stack; + path_vertex_stack = vlelctx->dfs_path_vertex_stack; /* while we have edges to process */ while (!(gid_stack_is_empty(edge_stack))) @@ -1142,10 +1977,15 @@ static bool dfs_find_a_path_from(VLE_local_context *vlelctx) graphid next_vertex_id; edge_state_entry *ese = NULL; edge_entry *ee = NULL; + graphid cached_next_vertex_id; bool found = false; + int64 path_length; + + Assert(gid_stack_size(edge_stack) == gid_stack_size(next_vertex_stack)); /* get an edge, but leave it on the stack for now */ edge_id = gid_stack_peek(edge_stack); + cached_next_vertex_id = gid_stack_peek(next_vertex_stack); /* get the edge's state */ ese = get_edge_state(vlelctx, edge_id); /* @@ -1169,10 +2009,12 @@ static bool dfs_find_a_path_from(VLE_local_context *vlelctx) if (edge_id == path_edge_id) { gid_stack_pop(path_stack); + gid_stack_pop(path_vertex_stack); ese->used_in_path = false; } /* now remove it from the edge stack */ gid_stack_pop(edge_stack); + gid_stack_pop(next_vertex_stack); /* * Remove its source vertex, if we are looking at edges as * un-directional. We only maintain the vertex stack when the @@ -1193,18 +2035,27 @@ static bool dfs_find_a_path_from(VLE_local_context *vlelctx) */ ese->used_in_path = true; gid_stack_push(path_stack, edge_id); + path_length = gid_stack_size(path_stack); - /* now get the edge entry so we can get the next vertex to move to */ - ee = get_edge_entry(vlelctx->ggctx, edge_id); - next_vertex_id = get_next_vertex(vlelctx, ee); + if (cached_next_vertex_id != VLE_NEXT_VERTEX_UNKNOWN) + { + next_vertex_id = cached_next_vertex_id; + } + else + { + /* get the edge entry so we can get the next vertex to move to */ + ee = get_edge_entry(vlelctx->ggctx, edge_id); + next_vertex_id = get_next_vertex(vlelctx, ee); + } + gid_stack_push(path_vertex_stack, next_vertex_id); /* * Is this a path that meets our requirements? Is its length within the * bounds specified? */ - if (gid_stack_size(path_stack) >= vlelctx->lidx && + if (path_length >= vlelctx->lidx && (vlelctx->uidx_infinite || - gid_stack_size(path_stack) <= vlelctx->uidx)) + path_length <= vlelctx->uidx)) { /* we found one */ found = true; @@ -1212,7 +2063,7 @@ static bool dfs_find_a_path_from(VLE_local_context *vlelctx) /* add in the edges for the next vertex if we won't exceed the bounds */ if (vlelctx->uidx_infinite || - gid_stack_size(path_stack) < vlelctx->uidx) + path_length < vlelctx->uidx) { add_valid_vertex_edges(vlelctx, next_vertex_id); } @@ -1232,15 +2083,17 @@ static bool dfs_find_a_path_from(VLE_local_context *vlelctx) * smaller sized lists. But, it is O(n) so it should only be used for small * path_stacks and where appropriate. */ -static bool is_edge_in_path(VLE_local_context *vlelctx, graphid edge_id) +static bool is_edge_in_path(VLE_local_context *vlelctx, graphid edge_id, + int64 stack_size) { GraphIdStack *stack = vlelctx->dfs_path_stack; + graphid *path_edges = stack->array; int64 i; - /* scan the array-based path stack */ - for (i = 0; i < gid_stack_size(stack); i++) + /* Scan from the tail; immediate back edges are common during DFS. */ + for (i = stack_size; i > 0; i--) { - if (gid_stack_get(stack, i) == edge_id) + if (path_edges[i - 1] == edge_id) { return true; } @@ -1262,13 +2115,7 @@ static bool is_edge_in_path(VLE_local_context *vlelctx, graphid edge_id) static void add_valid_vertex_edges(VLE_local_context *vlelctx, graphid vertex_id) { - GraphIdStack *vertex_stack = NULL; - GraphIdStack *edge_stack = NULL; - ListGraphId *edges = NULL; vertex_entry *ve = NULL; - GraphIdNode *edge_in = NULL; - GraphIdNode *edge_out = NULL; - GraphIdNode *edge_self = NULL; /* get the vertex entry */ ve = get_vertex_entry(vlelctx->ggctx, vertex_id); @@ -1278,33 +2125,92 @@ static void add_valid_vertex_edges(VLE_local_context *vlelctx, elog(ERROR, "add_valid_vertex_edges: no vertex found"); } + add_valid_vertex_edges_for_entry(vlelctx, ve); +} + +static void add_valid_vertex_edges_for_entry(VLE_local_context *vlelctx, + vertex_entry *ve) +{ + GraphIdStack *vertex_stack = NULL; + GraphIdStack *edge_stack = NULL; + GraphIdStack *next_vertex_stack = NULL; + ListGraphId *edges = NULL; + GraphIdNode *edge_in = NULL; + GraphIdNode *edge_out = NULL; + GraphIdNode *edge_self = NULL; + HTAB *relation_cache = NULL; + graphid source_vertex_id = 0; + int64 path_stack_size; + bool label_constrained = vlelctx->edge_label_name_oid != InvalidOid; + bool has_property_constraints = + vlelctx->num_edge_property_constraints > 0; + /* point to stacks */ vertex_stack = vlelctx->dfs_vertex_stack; edge_stack = vlelctx->dfs_edge_stack; + next_vertex_stack = vlelctx->dfs_next_vertex_stack; + path_stack_size = gid_stack_size(vlelctx->dfs_path_stack); + source_vertex_id = get_vertex_entry_id(ve); /* set to the first edge for each edge list for the specified direction */ if (vlelctx->edge_direction == CYPHER_REL_DIR_RIGHT || vlelctx->edge_direction == CYPHER_REL_DIR_NONE) { - edges = get_vertex_entry_edges_out(ve); + if (label_constrained) + { + edges = get_vertex_entry_edges_out_for_label( + ve, vlelctx->edge_label_name_oid); + } + else + { + edges = get_vertex_entry_edges_out(ve); + } edge_out = (edges != NULL) ? get_list_head(edges) : NULL; } if (vlelctx->edge_direction == CYPHER_REL_DIR_LEFT || vlelctx->edge_direction == CYPHER_REL_DIR_NONE) { - edges = get_vertex_entry_edges_in(ve); + if (label_constrained) + { + edges = get_vertex_entry_edges_in_for_label( + ve, vlelctx->edge_label_name_oid); + } + else + { + edges = get_vertex_entry_edges_in(ve); + } edge_in = (edges != NULL) ? get_list_head(edges) : NULL; } /* set to the first selfloop edge */ - edges = get_vertex_entry_edges_self(ve); + if (label_constrained) + { + edges = get_vertex_entry_edges_self_for_label( + ve, vlelctx->edge_label_name_oid); + } + else + { + edges = get_vertex_entry_edges_self(ve); + } edge_self = (edges != NULL) ? get_list_head(edges) : NULL; + if (has_property_constraints) + { + if (vlelctx->edge_property_relation_cache == NULL) + { + vlelctx->edge_property_relation_cache = + create_entry_property_relation_cache( + "VLE edge match relation cache"); + } + relation_cache = vlelctx->edge_property_relation_cache; + } + /* add in valid vertex edges */ while (edge_out != NULL || edge_in != NULL || edge_self != NULL) { edge_entry *ee = NULL; edge_state_entry *ese = NULL; graphid edge_id; + graphid fast_next_vertex_id = VLE_NEXT_VERTEX_UNKNOWN; /* get the edge_id from the next available edge*/ if (edge_out != NULL) @@ -1318,14 +2224,16 @@ static void add_valid_vertex_edges(VLE_local_context *vlelctx, else { edge_id = get_graphid(edge_self); + fast_next_vertex_id = source_vertex_id; } /* * This is a fast existence check, relative to the hash search, for when * the path stack is small. If the edge is in the path, we skip it. */ - if (gid_stack_size(vlelctx->dfs_path_stack) < 10 && - is_edge_in_path(vlelctx, edge_id)) + if (path_stack_size > 0 && + path_stack_size < 10 && + is_edge_in_path(vlelctx, edge_id, path_stack_size)) { /* set to the next available edge */ if (edge_out != NULL) @@ -1343,13 +2251,38 @@ static void add_valid_vertex_edges(VLE_local_context *vlelctx, continue; } - /* get the edge entry */ - ee = get_edge_entry(vlelctx->ggctx, edge_id); - /* it better exist */ - if (ee == NULL) + /* + * When the current path is short, the direct path-stack scan above + * already proved that this edge is not currently used. If there are no + * property constraints, every edge from the selected adjacency list is + * a match, so defer edge-state hash creation until DFS actually + * consumes the edge. + */ + if (path_stack_size < 10 && + !has_property_constraints) { - elog(ERROR, "add_valid_vertex_edges: no edge found"); + if (vlelctx->edge_direction == CYPHER_REL_DIR_NONE) + { + gid_stack_push(vertex_stack, source_vertex_id); + } + gid_stack_push(edge_stack, edge_id); + gid_stack_push(next_vertex_stack, fast_next_vertex_id); + + if (edge_out != NULL) + { + edge_out = next_GraphIdNode(edge_out); + } + else if (edge_in != NULL) + { + edge_in = next_GraphIdNode(edge_in); + } + else + { + edge_self = next_GraphIdNode(edge_self); + } + continue; } + /* get its state */ ese = get_edge_state(vlelctx, edge_id); /* @@ -1359,19 +2292,38 @@ static void add_valid_vertex_edges(VLE_local_context *vlelctx, if (!ese->used_in_path) { /* validate the edge if it hasn't been already */ - if (!ese->has_been_matched && is_an_edge_match(vlelctx, ee)) + if (!ese->has_been_matched && + !has_property_constraints) { ese->has_been_matched = true; ese->matched = true; } else if (!ese->has_been_matched) { + /* get the edge entry */ + ee = get_edge_entry(vlelctx->ggctx, edge_id); + /* it better exist */ + if (ee == NULL) + { + elog(ERROR, "add_valid_vertex_edges: no edge found"); + } + + if (is_an_edge_match(vlelctx, ee, relation_cache)) + { + ese->matched = true; + } + else + { + ese->matched = false; + } ese->has_been_matched = true; - ese->matched = false; } + /* if it is a match, add it */ if (ese->has_been_matched && ese->matched) { + graphid next_vertex_id; + /* * We need to maintain our source vertex for each edge added * if the edge_direction is CYPHER_REL_DIR_NONE. This is due @@ -1380,13 +2332,32 @@ static void add_valid_vertex_edges(VLE_local_context *vlelctx, * un-directional VLE edge, you don't know the vertex that * you just came from. So, we need to store it. */ - if (vlelctx->edge_direction == CYPHER_REL_DIR_NONE) - { - gid_stack_push(vertex_stack, get_vertex_entry_id(ve)); - } - gid_stack_push(edge_stack, edge_id); - } - } + if (vlelctx->edge_direction == CYPHER_REL_DIR_NONE) + { + gid_stack_push(vertex_stack, source_vertex_id); + } + if (fast_next_vertex_id != VLE_NEXT_VERTEX_UNKNOWN) + { + next_vertex_id = fast_next_vertex_id; + } + else + { + if (ee == NULL) + { + ee = get_edge_entry(vlelctx->ggctx, edge_id); + if (ee == NULL) + { + elog(ERROR, + "add_valid_vertex_edges: no edge found"); + } + } + next_vertex_id = get_next_vertex_from_source( + vlelctx, ee, source_vertex_id); + } + gid_stack_push(edge_stack, edge_id); + gid_stack_push(next_vertex_stack, next_vertex_id); + } + } /* get the next working edge */ if (edge_out != NULL) { @@ -1401,6 +2372,8 @@ static void add_valid_vertex_edges(VLE_local_context *vlelctx, edge_self = next_GraphIdNode(edge_self); } } + + /* The relation cache is owned by the VLE context and freed with it. */ } /* @@ -1426,7 +2399,7 @@ static VLE_path_container *create_VLE_path_container(int64 path_size) container_size_bytes = sizeof(graphid) * (path_size + 4); /* allocate the container */ - vpc = palloc0(container_size_bytes); + vpc = palloc(container_size_bytes); /* initialize the PG headers */ SET_VARSIZE(vpc, container_size_bytes); @@ -1436,7 +2409,6 @@ static VLE_path_container *create_VLE_path_container(int64 path_size) vpc->graphid_array_size = path_size; vpc->container_size_bytes = container_size_bytes; - /* the graphid array is already zeroed out */ /* all of the other fields are set by the caller */ return vpc; @@ -1470,10 +2442,11 @@ static VLE_path_container *create_VLE_path_container(int64 path_size) static VLE_path_container *build_VLE_path_container(VLE_local_context *vlelctx) { GraphIdStack *stack = vlelctx->dfs_path_stack; + GraphIdStack *vertex_stack = vlelctx->dfs_path_vertex_stack; VLE_path_container *vpc = NULL; graphid *graphid_array = NULL; - graphid vid = 0; - int index = 0; + graphid *edge_array = NULL; + graphid *vertex_array = NULL; int ssize = 0; int j = 0; @@ -1497,43 +2470,68 @@ static VLE_path_container *build_VLE_path_container(VLE_local_context *vlelctx) /* get the graphid_array from the container */ graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); - /* get and store the start vertex */ - vid = vlelctx->vsid; - graphid_array[0] = vid; - /* - * Fill in edge entries from the back to the front. The path stack - * is array-based with index 0 = bottom (first pushed) and - * index size-1 = top (last pushed). We iterate from top to bottom - * to fill the graphid_array from back to front. + * The path edge stack and path vertex stack are both array based with + * index 0 = first path element. DFS already computed each next vertex, so + * building the VLE container does not need to look up every edge entry + * again just to recover endpoint ids. */ - index = vpc->graphid_array_size - 2; + Assert(gid_stack_size(vertex_stack) == ssize + 1); + edge_array = stack->array; + vertex_array = vertex_stack->array; - for (j = ssize - 1; j >= 0; j--) + for (j = 0; j < ssize; j++) { - /* 0 is the vsid, we should never get here */ - Assert(index > 0); + graphid_array[j * 2] = vertex_array[j]; + graphid_array[(j * 2) + 1] = edge_array[j]; + } + graphid_array[ssize * 2] = vertex_array[ssize]; - /* store the edge from stack position j */ - graphid_array[index] = gid_stack_get(stack, j); + /* return the container */ + return vpc; +} - /* we need to skip over the interior vertices */ - index -= 2; - } +static VLE_path_container *build_reversed_VLE_path_container( + VLE_local_context *vlelctx) +{ + GraphIdStack *stack = vlelctx->dfs_path_stack; + GraphIdStack *vertex_stack = vlelctx->dfs_path_vertex_stack; + VLE_path_container *vpc = NULL; + graphid *graphid_array = NULL; + graphid *edge_array = NULL; + graphid *vertex_array = NULL; + int index = 0; + int ssize = 0; + int j = 0; - /* now add in the interior vertices, starting from the first edge */ - for (index = 1; index < vpc->graphid_array_size - 1; index += 2) + if (stack == NULL) { - edge_entry *ee = NULL; + return NULL; + } + + ssize = gid_stack_size(stack); + vpc = create_VLE_path_container((ssize * 2) + 1); + vpc->graph_oid = vlelctx->graph_oid; + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); - ee = get_edge_entry(vlelctx->ggctx, graphid_array[index]); - vid = (vid == get_edge_entry_start_vertex_id(ee)) ? - get_edge_entry_end_vertex_id(ee) : - get_edge_entry_start_vertex_id(ee); - graphid_array[index+1] = vid; + /* + * Reverse paths-to traversal starts at the Cypher right endpoint and walks + * backward. The path vertex stack stores traversal-order vertices, so the + * original Cypher order is the reverse of both stacks. + */ + Assert(gid_stack_size(vertex_stack) == ssize + 1); + edge_array = stack->array; + vertex_array = vertex_stack->array; + + graphid_array[0] = vertex_array[ssize]; + index = 1; + for (j = ssize - 1; j >= 0; j--) + { + graphid_array[index] = edge_array[j]; + graphid_array[index + 1] = vertex_array[j]; + index += 2; } - /* return the container */ return vpc; } @@ -1582,11 +2580,18 @@ static agtype_value *build_edge_list(VLE_path_container *vpc) { GRAPH_global_context *ggctx = NULL; agtype_in_state edges_result; + HTAB *relation_cache = NULL; Oid graph_oid = InvalidOid; graphid *graphid_array = NULL; int64 graphid_array_size = 0; int index = 0; + graphid_array_size = vpc->graphid_array_size; + if (graphid_array_size <= 1) + { + return build_empty_agtype_value_array(); + } + /* get the graph_oid */ graph_oid = vpc->graph_oid; @@ -1597,28 +2602,23 @@ static agtype_value *build_edge_list(VLE_path_container *vpc) /* get the graphid_array and size */ graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); - graphid_array_size = vpc->graphid_array_size; /* initialize our agtype array */ MemSet(&edges_result, 0, sizeof(agtype_in_state)); edges_result.res = push_agtype_value(&edges_result.parse_state, WAGT_BEGIN_ARRAY, NULL); + if (graphid_array_size > 3) + { + relation_cache = create_entry_property_relation_cache( + "vle edge materialization relation cache"); + } for (index = 1; index < graphid_array_size - 1; index += 2) { - char *label_name = NULL; - edge_entry *ee = NULL; agtype_value *agtv_edge = NULL; - /* get the edge entry from the hashtable */ - ee = get_edge_entry(ggctx, graphid_array[index]); - /* get the label name from the oid */ - label_name = get_rel_name(get_edge_entry_label_table_oid(ee)); - /* reconstruct the edge */ - agtv_edge = agtype_value_build_edge(get_edge_entry_id(ee), label_name, - get_edge_entry_end_vertex_id(ee), - get_edge_entry_start_vertex_id(ee), - get_edge_entry_properties(ee)); + agtv_edge = build_vle_edge_value(ggctx, graphid_array[index], + relation_cache); /* push the edge*/ edges_result.res = push_agtype_value(&edges_result.parse_state, WAGT_ELEM, agtv_edge); @@ -1627,6 +2627,7 @@ static agtype_value *build_edge_list(VLE_path_container *vpc) /* close our agtype array */ edges_result.res = push_agtype_value(&edges_result.parse_state, WAGT_END_ARRAY, NULL); + destroy_entry_property_relation_cache(relation_cache); /* make it an array */ edges_result.res->type = AGTV_ARRAY; @@ -1635,6 +2636,69 @@ static agtype_value *build_edge_list(VLE_path_container *vpc) return edges_result.res; } +static agtype *build_empty_agtype_array(void) +{ + agtype_in_state result; + agtype *agt_result; + + MemSet(&result, 0, sizeof(agtype_in_state)); + + result.res = push_agtype_value(&result.parse_state, WAGT_BEGIN_ARRAY, + NULL); + result.res = push_agtype_value(&result.parse_state, WAGT_END_ARRAY, NULL); + agt_result = agtype_value_to_agtype(result.res); + + pfree_agtype_in_state(&result); + + return agt_result; +} + +static agtype_value *build_empty_agtype_value_array(void) +{ + agtype_in_state result; + + MemSet(&result, 0, sizeof(agtype_in_state)); + + result.res = push_agtype_value(&result.parse_state, WAGT_BEGIN_ARRAY, + NULL); + result.res = push_agtype_value(&result.parse_state, WAGT_END_ARRAY, NULL); + result.res->type = AGTV_ARRAY; + + return result.res; +} + +static agtype_value *build_vle_edge_value(GRAPH_global_context *ggctx, + graphid edge_id, + HTAB *relation_cache) +{ + edge_entry *ee = NULL; + + ee = get_edge_entry(ggctx, edge_id); + Assert(ee != NULL); + + return agtype_value_build_edge(get_edge_entry_id(ee), + get_edge_entry_label_name(ee), + get_edge_entry_end_vertex_id(ee), + get_edge_entry_start_vertex_id(ee), + get_edge_entry_properties_with_cache( + ee, relation_cache)); +} + +static agtype_value *build_vle_vertex_value(GRAPH_global_context *ggctx, + graphid vertex_id, + HTAB *relation_cache) +{ + vertex_entry *ve = NULL; + + ve = get_vertex_entry(ggctx, vertex_id); + Assert(ve != NULL); + + return agtype_value_build_vertex(get_vertex_entry_id(ve), + get_vertex_entry_label_name(ve), + get_vertex_entry_properties_with_cache( + ve, relation_cache)); +} + /* * Helper function to build an array of type AGTV_PATH from an array of * graphids. @@ -1646,6 +2710,7 @@ static agtype_value *build_path(VLE_path_container *vpc) { GRAPH_global_context *ggctx = NULL; agtype_in_state path_result; + HTAB *relation_cache = NULL; Oid graph_oid = InvalidOid; graphid *graphid_array = NULL; int64 graphid_array_size = 0; @@ -1667,23 +2732,20 @@ static agtype_value *build_path(VLE_path_container *vpc) MemSet(&path_result, 0, sizeof(agtype_in_state)); path_result.res = push_agtype_value(&path_result.parse_state, WAGT_BEGIN_ARRAY, NULL); + if (graphid_array_size > 1) + { + relation_cache = create_entry_property_relation_cache( + "vle path materialization relation cache"); + } for (index = 0; index < graphid_array_size; index += 2) { - char *label_name = NULL; - vertex_entry *ve = NULL; - edge_entry *ee = NULL; agtype_value *agtv_vertex = NULL; agtype_value *agtv_edge = NULL; - /* get the vertex entry from the hashtable */ - ve = get_vertex_entry(ggctx, graphid_array[index]); - /* get the label name from the oid */ - label_name = get_rel_name(get_vertex_entry_label_table_oid(ve)); /* reconstruct the vertex */ - agtv_vertex = agtype_value_build_vertex(get_vertex_entry_id(ve), - label_name, - get_vertex_entry_properties(ve)); + agtv_vertex = build_vle_vertex_value(ggctx, graphid_array[index], + relation_cache); /* push the vertex */ path_result.res = push_agtype_value(&path_result.parse_state, WAGT_ELEM, agtv_vertex); @@ -1697,15 +2759,9 @@ static agtype_value *build_path(VLE_path_container *vpc) break; } - /* get the edge entry from the hashtable */ - ee = get_edge_entry(ggctx, graphid_array[index+1]); - /* get the label name from the oid */ - label_name = get_rel_name(get_edge_entry_label_table_oid(ee)); /* reconstruct the edge */ - agtv_edge = agtype_value_build_edge(get_edge_entry_id(ee), label_name, - get_edge_entry_end_vertex_id(ee), - get_edge_entry_start_vertex_id(ee), - get_edge_entry_properties(ee)); + agtv_edge = build_vle_edge_value(ggctx, graphid_array[index + 1], + relation_cache); /* push the edge*/ path_result.res = push_agtype_value(&path_result.parse_state, WAGT_ELEM, agtv_edge); @@ -1714,6 +2770,10 @@ static agtype_value *build_path(VLE_path_container *vpc) /* close our agtype array */ path_result.res = push_agtype_value(&path_result.parse_state, WAGT_END_ARRAY, NULL); + if (relation_cache != NULL) + { + destroy_entry_property_relation_cache(relation_cache); + } /* make it a path */ path_result.res->type = AGTV_PATH; @@ -1807,8 +2867,12 @@ Datum age_vle(PG_FUNCTION_ARGS) */ funcctx->user_fctx = vlelctx; + if (is_empty_length_range(vlelctx)) + { + done = true; + } /* if we are starting from zero [*0..x] flag it */ - if (vlelctx->lidx == 0) + else if (vlelctx->lidx == 0 && should_emit_zero_bound(vlelctx)) { is_zero_bound = true; done = true; @@ -1833,6 +2897,12 @@ Datum age_vle(PG_FUNCTION_ARGS) switch (vlelctx->path_function) { case VLE_FUNCTION_PATHS_TO: + if (vlelctx->reverse_paths_to) + { + found_a_path = dfs_find_a_path_from(vlelctx); + break; + } + /* fall through */ case VLE_FUNCTION_PATHS_BETWEEN: found_a_path = dfs_find_a_path_between(vlelctx); break; @@ -1870,7 +2940,11 @@ Datum age_vle(PG_FUNCTION_ARGS) load_initial_dfs_stacks(vlelctx); /* if we are starting from zero [*0..x] flag it */ - if (vlelctx->lidx == 0) + if (is_empty_length_range(vlelctx)) + { + done = true; + } + else if (vlelctx->lidx == 0 && should_emit_zero_bound(vlelctx)) { is_zero_bound = true; done = true; @@ -1912,7 +2986,14 @@ Datum age_vle(PG_FUNCTION_ARGS) * path_stack. This will also correct for the path_stack being last * in, first out. */ - vpc = build_VLE_path_container(vlelctx); + if (vlelctx->reverse_output_path) + { + vpc = build_reversed_VLE_path_container(vlelctx); + } + else + { + vpc = build_VLE_path_container(vlelctx); + } } /* otherwise, this is the zero boundary case [*0..x] */ else @@ -1926,6 +3007,10 @@ Datum age_vle(PG_FUNCTION_ARGS) /* otherwise, we are done and we need to cleanup and signal done */ else { + destroy_entry_property_relation_cache( + vlelctx->edge_property_relation_cache); + vlelctx->edge_property_relation_cache = NULL; + /* mark local context as clean */ vlelctx->is_dirty = false; @@ -1992,6 +3077,66 @@ agtype_value *agtv_materialize_vle_path(agtype *agt_arg_vpc) return agtv_path; } +int64 agt_vle_append_path_interior(agtype *agt_arg_vpc, + agtype_in_state *result) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + HTAB *relation_cache = NULL; + graphid *graphid_array = NULL; + Oid graph_oid = InvalidOid; + int64 graphid_array_size = 0; + int64 index; + int64 appended = 0; + + Assert(agt_arg_vpc != NULL); + Assert(result != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + graphid_array_size = vpc->graphid_array_size; + if (graphid_array_size <= 1) + { + return 0; + } + + graph_oid = vpc->graph_oid; + ggctx = find_GRAPH_global_context(graph_oid); + Assert(ggctx != NULL); + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + + if (graphid_array_size > 3) + { + relation_cache = create_entry_property_relation_cache( + "vle path interior materialization relation cache"); + } + + for (index = 1; index < graphid_array_size - 1; index++) + { + agtype_value *agtv_value = NULL; + + if (index % 2 == 1) + { + agtv_value = build_vle_edge_value(ggctx, graphid_array[index], + relation_cache); + } + else + { + agtv_value = build_vle_vertex_value(ggctx, graphid_array[index], + relation_cache); + } + + result->res = push_agtype_value(&result->parse_state, WAGT_ELEM, + agtv_value); + appended++; + } + + destroy_entry_property_relation_cache(relation_cache); + + return appended; +} + /* PG function to match 2 VLE edges */ PG_FUNCTION_INFO_V1(age_match_two_vle_edges); @@ -2028,8 +3173,6 @@ Datum age_match_two_vle_edges(PG_FUNCTION_ARGS) 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) || @@ -2044,13 +3187,15 @@ Datum age_match_two_vle_edges(PG_FUNCTION_ARGS) 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_FREE_IF_COPY(left_path, 0); + PG_FREE_IF_COPY(right_path, 1); PG_RETURN_BOOL(false); } + PG_FREE_IF_COPY(left_path, 0); + PG_FREE_IF_COPY(right_path, 1); PG_RETURN_BOOL(true); } @@ -2067,7 +3212,6 @@ Datum age_match_vle_edge_to_id_qual(PG_FUNCTION_ARGS) agtype *agt_arg_vpc = NULL; agtype *edge_id = NULL; agtype *pos_agt = NULL; - agtype_value *id, *position; VLE_path_container *vle_path = NULL; graphid *array = NULL; bool vle_is_on_left = false; @@ -2123,23 +3267,8 @@ Datum age_match_vle_edge_to_id_qual(PG_FUNCTION_ARGS) { /* Get the edge id we are checking the end of the list too */ edge_id = AG_GET_ARG_AGTYPE_P(1); - if (!AGT_ROOT_IS_SCALAR(edge_id)) - { - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("argument 2 of age_match_vle_edge_to_edge_qual must be an integer"))); - } - - id = get_ith_agtype_value_from_container(&edge_id->root, 0); - - if (id->type != AGTV_INTEGER) - { - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("argument 2 of age_match_vle_edge_to_edge_qual must be an integer"))); - } - - gid = id->val.int_value; + gid = get_agtype_scalar_graphid_arg(edge_id, + "argument 2 of age_match_vle_edge_to_edge_qual must be an integer"); } else if (type1 == GRAPHIDOID) { @@ -2154,23 +3283,8 @@ Datum age_match_vle_edge_to_id_qual(PG_FUNCTION_ARGS) pos_agt = AG_GET_ARG_AGTYPE_P(2); - if (!AGT_ROOT_IS_SCALAR(pos_agt)) - { - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("argument 3 of age_match_vle_edge_to_edge_qual must be an integer"))); - } - - position = get_ith_agtype_value_from_container(&pos_agt->root, 0); - - if (position->type != AGTV_BOOL) - { - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("argument 3 of age_match_vle_edge_to_edge_qual must be an integer"))); - } - - vle_is_on_left = position->val.boolean; + vle_is_on_left = get_agtype_scalar_bool_arg(pos_agt, + "argument 3 of age_match_vle_edge_to_edge_qual must be an integer"); if (vle_is_on_left) { @@ -2236,86 +3350,3825 @@ agtype_value *agtv_materialize_vle_edges(agtype *agt_arg_vpc) } -/* PG wrapper function for agtv_materialize_vle_edges */ -PG_FUNCTION_INFO_V1(age_materialize_vle_edges); - -Datum age_materialize_vle_edges(PG_FUNCTION_ARGS) +agtype_value *agtv_materialize_vle_edge_at(agtype *agt_arg_vpc, + int64 edge_index) { - agtype *agt_arg_vpc = NULL; - agtype_value *agtv_array = NULL; + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + graphid *graphid_array = NULL; + int64 edge_count = 0; + Oid graph_oid = InvalidOid; - /* if we have a NULL VLE_path_container, return NULL */ - if (PG_ARGISNULL(0)) - { - PG_RETURN_NULL(); - } + Assert(agt_arg_vpc != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); - /* get the VLE_path_container argument */ - agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = agtv_vle_edge_count(agt_arg_vpc); - /* if NULL, return NULL */ - if (is_agtype_null(agt_arg_vpc)) + if (edge_index < 0) { - PG_RETURN_NULL(); + edge_index = edge_count + edge_index; + } + if (edge_index < 0 || edge_index >= edge_count) + { + return NULL; } - agtv_array = agtv_materialize_vle_edges(agt_arg_vpc); + graph_oid = vpc->graph_oid; + ggctx = find_GRAPH_global_context(graph_oid); + Assert(ggctx != NULL); - PG_RETURN_POINTER(agtype_value_to_agtype(agtv_array)); -} + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); -/* PG wrapper function for age_materialize_vle_path */ -PG_FUNCTION_INFO_V1(age_materialize_vle_path); + return build_vle_edge_value(ggctx, graphid_array[(edge_index * 2) + 1], + NULL); +} -Datum age_materialize_vle_path(PG_FUNCTION_ARGS) +static agtype_value *agtv_materialize_vle_vertex_at(agtype *agt_arg_vpc, + int64 node_index) { - agtype *agt_arg_vpc = NULL; + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + graphid *graphid_array = NULL; + int64 node_count; - /* if we have a NULL VLE_path_container, return NULL */ - if (PG_ARGISNULL(0)) + Assert(agt_arg_vpc != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + node_count = agtv_vle_node_count(agt_arg_vpc); + if (node_index < 0) { - PG_RETURN_NULL(); + node_index = node_count + node_index; } - - /* get the VLE_path_container argument */ - agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); - - /* if NULL, return NULL */ - if (is_agtype_null(agt_arg_vpc)) + if (node_index < 0 || node_index >= node_count) { - PG_RETURN_NULL(); + return NULL; } - PG_RETURN_POINTER(agt_materialize_vle_path(agt_arg_vpc)); -} + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(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. - */ -PG_FUNCTION_INFO_V1(age_match_vle_terminal_edge); + return build_vle_vertex_value(ggctx, graphid_array[node_index * 2], + NULL); +} -Datum age_match_vle_terminal_edge(PG_FUNCTION_ARGS) +static agtype_value *agtv_materialize_vle_edge_endpoint_at( + agtype *agt_arg_vpc, int64 edge_index, bool start_endpoint) { + GRAPH_global_context *ggctx = NULL; 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; + edge_entry *ee = NULL; + graphid *graphid_array = NULL; + graphid endpoint_id; + graphid edge_id; + int64 edge_count = 0; + Oid graph_oid = InvalidOid; - /* check argument count */ - if (PG_NARGS() != 3) + Assert(agt_arg_vpc != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = agtv_vle_edge_count(agt_arg_vpc); + + if (edge_index < 0) { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("age_match_vle_terminal_edge() invalid number of arguments"))); + edge_index = edge_count + edge_index; } - + if (edge_index < 0 || edge_index >= edge_count) + { + return NULL; + } + + graph_oid = vpc->graph_oid; + ggctx = find_GRAPH_global_context(graph_oid); + Assert(ggctx != NULL); + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + edge_id = graphid_array[(edge_index * 2) + 1]; + ee = get_edge_entry(ggctx, edge_id); + Assert(ee != NULL); + + endpoint_id = start_endpoint ? + get_edge_entry_start_vertex_id(ee) : + get_edge_entry_end_vertex_id(ee); + + return build_vle_vertex_value(ggctx, endpoint_id, NULL); +} + +agtype_value *agtv_materialize_vle_edges_slice(agtype *agt_arg_vpc, + int64 lower_index, + int64 upper_index) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + agtype_in_state edges_result; + HTAB *relation_cache = NULL; + graphid *graphid_array = NULL; + Oid graph_oid = InvalidOid; + int64 index; + + Assert(agt_arg_vpc != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + if (lower_index >= upper_index) + { + return build_empty_agtype_value_array(); + } + + vpc = (VLE_path_container *)agt_arg_vpc; + graph_oid = vpc->graph_oid; + ggctx = find_GRAPH_global_context(graph_oid); + Assert(ggctx != NULL); + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + + MemSet(&edges_result, 0, sizeof(agtype_in_state)); + edges_result.res = push_agtype_value(&edges_result.parse_state, + WAGT_BEGIN_ARRAY, NULL); + + if (upper_index - lower_index > 1) + { + relation_cache = create_entry_property_relation_cache( + "vle edge slice materialization relation cache"); + } + + for (index = lower_index; index < upper_index; index++) + { + agtype_value *agtv_edge = NULL; + + agtv_edge = build_vle_edge_value( + ggctx, graphid_array[(index * 2) + 1], relation_cache); + edges_result.res = push_agtype_value(&edges_result.parse_state, + WAGT_ELEM, agtv_edge); + } + + edges_result.res = push_agtype_value(&edges_result.parse_state, + WAGT_END_ARRAY, NULL); + destroy_entry_property_relation_cache(relation_cache); + edges_result.res->type = AGTV_ARRAY; + + return edges_result.res; +} + +static agtype_value *agtv_materialize_vle_edges_reversed_slice( + agtype *agt_arg_vpc, int64 lower_index, int64 upper_index) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + agtype_in_state edges_result; + HTAB *relation_cache = NULL; + graphid *graphid_array = NULL; + Oid graph_oid = InvalidOid; + int64 edge_count; + int64 index; + + Assert(agt_arg_vpc != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + if (lower_index >= upper_index) + { + return build_empty_agtype_value_array(); + } + + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = agtv_vle_edge_count(agt_arg_vpc); + graph_oid = vpc->graph_oid; + ggctx = find_GRAPH_global_context(graph_oid); + Assert(ggctx != NULL); + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + + MemSet(&edges_result, 0, sizeof(agtype_in_state)); + edges_result.res = push_agtype_value(&edges_result.parse_state, + WAGT_BEGIN_ARRAY, NULL); + + if (upper_index - lower_index > 1) + { + relation_cache = create_entry_property_relation_cache( + "vle reversed edge slice materialization relation cache"); + } + + for (index = upper_index - 1; index >= lower_index; index--) + { + agtype_value *agtv_edge = NULL; + int64 original_index = edge_count - index - 1; + + agtv_edge = build_vle_edge_value( + ggctx, graphid_array[(original_index * 2) + 1], + relation_cache); + edges_result.res = push_agtype_value(&edges_result.parse_state, + WAGT_ELEM, agtv_edge); + } + + edges_result.res = push_agtype_value(&edges_result.parse_state, + WAGT_END_ARRAY, NULL); + destroy_entry_property_relation_cache(relation_cache); + edges_result.res->type = AGTV_ARRAY; + + return edges_result.res; +} + +agtype_value *agtv_materialize_vle_edges_reversed(agtype *agt_arg_vpc) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + agtype_in_state edges_result; + HTAB *relation_cache = NULL; + graphid *graphid_array = NULL; + Oid graph_oid = InvalidOid; + int64 edge_count; + int64 index; + + Assert(agt_arg_vpc != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = agtv_vle_edge_count(agt_arg_vpc); + if (edge_count == 0) + { + return build_empty_agtype_value_array(); + } + + graph_oid = vpc->graph_oid; + ggctx = find_GRAPH_global_context(graph_oid); + Assert(ggctx != NULL); + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + + MemSet(&edges_result, 0, sizeof(agtype_in_state)); + edges_result.res = push_agtype_value(&edges_result.parse_state, + WAGT_BEGIN_ARRAY, NULL); + + if (edge_count > 1) + { + relation_cache = create_entry_property_relation_cache( + "vle edge reverse materialization relation cache"); + } + + for (index = edge_count - 1; index >= 0; index--) + { + agtype_value *agtv_edge = NULL; + + agtv_edge = build_vle_edge_value( + ggctx, graphid_array[(index * 2) + 1], relation_cache); + edges_result.res = push_agtype_value(&edges_result.parse_state, + WAGT_ELEM, agtv_edge); + } + + edges_result.res = push_agtype_value(&edges_result.parse_state, + WAGT_END_ARRAY, NULL); + destroy_entry_property_relation_cache(relation_cache); + edges_result.res->type = AGTV_ARRAY; + + return edges_result.res; +} + +agtype *agt_vle_edge_properties_at(agtype *agt_arg_vpc, int64 edge_index) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + edge_entry *ee = NULL; + graphid *graphid_array = NULL; + graphid edge_id; + int64 edge_count; + + Assert(agt_arg_vpc != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = (vpc->graphid_array_size - 1) / 2; + if (edge_index < 0) + { + edge_index = edge_count + edge_index; + } + if (edge_index < 0 || edge_index >= edge_count) + { + return NULL; + } + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + edge_id = graphid_array[(edge_index * 2) + 1]; + + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + ee = get_edge_entry(ggctx, edge_id); + Assert(ee != NULL); + + return DATUM_GET_AGTYPE_P(get_edge_entry_properties(ee)); +} + +agtype_value *agtv_materialize_vle_nodes(agtype *agt_arg_vpc) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + agtype_in_state nodes_result; + HTAB *relation_cache = NULL; + graphid *graphid_array = NULL; + Oid graph_oid = InvalidOid; + int64 graphid_array_size = 0; + int64 index; + + Assert(agt_arg_vpc != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + graph_oid = vpc->graph_oid; + ggctx = find_GRAPH_global_context(graph_oid); + Assert(ggctx != NULL); + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + graphid_array_size = vpc->graphid_array_size; + + MemSet(&nodes_result, 0, sizeof(agtype_in_state)); + nodes_result.res = push_agtype_value(&nodes_result.parse_state, + WAGT_BEGIN_ARRAY, NULL); + + if (graphid_array_size > 1) + { + relation_cache = create_entry_property_relation_cache( + "vle node materialization relation cache"); + } + + for (index = 0; index < graphid_array_size; index += 2) + { + agtype_value *agtv_vertex = NULL; + + agtv_vertex = build_vle_vertex_value(ggctx, graphid_array[index], + relation_cache); + nodes_result.res = push_agtype_value(&nodes_result.parse_state, + WAGT_ELEM, agtv_vertex); + } + + nodes_result.res = push_agtype_value(&nodes_result.parse_state, + WAGT_END_ARRAY, NULL); + destroy_entry_property_relation_cache(relation_cache); + nodes_result.res->type = AGTV_ARRAY; + + return nodes_result.res; +} + +static agtype_value *agtv_materialize_vle_nodes_slice(agtype *agt_arg_vpc, + int64 lower_index, + int64 upper_index) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + agtype_in_state nodes_result; + HTAB *relation_cache = NULL; + graphid *graphid_array = NULL; + Oid graph_oid = InvalidOid; + int64 index; + + Assert(agt_arg_vpc != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + if (lower_index >= upper_index) + { + return build_empty_agtype_value_array(); + } + + vpc = (VLE_path_container *)agt_arg_vpc; + graph_oid = vpc->graph_oid; + ggctx = find_GRAPH_global_context(graph_oid); + Assert(ggctx != NULL); + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + + MemSet(&nodes_result, 0, sizeof(agtype_in_state)); + nodes_result.res = push_agtype_value(&nodes_result.parse_state, + WAGT_BEGIN_ARRAY, NULL); + + if (upper_index - lower_index > 1) + { + relation_cache = create_entry_property_relation_cache( + "vle node slice materialization relation cache"); + } + + for (index = lower_index; index < upper_index; index++) + { + agtype_value *agtv_vertex = NULL; + + agtv_vertex = build_vle_vertex_value( + ggctx, graphid_array[index * 2], relation_cache); + nodes_result.res = push_agtype_value(&nodes_result.parse_state, + WAGT_ELEM, agtv_vertex); + } + + nodes_result.res = push_agtype_value(&nodes_result.parse_state, + WAGT_END_ARRAY, NULL); + destroy_entry_property_relation_cache(relation_cache); + nodes_result.res->type = AGTV_ARRAY; + + return nodes_result.res; +} + +static agtype_value *agtv_materialize_vle_nodes_reversed_slice( + agtype *agt_arg_vpc, int64 lower_index, int64 upper_index) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + agtype_in_state nodes_result; + HTAB *relation_cache = NULL; + graphid *graphid_array = NULL; + Oid graph_oid = InvalidOid; + int64 node_count; + int64 index; + + Assert(agt_arg_vpc != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + if (lower_index >= upper_index) + { + return build_empty_agtype_value_array(); + } + + vpc = (VLE_path_container *)agt_arg_vpc; + graph_oid = vpc->graph_oid; + ggctx = find_GRAPH_global_context(graph_oid); + Assert(ggctx != NULL); + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + node_count = agtv_vle_node_count(agt_arg_vpc); + + MemSet(&nodes_result, 0, sizeof(agtype_in_state)); + nodes_result.res = push_agtype_value(&nodes_result.parse_state, + WAGT_BEGIN_ARRAY, NULL); + + if (upper_index - lower_index > 1) + { + relation_cache = create_entry_property_relation_cache( + "vle reversed node slice materialization relation cache"); + } + + for (index = upper_index - 1; index >= lower_index; index--) + { + agtype_value *agtv_vertex = NULL; + int64 original_index = node_count - index - 1; + + agtv_vertex = build_vle_vertex_value( + ggctx, graphid_array[original_index * 2], relation_cache); + nodes_result.res = push_agtype_value(&nodes_result.parse_state, + WAGT_ELEM, agtv_vertex); + } + + nodes_result.res = push_agtype_value(&nodes_result.parse_state, + WAGT_END_ARRAY, NULL); + destroy_entry_property_relation_cache(relation_cache); + nodes_result.res->type = AGTV_ARRAY; + + return nodes_result.res; +} + +static agtype_value *agtv_materialize_vle_nodes_reversed(agtype *agt_arg_vpc) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + agtype_in_state nodes_result; + HTAB *relation_cache = NULL; + graphid *graphid_array = NULL; + Oid graph_oid = InvalidOid; + int64 node_count; + int64 index; + + Assert(agt_arg_vpc != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + graph_oid = vpc->graph_oid; + ggctx = find_GRAPH_global_context(graph_oid); + Assert(ggctx != NULL); + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + node_count = agtv_vle_node_count(agt_arg_vpc); + + MemSet(&nodes_result, 0, sizeof(agtype_in_state)); + nodes_result.res = push_agtype_value(&nodes_result.parse_state, + WAGT_BEGIN_ARRAY, NULL); + + if (node_count > 1) + { + relation_cache = create_entry_property_relation_cache( + "vle node reverse materialization relation cache"); + } + + for (index = node_count - 1; index >= 0; index--) + { + agtype_value *agtv_vertex = NULL; + + agtv_vertex = build_vle_vertex_value( + ggctx, graphid_array[index * 2], relation_cache); + nodes_result.res = push_agtype_value(&nodes_result.parse_state, + WAGT_ELEM, agtv_vertex); + } + + nodes_result.res = push_agtype_value(&nodes_result.parse_state, + WAGT_END_ARRAY, NULL); + destroy_entry_property_relation_cache(relation_cache); + nodes_result.res->type = AGTV_ARRAY; + + return nodes_result.res; +} + +int64 agtv_vle_edge_count(agtype *agt_arg_vpc) +{ + VLE_path_container *vpc = NULL; + + Assert(agt_arg_vpc != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + + return (vpc->graphid_array_size - 1) / 2; +} + +bool agt_vle_contains_edge_id(agtype *agt_arg_vpc, graphid edge_id) +{ + VLE_path_container *vpc = NULL; + graphid *graphid_array = NULL; + int64 graphid_array_size = 0; + int64 i; + + Assert(agt_arg_vpc != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + graphid_array_size = vpc->graphid_array_size; + + for (i = 1; i < graphid_array_size - 1; i += 2) + { + if (graphid_array[i] == edge_id) + { + return true; + } + } + + return false; +} + +static bool get_vle_edge_id_at_index(agtype *agt_arg_vpc, int64 edge_index, + graphid *edge_id) +{ + VLE_path_container *vpc = NULL; + graphid *graphid_array = NULL; + int64 edge_count; + + Assert(agt_arg_vpc != NULL); + Assert(edge_id != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = (vpc->graphid_array_size - 1) / 2; + if (edge_index < 0) + { + edge_index = edge_count + edge_index; + } + if (edge_index < 0 || edge_index >= edge_count) + { + return false; + } + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + *edge_id = graphid_array[(edge_index * 2) + 1]; + + return true; +} + +static int64 agtv_vle_node_count(agtype *agt_arg_vpc) +{ + VLE_path_container *vpc = NULL; + + Assert(agt_arg_vpc != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + + return (vpc->graphid_array_size + 1) / 2; +} + +/* PG wrapper function for agtv_materialize_vle_edges */ +PG_FUNCTION_INFO_V1(age_materialize_vle_edges); + +Datum age_materialize_vle_edges(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value *agtv_array = NULL; + int64 edge_count; + + /* if we have a NULL VLE_path_container, return NULL */ + if (PG_ARGISNULL(0)) + { + PG_RETURN_NULL(); + } + + /* get the VLE_path_container argument */ + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + + /* if NULL, return NULL */ + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + edge_count = agtv_vle_edge_count(agt_arg_vpc); + if (edge_count == 0) + { + PG_RETURN_POINTER(build_empty_agtype_array()); + } + + agtv_array = agtv_materialize_vle_edges(agt_arg_vpc); + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_array)); +} + +PG_FUNCTION_INFO_V1(age_materialize_vle_nodes); + +Datum age_materialize_vle_nodes(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value *agtv_array = NULL; + + if (PG_ARGISNULL(0)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + agtv_array = agtv_materialize_vle_nodes(agt_arg_vpc); + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_array)); +} + +PG_FUNCTION_INFO_V1(age_vle_path_node_count); + +Datum age_vle_path_node_count(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value agtv_result; + + if (PG_ARGISNULL(0)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + agtv_result.type = AGTV_INTEGER; + agtv_result.val.int_value = agtv_vle_node_count(agt_arg_vpc); + + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); +} + +PG_FUNCTION_INFO_V1(age_vle_edge_tail_count); + +Datum age_vle_edge_tail_count(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value agtv_result; + int64 edge_count; + + if (PG_ARGISNULL(0)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + edge_count = agtv_vle_edge_count(agt_arg_vpc); + agtv_result.type = AGTV_INTEGER; + agtv_result.val.int_value = edge_count > 0 ? edge_count - 1 : 0; + + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); +} + +PG_FUNCTION_INFO_V1(age_vle_list_is_empty); + +Datum age_vle_list_is_empty(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value agtv_mode; + bool mode_needs_free = false; + int64 edge_count; + int64 node_count; + int64 mode; + bool result; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_list_is_empty", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_mode, &mode_needs_free); + mode = agtv_mode.val.int_value; + if (mode_needs_free) + { + pfree_agtype_value_content(&agtv_mode); + } + + edge_count = agtv_vle_edge_count(agt_arg_vpc); + node_count = edge_count + 1; + + switch (mode) + { + case 0: + result = node_count == 0; + break; + + case 1: + result = edge_count == 0; + break; + + case 2: + result = node_count <= 1; + break; + + case 3: + result = edge_count <= 1; + break; + + default: + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("age_vle_list_is_empty: invalid mode"))); + } + + PG_RETURN_BOOL(result); +} + +PG_FUNCTION_INFO_V1(age_vle_list_slice_count); + +Datum age_vle_list_slice_count(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype *agt_lidx = NULL; + agtype *agt_uidx = NULL; + agtype_value lidx_value; + agtype_value uidx_value; + agtype_value mode_value; + agtype_value agtv_result; + bool lidx_needs_free = false; + bool uidx_needs_free = false; + bool mode_needs_free = false; + int64 edge_count; + int64 list_count; + int64 lower_index = 0; + int64 upper_index = 0; + int64 mode; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(3)) + { + PG_RETURN_NULL(); + } + + if (PG_ARGISNULL(1) && PG_ARGISNULL(2)) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("slice start and/or end is required"))); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_list_slice_count", + AG_GET_ARG_AGTYPE_P(3), AGTV_INTEGER, true, + &mode_value, &mode_needs_free); + mode = mode_value.val.int_value; + if (mode_needs_free) + { + pfree_agtype_value_content(&mode_value); + } + + edge_count = agtv_vle_edge_count(agt_arg_vpc); + switch (mode) + { + case 0: + list_count = edge_count; + break; + case 1: + list_count = edge_count + 1; + break; + case 2: + list_count = Max(edge_count - 1, 0); + break; + case 3: + list_count = edge_count; + break; + case 6: + list_count = Max(edge_count - 2, 0); + break; + case 7: + list_count = Max(edge_count - 1, 0); + break; + case 8: + list_count = Max(edge_count - 1, 0); + break; + case 9: + list_count = edge_count; + break; + case 10: + list_count = Max(edge_count - 1, 0); + break; + case 11: + list_count = edge_count; + break; + default: + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("age_vle_list_slice_count: invalid mode"))); + } + + if (PG_ARGISNULL(1)) + { + lower_index = 0; + } + else + { + agt_lidx = AG_GET_ARG_AGTYPE_P(1); + get_vle_scalar_arg_no_copy("age_vle_list_slice_count", + agt_lidx, AGTV_INTEGER, false, + &lidx_value, &lidx_needs_free); + if (lidx_value.type == AGTV_NULL) + { + if (lidx_needs_free) + { + pfree_agtype_value_content(&lidx_value); + } + PG_RETURN_NULL(); + } + if (lidx_value.type != AGTV_INTEGER) + { + if (lidx_needs_free) + { + pfree_agtype_value_content(&lidx_value); + } + ereport(ERROR, + (errmsg("array slices must resolve to an integer value"))); + } + lower_index = lidx_value.val.int_value; + if (lidx_needs_free) + { + pfree_agtype_value_content(&lidx_value); + } + } + + if (PG_ARGISNULL(2)) + { + upper_index = list_count; + } + else + { + agt_uidx = AG_GET_ARG_AGTYPE_P(2); + get_vle_scalar_arg_no_copy("age_vle_list_slice_count", + agt_uidx, AGTV_INTEGER, false, + &uidx_value, &uidx_needs_free); + if (uidx_value.type == AGTV_NULL) + { + if (uidx_needs_free) + { + pfree_agtype_value_content(&uidx_value); + } + PG_RETURN_NULL(); + } + if (uidx_value.type != AGTV_INTEGER) + { + if (uidx_needs_free) + { + pfree_agtype_value_content(&uidx_value); + } + ereport(ERROR, + (errmsg("array slices must resolve to an integer value"))); + } + upper_index = uidx_value.val.int_value; + if (uidx_needs_free) + { + pfree_agtype_value_content(&uidx_value); + } + } + + if (lower_index < 0) + { + lower_index = list_count + lower_index; + } + if (lower_index < 0) + { + lower_index = 0; + } + if (lower_index > list_count) + { + lower_index = list_count; + } + if (upper_index < 0) + { + upper_index = list_count + upper_index; + } + if (upper_index < 0) + { + upper_index = 0; + } + if (upper_index > list_count) + { + upper_index = list_count; + } + + agtv_result.type = AGTV_INTEGER; + agtv_result.val.int_value = Max(upper_index - lower_index, 0); + + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); +} + +PG_FUNCTION_INFO_V1(age_vle_list_slice_is_empty); + +Datum age_vle_list_slice_is_empty(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype *agt_lidx = NULL; + agtype *agt_uidx = NULL; + agtype_value lidx_value; + agtype_value uidx_value; + agtype_value mode_value; + bool lidx_needs_free = false; + bool uidx_needs_free = false; + bool mode_needs_free = false; + int64 edge_count; + int64 list_count; + int64 lower_index = 0; + int64 upper_index = 0; + int64 mode; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(3)) + { + PG_RETURN_NULL(); + } + + if (PG_ARGISNULL(1) && PG_ARGISNULL(2)) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("slice start and/or end is required"))); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_list_slice_is_empty", + AG_GET_ARG_AGTYPE_P(3), AGTV_INTEGER, true, + &mode_value, &mode_needs_free); + mode = mode_value.val.int_value; + if (mode_needs_free) + { + pfree_agtype_value_content(&mode_value); + } + + edge_count = agtv_vle_edge_count(agt_arg_vpc); + switch (mode) + { + case 0: + list_count = edge_count; + break; + case 1: + list_count = edge_count + 1; + break; + case 2: + list_count = Max(edge_count - 1, 0); + break; + case 3: + list_count = edge_count; + break; + case 6: + list_count = Max(edge_count - 2, 0); + break; + case 7: + list_count = Max(edge_count - 1, 0); + break; + case 8: + list_count = Max(edge_count - 1, 0); + break; + case 9: + list_count = edge_count; + break; + case 10: + list_count = Max(edge_count - 1, 0); + break; + case 11: + list_count = edge_count; + break; + default: + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("age_vle_list_slice_is_empty: invalid mode"))); + } + + if (PG_ARGISNULL(1)) + { + lower_index = 0; + } + else + { + agt_lidx = AG_GET_ARG_AGTYPE_P(1); + get_vle_scalar_arg_no_copy("age_vle_list_slice_is_empty", + agt_lidx, AGTV_INTEGER, false, + &lidx_value, &lidx_needs_free); + if (lidx_value.type == AGTV_NULL) + { + if (lidx_needs_free) + { + pfree_agtype_value_content(&lidx_value); + } + PG_RETURN_NULL(); + } + if (lidx_value.type != AGTV_INTEGER) + { + if (lidx_needs_free) + { + pfree_agtype_value_content(&lidx_value); + } + ereport(ERROR, + (errmsg("array slices must resolve to an integer value"))); + } + lower_index = lidx_value.val.int_value; + if (lidx_needs_free) + { + pfree_agtype_value_content(&lidx_value); + } + } + + if (PG_ARGISNULL(2)) + { + upper_index = list_count; + } + else + { + agt_uidx = AG_GET_ARG_AGTYPE_P(2); + get_vle_scalar_arg_no_copy("age_vle_list_slice_is_empty", + agt_uidx, AGTV_INTEGER, false, + &uidx_value, &uidx_needs_free); + if (uidx_value.type == AGTV_NULL) + { + if (uidx_needs_free) + { + pfree_agtype_value_content(&uidx_value); + } + PG_RETURN_NULL(); + } + if (uidx_value.type != AGTV_INTEGER) + { + if (uidx_needs_free) + { + pfree_agtype_value_content(&uidx_value); + } + ereport(ERROR, + (errmsg("array slices must resolve to an integer value"))); + } + upper_index = uidx_value.val.int_value; + if (uidx_needs_free) + { + pfree_agtype_value_content(&uidx_value); + } + } + + if (lower_index < 0) + { + lower_index = list_count + lower_index; + } + if (lower_index < 0) + { + lower_index = 0; + } + if (lower_index > list_count) + { + lower_index = list_count; + } + if (upper_index < 0) + { + upper_index = list_count + upper_index; + } + if (upper_index < 0) + { + upper_index = 0; + } + if (upper_index > list_count) + { + upper_index = list_count; + } + + PG_RETURN_BOOL(upper_index <= lower_index); +} + +PG_FUNCTION_INFO_V1(age_materialize_vle_slice_boundary); + +Datum age_materialize_vle_slice_boundary(PG_FUNCTION_ARGS) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + agtype *agt_arg_vpc = NULL; + agtype *agt_lidx = NULL; + agtype *agt_uidx = NULL; + agtype_value lidx_value; + agtype_value uidx_value; + agtype_value mode_value; + agtype_value id_result; + agtype_value label_result; + agtype_value label_array_elem; + agtype_in_state label_array_result; + agtype_value *agtv_result = NULL; + bool lidx_needs_free = false; + bool uidx_needs_free = false; + bool mode_needs_free = false; + bool return_id = false; + bool return_label = false; + bool return_labels = false; + bool return_properties = false; + bool return_endpoint = false; + bool return_endpoint_id = false; + bool return_endpoint_label = false; + bool return_endpoint_labels = false; + bool return_endpoint_properties = false; + bool start_endpoint = false; + bool double_tail_list = false; + bool tail_reverse_list = false; + bool reverse_list = false; + bool return_node = false; + bool return_last = false; + graphid *graphid_array = NULL; + int64 edge_count; + int64 list_count; + int64 lower_index = 0; + int64 upper_index = 0; + int64 base_index = 0; + int64 original_index; + int64 mode; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(3)) + { + PG_RETURN_NULL(); + } + + if (PG_ARGISNULL(1) && PG_ARGISNULL(2)) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("slice start and/or end is required"))); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_materialize_vle_slice_boundary", + AG_GET_ARG_AGTYPE_P(3), AGTV_INTEGER, true, + &mode_value, &mode_needs_free); + mode = mode_value.val.int_value; + if (mode_needs_free) + { + pfree_agtype_value_content(&mode_value); + } + if (mode >= 360) + { + double_tail_list = true; + mode -= 360; + } + else if (mode >= 240) + { + tail_reverse_list = true; + mode -= 240; + } + else if (mode >= 120) + { + reverse_list = true; + mode -= 120; + } + if (mode >= 8 && mode <= 15) + { + return_id = true; + mode -= 8; + } + else if (mode >= 16 && mode <= 23) + { + return_label = true; + mode -= 16; + } + else if (mode >= 24 && mode <= 31) + { + return_labels = true; + mode -= 24; + } + else if (mode >= 32 && mode <= 39) + { + return_properties = true; + mode -= 32; + } + else if (mode >= 40 && mode <= 47) + { + return_endpoint = true; + start_endpoint = true; + mode -= 40; + } + else if (mode >= 48 && mode <= 55) + { + return_endpoint = true; + start_endpoint = false; + mode -= 48; + } + else if (mode >= 56 && mode <= 63) + { + return_endpoint_id = true; + start_endpoint = true; + mode -= 56; + } + else if (mode >= 64 && mode <= 71) + { + return_endpoint_id = true; + start_endpoint = false; + mode -= 64; + } + else if (mode >= 72 && mode <= 79) + { + return_endpoint_label = true; + start_endpoint = true; + mode -= 72; + } + else if (mode >= 80 && mode <= 87) + { + return_endpoint_label = true; + start_endpoint = false; + mode -= 80; + } + else if (mode >= 88 && mode <= 95) + { + return_endpoint_labels = true; + start_endpoint = true; + mode -= 88; + } + else if (mode >= 96 && mode <= 103) + { + return_endpoint_labels = true; + start_endpoint = false; + mode -= 96; + } + else if (mode >= 104 && mode <= 111) + { + return_endpoint_properties = true; + start_endpoint = true; + mode -= 104; + } + else if (mode >= 112 && mode <= 119) + { + return_endpoint_properties = true; + start_endpoint = false; + mode -= 112; + } + + edge_count = agtv_vle_edge_count(agt_arg_vpc); + switch (mode) + { + case 0: + case 1: + list_count = edge_count; + return_node = false; + return_last = mode == 1; + break; + case 2: + case 3: + list_count = edge_count + 1; + return_node = true; + return_last = mode == 3; + break; + case 4: + case 5: + if (double_tail_list) + { + list_count = Max(edge_count - 2, 0); + base_index = 2; + } + else if (tail_reverse_list) + { + list_count = Max(edge_count - 1, 0); + } + else + { + list_count = Max(edge_count - 1, 0); + base_index = 1; + } + return_node = false; + return_last = mode == 5; + break; + case 6: + case 7: + if (double_tail_list) + { + list_count = Max(edge_count - 1, 0); + base_index = 2; + } + else if (tail_reverse_list) + { + list_count = edge_count; + } + else + { + list_count = edge_count; + base_index = 1; + } + return_node = true; + return_last = mode == 7; + break; + default: + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("age_materialize_vle_slice_boundary: invalid mode"))); + } + + if (PG_ARGISNULL(1)) + { + lower_index = 0; + } + else + { + agt_lidx = AG_GET_ARG_AGTYPE_P(1); + get_vle_scalar_arg_no_copy("age_materialize_vle_slice_boundary", + agt_lidx, AGTV_INTEGER, false, + &lidx_value, &lidx_needs_free); + if (lidx_value.type == AGTV_NULL) + { + if (lidx_needs_free) + { + pfree_agtype_value_content(&lidx_value); + } + PG_RETURN_NULL(); + } + if (lidx_value.type != AGTV_INTEGER) + { + if (lidx_needs_free) + { + pfree_agtype_value_content(&lidx_value); + } + ereport(ERROR, + (errmsg("array slices must resolve to an integer value"))); + } + lower_index = lidx_value.val.int_value; + if (lidx_needs_free) + { + pfree_agtype_value_content(&lidx_value); + } + } + + if (PG_ARGISNULL(2)) + { + upper_index = list_count; + } + else + { + agt_uidx = AG_GET_ARG_AGTYPE_P(2); + get_vle_scalar_arg_no_copy("age_materialize_vle_slice_boundary", + agt_uidx, AGTV_INTEGER, false, + &uidx_value, &uidx_needs_free); + if (uidx_value.type == AGTV_NULL) + { + if (uidx_needs_free) + { + pfree_agtype_value_content(&uidx_value); + } + PG_RETURN_NULL(); + } + if (uidx_value.type != AGTV_INTEGER) + { + if (uidx_needs_free) + { + pfree_agtype_value_content(&uidx_value); + } + ereport(ERROR, + (errmsg("array slices must resolve to an integer value"))); + } + upper_index = uidx_value.val.int_value; + if (uidx_needs_free) + { + pfree_agtype_value_content(&uidx_value); + } + } + + if (lower_index < 0) + { + lower_index = list_count + lower_index; + } + if (lower_index < 0) + { + lower_index = 0; + } + if (lower_index > list_count) + { + lower_index = list_count; + } + if (upper_index < 0) + { + upper_index = list_count + upper_index; + } + if (upper_index < 0) + { + upper_index = 0; + } + if (upper_index > list_count) + { + upper_index = list_count; + } + + if (upper_index <= lower_index) + { + PG_RETURN_NULL(); + } + + original_index = return_last ? upper_index - 1 : lower_index; + if (reverse_list || tail_reverse_list) + { + original_index = list_count - original_index - 1; + } + original_index += base_index; + vpc = (VLE_path_container *)agt_arg_vpc; + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + + if (return_node) + { + graphid node_id = graphid_array[original_index * 2]; + + if (return_endpoint || return_endpoint_id || return_endpoint_label || + return_endpoint_labels || return_endpoint_properties) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("age_materialize_vle_slice_boundary: endpoint mode is invalid for nodes"))); + } + + if (return_id) + { + id_result.type = AGTV_INTEGER; + id_result.val.int_value = node_id; + PG_RETURN_POINTER(agtype_value_to_agtype(&id_result)); + } + + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + + if (return_label || return_labels || return_properties) + { + vertex_entry *ve = get_vertex_entry(ggctx, node_id); + char *label_name = NULL; + + Assert(ve != NULL); + if (return_properties) + { + PG_RETURN_DATUM(get_vertex_entry_properties(ve)); + } + + label_name = get_vertex_entry_label_name(ve); + label_result.type = AGTV_STRING; + label_result.val.string.val = label_name; + label_result.val.string.len = strlen(label_name); + if (return_label) + { + PG_RETURN_POINTER(agtype_value_to_agtype(&label_result)); + } + + label_array_elem = label_result; + MemSet(&label_array_result, 0, sizeof(agtype_in_state)); + label_array_result.res = push_agtype_value( + &label_array_result.parse_state, WAGT_BEGIN_ARRAY, NULL); + label_array_result.res = push_agtype_value( + &label_array_result.parse_state, WAGT_ELEM, + &label_array_elem); + label_array_result.res = push_agtype_value( + &label_array_result.parse_state, WAGT_END_ARRAY, NULL); + + PG_RETURN_POINTER(agtype_value_to_agtype(label_array_result.res)); + } + + agtv_result = build_vle_vertex_value(ggctx, node_id, NULL); + } + else + { + graphid edge_id = graphid_array[(original_index * 2) + 1]; + + if (return_id) + { + id_result.type = AGTV_INTEGER; + id_result.val.int_value = edge_id; + PG_RETURN_POINTER(agtype_value_to_agtype(&id_result)); + } + + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + + if (return_label || return_properties) + { + edge_entry *ee = get_edge_entry(ggctx, edge_id); + char *label_name = NULL; + + Assert(ee != NULL); + if (return_properties) + { + PG_RETURN_DATUM(get_edge_entry_properties(ee)); + } + + label_name = get_edge_entry_label_name(ee); + label_result.type = AGTV_STRING; + label_result.val.string.val = label_name; + label_result.val.string.len = strlen(label_name); + + PG_RETURN_POINTER(agtype_value_to_agtype(&label_result)); + } + if (return_labels) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("age_materialize_vle_slice_boundary: labels mode is invalid for relationships"))); + } + if (return_endpoint || return_endpoint_id || return_endpoint_label || + return_endpoint_labels || return_endpoint_properties) + { + edge_entry *ee = get_edge_entry(ggctx, edge_id); + graphid endpoint_id; + + Assert(ee != NULL); + endpoint_id = start_endpoint ? + get_edge_entry_start_vertex_id(ee) : + get_edge_entry_end_vertex_id(ee); + + if (return_endpoint_id) + { + id_result.type = AGTV_INTEGER; + id_result.val.int_value = endpoint_id; + PG_RETURN_POINTER(agtype_value_to_agtype(&id_result)); + } + if (return_endpoint) + { + agtv_result = build_vle_vertex_value(ggctx, endpoint_id, NULL); + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result)); + } + if (return_endpoint_label || return_endpoint_labels || + return_endpoint_properties) + { + vertex_entry *ve = get_vertex_entry(ggctx, endpoint_id); + char *label_name = NULL; + + Assert(ve != NULL); + if (return_endpoint_properties) + { + PG_RETURN_DATUM(get_vertex_entry_properties(ve)); + } + + label_name = get_vertex_entry_label_name(ve); + label_result.type = AGTV_STRING; + label_result.val.string.val = label_name; + label_result.val.string.len = strlen(label_name); + if (return_endpoint_label) + { + PG_RETURN_POINTER(agtype_value_to_agtype(&label_result)); + } + + label_array_elem = label_result; + MemSet(&label_array_result, 0, sizeof(agtype_in_state)); + label_array_result.res = push_agtype_value( + &label_array_result.parse_state, WAGT_BEGIN_ARRAY, NULL); + label_array_result.res = push_agtype_value( + &label_array_result.parse_state, WAGT_ELEM, + &label_array_elem); + label_array_result.res = push_agtype_value( + &label_array_result.parse_state, WAGT_END_ARRAY, NULL); + + PG_RETURN_POINTER( + agtype_value_to_agtype(label_array_result.res)); + } + } + + agtv_result = build_vle_edge_value(ggctx, edge_id, NULL); + } + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result)); +} + +PG_FUNCTION_INFO_V1(age_materialize_vle_node_at); + +Datum age_materialize_vle_node_at(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value agtv_index; + agtype_value *agtv_vertex = NULL; + bool index_needs_free = false; + int64 node_index; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_materialize_vle_node_at", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_index, &index_needs_free); + + node_index = agtv_index.val.int_value; + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + + agtv_vertex = agtv_materialize_vle_vertex_at(agt_arg_vpc, node_index); + if (agtv_vertex == NULL) + { + PG_RETURN_NULL(); + } + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_vertex)); +} + +PG_FUNCTION_INFO_V1(age_materialize_vle_node_reversed_at); + +Datum age_materialize_vle_node_reversed_at(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value agtv_index; + agtype_value *agtv_vertex = NULL; + bool index_needs_free = false; + int64 node_index; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_materialize_vle_node_reversed_at", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_index, &index_needs_free); + + node_index = agtv_index.val.int_value; + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + + if (node_index == PG_INT64_MIN) + { + PG_RETURN_NULL(); + } + + agtv_vertex = agtv_materialize_vle_vertex_at( + agt_arg_vpc, -node_index - 1); + if (agtv_vertex == NULL) + { + PG_RETURN_NULL(); + } + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_vertex)); +} + +PG_FUNCTION_INFO_V1(age_materialize_vle_node_tail_last); + +Datum age_materialize_vle_node_tail_last(PG_FUNCTION_ARGS) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + agtype *agt_arg_vpc = NULL; + graphid *graphid_array = NULL; + int64 node_count; + agtype_value *agtv_vertex = NULL; + + if (PG_ARGISNULL(0)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + node_count = agtv_vle_node_count(agt_arg_vpc); + if (node_count <= 1) + { + PG_RETURN_NULL(); + } + + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + + agtv_vertex = build_vle_vertex_value( + ggctx, graphid_array[(node_count - 1) * 2], NULL); + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_vertex)); +} + +PG_FUNCTION_INFO_V1(age_vle_node_tail_last_id); + +Datum age_vle_node_tail_last_id(PG_FUNCTION_ARGS) +{ + VLE_path_container *vpc = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value agtv_result; + graphid *graphid_array = NULL; + int64 node_count; + + if (PG_ARGISNULL(0)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + node_count = agtv_vle_node_count(agt_arg_vpc); + if (node_count <= 1) + { + PG_RETURN_NULL(); + } + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + agtv_result.type = AGTV_INTEGER; + agtv_result.val.int_value = graphid_array[(node_count - 1) * 2]; + + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); +} + +PG_FUNCTION_INFO_V1(age_vle_node_id_at); + +Datum age_vle_node_id_at(PG_FUNCTION_ARGS) +{ + VLE_path_container *vpc = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value agtv_index; + agtype_value agtv_result; + bool index_needs_free = false; + graphid *graphid_array = NULL; + int64 node_count; + int64 node_index; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_node_id_at", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_index, &index_needs_free); + + node_index = agtv_index.val.int_value; + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + node_count = (vpc->graphid_array_size + 1) / 2; + if (node_index < 0) + { + node_index = node_count + node_index; + } + if (node_index < 0 || node_index >= node_count) + { + PG_RETURN_NULL(); + } + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + agtv_result.type = AGTV_INTEGER; + agtv_result.val.int_value = graphid_array[node_index * 2]; + + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); +} + +PG_FUNCTION_INFO_V1(age_vle_node_label_at); + +Datum age_vle_node_label_at(PG_FUNCTION_ARGS) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + vertex_entry *ve = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value agtv_index; + agtype_value agtv_result; + bool index_needs_free = false; + graphid *graphid_array = NULL; + graphid node_id; + int64 node_count; + int64 node_index; + char *label_name = NULL; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_node_label_at", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_index, &index_needs_free); + + node_index = agtv_index.val.int_value; + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + node_count = (vpc->graphid_array_size + 1) / 2; + if (node_index < 0) + { + node_index = node_count + node_index; + } + if (node_index < 0 || node_index >= node_count) + { + PG_RETURN_NULL(); + } + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + node_id = graphid_array[node_index * 2]; + + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + ve = get_vertex_entry(ggctx, node_id); + Assert(ve != NULL); + + label_name = get_vertex_entry_label_name(ve); + agtv_result.type = AGTV_STRING; + agtv_result.val.string.val = label_name; + agtv_result.val.string.len = strlen(label_name); + + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); +} + +PG_FUNCTION_INFO_V1(age_vle_node_labels_at); + +Datum age_vle_node_labels_at(PG_FUNCTION_ARGS) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + vertex_entry *ve = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value agtv_index; + agtype_value agtv_label; + agtype_in_state agis_result; + bool index_needs_free = false; + graphid *graphid_array = NULL; + graphid node_id; + int64 node_count; + int64 node_index; + char *label_name = NULL; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_node_labels_at", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_index, &index_needs_free); + + node_index = agtv_index.val.int_value; + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + node_count = (vpc->graphid_array_size + 1) / 2; + if (node_index < 0) + { + node_index = node_count + node_index; + } + if (node_index < 0 || node_index >= node_count) + { + PG_RETURN_NULL(); + } + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + node_id = graphid_array[node_index * 2]; + + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + ve = get_vertex_entry(ggctx, node_id); + Assert(ve != NULL); + + label_name = get_vertex_entry_label_name(ve); + agtv_label.type = AGTV_STRING; + agtv_label.val.string.val = label_name; + agtv_label.val.string.len = strlen(label_name); + + MemSet(&agis_result, 0, sizeof(agtype_in_state)); + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_BEGIN_ARRAY, NULL); + agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, + &agtv_label); + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_END_ARRAY, NULL); + + PG_RETURN_POINTER(agtype_value_to_agtype(agis_result.res)); +} + +PG_FUNCTION_INFO_V1(age_vle_node_properties_at); + +Datum age_vle_node_properties_at(PG_FUNCTION_ARGS) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + vertex_entry *ve = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value agtv_index; + bool index_needs_free = false; + graphid *graphid_array = NULL; + graphid node_id; + int64 node_count; + int64 node_index; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_node_properties_at", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_index, &index_needs_free); + + node_index = agtv_index.val.int_value; + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + node_count = (vpc->graphid_array_size + 1) / 2; + if (node_index < 0) + { + node_index = node_count + node_index; + } + if (node_index < 0 || node_index >= node_count) + { + PG_RETURN_NULL(); + } + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + node_id = graphid_array[node_index * 2]; + + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + ve = get_vertex_entry(ggctx, node_id); + Assert(ve != NULL); + + PG_RETURN_DATUM(get_vertex_entry_properties(ve)); +} + +PG_FUNCTION_INFO_V1(age_materialize_vle_nodes_slice); + +Datum age_materialize_vle_nodes_slice(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype *agt_lidx = NULL; + agtype *agt_uidx = NULL; + agtype_value lidx_value; + agtype_value uidx_value; + agtype_value *lidx_value_ptr = NULL; + agtype_value *uidx_value_ptr = NULL; + agtype_value *agtv_array = NULL; + bool lidx_needs_free = false; + bool uidx_needs_free = false; + int64 node_count; + int64 lower_index = 0; + int64 upper_index = 0; + + if (PG_ARGISNULL(0)) + { + PG_RETURN_NULL(); + } + + if (PG_ARGISNULL(1) && PG_ARGISNULL(2)) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("slice start and/or end is required"))); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + node_count = agtv_vle_node_count(agt_arg_vpc); + if (PG_ARGISNULL(1)) + { + lower_index = 0; + } + else + { + agt_lidx = AG_GET_ARG_AGTYPE_P(1); + get_vle_scalar_arg_no_copy("age_materialize_vle_nodes_slice", + agt_lidx, AGTV_INTEGER, false, + &lidx_value, &lidx_needs_free); + lidx_value_ptr = &lidx_value; + if (lidx_value_ptr->type == AGTV_NULL) + { + if (lidx_needs_free) + { + pfree_agtype_value_content(lidx_value_ptr); + } + PG_RETURN_NULL(); + } + } + + if (PG_ARGISNULL(2)) + { + upper_index = node_count; + } + else + { + agt_uidx = AG_GET_ARG_AGTYPE_P(2); + get_vle_scalar_arg_no_copy("age_materialize_vle_nodes_slice", + agt_uidx, AGTV_INTEGER, false, + &uidx_value, &uidx_needs_free); + uidx_value_ptr = &uidx_value; + if (uidx_value_ptr->type == AGTV_NULL) + { + if (lidx_value_ptr != NULL && lidx_needs_free) + { + pfree_agtype_value_content(lidx_value_ptr); + } + if (uidx_needs_free) + { + pfree_agtype_value_content(uidx_value_ptr); + } + PG_RETURN_NULL(); + } + } + + if ((lidx_value_ptr != NULL && lidx_value_ptr->type != AGTV_INTEGER) || + (uidx_value_ptr != NULL && uidx_value_ptr->type != AGTV_INTEGER)) + { + if (lidx_value_ptr != NULL && lidx_needs_free) + { + pfree_agtype_value_content(lidx_value_ptr); + } + if (uidx_value_ptr != NULL && uidx_needs_free) + { + pfree_agtype_value_content(uidx_value_ptr); + } + ereport(ERROR, + (errmsg("array slices must resolve to an integer value"))); + } + + if (lidx_value_ptr != NULL) + { + lower_index = lidx_value_ptr->val.int_value; + if (lidx_needs_free) + { + pfree_agtype_value_content(lidx_value_ptr); + } + } + if (uidx_value_ptr != NULL) + { + upper_index = uidx_value_ptr->val.int_value; + if (uidx_needs_free) + { + pfree_agtype_value_content(uidx_value_ptr); + } + } + + if (lower_index < 0) + { + lower_index = node_count + lower_index; + } + if (lower_index < 0) + { + lower_index = 0; + } + if (lower_index > node_count) + { + lower_index = node_count; + } + if (upper_index < 0) + { + upper_index = node_count + upper_index; + } + if (upper_index < 0) + { + upper_index = 0; + } + if (upper_index > node_count) + { + upper_index = node_count; + } + + if (upper_index <= lower_index) + { + PG_FREE_IF_COPY(agt_arg_vpc, 0); + PG_FREE_IF_COPY(agt_lidx, 1); + PG_FREE_IF_COPY(agt_uidx, 2); + PG_RETURN_POINTER(build_empty_agtype_array()); + } + + agtv_array = agtv_materialize_vle_nodes_slice( + agt_arg_vpc, lower_index, upper_index); + + PG_FREE_IF_COPY(agt_arg_vpc, 0); + PG_FREE_IF_COPY(agt_lidx, 1); + PG_FREE_IF_COPY(agt_uidx, 2); + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_array)); +} + +PG_FUNCTION_INFO_V1(age_materialize_vle_list_slice); + +Datum age_materialize_vle_list_slice(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype *agt_lidx = NULL; + agtype *agt_uidx = NULL; + agtype_value lidx_value; + agtype_value uidx_value; + agtype_value mode_value; + agtype_value *agtv_array = NULL; + bool lidx_needs_free = false; + bool uidx_needs_free = false; + bool mode_needs_free = false; + bool reverse = false; + bool node_list = false; + int64 edge_count; + int64 list_count; + int64 lower_index = 0; + int64 upper_index = 0; + int64 base_index = 0; + int64 reverse_base_index = 0; + int64 mode; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(3)) + { + PG_RETURN_NULL(); + } + + if (PG_ARGISNULL(1) && PG_ARGISNULL(2)) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("slice start and/or end is required"))); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_materialize_vle_list_slice", + AG_GET_ARG_AGTYPE_P(3), AGTV_INTEGER, true, + &mode_value, &mode_needs_free); + mode = mode_value.val.int_value; + if (mode_needs_free) + { + pfree_agtype_value_content(&mode_value); + } + + edge_count = agtv_vle_edge_count(agt_arg_vpc); + switch (mode) + { + case 0: + list_count = edge_count; + break; + case 1: + node_list = true; + list_count = edge_count + 1; + break; + case 2: + list_count = Max(edge_count - 1, 0); + base_index = 1; + break; + case 3: + node_list = true; + list_count = edge_count; + base_index = 1; + break; + case 4: + reverse = true; + list_count = edge_count; + break; + case 5: + reverse = true; + node_list = true; + list_count = edge_count + 1; + break; + case 6: + list_count = Max(edge_count - 2, 0); + base_index = 2; + break; + case 7: + node_list = true; + list_count = Max(edge_count - 1, 0); + base_index = 2; + break; + case 8: + reverse = true; + list_count = Max(edge_count - 1, 0); + reverse_base_index = 1; + break; + case 9: + reverse = true; + node_list = true; + list_count = edge_count; + reverse_base_index = 1; + break; + case 10: + reverse = true; + list_count = Max(edge_count - 1, 0); + break; + case 11: + reverse = true; + node_list = true; + list_count = edge_count; + break; + default: + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("age_materialize_vle_list_slice: invalid mode"))); + } + + if (PG_ARGISNULL(1)) + { + lower_index = 0; + } + else + { + agt_lidx = AG_GET_ARG_AGTYPE_P(1); + get_vle_scalar_arg_no_copy("age_materialize_vle_list_slice", + agt_lidx, AGTV_INTEGER, false, + &lidx_value, &lidx_needs_free); + if (lidx_value.type == AGTV_NULL) + { + if (lidx_needs_free) + { + pfree_agtype_value_content(&lidx_value); + } + PG_RETURN_NULL(); + } + if (lidx_value.type != AGTV_INTEGER) + { + if (lidx_needs_free) + { + pfree_agtype_value_content(&lidx_value); + } + ereport(ERROR, + (errmsg("array slices must resolve to an integer value"))); + } + lower_index = lidx_value.val.int_value; + if (lidx_needs_free) + { + pfree_agtype_value_content(&lidx_value); + } + } + + if (PG_ARGISNULL(2)) + { + upper_index = list_count; + } + else + { + agt_uidx = AG_GET_ARG_AGTYPE_P(2); + get_vle_scalar_arg_no_copy("age_materialize_vle_list_slice", + agt_uidx, AGTV_INTEGER, false, + &uidx_value, &uidx_needs_free); + if (uidx_value.type == AGTV_NULL) + { + if (uidx_needs_free) + { + pfree_agtype_value_content(&uidx_value); + } + PG_RETURN_NULL(); + } + if (uidx_value.type != AGTV_INTEGER) + { + if (uidx_needs_free) + { + pfree_agtype_value_content(&uidx_value); + } + ereport(ERROR, + (errmsg("array slices must resolve to an integer value"))); + } + upper_index = uidx_value.val.int_value; + if (uidx_needs_free) + { + pfree_agtype_value_content(&uidx_value); + } + } + + if (lower_index < 0) + { + lower_index = list_count + lower_index; + } + if (lower_index < 0) + { + lower_index = 0; + } + if (lower_index > list_count) + { + lower_index = list_count; + } + if (upper_index < 0) + { + upper_index = list_count + upper_index; + } + if (upper_index < 0) + { + upper_index = 0; + } + if (upper_index > list_count) + { + upper_index = list_count; + } + + if (upper_index <= lower_index) + { + PG_RETURN_POINTER(build_empty_agtype_array()); + } + + if (reverse) + { + lower_index += reverse_base_index; + upper_index += reverse_base_index; + agtv_array = node_list ? + agtv_materialize_vle_nodes_reversed_slice(agt_arg_vpc, + lower_index, + upper_index) : + agtv_materialize_vle_edges_reversed_slice(agt_arg_vpc, + lower_index, + upper_index); + } + else + { + lower_index += base_index; + upper_index += base_index; + agtv_array = node_list ? + agtv_materialize_vle_nodes_slice(agt_arg_vpc, lower_index, + upper_index) : + agtv_materialize_vle_edges_slice(agt_arg_vpc, lower_index, + upper_index); + } + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_array)); +} + +PG_FUNCTION_INFO_V1(age_materialize_vle_nodes_tail); + +Datum age_materialize_vle_nodes_tail(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value *agtv_array = NULL; + int64 node_count; + + if (PG_ARGISNULL(0)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + node_count = agtv_vle_node_count(agt_arg_vpc); + if (node_count <= 1) + { + PG_RETURN_POINTER(build_empty_agtype_array()); + } + + agtv_array = agtv_materialize_vle_nodes_slice( + agt_arg_vpc, Min(node_count, 1), node_count); + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_array)); +} + +PG_FUNCTION_INFO_V1(age_materialize_vle_nodes_reversed); + +Datum age_materialize_vle_nodes_reversed(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value *agtv_array = NULL; + + if (PG_ARGISNULL(0)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + agtv_array = agtv_materialize_vle_nodes_reversed(agt_arg_vpc); + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_array)); +} + +PG_FUNCTION_INFO_V1(age_materialize_vle_edge_at); + +Datum age_materialize_vle_edge_at(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value agtv_index; + agtype_value *agtv_edge = NULL; + bool index_needs_free = false; + int64 edge_index; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_materialize_vle_edge_at", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_index, &index_needs_free); + + edge_index = agtv_index.val.int_value; + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + + agtv_edge = agtv_materialize_vle_edge_at(agt_arg_vpc, edge_index); + if (agtv_edge == NULL) + { + PG_RETURN_NULL(); + } + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_edge)); +} + +PG_FUNCTION_INFO_V1(age_materialize_vle_edge_reversed_at); + +Datum age_materialize_vle_edge_reversed_at(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value agtv_index; + agtype_value *agtv_edge = NULL; + bool index_needs_free = false; + int64 edge_index; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_materialize_vle_edge_reversed_at", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_index, &index_needs_free); + + edge_index = agtv_index.val.int_value; + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + + if (edge_index == PG_INT64_MIN) + { + PG_RETURN_NULL(); + } + + agtv_edge = agtv_materialize_vle_edge_at(agt_arg_vpc, -edge_index - 1); + if (agtv_edge == NULL) + { + PG_RETURN_NULL(); + } + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_edge)); +} + +PG_FUNCTION_INFO_V1(age_materialize_vle_edge_tail_last); + +Datum age_materialize_vle_edge_tail_last(PG_FUNCTION_ARGS) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value *agtv_edge = NULL; + graphid *graphid_array = NULL; + int64 edge_count; + + if (PG_ARGISNULL(0)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = agtv_vle_edge_count(agt_arg_vpc); + if (edge_count <= 1) + { + PG_RETURN_NULL(); + } + + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + agtv_edge = build_vle_edge_value(ggctx, graphid_array[edge_count * 2 - 1], + NULL); + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_edge)); +} + +PG_FUNCTION_INFO_V1(age_vle_edge_tail_last_id); + +Datum age_vle_edge_tail_last_id(PG_FUNCTION_ARGS) +{ + VLE_path_container *vpc = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value agtv_result; + graphid *graphid_array = NULL; + int64 edge_count; + + if (PG_ARGISNULL(0)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = agtv_vle_edge_count(agt_arg_vpc); + if (edge_count <= 1) + { + PG_RETURN_NULL(); + } + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + agtv_result.type = AGTV_INTEGER; + agtv_result.val.int_value = graphid_array[edge_count * 2 - 1]; + + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); +} + +PG_FUNCTION_INFO_V1(age_vle_tail_last_field); + +Datum age_vle_tail_last_field(PG_FUNCTION_ARGS) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + vertex_entry *ve = NULL; + edge_entry *ee = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value agtv_mode; + agtype_value agtv_result; + agtype_value agtv_label; + agtype_in_state agis_result; + bool mode_needs_free = false; + graphid *graphid_array = NULL; + graphid entity_id; + int64 node_count; + int64 edge_count; + int64 mode; + char *label_name = NULL; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_tail_last_field", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_mode, &mode_needs_free); + mode = agtv_mode.val.int_value; + if (mode_needs_free) + { + pfree_agtype_value_content(&agtv_mode); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + + if (mode >= 0 && mode <= 2) + { + node_count = agtv_vle_node_count(agt_arg_vpc); + if (node_count <= 1) + { + PG_RETURN_NULL(); + } + + entity_id = graphid_array[(node_count - 1) * 2]; + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + ve = get_vertex_entry(ggctx, entity_id); + Assert(ve != NULL); + + if (mode == 2) + { + PG_RETURN_DATUM(get_vertex_entry_properties(ve)); + } + + label_name = get_vertex_entry_label_name(ve); + agtv_label.type = AGTV_STRING; + agtv_label.val.string.val = label_name; + agtv_label.val.string.len = strlen(label_name); + + if (mode == 0) + { + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_label)); + } + + MemSet(&agis_result, 0, sizeof(agtype_in_state)); + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_BEGIN_ARRAY, NULL); + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_ELEM, &agtv_label); + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_END_ARRAY, NULL); + + PG_RETURN_POINTER(agtype_value_to_agtype(agis_result.res)); + } + + edge_count = agtv_vle_edge_count(agt_arg_vpc); + if (edge_count <= 1) + { + PG_RETURN_NULL(); + } + + if (mode < 3 || mode > 4) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("age_vle_tail_last_field: invalid mode"))); + } + + entity_id = graphid_array[edge_count * 2 - 1]; + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + ee = get_edge_entry(ggctx, entity_id); + Assert(ee != NULL); + + if (mode == 3) + { + label_name = get_edge_entry_label_name(ee); + agtv_result.type = AGTV_STRING; + agtv_result.val.string.val = label_name; + agtv_result.val.string.len = strlen(label_name); + + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); + } + else if (mode == 4) + { + PG_RETURN_DATUM(get_edge_entry_properties(ee)); + } + + Assert(false); + PG_RETURN_NULL(); +} + +PG_FUNCTION_INFO_V1(age_vle_tail_last_edge_endpoint); + +Datum age_vle_tail_last_edge_endpoint(PG_FUNCTION_ARGS) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + edge_entry *ee = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value agtv_mode; + agtype_value agtv_result; + agtype_value *agtv_vertex = NULL; + bool mode_needs_free = false; + graphid *graphid_array = NULL; + graphid edge_id; + graphid endpoint_id; + int64 edge_count; + int64 mode; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_tail_last_edge_endpoint", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_mode, &mode_needs_free); + mode = agtv_mode.val.int_value; + if (mode_needs_free) + { + pfree_agtype_value_content(&agtv_mode); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = agtv_vle_edge_count(agt_arg_vpc); + if (edge_count <= 1) + { + PG_RETURN_NULL(); + } + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + edge_id = graphid_array[edge_count * 2 - 1]; + + if (mode < 0 || mode > 3) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("age_vle_tail_last_edge_endpoint: invalid mode"))); + } + + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + ee = get_edge_entry(ggctx, edge_id); + Assert(ee != NULL); + + endpoint_id = (mode == 0 || mode == 2) ? + get_edge_entry_start_vertex_id(ee) : + get_edge_entry_end_vertex_id(ee); + + if (mode == 0 || mode == 1) + { + agtv_vertex = build_vle_vertex_value(ggctx, endpoint_id, NULL); + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_vertex)); + } + else if (mode == 2 || mode == 3) + { + agtv_result.type = AGTV_INTEGER; + agtv_result.val.int_value = endpoint_id; + + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); + } + + Assert(false); + PG_RETURN_NULL(); +} + +PG_FUNCTION_INFO_V1(age_vle_tail_last_endpoint_field); + +Datum age_vle_tail_last_endpoint_field(PG_FUNCTION_ARGS) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + edge_entry *ee = NULL; + vertex_entry *ve = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value agtv_mode; + agtype_value agtv_label; + agtype_in_state agis_result; + bool mode_needs_free = false; + graphid *graphid_array = NULL; + graphid edge_id; + graphid endpoint_id; + int64 edge_count; + int64 mode; + char *label_name = NULL; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_tail_last_endpoint_field", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_mode, &mode_needs_free); + mode = agtv_mode.val.int_value; + if (mode_needs_free) + { + pfree_agtype_value_content(&agtv_mode); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = agtv_vle_edge_count(agt_arg_vpc); + if (edge_count <= 1) + { + PG_RETURN_NULL(); + } + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + edge_id = graphid_array[edge_count * 2 - 1]; + + if (mode < 0 || mode > 5) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("age_vle_tail_last_endpoint_field: invalid mode"))); + } + + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + ee = get_edge_entry(ggctx, edge_id); + Assert(ee != NULL); + + endpoint_id = (mode % 2 == 0) ? + get_edge_entry_start_vertex_id(ee) : + get_edge_entry_end_vertex_id(ee); + ve = get_vertex_entry(ggctx, endpoint_id); + Assert(ve != NULL); + + if (mode == 4 || mode == 5) + { + PG_RETURN_DATUM(get_vertex_entry_properties(ve)); + } + + label_name = get_vertex_entry_label_name(ve); + agtv_label.type = AGTV_STRING; + agtv_label.val.string.val = label_name; + agtv_label.val.string.len = strlen(label_name); + + if (mode == 0 || mode == 1) + { + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_label)); + } + else if (mode == 2 || mode == 3) + { + MemSet(&agis_result, 0, sizeof(agtype_in_state)); + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_BEGIN_ARRAY, NULL); + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_ELEM, &agtv_label); + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_END_ARRAY, NULL); + + PG_RETURN_POINTER(agtype_value_to_agtype(agis_result.res)); + } + + Assert(false); + PG_RETURN_NULL(); +} + +PG_FUNCTION_INFO_V1(age_vle_edge_id_at); + +Datum age_vle_edge_id_at(PG_FUNCTION_ARGS) +{ + VLE_path_container *vpc = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value agtv_index; + agtype_value agtv_result; + bool index_needs_free = false; + graphid *graphid_array = NULL; + int64 edge_count; + int64 edge_index; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_edge_id_at", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_index, &index_needs_free); + + edge_index = agtv_index.val.int_value; + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = (vpc->graphid_array_size - 1) / 2; + if (edge_index < 0) + { + edge_index = edge_count + edge_index; + } + if (edge_index < 0 || edge_index >= edge_count) + { + PG_RETURN_NULL(); + } + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + agtv_result.type = AGTV_INTEGER; + agtv_result.val.int_value = graphid_array[(edge_index * 2) + 1]; + + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); +} + +PG_FUNCTION_INFO_V1(age_vle_edge_index_exists); + +Datum age_vle_edge_index_exists(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value agtv_index; + bool index_needs_free = false; + int64 edge_index; + graphid edge_id; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_edge_index_exists", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, false, + &agtv_index, &index_needs_free); + + if (agtv_index.type == AGTV_NULL) + { + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + PG_RETURN_NULL(); + } + if (agtv_index.type != AGTV_INTEGER) + { + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("age_vle_edge_index_exists: index must resolve to an integer"))); + } + + edge_index = agtv_index.val.int_value; + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + if (!get_vle_edge_id_at_index(agt_arg_vpc, edge_index, &edge_id)) + { + PG_RETURN_NULL(); + } + + PG_RETURN_DATUM(boolean_to_agtype(true)); +} + +PG_FUNCTION_INFO_V1(age_vle_edge_indices_equal); + +Datum age_vle_edge_indices_equal(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value agtv_left_index; + agtype_value agtv_right_index; + bool left_index_needs_free = false; + bool right_index_needs_free = false; + graphid left_edge_id; + graphid right_edge_id; + bool result; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_edge_indices_equal", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, false, + &agtv_left_index, &left_index_needs_free); + get_vle_scalar_arg_no_copy("age_vle_edge_indices_equal", + AG_GET_ARG_AGTYPE_P(2), AGTV_INTEGER, false, + &agtv_right_index, &right_index_needs_free); + + if (agtv_left_index.type == AGTV_NULL || + agtv_right_index.type == AGTV_NULL) + { + if (left_index_needs_free) + { + pfree_agtype_value_content(&agtv_left_index); + } + if (right_index_needs_free) + { + pfree_agtype_value_content(&agtv_right_index); + } + PG_RETURN_NULL(); + } + if (agtv_left_index.type != AGTV_INTEGER || + agtv_right_index.type != AGTV_INTEGER) + { + if (left_index_needs_free) + { + pfree_agtype_value_content(&agtv_left_index); + } + if (right_index_needs_free) + { + pfree_agtype_value_content(&agtv_right_index); + } + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("age_vle_edge_indices_equal: indexes must resolve to integers"))); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + if (!get_vle_edge_id_at_index(agt_arg_vpc, + agtv_left_index.val.int_value, + &left_edge_id) || + !get_vle_edge_id_at_index(agt_arg_vpc, + agtv_right_index.val.int_value, + &right_edge_id)) + { + if (left_index_needs_free) + { + pfree_agtype_value_content(&agtv_left_index); + } + if (right_index_needs_free) + { + pfree_agtype_value_content(&agtv_right_index); + } + PG_RETURN_NULL(); + } + + result = left_edge_id == right_edge_id; + + if (left_index_needs_free) + { + pfree_agtype_value_content(&agtv_left_index); + } + if (right_index_needs_free) + { + pfree_agtype_value_content(&agtv_right_index); + } + + PG_RETURN_DATUM(boolean_to_agtype(result)); +} + +PG_FUNCTION_INFO_V1(age_vle_edge_reversed_index_equal); + +Datum age_vle_edge_reversed_index_equal(PG_FUNCTION_ARGS) +{ + VLE_path_container *vpc = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value agtv_reversed_index; + agtype_value agtv_normal_index; + bool reversed_index_needs_free = false; + bool normal_index_needs_free = false; + graphid reversed_edge_id; + graphid normal_edge_id; + int64 edge_count; + int64 reversed_index; + bool result; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_edge_reversed_index_equal", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, false, + &agtv_reversed_index, + &reversed_index_needs_free); + get_vle_scalar_arg_no_copy("age_vle_edge_reversed_index_equal", + AG_GET_ARG_AGTYPE_P(2), AGTV_INTEGER, false, + &agtv_normal_index, &normal_index_needs_free); + + if (agtv_reversed_index.type == AGTV_NULL || + agtv_normal_index.type == AGTV_NULL) + { + if (reversed_index_needs_free) + { + pfree_agtype_value_content(&agtv_reversed_index); + } + if (normal_index_needs_free) + { + pfree_agtype_value_content(&agtv_normal_index); + } + PG_RETURN_NULL(); + } + if (agtv_reversed_index.type != AGTV_INTEGER || + agtv_normal_index.type != AGTV_INTEGER) + { + if (reversed_index_needs_free) + { + pfree_agtype_value_content(&agtv_reversed_index); + } + if (normal_index_needs_free) + { + pfree_agtype_value_content(&agtv_normal_index); + } + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("age_vle_edge_reversed_index_equal: indexes must resolve to integers"))); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = (vpc->graphid_array_size - 1) / 2; + reversed_index = agtv_reversed_index.val.int_value; + if (reversed_index < 0) + { + reversed_index = edge_count + reversed_index; + } + if (reversed_index < 0 || reversed_index >= edge_count || + !get_vle_edge_id_at_index(agt_arg_vpc, + edge_count - reversed_index - 1, + &reversed_edge_id) || + !get_vle_edge_id_at_index(agt_arg_vpc, + agtv_normal_index.val.int_value, + &normal_edge_id)) + { + if (reversed_index_needs_free) + { + pfree_agtype_value_content(&agtv_reversed_index); + } + if (normal_index_needs_free) + { + pfree_agtype_value_content(&agtv_normal_index); + } + PG_RETURN_NULL(); + } + + result = reversed_edge_id == normal_edge_id; + + if (reversed_index_needs_free) + { + pfree_agtype_value_content(&agtv_reversed_index); + } + if (normal_index_needs_free) + { + pfree_agtype_value_content(&agtv_normal_index); + } + + PG_RETURN_DATUM(boolean_to_agtype(result)); +} + +PG_FUNCTION_INFO_V1(age_vle_edge_label_at); + +Datum age_vle_edge_label_at(PG_FUNCTION_ARGS) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + edge_entry *ee = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value agtv_index; + agtype_value agtv_result; + bool index_needs_free = false; + graphid *graphid_array = NULL; + graphid edge_id; + int64 edge_count; + int64 edge_index; + char *label_name = NULL; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_edge_label_at", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_index, &index_needs_free); + + edge_index = agtv_index.val.int_value; + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = (vpc->graphid_array_size - 1) / 2; + if (edge_index < 0) + { + edge_index = edge_count + edge_index; + } + if (edge_index < 0 || edge_index >= edge_count) + { + PG_RETURN_NULL(); + } + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + edge_id = graphid_array[(edge_index * 2) + 1]; + + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + ee = get_edge_entry(ggctx, edge_id); + Assert(ee != NULL); + + label_name = get_edge_entry_label_name(ee); + agtv_result.type = AGTV_STRING; + agtv_result.val.string.val = label_name; + agtv_result.val.string.len = strlen(label_name); + + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); +} + +PG_FUNCTION_INFO_V1(age_vle_edge_properties_at); + +Datum age_vle_edge_properties_at(PG_FUNCTION_ARGS) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + edge_entry *ee = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value agtv_index; + bool index_needs_free = false; + graphid *graphid_array = NULL; + graphid edge_id; + int64 edge_count; + int64 edge_index; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_edge_properties_at", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_index, &index_needs_free); + + edge_index = agtv_index.val.int_value; + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = (vpc->graphid_array_size - 1) / 2; + if (edge_index < 0) + { + edge_index = edge_count + edge_index; + } + if (edge_index < 0 || edge_index >= edge_count) + { + PG_RETURN_NULL(); + } + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + edge_id = graphid_array[(edge_index * 2) + 1]; + + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + ee = get_edge_entry(ggctx, edge_id); + Assert(ee != NULL); + + PG_RETURN_DATUM(get_edge_entry_properties(ee)); +} + +static Datum age_vle_edge_endpoint_at(FunctionCallInfo fcinfo, + bool start_endpoint) +{ + agtype *agt_arg_vpc = NULL; + agtype_value agtv_index; + agtype_value *agtv_vertex = NULL; + bool index_needs_free = false; + int64 edge_index; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy(start_endpoint ? + "age_vle_edge_start_node_at" : + "age_vle_edge_end_node_at", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_index, &index_needs_free); + + edge_index = agtv_index.val.int_value; + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + + agtv_vertex = agtv_materialize_vle_edge_endpoint_at( + agt_arg_vpc, edge_index, start_endpoint); + if (agtv_vertex == NULL) + { + PG_RETURN_NULL(); + } + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_vertex)); +} + +PG_FUNCTION_INFO_V1(age_vle_edge_start_node_at); + +Datum age_vle_edge_start_node_at(PG_FUNCTION_ARGS) +{ + return age_vle_edge_endpoint_at(fcinfo, true); +} + +PG_FUNCTION_INFO_V1(age_vle_edge_end_node_at); + +Datum age_vle_edge_end_node_at(PG_FUNCTION_ARGS) +{ + return age_vle_edge_endpoint_at(fcinfo, false); +} + +PG_FUNCTION_INFO_V1(age_vle_edge_endpoint_field_at); + +Datum age_vle_edge_endpoint_field_at(PG_FUNCTION_ARGS) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + edge_entry *ee = NULL; + vertex_entry *ve = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value agtv_index; + agtype_value agtv_mode; + agtype_value agtv_label; + agtype_in_state agis_result; + bool index_needs_free = false; + bool mode_needs_free = false; + graphid *graphid_array = NULL; + graphid edge_id; + graphid endpoint_id; + int64 edge_count; + int64 edge_index; + int64 mode; + char *label_name = NULL; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_edge_endpoint_field_at", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_index, &index_needs_free); + get_vle_scalar_arg_no_copy("age_vle_edge_endpoint_field_at", + AG_GET_ARG_AGTYPE_P(2), AGTV_INTEGER, true, + &agtv_mode, &mode_needs_free); + + edge_index = agtv_index.val.int_value; + mode = agtv_mode.val.int_value; + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + if (mode_needs_free) + { + pfree_agtype_value_content(&agtv_mode); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = (vpc->graphid_array_size - 1) / 2; + if (edge_index < 0) + { + edge_index = edge_count + edge_index; + } + if (edge_index < 0 || edge_index >= edge_count) + { + PG_RETURN_NULL(); + } + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + edge_id = graphid_array[(edge_index * 2) + 1]; + + if (mode < 0 || mode > 5) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("age_vle_edge_endpoint_field_at: invalid mode"))); + } + + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + ee = get_edge_entry(ggctx, edge_id); + Assert(ee != NULL); + + endpoint_id = (mode % 2 == 0) ? + get_edge_entry_start_vertex_id(ee) : + get_edge_entry_end_vertex_id(ee); + ve = get_vertex_entry(ggctx, endpoint_id); + Assert(ve != NULL); + + if (mode == 4 || mode == 5) + { + PG_RETURN_DATUM(get_vertex_entry_properties(ve)); + } + + label_name = get_vertex_entry_label_name(ve); + agtv_label.type = AGTV_STRING; + agtv_label.val.string.val = label_name; + agtv_label.val.string.len = strlen(label_name); + + if (mode == 0 || mode == 1) + { + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_label)); + } + else if (mode == 2 || mode == 3) + { + MemSet(&agis_result, 0, sizeof(agtype_in_state)); + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_BEGIN_ARRAY, NULL); + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_ELEM, &agtv_label); + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_END_ARRAY, NULL); + + PG_RETURN_POINTER(agtype_value_to_agtype(agis_result.res)); + } + + Assert(false); + PG_RETURN_NULL(); +} + +static Datum age_vle_edge_endpoint_id_at(FunctionCallInfo fcinfo, + bool start_endpoint) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + edge_entry *ee = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value agtv_index; + agtype_value agtv_result; + bool index_needs_free = false; + graphid *graphid_array = NULL; + graphid edge_id; + int64 edge_count; + int64 edge_index; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy(start_endpoint ? + "age_vle_edge_start_id_at" : + "age_vle_edge_end_id_at", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_index, &index_needs_free); + + edge_index = agtv_index.val.int_value; + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = (vpc->graphid_array_size - 1) / 2; + if (edge_index < 0) + { + edge_index = edge_count + edge_index; + } + if (edge_index < 0 || edge_index >= edge_count) + { + PG_RETURN_NULL(); + } + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + edge_id = graphid_array[(edge_index * 2) + 1]; + + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + ee = get_edge_entry(ggctx, edge_id); + Assert(ee != NULL); + + agtv_result.type = AGTV_INTEGER; + agtv_result.val.int_value = start_endpoint ? + get_edge_entry_start_vertex_id(ee) : + get_edge_entry_end_vertex_id(ee); + + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); +} + +PG_FUNCTION_INFO_V1(age_vle_edge_start_id_at); + +Datum age_vle_edge_start_id_at(PG_FUNCTION_ARGS) +{ + return age_vle_edge_endpoint_id_at(fcinfo, true); +} + +PG_FUNCTION_INFO_V1(age_vle_edge_end_id_at); + +Datum age_vle_edge_end_id_at(PG_FUNCTION_ARGS) +{ + return age_vle_edge_endpoint_id_at(fcinfo, false); +} + +PG_FUNCTION_INFO_V1(age_materialize_vle_edges_tail); + +Datum age_materialize_vle_edges_tail(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value *agtv_array = NULL; + int64 edge_count; + + if (PG_ARGISNULL(0)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + edge_count = agtv_vle_edge_count(agt_arg_vpc); + if (edge_count <= 1) + { + PG_RETURN_POINTER(build_empty_agtype_array()); + } + + agtv_array = agtv_materialize_vle_edges_slice( + agt_arg_vpc, Min(edge_count, 1), edge_count); + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_array)); +} + +PG_FUNCTION_INFO_V1(age_materialize_vle_edges_reversed); + +Datum age_materialize_vle_edges_reversed(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value *agtv_array = NULL; + int64 edge_count; + + if (PG_ARGISNULL(0)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + edge_count = agtv_vle_edge_count(agt_arg_vpc); + if (edge_count == 0) + { + PG_RETURN_POINTER(build_empty_agtype_array()); + } + + agtv_array = agtv_materialize_vle_edges_reversed(agt_arg_vpc); + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_array)); +} + +/* PG wrapper function for age_materialize_vle_path */ +PG_FUNCTION_INFO_V1(age_materialize_vle_path); + +Datum age_materialize_vle_path(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + + /* if we have a NULL VLE_path_container, return NULL */ + if (PG_ARGISNULL(0)) + { + PG_RETURN_NULL(); + } + + /* get the VLE_path_container argument */ + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + + /* if NULL, return NULL */ + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + PG_RETURN_POINTER(agt_materialize_vle_path(agt_arg_vpc)); +} + +PG_FUNCTION_INFO_V1(age_vle_path_length); + +Datum age_vle_path_length(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value agtv_result; + + if (PG_ARGISNULL(0)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + agtv_result.type = AGTV_INTEGER; + agtv_result.val.int_value = agtv_vle_edge_count(agt_arg_vpc); + + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); +} + +/* + * 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. + */ +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; + 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) @@ -2378,11 +7231,8 @@ Datum age_match_vle_terminal_edge(PG_FUNCTION_ARGS) 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; + vsid = get_agtype_scalar_graphid_arg(agt_arg_vsid, + "match_vle_terminal_edge() argument 1 must be an agtype integer or a graphid"); } else { @@ -2407,10 +7257,8 @@ Datum age_match_vle_terminal_edge(PG_FUNCTION_ARGS) 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; + veid = get_agtype_scalar_graphid_arg(agt_arg_veid, + "match_vle_terminal_edge() argument 2 must be an agtype integer or a graphid"); } else { @@ -2440,7 +7288,9 @@ Datum age_build_vle_match_edge(PG_FUNCTION_ARGS) agtype_in_state result; agtype_value agtv_zero; agtype_value agtv_nstr; - agtype_value *agtv_temp = NULL; + agtype_value agtv_temp; + bool agtv_temp_needs_free = false; + agtype *agt_result; /* create an agtype_value integer 0 */ agtv_zero.type = AGTV_INTEGER; @@ -2466,10 +7316,11 @@ Datum age_build_vle_match_edge(PG_FUNCTION_ARGS) string_to_agtype_value("label")); if (!PG_ARGISNULL(0)) { - agtv_temp = get_agtype_value("build_vle_match_edge", - AG_GET_ARG_AGTYPE_P(0), AGTV_STRING, true); + get_vle_scalar_arg_no_copy("build_vle_match_edge", + AG_GET_ARG_AGTYPE_P(0), AGTV_STRING, true, + &agtv_temp, &agtv_temp_needs_free); result.res = push_agtype_value(&result.parse_state, WAGT_VALUE, - agtv_temp); + &agtv_temp); } else { @@ -2516,7 +7367,13 @@ Datum age_build_vle_match_edge(PG_FUNCTION_ARGS) result.res->type = AGTV_EDGE; - PG_RETURN_POINTER(agtype_value_to_agtype(result.res)); + agt_result = agtype_value_to_agtype(result.res); + if (agtv_temp_needs_free) + { + pfree_agtype_value_content(&agtv_temp); + } + + PG_RETURN_POINTER(agt_result); } PG_FUNCTION_INFO_V1(_ag_enforce_edge_uniqueness2); @@ -2568,6 +7425,267 @@ Datum _ag_enforce_edge_uniqueness4(PG_FUNCTION_ARGS) PG_RETURN_BOOL(true); } +static int get_edge_uniqueness_args_fast(FunctionCallInfo fcinfo, + Datum **args, Oid **types, + bool **nulls, Datum *fast_args, + Oid *fast_types, bool *fast_nulls) +{ + int nargs; + int i; + + if (get_fn_expr_variadic(fcinfo->flinfo)) + { + return extract_variadic_args(fcinfo, 0, true, args, types, nulls); + } + + nargs = PG_NARGS(); + if (nargs > EDGE_UNIQUENESS_FAST_ARGS) + { + return extract_variadic_args(fcinfo, 0, true, args, types, nulls); + } + + for (i = 0; i < nargs; i++) + { + fast_nulls[i] = PG_ARGISNULL(i); + fast_types[i] = get_cached_edge_uniqueness_argtype(fcinfo, i, nargs); + + if (fast_types[i] == UNKNOWNOID && + get_fn_expr_arg_stable(fcinfo->flinfo, i)) + { + fast_types[i] = TEXTOID; + fast_args[i] = fast_nulls[i] ? (Datum)0 : + CStringGetTextDatum(PG_GETARG_POINTER(i)); + } + else + { + fast_args[i] = PG_GETARG_DATUM(i); + } + } + + *args = fast_args; + *types = fast_types; + *nulls = fast_nulls; + + return nargs; +} + +static Oid get_cached_edge_uniqueness_argtype(FunctionCallInfo fcinfo, + int argno, int nargs) +{ + edge_uniqueness_argtype_cache *cache; + int i; + + cache = (edge_uniqueness_argtype_cache *)fcinfo->flinfo->fn_extra; + if (cache == NULL || cache->nargs != nargs) + { + cache = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + offsetof(edge_uniqueness_argtype_cache, + types) + + sizeof(Oid) * nargs); + cache->nargs = nargs; + + for (i = 0; i < nargs; i++) + { + cache->types[i] = get_fn_expr_argtype(fcinfo->flinfo, i); + } + + fcinfo->flinfo->fn_extra = cache; + } + + return cache->types[argno]; +} + +static bool get_edge_uniqueness_vle_arg(Datum arg, Oid type, + VLE_path_container **vpc) +{ + agtype *agt = NULL; + + if (type != AGTYPEOID) + { + return false; + } + + agt = DATUM_GET_AGTYPE_P(arg); + if (!AGT_ROOT_IS_BINARY(agt) || + AGT_ROOT_BINARY_FLAGS(agt) != AGT_FBINARY_TYPE_VLE_PATH) + { + return false; + } + + *vpc = (VLE_path_container *)agt; + return true; +} + +static bool get_edge_uniqueness_fixed_edge_id(Datum arg, Oid type, int argno, + graphid *edge_id) +{ + agtype *agt = NULL; + agtype_value agtv_id; + bool id_needs_free = false; + bool id_found; + + if (type == INT8OID || type == GRAPHIDOID) + { + *edge_id = DatumGetInt64(arg); + return true; + } + if (type != AGTYPEOID) + { + return false; + } + + agt = DATUM_GET_AGTYPE_P(arg); + if (AGT_ROOT_IS_BINARY(agt) && + AGT_ROOT_BINARY_FLAGS(agt) == AGT_FBINARY_TYPE_VLE_PATH) + { + return false; + } + if (!AGT_ROOT_IS_SCALAR(agt)) + { + return false; + } + + id_found = get_ith_agtype_value_from_container_no_copy( + &agt->root, 0, &agtv_id, &id_needs_free); + Assert(id_found); + + if (agtv_id.type != AGTV_INTEGER) + { + if (id_needs_free) + { + pfree_agtype_value_content(&agtv_id); + } + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("_ag_enforce_edge_uniqueness parameter %d must resolve to an agtype integer", + argno))); + } + + *edge_id = agtv_id.val.int_value; + if (id_needs_free) + { + pfree_agtype_value_content(&agtv_id); + } + + return true; +} + +static bool vle_path_contains_edge_id(VLE_path_container *vpc, graphid edge_id) +{ + return agt_vle_contains_edge_id((agtype *)vpc, edge_id); +} + +static int64 get_vle_path_edge_count(VLE_path_container *vpc) +{ + return (vpc->graphid_array_size - 1) / 2; +} + +static bool vle_paths_have_common_edge(VLE_path_container *left_vpc, + int64 left_edge_count, + VLE_path_container *right_vpc, + int64 right_edge_count) +{ + VLE_path_container *outer_vpc = left_vpc; + VLE_path_container *inner_vpc = right_vpc; + graphid *outer_array = NULL; + graphid *inner_array = NULL; + int64 outer_edge_count = left_edge_count; + int64 inner_edge_count = right_edge_count; + int64 i; + int64 j; + + if (outer_edge_count > inner_edge_count) + { + outer_vpc = right_vpc; + inner_vpc = left_vpc; + outer_edge_count = right_edge_count; + inner_edge_count = left_edge_count; + } + + outer_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(outer_vpc); + inner_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(inner_vpc); + + for (i = 0; i < outer_edge_count; i++) + { + graphid edge_id = outer_array[(i * 2) + 1]; + + for (j = 0; j < inner_edge_count; j++) + { + if (edge_id == inner_array[(j * 2) + 1]) + { + return true; + } + } + } + + return false; +} + +static bool try_enforce_two_vle_uniqueness(Datum *args, Oid *types, + int nargs, bool *result) +{ + VLE_path_container *left_vpc = NULL; + VLE_path_container *right_vpc = NULL; + int64 left_edge_count; + int64 right_edge_count; + + if (nargs != 2) + { + return false; + } + + if (!get_edge_uniqueness_vle_arg(args[0], types[0], &left_vpc) || + !get_edge_uniqueness_vle_arg(args[1], types[1], &right_vpc)) + { + return false; + } + + left_edge_count = get_vle_path_edge_count(left_vpc); + right_edge_count = get_vle_path_edge_count(right_vpc); + if (left_edge_count == 0 || right_edge_count == 0) + { + *result = true; + return true; + } + + if (left_edge_count * right_edge_count > EDGE_UNIQUENESS_NESTED_SCAN_LIMIT) + { + return false; + } + + *result = !vle_paths_have_common_edge(left_vpc, left_edge_count, + right_vpc, right_edge_count); + return true; +} + +static bool try_enforce_one_vle_one_edge_uniqueness(Datum *args, Oid *types, + int nargs, bool *result) +{ + VLE_path_container *vpc = NULL; + graphid edge_id = 0; + + if (nargs != 2) + { + return false; + } + + if (get_edge_uniqueness_vle_arg(args[0], types[0], &vpc) && + get_edge_uniqueness_fixed_edge_id(args[1], types[1], 1, &edge_id)) + { + *result = !vle_path_contains_edge_id(vpc, edge_id); + return true; + } + + if (get_edge_uniqueness_vle_arg(args[1], types[1], &vpc) && + get_edge_uniqueness_fixed_edge_id(args[0], types[0], 0, &edge_id)) + { + *result = !vle_path_contains_edge_id(vpc, edge_id); + return true; + } + + return false; +} + /* * This function checks the edges in a MATCH clause to see if they are unique or * not. Filters out all the paths where the edge uniques rules are not met. @@ -2579,14 +7697,20 @@ Datum _ag_enforce_edge_uniqueness(PG_FUNCTION_ARGS) { HTAB *exists_hash = NULL; HASHCTL exists_ctl; + Datum fast_args[EDGE_UNIQUENESS_FAST_ARGS]; + Oid fast_types[EDGE_UNIQUENESS_FAST_ARGS]; + bool fast_nulls[EDGE_UNIQUENESS_FAST_ARGS]; Datum *args = NULL; bool *nulls = NULL; Oid *types = NULL; int nargs = 0; + int64 estimated_edges = 0; int i = 0; + bool fast_result = false; /* extract our arguments */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_edge_uniqueness_args_fast(fcinfo, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* verify the arguments */ for (i = 0; i < nargs; i++) @@ -2609,6 +7733,45 @@ Datum _ag_enforce_edge_uniqueness(PG_FUNCTION_ARGS) } } + if (try_enforce_one_vle_one_edge_uniqueness(args, types, nargs, + &fast_result)) + { + PG_RETURN_BOOL(fast_result); + } + if (try_enforce_two_vle_uniqueness(args, types, nargs, &fast_result)) + { + PG_RETURN_BOOL(fast_result); + } + + for (i = 0; i < nargs; i++) + { + if (types[i] == INT8OID || types[i] == GRAPHIDOID) + { + estimated_edges++; + } + else if (types[i] == AGTYPEOID) + { + agtype *agt_i = DATUM_GET_AGTYPE_P(args[i]); + + if (AGT_ROOT_IS_BINARY(agt_i) && + AGT_ROOT_BINARY_FLAGS(agt_i) == AGT_FBINARY_TYPE_VLE_PATH) + { + VLE_path_container *vpc = (VLE_path_container *)agt_i; + + estimated_edges += vpc->graphid_array_size / 2; + } + else + { + estimated_edges++; + } + } + } + + if (estimated_edges < 2) + { + PG_RETURN_BOOL(true); + } + /* configure the hash table */ MemSet(&exists_ctl, 0, sizeof(exists_ctl)); exists_ctl.keysize = sizeof(int64); @@ -2616,7 +7779,8 @@ Datum _ag_enforce_edge_uniqueness(PG_FUNCTION_ARGS) exists_ctl.hash = tag_hash; /* create exists_hash table */ - exists_hash = hash_create(EXISTS_HTAB_NAME, EXISTS_HTAB_NAME_INITIAL_SIZE, + exists_hash = hash_create(EXISTS_HTAB_NAME, + Max(estimated_edges, EXISTS_HTAB_NAME_MIN_SIZE), &exists_ctl, HASH_ELEM | HASH_FUNCTION); /* insert arguments into hash table */ @@ -2702,22 +7866,35 @@ Datum _ag_enforce_edge_uniqueness(PG_FUNCTION_ARGS) /* if it is a regular AGTYPE scalar */ else if (AGT_ROOT_IS_SCALAR(agt_i)) { - agtype_value *agtv_id = NULL; + agtype_value agtv_id; int64 *value = NULL; bool found = false; + bool id_needs_free = false; + bool id_found; graphid edge_id = 0; - agtv_id = get_ith_agtype_value_from_container(&agt_i->root, 0); + id_found = get_ith_agtype_value_from_container_no_copy( + &agt_i->root, 0, &agtv_id, &id_needs_free); + Assert(id_found); - if (agtv_id->type != AGTV_INTEGER) + if (agtv_id.type != AGTV_INTEGER) { + if (id_needs_free) + { + pfree_agtype_value_content(&agtv_id); + } ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("_ag_enforce_edge_uniqueness parameter %d must resolve to an agtype integer", i))); } - edge_id = agtv_id->val.int_value; + edge_id = agtv_id.val.int_value; + + if (id_needs_free) + { + pfree_agtype_value_content(&agtv_id); + } /* insert the edge_id */ value = (int64 *)hash_search(exists_hash, (void *)&edge_id, diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index 6700be3f3..04be6064a 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -33,8 +33,6 @@ #include "utils/jsonfuncs.h" #include -#include - #include "access/genam.h" #include "access/heapam.h" #include "catalog/namespace.h" @@ -47,10 +45,18 @@ #include "parser/parse_coerce.h" #include "nodes/nodes.h" #include "utils/acl.h" +#include "utils/ag_cache.h" +#include "utils/array.h" #include "utils/builtins.h" +#include "utils/catcache.h" #include "executor/cypher_utils.h" #include "utils/float.h" +#include "utils/hsearch.h" +#include "utils/inval.h" #include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/relcache.h" +#include "utils/syscache.h" #include "utils/snapmgr.h" #include "utils/typcache.h" #include "utils/age_vle.h" @@ -73,6 +79,18 @@ typedef struct PercentileGroupAggState bool sort_done; } PercentileGroupAggState; +typedef struct btree_index_attr_cache_key +{ + Oid relid; + AttrNumber attnum; +} btree_index_attr_cache_key; + +typedef struct btree_index_attr_cache_entry +{ + btree_index_attr_cache_key key; + Oid index_oid; +} btree_index_attr_cache_entry; + typedef enum /* type categories for datum_to_agtype */ { AGT_TYPE_NULL, /* null, so we didn't bother to identify */ @@ -93,8 +111,27 @@ typedef enum /* type categories for datum_to_agtype */ AGT_TYPE_OTHER /* all else */ } agt_type_category; +typedef struct agtype_variadic_argtype_cache +{ + int nargs; + Oid types[FLEXIBLE_ARRAY_MEMBER]; +} agtype_variadic_argtype_cache; + static inline Datum agtype_from_cstring(char *str, int len); size_t check_string_length(size_t len); +static Oid get_cached_fn_expr_argtype(FunctionCallInfo fcinfo, int argno); +static bool get_single_variadic_arg(FunctionCallInfo fcinfo, + const char *funcname, + bool convert_unknown, + Datum *arg, Oid *type); +static int get_variadic_args_fast(FunctionCallInfo fcinfo, + bool convert_unknown, + int max_args, + Datum **args, Oid **types, bool **nulls, + Datum *fast_args, Oid *fast_types, + bool *fast_nulls); +static Oid get_cached_agtype_variadic_argtype(FunctionCallInfo fcinfo, + int argno, int nargs); static void agtype_in_agtype_annotation(void *pstate, char *annotation); static void agtype_in_object_start(void *pstate); static void agtype_in_object_end(void *pstate); @@ -104,7 +141,7 @@ static void agtype_in_object_field_start(void *pstate, char *fname, bool isnull); static void agtype_put_escaped_value(StringInfo out, agtype_value *scalar_val, bool extend); -static void escape_agtype(StringInfo buf, const char *str); +static void escape_agtype(StringInfo buf, const char *str, int len); bool is_decimal_needed(char *numstr); static void agtype_in_scalar(void *pstate, char *token, agtype_token_type tokentype, @@ -129,6 +166,15 @@ static void add_indent(StringInfo out, bool indent, int level); static void cannot_cast_agtype_value(enum agtype_value_type type, const char *sqltype); static bool agtype_extract_scalar(agtype_container *agtc, agtype_value *res); +static void get_scalar_agtype_value_no_copy(agtype *agt, + agtype_value *value, + bool *needs_free); +static void free_agtype_value_no_copy(agtype_value *value, bool needs_free); +static text *get_agtype_scalar_string_as_text_no_copy(agtype *agt, + const char *funcname, + bool *is_null); +static int64 get_agtype_scalar_integer_no_copy(agtype *agt, + const char *funcname); static agtype_value *execute_array_access_operator(agtype *array, agtype_value *array_value, agtype *array_index); @@ -142,6 +188,7 @@ static agtype_value *execute_map_access_operator_internal(agtype *map, agtype_value *map_value, char *key, int key_len); +static agtype *build_empty_agtype_array(void); static Datum agtype_object_field_impl(FunctionCallInfo fcinfo, agtype *agtype_in, char *key, int key_len, bool as_text); @@ -151,6 +198,14 @@ static Datum agtype_array_element_impl(FunctionCallInfo fcinfo, static Datum process_access_operator_result(FunctionCallInfo fcinfo, agtype_value *agtv, bool as_text); +static Datum process_access_operator_result_no_copy(FunctionCallInfo fcinfo, + agtype_value *agtv, + bool as_text, + bool needs_free); +static agtype *build_vertex_agtype(graphid id, const char *label, + agtype *properties); +static agtype *build_edge_agtype(graphid id, graphid start_id, graphid end_id, + const char *label, agtype *properties); /* typecast functions */ static void agtype_typecast_object(agtype_in_state *state, char *annotation); static void agtype_typecast_array(agtype_in_state *state, char *annotation); @@ -159,9 +214,16 @@ static bool is_object_vertex(agtype_value *agtv); static bool is_object_edge(agtype_value *agtv); static bool is_array_path(agtype_value *agtv); /* graph entity retrieval */ -static Datum get_vertex(const char *graph, const char *vertex_label, +static Oid get_cached_graph_oid_for_name(const char *graph_name, + int graph_name_len); +static Datum get_vertex(const char *vertex_label, Oid vertex_label_table_oid, int64 graphid); -static char *get_label_name(const char *graph_name, graphid element_graphid); +static void initialize_btree_index_attr_cache(void); +static void invalidate_btree_index_attr_cache(Datum arg, Oid relid); +static Oid find_usable_btree_index_for_attr_uncached(Relation rel, + AttrNumber attnum); +static char *get_label_name(const char *graph_name, int graph_name_len, + graphid element_graphid, Oid *label_relation); static float8 get_float_compatible_arg(Datum arg, Oid type, char *funcname, bool *is_null); static Numeric get_numeric_compatible_arg(Datum arg, Oid type, char *funcname, @@ -184,6 +246,9 @@ static int extract_variadic_args_min(FunctionCallInfo fcinfo, static agtype_value *agtype_build_map_as_agtype_value(FunctionCallInfo fcinfo); agtype_value *agtype_composite_to_agtype_value_binary(agtype *a); static agtype_value *tostring_helper(Datum arg, Oid type, char *msghdr); +static bool agtype_string_contains(char *haystack, int haystack_len, + char *needle, int needle_len); +static agtype *text_to_agtype_string_value(text *txt); void *repalloc_check(void *ptr, size_t len) @@ -209,13 +274,145 @@ void pfree_if_not_null(void *ptr) } } +static void get_scalar_agtype_value_no_copy(agtype *agt, + agtype_value *value, + bool *needs_free) +{ + bool found; + + Assert(AGT_ROOT_IS_SCALAR(agt)); + + found = get_ith_agtype_value_from_container_no_copy(&agt->root, 0, value, + needs_free); + Assert(found); +} + +static void free_agtype_value_no_copy(agtype_value *value, bool needs_free) +{ + if (needs_free) + { + pfree_agtype_value_content(value); + } +} + +static text *get_agtype_scalar_string_as_text_no_copy(agtype *agt, + const char *funcname, + bool *is_null) +{ + agtype_value value; + bool needs_free = false; + text *result; + + if (!AGT_ROOT_IS_SCALAR(agt)) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s only supports scalar arguments", funcname))); + } + + get_scalar_agtype_value_no_copy(agt, &value, &needs_free); + + if (value.type == AGTV_NULL) + { + free_agtype_value_no_copy(&value, needs_free); + *is_null = true; + return NULL; + } + + if (value.type != AGTV_STRING) + { + free_agtype_value_no_copy(&value, needs_free); + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s unsupported argument agtype %d", funcname, + value.type))); + } + + result = cstring_to_text_with_len(value.val.string.val, + value.val.string.len); + free_agtype_value_no_copy(&value, needs_free); + *is_null = false; + + return result; +} + +static int64 get_agtype_scalar_integer_no_copy(agtype *agt, + const char *funcname) +{ + agtype_value value; + bool needs_free = false; + int64 result; + + if (!AGT_ROOT_IS_SCALAR(agt)) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s only supports scalar arguments", funcname))); + } + + get_scalar_agtype_value_no_copy(agt, &value, &needs_free); + + if (value.type != AGTV_INTEGER) + { + free_agtype_value_no_copy(&value, needs_free); + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s unsupported argument agtype %d", funcname, + value.type))); + } + + result = value.val.int_value; + free_agtype_value_no_copy(&value, needs_free); + + return result; +} + +static bool agtype_string_contains(char *haystack, int haystack_len, + char *needle, int needle_len) +{ + char *match; + char *end; + + if (needle_len == 0) + { + return true; + } + + if (haystack_len < needle_len) + { + return false; + } + + end = haystack + haystack_len - needle_len + 1; + for (match = haystack; match < end; match++) + { + match = memchr(match, needle[0], end - match); + if (match == NULL) + { + return false; + } + + if (memcmp(match, needle, needle_len) == 0) + { + return true; + } + } + + return false; +} + /* global storage of OID for agtype and _agtype */ static Oid g_AGTYPEOID = InvalidOid; static Oid g_AGTYPEARRAYOID = InvalidOid; +static bool agtype_oid_callback_registered = false; +static HTAB *btree_index_attr_cache = NULL; +static bool btree_index_attr_callback_registered = false; + +static void initialize_agtype_oid_cache(void); +static void invalidate_agtype_oid_cache(Datum arg, int cache_id, + uint32 hash_value); /* helper function to quickly set, if necessary, and retrieve AGTYPEOID */ Oid get_AGTYPEOID(void) { + initialize_agtype_oid_cache(); + if (g_AGTYPEOID == InvalidOid) { g_AGTYPEOID = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid, @@ -229,6 +426,8 @@ Oid get_AGTYPEOID(void) /* helper function to quickly set, if necessary, and retrieve AGTYPEARRAYOID */ Oid get_AGTYPEARRAYOID(void) { + initialize_agtype_oid_cache(); + if (g_AGTYPEARRAYOID == InvalidOid) { g_AGTYPEARRAYOID = GetSysCacheOid2(TYPENAMENSP,Anum_pg_type_oid, @@ -246,6 +445,24 @@ void clear_global_Oids_AGTYPE(void) g_AGTYPEARRAYOID = InvalidOid; } +static void initialize_agtype_oid_cache(void) +{ + if (agtype_oid_callback_registered) + { + return; + } + + CacheRegisterSyscacheCallback(TYPENAMENSP, invalidate_agtype_oid_cache, + (Datum)0); + agtype_oid_callback_registered = true; +} + +static void invalidate_agtype_oid_cache(Datum arg, int cache_id, + uint32 hash_value) +{ + clear_global_Oids_AGTYPE(); +} + /* fast helper function to test for AGTV_NULL in an agtype */ bool is_agtype_null(agtype *agt_arg) { @@ -444,6 +661,154 @@ static inline Datum agtype_from_cstring(char *str, int len) PG_RETURN_POINTER(agt); } +static Oid get_cached_fn_expr_argtype(FunctionCallInfo fcinfo, int argno) +{ + Oid *cached_type; + + if (fcinfo->flinfo->fn_extra == NULL) + { + cached_type = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + sizeof(Oid)); + *cached_type = get_fn_expr_argtype(fcinfo->flinfo, argno); + fcinfo->flinfo->fn_extra = cached_type; + } + + return *(Oid *)fcinfo->flinfo->fn_extra; +} + +static bool get_single_variadic_arg(FunctionCallInfo fcinfo, + const char *funcname, + bool convert_unknown, + Datum *arg, Oid *type) +{ + int nargs; + Datum *args = NULL; + bool *nulls = NULL; + Oid *types = NULL; + + if (PG_NARGS() == 1 && !get_fn_expr_variadic(fcinfo->flinfo)) + { + if (PG_ARGISNULL(0)) + { + return false; + } + + *arg = PG_GETARG_DATUM(0); + *type = get_cached_fn_expr_argtype(fcinfo, 0); + if (convert_unknown && + *type == UNKNOWNOID && + get_fn_expr_arg_stable(fcinfo->flinfo, 0)) + { + *type = TEXTOID; + *arg = CStringGetTextDatum(PG_GETARG_POINTER(0)); + } + return true; + } + + nargs = extract_variadic_args(fcinfo, 0, convert_unknown, + &args, &types, &nulls); + + if (nargs > 1) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s only supports one argument", funcname))); + } + + if (nargs < 1 || nulls[0]) + { + return false; + } + + *arg = args[0]; + *type = types[0]; + + return true; +} + +static int get_variadic_args_fast(FunctionCallInfo fcinfo, + bool convert_unknown, + int max_args, + Datum **args, Oid **types, bool **nulls, + Datum *fast_args, Oid *fast_types, + bool *fast_nulls) +{ + int nargs; + int i; + + if (!get_fn_expr_variadic(fcinfo->flinfo)) + { + nargs = PG_NARGS(); + + if (nargs > max_args) + { + return extract_variadic_args(fcinfo, 0, convert_unknown, + args, types, nulls); + } + + for (i = 0; i < nargs; i++) + { + fast_nulls[i] = PG_ARGISNULL(i); + fast_types[i] = get_cached_agtype_variadic_argtype(fcinfo, i, + nargs); + + if (convert_unknown && + fast_types[i] == UNKNOWNOID && + get_fn_expr_arg_stable(fcinfo->flinfo, i)) + { + fast_types[i] = TEXTOID; + + if (fast_nulls[i]) + { + fast_args[i] = (Datum)0; + } + else + { + fast_args[i] = CStringGetTextDatum(PG_GETARG_POINTER(i)); + } + } + else + { + fast_args[i] = PG_GETARG_DATUM(i); + } + } + + *args = fast_args; + *types = fast_types; + *nulls = fast_nulls; + + return nargs; + } + + return extract_variadic_args(fcinfo, 0, convert_unknown, + args, types, nulls); +} + +static Oid get_cached_agtype_variadic_argtype(FunctionCallInfo fcinfo, + int argno, int nargs) +{ + agtype_variadic_argtype_cache *cache; + int i; + + cache = (agtype_variadic_argtype_cache *)fcinfo->flinfo->fn_extra; + if (cache == NULL || cache->nargs != nargs) + { + cache = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + offsetof(agtype_variadic_argtype_cache, + types) + + sizeof(Oid) * nargs); + cache->nargs = nargs; + + for (i = 0; i < nargs; i++) + { + cache->types[i] = get_fn_expr_argtype(fcinfo->flinfo, i); + } + + fcinfo->flinfo->fn_extra = cache; + } + + return cache->types[argno]; +} + size_t check_string_length(size_t len) { if (len > AGTENTRY_OFFLENMASK) @@ -844,6 +1209,7 @@ static void agtype_put_escaped_value(StringInfo out, agtype_value *scalar_val, bool extend) { char *numstr; + int numstr_len; switch (scalar_val->type) { @@ -851,29 +1217,34 @@ static void agtype_put_escaped_value(StringInfo out, agtype_value *scalar_val, appendBinaryStringInfo(out, "null", 4); break; case AGTV_STRING: - escape_agtype(out, pnstrdup(scalar_val->val.string.val, - scalar_val->val.string.len)); + escape_agtype(out, scalar_val->val.string.val, + scalar_val->val.string.len); break; case AGTV_NUMERIC: - appendStringInfoString( - out, DatumGetCString(DirectFunctionCall1( - numeric_out, PointerGetDatum(scalar_val->val.numeric)))); + numstr = DatumGetCString(DirectFunctionCall1( + numeric_out, PointerGetDatum(scalar_val->val.numeric))); + numstr_len = strlen(numstr); + appendBinaryStringInfo(out, numstr, numstr_len); if (extend) { appendBinaryStringInfo(out, "::numeric", 9); } break; case AGTV_INTEGER: - appendStringInfoString( - out, DatumGetCString(DirectFunctionCall1( - int8out, Int64GetDatum(scalar_val->val.int_value)))); + { + char intbuf[24]; + int intbuf_len; + + intbuf_len = pg_lltoa(scalar_val->val.int_value, intbuf); + appendBinaryStringInfo(out, intbuf, intbuf_len); break; + } case AGTV_FLOAT: - numstr = DatumGetCString(DirectFunctionCall1( - float8out, Float8GetDatum(scalar_val->val.float_value))); - appendStringInfoString(out, numstr); + numstr = float8out_internal(scalar_val->val.float_value); + numstr_len = strlen(numstr); + appendBinaryStringInfo(out, numstr, numstr_len); - if (is_decimal_needed(numstr)) + if (is_decimal_needed_len(numstr, numstr_len)) appendBinaryStringInfo(out, ".0", 2); break; case AGTV_BOOL: @@ -930,12 +1301,13 @@ static void agtype_put_escaped_value(StringInfo out, agtype_value *scalar_val, /* * Produce an agtype string literal, properly escaping characters in the text. */ -static void escape_agtype(StringInfo buf, const char *str) +static void escape_agtype(StringInfo buf, const char *str, int len) { const char *p; + const char *end = str + len; appendStringInfoCharMacro(buf, '"'); - for (p = str; *p; p++) + for (p = str; p < end; p++) { switch (*p) { @@ -972,6 +1344,13 @@ static void escape_agtype(StringInfo buf, const char *str) } bool is_decimal_needed(char *numstr) +{ + Assert(numstr); + + return is_decimal_needed_len(numstr, strlen(numstr)); +} + +bool is_decimal_needed_len(const char *numstr, int len) { int i; @@ -979,7 +1358,7 @@ bool is_decimal_needed(char *numstr) i = (numstr[0] == '-') ? 1 : 0; - while (numstr[i] != '\0') + while (i < len) { if (numstr[i] < '0' || numstr[i] > '9') return false; @@ -1273,23 +1652,37 @@ static text *agtype_value_to_text(agtype_value *scalar_val, switch (scalar_val->type) { case AGTV_INTEGER: - result = cstring_to_text(DatumGetCString(DirectFunctionCall1( - int8out, Int64GetDatum(scalar_val->val.int_value)))); + { + char intbuf[24]; + int intbuf_len; + + intbuf_len = pg_lltoa(scalar_val->val.int_value, intbuf); + result = cstring_to_text_with_len(intbuf, intbuf_len); break; + } case AGTV_FLOAT: - result = cstring_to_text(DatumGetCString(DirectFunctionCall1( - float8out, Float8GetDatum(scalar_val->val.float_value)))); + { + char *numstr = float8out_internal(scalar_val->val.float_value); + + result = cstring_to_text_with_len(numstr, strlen(numstr)); break; + } case AGTV_STRING: result = cstring_to_text_with_len(scalar_val->val.string.val, scalar_val->val.string.len); break; case AGTV_NUMERIC: - result = cstring_to_text(DatumGetCString(DirectFunctionCall1( - numeric_out, PointerGetDatum(scalar_val->val.numeric)))); + { + char *numstr = DatumGetCString(DirectFunctionCall1( + numeric_out, PointerGetDatum(scalar_val->val.numeric))); + + result = cstring_to_text_with_len(numstr, strlen(numstr)); break; + } case AGTV_BOOL: - result = cstring_to_text((scalar_val->val.boolean) ? "true" : "false"); + result = scalar_val->val.boolean ? + cstring_to_text_with_len("true", 4) : + cstring_to_text_with_len("false", 5); break; case AGTV_NULL: result = NULL; @@ -1512,6 +1905,7 @@ static void datum_to_agtype(Datum val, bool is_null, agtype_in_state *result, bool key_scalar) { char *outputstr; + int outputstr_len; bool numeric_error; agtype_value agtv; bool scalar_agtype = false; @@ -1551,9 +1945,11 @@ static void datum_to_agtype(Datum val, bool is_null, agtype_in_state *result, case AGT_TYPE_BOOL: if (key_scalar) { - outputstr = DatumGetBool(val) ? "true" : "false"; + bool boolval = DatumGetBool(val); + + outputstr = boolval ? "true" : "false"; agtv.type = AGTV_STRING; - agtv.val.string.len = strlen(outputstr); + agtv.val.string.len = boolval ? 4 : 5; agtv.val.string.val = outputstr; } else @@ -1596,11 +1992,20 @@ static void datum_to_agtype(Datum val, bool is_null, agtype_in_state *result, break; case AGT_TYPE_NUMERIC: outputstr = OidOutputFunctionCall(outfuncoid, val); + outputstr_len = 0; + numeric_error = false; + for (char *ptr = outputstr; *ptr != '\0'; ptr++) + { + if (*ptr == 'N' || *ptr == 'n') + numeric_error = true; + outputstr_len++; + } + if (key_scalar) { /* always quote keys */ agtv.type = AGTV_STRING; - agtv.val.string.len = strlen(outputstr); + agtv.val.string.len = outputstr_len; agtv.val.string.val = outputstr; } else @@ -1610,8 +2015,6 @@ static void datum_to_agtype(Datum val, bool is_null, agtype_in_state *result, * a string. Invalid numeric output will always have an * 'N' or 'n' in it (I think). */ - numeric_error = (strchr(outputstr, 'N') != NULL || - strchr(outputstr, 'n') != NULL); if (!numeric_error) { Datum numd; @@ -1627,7 +2030,7 @@ static void datum_to_agtype(Datum val, bool is_null, agtype_in_state *result, else { agtv.type = AGTV_STRING; - agtv.val.string.len = strlen(outputstr); + agtv.val.string.len = outputstr_len; agtv.val.string.val = outputstr; } } @@ -2009,7 +2412,7 @@ void add_agtype(Datum val, bool is_null, agtype_in_state *result, agtype_value *string_to_agtype_value(char *s) { - agtype_value *agtv = palloc0(sizeof(agtype_value)); + agtype_value *agtv = palloc(sizeof(agtype_value)); agtv->type = AGTV_STRING; agtv->val.string.len = check_string_length(strlen(s)); @@ -2018,10 +2421,21 @@ agtype_value *string_to_agtype_value(char *s) return agtv; } +static agtype *text_to_agtype_string_value(text *txt) +{ + agtype_value agtv; + + agtv.type = AGTV_STRING; + agtv.val.string.len = check_string_length(VARSIZE_ANY_EXHDR(txt)); + agtv.val.string.val = VARDATA_ANY(txt); + + return agtype_value_to_agtype(&agtv); +} + /* helper function to create an agtype_value integer from an integer */ agtype_value *integer_to_agtype_value(int64 int_value) { - agtype_value *agtv = palloc0(sizeof(agtype_value)); + agtype_value *agtv = palloc(sizeof(agtype_value)); agtv->type = AGTV_INTEGER; agtv->val.int_value = int_value; @@ -2039,14 +2453,18 @@ Datum _agtype_build_path(PG_FUNCTION_ARGS) agtype_in_state result; agtype *agt_result; Datum *args = NULL; + Datum fast_args[16]; bool *nulls = NULL; + bool fast_nulls[16]; Oid *types = NULL; + Oid fast_types[16]; int nargs = 0; int i = 0; bool is_zero_boundary_case = false; /* build argument values to build the object */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 16, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); if (nargs < 1) { @@ -2127,16 +2545,7 @@ Datum _agtype_build_path(PG_FUNCTION_ARGS) AGT_ROOT_IS_BINARY(agt) && AGT_ROOT_BINARY_FLAGS(agt) == AGT_FBINARY_TYPE_VLE_PATH) { - agtype_value *agtv_path = NULL; - int j = 0; - - /* get the VLE path from the container as an agtype_value */ - agtv_path = agtv_materialize_vle_path(agt); - - PG_FREE_IF_COPY(agt, i); - - /* it better be an AGTV_PATH */ - Assert(agtv_path->type == AGTV_PATH); + int64 appended; /* * If the VLE path is the zero boundary case, there isn't an edge to @@ -2144,21 +2553,11 @@ Datum _agtype_build_path(PG_FUNCTION_ARGS) * We need to flag this condition so that we can skip processing the * following vertex. */ - if (agtv_path->val.array.num_elems == 1) + appended = agt_vle_append_path_interior(agt, &result); + PG_FREE_IF_COPY(agt, i); + if (appended == 0) { is_zero_boundary_case = true; - continue; - } - - /* - * Add in the interior path - excluding the start and end vertices. - * The other iterations of the for loop has handled start and will - * handle end. - */ - for (j = 1; j <= agtv_path->val.array.num_elems - 2; j++) - { - result.res = push_agtype_value(&result.parse_state, WAGT_ELEM, - &agtv_path->val.array.elems[j]); } } else if (i % 2 == 1 && (!AGTE_IS_AGTYPE(agt->root.children[0]) || @@ -2210,7 +2609,7 @@ Datum _agtype_build_path(PG_FUNCTION_ARGS) PG_RETURN_POINTER(agt_result); } -Datum make_path(List *path) +Datum make_path_with_length(List *path, int path_len) { agtype *agt_result; ListCell *lc; @@ -2221,14 +2620,14 @@ Datum make_path(List *path) result.res = push_agtype_value(&result.parse_state, WAGT_BEGIN_ARRAY, NULL); - if (list_length(path) < 1) + if (path_len < 1) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("paths require at least 1 vertex"))); } - if (list_length(path) % 2 != 1) + if (path_len % 2 != 1) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -2239,8 +2638,6 @@ Datum make_path(List *path) foreach (lc, path) { agtype *agt = DATUM_GET_AGTYPE_P(PointerGetDatum(lfirst(lc))); - agtype_value *elem; - elem = get_ith_agtype_value_from_container(&agt->root, 0); if (!agt) { @@ -2248,13 +2645,15 @@ Datum make_path(List *path) (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("argument must not be null"))); } - else if (i % 2 == 1 && elem->type != AGTV_VERTEX) + else if (i % 2 == 1 && (!AGTE_IS_AGTYPE(agt->root.children[0]) || + agt->root.children[1] != AGT_HEADER_VERTEX)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("argument %i must be a vertex", i))); } - else if (i % 2 == 0 && elem->type != AGTV_EDGE) + else if (i % 2 == 0 && (!AGTE_IS_AGTYPE(agt->root.children[0]) || + agt->root.children[1] != AGT_HEADER_EDGE)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -2267,7 +2666,6 @@ Datum make_path(List *path) { pfree_if_not_null(agt); } - pfree_agtype_value(elem); i++; } @@ -2283,53 +2681,33 @@ Datum make_path(List *path) PG_RETURN_POINTER(agt_result); } +Datum make_path(List *path) +{ + return make_path_with_length(path, list_length(path)); +} + PG_FUNCTION_INFO_V1(_agtype_build_vertex); -/* - * SQL function agtype_build_vertex(graphid, cstring, agtype) - */ -Datum _agtype_build_vertex(PG_FUNCTION_ARGS) +static agtype *build_vertex_agtype(graphid id, const char *label, + agtype *properties) { - graphid id; - char *label; - agtype *properties; agtype_build_state *bstate; agtype *rawscalar; agtype *vertex; + bool properties_was_null = false; - /* handles null */ - if (fcinfo->args[0].isnull) - { - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("_agtype_build_vertex() graphid cannot be NULL"))); - } - - if (fcinfo->args[1].isnull) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("_agtype_build_vertex() label cannot be NULL"))); - } - - id = AG_GETARG_GRAPHID(0); - label = PG_GETARG_CSTRING(1); - - if (fcinfo->args[2].isnull) + if (properties == NULL) { + properties_was_null = true; bstate = init_agtype_build_state(0, AGT_FOBJECT); properties = build_agtype(bstate); pfree_agtype_build_state(bstate); } - else + else if (!AGT_ROOT_IS_OBJECT(properties)) { - properties = AG_GET_ARG_AGTYPE_P(2); - - if (!AGT_ROOT_IS_OBJECT(properties)) - { - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("_agtype_build_vertex() properties argument must be an object"))); - } + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("_agtype_build_vertex() properties argument must be an object"))); } bstate = init_agtype_build_state(3, AGT_FOBJECT); @@ -2337,7 +2715,7 @@ Datum _agtype_build_vertex(PG_FUNCTION_ARGS) write_string(bstate, "label"); write_string(bstate, "properties"); write_graphid(bstate, id); - write_string(bstate, label); + write_string(bstate, (char *)label); write_container(bstate, properties); vertex = build_agtype(bstate); pfree_agtype_build_state(bstate); @@ -2347,29 +2725,128 @@ Datum _agtype_build_vertex(PG_FUNCTION_ARGS) rawscalar = build_agtype(bstate); pfree_agtype_build_state(bstate); + if (properties_was_null) + { + pfree(properties); + } + + return rawscalar; +} + +/* + * SQL function agtype_build_vertex(graphid, cstring, agtype) + */ +Datum _agtype_build_vertex(PG_FUNCTION_ARGS) +{ + graphid id; + char *label; + agtype *properties; + agtype *result; + + /* handles null */ + if (fcinfo->args[0].isnull) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("_agtype_build_vertex() graphid cannot be NULL"))); + } + + if (fcinfo->args[1].isnull) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("_agtype_build_vertex() label cannot be NULL"))); + } + + id = AG_GETARG_GRAPHID(0); + label = PG_GETARG_CSTRING(1); + + if (fcinfo->args[2].isnull) + { + properties = NULL; + } + else + { + properties = AG_GET_ARG_AGTYPE_P(2); + } + + result = build_vertex_agtype(id, label, properties); + PG_FREE_IF_COPY(label, 1); - PG_FREE_IF_COPY(properties, 2); + if (properties != NULL) + { + PG_FREE_IF_COPY(properties, 2); + } - PG_RETURN_POINTER(rawscalar); + PG_RETURN_POINTER(result); } Datum make_vertex(Datum id, Datum label, Datum properties) { - return DirectFunctionCall3(_agtype_build_vertex, id, label, properties); + return PointerGetDatum(build_vertex_agtype(DATUM_GET_GRAPHID(id), + DatumGetCString(label), + DATUM_GET_AGTYPE_P(properties))); } PG_FUNCTION_INFO_V1(_agtype_build_edge); +static agtype *build_edge_agtype(graphid id, graphid start_id, graphid end_id, + const char *label, agtype *properties) +{ + agtype_build_state *bstate; + agtype *edge; + agtype *rawscalar; + bool properties_was_null = false; + + if (properties == NULL) + { + properties_was_null = true; + bstate = init_agtype_build_state(0, AGT_FOBJECT); + properties = build_agtype(bstate); + pfree_agtype_build_state(bstate); + } + else if (!AGT_ROOT_IS_OBJECT(properties)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("_agtype_build_edge() properties argument must be an object"))); + } + + bstate = init_agtype_build_state(5, AGT_FOBJECT); + write_string(bstate, "id"); + write_string(bstate, "label"); + write_string(bstate, "end_id"); + write_string(bstate, "start_id"); + write_string(bstate, "properties"); + write_graphid(bstate, id); + write_string(bstate, (char *)label); + write_graphid(bstate, end_id); + write_graphid(bstate, start_id); + write_container(bstate, properties); + edge = build_agtype(bstate); + pfree_agtype_build_state(bstate); + + bstate = init_agtype_build_state(1, AGT_FARRAY | AGT_FSCALAR); + write_extended(bstate, edge, AGT_HEADER_EDGE); + rawscalar = build_agtype(bstate); + pfree_agtype_build_state(bstate); + + if (properties_was_null) + { + pfree(properties); + } + + return rawscalar; +} + /* * SQL function agtype_build_edge(graphid, graphid, graphid, cstring, agtype) */ Datum _agtype_build_edge(PG_FUNCTION_ARGS) { - agtype_build_state *bstate; - agtype *edge, *rawscalar; graphid id, start_id, end_id; char *label; agtype *properties; + agtype *result; /* process graph id */ if (fcinfo->args[0].isnull) @@ -2415,52 +2892,32 @@ Datum _agtype_build_edge(PG_FUNCTION_ARGS) /* if the properties object is null, push an empty object */ if (fcinfo->args[4].isnull) { - bstate = init_agtype_build_state(0, AGT_FOBJECT); - properties = build_agtype(bstate); - pfree_agtype_build_state(bstate); + properties = NULL; } else { properties = AG_GET_ARG_AGTYPE_P(4); - - if (!AGT_ROOT_IS_OBJECT(properties)) - { - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("_agtype_build_edge() properties argument must be an object"))); - } } - bstate = init_agtype_build_state(5, AGT_FOBJECT); - write_string(bstate, "id"); - write_string(bstate, "label"); - write_string(bstate, "end_id"); - write_string(bstate, "start_id"); - write_string(bstate, "properties"); - write_graphid(bstate, id); - write_string(bstate, label); - write_graphid(bstate, end_id); - write_graphid(bstate, start_id); - write_container(bstate, properties); - edge = build_agtype(bstate); - pfree_agtype_build_state(bstate); - - bstate = init_agtype_build_state(1, AGT_FARRAY | AGT_FSCALAR); - write_extended(bstate, edge, AGT_HEADER_EDGE); - rawscalar = build_agtype(bstate); - pfree_agtype_build_state(bstate); + result = build_edge_agtype(id, start_id, end_id, label, properties); PG_FREE_IF_COPY(label, 3); - PG_FREE_IF_COPY(properties, 4); + if (properties != NULL) + { + PG_FREE_IF_COPY(properties, 4); + } - PG_RETURN_POINTER(rawscalar); + PG_RETURN_POINTER(result); } Datum make_edge(Datum id, Datum startid, Datum endid, Datum label, Datum properties) { - return DirectFunctionCall5(_agtype_build_edge, id, startid, endid, label, - properties); + return PointerGetDatum(build_edge_agtype(DATUM_GET_GRAPHID(id), + DATUM_GET_GRAPHID(startid), + DATUM_GET_GRAPHID(endid), + DatumGetCString(label), + DATUM_GET_AGTYPE_P(properties))); } static agtype_value *agtype_build_map_as_agtype_value(FunctionCallInfo fcinfo) @@ -2469,11 +2926,15 @@ static agtype_value *agtype_build_map_as_agtype_value(FunctionCallInfo fcinfo) int i; agtype_in_state result; Datum *args; + Datum fast_args[16]; bool *nulls; + bool fast_nulls[16]; Oid *types; + Oid fast_types[16]; /* build argument values to build the object */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 16, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); if (nargs < 0) { @@ -2611,6 +3072,23 @@ Datum agtype_build_map_nonull(PG_FUNCTION_ARGS) PG_FUNCTION_INFO_V1(agtype_build_list); +static agtype *build_empty_agtype_array(void) +{ + agtype_in_state result; + agtype *agt_result; + + memset(&result, 0, sizeof(agtype_in_state)); + + result.res = push_agtype_value(&result.parse_state, WAGT_BEGIN_ARRAY, + NULL); + result.res = push_agtype_value(&result.parse_state, WAGT_END_ARRAY, NULL); + agt_result = agtype_value_to_agtype(result.res); + + pfree_agtype_in_state(&result); + + return agt_result; +} + /* * SQL function agtype_build_list(variadic "any") */ @@ -2620,12 +3098,16 @@ Datum agtype_build_list(PG_FUNCTION_ARGS) int i; agtype_in_state result; Datum *args; + Datum fast_args[16]; bool *nulls; + bool fast_nulls[16]; Oid *types; + Oid fast_types[16]; agtype *agt_result; /*build argument values to build the array */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 16, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); if (nargs < 0) { @@ -3167,22 +3649,7 @@ Datum agtype_to_float8(PG_FUNCTION_ARGS) } else if (agtv.type == AGTV_INTEGER) { - /* - * Get the string representation of the integer because it could be - * too large to fit in a float. Let the float routine determine - * what to do with it. - */ - char *string = DatumGetCString(DirectFunctionCall1(int8out, - Int64GetDatum(agtv.val.int_value))); - bool is_valid = false; - /* turn it into a float */ - result = float8in_internal_null(string, NULL, "double precision", - string, &is_valid); - - /* return null if it was not a invalid float */ - if (!is_valid) - ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("cannot cast to float8, integer value out of range"))); + result = (float8) agtv.val.int_value; } else if (agtv.type == AGTV_NUMERIC) { @@ -3210,8 +3677,9 @@ PG_FUNCTION_INFO_V1(agtype_to_text); Datum agtype_to_text(PG_FUNCTION_ARGS) { agtype *arg_agt; - agtype_value *arg_value; + agtype_value arg_value; text *text_value; + bool value_needs_free = false; /* get the agtype equivalence of any convertable input type */ arg_agt = get_one_agtype_from_variadic_args(fcinfo, 0, 1); @@ -3232,11 +3700,11 @@ Datum agtype_to_text(PG_FUNCTION_ARGS) } /* get the arg parameter */ - arg_value = get_ith_agtype_value_from_container(&arg_agt->root, 0); - PG_FREE_IF_COPY(arg_agt, 0); + get_scalar_agtype_value_no_copy(arg_agt, &arg_value, &value_needs_free); - text_value = agtype_value_to_text(arg_value, true); - pfree_agtype_value(arg_value); + text_value = agtype_value_to_text(&arg_value, true); + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); if (text_value == NULL) { @@ -3254,10 +3722,7 @@ PG_FUNCTION_INFO_V1(text_to_agtype); Datum text_to_agtype(PG_FUNCTION_ARGS) { agtype *result = NULL; - agtype_value agtv; text *text_value = NULL; - char *string = NULL; - int len = 0; if (PG_ARGISNULL(0)) { @@ -3266,21 +3731,7 @@ Datum text_to_agtype(PG_FUNCTION_ARGS) /* get the text value */ text_value = PG_GETARG_TEXT_PP(0); - /* convert it to a string */ - string = text_to_cstring(text_value); - /* get the length */ - len = strlen(string); - - /* create a temporary agtype string */ - agtv.type = AGTV_STRING; - agtv.val.string.len = len; - agtv.val.string.val = pstrdup(string); - - /* free the string */ - pfree_if_not_null(string); - - /* convert to agtype */ - result = agtype_value_to_agtype(&agtv); + result = text_to_agtype_string_value(text_value); /* free the input arg if necessary */ PG_FREE_IF_COPY(text_value, 0); @@ -3395,7 +3846,7 @@ Datum agtype_to_int4_array(PG_FUNCTION_ARGS) int i; /* get the input data type */ - arg_type = get_fn_expr_argtype(fcinfo->flinfo, 0); + arg_type = get_cached_fn_expr_argtype(fcinfo, 0); /* verify the input is agtype */ if (arg_type != AGTYPEOID) @@ -3450,45 +3901,55 @@ static agtype_value *execute_map_access_operator(agtype *map, agtype_value *map_value, agtype *key) { - agtype_value *key_value; + agtype_value key_value; char *key_str; int key_len = 0; + bool key_needs_free = false; + agtype_value *result; /* get the key from the container */ - key_value = get_ith_agtype_value_from_container(&key->root, 0); + get_scalar_agtype_value_no_copy(key, &key_value, &key_needs_free); - switch (key_value->type) + switch (key_value.type) { case AGTV_NULL: + free_agtype_value_no_copy(&key_value, key_needs_free); return NULL; case AGTV_INTEGER: + free_agtype_value_no_copy(&key_value, key_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("AGTV_INTEGER is not a valid key type"))); break; case AGTV_FLOAT: + free_agtype_value_no_copy(&key_value, key_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("AGTV_FLOAT is not a valid key type"))); break; case AGTV_NUMERIC: + free_agtype_value_no_copy(&key_value, key_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("AGTV_NUMERIC is not a valid key type"))); break; case AGTV_BOOL: + free_agtype_value_no_copy(&key_value, key_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("AGTV_BOOL is not a valid key type"))); break; case AGTV_STRING: - key_str = key_value->val.string.val; - key_len = key_value->val.string.len; + key_str = key_value.val.string.val; + key_len = key_value.val.string.len; break; default: + free_agtype_value_no_copy(&key_value, key_needs_free); ereport(ERROR, (errmsg("unknown agtype scalar type"))); break; } - return execute_map_access_operator_internal(map, map_value, key_str, - key_len); + result = execute_map_access_operator_internal(map, map_value, key_str, + key_len); + free_agtype_value_no_copy(&key_value, key_needs_free); + return result; } static agtype_value *execute_map_access_operator_internal( @@ -3534,31 +3995,34 @@ static agtype_value *execute_array_access_operator(agtype *array, agtype_value *array_value, agtype *array_index) { - agtype_value *array_index_value = NULL; + agtype_value array_index_value; + bool array_index_needs_free = false; agtype_value *result = NULL; /* unpack the array index value */ - array_index_value = get_ith_agtype_value_from_container(&array_index->root, - 0); + (void)get_ith_agtype_value_from_container_no_copy(&array_index->root, 0, + &array_index_value, + &array_index_needs_free); /* if AGTV_NULL return NULL */ - if (array_index_value->type == AGTV_NULL) + if (array_index_value.type == AGTV_NULL) { - pfree_agtype_value(array_index_value); + free_agtype_value_no_copy(&array_index_value, + array_index_needs_free); return NULL; } /* index must be an integer */ - if (array_index_value->type != AGTV_INTEGER) + if (array_index_value.type != AGTV_INTEGER) { ereport(ERROR, (errmsg("array index must resolve to an integer value"))); } result = execute_array_access_operator_internal(array, array_value, - array_index_value->val.int_value); + array_index_value.val.int_value); - pfree_agtype_value(array_index_value); + free_agtype_value_no_copy(&array_index_value, array_index_needs_free); return result; } @@ -3681,8 +4145,8 @@ agtype_value *get_agtype_value_object_value(const agtype_value *agtv_object, } /* they are the same length so compare the keys */ - result = strncmp(search_key, agtv_key->val.string.val, - search_key_length); + result = memcmp(search_key, agtv_key->val.string.val, + search_key_length); /* if they don't match */ if (result != 0) @@ -3769,7 +4233,7 @@ static int extract_variadic_args_min(FunctionCallInfo fcinfo, &args_res, &nulls_res, &nargs); /* All the elements of the array have the same type */ - types_res = (Oid *) palloc0(nargs * sizeof(Oid)); + types_res = (Oid *) palloc(nargs * sizeof(Oid)); for (i = 0; i < nargs; i++) { types_res[i] = element_type; @@ -3788,14 +4252,15 @@ static int extract_variadic_args_min(FunctionCallInfo fcinfo, } /* allocate result memory */ - nulls_res = (bool *) palloc0(nargs * sizeof(bool)); - args_res = (Datum *) palloc0(nargs * sizeof(Datum)); - types_res = (Oid *) palloc0(nargs * sizeof(Oid)); + nulls_res = (bool *) palloc(nargs * sizeof(bool)); + args_res = (Datum *) palloc(nargs * sizeof(Datum)); + types_res = (Oid *) palloc(nargs * sizeof(Oid)); for (i = 0; i < nargs; i++) { nulls_res[i] = PG_ARGISNULL(i + variadic_start); - types_res[i] = get_fn_expr_argtype(fcinfo->flinfo, i + variadic_start); + types_res[i] = get_cached_agtype_variadic_argtype( + fcinfo, i + variadic_start, PG_NARGS()); /* * Turn a constant (more or less literal) value that's of unknown @@ -3885,50 +4350,187 @@ static Datum process_access_operator_result(FunctionCallInfo fcinfo, PG_RETURN_NULL(); } +static Datum process_access_operator_result_no_copy(FunctionCallInfo fcinfo, + agtype_value *agtv, + bool as_text, + bool needs_free) +{ + if (agtv != NULL) + { + if (as_text) + { + text *result; + + if (agtv->type == AGTV_BINARY) + { + StringInfo out = makeStringInfo(); + agtype_container *agtc = + (agtype_container *)agtv->val.binary.data; + char *str; + + str = agtype_to_cstring_worker(out, agtc, + agtv->val.binary.len, + false, true); + result = cstring_to_text(str); + } + else + { + result = agtype_value_to_text(agtv, false); + } + + free_agtype_value_no_copy(agtv, needs_free); + + if (result) + { + PG_RETURN_TEXT_P(result); + } + + PG_RETURN_NULL(); + } + else + { + agtype *result = agtype_value_to_agtype(agtv); + + free_agtype_value_no_copy(agtv, needs_free); + AG_RETURN_AGTYPE_P(result); + } + } + + PG_RETURN_NULL(); +} + Datum agtype_array_element_impl(FunctionCallInfo fcinfo, agtype *agtype_in, int element, bool as_text) { - agtype_value *v; + agtype_value value; + bool value_needs_free = false; + uint32 size; if (!AGT_ROOT_IS_ARRAY(agtype_in)) { PG_RETURN_NULL(); } - v = execute_array_access_operator_internal(agtype_in, NULL, element); + size = AGT_ROOT_COUNT(agtype_in); - return process_access_operator_result(fcinfo, v, as_text); + if (element < 0) + { + element = size + element; + } + + if (element < 0 || (uint32)element >= size) + { + PG_RETURN_NULL(); + } + + if (!get_ith_agtype_value_from_container_no_copy(&agtype_in->root, element, + &value, &value_needs_free)) + { + PG_RETURN_NULL(); + } + + return process_access_operator_result_no_copy(fcinfo, &value, as_text, + value_needs_free); } Datum agtype_object_field_impl(FunctionCallInfo fcinfo, agtype *agtype_in, char *key, int key_len, bool as_text) { agtype_value *v; - agtype* process_agtype; if (AGT_ROOT_IS_SCALAR(agtype_in)) { - agtype_value *process_agtv = extract_entity_properties(agtype_in, - false); - if (!process_agtv) + agtype_value scalar_value; + agtype_value *properties; + bool scalar_needs_free = false; + + get_scalar_agtype_value_no_copy(agtype_in, &scalar_value, + &scalar_needs_free); + + if (scalar_value.type == AGTV_VERTEX) + { + properties = &scalar_value.val.object.pairs[2].value; + } + else if (scalar_value.type == AGTV_EDGE) + { + properties = &scalar_value.val.object.pairs[4].value; + } + else if (scalar_value.type == AGTV_PATH) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot extract properties from an agtype path"))); + } + else { + properties = &scalar_value; + } + + if (properties == NULL || properties->type == AGTV_NULL) + { + free_agtype_value_no_copy(&scalar_value, scalar_needs_free); PG_RETURN_NULL(); } - process_agtype = agtype_value_to_agtype(process_agtv); - } - else - { - process_agtype = agtype_in; + if (properties->type != AGTV_OBJECT && + (properties->type != AGTV_BINARY || + !AGTYPE_CONTAINER_IS_OBJECT(properties->val.binary.data))) + { + free_agtype_value_no_copy(&scalar_value, scalar_needs_free); + PG_RETURN_NULL(); + } + + v = execute_map_access_operator_internal(NULL, properties, + key, key_len); + if (v == NULL || v->type == AGTV_NULL) + { + free_agtype_value_no_copy(&scalar_value, scalar_needs_free); + PG_RETURN_NULL(); + } + + if (as_text) + { + text *result; + + if (v->type == AGTV_BINARY) + { + StringInfo out = makeStringInfo(); + agtype_container *agtc = (agtype_container *)v->val.binary.data; + char *str; + + str = agtype_to_cstring_worker(out, agtc, v->val.binary.len, + false, true); + result = cstring_to_text(str); + } + else + { + result = agtype_value_to_text(v, false); + } + + free_agtype_value_no_copy(&scalar_value, scalar_needs_free); + + if (result) + { + PG_RETURN_TEXT_P(result); + } + + PG_RETURN_NULL(); + } + else + { + agtype *result = agtype_value_to_agtype(v); + + free_agtype_value_no_copy(&scalar_value, scalar_needs_free); + AG_RETURN_AGTYPE_P(result); + } } - if (!AGT_ROOT_IS_OBJECT(process_agtype)) + if (!AGT_ROOT_IS_OBJECT(agtype_in)) { PG_RETURN_NULL(); } - v = execute_map_access_operator_internal(process_agtype, NULL, - key, key_len); + v = execute_map_access_operator_internal(agtype_in, NULL, key, key_len); return process_access_operator_result(fcinfo, v, as_text); } @@ -3942,36 +4544,37 @@ Datum agtype_object_field_agtype(PG_FUNCTION_ARGS) if (AGT_ROOT_IS_SCALAR(key)) { - agtype_value *key_value; + agtype_value key_value; + bool key_needs_free = false; - key_value = get_ith_agtype_value_from_container(&key->root, 0); + get_scalar_agtype_value_no_copy(key, &key_value, &key_needs_free); - if (key_value->type == AGTV_INTEGER || - key_value->type == AGTV_STRING) + if (key_value.type == AGTV_INTEGER || + key_value.type == AGTV_STRING) { Datum retval = 0; - if (key_value->type == AGTV_INTEGER) + if (key_value.type == AGTV_INTEGER) { retval = agtype_array_element_impl(fcinfo, agt, - key_value->val.int_value, + key_value.val.int_value, false); } - else if (key_value->type == AGTV_STRING) + else if (key_value.type == AGTV_STRING) { retval = agtype_object_field_impl(fcinfo, agt, - key_value->val.string.val, - key_value->val.string.len, + key_value.val.string.val, + key_value.val.string.len, false); } - pfree_agtype_value(key_value); + free_agtype_value_no_copy(&key_value, key_needs_free); PG_FREE_IF_COPY(agt, 0); PG_FREE_IF_COPY(key, 1); PG_RETURN_POINTER((const void*) retval); } - pfree_agtype_value(key_value); + free_agtype_value_no_copy(&key_value, key_needs_free); } PG_FREE_IF_COPY(agt, 0); @@ -3988,35 +4591,36 @@ Datum agtype_object_field_text_agtype(PG_FUNCTION_ARGS) if (AGT_ROOT_IS_SCALAR(key)) { - agtype_value *key_value; + agtype_value key_value; + bool key_needs_free = false; - key_value = get_ith_agtype_value_from_container(&key->root, 0); + get_scalar_agtype_value_no_copy(key, &key_value, &key_needs_free); - if (key_value->type == AGTV_INTEGER || key_value->type == AGTV_STRING) + if (key_value.type == AGTV_INTEGER || key_value.type == AGTV_STRING) { Datum retval = 0; - if (key_value->type == AGTV_INTEGER) + if (key_value.type == AGTV_INTEGER) { retval = agtype_array_element_impl(fcinfo, agt, - key_value->val.int_value, + key_value.val.int_value, true); } - else if (key_value->type == AGTV_STRING) + else if (key_value.type == AGTV_STRING) { retval = agtype_object_field_impl(fcinfo, agt, - key_value->val.string.val, - key_value->val.string.len, + key_value.val.string.val, + key_value.val.string.len, true); } - pfree_agtype_value(key_value); + free_agtype_value_no_copy(&key_value, key_needs_free); PG_FREE_IF_COPY(agt, 0); PG_FREE_IF_COPY(key, 1); PG_RETURN_POINTER((const void*) retval); } - pfree_agtype_value(key_value); + free_agtype_value_no_copy(&key_value, key_needs_free); } PG_FREE_IF_COPY(agt, 0); @@ -4093,14 +4697,20 @@ PG_FUNCTION_INFO_V1(agtype_access_operator); */ Datum agtype_access_operator(PG_FUNCTION_ARGS) { + Datum fast_args[16]; + bool fast_nulls[16]; Datum *args = NULL; bool *nulls = NULL; Oid *types = NULL; int nargs = 0; agtype *container = NULL; agtype_value *container_value = NULL; + agtype_value scalar_container_value; + bool scalar_container_needs_free = false; agtype *result = NULL; int i = 0; + bool using_fast_args = false; + bool container_is_vpc = false; /* * Fast path for the common 2-argument case (object.property or @@ -4111,6 +4721,7 @@ Datum agtype_access_operator(PG_FUNCTION_ARGS) if (PG_NARGS() == 2) { agtype *key = NULL; + agtype *vpc_container = NULL; /* check for NULLs */ if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) @@ -4126,7 +4737,8 @@ Datum agtype_access_operator(PG_FUNCTION_ARGS) { if (AGT_ROOT_BINARY_FLAGS(container) == AGT_FBINARY_TYPE_VLE_PATH) { - container_value = agtv_materialize_vle_edges(container); + vpc_container = container; + container_is_vpc = true; container = NULL; } else @@ -4139,8 +4751,11 @@ Datum agtype_access_operator(PG_FUNCTION_ARGS) /* handle scalar (vertex or edge) */ else if (AGT_ROOT_IS_SCALAR(container)) { - container_value = get_ith_agtype_value_from_container( - &container->root, 0); + (void)get_ith_agtype_value_from_container_no_copy( + &container->root, 0, &scalar_container_value, + &scalar_container_needs_free); + container_value = &scalar_container_value; + if (container_value->type != AGTV_EDGE && container_value->type != AGTV_VERTEX) { @@ -4160,13 +4775,45 @@ Datum agtype_access_operator(PG_FUNCTION_ARGS) errmsg("key must resolve to a scalar value"))); } - /* extract properties from vertex/edge */ - if (container_value != NULL && - (container_value->type == AGTV_EDGE || - container_value->type == AGTV_VERTEX)) + if (container_is_vpc) { - container_value = (container_value->type == AGTV_EDGE) - ? &container_value->val.object.pairs[4].value + agtype_value key_value; + bool key_needs_free = false; + + (void)get_ith_agtype_value_from_container_no_copy( + &key->root, 0, &key_value, &key_needs_free); + + if (key_value.type == AGTV_NULL) + { + free_agtype_value_no_copy(&key_value, key_needs_free); + PG_RETURN_NULL(); + } + if (key_value.type != AGTV_INTEGER) + { + ereport(ERROR, + (errmsg("array index must resolve to an integer value"))); + } + + container_value = agtv_materialize_vle_edge_at( + vpc_container, key_value.val.int_value); + free_agtype_value_no_copy(&key_value, key_needs_free); + + if (container_value == NULL || container_value->type == AGTV_NULL) + { + PG_RETURN_NULL(); + } + + result = agtype_value_to_agtype(container_value); + return AGTYPE_P_GET_DATUM(result); + } + + /* extract properties from vertex/edge */ + if (container_value != NULL && + (container_value->type == AGTV_EDGE || + container_value->type == AGTV_VERTEX)) + { + container_value = (container_value->type == AGTV_EDGE) + ? &container_value->val.object.pairs[4].value : &container_value->val.object.pairs[2].value; } @@ -4199,10 +4846,14 @@ Datum agtype_access_operator(PG_FUNCTION_ARGS) if (container_value == NULL || container_value->type == AGTV_NULL) { + free_agtype_value_no_copy(&scalar_container_value, + scalar_container_needs_free); PG_RETURN_NULL(); } result = agtype_value_to_agtype(container_value); + free_agtype_value_no_copy(&scalar_container_value, + scalar_container_needs_free); return AGTYPE_P_GET_DATUM(result); } @@ -4212,8 +4863,29 @@ Datum agtype_access_operator(PG_FUNCTION_ARGS) */ /* extract our args, we need at least 2 */ - nargs = extract_variadic_args_min(fcinfo, 0, true, &args, &types, &nulls, - 2); + if (!get_fn_expr_variadic(fcinfo->flinfo) && + PG_NARGS() <= lengthof(fast_args)) + { + nargs = PG_NARGS(); + if (nargs >= 2) + { + for (i = 0; i < nargs; i++) + { + fast_nulls[i] = PG_ARGISNULL(i); + fast_args[i] = PG_GETARG_DATUM(i); + } + + args = fast_args; + nulls = fast_nulls; + using_fast_args = true; + } + } + else + { + nargs = extract_variadic_args_min(fcinfo, 0, true, &args, &types, + &nulls, 2); + } + /* * Return NULL if - * @@ -4226,9 +4898,12 @@ Datum agtype_access_operator(PG_FUNCTION_ARGS) */ if (args == NULL || nargs == 0 || nulls[0] == true) { - pfree_if_not_null(args); - pfree_if_not_null(types); - pfree_if_not_null(nulls); + if (!using_fast_args) + { + pfree_if_not_null(args); + pfree_if_not_null(types); + pfree_if_not_null(nulls); + } PG_RETURN_NULL(); } @@ -4239,26 +4914,26 @@ Datum agtype_access_operator(PG_FUNCTION_ARGS) /* if we have a NULL, return NULL */ if (nulls[i] == true) { - pfree_if_not_null(args); - pfree_if_not_null(types); - pfree_if_not_null(nulls); + if (!using_fast_args) + { + pfree_if_not_null(args); + pfree_if_not_null(types); + pfree_if_not_null(nulls); + } PG_RETURN_NULL(); } } /* get the container argument. It could be an object or array */ container = DATUM_GET_AGTYPE_P(args[0]); + container_is_vpc = false; /* if it is a binary container, check for a VLE vpc */ if (AGT_ROOT_IS_BINARY(container)) { if (AGT_ROOT_BINARY_FLAGS(container) == AGT_FBINARY_TYPE_VLE_PATH) { - /* retrieve an array of edges from the vpc */ - container_value = agtv_materialize_vle_edges(container); - /* clear the container reference */ - - container = NULL; + container_is_vpc = true; } else { @@ -4270,8 +4945,10 @@ Datum agtype_access_operator(PG_FUNCTION_ARGS) /* if it is a scalar, open it and pull out the value */ else if (AGT_ROOT_IS_SCALAR(container)) { - container_value = get_ith_agtype_value_from_container(&container->root, - 0); + (void)get_ith_agtype_value_from_container_no_copy( + &container->root, 0, &scalar_container_value, + &scalar_container_needs_free); + container_value = &scalar_container_value; /* it must be either a vertex or an edge */ if (container_value->type != AGTV_EDGE && @@ -4300,6 +4977,72 @@ Datum agtype_access_operator(PG_FUNCTION_ARGS) errmsg("key must resolve to a scalar value"))); } + if (container_is_vpc) + { + agtype_value key_value; + bool key_needs_free = false; + int64 edge_index; + + (void)get_ith_agtype_value_from_container_no_copy( + &key->root, 0, &key_value, &key_needs_free); + + if (key_value.type == AGTV_NULL) + { + free_agtype_value_no_copy(&key_value, key_needs_free); + if (!using_fast_args) + { + pfree_if_not_null(args); + pfree_if_not_null(types); + pfree_if_not_null(nulls); + } + PG_RETURN_NULL(); + } + if (key_value.type != AGTV_INTEGER) + { + ereport(ERROR, + (errmsg("array index must resolve to an integer value"))); + } + + edge_index = key_value.val.int_value; + free_agtype_value_no_copy(&key_value, key_needs_free); + + if (i + 1 < nargs) + { + container = agt_vle_edge_properties_at(container, edge_index); + if (container == NULL) + { + if (!using_fast_args) + { + pfree_if_not_null(args); + pfree_if_not_null(types); + pfree_if_not_null(nulls); + } + PG_RETURN_NULL(); + } + + container_is_vpc = false; + container_value = NULL; + continue; + } + + container_value = agtv_materialize_vle_edge_at(container, + edge_index); + if (container_value == NULL || container_value->type == AGTV_NULL) + { + if (!using_fast_args) + { + pfree_if_not_null(args); + pfree_if_not_null(types); + pfree_if_not_null(nulls); + } + PG_RETURN_NULL(); + } + + container_is_vpc = false; + container = NULL; + continue; + } + /* * Check for a vertex or edge container_value and extract the properties * object. @@ -4353,6 +5096,14 @@ Datum agtype_access_operator(PG_FUNCTION_ARGS) /* for NULL values return NULL */ if (container_value == NULL || container_value->type == AGTV_NULL) { + free_agtype_value_no_copy(&scalar_container_value, + scalar_container_needs_free); + if (!using_fast_args) + { + pfree_if_not_null(args); + pfree_if_not_null(types); + pfree_if_not_null(nulls); + } PG_RETURN_NULL(); } @@ -4360,12 +5111,17 @@ Datum agtype_access_operator(PG_FUNCTION_ARGS) container = NULL; } - pfree_if_not_null(args); - pfree_if_not_null(types); - pfree_if_not_null(nulls); + if (!using_fast_args) + { + pfree_if_not_null(args); + pfree_if_not_null(types); + pfree_if_not_null(nulls); + } /* serialize and return the result */ result = agtype_value_to_agtype(container_value); + free_agtype_value_no_copy(&scalar_container_value, + scalar_container_needs_free); return AGTYPE_P_GET_DATUM(result); } @@ -4376,8 +5132,12 @@ PG_FUNCTION_INFO_V1(agtype_access_slice); */ Datum agtype_access_slice(PG_FUNCTION_ARGS) { - agtype_value *lidx_value = NULL; - agtype_value *uidx_value = NULL; + agtype_value lidx_value; + agtype_value uidx_value; + agtype_value *lidx_value_ptr = NULL; + agtype_value *uidx_value_ptr = NULL; + bool lidx_needs_free = false; + bool uidx_needs_free = false; agtype_in_state result; agtype *agt_result = NULL; agtype *agt_array = NULL; @@ -4388,6 +5148,7 @@ Datum agtype_access_slice(PG_FUNCTION_ARGS) int64 lower_index = 0; uint32 array_size = 0; int64 i = 0; + bool is_vpc = false; /* return null if the array to slice is null */ if (PG_ARGISNULL(0)) @@ -4404,20 +5165,18 @@ Datum agtype_access_slice(PG_FUNCTION_ARGS) /* get the array parameter and verify that it is a list */ agt_array = AG_GET_ARG_AGTYPE_P(0); + is_vpc = AGT_ROOT_IS_VPC(agt_array); - if ((!AGT_ROOT_IS_ARRAY(agt_array) && !AGT_ROOT_IS_VPC(agt_array)) || AGT_ROOT_IS_SCALAR(agt_array)) + if ((!AGT_ROOT_IS_ARRAY(agt_array) && !is_vpc) || + AGT_ROOT_IS_SCALAR(agt_array)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("slice must access a list"))); } - /* If we have a vpc, decode it and get AGTV_ARRAY agtype_value */ - if (AGT_ROOT_IS_VPC(agt_array)) + if (is_vpc) { - agtv_array = agtv_materialize_vle_edges(agt_array); - - /* get the size of array */ - array_size = agtv_array->val.array.num_elems; + array_size = (uint32)agtv_vle_edge_count(agt_array); } else { @@ -4432,14 +5191,18 @@ Datum agtype_access_slice(PG_FUNCTION_ARGS) else { agt_lidx = AG_GET_ARG_AGTYPE_P(1); - lidx_value = get_ith_agtype_value_from_container(&agt_lidx->root, 0); + (void)get_ith_agtype_value_from_container_no_copy(&agt_lidx->root, 0, + &lidx_value, + &lidx_needs_free); + lidx_value_ptr = &lidx_value; /* * Under Cypher null-propagation semantics, list[a..b] is null when * either bound is null. Return null directly instead of silently * treating AGTV_NULL as an omitted bound. */ - if (lidx_value->type == AGTV_NULL) + if (lidx_value_ptr->type == AGTV_NULL) { + free_agtype_value_no_copy(lidx_value_ptr, lidx_needs_free); PG_RETURN_NULL(); } } @@ -4452,39 +5215,44 @@ Datum agtype_access_slice(PG_FUNCTION_ARGS) else { agt_uidx = AG_GET_ARG_AGTYPE_P(2); - uidx_value = get_ith_agtype_value_from_container(&agt_uidx->root, 0); + (void)get_ith_agtype_value_from_container_no_copy(&agt_uidx->root, 0, + &uidx_value, + &uidx_needs_free); + uidx_value_ptr = &uidx_value; /* Symmetric to the lower bound: null propagates to a null result. */ - if (uidx_value->type == AGTV_NULL) + if (uidx_value_ptr->type == AGTV_NULL) { + free_agtype_value_no_copy(lidx_value_ptr, lidx_needs_free); + free_agtype_value_no_copy(uidx_value_ptr, uidx_needs_free); PG_RETURN_NULL(); } } /* if both indices are NULL (AGTV_NULL) return an error */ - if (lidx_value == NULL && uidx_value == NULL) + if (lidx_value_ptr == NULL && uidx_value_ptr == NULL) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("slice start and/or end is required"))); } /* key must be an integer or NULL */ - if ((lidx_value != NULL && lidx_value->type != AGTV_INTEGER) || - (uidx_value != NULL && uidx_value->type != AGTV_INTEGER)) + if ((lidx_value_ptr != NULL && lidx_value_ptr->type != AGTV_INTEGER) || + (uidx_value_ptr != NULL && uidx_value_ptr->type != AGTV_INTEGER)) { ereport(ERROR, (errmsg("array slices must resolve to an integer value"))); } /* set indices if not already set */ - if (lidx_value) + if (lidx_value_ptr) { - lower_index = lidx_value->val.int_value; - pfree_agtype_value(lidx_value); + lower_index = lidx_value_ptr->val.int_value; + free_agtype_value_no_copy(lidx_value_ptr, lidx_needs_free); } - if (uidx_value) + if (uidx_value_ptr) { - upper_index = uidx_value->val.int_value; - pfree_agtype_value(uidx_value); + upper_index = uidx_value_ptr->val.int_value; + free_agtype_value_no_copy(uidx_value_ptr, uidx_needs_free); } /* adjust for negative and out of bounds index values */ @@ -4513,6 +5281,30 @@ Datum agtype_access_slice(PG_FUNCTION_ARGS) upper_index = array_size; } + if (lower_index >= upper_index) + { + agt_result = build_empty_agtype_array(); + + PG_FREE_IF_COPY(agt_array, 0); + PG_FREE_IF_COPY(agt_lidx, 1); + PG_FREE_IF_COPY(agt_uidx, 2); + + PG_RETURN_POINTER(agt_result); + } + + if (is_vpc) + { + agtv_array = agtv_materialize_vle_edges_slice( + agt_array, lower_index, upper_index); + agt_result = agtype_value_to_agtype(agtv_array); + + PG_FREE_IF_COPY(agt_array, 0); + PG_FREE_IF_COPY(agt_lidx, 1); + PG_FREE_IF_COPY(agt_uidx, 2); + + PG_RETURN_POINTER(agt_result); + } + /* build our result array */ memset(&result, 0, sizeof(agtype_in_state)); @@ -4558,9 +5350,10 @@ Datum agtype_in_operator(PG_FUNCTION_ARGS) { agtype *agt_arg, *agt_item; agtype_iterator *it_array, *it_item; - agtype_value *agtv_arg, agtv_item, agtv_elem; + agtype_value agtv_item, agtv_elem; uint32 array_size = 0; bool result = false; + bool is_vpc; uint32 i = 0; /* return null if the array is null */ @@ -4577,55 +5370,11 @@ Datum agtype_in_operator(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("object of IN must be a list"))); } - /* If we have vpc as arg, get the agtype_value AGTV_ARRAY of edges */ - if (AGT_ROOT_IS_VPC(agt_arg)) - { - agtv_arg = agtv_materialize_vle_edges(agt_arg); - array_size = agtv_arg->val.array.num_elems; - - /* return null if the item to find is null */ - if (PG_ARGISNULL(1)) - { - PG_RETURN_NULL(); - } - /* get the item to search for */ - agt_item = AG_GET_ARG_AGTYPE_P(1); - - /* init item iterator */ - it_item = agtype_iterator_init(&agt_item->root); - - /* get value of item */ - agtype_iterator_next(&it_item, &agtv_item, false); - if (agtv_item.type == AGTV_ARRAY && agtv_item.val.array.raw_scalar) - { - agtype_iterator_next(&it_item, &agtv_item, false); - /* check for AGTYPE NULL */ - if (agtv_item.type == AGTV_NULL) - { - PG_RETURN_NULL(); - } - } - - /* iterate through the array, but stop if we find it */ - for (i = 0; i < array_size && !result; i++) - { - agtv_elem = agtv_arg->val.array.elems[i]; + is_vpc = AGT_ROOT_IS_VPC(agt_arg); - /* if both are containers, compare containers */ - if (!IS_A_AGTYPE_SCALAR(&agtv_item) && !IS_A_AGTYPE_SCALAR(&agtv_elem)) - { - result = (compare_agtype_containers_orderability( - &agt_item->root, agtv_elem.val.binary.data) == 0); - } - /* if both are scalars and of the same type, compare scalars */ - else if (IS_A_AGTYPE_SCALAR(&agtv_item) && - IS_A_AGTYPE_SCALAR(&agtv_elem) && - agtv_item.type == agtv_elem.type) - { - result = (compare_agtype_scalar_values(&agtv_item, &agtv_elem) == - 0); - } - } + if (is_vpc) + { + array_size = (uint32)agtv_vle_edge_count(agt_arg); } /* Else we need to iterate agtype_container */ else @@ -4649,37 +5398,57 @@ Datum agtype_in_operator(PG_FUNCTION_ARGS) } array_size = AGT_ROOT_COUNT(agt_arg); + } - /* return null if the item to find is null */ - if (PG_ARGISNULL(1)) - { - PG_RETURN_NULL(); - } - /* get the item to search for */ - agt_item = AG_GET_ARG_AGTYPE_P(1); + /* return null if the item to find is null */ + if (PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + /* get the item to search for */ + agt_item = AG_GET_ARG_AGTYPE_P(1); - /* init item iterator */ - it_item = agtype_iterator_init(&agt_item->root); + /* init item iterator */ + it_item = agtype_iterator_init(&agt_item->root); - /* get value of item */ + /* get value of item */ + agtype_iterator_next(&it_item, &agtv_item, false); + if (agtv_item.type == AGTV_ARRAY && agtv_item.val.array.raw_scalar) + { agtype_iterator_next(&it_item, &agtv_item, false); - if (agtv_item.type == AGTV_ARRAY && agtv_item.val.array.raw_scalar) + /* check for AGTYPE NULL */ + if (agtv_item.type == AGTV_NULL) { - agtype_iterator_next(&it_item, &agtv_item, false); - /* check for AGTYPE NULL */ - if (agtv_item.type == AGTV_NULL) - { - PG_RETURN_NULL(); - } + PG_RETURN_NULL(); } + } + + if (is_vpc && agtv_item.type == AGTV_EDGE) + { + agtype_value *agtv_edge_id = AGTYPE_EDGE_GET_ID(&agtv_item); - /* iterate through the array, but stop if we find it */ - for (i = 0; i < array_size && !result; i++) + Assert(agtv_edge_id != NULL); + Assert(agtv_edge_id->type == AGTV_INTEGER); + + result = agt_vle_contains_edge_id(agt_arg, + agtv_edge_id->val.int_value); + return boolean_to_agtype(result); + } + + /* iterate through the array, but stop if we find it */ + for (i = 0; i < array_size && !result; i++) + { + if (is_vpc) { - /* get next element */ - agtype_iterator_next(&it_array, &agtv_elem, true); + agtype_value *agtv_vle_edge = NULL; + + agtv_vle_edge = agtv_materialize_vle_edge_at(agt_arg, i); + Assert(agtv_vle_edge != NULL); + agtv_elem = *agtv_vle_edge; + /* if both are containers, compare containers */ - if (!IS_A_AGTYPE_SCALAR(&agtv_item) && !IS_A_AGTYPE_SCALAR(&agtv_elem)) + if (!IS_A_AGTYPE_SCALAR(&agtv_item) && + !IS_A_AGTYPE_SCALAR(&agtv_elem)) { result = (compare_agtype_containers_orderability( &agt_item->root, agtv_elem.val.binary.data) == 0); @@ -4689,9 +5458,31 @@ Datum agtype_in_operator(PG_FUNCTION_ARGS) IS_A_AGTYPE_SCALAR(&agtv_elem) && agtv_item.type == agtv_elem.type) { - result = (compare_agtype_scalar_values(&agtv_item, &agtv_elem) == - 0); + result = (compare_agtype_scalar_values(&agtv_item, + &agtv_elem) == 0); } + + pfree_agtype_value(agtv_vle_edge); + continue; + } + else + { + agtype_iterator_next(&it_array, &agtv_elem, true); + } + + /* if both are containers, compare containers */ + if (!IS_A_AGTYPE_SCALAR(&agtv_item) && !IS_A_AGTYPE_SCALAR(&agtv_elem)) + { + result = (compare_agtype_containers_orderability( + &agt_item->root, agtv_elem.val.binary.data) == 0); + } + /* if both are scalars and of the same type, compare scalars */ + else if (IS_A_AGTYPE_SCALAR(&agtv_item) && + IS_A_AGTYPE_SCALAR(&agtv_elem) && + agtv_item.type == agtv_elem.type) + { + result = (compare_agtype_scalar_values(&agtv_item, &agtv_elem) == + 0); } } @@ -4710,21 +5501,27 @@ Datum agtype_string_match_starts_with(PG_FUNCTION_ARGS) if (AGT_ROOT_IS_SCALAR(lhs) && AGT_ROOT_IS_SCALAR(rhs)) { - agtype_value *lhs_value; - agtype_value *rhs_value; + agtype_value lhs_value; + agtype_value rhs_value; + bool lhs_needs_free = false; + bool rhs_needs_free = false; - lhs_value = get_ith_agtype_value_from_container(&lhs->root, 0); - rhs_value = get_ith_agtype_value_from_container(&rhs->root, 0); + (void)get_ith_agtype_value_from_container_no_copy(&lhs->root, 0, + &lhs_value, + &lhs_needs_free); + (void)get_ith_agtype_value_from_container_no_copy(&rhs->root, 0, + &rhs_value, + &rhs_needs_free); - if (lhs_value->type == AGTV_STRING && rhs_value->type == AGTV_STRING) + if (lhs_value.type == AGTV_STRING && rhs_value.type == AGTV_STRING) { - if (lhs_value->val.string.len < rhs_value->val.string.len) + if (lhs_value.val.string.len < rhs_value.val.string.len) { result = false; } - else if (strncmp(lhs_value->val.string.val, - rhs_value->val.string.val, - rhs_value->val.string.len) == 0) + else if (memcmp(lhs_value.val.string.val, + rhs_value.val.string.val, + rhs_value.val.string.len) == 0) { result = true; } @@ -4733,8 +5530,14 @@ Datum agtype_string_match_starts_with(PG_FUNCTION_ARGS) result = false; } } - pfree_agtype_value(lhs_value); - pfree_agtype_value(rhs_value); + if (lhs_needs_free) + { + pfree_agtype_value_content(&lhs_value); + } + if (rhs_needs_free) + { + pfree_agtype_value_content(&rhs_value); + } } else { @@ -4760,23 +5563,29 @@ Datum agtype_string_match_ends_with(PG_FUNCTION_ARGS) if (AGT_ROOT_IS_SCALAR(lhs) && AGT_ROOT_IS_SCALAR(rhs)) { - agtype_value *lhs_value; - agtype_value *rhs_value; + agtype_value lhs_value; + agtype_value rhs_value; + bool lhs_needs_free = false; + bool rhs_needs_free = false; - lhs_value = get_ith_agtype_value_from_container(&lhs->root, 0); - rhs_value = get_ith_agtype_value_from_container(&rhs->root, 0); + (void)get_ith_agtype_value_from_container_no_copy(&lhs->root, 0, + &lhs_value, + &lhs_needs_free); + (void)get_ith_agtype_value_from_container_no_copy(&rhs->root, 0, + &rhs_value, + &rhs_needs_free); - if (lhs_value->type == AGTV_STRING && rhs_value->type == AGTV_STRING) + if (lhs_value.type == AGTV_STRING && rhs_value.type == AGTV_STRING) { - if (lhs_value->val.string.len < rhs_value->val.string.len) + if (lhs_value.val.string.len < rhs_value.val.string.len) { result = false; } - else if (strncmp((lhs_value->val.string.val + - lhs_value->val.string.len - - rhs_value->val.string.len), - rhs_value->val.string.val, - rhs_value->val.string.len) == 0) + else if (memcmp((lhs_value.val.string.val + + lhs_value.val.string.len - + rhs_value.val.string.len), + rhs_value.val.string.val, + rhs_value.val.string.len) == 0) { result = true; } @@ -4785,8 +5594,14 @@ Datum agtype_string_match_ends_with(PG_FUNCTION_ARGS) result = false; } } - pfree_agtype_value(lhs_value); - pfree_agtype_value(rhs_value); + if (lhs_needs_free) + { + pfree_agtype_value_content(&lhs_value); + } + if (rhs_needs_free) + { + pfree_agtype_value_content(&rhs_value); + } } else { @@ -4812,38 +5627,33 @@ Datum agtype_string_match_contains(PG_FUNCTION_ARGS) if (AGT_ROOT_IS_SCALAR(lhs) && AGT_ROOT_IS_SCALAR(rhs)) { - agtype_value *lhs_value; - agtype_value *rhs_value; + agtype_value lhs_value; + agtype_value rhs_value; + bool lhs_needs_free = false; + bool rhs_needs_free = false; - lhs_value = get_ith_agtype_value_from_container(&lhs->root, 0); - rhs_value = get_ith_agtype_value_from_container(&rhs->root, 0); + (void)get_ith_agtype_value_from_container_no_copy(&lhs->root, 0, + &lhs_value, + &lhs_needs_free); + (void)get_ith_agtype_value_from_container_no_copy(&rhs->root, 0, + &rhs_value, + &rhs_needs_free); - if (lhs_value->type == AGTV_STRING && rhs_value->type == AGTV_STRING) + if (lhs_value.type == AGTV_STRING && rhs_value.type == AGTV_STRING) { - char *l; - char *r; - - if (lhs_value->val.string.len < rhs_value->val.string.len) - { - result = false; - } - - l = pnstrdup(lhs_value->val.string.val, lhs_value->val.string.len); - r = pnstrdup(rhs_value->val.string.val, rhs_value->val.string.len); - - if (strstr(l, r) == NULL) - { - result = false; - } - else - { - result = true; - } - pfree_if_not_null(l); - pfree_if_not_null(r); + result = agtype_string_contains(lhs_value.val.string.val, + lhs_value.val.string.len, + rhs_value.val.string.val, + rhs_value.val.string.len); + } + if (lhs_needs_free) + { + pfree_agtype_value_content(&lhs_value); + } + if (rhs_needs_free) + { + pfree_agtype_value_content(&rhs_value); } - pfree_agtype_value(lhs_value); - pfree_agtype_value(rhs_value); } else { @@ -4869,7 +5679,7 @@ Datum agtype_hash_cmp(PG_FUNCTION_ARGS) agtype *agt; agtype_iterator *it; agtype_iterator_token tok; - agtype_value *r; + agtype_value r; uint64 seed = 0xF0F0F0F0; /* this function returns INTEGER which is 32 bits */ @@ -4880,18 +5690,16 @@ Datum agtype_hash_cmp(PG_FUNCTION_ARGS) agt = AG_GET_ARG_AGTYPE_P(0); - r = palloc0(sizeof(agtype_value)); - it = agtype_iterator_init(&agt->root); - while ((tok = agtype_iterator_next(&it, r, false)) != WAGT_DONE) + while ((tok = agtype_iterator_next(&it, &r, false)) != WAGT_DONE) { - if (IS_A_AGTYPE_SCALAR(r) && AGTYPE_ITERATOR_TOKEN_IS_HASHABLE(tok)) - agtype_hash_scalar_value_extended(r, &hash, seed); - else if (tok == WAGT_BEGIN_ARRAY && !r->val.array.raw_scalar) + if (IS_A_AGTYPE_SCALAR(&r) && AGTYPE_ITERATOR_TOKEN_IS_HASHABLE(tok)) + agtype_hash_scalar_value_extended(&r, &hash, seed); + else if (tok == WAGT_BEGIN_ARRAY && !r.val.array.raw_scalar) seed = LEFT_ROTATE(seed, 4); else if (tok == WAGT_BEGIN_OBJECT) seed = LEFT_ROTATE(seed, 6); - else if (tok == WAGT_END_ARRAY && !r->val.array.raw_scalar) + else if (tok == WAGT_END_ARRAY && !r.val.array.raw_scalar) seed = RIGHT_ROTATE(seed, 4); else if (tok == WAGT_END_OBJECT) seed = RIGHT_ROTATE(seed, 4); @@ -4899,7 +5707,6 @@ Datum agtype_hash_cmp(PG_FUNCTION_ARGS) seed = LEFT_ROTATE(seed, 1); } - pfree_if_not_null(r); PG_FREE_IF_COPY(agt, 0); PG_RETURN_INT32(hash); @@ -4947,11 +5754,12 @@ PG_FUNCTION_INFO_V1(agtype_typecast_numeric); Datum agtype_typecast_numeric(PG_FUNCTION_ARGS) { agtype *arg_agt; - agtype_value *arg_value; + agtype_value arg_value; agtype_value result_value; Datum numd; char *string = NULL; agtype *result = NULL; + bool value_needs_free = false; /* get the agtype equivalence of any convertable input type */ arg_agt = get_one_agtype_from_variadic_args(fcinfo, 0, 1); @@ -4971,35 +5779,31 @@ Datum agtype_typecast_numeric(PG_FUNCTION_ARGS) } /* get the arg parameter */ - arg_value = get_ith_agtype_value_from_container(&arg_agt->root, 0); - - /* we don't need to agtype arg anymore */ - PG_FREE_IF_COPY(arg_agt, 0); + get_scalar_agtype_value_no_copy(arg_agt, &arg_value, &value_needs_free); /* the input type drives the casting */ - switch(arg_value->type) + switch(arg_value.type) { case AGTV_INTEGER: numd = DirectFunctionCall1(int8_numeric, - Int64GetDatum(arg_value->val.int_value)); + Int64GetDatum(arg_value.val.int_value)); break; case AGTV_FLOAT: numd = DirectFunctionCall1(float8_numeric, - Float8GetDatum(arg_value->val.float_value)); + Float8GetDatum(arg_value.val.float_value)); break; case AGTV_NUMERIC: /* it is already a numeric so just return it */ - result = agtype_value_to_agtype(arg_value); - pfree_agtype_value(arg_value); + result = agtype_value_to_agtype(&arg_value); + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); PG_RETURN_POINTER(result); break; /* this allows string numbers and NaN */ case AGTV_STRING: /* we need a null terminated string */ - string = (char *) palloc0(sizeof(char)*arg_value->val.string.len + 1); - string = strncpy(string, arg_value->val.string.val, - arg_value->val.string.len); - string[arg_value->val.string.len] = '\0'; + string = pnstrdup(arg_value.val.string.val, + arg_value.val.string.len); /* pass the string to the numeric in function for conversion */ numd = DirectFunctionCall3(numeric_in, CStringGetDatum(string), @@ -5011,13 +5815,16 @@ Datum agtype_typecast_numeric(PG_FUNCTION_ARGS) break; /* what was given doesn't cast to a numeric */ default: + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("typecast expression must be a number or a string"))); break; } - pfree_agtype_value(arg_value); + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); /* fill in and return our result */ result_value.type = AGTV_NUMERIC; @@ -5036,10 +5843,11 @@ PG_FUNCTION_INFO_V1(agtype_typecast_int); Datum agtype_typecast_int(PG_FUNCTION_ARGS) { agtype *arg_agt; - agtype_value *arg_value; + agtype_value arg_value; agtype_value result_value; Datum d; char *string = NULL; + bool value_needs_free = false; /* get the agtype equivalence of any convertable input type */ arg_agt = get_one_agtype_from_variadic_args(fcinfo, 0, 1); @@ -5059,38 +5867,44 @@ Datum agtype_typecast_int(PG_FUNCTION_ARGS) } /* get the arg parameter */ - arg_value = get_ith_agtype_value_from_container(&arg_agt->root, 0); + get_scalar_agtype_value_no_copy(arg_agt, &arg_value, &value_needs_free); /* check for agtype null */ - if (arg_value->type == AGTV_NULL) + if (arg_value.type == AGTV_NULL) { + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); PG_RETURN_NULL(); } /* the input type drives the casting */ - switch(arg_value->type) + switch(arg_value.type) { case AGTV_INTEGER: - PG_RETURN_POINTER(agtype_value_to_agtype(arg_value)); + { + agtype *result = agtype_value_to_agtype(&arg_value); + + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); + PG_RETURN_POINTER(result); break; + } case AGTV_FLOAT: d = DirectFunctionCall1(dtoi8, - Float8GetDatum(arg_value->val.float_value)); + Float8GetDatum(arg_value.val.float_value)); break; case AGTV_NUMERIC: d = DirectFunctionCall1(numeric_int8, - NumericGetDatum(arg_value->val.numeric)); + NumericGetDatum(arg_value.val.numeric)); break; case AGTV_BOOL: d = DirectFunctionCall1(bool_int4, - BoolGetDatum(arg_value->val.boolean)); + BoolGetDatum(arg_value.val.boolean)); break; case AGTV_STRING: /* we need a null terminated string */ - string = (char *) palloc0(sizeof(char)*arg_value->val.string.len + 1); - string = strncpy(string, arg_value->val.string.val, - arg_value->val.string.len); - string[arg_value->val.string.len] = '\0'; + string = pnstrdup(arg_value.val.string.val, + arg_value.val.string.len); d = DirectFunctionCall1(int8in, CStringGetDatum(string)); /* free the string */ @@ -5099,12 +5913,17 @@ Datum agtype_typecast_int(PG_FUNCTION_ARGS) break; /* what was given doesn't cast to an int */ default: + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("typecast expression must be a number or a string"))); break; } + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); + /* set the result type and return our result */ result_value.type = AGTV_INTEGER; result_value.val.int_value = DatumGetInt64(d); @@ -5119,9 +5938,10 @@ PG_FUNCTION_INFO_V1(agtype_typecast_bool); Datum agtype_typecast_bool(PG_FUNCTION_ARGS) { agtype *arg_agt; - agtype_value *arg_value; + agtype_value arg_value; agtype_value result_value; Datum d; + bool value_needs_free = false; /* get the agtype equivalence of any convertable input type */ arg_agt = get_one_agtype_from_variadic_args(fcinfo, 0, 1); @@ -5141,32 +5961,44 @@ Datum agtype_typecast_bool(PG_FUNCTION_ARGS) } /* get the arg parameter */ - arg_value = get_ith_agtype_value_from_container(&arg_agt->root, 0); + get_scalar_agtype_value_no_copy(arg_agt, &arg_value, &value_needs_free); /* check for agtype null */ - if (arg_value->type == AGTV_NULL) + if (arg_value.type == AGTV_NULL) { + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); PG_RETURN_NULL(); } /* the input type drives the casting */ - switch(arg_value->type) + switch(arg_value.type) { case AGTV_BOOL: - PG_RETURN_POINTER(agtype_value_to_agtype(arg_value)); + { + agtype *result = agtype_value_to_agtype(&arg_value); + + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); + PG_RETURN_POINTER(result); break; + } case AGTV_INTEGER: - d = DirectFunctionCall1(int4_bool, - Int64GetDatum(arg_value->val.int_value)); + d = BoolGetDatum(arg_value.val.int_value != 0); break; /* what was given doesn't cast to a bool */ default: + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("typecast expression must be an integer or a boolean"))); break; } + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); + /* set the result type and return our result */ result_value.type = AGTV_BOOL; result_value.val.boolean = DatumGetBool(d); @@ -5181,10 +6013,11 @@ PG_FUNCTION_INFO_V1(agtype_typecast_float); Datum agtype_typecast_float(PG_FUNCTION_ARGS) { agtype *arg_agt; - agtype_value *arg_value; + agtype_value arg_value; agtype_value result_value; Datum d; char *string = NULL; + bool value_needs_free = false; /* get the agtype equivalence of any convertable input type */ arg_agt = get_one_agtype_from_variadic_args(fcinfo, 0, 1); @@ -5200,34 +6033,40 @@ Datum agtype_typecast_float(PG_FUNCTION_ARGS) errmsg("typecast argument must be a scalar value"))); /* get the arg parameter */ - arg_value = get_ith_agtype_value_from_container(&arg_agt->root, 0); + get_scalar_agtype_value_no_copy(arg_agt, &arg_value, &value_needs_free); /* check for agtype null */ - if (arg_value->type == AGTV_NULL) + if (arg_value.type == AGTV_NULL) + { + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); PG_RETURN_NULL(); + } /* the input type drives the casting */ - switch(arg_value->type) + switch(arg_value.type) { case AGTV_INTEGER: - d = DirectFunctionCall1(int8out, - Int64GetDatum(arg_value->val.int_value)); - d = DirectFunctionCall1(float8in, d); + d = Float8GetDatum((float8)arg_value.val.int_value); break; case AGTV_FLOAT: /* it is already a float so just return it */ - PG_RETURN_POINTER(agtype_value_to_agtype(arg_value)); + { + agtype *result = agtype_value_to_agtype(&arg_value); + + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); + PG_RETURN_POINTER(result); break; + } case AGTV_NUMERIC: d = DirectFunctionCall1(numeric_float8, - NumericGetDatum(arg_value->val.numeric)); + NumericGetDatum(arg_value.val.numeric)); break; /* this allows string numbers, NaN, Infinity, and -Infinity */ case AGTV_STRING: /* we need a null terminated string */ - string = (char *) palloc0(sizeof(char)*arg_value->val.string.len + 1); - string = strncpy(string, arg_value->val.string.val, - arg_value->val.string.len); - string[arg_value->val.string.len] = '\0'; + string = pnstrdup(arg_value.val.string.val, + arg_value.val.string.len); d = DirectFunctionCall1(float8in, CStringGetDatum(string)); /* free the string */ @@ -5236,12 +6075,17 @@ Datum agtype_typecast_float(PG_FUNCTION_ARGS) break; /* what was given doesn't cast to a float */ default: + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("typecast expression must be a number or a string"))); break; } + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); + /* set the result type and return our result */ result_value.type = AGTV_FLOAT; result_value.val.float_value = DatumGetFloat8(d); @@ -5316,10 +6160,10 @@ Datum agtype_typecast_vertex(PG_FUNCTION_ARGS) errmsg("vertex typecast object has invalid or missing properties"))); /* Hand it off to the build vertex routine */ - result = DirectFunctionCall3(_agtype_build_vertex, - Int64GetDatum(agtv_graphid->val.int_value), - CStringGetDatum(agtv_label->val.string.val), - PointerGetDatum(agtype_value_to_agtype(agtv_properties))); + result = PointerGetDatum(build_vertex_agtype( + agtv_graphid->val.int_value, + agtv_label->val.string.val, + agtype_value_to_agtype(agtv_properties))); return result; } @@ -5409,12 +6253,12 @@ Datum agtype_typecast_edge(PG_FUNCTION_ARGS) errmsg("edge typecast object has an invalid or missing end_id"))); /* Hand it off to the build edge routine */ - result = DirectFunctionCall5(_agtype_build_edge, - Int64GetDatum(agtv_graphid->val.int_value), - Int64GetDatum(agtv_startid->val.int_value), - Int64GetDatum(agtv_endid->val.int_value), - CStringGetDatum(agtv_label->val.string.val), - PointerGetDatum(agtype_value_to_agtype(agtv_properties))); + result = PointerGetDatum(build_edge_agtype( + agtv_graphid->val.int_value, + agtv_startid->val.int_value, + agtv_endid->val.int_value, + agtv_label->val.string.val, + agtype_value_to_agtype(agtv_properties))); return result; } @@ -5426,7 +6270,9 @@ Datum agtype_typecast_path(PG_FUNCTION_ARGS) { agtype *arg_agt = NULL; agtype_in_state path; - agtype_value *agtv_element = NULL; + agtype_value *path_values = NULL; + bool *path_needs_free = NULL; + agtype *result; int count = 0; int i = 0; @@ -5451,6 +6297,9 @@ Datum agtype_typecast_path(PG_FUNCTION_ARGS) (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("typecast argument is not a valid path"))); + path_values = palloc0(sizeof(agtype_value) * count); + path_needs_free = palloc0(sizeof(bool) * count); + /* create an agtype array */ memset(&path, 0, sizeof(agtype_in_state)); path.res = push_agtype_value(&path.parse_state, WAGT_BEGIN_ARRAY, NULL); @@ -5462,36 +6311,51 @@ Datum agtype_typecast_path(PG_FUNCTION_ARGS) for (i = 0; i+1 < count; i+=2) { /* get a potential vertex, check it, then add it */ - agtv_element = get_ith_agtype_value_from_container(&arg_agt->root, i); - if (agtv_element == NULL || agtv_element->type != AGTV_VERTEX) + (void)get_ith_agtype_value_from_container_no_copy(&arg_agt->root, i, + &path_values[i], + &path_needs_free[i]); + if (path_values[i].type != AGTV_VERTEX) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("typecast argument is not a valid path"))); - push_agtype_value(&path.parse_state, WAGT_ELEM, agtv_element); + push_agtype_value(&path.parse_state, WAGT_ELEM, &path_values[i]); /* get a potential edge, check it, then add it */ - agtv_element = get_ith_agtype_value_from_container(&arg_agt->root, i+1); - if (agtv_element == NULL || agtv_element->type != AGTV_EDGE) + (void)get_ith_agtype_value_from_container_no_copy(&arg_agt->root, i+1, + &path_values[i+1], + &path_needs_free[i+1]); + if (path_values[i+1].type != AGTV_EDGE) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("typecast argument is not a valid path"))); - push_agtype_value(&path.parse_state, WAGT_ELEM, agtv_element); + push_agtype_value(&path.parse_state, WAGT_ELEM, &path_values[i+1]); } /* validate the last element is a vertex, add it if it is, fail otherwise */ - agtv_element = get_ith_agtype_value_from_container(&arg_agt->root, i); - if (agtv_element == NULL || agtv_element->type != AGTV_VERTEX) + (void)get_ith_agtype_value_from_container_no_copy(&arg_agt->root, i, + &path_values[i], + &path_needs_free[i]); + if (path_values[i].type != AGTV_VERTEX) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("typecast argument is not a valid path"))); - push_agtype_value(&path.parse_state, WAGT_ELEM, agtv_element); + push_agtype_value(&path.parse_state, WAGT_ELEM, &path_values[i]); /* close the array */ path.res = push_agtype_value(&path.parse_state, WAGT_END_ARRAY, NULL); /* set it to a path */ path.res->type = AGTV_PATH; - PG_RETURN_POINTER(agtype_value_to_agtype(path.res)); + result = agtype_value_to_agtype(path.res); + + for (i = 0; i < count; i++) + { + free_agtype_value_no_copy(&path_values[i], path_needs_free[i]); + } + pfree(path_values); + pfree(path_needs_free); + + PG_RETURN_POINTER(result); } PG_FUNCTION_INFO_V1(age_id); @@ -5499,8 +6363,10 @@ PG_FUNCTION_INFO_V1(age_id); Datum age_id(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; - agtype_value *agtv_object = NULL; + agtype_value agtv_object; agtype_value *agtv_result = NULL; + agtype *result; + bool object_needs_free = false; /* check for null */ if (PG_ARGISNULL(0)) @@ -5512,29 +6378,34 @@ Datum age_id(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("id() argument must resolve to a scalar value"))); - /* get the object out of the array */ - agtv_object = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_object, &object_needs_free); /* is it an agtype null? */ - if (agtv_object->type == AGTV_NULL) - PG_RETURN_NULL(); + if (agtv_object.type == AGTV_NULL) + { + free_agtype_value_no_copy(&agtv_object, object_needs_free); + PG_RETURN_NULL(); + } /* check for proper agtype */ - if (agtv_object->type != AGTV_VERTEX && agtv_object->type != AGTV_EDGE) + if (agtv_object.type != AGTV_VERTEX && agtv_object.type != AGTV_EDGE) + { + free_agtype_value_no_copy(&agtv_object, object_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("id() argument must be a vertex, an edge or null"))); + } /* * Direct field access optimization: id is at a fixed index for both * vertex and edge objects due to key length sorting. */ - if (agtv_object->type == AGTV_VERTEX) + if (agtv_object.type == AGTV_VERTEX) { - agtv_result = AGTYPE_VERTEX_GET_ID(agtv_object); + agtv_result = AGTYPE_VERTEX_GET_ID(&agtv_object); } - else if (agtv_object->type == AGTV_EDGE) + else if (agtv_object.type == AGTV_EDGE) { - agtv_result = AGTYPE_EDGE_GET_ID(agtv_object); + agtv_result = AGTYPE_EDGE_GET_ID(&agtv_object); } else { @@ -5543,7 +6414,10 @@ Datum age_id(PG_FUNCTION_ARGS) errmsg("id() unexpected argument type"))); } - PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result)); + result = agtype_value_to_agtype(agtv_result); + free_agtype_value_no_copy(&agtv_object, object_needs_free); + + PG_RETURN_POINTER(result); } PG_FUNCTION_INFO_V1(age_start_id); @@ -5551,8 +6425,10 @@ PG_FUNCTION_INFO_V1(age_start_id); Datum age_start_id(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; - agtype_value *agtv_object = NULL; + agtype_value agtv_object; agtype_value *agtv_result = NULL; + agtype *result; + bool object_needs_free = false; /* check for null */ if (PG_ARGISNULL(0)) @@ -5564,25 +6440,33 @@ Datum age_start_id(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("start_id() argument must resolve to a scalar value"))); - /* get the object out of the array */ - agtv_object = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_object, &object_needs_free); /* is it an agtype null? */ - if (agtv_object->type == AGTV_NULL) - PG_RETURN_NULL(); + if (agtv_object.type == AGTV_NULL) + { + free_agtype_value_no_copy(&agtv_object, object_needs_free); + PG_RETURN_NULL(); + } /* check for proper agtype */ - if (agtv_object->type != AGTV_EDGE) + if (agtv_object.type != AGTV_EDGE) + { + free_agtype_value_no_copy(&agtv_object, object_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("start_id() argument must be an edge or null"))); + } /* * Direct field access optimization: start_id is at index 3 for edge * objects due to key length sorting (id=0, label=1, end_id=2, start_id=3). */ - agtv_result = AGTYPE_EDGE_GET_START_ID(agtv_object); + agtv_result = AGTYPE_EDGE_GET_START_ID(&agtv_object); - PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result)); + result = agtype_value_to_agtype(agtv_result); + free_agtype_value_no_copy(&agtv_object, object_needs_free); + + PG_RETURN_POINTER(result); } PG_FUNCTION_INFO_V1(age_end_id); @@ -5590,8 +6474,10 @@ PG_FUNCTION_INFO_V1(age_end_id); Datum age_end_id(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; - agtype_value *agtv_object = NULL; + agtype_value agtv_object; agtype_value *agtv_result = NULL; + agtype *result; + bool object_needs_free = false; /* check for null */ if (PG_ARGISNULL(0)) @@ -5603,135 +6489,134 @@ Datum age_end_id(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("end_id() argument must resolve to a scalar value"))); - /* get the object out of the array */ - agtv_object = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_object, &object_needs_free); /* is it an agtype null? */ - if (agtv_object->type == AGTV_NULL) - PG_RETURN_NULL(); + if (agtv_object.type == AGTV_NULL) + { + free_agtype_value_no_copy(&agtv_object, object_needs_free); + PG_RETURN_NULL(); + } /* check for proper agtype */ - if (agtv_object->type != AGTV_EDGE) + if (agtv_object.type != AGTV_EDGE) + { + free_agtype_value_no_copy(&agtv_object, object_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("end_id() argument must be an edge or null"))); + } /* * Direct field access optimization: end_id is at index 2 for edge * objects due to key length sorting (id=0, label=1, end_id=2). */ - agtv_result = AGTYPE_EDGE_GET_END_ID(agtv_object); - - PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result)); -} + agtv_result = AGTYPE_EDGE_GET_END_ID(&agtv_object); -/* - * Helper function to return the Datum value of a column (attribute) in a heap - * tuple (row) given the column number (starting from 0), attribute name, typid, - * and whether it can be null. The function is designed to extract and validate - * that the data (attribute) is what is expected. The function will error on any - * issues. - */ -Datum column_get_datum(TupleDesc tupdesc, HeapTuple tuple, int column, - const char *attname, Oid typid, bool isnull) -{ - Form_pg_attribute att; - HeapTupleHeader hth; - HeapTupleData tmptup, *htd; - Datum result; - bool _isnull = true; - - /* build the heap tuple data */ - hth = tuple->t_data; - tmptup.t_len = HeapTupleHeaderGetDatumLength(hth); - tmptup.t_data = hth; - htd = &tmptup; - - /* get the description for the column from the tuple descriptor */ - att = TupleDescAttr(tupdesc, column); - /* get the datum (attribute) for that column*/ - result = heap_getattr(htd, column + 1, tupdesc, &_isnull); - /* verify that the attribute typid is as expected */ - if (att->atttypid != typid) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("Invalid attribute typid. Expected %d, found %d", typid, - att->atttypid))); - /* verify that the attribute name is as expected */ - if (strcmp(att->attname.data, attname) != 0) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("Invalid attribute name. Expected %s, found %s", - attname, att->attname.data))); - /* verify that if it is null, it is allowed to be null */ - if (isnull == false && _isnull == true) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("Attribute was found to be null when null is not allowed."))); + result = agtype_value_to_agtype(agtv_result); + free_agtype_value_no_copy(&agtv_object, object_needs_free); - return result; + PG_RETURN_POINTER(result); } /* * Function to retrieve a label name, given the graph name and graphid of the - * node or edge. The function returns a pointer to a duplicated string that - * needs to be freed when you are finished using it. + * node or edge. The returned pointer is owned by the label cache and must not + * be modified or freed. */ -static char *get_label_name(const char *graph_name, graphid element_graphid) +static char *get_label_name(const char *graph_name, int graph_name_len, + graphid element_graphid, Oid *label_relation) { - ScanKeyData scan_keys[2]; - Relation ag_label; - SysScanDesc scan_desc; - HeapTuple tuple; - TupleDesc tupdesc; - char *result = NULL; - bool column_is_null = false; - Oid graph_oid = get_graph_oid(graph_name); + Oid graph_oid = get_cached_graph_oid_for_name(graph_name, graph_name_len); int32 label_id = get_graphid_label_id(element_graphid); + static Oid cached_graph_oid = InvalidOid; + static int32 cached_label_id = -1; + static uint64 cached_generation = 0; + static NameData cached_label_name; + static Oid cached_label_relation = InvalidOid; + uint64 current_generation = get_label_cache_generation(); + label_cache_data *label_cache; - /* scankey for first match in ag_label, column 2, graphoid, BTEQ, OidEQ */ - ScanKeyInit(&scan_keys[0], Anum_ag_label_graph, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(graph_oid)); - /* scankey for second match in ag_label, column 3, label id, BTEQ, Int4EQ */ - ScanKeyInit(&scan_keys[1], Anum_ag_label_id, BTEqualStrategyNumber, - F_INT4EQ, Int32GetDatum(label_id)); + if (cached_graph_oid == graph_oid && + cached_label_id == label_id && + cached_generation == current_generation) + { + *label_relation = cached_label_relation; + return NameStr(cached_label_name); + } - ag_label = table_open(ag_label_relation_id(), ShareLock); - scan_desc = systable_beginscan(ag_label, ag_label_graph_oid_index_id(), true, - NULL, 2, scan_keys); + label_cache = search_label_graph_oid_cache_cached(graph_oid, label_id); - tuple = systable_getnext(scan_desc); - if (!HeapTupleIsValid(tuple)) + if (label_cache == NULL) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), errmsg("graphid %lu does not exist", element_graphid))); } - /* get the tupdesc - we don't need to release this one */ - tupdesc = RelationGetDescr(ag_label); + *label_relation = label_cache->relation; + cached_graph_oid = graph_oid; + cached_label_id = label_id; + cached_generation = current_generation; + namestrcpy(&cached_label_name, NameStr(label_cache->name)); + cached_label_relation = label_cache->relation; - /* bail if the number of columns differs */ - if (tupdesc->natts != Natts_ag_label) + return NameStr(cached_label_name); +} + +static Oid get_cached_graph_oid_for_name(const char *graph_name, + int graph_name_len) +{ + static NameData cached_graph_name; + static Oid cached_graph_oid = InvalidOid; + static uint64 cached_generation = 0; + uint64 current_generation = get_graph_cache_generation(); + char *graph_name_cstr; + NameData graph_name_buf; + bool free_graph_name = false; + + if (OidIsValid(cached_graph_oid) && + cached_generation == current_generation && + graph_name_len < NAMEDATALEN && + strncmp(NameStr(cached_graph_name), graph_name, graph_name_len) == 0 && + NameStr(cached_graph_name)[graph_name_len] == '\0') { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("Invalid number of attributes for ag_catalog.ag_label"))); + return cached_graph_oid; } - /* get the label name */ - result = NameStr(*DatumGetName(heap_getattr(tuple, Anum_ag_label_name, - tupdesc, &column_is_null))); - /* duplicate it */ - result = pstrdup(result); + if (graph_name_len < NAMEDATALEN) + { + memcpy(NameStr(graph_name_buf), graph_name, graph_name_len); + NameStr(graph_name_buf)[graph_name_len] = '\0'; + graph_name_cstr = NameStr(graph_name_buf); + } + else + { + graph_name_cstr = pnstrdup(graph_name, graph_name_len); + free_graph_name = true; + } - /* end the scan and close the relation */ - systable_endscan(scan_desc); - table_close(ag_label, ShareLock); + cached_graph_oid = get_graph_oid(graph_name_cstr); + if (OidIsValid(cached_graph_oid)) + { + if (graph_name_len < NAMEDATALEN) + { + cached_graph_name = graph_name_buf; + } + else + { + namestrcpy(&cached_graph_name, graph_name_cstr); + } + cached_generation = current_generation; + } + if (free_graph_name) + { + pfree(graph_name_cstr); + } - return result; + return cached_graph_oid; } -static Datum get_vertex(const char *graph, const char *vertex_label, +static Datum get_vertex(const char *vertex_label, Oid vertex_label_table_oid, int64 graphid) { ScanKeyData scan_keys[1]; @@ -5741,15 +6626,12 @@ static Datum get_vertex(const char *graph, const char *vertex_label, TupleDesc tupdesc; Datum id, properties, result; AclResult aclresult; - TupleTableSlot *slot; + TupleTableSlot *slot = NULL; Oid index_oid; + agtype *properties_agt = NULL; bool should_free_tuple = false; + bool isnull; - /* get the specific graph namespace (schema) */ - Oid graph_namespace_oid = get_namespace_oid(graph, false); - /* get the specific vertex label table (schema.vertex_label) */ - Oid vertex_label_table_oid = get_relname_relid(vertex_label, - graph_namespace_oid); /* get the active snapshot */ Snapshot snapshot = GetActiveSnapshot(); @@ -5762,16 +6644,17 @@ static Datum get_vertex(const char *graph, const char *vertex_label, } /* open the relation (table) */ - graph_vertex_label = table_open(vertex_label_table_oid, ShareLock); + graph_vertex_label = table_open(vertex_label_table_oid, AccessShareLock); - index_oid = find_usable_btree_index_for_attr(graph_vertex_label, 1); + index_oid = find_usable_btree_index_for_attr( + graph_vertex_label, Anum_ag_label_vertex_table_id); if (OidIsValid(index_oid)) { IndexScanDesc index_scan_desc; Relation index_rel; - index_rel = index_open(index_oid, ShareLock); + index_rel = index_open(index_oid, AccessShareLock); slot = table_slot_create(graph_vertex_label, NULL); /* initialize the scan key using GRAPHIDEQ for index */ @@ -5784,13 +6667,11 @@ static Datum get_vertex(const char *graph, const char *vertex_label, if (index_getnext_slot(index_scan_desc, ForwardScanDirection, slot)) { - tuple = ExecCopySlotHeapTuple(slot); - should_free_tuple = true; + tuple = ExecFetchSlotHeapTuple(slot, true, &should_free_tuple); } index_endscan(index_scan_desc); - index_close(index_rel, ShareLock); - ExecDropSingleTupleTableSlot(slot); + index_close(index_rel, AccessShareLock); } else { @@ -5809,7 +6690,11 @@ static Datum get_vertex(const char *graph, const char *vertex_label, { table_endscan(scan_desc); } - table_close(graph_vertex_label, ShareLock); + if (slot != NULL) + { + ExecDropSingleTupleTableSlot(slot); + } + table_close(graph_vertex_label, AccessShareLock); ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), errmsg("graphid %lu does not exist", graphid))); @@ -5826,8 +6711,12 @@ static Datum get_vertex(const char *graph, const char *vertex_label, { heap_freetuple(tuple); } + if (slot != NULL) + { + ExecDropSingleTupleTableSlot(slot); + } - table_close(graph_vertex_label, ShareLock); + table_close(graph_vertex_label, AccessShareLock); ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("access to vertex %lu denied by row-level security policy on \"%s\"", @@ -5840,17 +6729,34 @@ static Datum get_vertex(const char *graph, const char *vertex_label, if (tupdesc->natts != 2) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("Invalid number of attributes for %s.%s", graph, - vertex_label ))); - - /* get the id */ - id = column_get_datum(tupdesc, tuple, 0, "id", GRAPHIDOID, true); - /* get the properties */ - properties = column_get_datum(tupdesc, tuple, 1, "properties", - AGTYPEOID, true); + errmsg("Invalid number of attributes for %s", vertex_label))); + + id = heap_getattr(tuple, Anum_ag_label_vertex_table_id, tupdesc, &isnull); + if (isnull) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("vertex id is null for %s", vertex_label))); + } + + properties = heap_getattr(tuple, Anum_ag_label_vertex_table_properties, + tupdesc, &isnull); + if (isnull) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("vertex properties are null for %s", vertex_label))); + } + /* reconstruct the vertex */ - result = DirectFunctionCall3(_agtype_build_vertex, id, - CStringGetDatum(vertex_label), properties); + properties_agt = DATUM_GET_AGTYPE_P(properties); + result = PointerGetDatum(build_vertex_agtype(DATUM_GET_GRAPHID(id), + vertex_label, + properties_agt)); + if ((Pointer)properties_agt != DatumGetPointer(properties)) + { + pfree(properties_agt); + } /* end the scan and close the relation with new cleanup logic */ if (scan_desc) @@ -5861,8 +6767,12 @@ static Datum get_vertex(const char *graph, const char *vertex_label, { heap_freetuple(tuple); } + if (slot != NULL) + { + ExecDropSingleTupleTableSlot(slot); + } - table_close(graph_vertex_label, ShareLock); + table_close(graph_vertex_label, AccessShareLock); /* return the vertex datum */ return result; } @@ -5872,12 +6782,17 @@ PG_FUNCTION_INFO_V1(age_startnode); Datum age_startnode(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; - agtype_value *agtv_object = NULL; + agtype_value graph_name_value; + agtype_value edge_value; agtype_value *agtv_value = NULL; - char *graph_name = NULL; + char *graph_name; + int graph_name_len; char *label_name = NULL; + Oid label_relation = InvalidOid; graphid start_id; Datum result; + bool graph_name_needs_free = false; + bool edge_needs_free = false; /* we need the graph name */ Assert(PG_ARGISNULL(0) == false); @@ -5890,10 +6805,11 @@ Datum age_startnode(PG_FUNCTION_ARGS) agt_arg = AG_GET_ARG_AGTYPE_P(0); /* it must be a scalar and must be a string */ Assert(AGT_ROOT_IS_SCALAR(agt_arg)); - agtv_object = get_ith_agtype_value_from_container(&agt_arg->root, 0); - Assert(agtv_object->type == AGTV_STRING); - graph_name = pnstrdup(agtv_object->val.string.val, - agtv_object->val.string.len); + get_scalar_agtype_value_no_copy(agt_arg, &graph_name_value, + &graph_name_needs_free); + Assert(graph_name_value.type == AGTV_STRING); + graph_name = graph_name_value.val.string.val; + graph_name_len = graph_name_value.val.string.len; /* get the edge */ agt_arg = AG_GET_ARG_AGTYPE_P(1); @@ -5901,31 +6817,46 @@ Datum age_startnode(PG_FUNCTION_ARGS) if (!AGT_ROOT_IS_SCALAR(agt_arg)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("startNode() argument must resolve to a scalar value"))); - /* get the object */ - agtv_object = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &edge_value, &edge_needs_free); /* is it an agtype null, return null if it is */ - if (agtv_object->type == AGTV_NULL) - PG_RETURN_NULL(); + if (edge_value.type == AGTV_NULL) + { + free_agtype_value_no_copy(&edge_value, edge_needs_free); + free_agtype_value_no_copy(&graph_name_value, graph_name_needs_free); + PG_RETURN_NULL(); + } /* check for proper agtype */ - if (agtv_object->type != AGTV_EDGE) + if (edge_value.type != AGTV_EDGE) + { + free_agtype_value_no_copy(&edge_value, edge_needs_free); + free_agtype_value_no_copy(&graph_name_value, graph_name_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("startNode() argument must be an edge or null"))); + } - /* get the graphid for start_id */ - agtv_value = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_object, "start_id"); + /* + * Get the graphid for start_id. Edge objects have deterministic field + * ordering, so use direct access instead of a keyed lookup. + */ + agtv_value = AGTYPE_EDGE_GET_START_ID(&edge_value); /* it must not be null and must be an integer */ Assert(agtv_value != NULL); - Assert(agtv_value->type = AGTV_INTEGER); + Assert(agtv_value->type == AGTV_INTEGER); start_id = agtv_value->val.int_value; /* get the label */ - label_name = get_label_name(graph_name, start_id); + label_name = get_label_name(graph_name, graph_name_len, start_id, + &label_relation); /* it must not be null and must be a string */ Assert(label_name != NULL); + Assert(OidIsValid(label_relation)); - result = get_vertex(graph_name, label_name, start_id); + result = get_vertex(label_name, label_relation, start_id); + + free_agtype_value_no_copy(&edge_value, edge_needs_free); + free_agtype_value_no_copy(&graph_name_value, graph_name_needs_free); return result; } @@ -5935,12 +6866,17 @@ PG_FUNCTION_INFO_V1(age_endnode); Datum age_endnode(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; - agtype_value *agtv_object = NULL; + agtype_value graph_name_value; + agtype_value edge_value; agtype_value *agtv_value = NULL; - char *graph_name = NULL; + char *graph_name; + int graph_name_len; char *label_name = NULL; + Oid label_relation = InvalidOid; graphid end_id; Datum result; + bool graph_name_needs_free = false; + bool edge_needs_free = false; /* we need the graph name */ Assert(PG_ARGISNULL(0) == false); @@ -5953,10 +6889,11 @@ Datum age_endnode(PG_FUNCTION_ARGS) agt_arg = AG_GET_ARG_AGTYPE_P(0); /* it must be a scalar and must be a string */ Assert(AGT_ROOT_IS_SCALAR(agt_arg)); - agtv_object = get_ith_agtype_value_from_container(&agt_arg->root, 0); - Assert(agtv_object->type == AGTV_STRING); - graph_name = pnstrdup(agtv_object->val.string.val, - agtv_object->val.string.len); + get_scalar_agtype_value_no_copy(agt_arg, &graph_name_value, + &graph_name_needs_free); + Assert(graph_name_value.type == AGTV_STRING); + graph_name = graph_name_value.val.string.val; + graph_name_len = graph_name_value.val.string.len; /* get the edge */ agt_arg = AG_GET_ARG_AGTYPE_P(1); @@ -5964,31 +6901,46 @@ Datum age_endnode(PG_FUNCTION_ARGS) if (!AGT_ROOT_IS_SCALAR(agt_arg)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("endNode() argument must resolve to a scalar value"))); - /* get the object */ - agtv_object = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &edge_value, &edge_needs_free); /* is it an agtype null, return null if it is */ - if (agtv_object->type == AGTV_NULL) - PG_RETURN_NULL(); + if (edge_value.type == AGTV_NULL) + { + free_agtype_value_no_copy(&edge_value, edge_needs_free); + free_agtype_value_no_copy(&graph_name_value, graph_name_needs_free); + PG_RETURN_NULL(); + } /* check for proper agtype */ - if (agtv_object->type != AGTV_EDGE) + if (edge_value.type != AGTV_EDGE) + { + free_agtype_value_no_copy(&edge_value, edge_needs_free); + free_agtype_value_no_copy(&graph_name_value, graph_name_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("endNode() argument must be an edge or null"))); + } - /* get the graphid for the end_id */ - agtv_value = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_object, "end_id"); + /* + * Get the graphid for end_id. Edge objects have deterministic field + * ordering, so use direct access instead of a keyed lookup. + */ + agtv_value = AGTYPE_EDGE_GET_END_ID(&edge_value); /* it must not be null and must be an integer */ Assert(agtv_value != NULL); - Assert(agtv_value->type = AGTV_INTEGER); + Assert(agtv_value->type == AGTV_INTEGER); end_id = agtv_value->val.int_value; /* get the label */ - label_name = get_label_name(graph_name, end_id); + label_name = get_label_name(graph_name, graph_name_len, end_id, + &label_relation); /* it must not be null and must be a string */ Assert(label_name != NULL); + Assert(OidIsValid(label_relation)); + + result = get_vertex(label_name, label_relation, end_id); - result = get_vertex(graph_name, label_name, end_id); + free_agtype_value_no_copy(&edge_value, edge_needs_free); + free_agtype_value_no_copy(&graph_name_value, graph_name_needs_free); return result; } @@ -5998,8 +6950,10 @@ PG_FUNCTION_INFO_V1(age_head); Datum age_head(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; - agtype_value *agtv_arg = NULL; agtype_value *agtv_result = NULL; + agtype_value borrowed_result; + agtype *result; + bool result_needs_free = false; /* check for null */ if (PG_ARGISNULL(0)) @@ -6022,16 +6976,11 @@ Datum age_head(PG_FUNCTION_ARGS) */ if (AGT_ROOT_IS_VPC(agt_arg)) { - agtv_arg = agtv_materialize_vle_edges(agt_arg); - - /* if we have an empty list, return a null */ - if (agtv_arg->val.array.num_elems == 0) + agtv_result = agtv_materialize_vle_edge_at(agt_arg, 0); + if (agtv_result == NULL) { PG_RETURN_NULL(); } - - /* get the first element of the array */ - agtv_result = &agtv_arg->val.array.elems[0]; } else { @@ -6042,16 +6991,23 @@ Datum age_head(PG_FUNCTION_ARGS) } /* get the first element of the array */ - agtv_result = get_ith_agtype_value_from_container(&agt_arg->root, 0); + (void)get_ith_agtype_value_from_container_no_copy(&agt_arg->root, 0, + &borrowed_result, + &result_needs_free); + agtv_result = &borrowed_result; } /* if it is AGTV_NULL, return null */ if (agtv_result->type == AGTV_NULL) { + free_agtype_value_no_copy(agtv_result, result_needs_free); PG_RETURN_NULL(); } - PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result)); + result = agtype_value_to_agtype(agtv_result); + free_agtype_value_no_copy(agtv_result, result_needs_free); + + PG_RETURN_POINTER(result); } PG_FUNCTION_INFO_V1(age_last); @@ -6059,8 +7015,10 @@ PG_FUNCTION_INFO_V1(age_last); Datum age_last(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; - agtype_value *agtv_arg = NULL; agtype_value *agtv_result = NULL; + agtype_value borrowed_result; + agtype *result; + bool result_needs_free = false; int size; /* check for null */ @@ -6084,18 +7042,11 @@ Datum age_last(PG_FUNCTION_ARGS) */ if (AGT_ROOT_IS_VPC(agt_arg)) { - agtv_arg = agtv_materialize_vle_edges(agt_arg); - - size = agtv_arg->val.array.num_elems; - - /* if we have an empty list, return a null */ - if (size == 0) + agtv_result = agtv_materialize_vle_edge_at(agt_arg, -1); + if (agtv_result == NULL) { PG_RETURN_NULL(); } - - /* get the first element of the array */ - agtv_result = &agtv_arg->val.array.elems[size-1]; } else { @@ -6108,16 +7059,24 @@ Datum age_last(PG_FUNCTION_ARGS) } /* get the first element of the array */ - agtv_result = get_ith_agtype_value_from_container(&agt_arg->root, size-1); + (void)get_ith_agtype_value_from_container_no_copy(&agt_arg->root, + size-1, + &borrowed_result, + &result_needs_free); + agtv_result = &borrowed_result; } /* if it is AGTV_NULL, return null */ if (agtv_result->type == AGTV_NULL) { + free_agtype_value_no_copy(agtv_result, result_needs_free); PG_RETURN_NULL(); } - PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result)); + result = agtype_value_to_agtype(agtv_result); + free_agtype_value_no_copy(agtv_result, result_needs_free); + + PG_RETURN_POINTER(result); } @@ -6133,6 +7092,7 @@ Datum age_tail(PG_FUNCTION_ARGS) agtype_in_state agis_result; int count; int i; + bool is_vpc = false; /* check number of arguments */ if (PG_NARGS() < 1 || PG_NARGS() > 1) @@ -6142,7 +7102,7 @@ Datum age_tail(PG_FUNCTION_ARGS) } /* get the data type */ - arg_type = get_fn_expr_argtype(fcinfo->flinfo, 0); + arg_type = get_cached_fn_expr_argtype(fcinfo, 0); /* check the data type */ if (arg_type != AGTYPEOID) @@ -6158,13 +7118,30 @@ Datum age_tail(PG_FUNCTION_ARGS) } agt_arg = AG_GET_ARG_AGTYPE_P(0); + is_vpc = AGT_ROOT_IS_VPC(agt_arg); + /* check for an array */ - if (!AGT_ROOT_IS_ARRAY(agt_arg) || AGT_ROOT_IS_SCALAR(agt_arg)) + if ((!AGT_ROOT_IS_ARRAY(agt_arg) && !is_vpc) || + AGT_ROOT_IS_SCALAR(agt_arg)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("tail() argument must resolve to a list or null"))); } + if (is_vpc) + { + count = agtv_vle_edge_count(agt_arg); + if (count <= 1) + { + PG_RETURN_POINTER(build_empty_agtype_array()); + } + + agt_result = agtype_value_to_agtype( + agtv_materialize_vle_edges_slice(agt_arg, Min(count, 1), count)); + + PG_RETURN_POINTER(agt_result); + } + count = AGT_ROOT_COUNT(agt_arg); /* @@ -6202,8 +7179,10 @@ PG_FUNCTION_INFO_V1(age_properties); Datum age_properties(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; - agtype_value *agtv_object = NULL; + agtype_value agtv_object; agtype_value *agtv_result = NULL; + agtype *result; + bool object_needs_free = false; /* check for null */ if (PG_ARGISNULL(0)) @@ -6230,18 +7209,19 @@ Datum age_properties(PG_FUNCTION_ARGS) PG_RETURN_POINTER(agt_arg); } - /* get the object out of the array */ - agtv_object = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_object, &object_needs_free); /* is it an agtype null? */ - if (agtv_object->type == AGTV_NULL) + if (agtv_object.type == AGTV_NULL) { + free_agtype_value_no_copy(&agtv_object, object_needs_free); PG_RETURN_NULL(); } /* check for proper agtype */ - if (agtv_object->type != AGTV_VERTEX && agtv_object->type != AGTV_EDGE) + if (agtv_object.type != AGTV_VERTEX && agtv_object.type != AGTV_EDGE) { + free_agtype_value_no_copy(&agtv_object, object_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("properties() argument must be a vertex, an edge or null"))); } @@ -6251,13 +7231,13 @@ Datum age_properties(PG_FUNCTION_ARGS) * (id=0, label=1, properties=2) and index 4 for edge (id=0, label=1, * end_id=2, start_id=3, properties=4) due to key length sorting. */ - if (agtv_object->type == AGTV_VERTEX) + if (agtv_object.type == AGTV_VERTEX) { - agtv_result = AGTYPE_VERTEX_GET_PROPERTIES(agtv_object); + agtv_result = AGTYPE_VERTEX_GET_PROPERTIES(&agtv_object); } - else if (agtv_object->type == AGTV_EDGE) + else if (agtv_object.type == AGTV_EDGE) { - agtv_result = AGTYPE_EDGE_GET_PROPERTIES(agtv_object); + agtv_result = AGTYPE_EDGE_GET_PROPERTIES(&agtv_object); } else { @@ -6266,7 +7246,10 @@ Datum age_properties(PG_FUNCTION_ARGS) errmsg("properties() unexpected argument type"))); } - PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result)); + result = agtype_value_to_agtype(agtv_result); + free_agtype_value_no_copy(&agtv_object, object_needs_free); + + PG_RETURN_POINTER(result); } PG_FUNCTION_INFO_V1(age_length); @@ -6274,70 +7257,75 @@ PG_FUNCTION_INFO_V1(age_length); Datum age_length(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; - agtype_value *agtv_path = NULL; + agtype_value agtv_path; agtype_value agtv_result; + agtype *result; + bool path_needs_free = false; /* check for null */ if (PG_ARGISNULL(0)) PG_RETURN_NULL(); agt_arg = AG_GET_ARG_AGTYPE_P(0); + if (AGT_ROOT_IS_BINARY(agt_arg) && + AGT_ROOT_BINARY_FLAGS(agt_arg) == AGT_FBINARY_TYPE_VLE_PATH) + { + agtv_result.type = AGTV_INTEGER; + agtv_result.val.int_value = agtv_vle_edge_count(agt_arg); + + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); + } + /* check for a scalar */ if (!AGT_ROOT_IS_SCALAR(agt_arg)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("length() argument must resolve to a scalar"))); - /* get the path array */ - agtv_path = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_path, &path_needs_free); /* if it is AGTV_NULL, return null */ - if (agtv_path->type == AGTV_NULL) + if (agtv_path.type == AGTV_NULL) + { + free_agtype_value_no_copy(&agtv_path, path_needs_free); PG_RETURN_NULL(); + } /* check for a path */ - if (agtv_path ->type != AGTV_PATH) + if (agtv_path.type != AGTV_PATH) + { + free_agtype_value_no_copy(&agtv_path, path_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("length() argument must resolve to a path or null"))); + } agtv_result.type = AGTV_INTEGER; - agtv_result.val.int_value = (agtv_path->val.array.num_elems - 1) /2; + agtv_result.val.int_value = (agtv_path.val.array.num_elems - 1) /2; - PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); + result = agtype_value_to_agtype(&agtv_result); + free_agtype_value_no_copy(&agtv_path, path_needs_free); + + PG_RETURN_POINTER(result); } PG_FUNCTION_INFO_V1(age_toboolean); Datum age_toboolean(PG_FUNCTION_ARGS) { - int nargs; - Datum *args; Datum arg; - bool *nulls; - Oid *types; Oid type; agtype_value agtv_result; char *string = NULL; bool result = false; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); - - /* check number of args */ - if (nargs > 1) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("toBoolean() only supports one argument"))); - - /* check for null */ - if (nargs < 0 || nulls[0]) + if (!get_single_variadic_arg(fcinfo, "toBoolean()", true, &arg, &type)) + { PG_RETURN_NULL(); + } /* * toBoolean() supports bool, text, cstring, integer or the agtype bool, * string and integer input. */ - arg = args[0]; - type = types[0]; - if (type != AGTYPEOID) { if (type == BOOLOID) @@ -6345,20 +7333,41 @@ Datum age_toboolean(PG_FUNCTION_ARGS) else if (type == CSTRINGOID || type == TEXTOID) { if (type == CSTRINGOID) - string = DatumGetCString(arg); - else - string = text_to_cstring(DatumGetTextPP(arg)); + { + int len; - if (pg_strcasecmp(string, "true") == 0) - result = true; - else if (pg_strcasecmp(string, "false") == 0) - result = false; + string = DatumGetCString(arg); + len = strlen(string); + if (len == 4 && pg_strncasecmp(string, "true", len) == 0) + result = true; + else if (len == 5 && + pg_strncasecmp(string, "false", len) == 0) + result = false; + else + PG_RETURN_NULL(); + } else - PG_RETURN_NULL(); + { + text *txt = DatumGetTextPP(arg); + int len = VARSIZE_ANY_EXHDR(txt); + + string = VARDATA_ANY(txt); + if (len == 4 && pg_strncasecmp(string, "true", len) == 0) + result = true; + else if (len == 5 && pg_strncasecmp(string, "false", len) == 0) + result = false; + else + PG_RETURN_NULL(); + } } else if (type == INT2OID || type == INT4OID || type == INT8OID) { - result = DatumGetBool(DirectFunctionCall1(int4_bool, arg)); + if (type == INT2OID) + result = DatumGetInt16(arg) != 0; + else if (type == INT4OID) + result = DatumGetInt32(arg) != 0; + else + result = DatumGetInt64(arg) != 0; } else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -6368,7 +7377,8 @@ Datum age_toboolean(PG_FUNCTION_ARGS) else { agtype *agt_arg; - agtype_value *agtv_value; + agtype_value agtv_value; + bool value_needs_free = false; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); @@ -6377,32 +7387,37 @@ Datum age_toboolean(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("toBoolean() only supports scalar arguments"))); - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_value, + &value_needs_free); - if (agtv_value->type == AGTV_BOOL) - result = agtv_value->val.boolean; - else if (agtv_value->type == AGTV_STRING) + if (agtv_value.type == AGTV_BOOL) + result = agtv_value.val.boolean; + else if (agtv_value.type == AGTV_STRING) { - int len = agtv_value->val.string.len; + int len = agtv_value.val.string.len; - string = agtv_value->val.string.val; + string = agtv_value.val.string.val; if (len == 4 && pg_strncasecmp(string, "true", len) == 0) result = true; else if (len == 5 && pg_strncasecmp(string, "false", len) == 0) result = false; else + { + free_agtype_value_no_copy(&agtv_value, value_needs_free); PG_RETURN_NULL(); + } } - else if (agtv_value->type == AGTV_INTEGER) + else if (agtv_value.type == AGTV_INTEGER) { - result = DatumGetBool(DirectFunctionCall1(int4_bool, - Int64GetDatum(agtv_value->val.int_value))); + result = agtv_value.val.int_value != 0; } else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("toBoolean() unsupported argument agtype %d", - agtv_value->type))); + agtv_value.type))); + + free_agtype_value_no_copy(&agtv_value, value_needs_free); } /* build the result */ @@ -6423,7 +7438,6 @@ Datum age_tobooleanlist(PG_FUNCTION_ARGS) agtype_in_state agis_result; agtype_value *elem; agtype_value bool_elem; - char *string = NULL; int count; int i; @@ -6453,23 +7467,30 @@ Datum age_tobooleanlist(PG_FUNCTION_ARGS) /* iterate through the list */ for (i = 0; i < count; i++) { + agtype_value elem_value; + bool elem_needs_free = false; + /* check element's type, it's value, and convert it to boolean if possible. */ - elem = get_ith_agtype_value_from_container(&agt_arg->root, i); + (void)get_ith_agtype_value_from_container_no_copy(&agt_arg->root, i, + &elem_value, + &elem_needs_free); + elem = &elem_value; bool_elem.type = AGTV_BOOL; switch (elem->type) { case AGTV_STRING: - - string = elem->val.string.val; + { + char *string = elem->val.string.val; + int len = elem->val.string.len; - if (pg_strcasecmp(string, "true") == 0) + if (len == 4 && pg_strncasecmp(string, "true", len) == 0) { bool_elem.val.boolean = true; agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &bool_elem); } - else if (pg_strcasecmp(string, "false") == 0) + else if (len == 5 && pg_strncasecmp(string, "false", len) == 0) { bool_elem.val.boolean = false; agis_result.res = push_agtype_value(&agis_result.parse_state, @@ -6481,9 +7502,10 @@ Datum age_tobooleanlist(PG_FUNCTION_ARGS) agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &bool_elem); } - + break; - + } + case AGTV_BOOL: bool_elem.val.boolean = elem->val.boolean; @@ -6494,8 +7516,7 @@ Datum age_tobooleanlist(PG_FUNCTION_ARGS) case AGTV_INTEGER: - bool_elem.val.boolean = DatumGetBool(DirectFunctionCall1(int4_bool, - Int64GetDatum(elem->val.int_value))); + bool_elem.val.boolean = elem->val.int_value != 0; agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &bool_elem); @@ -6509,6 +7530,8 @@ Datum age_tobooleanlist(PG_FUNCTION_ARGS) break; } + + free_agtype_value_no_copy(elem, elem_needs_free); } /* push the end of the array */ @@ -6522,36 +7545,22 @@ PG_FUNCTION_INFO_V1(age_tofloat); Datum age_tofloat(PG_FUNCTION_ARGS) { - int nargs; - Datum *args; Datum arg; - bool *nulls; - Oid *types; agtype_value agtv_result; char *string = NULL; bool is_valid = false; Oid type; float8 result; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); - - /* check number of args */ - if (nargs > 1) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("toFloat() only supports one argument"))); - - /* check for null */ - if (nargs < 0 || nulls[0]) + if (!get_single_variadic_arg(fcinfo, "toFloat()", true, &arg, &type)) + { PG_RETURN_NULL(); + } /* * toFloat() supports integer, float, numeric, text, cstring, or the * agtype integer, float, numeric, and string input */ - arg = args[0]; - type = types[0]; - if (type != AGTYPEOID) { if (type == INT2OID) @@ -6559,20 +7568,7 @@ Datum age_tofloat(PG_FUNCTION_ARGS) else if (type == INT4OID) result = (float8) DatumGetInt32(arg); else if (type == INT8OID) - { - /* - * Get the string representation of the integer because it could be - * too large to fit in a float. Let the float routine determine - * what to do with it. - */ - string = DatumGetCString(DirectFunctionCall1(int8out, arg)); - /* turn it into a float */ - result = float8in_internal_null(string, NULL, "double precision", - string, &is_valid); - /* return null if it was not a invalid float */ - if (!is_valid) - PG_RETURN_NULL(); - } + result = (float8) DatumGetInt64(arg); else if (type == FLOAT4OID) result = (float8) DatumGetFloat4(arg); else if (type == FLOAT8OID) @@ -6600,7 +7596,8 @@ Datum age_tofloat(PG_FUNCTION_ARGS) else { agtype *agt_arg; - agtype_value *agtv_value; + agtype_value agtv_value; + bool value_needs_free = false; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); @@ -6609,39 +7606,38 @@ Datum age_tofloat(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("toFloat() only supports scalar arguments"))); - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_value, + &value_needs_free); - if (agtv_value->type == AGTV_INTEGER) + if (agtv_value.type == AGTV_INTEGER) { - /* get the string representation of the integer */ - string = DatumGetCString(DirectFunctionCall1(int8out, - Int64GetDatum(agtv_value->val.int_value))); - /* turn it into a float */ - result = float8in_internal_null(string, NULL, "double precision", - string, &is_valid); - /* return null if it was an invalid float */ - if (!is_valid) - PG_RETURN_NULL(); + result = (float8) agtv_value.val.int_value; } - else if (agtv_value->type == AGTV_FLOAT) - result = agtv_value->val.float_value; - else if (agtv_value->type == AGTV_NUMERIC) + else if (agtv_value.type == AGTV_FLOAT) + result = agtv_value.val.float_value; + else if (agtv_value.type == AGTV_NUMERIC) result = DatumGetFloat8(DirectFunctionCall1( numeric_float8_no_overflow, - NumericGetDatum(agtv_value->val.numeric))); - else if (agtv_value->type == AGTV_STRING) + NumericGetDatum(agtv_value.val.numeric))); + else if (agtv_value.type == AGTV_STRING) { - string = pnstrdup(agtv_value->val.string.val, - agtv_value->val.string.len); + string = pnstrdup(agtv_value.val.string.val, + agtv_value.val.string.len); result = float8in_internal_null(string, NULL, "double precision", string, &is_valid); + pfree(string); if (!is_valid) + { + free_agtype_value_no_copy(&agtv_value, value_needs_free); PG_RETURN_NULL(); + } } else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("toFloat() unsupported argument agtype %d", - agtv_value->type))); + agtv_value.type))); + + free_agtype_value_no_copy(&agtv_value, value_needs_free); } /* build the result */ @@ -6662,12 +7658,8 @@ Datum age_tofloatlist(PG_FUNCTION_ARGS) agtype_in_state agis_result; agtype_value *elem; agtype_value float_elem; - char *string = NULL; int count; int i; - bool is_valid = false; - float8 float_num; - char buffer[64]; /* check for null */ if (PG_ARGISNULL(0)) @@ -6701,48 +7693,64 @@ Datum age_tofloatlist(PG_FUNCTION_ARGS) /* iterate through the list */ for (i = 0; i < count; i++) { + agtype_value elem_value; + bool elem_needs_free = false; + /* TODO: check element's type, it's value, and convert it to float if possible. */ - elem = get_ith_agtype_value_from_container(&agt_arg->root, i); + (void)get_ith_agtype_value_from_container_no_copy(&agt_arg->root, i, + &elem_value, + &elem_needs_free); + elem = &elem_value; float_elem.type = AGTV_FLOAT; switch (elem->type) { case AGTV_STRING: + { + bool is_valid = false; + char *string; - string = elem->val.string.val; - if (atof(string)) + string = pnstrdup(elem->val.string.val, elem->val.string.len); + float_elem.val.float_value = + float8in_internal_null(string, NULL, "double precision", + string, &is_valid); + pfree(string); + + if (is_valid) { float_elem.type = AGTV_FLOAT; - float_elem.val.float_value = float8in_internal_null(string, NULL, "double precision", - string, &is_valid); - agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &float_elem); + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_ELEM, &float_elem); } else { float_elem.type = AGTV_NULL; - agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &float_elem); + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_ELEM, &float_elem); } break; + } case AGTV_FLOAT: float_elem.type = AGTV_FLOAT; - float_num = elem->val.float_value; - sprintf(buffer, "%f", float_num); - string = buffer; - float_elem.val.float_value = float8in_internal_null(string, NULL, "double precision", string, &is_valid); - agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &float_elem); + float_elem.val.float_value = elem->val.float_value; + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_ELEM, &float_elem); break; default: float_elem.type = AGTV_NULL; - agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &float_elem); + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_ELEM, &float_elem); break; } + + free_agtype_value_no_copy(elem, elem_needs_free); } agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_END_ARRAY, NULL); @@ -6753,73 +7761,27 @@ PG_FUNCTION_INFO_V1(age_tointeger); Datum age_tointeger(PG_FUNCTION_ARGS) { - int nargs; - Datum *args; Datum arg; - bool *nulls; - Oid *types; agtype_value agtv_result; char *string = NULL; bool is_valid = false; Oid type; int64 result; - /* - * Fast path: toInteger() always takes exactly 1 argument. - * Avoid extract_variadic_args overhead by accessing the arg directly - * and caching the type via fn_extra. - */ - if (PG_NARGS() == 1) + if (!get_single_variadic_arg(fcinfo, "toInteger()", true, &arg, &type)) { - if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + } + + if (type != AGTYPEOID) + { + if (type == INT2OID) { - PG_RETURN_NULL(); + result = (int64) DatumGetInt16(arg); } - - arg = PG_GETARG_DATUM(0); - - /* cache the arg type on first call */ - if (fcinfo->flinfo->fn_extra == NULL) + else if (type == INT4OID) { - Oid *cached = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, - sizeof(Oid)); - *cached = get_fn_expr_argtype(fcinfo->flinfo, 0); - fcinfo->flinfo->fn_extra = cached; - } - type = *(Oid *)fcinfo->flinfo->fn_extra; - nargs = 1; - } - else - { - /* fallback variadic path */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); - - /* check number of args */ - if (nargs > 1) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("toInteger() only supports one argument"))); - } - - /* check for null */ - if (nargs < 0 || nulls[0]) - { - PG_RETURN_NULL(); - } - - arg = args[0]; - type = types[0]; - } - - if (type != AGTYPEOID) - { - if (type == INT2OID) - { - result = (int64) DatumGetInt16(arg); - } - else if (type == INT4OID) - { - result = (int64) DatumGetInt32(arg); + result = (int64) DatumGetInt32(arg); } else if (type == INT8OID) { @@ -6883,9 +7845,6 @@ Datum age_tointeger(PG_FUNCTION_ARGS) * If it isn't an integer string, try converting it as a float * string. */ - result = float8in_internal_null(string, NULL, "double precision", - string, &is_valid); - if (*endptr != '\0') { float8 f; @@ -6915,7 +7874,8 @@ Datum age_tointeger(PG_FUNCTION_ARGS) else { agtype *agt_arg; - agtype_value *agtv_value; + agtype_value agtv_value; + bool value_needs_free = false; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); @@ -6924,26 +7884,28 @@ Datum age_tointeger(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("toInteger() only supports scalar arguments"))); - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_value, + &value_needs_free); - if (agtv_value->type == AGTV_INTEGER) - result = agtv_value->val.int_value; - else if (agtv_value->type == AGTV_FLOAT) + if (agtv_value.type == AGTV_INTEGER) + result = agtv_value.val.int_value; + else if (agtv_value.type == AGTV_FLOAT) { - float8 f = agtv_value->val.float_value; + float8 f = agtv_value.val.float_value; if (isnan(f) || isinf(f) || f < (float8)PG_INT64_MIN || f > (float8)PG_INT64_MAX) { + free_agtype_value_no_copy(&agtv_value, value_needs_free); PG_RETURN_NULL(); } result = (int64) f; } - else if (agtv_value->type == AGTV_NUMERIC) + else if (agtv_value.type == AGTV_NUMERIC) { float8 f; - Datum num = NumericGetDatum(agtv_value->val.numeric); + Datum num = NumericGetDatum(agtv_value.val.numeric); f = DatumGetFloat8(DirectFunctionCall1( numeric_float8_no_overflow, num)); @@ -6951,17 +7913,18 @@ Datum age_tointeger(PG_FUNCTION_ARGS) if (isnan(f) || isinf(f) || f < (float8)PG_INT64_MIN || f > (float8)PG_INT64_MAX) { + free_agtype_value_no_copy(&agtv_value, value_needs_free); PG_RETURN_NULL(); } result = (int64) f; } - else if (agtv_value->type == AGTV_STRING) + else if (agtv_value.type == AGTV_STRING) { char *endptr; /* we need a null terminated cstring */ - string = pnstrdup(agtv_value->val.string.val, - agtv_value->val.string.len); + string = pnstrdup(agtv_value.val.string.val, + agtv_value.val.string.len); /* convert it if it is a regular integer string */ result = strtoi64(string, &endptr, 10); @@ -6982,18 +7945,24 @@ Datum age_tointeger(PG_FUNCTION_ARGS) if (!is_valid || isnan(f) || isinf(f) || f < (float8)PG_INT64_MIN || f > (float8)PG_INT64_MAX) { + pfree(string); + free_agtype_value_no_copy(&agtv_value, value_needs_free); PG_RETURN_NULL(); } result = (int64) f; } + + pfree(string); } else { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("toInteger() unsupported argument agtype %d", - agtv_value->type))); + agtv_value.type))); } + + free_agtype_value_no_copy(&agtv_value, value_needs_free); } /* build the result */ @@ -7016,10 +7985,6 @@ Datum age_tointegerlist(PG_FUNCTION_ARGS) agtype_value integer_elem; int count; int i; - char *string = NULL; - int integer_num; - float float_num; - int is_float; /* check for null */ if (PG_ARGISNULL(0)) @@ -7052,72 +8017,98 @@ Datum age_tointegerlist(PG_FUNCTION_ARGS) /* iterate through the list */ for (i = 0; i < count; i++) { + agtype_value elem_value; + bool elem_needs_free = false; + /* TODO: check element's type, it's value, and convert it to integer if possible. */ - elem = get_ith_agtype_value_from_container(&agt_arg->root, i); + (void)get_ith_agtype_value_from_container_no_copy(&agt_arg->root, i, + &elem_value, + &elem_needs_free); + elem = &elem_value; integer_elem.type = AGTV_INTEGER; switch (elem->type) { case AGTV_STRING: + { + char *string; + char *endptr; + int64 value; - string = elem->val.string.val; - integer_elem.type = AGTV_INTEGER; - integer_elem.val.int_value = atoi(string); + string = pnstrdup(elem->val.string.val, elem->val.string.len); + value = strtoi64(string, &endptr, 10); - if (*string == '+' || *string == '-' || (*string >= '0' && *string <= '9')) + if (*endptr != '\0') { - is_float = 1; - while (*(++string)) + bool is_valid = false; + float8 f; + + f = float8in_internal_null(string, NULL, "double precision", + string, &is_valid); + if (!is_valid || isnan(f) || isinf(f) || + f < (float8)PG_INT64_MIN || f > (float8)PG_INT64_MAX) { - if(!(*string >= '0' && *string <= '9')) - { - if(*string == '.' && is_float) - { - is_float--; - } - else - { - integer_elem.type = AGTV_NULL; - break; - } - } + integer_elem.type = AGTV_NULL; + } + else + { + integer_elem.type = AGTV_INTEGER; + integer_elem.val.int_value = (int64)f; } } else { - - integer_elem.type = AGTV_NULL; + integer_elem.type = AGTV_INTEGER; + integer_elem.val.int_value = value; } - agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &integer_elem); + pfree(string); + + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_ELEM, &integer_elem); break; + } case AGTV_FLOAT: + { + float8 f = elem->val.float_value; - integer_elem.type = AGTV_INTEGER; - float_num = elem->val.float_value; - integer_elem.val.int_value = (int)float_num; - agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &integer_elem); + if (isnan(f) || isinf(f) || + f < (float8)PG_INT64_MIN || f > (float8)PG_INT64_MAX) + { + integer_elem.type = AGTV_NULL; + } + else + { + integer_elem.type = AGTV_INTEGER; + integer_elem.val.int_value = (int64)f; + } + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_ELEM, &integer_elem); break; + } case AGTV_INTEGER: integer_elem.type = AGTV_INTEGER; - integer_num = elem->val.int_value; - integer_elem.val.int_value = integer_num; - agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &integer_elem); + integer_elem.val.int_value = elem->val.int_value; + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_ELEM, &integer_elem); break; default: integer_elem.type = AGTV_NULL; - agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &integer_elem); + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_ELEM, &integer_elem); break; } + + free_agtype_value_no_copy(elem, elem_needs_free); } agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_END_ARRAY, NULL); @@ -7128,28 +8119,13 @@ PG_FUNCTION_INFO_V1(age_size); Datum age_size(PG_FUNCTION_ARGS) { - int nargs; - Datum *args; Datum arg; - bool *nulls; - Oid *types; agtype_value agtv_result; char *string = NULL; Oid type; int64 result; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); - - /* check number of args */ - if (nargs > 1) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("size() only supports one argument"))); - } - - /* check for null */ - if (nargs < 0 || nulls[0]) + if (!get_single_variadic_arg(fcinfo, "size()", true, &arg, &type)) { PG_RETURN_NULL(); } @@ -7157,9 +8133,6 @@ Datum age_size(PG_FUNCTION_ARGS) /* * size() supports cstring, text, or the agtype string or list input */ - arg = args[0]; - type = types[0]; - if (type == CSTRINGOID) { string = DatumGetCString(arg); @@ -7167,35 +8140,39 @@ Datum age_size(PG_FUNCTION_ARGS) } else if (type == TEXTOID) { - string = text_to_cstring(DatumGetTextPP(arg)); - result = strlen(string); + result = VARSIZE_ANY_EXHDR(DatumGetTextPP(arg)); } else if (type == AGTYPEOID) { agtype *agt_arg; - agtype_value *agtv_value; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); if (AGT_ROOT_IS_SCALAR(agt_arg)) { - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + agtype_value agtv_value; + bool value_needs_free = false; - if (agtv_value->type == AGTV_STRING) + get_scalar_agtype_value_no_copy(agt_arg, &agtv_value, + &value_needs_free); + + if (agtv_value.type == AGTV_STRING) { - result = agtv_value->val.string.len; + result = agtv_value.val.string.len; } else { + free_agtype_value_no_copy(&agtv_value, value_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("size() unsupported argument"))); } + + free_agtype_value_no_copy(&agtv_value, value_needs_free); } else if (AGT_ROOT_IS_VPC(agt_arg)) { - agtv_value = agtv_materialize_vle_edges(agt_arg); - result = agtv_value->val.array.num_elems; + result = agtv_vle_edge_count(agt_arg); } else if (AGT_ROOT_IS_ARRAY(agt_arg)) { @@ -7248,8 +8225,10 @@ PG_FUNCTION_INFO_V1(age_type); Datum age_type(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; - agtype_value *agtv_object = NULL; + agtype_value agtv_object; agtype_value *agtv_result = NULL; + agtype *result; + bool object_needs_free = false; /* check for null */ if (PG_ARGISNULL(0)) @@ -7261,24 +8240,32 @@ Datum age_type(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("type() argument must resolve to a scalar value"))); - /* get the object out of the array */ - agtv_object = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_object, &object_needs_free); /* is it an agtype null? */ - if (agtv_object->type == AGTV_NULL) - PG_RETURN_NULL(); + if (agtv_object.type == AGTV_NULL) + { + free_agtype_value_no_copy(&agtv_object, object_needs_free); + PG_RETURN_NULL(); + } /* check for proper agtype */ - if (agtv_object->type != AGTV_EDGE) + if (agtv_object.type != AGTV_EDGE) + { + free_agtype_value_no_copy(&agtv_object, object_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("type() argument must be an edge or null"))); + } - agtv_result = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_object, "label"); + agtv_result = AGTYPE_EDGE_GET_LABEL(&agtv_object); Assert(agtv_result != NULL); - Assert(agtv_result->type = AGTV_STRING); + Assert(agtv_result->type == AGTV_STRING); - PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result)); + result = agtype_value_to_agtype(agtv_result); + free_agtype_value_no_copy(&agtv_object, object_needs_free); + + PG_RETURN_POINTER(result); } PG_FUNCTION_INFO_V1(age_exists); @@ -7305,23 +8292,19 @@ PG_FUNCTION_INFO_V1(age_isempty); Datum age_isempty(PG_FUNCTION_ARGS) { - Datum *args; Datum arg; - bool *nulls; - Oid *types; char *string = NULL; Oid type; int64 result; - /* extract argument values */ - extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + if (!get_single_variadic_arg(fcinfo, "isEmpty()", true, &arg, &type)) + { + PG_RETURN_NULL(); + } /* * isEmpty() supports cstring, text, or the agtype string or list input */ - arg = args[0]; - type = types[0]; - if (type == CSTRINGOID) { string = DatumGetCString(arg); @@ -7329,35 +8312,38 @@ Datum age_isempty(PG_FUNCTION_ARGS) } else if (type == TEXTOID) { - string = text_to_cstring(DatumGetTextPP(arg)); - result = strlen(string); + result = VARSIZE_ANY_EXHDR(DatumGetTextPP(arg)); } else if (type == AGTYPEOID) { agtype *agt_arg; - agtype_value *agtv_value; + agtype_value agtv_value; + bool value_needs_free = false; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); if (AGT_ROOT_IS_SCALAR(agt_arg)) { - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_value, + &value_needs_free); - if (agtv_value->type == AGTV_STRING) + if (agtv_value.type == AGTV_STRING) { - result = agtv_value->val.string.len; + result = agtv_value.val.string.len; } else { + free_agtype_value_no_copy(&agtv_value, value_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("isEmpty() unsupported argument, expected a List, Map, or String"))); } + + free_agtype_value_no_copy(&agtv_value, value_needs_free); } else if (AGT_ROOT_IS_VPC(agt_arg)) { - agtv_value = agtv_materialize_vle_edges(agt_arg); - result = agtv_value->val.array.num_elems; + result = agtv_vle_edge_count(agt_arg); } else if (AGT_ROOT_IS_ARRAY(agt_arg)) { @@ -7390,8 +8376,10 @@ PG_FUNCTION_INFO_V1(age_label); Datum age_label(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; - agtype_value *agtv_value = NULL; + agtype_value agtv_value; agtype_value *label = NULL; + agtype *result; + bool value_needs_free = false; /* check for NULL, NULL is FALSE */ if (PG_ARGISNULL(0)) @@ -7411,11 +8399,12 @@ Datum age_label(PG_FUNCTION_ARGS) } - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_value, &value_needs_free); /* fail if agtype value isn't an edge or vertex */ - if (agtv_value->type != AGTV_VERTEX && agtv_value->type != AGTV_EDGE) + if (agtv_value.type != AGTV_VERTEX && agtv_value.type != AGTV_EDGE) { + free_agtype_value_no_copy(&agtv_value, value_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("label() argument must resolve to an edge or vertex"))); @@ -7425,13 +8414,13 @@ Datum age_label(PG_FUNCTION_ARGS) * Direct field access optimization: label is at a fixed index for both * vertex and edge objects due to key length sorting. */ - if (agtv_value->type == AGTV_VERTEX) + if (agtv_value.type == AGTV_VERTEX) { - label = AGTYPE_VERTEX_GET_LABEL(agtv_value); + label = AGTYPE_VERTEX_GET_LABEL(&agtv_value); } - else if (agtv_value->type == AGTV_EDGE) + else if (agtv_value.type == AGTV_EDGE) { - label = AGTYPE_EDGE_GET_LABEL(agtv_value); + label = AGTYPE_EDGE_GET_LABEL(&agtv_value); } else { @@ -7440,7 +8429,10 @@ Datum age_label(PG_FUNCTION_ARGS) errmsg("label() unexpected argument type"))); } - PG_RETURN_POINTER(agtype_value_to_agtype(label)); + result = agtype_value_to_agtype(label); + free_agtype_value_no_copy(&agtv_value, value_needs_free); + + PG_RETURN_POINTER(result); } PG_FUNCTION_INFO_V1(age_tostring); @@ -7470,7 +8462,7 @@ Datum age_tostring(PG_FUNCTION_ARGS) /* get the argument and type */ arg = PG_GETARG_DATUM(0); - type = get_fn_expr_argtype(fcinfo->flinfo, 0); + type = get_cached_fn_expr_argtype(fcinfo, 0); /* verify that if the type is UNKNOWNOID it can be converted */ if (type == UNKNOWNOID && !get_fn_expr_arg_stable(fcinfo->flinfo, 0)) @@ -7502,12 +8494,20 @@ Datum age_tostring(PG_FUNCTION_ARGS) * Helper function to take any valid type and convert it to an agtype string. * Returns NULL for NULL input. */ +static char *int64_to_palloc_string(int64 value, int *string_len) +{ + char *string = palloc(24); + + *string_len = pg_lltoa(value, string); + + return string; +} + static agtype_value *tostring_helper(Datum arg, Oid type, char *msghdr) { agtype_value *agtv_result = NULL; char *string = NULL; - - agtv_result = palloc0(sizeof(agtype_value)); + int string_len = -1; /* * toString() supports: unknown, integer, float, numeric, text, cstring, @@ -7523,36 +8523,40 @@ static agtype_value *tostring_helper(Datum arg, Oid type, char *msghdr) { char *str = DatumGetPointer(arg); - string = pnstrdup(str, strlen(str)); + string = str; + string_len = strlen(str); } /* if it is not an AGTYPEOID */ else if (type != AGTYPEOID) { if (type == INT2OID) { - string = DatumGetCString(DirectFunctionCall1(int8out, - Int64GetDatum((int64) DatumGetInt16(arg)))); + string = int64_to_palloc_string((int64) DatumGetInt16(arg), + &string_len); } else if (type == INT4OID) { - string = DatumGetCString(DirectFunctionCall1(int8out, - Int64GetDatum((int64) DatumGetInt32(arg)))); + string = int64_to_palloc_string((int64) DatumGetInt32(arg), + &string_len); } else if (type == INT8OID) { - string = DatumGetCString(DirectFunctionCall1(int8out, arg)); + string = int64_to_palloc_string(DatumGetInt64(arg), &string_len); } else if (type == FLOAT4OID) { - string = DatumGetCString(DirectFunctionCall1(float8out, arg)); + string = float8out_internal((float8)DatumGetFloat4(arg)); + string_len = strlen(string); } else if (type == FLOAT8OID) { - string = DatumGetCString(DirectFunctionCall1(float8out, arg)); + string = float8out_internal(DatumGetFloat8(arg)); + string_len = strlen(string); } else if (type == NUMERICOID) { string = DatumGetCString(DirectFunctionCall1(numeric_out, arg)); + string_len = strlen(string); } else if (type == CSTRINGOID) { @@ -7560,11 +8564,17 @@ static agtype_value *tostring_helper(Datum arg, Oid type, char *msghdr) } else if (type == TEXTOID) { - string = text_to_cstring(DatumGetTextPP(arg)); + text *txt = DatumGetTextPP(arg); + + string = VARDATA_ANY(txt); + string_len = VARSIZE_ANY_EXHDR(txt); } else if (type == BOOLOID) { - string = DatumGetBool(arg) ? "true" : "false"; + bool value = DatumGetBool(arg); + + string = value ? "true" : "false"; + string_len = value ? 4 : 5; } else if (type == REGTYPEOID) { @@ -7597,37 +8607,43 @@ static agtype_value *tostring_helper(Datum arg, Oid type, char *msghdr) if (agtv_value->type == AGTV_NULL) { + pfree_agtype_value(agtv_value); return NULL; } else if (agtv_value->type == AGTV_INTEGER) { - string = DatumGetCString(DirectFunctionCall1(int8out, - Int64GetDatum(agtv_value->val.int_value))); + string = int64_to_palloc_string(agtv_value->val.int_value, + &string_len); } else if (agtv_value->type == AGTV_FLOAT) { - string = DatumGetCString(DirectFunctionCall1(float8out, - Float8GetDatum(agtv_value->val.float_value))); + string = float8out_internal(agtv_value->val.float_value); + string_len = strlen(string); } else if (agtv_value->type == AGTV_STRING) { - string = pnstrdup(agtv_value->val.string.val, - agtv_value->val.string.len); + string = agtv_value->val.string.val; + string_len = agtv_value->val.string.len; } else if (agtv_value->type == AGTV_NUMERIC) { string = DatumGetCString(DirectFunctionCall1(numeric_out, PointerGetDatum(agtv_value->val.numeric))); + string_len = strlen(string); } else if (agtv_value->type == AGTV_BOOL) { - string = (agtv_value->val.boolean) ? "true" : "false"; + string = agtv_value->val.boolean ? "true" : "false"; + string_len = agtv_value->val.boolean ? 4 : 5; } else { + int value_type = agtv_value->type; + + pfree_agtype_value(agtv_value); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("%s unsupported argument agtype %d", - msghdr, agtv_value->type))); + msghdr, value_type))); } } /* it is an unknown type */ @@ -7639,9 +8655,10 @@ static agtype_value *tostring_helper(Datum arg, Oid type, char *msghdr) } /* build the result */ + agtv_result = palloc(sizeof(agtype_value)); agtv_result->type = AGTV_STRING; agtv_result->val.string.val = string; - agtv_result->val.string.len = strlen(string); + agtv_result->val.string.len = string_len >= 0 ? string_len : strlen(string); return agtv_result; } @@ -7655,7 +8672,6 @@ Datum age_tostringlist(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; agtype_in_state agis_result; - agtype_value *elem; agtype_value string_elem; int count; int i; @@ -7693,18 +8709,22 @@ Datum age_tostringlist(PG_FUNCTION_ARGS) for (i = 0; i < count; i++) { /* TODO: check element's type, it's value, and convert it to string if possible. */ + agtype_value elem; + bool elem_needs_free = false; enum agtype_value_type elem_type; - elem = get_ith_agtype_value_from_container(&agt_arg->root, i); + (void)get_ith_agtype_value_from_container_no_copy(&agt_arg->root, i, + &elem, + &elem_needs_free); string_elem.type = AGTV_STRING; - elem_type = elem ? elem->type : AGTV_NULL; + elem_type = elem.type; switch (elem_type) { case AGTV_STRING: - string_elem.val.string.val = elem->val.string.val; - string_elem.val.string.len = elem->val.string.len; + string_elem.val.string.val = elem.val.string.val; + string_elem.val.string.len = elem.val.string.len; agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &string_elem); @@ -7712,26 +8732,32 @@ Datum age_tostringlist(PG_FUNCTION_ARGS) break; case AGTV_FLOAT: + { + int len; - sprintf(buffer, "%.*g", DBL_DIG, elem->val.float_value); - string_elem.val.string.val = pstrdup(buffer); - string_elem.val.string.len = strlen(buffer); + string_elem.val.string.val = float8out_internal(elem.val.float_value); + len = strlen(string_elem.val.string.val); + string_elem.val.string.len = len; agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &string_elem); break; + } case AGTV_INTEGER: + { + int len; - sprintf(buffer, "%ld", elem->val.int_value); - string_elem.val.string.val = pstrdup(buffer); - string_elem.val.string.len = strlen(buffer); + len = pg_lltoa(elem.val.int_value, buffer); + string_elem.val.string.val = pnstrdup(buffer, len); + string_elem.val.string.len = len; agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &string_elem); break; + } default: @@ -7741,6 +8767,8 @@ Datum age_tostringlist(PG_FUNCTION_ARGS) break; } + + free_agtype_value_no_copy(&elem, elem_needs_free); } agis_result.res = push_agtype_value(&agis_result.parse_state, @@ -7799,37 +8827,16 @@ PG_FUNCTION_INFO_V1(age_reverse); Datum age_reverse(PG_FUNCTION_ARGS) { - int nargs; - Datum *args; Datum arg; - bool *nulls; - Oid *types; - agtype_value agtv_result; text *text_string = NULL; - char *string = NULL; - int string_len; Oid type; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); - - /* check number of args */ - if (nargs > 1) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("reverse() only supports one argument"))); - } - - /* check for null */ - if (nargs < 0 || nulls[0]) + if (!get_single_variadic_arg(fcinfo, "reverse()", true, &arg, &type)) { PG_RETURN_NULL(); } /* reverse() supports text, cstring, or the agtype string input */ - arg = args[0]; - type = types[0]; - if (type != AGTYPEOID) { if (type == CSTRINGOID) @@ -7850,8 +8857,9 @@ Datum age_reverse(PG_FUNCTION_ARGS) else { agtype *agt_arg = NULL; - agtype_value *agtv_value = NULL; - agtype_in_state result; + agtype_value agtv_value; + agtype_value *agtv_result = NULL; + bool value_needs_free = false; agtype_parse_state *parse_state = NULL; agtype_value elem = {0}; agtype_iterator *it = NULL; @@ -7865,32 +8873,36 @@ Datum age_reverse(PG_FUNCTION_ARGS) if (AGT_ROOT_IS_SCALAR(agt_arg)) { - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_value, + &value_needs_free); /* check for agtype null */ - if (agtv_value->type == AGTV_NULL) + if (agtv_value.type == AGTV_NULL) { + free_agtype_value_no_copy(&agtv_value, value_needs_free); PG_RETURN_NULL(); } - if (agtv_value->type == AGTV_STRING) + if (agtv_value.type == AGTV_STRING) { - text_string = cstring_to_text_with_len(agtv_value->val.string.val, - agtv_value->val.string.len); + text_string = cstring_to_text_with_len(agtv_value.val.string.val, + agtv_value.val.string.len); + free_agtype_value_no_copy(&agtv_value, value_needs_free); } else { + free_agtype_value_no_copy(&agtv_value, value_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("reverse() unsupported argument agtype %d", - agtv_value->type))); + agtv_value.type))); } } else if (AGT_ROOT_IS_ARRAY(agt_arg)) { - agtv_value = push_agtype_value(&parse_state, WAGT_BEGIN_ARRAY, NULL); + agtv_result = push_agtype_value(&parse_state, WAGT_BEGIN_ARRAY, NULL); while ((it = get_next_list_element(it, &agt_arg->root, &elem))) { - agtv_value = push_agtype_value(&parse_state, WAGT_ELEM, &elem); + agtv_result = push_agtype_value(&parse_state, WAGT_ELEM, &elem); } /* now reverse the list */ @@ -7907,34 +8919,24 @@ Datum age_reverse(PG_FUNCTION_ARGS) elems = NULL; - agtv_value = push_agtype_value(&parse_state, WAGT_END_ARRAY, NULL); + agtv_result = push_agtype_value(&parse_state, WAGT_END_ARRAY, NULL); - Assert(agtv_value != NULL); - Assert(agtv_value->type = AGTV_ARRAY); + Assert(agtv_result != NULL); + Assert(agtv_result->type == AGTV_ARRAY); - PG_RETURN_POINTER(agtype_value_to_agtype(agtv_value)); + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result)); } else if (AGT_ROOT_IS_VPC(agt_arg)) { - elems = agtv_materialize_vle_edges(agt_arg); - num_elems = elems->val.array.num_elems; - - /* build our result array */ - memset(&result, 0, sizeof(agtype_in_state)); - - result.res = push_agtype_value(&result.parse_state, - WAGT_BEGIN_ARRAY, NULL); - - for (i = num_elems-1; i >= 0; i--) + if (agtv_vle_edge_count(agt_arg) == 0) { - result.res = push_agtype_value(&result.parse_state, WAGT_ELEM, - &elems->val.array.elems[i]); + PG_RETURN_POINTER(build_empty_agtype_array()); } - result.res = push_agtype_value(&result.parse_state, WAGT_END_ARRAY, NULL); + agtv_result = agtv_materialize_vle_edges_reversed(agt_arg); - PG_RETURN_POINTER(agtype_value_to_agtype(result.res)); + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result)); } else { @@ -7950,65 +8952,52 @@ Datum age_reverse(PG_FUNCTION_ARGS) text_string = DatumGetTextPP(DirectFunctionCall1(text_reverse, PointerGetDatum(text_string))); - /* convert it back to a cstring */ - string = text_to_cstring(text_string); - string_len = strlen(string); - - /* build the result */ - agtv_result.type = AGTV_STRING; - agtv_result.val.string.val = string; - agtv_result.val.string.len = string_len; - - PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); + PG_RETURN_POINTER(text_to_agtype_string_value(text_string)); } PG_FUNCTION_INFO_V1(age_toupper); Datum age_toupper(PG_FUNCTION_ARGS) { - int nargs; - Datum *args; Datum arg; - bool *nulls; - Oid *types; agtype_value agtv_result; char *string = NULL; char *result = NULL; int string_len; Oid type; int i; + agtype_value borrowed_value; + bool borrowed_needs_free = false; + bool borrowed_value_valid = false; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); - - /* check number of args */ - if (nargs > 1) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("toUpper() only supports one argument"))); - - /* check for null */ - if (nargs < 0 || nulls[0]) + if (!get_single_variadic_arg(fcinfo, "toUpper()", true, &arg, &type)) + { PG_RETURN_NULL(); + } /* toUpper() supports text, cstring, or the agtype string input */ - arg = args[0]; - type = types[0]; if (type != AGTYPEOID) { if (type == CSTRINGOID) + { string = DatumGetCString(arg); + string_len = strlen(string); + } else if (type == TEXTOID) - string = text_to_cstring(DatumGetTextPP(arg)); + { + text *txt = DatumGetTextPP(arg); + + string = VARDATA_ANY(txt); + string_len = VARSIZE_ANY_EXHDR(txt); + } else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("toUpper() unsupported argument type %d", type))); - string_len = strlen(string); } else { agtype *agt_arg; - agtype_value *agtv_value; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); @@ -8017,24 +9006,32 @@ Datum age_toupper(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("toUpper() only supports scalar arguments"))); - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &borrowed_value, + &borrowed_needs_free); + borrowed_value_valid = true; /* check for agtype null */ - if (agtv_value->type == AGTV_NULL) + if (borrowed_value.type == AGTV_NULL) + { + free_agtype_value_no_copy(&borrowed_value, borrowed_needs_free); PG_RETURN_NULL(); - if (agtv_value->type == AGTV_STRING) + } + if (borrowed_value.type == AGTV_STRING) { - string = agtv_value->val.string.val; - string_len = agtv_value->val.string.len; + string = borrowed_value.val.string.val; + string_len = borrowed_value.val.string.len; } else + { + free_agtype_value_no_copy(&borrowed_value, borrowed_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("toUpper() unsupported argument agtype %d", - agtv_value->type))); + borrowed_value.type))); + } } /* allocate the new string */ - result = palloc0(string_len); + result = palloc(string_len); /* upcase the string */ for (i = 0; i < string_len; i++) @@ -8045,6 +9042,9 @@ Datum age_toupper(PG_FUNCTION_ARGS) agtv_result.val.string.val = result; agtv_result.val.string.len = string_len; + if (borrowed_value_valid) + free_agtype_value_no_copy(&borrowed_value, borrowed_needs_free); + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); } @@ -8052,49 +9052,45 @@ PG_FUNCTION_INFO_V1(age_tolower); Datum age_tolower(PG_FUNCTION_ARGS) { - int nargs; - Datum *args; Datum arg; - bool *nulls; - Oid *types; agtype_value agtv_result; char *string = NULL; char *result = NULL; int string_len; Oid type; int i; + agtype_value borrowed_value; + bool borrowed_needs_free = false; + bool borrowed_value_valid = false; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); - - /* check number of args */ - if (nargs > 1) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("toLower() only supports one argument"))); - - /* check for null */ - if (nargs < 0 || nulls[0]) + if (!get_single_variadic_arg(fcinfo, "toLower()", true, &arg, &type)) + { PG_RETURN_NULL(); + } /* toLower() supports text, cstring, or the agtype string input */ - arg = args[0]; - type = types[0]; if (type != AGTYPEOID) { if (type == CSTRINGOID) + { string = DatumGetCString(arg); + string_len = strlen(string); + } else if (type == TEXTOID) - string = text_to_cstring(DatumGetTextPP(arg)); + { + text *txt = DatumGetTextPP(arg); + + string = VARDATA_ANY(txt); + string_len = VARSIZE_ANY_EXHDR(txt); + } else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("toLower() unsupported argument type %d", type))); - string_len = strlen(string); } else { agtype *agt_arg; - agtype_value *agtv_value; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); @@ -8103,24 +9099,32 @@ Datum age_tolower(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("toLower() only supports scalar arguments"))); - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &borrowed_value, + &borrowed_needs_free); + borrowed_value_valid = true; /* check for agtype null */ - if (agtv_value->type == AGTV_NULL) + if (borrowed_value.type == AGTV_NULL) + { + free_agtype_value_no_copy(&borrowed_value, borrowed_needs_free); PG_RETURN_NULL(); - if (agtv_value->type == AGTV_STRING) + } + if (borrowed_value.type == AGTV_STRING) { - string = agtv_value->val.string.val; - string_len = agtv_value->val.string.len; + string = borrowed_value.val.string.val; + string_len = borrowed_value.val.string.len; } else + { + free_agtype_value_no_copy(&borrowed_value, borrowed_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("toLower() unsupported argument agtype %d", - agtv_value->type))); + borrowed_value.type))); + } } /* allocate the new string */ - result = palloc0(string_len); + result = palloc(string_len); /* downcase the string */ for (i = 0; i < string_len; i++) @@ -8131,6 +9135,9 @@ Datum age_tolower(PG_FUNCTION_ARGS) agtv_result.val.string.val = result; agtv_result.val.string.len = string_len; + if (borrowed_value_valid) + free_agtype_value_no_copy(&borrowed_value, borrowed_needs_free); + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); } @@ -8138,33 +9145,16 @@ PG_FUNCTION_INFO_V1(age_rtrim); Datum age_rtrim(PG_FUNCTION_ARGS) { - int nargs; - Datum *args; Datum arg; - bool *nulls; - Oid *types; - agtype_value agtv_result; text *text_string = NULL; - char *string = NULL; - int string_len; Oid type; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); - - /* check number of args */ - if (nargs > 1) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("rTrim() only supports one argument"))); - - /* check for null */ - if (nargs < 0 || nulls[0]) + if (!get_single_variadic_arg(fcinfo, "rTrim()", true, &arg, &type)) + { PG_RETURN_NULL(); + } /* rTrim() supports text, cstring, or the agtype string input */ - arg = args[0]; - type = types[0]; - if (type != AGTYPEOID) { if (type == CSTRINGOID) @@ -8179,27 +9169,15 @@ Datum age_rtrim(PG_FUNCTION_ARGS) else { agtype *agt_arg; - agtype_value *agtv_value; + bool is_null; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); - - if (!AGT_ROOT_IS_SCALAR(agt_arg)) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("rTrim() only supports scalar arguments"))); - - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); - - /* check for agtype null */ - if (agtv_value->type == AGTV_NULL) + text_string = get_agtype_scalar_string_as_text_no_copy(agt_arg, + "rTrim()", + &is_null); + if (is_null) PG_RETURN_NULL(); - if (agtv_value->type == AGTV_STRING) - text_string = cstring_to_text_with_len(agtv_value->val.string.val, - agtv_value->val.string.len); - else - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("rTrim() unsupported argument agtype %d", - agtv_value->type))); } /* @@ -8209,49 +9187,23 @@ Datum age_rtrim(PG_FUNCTION_ARGS) text_string = DatumGetTextPP(DirectFunctionCall1(rtrim1, PointerGetDatum(text_string))); - /* convert it back to a cstring */ - string = text_to_cstring(text_string); - string_len = strlen(string); - - /* build the result */ - agtv_result.type = AGTV_STRING; - agtv_result.val.string.val = string; - agtv_result.val.string.len = string_len; - - PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); + PG_RETURN_POINTER(text_to_agtype_string_value(text_string)); } PG_FUNCTION_INFO_V1(age_ltrim); Datum age_ltrim(PG_FUNCTION_ARGS) { - int nargs; - Datum *args; Datum arg; - bool *nulls; - Oid *types; - agtype_value agtv_result; text *text_string = NULL; - char *string = NULL; - int string_len; Oid type; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); - - /* check number of args */ - if (nargs > 1) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("lTrim() only supports one argument"))); - - /* check for null */ - if (nargs < 0 || nulls[0]) + if (!get_single_variadic_arg(fcinfo, "lTrim()", true, &arg, &type)) + { PG_RETURN_NULL(); + } /* rTrim() supports text, cstring, or the agtype string input */ - arg = args[0]; - type = types[0]; - if (type != AGTYPEOID) { if (type == CSTRINGOID) @@ -8266,27 +9218,15 @@ Datum age_ltrim(PG_FUNCTION_ARGS) else { agtype *agt_arg; - agtype_value *agtv_value; + bool is_null; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); - - if (!AGT_ROOT_IS_SCALAR(agt_arg)) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("lTrim() only supports scalar arguments"))); - - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); - - /* check for agtype null */ - if (agtv_value->type == AGTV_NULL) + text_string = get_agtype_scalar_string_as_text_no_copy(agt_arg, + "lTrim()", + &is_null); + if (is_null) PG_RETURN_NULL(); - if (agtv_value->type == AGTV_STRING) - text_string = cstring_to_text_with_len(agtv_value->val.string.val, - agtv_value->val.string.len); - else - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("lTrim() unsupported argument agtype %d", - agtv_value->type))); } /* @@ -8296,49 +9236,23 @@ Datum age_ltrim(PG_FUNCTION_ARGS) text_string = DatumGetTextPP(DirectFunctionCall1(ltrim1, PointerGetDatum(text_string))); - /* convert it back to a cstring */ - string = text_to_cstring(text_string); - string_len = strlen(string); - - /* build the result */ - agtv_result.type = AGTV_STRING; - agtv_result.val.string.val = string; - agtv_result.val.string.len = string_len; - - PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); + PG_RETURN_POINTER(text_to_agtype_string_value(text_string)); } PG_FUNCTION_INFO_V1(age_trim); Datum age_trim(PG_FUNCTION_ARGS) { - int nargs; - Datum *args; Datum arg; - bool *nulls; - Oid *types; - agtype_value agtv_result; text *text_string = NULL; - char *string = NULL; - int string_len; Oid type; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); - - /* check number of args */ - if (nargs > 1) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("trim() only supports one argument"))); - - /* check for null */ - if (nargs < 0 || nulls[0]) + if (!get_single_variadic_arg(fcinfo, "trim()", true, &arg, &type)) + { PG_RETURN_NULL(); + } /* trim() supports text, cstring, or the agtype string input */ - arg = args[0]; - type = types[0]; - if (type != AGTYPEOID) { if (type == CSTRINGOID) @@ -8353,27 +9267,15 @@ Datum age_trim(PG_FUNCTION_ARGS) else { agtype *agt_arg; - agtype_value *agtv_value; + bool is_null; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); - - if (!AGT_ROOT_IS_SCALAR(agt_arg)) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("trim() only supports scalar arguments"))); - - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); - - /* check for agtype null */ - if (agtv_value->type == AGTV_NULL) + text_string = get_agtype_scalar_string_as_text_no_copy(agt_arg, + "trim()", + &is_null); + if (is_null) PG_RETURN_NULL(); - if (agtv_value->type == AGTV_STRING) - text_string = cstring_to_text_with_len(agtv_value->val.string.val, - agtv_value->val.string.len); - else - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("trim() unsupported argument agtype %d", - agtv_value->type))); } /* @@ -8383,16 +9285,7 @@ Datum age_trim(PG_FUNCTION_ARGS) text_string = DatumGetTextPP(DirectFunctionCall1(btrim1, PointerGetDatum(text_string))); - /* convert it back to a cstring */ - string = text_to_cstring(text_string); - string_len = strlen(string); - - /* build the result */ - agtv_result.type = AGTV_STRING; - agtv_result.val.string.val = string; - agtv_result.val.string.len = string_len; - - PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); + PG_RETURN_POINTER(text_to_agtype_string_value(text_string)); } PG_FUNCTION_INFO_V1(age_right); @@ -8401,17 +9294,18 @@ Datum age_right(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[2]; Datum arg; bool *nulls; + bool fast_nulls[2]; Oid *types; - agtype_value agtv_result; + Oid fast_types[2]; text *text_string = NULL; - char *string = NULL; int64 string_len; Oid type; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 2, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 2) @@ -8454,35 +9348,17 @@ Datum age_right(PG_FUNCTION_ARGS) else { agtype *agt_arg; - agtype_value *agtv_value; + bool is_null; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); - - if (!AGT_ROOT_IS_SCALAR(agt_arg)) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("right() only supports scalar arguments"))); - } - - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); - - /* check for agtype null */ - if (agtv_value->type == AGTV_NULL) + text_string = get_agtype_scalar_string_as_text_no_copy(agt_arg, + "right()", + &is_null); + if (is_null) { PG_RETURN_NULL(); } - if (agtv_value->type == AGTV_STRING) - { - text_string = cstring_to_text_with_len(agtv_value->val.string.val, - agtv_value->val.string.len); - } - else - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("right() unsupported argument agtype %d", - agtv_value->type))); - } } /* right() only supports integer and agtype integer for the second parameter. */ @@ -8512,28 +9388,10 @@ Datum age_right(PG_FUNCTION_ARGS) else { agtype *agt_arg; - agtype_value *agtv_value; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); - - if (!AGT_ROOT_IS_SCALAR(agt_arg)) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("right() only supports scalar arguments"))); - } - - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); - - /* no need to check for agtype null because it is an error if found */ - if (agtv_value->type != AGTV_INTEGER) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("right() unsupported argument agtype %d", - agtv_value->type))); - } - - string_len = agtv_value->val.int_value; + string_len = get_agtype_scalar_integer_no_copy(agt_arg, "right()"); } /* out of range and negative values are not supported in the opencypher spec */ @@ -8554,16 +9412,7 @@ Datum age_right(PG_FUNCTION_ARGS) PointerGetDatum(text_string), Int64GetDatum(string_len))); - /* convert it back to a cstring */ - string = text_to_cstring(text_string); - string_len = strlen(string); - - /* build the result */ - agtv_result.type = AGTV_STRING; - agtv_result.val.string.val = string; - agtv_result.val.string.len = string_len; - - PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); + PG_RETURN_POINTER(text_to_agtype_string_value(text_string)); } PG_FUNCTION_INFO_V1(age_left); @@ -8572,17 +9421,18 @@ Datum age_left(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[2]; Datum arg; bool *nulls; + bool fast_nulls[2]; Oid *types; - agtype_value agtv_result; + Oid fast_types[2]; text *text_string = NULL; - char *string = NULL; int64 string_len; Oid type; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 2, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 2) @@ -8628,35 +9478,17 @@ Datum age_left(PG_FUNCTION_ARGS) else { agtype *agt_arg; - agtype_value *agtv_value; + bool is_null; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); - - if (!AGT_ROOT_IS_SCALAR(agt_arg)) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("left() only supports scalar arguments"))); - } - - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); - - /* check for agtype null */ - if (agtv_value->type == AGTV_NULL) + text_string = get_agtype_scalar_string_as_text_no_copy(agt_arg, + "left()", + &is_null); + if (is_null) { PG_RETURN_NULL(); } - if (agtv_value->type == AGTV_STRING) - { - text_string = cstring_to_text_with_len(agtv_value->val.string.val, - agtv_value->val.string.len); - } - else - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("left() unsupported argument agtype %d", - agtv_value->type))); - } } /* left() only supports integer and agtype integer for the second parameter. */ @@ -8686,28 +9518,10 @@ Datum age_left(PG_FUNCTION_ARGS) else { agtype *agt_arg; - agtype_value *agtv_value; /* get the agtype argument */ - agt_arg = DATUM_GET_AGTYPE_P(arg); - - if (!AGT_ROOT_IS_SCALAR(agt_arg)) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("left() only supports scalar arguments"))); - } - - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); - - /* no need to check for agtype null because it is an error if found */ - if (agtv_value->type != AGTV_INTEGER) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("left() unsupported argument agtype %d", - agtv_value->type))); - } - - string_len = agtv_value->val.int_value; + agt_arg = DATUM_GET_AGTYPE_P(arg); + string_len = get_agtype_scalar_integer_no_copy(agt_arg, "left()"); } /* out of range and negative values are not supported in the opencypher spec */ @@ -8732,16 +9546,7 @@ Datum age_left(PG_FUNCTION_ARGS) PointerGetDatum(text_string), Int64GetDatum(string_len))); - /* convert it back to a cstring */ - string = text_to_cstring(text_string); - string_len = strlen(string); - - /* build the result */ - agtv_result.type = AGTV_STRING; - agtv_result.val.string.val = string; - agtv_result.val.string.len = string_len; - - PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); + PG_RETURN_POINTER(text_to_agtype_string_value(text_string)); } PG_FUNCTION_INFO_V1(age_substring); @@ -8750,20 +9555,21 @@ Datum age_substring(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[3]; Datum arg; bool *nulls; + bool fast_nulls[3]; Oid *types; - agtype_value agtv_result; + Oid fast_types[3]; text *text_string = NULL; - char *string = NULL; int64 param; int string_start = 0; int string_len = 0; int i; Oid type; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 3, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs < 2 || nargs > 3) @@ -8816,35 +9622,17 @@ Datum age_substring(PG_FUNCTION_ARGS) else { agtype *agt_arg; - agtype_value *agtv_value; + bool is_null; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); - - if (!AGT_ROOT_IS_SCALAR(agt_arg)) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("substring() only supports scalar arguments"))); - } - - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); - - /* check for agtype null */ - if (agtv_value->type == AGTV_NULL) + text_string = get_agtype_scalar_string_as_text_no_copy(agt_arg, + "substring()", + &is_null); + if (is_null) { PG_RETURN_NULL(); } - if (agtv_value->type == AGTV_STRING) - { - text_string = cstring_to_text_with_len(agtv_value->val.string.val, - agtv_value->val.string.len); - } - else - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("substring() unsupported argument agtype %d", - agtv_value->type))); - } } /* @@ -8880,27 +9668,10 @@ Datum age_substring(PG_FUNCTION_ARGS) else { agtype *agt_arg; - agtype_value *agtv_value; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); - - if (!AGT_ROOT_IS_SCALAR(agt_arg)) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("substring() only supports scalar arguments"))); - } - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); - - /* no need to check for agtype null because it is an error if found */ - if (agtv_value->type != AGTV_INTEGER) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("substring() unsupported argument agtype %d", - agtv_value->type))); - } - - param = agtv_value->val.int_value; + param = get_agtype_scalar_integer_no_copy(agt_arg, "substring()"); } /* out of range values are not supported in the opencypher spec */ @@ -8950,16 +9721,7 @@ Datum age_substring(PG_FUNCTION_ARGS) Int64GetDatum(string_start), Int64GetDatum(string_len))); } - /* convert it back to a cstring */ - string = text_to_cstring(text_string); - string_len = strlen(string); - - /* build the result */ - agtv_result.type = AGTV_STRING; - agtv_result.val.string.val = string; - agtv_result.val.string.len = string_len; - - PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); + PG_RETURN_POINTER(text_to_agtype_string_value(text_string)); } PG_FUNCTION_INFO_V1(age_split); @@ -8968,9 +9730,12 @@ Datum age_split(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[2]; Datum arg; bool *nulls; + bool fast_nulls[2]; Oid *types; + Oid fast_types[2]; agtype_value *agtv_result; text *param = NULL; text *text_string = NULL; @@ -8979,8 +9744,8 @@ Datum age_split(PG_FUNCTION_ARGS) Oid type; int i; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 2, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 2) @@ -9015,27 +9780,15 @@ Datum age_split(PG_FUNCTION_ARGS) else { agtype *agt_arg; - agtype_value *agtv_value; + bool is_null; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); - - if (!AGT_ROOT_IS_SCALAR(agt_arg)) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("split() only supports scalar arguments"))); - - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); - - /* check for agtype null */ - if (agtv_value->type == AGTV_NULL) + param = get_agtype_scalar_string_as_text_no_copy(agt_arg, + "split()", + &is_null); + if (is_null) PG_RETURN_NULL(); - if (agtv_value->type == AGTV_STRING) - param = cstring_to_text_with_len(agtv_value->val.string.val, - agtv_value->val.string.len); - else - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("split() unsupported argument agtype %d", - agtv_value->type))); } if (i == 0) text_string = param; @@ -9056,47 +9809,48 @@ Datum age_split(PG_FUNCTION_ARGS) if (PointerIsValid(DatumGetPointer(text_array))) { ArrayType *array = DatumGetArrayTypeP(text_array); + ArrayIterator iterator; agtype_in_state result; - Datum *elements; - int nelements; + Datum element; + bool is_null; - /* zero the state and deconstruct the ArrayType to TEXTOID */ + /* zero the state and stream the ArrayType to avoid extra arrays */ memset(&result, 0, sizeof(agtype_in_state)); - deconstruct_array(array, TEXTOID, -1, false, 'i', &elements, NULL, - &nelements); + iterator = array_create_iterator(array, 0, NULL); /* open the agtype array */ result.res = push_agtype_value(&result.parse_state, WAGT_BEGIN_ARRAY, NULL); /* add the values */ - for (i = 0; i < nelements; i++) + while (array_iterate(iterator, &element, &is_null)) { + text *elem; char *string; int string_len; - char *string_copy; agtype_value agtv_string; - Datum d; - /* get the string element from the array */ - string = VARDATA(elements[i]); - string_len = VARSIZE(elements[i]) - VARHDRSZ; + if (is_null) + { + continue; + } - /* make a copy */ - string_copy = palloc0(string_len); - memcpy(string_copy, string, string_len); + elem = DatumGetTextPP(element); + + /* get the string element from the array */ + string = VARDATA_ANY(elem); + string_len = VARSIZE_ANY_EXHDR(elem); /* build the agtype string */ agtv_string.type = AGTV_STRING; - agtv_string.val.string.val = string_copy; + agtv_string.val.string.val = string; agtv_string.val.string.len = string_len; - /* get the datum */ - d = PointerGetDatum(agtype_value_to_agtype(&agtv_string)); - - /* add the value */ - add_agtype(d, false, &result, AGTYPEOID, false); + result.res = push_agtype_value(&result.parse_state, WAGT_ELEM, + &agtv_string); } + array_free_iterator(iterator); + /* close the array */ result.res = push_agtype_value(&result.parse_state, WAGT_END_ARRAY, NULL); @@ -9116,22 +9870,22 @@ Datum age_replace(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[3]; Datum arg; bool *nulls; + bool fast_nulls[3]; Oid *types; - agtype_value agtv_result; + Oid fast_types[3]; text *param = NULL; text *text_string = NULL; text *text_search = NULL; text *text_replace = NULL; text *text_result = NULL; - char *string = NULL; - int string_len; Oid type; int i; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 3, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 3) @@ -9166,27 +9920,15 @@ Datum age_replace(PG_FUNCTION_ARGS) else { agtype *agt_arg; - agtype_value *agtv_value; + bool is_null; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); - - if (!AGT_ROOT_IS_SCALAR(agt_arg)) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("replace() only supports scalar arguments"))); - - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); - - /* check for agtype null */ - if (agtv_value->type == AGTV_NULL) + param = get_agtype_scalar_string_as_text_no_copy(agt_arg, + "replace()", + &is_null); + if (is_null) PG_RETURN_NULL(); - if (agtv_value->type == AGTV_STRING) - param = cstring_to_text_with_len(agtv_value->val.string.val, - agtv_value->val.string.len); - else - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("replace() unsupported argument agtype %d", - agtv_value->type))); } if (i == 0) text_string = param; @@ -9204,16 +9946,7 @@ Datum age_replace(PG_FUNCTION_ARGS) replace_text, C_COLLATION_OID, PointerGetDatum(text_string), PointerGetDatum(text_search), PointerGetDatum(text_replace))); - /* convert it back to a cstring */ - string = text_to_cstring(text_result); - string_len = strlen(string); - - /* build the result */ - agtv_result.type = AGTV_STRING; - agtv_result.val.string.val = string; - agtv_result.val.string.len = string_len; - - PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); + PG_RETURN_POINTER(text_to_agtype_string_value(text_result)); } /* @@ -9239,22 +9972,7 @@ static float8 get_float_compatible_arg(Datum arg, Oid type, char *funcname, else if (type == INT4OID) result = (float8) DatumGetInt32(arg); else if (type == INT8OID) - { - /* - * Get the string representation of the integer because it could be - * too large to fit in a float. Let the float routine determine - * what to do with it. - */ - char *string = DatumGetCString(DirectFunctionCall1(int8out, arg)); - bool is_valid = false; - /* turn it into a float */ - result = float8in_internal_null(string, NULL, "double precision", - string, &is_valid); - - /* return 0 if it was an invalid float */ - if (!is_valid) - return 0; - } + result = (float8) DatumGetInt64(arg); else if (type == FLOAT4OID) result = (float8) DatumGetFloat4(arg); else if (type == FLOAT8OID) @@ -9270,7 +9988,8 @@ static float8 get_float_compatible_arg(Datum arg, Oid type, char *funcname, else { agtype *agt_arg; - agtype_value *agtv_value; + agtype_value agtv_value; + bool value_needs_free = false; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); @@ -9280,40 +9999,35 @@ static float8 get_float_compatible_arg(Datum arg, Oid type, char *funcname, errmsg("%s() only supports scalar arguments", funcname))); - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_value, + &value_needs_free); /* check for agtype null */ - if (agtv_value->type == AGTV_NULL) + if (agtv_value.type == AGTV_NULL) + { + free_agtype_value_no_copy(&agtv_value, value_needs_free); return 0; + } - if (agtv_value->type == AGTV_INTEGER) + if (agtv_value.type == AGTV_INTEGER) { - /* - * Get the string representation of the integer because it could be - * too large to fit in a float. Let the float routine determine - * what to do with it. - */ - bool is_valid = false; - char *string = DatumGetCString(DirectFunctionCall1(int8out, - Int64GetDatum(agtv_value->val.int_value))); - /* turn it into a float */ - result = float8in_internal_null(string, NULL, "double precision", - string, &is_valid); - - /* return null if it was not a valid float */ - if (!is_valid) - return 0; + result = (float8) agtv_value.val.int_value; } - else if (agtv_value->type == AGTV_FLOAT) - result = agtv_value->val.float_value; - else if (agtv_value->type == AGTV_NUMERIC) + else if (agtv_value.type == AGTV_FLOAT) + result = agtv_value.val.float_value; + else if (agtv_value.type == AGTV_NUMERIC) result = DatumGetFloat8(DirectFunctionCall1( numeric_float8_no_overflow, - NumericGetDatum(agtv_value->val.numeric))); + NumericGetDatum(agtv_value.val.numeric))); else + { + free_agtype_value_no_copy(&agtv_value, value_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("%s() unsupported argument agtype %d", - funcname, agtv_value->type))); + funcname, agtv_value.type))); + } + + free_agtype_value_no_copy(&agtv_value, value_needs_free); } /* there is a valid non null value */ @@ -9364,7 +10078,8 @@ static Numeric get_numeric_compatible_arg(Datum arg, Oid type, char *funcname, else { agtype *agt_arg; - agtype_value *agtv_value; + agtype_value agtv_value; + bool value_needs_free = false; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); @@ -9374,36 +10089,45 @@ static Numeric get_numeric_compatible_arg(Datum arg, Oid type, char *funcname, errmsg("%s() only supports scalar arguments", funcname))); - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_value, + &value_needs_free); /* check for agtype null */ - if (agtv_value->type == AGTV_NULL) + if (agtv_value.type == AGTV_NULL) + { + free_agtype_value_no_copy(&agtv_value, value_needs_free); return 0; + } - if (agtv_value->type == AGTV_INTEGER) + if (agtv_value.type == AGTV_INTEGER) { result = DatumGetNumeric(DirectFunctionCall1( - int8_numeric, Int64GetDatum(agtv_value->val.int_value))); + int8_numeric, Int64GetDatum(agtv_value.val.int_value))); if (ag_type != NULL) *ag_type = AGTV_INTEGER; } - else if (agtv_value->type == AGTV_FLOAT) + else if (agtv_value.type == AGTV_FLOAT) { result = DatumGetNumeric(DirectFunctionCall1( - float8_numeric, Float8GetDatum(agtv_value->val.float_value))); + float8_numeric, Float8GetDatum(agtv_value.val.float_value))); if (ag_type != NULL) *ag_type = AGTV_FLOAT; } - else if (agtv_value->type == AGTV_NUMERIC) + else if (agtv_value.type == AGTV_NUMERIC) { - result = agtv_value->val.numeric; + result = agtv_value.val.numeric; if (ag_type != NULL) *ag_type = AGTV_NUMERIC; } else + { + free_agtype_value_no_copy(&agtv_value, value_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("%s() unsupported argument agtype %d", - funcname, agtv_value->type))); + funcname, agtv_value.type))); + } + + free_agtype_value_no_copy(&agtv_value, value_needs_free); } /* there is a valid non null value */ @@ -9418,15 +10142,18 @@ Datum age_sin(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; float8 angle; float8 result; bool is_null = true; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -9465,15 +10192,18 @@ Datum age_cos(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; float8 angle; float8 result; bool is_null = true; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -9512,15 +10242,18 @@ Datum age_tan(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; float8 angle; float8 result; bool is_null = true; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -9559,15 +10292,18 @@ Datum age_cot(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; float8 angle; float8 result; bool is_null = true; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -9606,15 +10342,18 @@ Datum age_asin(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; float8 x; float8 angle; bool is_null = true; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -9657,15 +10396,18 @@ Datum age_acos(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; float8 x; float8 angle; bool is_null = true; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -9708,15 +10450,18 @@ Datum age_atan(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; float8 x; float8 angle; bool is_null = true; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -9755,15 +10500,18 @@ Datum age_atan2(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[2]; bool *nulls; + bool fast_nulls[2]; Oid *types; + Oid fast_types[2]; agtype_value agtv_result; float8 x, y; float8 angle; bool is_null = true; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 2, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 2) @@ -9809,15 +10557,18 @@ Datum age_degrees(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; float8 angle_degrees; float8 angle_radians; bool is_null = true; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -9856,15 +10607,18 @@ Datum age_radians(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; float8 angle_degrees; float8 angle_radians; bool is_null = true; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -9903,8 +10657,11 @@ PG_FUNCTION_INFO_V1(age_round); Datum age_round(PG_FUNCTION_ARGS) { Datum *args = NULL; + Datum fast_args[2]; bool *nulls = NULL; + bool fast_nulls[2]; Oid *types = NULL; + Oid fast_types[2]; int nargs = 0; agtype_value agtv_result; Numeric arg, arg_precision; @@ -9913,8 +10670,8 @@ Datum age_round(PG_FUNCTION_ARGS) bool is_null = true; int precision = 0; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 2, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1 && nargs != 2) @@ -9985,16 +10742,19 @@ Datum age_ceil(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; Numeric arg; Numeric numeric_result; float8 float_result; bool is_null = true; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -10034,16 +10794,19 @@ Datum age_floor(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; Numeric arg; Numeric numeric_result; float8 float_result; bool is_null = true; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -10084,16 +10847,19 @@ Datum age_abs(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; Numeric arg; Numeric numeric_result; bool is_null = true; enum agtype_value_type type = AGTV_NULL; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -10157,16 +10923,19 @@ Datum age_sign(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; Numeric arg; Numeric numeric_result; int int_result; bool is_null = true; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -10207,8 +10976,11 @@ Datum age_log(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; Numeric arg; Numeric zero; @@ -10217,8 +10989,8 @@ Datum age_log(PG_FUNCTION_ARGS) bool is_null = true; int test; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -10268,8 +11040,11 @@ Datum age_log10(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; Numeric arg; Numeric zero; @@ -10279,8 +11054,8 @@ Datum age_log10(PG_FUNCTION_ARGS) bool is_null = true; int test; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -10383,16 +11158,19 @@ Datum age_exp(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; Numeric arg; Numeric numeric_result; float8 float_result; bool is_null = true; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -10432,8 +11210,11 @@ Datum age_sqrt(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; Numeric arg; Numeric zero; @@ -10442,8 +11223,8 @@ Datum age_sqrt(PG_FUNCTION_ARGS) bool is_null = true; int test; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -10541,10 +11322,11 @@ agtype_value *alter_property_value(agtype_value *properties, char *var_name, agtype_iterator *it; agtype_iterator_token tok = WAGT_DONE; agtype_parse_state *parse_state = NULL; - agtype_value *r; + agtype_value r; agtype *prop_agtype; agtype_value *parsed_agtype_value = NULL; bool found; + int var_name_len; /* if no properties, return NULL */ if (properties == NULL) @@ -10559,13 +11341,14 @@ agtype_value *alter_property_value(agtype_value *properties, char *var_name, errmsg("can only update objects"))); } - r = palloc0(sizeof(agtype_value)); + var_name_len = strlen(var_name); prop_agtype = agtype_value_to_agtype(properties); it = agtype_iterator_init(&prop_agtype->root); - tok = agtype_iterator_next(&it, r, true); + tok = agtype_iterator_next(&it, &r, true); - parsed_agtype_value = push_agtype_value(&parse_state, tok, tok < WAGT_BEGIN_ARRAY ? r : NULL); + parsed_agtype_value = push_agtype_value(&parse_state, tok, + tok < WAGT_BEGIN_ARRAY ? &r : NULL); /* * If the new value is NULL, this is equivalent to the remove_property @@ -10579,16 +11362,17 @@ agtype_value *alter_property_value(agtype_value *properties, char *var_name, found = false; while (true) { - char *str; + bool key_matches; - tok = agtype_iterator_next(&it, r, true); + tok = agtype_iterator_next(&it, &r, true); if (tok == WAGT_DONE || tok == WAGT_END_OBJECT) { break; } - str = pnstrdup(r->val.string.val, r->val.string.len); + key_matches = (r.val.string.len == var_name_len && + memcmp(r.val.string.val, var_name, var_name_len) == 0); /* * Check the key value, if it is equal to the passed in @@ -10596,15 +11380,15 @@ agtype_value *alter_property_value(agtype_value *properties, char *var_name, * in agtype. Otherwise pass the existing value to the * new properties agtype_value. */ - if (strcmp(str, var_name)) + if (!key_matches) { /* push the key */ parsed_agtype_value = push_agtype_value( - &parse_state, tok, tok < WAGT_BEGIN_ARRAY ? r : NULL); + &parse_state, tok, tok < WAGT_BEGIN_ARRAY ? &r : NULL); /* get the value and push the value */ - tok = agtype_iterator_next(&it, r, true); - parsed_agtype_value = push_agtype_value(&parse_state, tok, r); + tok = agtype_iterator_next(&it, &r, true); + parsed_agtype_value = push_agtype_value(&parse_state, tok, &r); } else { @@ -10614,16 +11398,16 @@ agtype_value *alter_property_value(agtype_value *properties, char *var_name, if(remove_property) { /* skip the value */ - tok = agtype_iterator_next(&it, r, true); + tok = agtype_iterator_next(&it, &r, true); continue; } /* push the key */ parsed_agtype_value = push_agtype_value( - &parse_state, tok, tok < WAGT_BEGIN_ARRAY ? r : NULL); + &parse_state, tok, tok < WAGT_BEGIN_ARRAY ? &r : NULL); /* skip the existing value for the key */ - tok = agtype_iterator_next(&it, r, true); + tok = agtype_iterator_next(&it, &r, true); /* * If the new agtype is scalar, push the agtype_value to the @@ -10704,8 +11488,8 @@ agtype_value *alter_properties(agtype_value *original_properties, agtype_iterator *it; agtype_iterator_token tok = WAGT_DONE; agtype_parse_state *parse_state = NULL; - agtype_value *key; - agtype_value *value; + agtype_value key; + agtype_value value; agtype_value *parsed_agtype_value = NULL; parsed_agtype_value = push_agtype_value(&parse_state, WAGT_BEGIN_OBJECT, @@ -10725,10 +11509,8 @@ agtype_value *alter_properties(agtype_value *original_properties, } /* Append new properties. */ - key = palloc0(sizeof(agtype_value)); - value = palloc0(sizeof(agtype_value)); it = agtype_iterator_init(&new_properties->root); - tok = agtype_iterator_next(&it, key, true); + tok = agtype_iterator_next(&it, &key, true); if (tok != WAGT_BEGIN_OBJECT) { @@ -10738,19 +11520,19 @@ agtype_value *alter_properties(agtype_value *original_properties, while (true) { - tok = agtype_iterator_next(&it, key, true); + tok = agtype_iterator_next(&it, &key, true); if (tok == WAGT_DONE || tok == WAGT_END_OBJECT) { break; } - agtype_iterator_next(&it, value, true); + agtype_iterator_next(&it, &value, true); parsed_agtype_value = push_agtype_value(&parse_state, WAGT_KEY, - key); + &key); parsed_agtype_value = push_agtype_value(&parse_state, WAGT_VALUE, - value); + &value); } parsed_agtype_value = push_agtype_value(&parse_state, WAGT_END_OBJECT, @@ -10786,6 +11568,7 @@ agtype *get_one_agtype_from_variadic_args(FunctionCallInfo fcinfo, { int total_args = PG_NARGS(); int actual_nargs = total_args - variadic_offset; + Oid argtype; /* Verify expected number of arguments */ if (actual_nargs != expected_nargs) @@ -10802,7 +11585,9 @@ agtype *get_one_agtype_from_variadic_args(FunctionCallInfo fcinfo, } /* Check if the argument is already an agtype */ - if (get_fn_expr_argtype(fcinfo->flinfo, variadic_offset) == AGTYPEOID) + argtype = get_cached_agtype_variadic_argtype(fcinfo, variadic_offset, + total_args); + if (argtype == AGTYPEOID) { agtype_container *agtc; @@ -10822,10 +11607,36 @@ agtype *get_one_agtype_from_variadic_args(FunctionCallInfo fcinfo, return agtype_result; } + if (!OidIsValid(argtype)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not determine data type for argument %d", + variadic_offset + 1))); + } + /* - * Not an agtype, need to convert. Fall through to use - * extract_variadic_args for type conversion handling. + * Not an agtype, but still a fixed non-variadic call. Convert this + * single datum directly instead of allocating extract_variadic_args() + * arrays just to read one value. */ + { + agtype_in_state state; + agt_type_category tcategory; + Oid outfuncoid; + + state.parse_state = NULL; + state.res = NULL; + + agtype_categorize_type(argtype, &tcategory, &outfuncoid); + datum_to_agtype(PG_GETARG_DATUM(variadic_offset), false, &state, + tcategory, outfuncoid, false); + agtype_result = agtype_value_to_agtype(state.res); + + pfree_agtype_in_state(&state); + + return agtype_result; + } } /* Standard path using extract_variadic_args */ @@ -10913,9 +11724,12 @@ Datum age_agtype_sum(PG_FUNCTION_ARGS) { agtype *agt_arg0 = AG_GET_ARG_AGTYPE_P(0); agtype *agt_arg1 = AG_GET_ARG_AGTYPE_P(1); - agtype_value *agtv_lhs; - agtype_value *agtv_rhs; + agtype_value agtv_lhs; + agtype_value agtv_rhs; agtype_value agtv_result; + bool lhs_needs_free = false; + bool rhs_needs_free = false; + agtype *result; /* get our args */ agt_arg0 = AG_GET_ARG_AGTYPE_P(0); @@ -10928,29 +11742,41 @@ Datum age_agtype_sum(PG_FUNCTION_ARGS) errmsg("arguments must resolve to a scalar"))); /* get the values */ - agtv_lhs = get_ith_agtype_value_from_container(&agt_arg0->root, 0); - agtv_rhs = get_ith_agtype_value_from_container(&agt_arg1->root, 0); + (void)get_ith_agtype_value_from_container_no_copy(&agt_arg0->root, 0, + &agtv_lhs, + &lhs_needs_free); + (void)get_ith_agtype_value_from_container_no_copy(&agt_arg1->root, 0, + &agtv_rhs, + &rhs_needs_free); /* only numbers are allowed */ - if ((agtv_lhs->type != AGTV_INTEGER && agtv_lhs->type != AGTV_FLOAT && - agtv_lhs->type != AGTV_NUMERIC) || (agtv_rhs->type != AGTV_INTEGER && - agtv_rhs->type != AGTV_FLOAT && agtv_rhs->type != AGTV_NUMERIC)) + if ((agtv_lhs.type != AGTV_INTEGER && agtv_lhs.type != AGTV_FLOAT && + agtv_lhs.type != AGTV_NUMERIC) || (agtv_rhs.type != AGTV_INTEGER && + agtv_rhs.type != AGTV_FLOAT && agtv_rhs.type != AGTV_NUMERIC)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("arguments must resolve to a number"))); /* check for agtype null */ - if (agtv_lhs->type == AGTV_NULL) + if (agtv_lhs.type == AGTV_NULL) + { + free_agtype_value_no_copy(&agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(&agtv_rhs, rhs_needs_free); PG_RETURN_POINTER(agt_arg1); - if (agtv_rhs->type == AGTV_NULL) + } + if (agtv_rhs.type == AGTV_NULL) + { + free_agtype_value_no_copy(&agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(&agtv_rhs, rhs_needs_free); PG_RETURN_POINTER(agt_arg0); + } /* we want to maintain the precision of the most precise input */ - if (agtv_lhs->type == AGTV_NUMERIC || agtv_rhs->type == AGTV_NUMERIC) + if (agtv_lhs.type == AGTV_NUMERIC || agtv_rhs.type == AGTV_NUMERIC) { agtv_result.type = AGTV_NUMERIC; } - else if (agtv_lhs->type == AGTV_FLOAT || agtv_rhs->type == AGTV_FLOAT) + else if (agtv_lhs.type == AGTV_FLOAT || agtv_rhs.type == AGTV_FLOAT) { agtv_result.type = AGTV_FLOAT; } @@ -10966,8 +11792,8 @@ Datum age_agtype_sum(PG_FUNCTION_ARGS) case AGTV_INTEGER: agtv_result.val.int_value = DatumGetInt64( DirectFunctionCall2(int8pl, - Int64GetDatum(agtv_lhs->val.int_value), - Int64GetDatum(agtv_rhs->val.int_value))); + Int64GetDatum(agtv_lhs.val.int_value), + Int64GetDatum(agtv_rhs.val.int_value))); break; /* for float it can be either, float + float or float + int */ case AGTV_FLOAT: @@ -10977,10 +11803,10 @@ Datum age_agtype_sum(PG_FUNCTION_ARGS) Datum dresult; /* extract and convert the values as necessary */ /* float + float */ - if (agtv_lhs->type == AGTV_FLOAT && agtv_rhs->type == AGTV_FLOAT) + if (agtv_lhs.type == AGTV_FLOAT && agtv_rhs.type == AGTV_FLOAT) { - dfl = Float8GetDatum(agtv_lhs->val.float_value); - dfr = Float8GetDatum(agtv_rhs->val.float_value); + dfl = Float8GetDatum(agtv_lhs.val.float_value); + dfr = Float8GetDatum(agtv_rhs.val.float_value); } /* float + int */ else @@ -10989,10 +11815,10 @@ Datum age_agtype_sum(PG_FUNCTION_ARGS) float8 fval; bool is_null; - ival = (agtv_lhs->type == AGTV_INTEGER) ? - agtv_lhs->val.int_value : agtv_rhs->val.int_value; - fval = (agtv_lhs->type == AGTV_FLOAT) ? - agtv_lhs->val.float_value : agtv_rhs->val.float_value; + ival = (agtv_lhs.type == AGTV_INTEGER) ? + agtv_lhs.val.int_value : agtv_rhs.val.int_value; + fval = (agtv_lhs.type == AGTV_FLOAT) ? + agtv_lhs.val.float_value : agtv_rhs.val.float_value; dfl = Float8GetDatum(get_float_compatible_arg(Int64GetDatum(ival), INT8OID, "", @@ -11015,21 +11841,21 @@ Datum age_agtype_sum(PG_FUNCTION_ARGS) Datum dresult; /* extract and convert the values as necessary */ /* numeric + numeric */ - if (agtv_lhs->type == AGTV_NUMERIC && agtv_rhs->type == AGTV_NUMERIC) + if (agtv_lhs.type == AGTV_NUMERIC && agtv_rhs.type == AGTV_NUMERIC) { - dnl = NumericGetDatum(agtv_lhs->val.numeric); - dnr = NumericGetDatum(agtv_rhs->val.numeric); + dnl = NumericGetDatum(agtv_lhs.val.numeric); + dnr = NumericGetDatum(agtv_rhs.val.numeric); } /* numeric + float */ - else if (agtv_lhs->type == AGTV_FLOAT || agtv_rhs->type == AGTV_FLOAT) + else if (agtv_lhs.type == AGTV_FLOAT || agtv_rhs.type == AGTV_FLOAT) { float8 fval; Numeric nval; - fval = (agtv_lhs->type == AGTV_FLOAT) ? - agtv_lhs->val.float_value : agtv_rhs->val.float_value; - nval = (agtv_lhs->type == AGTV_NUMERIC) ? - agtv_lhs->val.numeric : agtv_rhs->val.numeric; + fval = (agtv_lhs.type == AGTV_FLOAT) ? + agtv_lhs.val.float_value : agtv_rhs.val.float_value; + nval = (agtv_lhs.type == AGTV_NUMERIC) ? + agtv_lhs.val.numeric : agtv_rhs.val.numeric; dnl = DirectFunctionCall1(float8_numeric, Float8GetDatum(fval)); dnr = NumericGetDatum(nval); @@ -11040,10 +11866,10 @@ Datum age_agtype_sum(PG_FUNCTION_ARGS) int64 ival; Numeric nval; - ival = (agtv_lhs->type == AGTV_INTEGER) ? - agtv_lhs->val.int_value : agtv_rhs->val.int_value; - nval = (agtv_lhs->type == AGTV_NUMERIC) ? - agtv_lhs->val.numeric : agtv_rhs->val.numeric; + ival = (agtv_lhs.type == AGTV_INTEGER) ? + agtv_lhs.val.int_value : agtv_rhs.val.int_value; + nval = (agtv_lhs.type == AGTV_NUMERIC) ? + agtv_lhs.val.numeric : agtv_rhs.val.numeric; dnl = DirectFunctionCall1(int8_numeric, Int64GetDatum(ival)); dnr = NumericGetDatum(nval); @@ -11059,7 +11885,10 @@ Datum age_agtype_sum(PG_FUNCTION_ARGS) break; } /* return the result */ - PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); + result = agtype_value_to_agtype(&agtv_result); + free_agtype_value_no_copy(&agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(&agtv_rhs, rhs_needs_free); + PG_RETURN_POINTER(result); } /* @@ -11252,7 +12081,7 @@ Datum age_percentile_aggtransfn(PG_FUNCTION_ARGS) /* switch to the correct aggregate context */ old_mcxt = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt); /* create and initialize the state */ - pgastate = palloc0(sizeof(PercentileGroupAggState)); + pgastate = palloc(sizeof(PercentileGroupAggState)); pgastate->percentile = percentile; /* * Percentiles need to be calculated from a sorted set. We are only @@ -11433,9 +12262,12 @@ Datum age_collect_aggtransfn(PG_FUNCTION_ARGS) { agtype_in_state *castate; int nargs; - Datum *args; - bool *nulls; - Oid *types; + Datum arg; + Datum *args = NULL; + bool is_null = true; + bool *nulls = NULL; + Oid type = InvalidOid; + Oid *types = NULL; MemoryContext old_mcxt; /* verify we are in an aggregate context */ @@ -11451,8 +12283,9 @@ Datum age_collect_aggtransfn(PG_FUNCTION_ARGS) if (PG_ARGISNULL(0)) { /* create and initialize the state */ - castate = palloc0(sizeof(agtype_in_state)); - memset(castate, 0, sizeof(agtype_in_state)); + castate = palloc(sizeof(agtype_in_state)); + castate->parse_state = NULL; + castate->res = NULL; /* start the array */ castate->res = push_agtype_value(&castate->parse_state, @@ -11469,41 +12302,71 @@ Datum age_collect_aggtransfn(PG_FUNCTION_ARGS) * Insert the arg into the array, unless it is null. Nulls are * skipped over. */ - if (PG_ARGISNULL(1)) + if (!get_fn_expr_variadic(fcinfo->flinfo)) { - nargs = 0; + nargs = PG_NARGS() - 1; + if (nargs == 1 && !PG_ARGISNULL(1)) + { + arg = PG_GETARG_DATUM(1); + type = get_cached_fn_expr_argtype(fcinfo, 1); + if (type == UNKNOWNOID && get_fn_expr_arg_stable(fcinfo->flinfo, 1)) + { + type = TEXTOID; + arg = CStringGetTextDatum(PG_GETARG_POINTER(1)); + } + is_null = false; + } } else { - nargs = extract_variadic_args(fcinfo, 1, true, &args, &types, &nulls); + if (PG_ARGISNULL(1)) + { + nargs = 0; + } + else + { + nargs = extract_variadic_args(fcinfo, 1, true, &args, &types, + &nulls); + if (nargs == 1) + { + arg = args[0]; + type = types[0]; + is_null = nulls[0]; + } + } } if (nargs == 1) { /* only add non null values */ - if (nulls[0] == false) + if (!is_null) { - agtype_value *agtv_value = NULL; + agtype_value agtv_value; + bool value_needs_free = false; + bool is_agtype_null = false; /* we need to check for agtype null and skip it, if found */ - if (types[0] == AGTYPEOID) + if (type == AGTYPEOID) { agtype *agt_arg; /* get the agtype argument */ - agt_arg = DATUM_GET_AGTYPE_P(args[0]); + agt_arg = DATUM_GET_AGTYPE_P(arg); /* get the scalar value */ if (AGTYPE_CONTAINER_IS_SCALAR(&agt_arg->root)) { - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_value, + &value_needs_free); + is_agtype_null = agtv_value.type == AGTV_NULL; + free_agtype_value_no_copy(&agtv_value, value_needs_free); } } /* skip the arg if agtype null */ - if (agtv_value == NULL || agtv_value->type != AGTV_NULL) + if (!is_agtype_null) { - add_agtype(args[0], nulls[0], castate, types[0], false); + add_agtype(arg, is_null, castate, type, false); } } } @@ -11536,8 +12399,9 @@ Datum age_collect_aggfinalfn(PG_FUNCTION_ARGS) if (PG_ARGISNULL(0)) { /* create and initialize the state */ - castate = palloc0(sizeof(agtype_in_state)); - memset(castate, 0, sizeof(agtype_in_state)); + castate = palloc(sizeof(agtype_in_state)); + castate->parse_state = NULL; + castate->res = NULL; /* start the array */ castate->res = push_agtype_value(&castate->parse_state, WAGT_BEGIN_ARRAY, NULL); @@ -11681,8 +12545,11 @@ agtype_value *get_agtype_value(char *funcname, agtype *agt_arg, funcname))); } + /* get the agtype value */ + agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + /* is it AGTV_NULL? */ - if (error && is_agtype_null(agt_arg)) + if (error && agtv_value->type == AGTV_NULL) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -11690,9 +12557,6 @@ agtype_value *get_agtype_value(char *funcname, agtype *agt_arg, funcname))); } - /* get the agtype value */ - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); - /* is it the correct type? */ if (error && agtv_value->type != type) { @@ -11785,38 +12649,48 @@ Datum age_eq_tilde(PG_FUNCTION_ARGS) /* they both need to scalars */ if (AGT_ROOT_IS_SCALAR(agt_string) && AGT_ROOT_IS_SCALAR(agt_pattern)) { - agtype_value *agtv_string; - agtype_value *agtv_pattern; + agtype_value agtv_string; + agtype_value agtv_pattern; + bool string_needs_free = false; + bool pattern_needs_free = false; /* get the contents of each container */ - agtv_string = get_ith_agtype_value_from_container(&agt_string->root, 0); - agtv_pattern = get_ith_agtype_value_from_container(&agt_pattern->root, - 0); + get_scalar_agtype_value_no_copy(agt_string, &agtv_string, + &string_needs_free); + get_scalar_agtype_value_no_copy(agt_pattern, &agtv_pattern, + &pattern_needs_free); /* if either are agtype null, return NULL */ - if (agtv_string->type == AGTV_NULL || - agtv_pattern->type == AGTV_NULL) + if (agtv_string.type == AGTV_NULL || + agtv_pattern.type == AGTV_NULL) { + free_agtype_value_no_copy(&agtv_string, string_needs_free); + free_agtype_value_no_copy(&agtv_pattern, pattern_needs_free); PG_RETURN_NULL(); } /* only strings can be compared, all others are errors */ - if (agtv_string->type == AGTV_STRING && - agtv_pattern->type == AGTV_STRING) + if (agtv_string.type == AGTV_STRING && + agtv_pattern.type == AGTV_STRING) { text *string = NULL; text *pattern = NULL; Datum result; - string = cstring_to_text_with_len(agtv_string->val.string.val, - agtv_string->val.string.len); - pattern = cstring_to_text_with_len(agtv_pattern->val.string.val, - agtv_pattern->val.string.len); + string = cstring_to_text_with_len(agtv_string.val.string.val, + agtv_string.val.string.len); + pattern = cstring_to_text_with_len(agtv_pattern.val.string.val, + agtv_pattern.val.string.len); result = (DirectFunctionCall2Coll(textregexeq, C_COLLATION_OID, PointerGetDatum(string), PointerGetDatum(pattern))); + free_agtype_value_no_copy(&agtv_string, string_needs_free); + free_agtype_value_no_copy(&agtv_pattern, pattern_needs_free); return boolean_to_agtype(DatumGetBool(result)); } + + free_agtype_value_no_copy(&agtv_string, string_needs_free); + free_agtype_value_no_copy(&agtv_pattern, pattern_needs_free); } /* if we got here we have values that are invalid */ ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -11892,6 +12766,7 @@ Datum age_keys(PG_FUNCTION_ARGS) agtype_value obj_key = {0}; agtype_iterator *it = NULL; agtype_parse_state *parse_state = NULL; + bool result_needs_free = false; /* check for null */ if (PG_ARGISNULL(0)) @@ -11908,29 +12783,44 @@ Datum age_keys(PG_FUNCTION_ARGS) */ if (AGT_ROOT_IS_SCALAR(agt_arg)) { - agtv_result = get_ith_agtype_value_from_container(&agt_arg->root, 0); + agtype_value scalar_result; + + get_scalar_agtype_value_no_copy(agt_arg, &scalar_result, + &result_needs_free); + agtv_result = &scalar_result; /* is it an agtype null, return null if it is */ if (agtv_result->type == AGTV_NULL) + { + free_agtype_value_no_copy(agtv_result, result_needs_free); PG_RETURN_NULL(); + } /* check for proper agtype and extract the properties field */ if (agtv_result->type == AGTV_EDGE || agtv_result->type == AGTV_VERTEX) { - agtv_result = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_result, - "properties"); + if (agtv_result->type == AGTV_VERTEX) + { + agtv_result = AGTYPE_VERTEX_GET_PROPERTIES(agtv_result); + } + else + { + agtv_result = AGTYPE_EDGE_GET_PROPERTIES(agtv_result); + } Assert(agtv_result != NULL); - Assert(agtv_result->type = AGTV_OBJECT); + Assert(agtv_result->type == AGTV_OBJECT); } else { + free_agtype_value_no_copy(agtv_result, result_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("keys() argument must be a vertex, edge, object or null"))); } agt_arg = agtype_value_to_agtype(agtv_result); + free_agtype_value_no_copy(&scalar_result, result_needs_free); agtv_result = NULL; } else if (!AGT_ROOT_IS_OBJECT(agt_arg)) @@ -11952,7 +12842,7 @@ Datum age_keys(PG_FUNCTION_ARGS) agtv_result = push_agtype_value(&parse_state, WAGT_END_ARRAY, NULL); Assert(agtv_result != NULL); - Assert(agtv_result->type = AGTV_ARRAY); + Assert(agtv_result->type == AGTV_ARRAY); PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result)); } @@ -11964,8 +12854,10 @@ PG_FUNCTION_INFO_V1(age_nodes); Datum age_nodes(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; - agtype_value *agtv_path = NULL; + agtype_value agtv_path; agtype_in_state agis_result; + agtype *result; + bool path_needs_free = false; int i = 0; /* check for null */ @@ -11975,6 +12867,16 @@ Datum age_nodes(PG_FUNCTION_ARGS) } agt_arg = AG_GET_ARG_AGTYPE_P(0); + if (AGT_ROOT_IS_BINARY(agt_arg) && + AGT_ROOT_BINARY_FLAGS(agt_arg) == AGT_FBINARY_TYPE_VLE_PATH) + { + agtype_value *agtv_nodes = agtv_materialize_vle_nodes(agt_arg); + + result = agtype_value_to_agtype(agtv_nodes); + pfree_agtype_value(agtv_nodes); + PG_RETURN_POINTER(result); + } + /* check for a scalar object */ if (!AGT_ROOT_IS_SCALAR(agt_arg)) { @@ -11983,19 +12885,22 @@ Datum age_nodes(PG_FUNCTION_ARGS) errmsg("nodes() argument must resolve to a scalar value"))); } - /* get the potential path out of the array */ - agtv_path = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_path, &path_needs_free); /* is it an agtype null? */ - if (agtv_path->type == AGTV_NULL) + if (agtv_path.type == AGTV_NULL) { - PG_RETURN_NULL(); + free_agtype_value_no_copy(&agtv_path, path_needs_free); + PG_RETURN_NULL(); } /* verify that it is an agtype path */ - if (agtv_path->type != AGTV_PATH) + if (agtv_path.type != AGTV_PATH) + { + free_agtype_value_no_copy(&agtv_path, path_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("nodes() argument must be a path"))); + } /* clear the result structure */ MemSet(&agis_result, 0, sizeof(agtype_in_state)); @@ -12004,10 +12909,10 @@ Datum age_nodes(PG_FUNCTION_ARGS) agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_BEGIN_ARRAY, NULL); /* push in each vertex (every other entry) from the path */ - for (i = 0; i < agtv_path->val.array.num_elems; i += 2) + for (i = 0; i < agtv_path.val.array.num_elems; i += 2) { agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, - &agtv_path->val.array.elems[i]); + &agtv_path.val.array.elems[i]); } /* push the end of the array */ @@ -12015,7 +12920,10 @@ Datum age_nodes(PG_FUNCTION_ARGS) WAGT_END_ARRAY, NULL); /* convert the agtype_value to a datum to return to the caller */ - PG_RETURN_POINTER(agtype_value_to_agtype(agis_result.res)); + result = agtype_value_to_agtype(agis_result.res); + free_agtype_value_no_copy(&agtv_path, path_needs_free); + + PG_RETURN_POINTER(result); } PG_FUNCTION_INFO_V1(age_labels); @@ -12030,9 +12938,11 @@ PG_FUNCTION_INFO_V1(age_labels); Datum age_labels(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; - agtype_value *agtv_temp = NULL; + agtype_value agtv_temp; agtype_value *agtv_label = NULL; agtype_in_state agis_result; + agtype *result; + bool temp_needs_free = false; /* get the vertex argument */ agt_arg = AG_GET_ARG_AGTYPE_P(0); @@ -12045,26 +12955,26 @@ Datum age_labels(PG_FUNCTION_ARGS) errmsg("labels() argument must resolve to a scalar value"))); } + get_scalar_agtype_value_no_copy(agt_arg, &agtv_temp, &temp_needs_free); + /* is it an agtype null? */ - if (AGTYPE_CONTAINER_IS_SCALAR(&agt_arg->root) && - AGTE_IS_NULL((&agt_arg->root)->children[0])) + if (agtv_temp.type == AGTV_NULL) { + free_agtype_value_no_copy(&agtv_temp, temp_needs_free); PG_RETURN_NULL(); } - /* get the potential vertex */ - agtv_temp = get_ith_agtype_value_from_container(&agt_arg->root, 0); - /* verify that it is an agtype vertex */ - if (agtv_temp->type != AGTV_VERTEX) + if (agtv_temp.type != AGTV_VERTEX) { + free_agtype_value_no_copy(&agtv_temp, temp_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("labels() argument must be a vertex"))); } /* get the label from the vertex */ - agtv_label = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_temp, "label"); + agtv_label = AGTYPE_VERTEX_GET_LABEL(&agtv_temp); /* it cannot be NULL */ Assert(agtv_label != NULL); @@ -12084,7 +12994,10 @@ Datum age_labels(PG_FUNCTION_ARGS) WAGT_END_ARRAY, NULL); /* convert the agtype_value to a datum to return to the caller */ - PG_RETURN_POINTER(agtype_value_to_agtype(agis_result.res)); + result = agtype_value_to_agtype(agis_result.res); + free_agtype_value_no_copy(&agtv_temp, temp_needs_free); + + PG_RETURN_POINTER(result); } PG_FUNCTION_INFO_V1(age_relationships); @@ -12094,8 +13007,10 @@ PG_FUNCTION_INFO_V1(age_relationships); Datum age_relationships(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; - agtype_value *agtv_path = NULL; + agtype_value agtv_path; agtype_in_state agis_result; + agtype *result; + bool path_needs_free = false; int i = 0; /* check for null */ @@ -12105,6 +13020,16 @@ Datum age_relationships(PG_FUNCTION_ARGS) } agt_arg = AG_GET_ARG_AGTYPE_P(0); + if (AGT_ROOT_IS_BINARY(agt_arg) && + AGT_ROOT_BINARY_FLAGS(agt_arg) == AGT_FBINARY_TYPE_VLE_PATH) + { + agtype_value *agtv_edges = agtv_materialize_vle_edges(agt_arg); + + result = agtype_value_to_agtype(agtv_edges); + pfree_agtype_value(agtv_edges); + PG_RETURN_POINTER(result); + } + /* check for a scalar object */ if (!AGT_ROOT_IS_SCALAR(agt_arg)) { @@ -12113,19 +13038,22 @@ Datum age_relationships(PG_FUNCTION_ARGS) errmsg("relationships() argument must resolve to a scalar value"))); } - /* get the potential path out of the array */ - agtv_path = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_path, &path_needs_free); /* is it an agtype null? */ - if (agtv_path->type == AGTV_NULL) + if (agtv_path.type == AGTV_NULL) { - PG_RETURN_NULL(); + free_agtype_value_no_copy(&agtv_path, path_needs_free); + PG_RETURN_NULL(); } /* verify that it is an agtype path */ - if (agtv_path->type != AGTV_PATH) + if (agtv_path.type != AGTV_PATH) + { + free_agtype_value_no_copy(&agtv_path, path_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("relationships() argument must be a path"))); + } /* clear the result structure */ MemSet(&agis_result, 0, sizeof(agtype_in_state)); @@ -12134,10 +13062,10 @@ Datum age_relationships(PG_FUNCTION_ARGS) agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_BEGIN_ARRAY, NULL); /* push in each edge (every other entry) from the path */ - for (i = 1; i < agtv_path->val.array.num_elems; i += 2) + for (i = 1; i < agtv_path.val.array.num_elems; i += 2) { agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, - &agtv_path->val.array.elems[i]); + &agtv_path.val.array.elems[i]); } /* push the end of the array */ @@ -12145,7 +13073,10 @@ Datum age_relationships(PG_FUNCTION_ARGS) WAGT_END_ARRAY, NULL); /* convert the agtype_value to a datum to return to the caller */ - PG_RETURN_POINTER(agtype_value_to_agtype(agis_result.res)); + result = agtype_value_to_agtype(agis_result.res); + free_agtype_value_no_copy(&agtv_path, path_needs_free); + + PG_RETURN_POINTER(result); } /* @@ -12175,8 +13106,8 @@ static int64 get_int64_from_int_datums(Datum d, Oid type, char *funcname, else if (type == AGTYPEOID) { agtype *agt_arg = NULL; - agtype_value *agtv_value = NULL; - agtype_container *agtc = NULL; + agtype_value agtv_value; + bool value_needs_free = false; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(d); @@ -12187,24 +13118,27 @@ static int64 get_int64_from_int_datums(Datum d, Oid type, char *funcname, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("%s() only supports scalar arguments", funcname))); } - /* check for agtype null*/ - agtc = &agt_arg->root; - if (AGTE_IS_NULL(agtc->children[0])) + /* extract it from the scalar array */ + get_scalar_agtype_value_no_copy(agt_arg, &agtv_value, + &value_needs_free); + + /* check for agtype null */ + if (agtv_value.type == AGTV_NULL) { + free_agtype_value_no_copy(&agtv_value, value_needs_free); *is_agnull = true; return 0; } - /* extract it from the scalar array */ - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); - /* check for agtype integer */ - if (agtv_value->type == AGTV_INTEGER) + if (agtv_value.type == AGTV_INTEGER) { - result = agtv_value->val.int_value; + result = agtv_value.val.int_value; + free_agtype_value_no_copy(&agtv_value, value_needs_free); } else { + free_agtype_value_no_copy(&agtv_value, value_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("%s() unsupported argument type", funcname))); @@ -12228,17 +13162,123 @@ static int64 get_int64_from_int_datums(Datum d, Oid type, char *funcname, */ Oid find_usable_btree_index_for_attr(Relation rel, AttrNumber attnum) { -List *index_list = RelationGetIndexList(rel); + btree_index_attr_cache_key key; + btree_index_attr_cache_entry *entry; + bool found; + + initialize_btree_index_attr_cache(); + + MemSet(&key, 0, sizeof(key)); + key.relid = RelationGetRelid(rel); + key.attnum = attnum; + + entry = hash_search(btree_index_attr_cache, &key, HASH_ENTER, &found); + if (!found) + { + entry->index_oid = + find_usable_btree_index_for_attr_uncached(rel, attnum); + } + + return entry->index_oid; +} + +static void initialize_btree_index_attr_cache(void) +{ + HASHCTL hash_ctl; + + if (btree_index_attr_cache != NULL) + { + return; + } + + if (!CacheMemoryContext) + { + CreateCacheMemoryContext(); + } + + MemSet(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(btree_index_attr_cache_key); + hash_ctl.entrysize = sizeof(btree_index_attr_cache_entry); + hash_ctl.hcxt = CacheMemoryContext; + + btree_index_attr_cache = + hash_create("AGE btree index attribute cache", 32, &hash_ctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + + if (!btree_index_attr_callback_registered) + { + CacheRegisterRelcacheCallback(invalidate_btree_index_attr_cache, + (Datum)0); + btree_index_attr_callback_registered = true; + } +} + +static void invalidate_btree_index_attr_cache(Datum arg, Oid relid) +{ + HASH_SEQ_STATUS hash_seq; + btree_index_attr_cache_entry *entry; + + if (btree_index_attr_cache == NULL) + { + return; + } + + if (!OidIsValid(relid)) + { + hash_destroy(btree_index_attr_cache); + btree_index_attr_cache = NULL; + return; + } + + hash_seq_init(&hash_seq, btree_index_attr_cache); + while ((entry = hash_seq_search(&hash_seq)) != NULL) + { + if (entry->key.relid == relid || entry->index_oid == relid) + { + hash_search(btree_index_attr_cache, &entry->key, HASH_REMOVE, + NULL); + } + } +} + +static Oid find_usable_btree_index_for_attr_uncached(Relation rel, + AttrNumber attnum) +{ + List *index_list; ListCell *ilc; Oid index_oid = InvalidOid; + if (attnum == 1) + { + Oid pk_index_oid = RelationGetPrimaryKeyIndex(rel, true); + + if (OidIsValid(pk_index_oid)) + { + Relation pk_index_rel = index_open(pk_index_oid, AccessShareLock); + + if (pk_index_rel->rd_index->indisvalid && + pk_index_rel->rd_index->indnkeyatts >= 1 && + pk_index_rel->rd_index->indkey.values[0] == attnum && + pk_index_rel->rd_rel->relam == BTREE_AM_OID && + RelationGetIndexPredicate(pk_index_rel) == NIL) + { + index_close(pk_index_rel, AccessShareLock); + return pk_index_oid; + } + + index_close(pk_index_rel, AccessShareLock); + } + } + + index_list = RelationGetIndexList(rel); + foreach(ilc, index_list) { Oid curr_idx_oid = lfirst_oid(ilc); Relation curr_idx_rel = index_open(curr_idx_oid, AccessShareLock); if (curr_idx_rel->rd_index->indisvalid && - curr_idx_rel->rd_index->indnatts >= 1 && + curr_idx_rel->rd_index->indnkeyatts >= 1 && curr_idx_rel->rd_index->indkey.values[0] == attnum && curr_idx_rel->rd_rel->relam == BTREE_AM_OID && RelationGetIndexPredicate(curr_idx_rel) == NIL) @@ -12263,8 +13303,11 @@ PG_FUNCTION_INFO_V1(age_range); Datum age_range(PG_FUNCTION_ARGS) { Datum *args = NULL; + Datum fast_args[3]; bool *nulls = NULL; + bool fast_nulls[3]; Oid *types = NULL; + Oid fast_types[3]; int nargs; int64 start_idx = 0; int64 end_idx = 0; @@ -12275,7 +13318,8 @@ Datum age_range(PG_FUNCTION_ARGS) int64 i = 0; /* get the arguments */ - nargs = extract_variadic_args(fcinfo, 0, false, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, false, 3, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* throw an error if the number of args is not the expected number */ if (nargs != 2 && nargs != 3) @@ -12480,7 +13524,7 @@ Datum agtype_volatile_wrapper(PG_FUNCTION_ARGS) } /* get the type of the input argument */ - type = get_fn_expr_argtype(fcinfo->flinfo, 0); + type = get_cached_fn_expr_argtype(fcinfo, 0); /* if it is NOT an AGTYPE, we need convert it to one, if possible */ if (type != AGTYPEOID) @@ -12537,9 +13581,11 @@ Datum agtype_volatile_wrapper(PG_FUNCTION_ARGS) } else if (type == TEXTOID) { + text *txt = DatumGetTextPP(arg); + agtv_result.type = AGTV_STRING; - agtv_result.val.string.val = text_to_cstring(DatumGetTextPP(arg)); - agtv_result.val.string.len = strlen(agtv_result.val.string.val); + agtv_result.val.string.val = VARDATA_ANY(txt); + agtv_result.val.string.len = VARSIZE_ANY_EXHDR(txt); } else { diff --git a/src/backend/utils/adt/agtype_gin.c b/src/backend/utils/adt/agtype_gin.c index 91ed15fc6..d57b516dd 100644 --- a/src/backend/utils/adt/agtype_gin.c +++ b/src/backend/utils/adt/agtype_gin.c @@ -519,14 +519,12 @@ static Datum make_scalar_key(const agtype_value *scalarVal, bool is_key) break; case AGTV_INTEGER: { - char *result; + int len; Assert(!is_key); - pg_lltoa(scalarVal->val.int_value, buf); - - result = pstrdup(buf); - item = make_text_key(AGT_GIN_FLAG_NUM, result, strlen(result)); + len = pg_lltoa(scalarVal->val.int_value, buf); + item = make_text_key(AGT_GIN_FLAG_NUM, buf, len); break; } case AGTV_FLOAT: diff --git a/src/backend/utils/adt/agtype_ops.c b/src/backend/utils/adt/agtype_ops.c index f7f45b467..4e93f50df 100644 --- a/src/backend/utils/adt/agtype_ops.c +++ b/src/backend/utils/adt/agtype_ops.c @@ -27,8 +27,10 @@ #include #include "utils/agtype.h" +#include "utils/array.h" #include "utils/datum.h" #include "utils/builtins.h" +#include "utils/float.h" static agtype *agtype_concat_impl(agtype *agt1, agtype *agt2); static agtype_value *iterator_concat(agtype_iterator **it1, @@ -37,22 +39,33 @@ static agtype_value *iterator_concat(agtype_iterator **it1, static void concat_to_agtype_string(agtype_value *result, char *lhs, int llen, char *rhs, int rlen); static char *get_string_from_agtype_value(agtype_value *agtv, int *length); +static text *agtype_container_to_text(agtype_container *container, + int estimated_len); static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text); static agtype *delete_from_object(agtype *agt, char *keyptr, int keylen); static agtype *delete_from_array(agtype *agt, agtype* indexes); +static bool parse_agtype_index_string(char *str, int len, long *lindex); +static void get_scalar_agtype_value_no_copy(agtype *agt, agtype_value *value, + bool *needs_free); +static void free_agtype_value_no_copy(agtype_value *value, bool needs_free); +static agtype_value *find_agtype_value_from_agtype_value_object( + agtype_value *object, agtype_value *key); +static agtype_container *get_entity_properties_container_no_copy( + agtype *agt, bool error_on_scalar, agtype_value *scalar_value, + bool *scalar_needs_free, agtype **owned_properties); static void concat_to_agtype_string(agtype_value *result, char *lhs, int llen, char *rhs, int rlen) { int length = llen + rlen; - char *buffer = result->val.string.val; + char *buffer; Assert(llen >= 0 && rlen >= 0); check_string_length(length); buffer = palloc(length); - strncpy(buffer, lhs, llen); - strncpy(buffer + llen, rhs, rlen); + memcpy(buffer, lhs, llen); + memcpy(buffer + llen, rhs, rlen); result->type = AGTV_STRING; result->val.string.len = length; @@ -61,28 +74,23 @@ static void concat_to_agtype_string(agtype_value *result, char *lhs, int llen, static char *get_string_from_agtype_value(agtype_value *agtv, int *length) { - Datum number; char *string; switch (agtv->type) { case AGTV_INTEGER: - number = DirectFunctionCall1(int8out, - Int64GetDatum(agtv->val.int_value)); - string = DatumGetCString(number); - *length = strlen(string); + string = palloc(24); + *length = pg_lltoa(agtv->val.int_value, string); return string; case AGTV_FLOAT: - number = DirectFunctionCall1(float8out, - Float8GetDatum(agtv->val.float_value)); - string = DatumGetCString(number); + string = float8out_internal(agtv->val.float_value); *length = strlen(string); - if (is_decimal_needed(string)) + if (is_decimal_needed_len(string, *length)) { char *str = palloc(*length + 2); - strncpy(str, string, *length); - strncpy(str + *length, ".0", 2); + memcpy(str, string, *length); + memcpy(str + *length, ".0", 2); *length += 2; string = str; } @@ -109,6 +117,161 @@ static char *get_string_from_agtype_value(agtype_value *agtv, int *length) return NULL; } +static text *agtype_container_to_text(agtype_container *container, + int estimated_len) +{ + StringInfoData out; + + initStringInfo(&out); + agtype_to_cstring(&out, container, estimated_len); + + return cstring_to_text_with_len(out.data, out.len); +} + +static bool parse_agtype_index_string(char *str, int len, long *lindex) +{ + int i = 0; + bool negative = false; + int64 value = 0; + int64 limit; + + while (i < len && (str[i] == ' ' || str[i] == '\t' || + str[i] == '\n' || str[i] == '\r' || + str[i] == '\f')) + { + i++; + } + + if (i < len && (str[i] == '-' || str[i] == '+')) + { + negative = (str[i] == '-'); + i++; + } + + if (i >= len) + { + return false; + } + + limit = negative ? (int64) INT_MAX + 1 : INT_MAX; + + for (; i < len; i++) + { + if (str[i] < '0' || str[i] > '9') + { + return false; + } + + value = value * 10 + (str[i] - '0'); + if (value > limit) + { + return false; + } + } + + *lindex = negative ? -value : value; + return true; +} + +static void get_scalar_agtype_value_no_copy(agtype *agt, agtype_value *value, + bool *needs_free) +{ + bool found; + + Assert(AGT_ROOT_IS_SCALAR(agt)); + + found = get_ith_agtype_value_from_container_no_copy(&agt->root, 0, value, + needs_free); + Assert(found); +} + +static void free_agtype_value_no_copy(agtype_value *value, bool needs_free) +{ + if (needs_free) + { + pfree_agtype_value_content(value); + } +} + +static agtype_value *find_agtype_value_from_agtype_value_object( + agtype_value *object, agtype_value *key) +{ + if (object == NULL || key->type != AGTV_STRING) + { + return NULL; + } + + if (object->type == AGTV_OBJECT) + { + return get_agtype_value_object_value(object, key->val.string.val, + key->val.string.len); + } + + if (object->type == AGTV_BINARY && + AGTYPE_CONTAINER_IS_OBJECT(object->val.binary.data)) + { + return find_agtype_value_from_container(object->val.binary.data, + AGT_FOBJECT, key); + } + + return NULL; +} + +static agtype_container *get_entity_properties_container_no_copy( + agtype *agt, bool error_on_scalar, agtype_value *scalar_value, + bool *scalar_needs_free, agtype **owned_properties) +{ + agtype_value *properties = NULL; + + *scalar_needs_free = false; + *owned_properties = NULL; + + if (!AGT_ROOT_IS_SCALAR(agt) || !AGTE_IS_AGTYPE(agt->root.children[0])) + { + return &agt->root; + } + + get_scalar_agtype_value_no_copy(agt, scalar_value, scalar_needs_free); + + if (scalar_value->type == AGTV_VERTEX) + { + properties = &scalar_value->val.object.pairs[2].value; + } + else if (scalar_value->type == AGTV_EDGE) + { + properties = &scalar_value->val.object.pairs[4].value; + } + else if (scalar_value->type == AGTV_PATH) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot extract properties from an agtype path"))); + } + else if (error_on_scalar) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("scalar object must be a vertex or edge"))); + } + else + { + properties = scalar_value; + } + + if (properties == NULL || properties->type == AGTV_NULL) + { + return NULL; + } + + if (properties->type == AGTV_BINARY) + { + return properties->val.binary.data; + } + + *owned_properties = agtype_value_to_agtype(properties); + return &(*owned_properties)->root; +} + Datum get_numeric_datum_from_agtype_value(agtype_value *agtv) { switch (agtv->type) @@ -151,7 +314,11 @@ Datum agtype_add(PG_FUNCTION_ARGS) agtype *rhs = AG_GET_ARG_AGTYPE_P(1); agtype_value *agtv_lhs; agtype_value *agtv_rhs; + agtype_value agtv_lhs_value; + agtype_value agtv_rhs_value; agtype_value agtv_result; + bool lhs_needs_free = false; + bool rhs_needs_free = false; /* If both are not scalars */ if (!(AGT_ROOT_IS_SCALAR(lhs) && AGT_ROOT_IS_SCALAR(rhs))) @@ -162,12 +329,16 @@ Datum agtype_add(PG_FUNCTION_ARGS) } /* Both are scalar */ - agtv_lhs = get_ith_agtype_value_from_container(&lhs->root, 0); - agtv_rhs = get_ith_agtype_value_from_container(&rhs->root, 0); + get_scalar_agtype_value_no_copy(lhs, &agtv_lhs_value, &lhs_needs_free); + get_scalar_agtype_value_no_copy(rhs, &agtv_rhs_value, &rhs_needs_free); + agtv_lhs = &agtv_lhs_value; + agtv_rhs = &agtv_rhs_value; /* openCypher: arithmetic over null yields null. */ if (agtv_lhs->type == AGTV_NULL || agtv_rhs->type == AGTV_NULL) { + free_agtype_value_no_copy(agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(agtv_rhs, rhs_needs_free); PG_RETURN_NULL(); } @@ -233,6 +404,8 @@ Datum agtype_add(PG_FUNCTION_ARGS) { Datum agt = AGTYPE_P_GET_DATUM(agtype_concat_impl(lhs, rhs)); + free_agtype_value_no_copy(agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(agtv_rhs, rhs_needs_free); PG_RETURN_DATUM(agt); } else @@ -243,6 +416,8 @@ Datum agtype_add(PG_FUNCTION_ARGS) errmsg("Invalid input parameter types for agtype_add"))); } + free_agtype_value_no_copy(agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(agtv_rhs, rhs_needs_free); AG_RETURN_AGTYPE_P(agtype_value_to_agtype(&agtv_result)); } @@ -421,7 +596,11 @@ Datum agtype_sub(PG_FUNCTION_ARGS) agtype *rhs = AG_GET_ARG_AGTYPE_P(1); agtype_value *agtv_lhs; agtype_value *agtv_rhs; + agtype_value agtv_lhs_value; + agtype_value agtv_rhs_value; agtype_value agtv_result; + bool lhs_needs_free = false; + bool rhs_needs_free = false; /* * Logic to handle when the rhs is a non scalar array. In this @@ -497,43 +676,65 @@ Datum agtype_sub(PG_FUNCTION_ARGS) */ if(!AGT_ROOT_IS_SCALAR(lhs)) { - agtype_value *key; - key = get_ith_agtype_value_from_container(&rhs->root, 0); + agtype_value key_value; + agtype_value *key = &key_value; + bool key_needs_free = false; + + (void)get_ith_agtype_value_from_container_no_copy(&rhs->root, 0, key, + &key_needs_free); if (AGT_ROOT_IS_OBJECT(lhs) && key->type == AGTV_STRING) { - AG_RETURN_AGTYPE_P(delete_from_object(lhs, key->val.string.val, - key->val.string.len)); + agtype *result = delete_from_object(lhs, key->val.string.val, + key->val.string.len); + + free_agtype_value_no_copy(key, key_needs_free); + AG_RETURN_AGTYPE_P(result); } else if (AGT_ROOT_IS_ARRAY(lhs) && key->type == AGTV_INTEGER) { - AG_RETURN_AGTYPE_P(delete_from_array(lhs, rhs)); + agtype *result = delete_from_array(lhs, rhs); + + free_agtype_value_no_copy(key, key_needs_free); + AG_RETURN_AGTYPE_P(result); } else { if (AGT_ROOT_IS_OBJECT(lhs)) { + char *type_name = agtype_value_type_to_string(key->type); + + free_agtype_value_no_copy(key, key_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("expected agtype string, not agtype %s", - agtype_value_type_to_string(key->type)))); + type_name))); } else if (AGT_ROOT_IS_ARRAY(lhs)) { + char *type_name = agtype_value_type_to_string(key->type); + + free_agtype_value_no_copy(key, key_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("expected agtype integer, not agtype %s", - agtype_value_type_to_string(key->type)))); + type_name))); } + + free_agtype_value_no_copy(key, key_needs_free); } } - agtv_lhs = get_ith_agtype_value_from_container(&lhs->root, 0); - agtv_rhs = get_ith_agtype_value_from_container(&rhs->root, 0); + get_scalar_agtype_value_no_copy(lhs, &agtv_lhs_value, &lhs_needs_free); + get_scalar_agtype_value_no_copy(rhs, &agtv_rhs_value, &rhs_needs_free); + agtv_lhs = &agtv_lhs_value; + agtv_rhs = &agtv_rhs_value; /* openCypher: arithmetic over null yields null. */ if (agtv_lhs->type == AGTV_NULL || agtv_rhs->type == AGTV_NULL) { + free_agtype_value_no_copy(agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(agtv_rhs, rhs_needs_free); PG_RETURN_NULL(); } @@ -580,6 +781,8 @@ Datum agtype_sub(PG_FUNCTION_ARGS) errmsg("Invalid input parameter types for agtype_sub"))); } + free_agtype_value_no_copy(agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(agtv_rhs, rhs_needs_free); AG_RETURN_AGTYPE_P(agtype_value_to_agtype(&agtv_result)); } @@ -615,7 +818,9 @@ Datum agtype_neg(PG_FUNCTION_ARGS) { agtype *v = AG_GET_ARG_AGTYPE_P(0); agtype_value *agtv_value; + agtype_value agtv_value_value; agtype_value agtv_result; + bool value_needs_free = false; if (!(AGT_ROOT_IS_SCALAR(v))) { @@ -625,11 +830,13 @@ Datum agtype_neg(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } - agtv_value = get_ith_agtype_value_from_container(&v->root, 0); + get_scalar_agtype_value_no_copy(v, &agtv_value_value, &value_needs_free); + agtv_value = &agtv_value_value; /* openCypher: arithmetic over null yields null. */ if (agtv_value->type == AGTV_NULL) { + free_agtype_value_no_copy(agtv_value, value_needs_free); PG_RETURN_NULL(); } @@ -657,6 +864,7 @@ Datum agtype_neg(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Invalid input parameter type for agtype_neg"))); + free_agtype_value_no_copy(agtv_value, value_needs_free); AG_RETURN_AGTYPE_P(agtype_value_to_agtype(&agtv_result)); } @@ -671,7 +879,11 @@ Datum agtype_mul(PG_FUNCTION_ARGS) agtype *rhs = AG_GET_ARG_AGTYPE_P(1); agtype_value *agtv_lhs; agtype_value *agtv_rhs; + agtype_value agtv_lhs_value; + agtype_value agtv_rhs_value; agtype_value agtv_result; + bool lhs_needs_free = false; + bool rhs_needs_free = false; if (!(AGT_ROOT_IS_SCALAR(lhs)) || !(AGT_ROOT_IS_SCALAR(rhs))) { @@ -681,12 +893,16 @@ Datum agtype_mul(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } - agtv_lhs = get_ith_agtype_value_from_container(&lhs->root, 0); - agtv_rhs = get_ith_agtype_value_from_container(&rhs->root, 0); + get_scalar_agtype_value_no_copy(lhs, &agtv_lhs_value, &lhs_needs_free); + get_scalar_agtype_value_no_copy(rhs, &agtv_rhs_value, &rhs_needs_free); + agtv_lhs = &agtv_lhs_value; + agtv_rhs = &agtv_rhs_value; /* openCypher: arithmetic over null yields null. */ if (agtv_lhs->type == AGTV_NULL || agtv_rhs->type == AGTV_NULL) { + free_agtype_value_no_copy(agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(agtv_rhs, rhs_needs_free); PG_RETURN_NULL(); } @@ -730,6 +946,8 @@ Datum agtype_mul(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Invalid input parameter types for agtype_mul"))); + free_agtype_value_no_copy(agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(agtv_rhs, rhs_needs_free); AG_RETURN_AGTYPE_P(agtype_value_to_agtype(&agtv_result)); } @@ -767,7 +985,11 @@ Datum agtype_div(PG_FUNCTION_ARGS) agtype *rhs = AG_GET_ARG_AGTYPE_P(1); agtype_value *agtv_lhs; agtype_value *agtv_rhs; + agtype_value agtv_lhs_value; + agtype_value agtv_rhs_value; agtype_value agtv_result; + bool lhs_needs_free = false; + bool rhs_needs_free = false; if (!(AGT_ROOT_IS_SCALAR(lhs)) || !(AGT_ROOT_IS_SCALAR(rhs))) { @@ -777,12 +999,16 @@ Datum agtype_div(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } - agtv_lhs = get_ith_agtype_value_from_container(&lhs->root, 0); - agtv_rhs = get_ith_agtype_value_from_container(&rhs->root, 0); + get_scalar_agtype_value_no_copy(lhs, &agtv_lhs_value, &lhs_needs_free); + get_scalar_agtype_value_no_copy(rhs, &agtv_rhs_value, &rhs_needs_free); + agtv_lhs = &agtv_lhs_value; + agtv_rhs = &agtv_rhs_value; /* openCypher: arithmetic over null yields null. */ if (agtv_lhs->type == AGTV_NULL || agtv_rhs->type == AGTV_NULL) { + free_agtype_value_no_copy(agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(agtv_rhs, rhs_needs_free); PG_RETURN_NULL(); } @@ -854,7 +1080,9 @@ Datum agtype_div(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Invalid input parameter types for agtype_div"))); - AG_RETURN_AGTYPE_P(agtype_value_to_agtype(&agtv_result)); + free_agtype_value_no_copy(agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(agtv_rhs, rhs_needs_free); + AG_RETURN_AGTYPE_P(agtype_value_to_agtype(&agtv_result)); } PG_FUNCTION_INFO_V1(agtype_any_div); @@ -891,7 +1119,11 @@ Datum agtype_mod(PG_FUNCTION_ARGS) agtype *rhs = AG_GET_ARG_AGTYPE_P(1); agtype_value *agtv_lhs; agtype_value *agtv_rhs; + agtype_value agtv_lhs_value; + agtype_value agtv_rhs_value; agtype_value agtv_result; + bool lhs_needs_free = false; + bool rhs_needs_free = false; if (!(AGT_ROOT_IS_SCALAR(lhs)) || !(AGT_ROOT_IS_SCALAR(rhs))) { @@ -901,12 +1133,16 @@ Datum agtype_mod(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } - agtv_lhs = get_ith_agtype_value_from_container(&lhs->root, 0); - agtv_rhs = get_ith_agtype_value_from_container(&rhs->root, 0); + get_scalar_agtype_value_no_copy(lhs, &agtv_lhs_value, &lhs_needs_free); + get_scalar_agtype_value_no_copy(rhs, &agtv_rhs_value, &rhs_needs_free); + agtv_lhs = &agtv_lhs_value; + agtv_rhs = &agtv_rhs_value; /* openCypher: arithmetic over null yields null. */ if (agtv_lhs->type == AGTV_NULL || agtv_rhs->type == AGTV_NULL) { + free_agtype_value_no_copy(agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(agtv_rhs, rhs_needs_free); PG_RETURN_NULL(); } @@ -950,6 +1186,8 @@ Datum agtype_mod(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Invalid input parameter types for agtype_mod"))); + free_agtype_value_no_copy(agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(agtv_rhs, rhs_needs_free); AG_RETURN_AGTYPE_P(agtype_value_to_agtype(&agtv_result)); } @@ -987,7 +1225,11 @@ Datum agtype_pow(PG_FUNCTION_ARGS) agtype *rhs = AG_GET_ARG_AGTYPE_P(1); agtype_value *agtv_lhs; agtype_value *agtv_rhs; + agtype_value agtv_lhs_value; + agtype_value agtv_rhs_value; agtype_value agtv_result; + bool lhs_needs_free = false; + bool rhs_needs_free = false; if (!(AGT_ROOT_IS_SCALAR(lhs)) || !(AGT_ROOT_IS_SCALAR(rhs))) { @@ -997,12 +1239,16 @@ Datum agtype_pow(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } - agtv_lhs = get_ith_agtype_value_from_container(&lhs->root, 0); - agtv_rhs = get_ith_agtype_value_from_container(&rhs->root, 0); + get_scalar_agtype_value_no_copy(lhs, &agtv_lhs_value, &lhs_needs_free); + get_scalar_agtype_value_no_copy(rhs, &agtv_rhs_value, &rhs_needs_free); + agtv_lhs = &agtv_lhs_value; + agtv_rhs = &agtv_rhs_value; /* openCypher: arithmetic over null yields null. */ if (agtv_lhs->type == AGTV_NULL || agtv_rhs->type == AGTV_NULL) { + free_agtype_value_no_copy(agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(agtv_rhs, rhs_needs_free); PG_RETURN_NULL(); } @@ -1046,6 +1292,8 @@ Datum agtype_pow(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Invalid input parameter types for agtype_pow"))); + free_agtype_value_no_copy(agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(agtv_rhs, rhs_needs_free); AG_RETURN_AGTYPE_P(agtype_value_to_agtype(&agtv_result)); } @@ -1317,23 +1565,61 @@ Datum agtype_exists_agtype(PG_FUNCTION_ARGS) { agtype *agt = AG_GET_ARG_AGTYPE_P(0); agtype *key = AG_GET_ARG_AGTYPE_P(1); - agtype_value *aval; + agtype_value aval_value; + agtype_value *aval = &aval_value; agtype_value *v = NULL; - - if (AGT_ROOT_IS_SCALAR(agt)) - { - agt = agtype_value_to_agtype(extract_entity_properties(agt, false)); - } + bool aval_needs_free = false; if (AGT_ROOT_IS_SCALAR(key)) { - aval = get_ith_agtype_value_from_container(&key->root, 0); + get_scalar_agtype_value_no_copy(key, aval, &aval_needs_free); } else { PG_RETURN_BOOL(false); } + if (AGT_ROOT_IS_SCALAR(agt)) + { + agtype_value scalar_value; + agtype_value *properties = NULL; + bool scalar_needs_free = false; + bool found = false; + + get_scalar_agtype_value_no_copy(agt, &scalar_value, + &scalar_needs_free); + + if (scalar_value.type == AGTV_VERTEX) + { + properties = &scalar_value.val.object.pairs[2].value; + } + else if (scalar_value.type == AGTV_EDGE) + { + properties = &scalar_value.val.object.pairs[4].value; + } + else if (scalar_value.type == AGTV_PATH) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot extract properties from an agtype path"))); + } + else + { + free_agtype_value_no_copy(&scalar_value, scalar_needs_free); + agt = agtype_value_to_agtype(extract_entity_properties(agt, + false)); + goto search_container; + } + + found = find_agtype_value_from_agtype_value_object(properties, + aval) != NULL; + + free_agtype_value_no_copy(&scalar_value, scalar_needs_free); + free_agtype_value_no_copy(aval, aval_needs_free); + PG_RETURN_BOOL(found); + } + +search_container: if (AGT_ROOT_IS_OBJECT(agt) && aval->type == AGTV_STRING) { @@ -1349,6 +1635,8 @@ Datum agtype_exists_agtype(PG_FUNCTION_ARGS) aval); } + free_agtype_value_no_copy(aval, aval_needs_free); + PG_RETURN_BOOL(v != NULL); } @@ -1363,10 +1651,38 @@ Datum agtype_exists_any_agtype(PG_FUNCTION_ARGS) agtype *keys = AG_GET_ARG_AGTYPE_P(1); agtype_value elem; agtype_iterator *it = NULL; + agtype_value scalar_value; + agtype_value *properties = NULL; + bool scalar_needs_free = false; + bool use_scalar_properties = false; if (AGT_ROOT_IS_SCALAR(agt)) { - agt = agtype_value_to_agtype(extract_entity_properties(agt, true)); + get_scalar_agtype_value_no_copy(agt, &scalar_value, + &scalar_needs_free); + + if (scalar_value.type == AGTV_VERTEX) + { + properties = &scalar_value.val.object.pairs[2].value; + use_scalar_properties = true; + } + else if (scalar_value.type == AGTV_EDGE) + { + properties = &scalar_value.val.object.pairs[4].value; + use_scalar_properties = true; + } + else if (scalar_value.type == AGTV_PATH) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot extract properties from an agtype path"))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("scalar object must be a vertex or edge"))); + } } if (!AGT_ROOT_IS_SCALAR(keys) && !AGT_ROOT_IS_OBJECT(keys)) @@ -1375,7 +1691,17 @@ Datum agtype_exists_any_agtype(PG_FUNCTION_ARGS) { if (IS_A_AGTYPE_SCALAR(&elem)) { - if (AGT_ROOT_IS_OBJECT(agt) && + if (use_scalar_properties) + { + if (find_agtype_value_from_agtype_value_object(properties, + &elem)) + { + free_agtype_value_no_copy(&scalar_value, + scalar_needs_free); + PG_RETURN_BOOL(true); + } + } + else if (AGT_ROOT_IS_OBJECT(agt) && (&elem)->type == AGTV_STRING && find_agtype_value_from_container(&agt->root, AGT_FOBJECT, @@ -1394,6 +1720,7 @@ Datum agtype_exists_any_agtype(PG_FUNCTION_ARGS) } else { + free_agtype_value_no_copy(&scalar_value, scalar_needs_free); PG_RETURN_BOOL(false); } } @@ -1404,6 +1731,7 @@ Datum agtype_exists_any_agtype(PG_FUNCTION_ARGS) errmsg("invalid agtype value for right operand"))); } + free_agtype_value_no_copy(&scalar_value, scalar_needs_free); PG_RETURN_BOOL(false); } @@ -1418,10 +1746,38 @@ Datum agtype_exists_all_agtype(PG_FUNCTION_ARGS) agtype *keys = AG_GET_ARG_AGTYPE_P(1); agtype_value elem; agtype_iterator *it = NULL; + agtype_value scalar_value; + agtype_value *properties = NULL; + bool scalar_needs_free = false; + bool use_scalar_properties = false; if (AGT_ROOT_IS_SCALAR(agt)) { - agt = agtype_value_to_agtype(extract_entity_properties(agt, true)); + get_scalar_agtype_value_no_copy(agt, &scalar_value, + &scalar_needs_free); + + if (scalar_value.type == AGTV_VERTEX) + { + properties = &scalar_value.val.object.pairs[2].value; + use_scalar_properties = true; + } + else if (scalar_value.type == AGTV_EDGE) + { + properties = &scalar_value.val.object.pairs[4].value; + use_scalar_properties = true; + } + else if (scalar_value.type == AGTV_PATH) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot extract properties from an agtype path"))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("scalar object must be a vertex or edge"))); + } } if (!AGT_ROOT_IS_SCALAR(keys) && !AGT_ROOT_IS_OBJECT(keys)) @@ -1434,6 +1790,12 @@ Datum agtype_exists_all_agtype(PG_FUNCTION_ARGS) { continue; } + else if (use_scalar_properties && + find_agtype_value_from_agtype_value_object(properties, + &elem)) + { + continue; + } else if (AGT_ROOT_IS_OBJECT(agt) && (&elem)->type == AGTV_STRING && find_agtype_value_from_container(&agt->root, @@ -1451,11 +1813,14 @@ Datum agtype_exists_all_agtype(PG_FUNCTION_ARGS) } else { + free_agtype_value_no_copy(&scalar_value, + scalar_needs_free); PG_RETURN_BOOL(false); } } else { + free_agtype_value_no_copy(&scalar_value, scalar_needs_free); PG_RETURN_BOOL(false); } } @@ -1466,6 +1831,7 @@ Datum agtype_exists_all_agtype(PG_FUNCTION_ARGS) errmsg("invalid agtype value for right operand"))); } + free_agtype_value_no_copy(&scalar_value, scalar_needs_free); PG_RETURN_BOOL(true); } @@ -1480,6 +1846,15 @@ Datum agtype_contains(PG_FUNCTION_ARGS) agtype_iterator *property_it = NULL; agtype *properties = NULL; agtype *constraints = NULL; + agtype *owned_properties = NULL; + agtype *owned_constraints = NULL; + agtype_value properties_scalar; + agtype_value constraints_scalar; + agtype_container *properties_container; + agtype_container *constraints_container; + bool properties_needs_free = false; + bool constraints_needs_free = false; + bool result; if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) { @@ -1489,31 +1864,29 @@ Datum agtype_contains(PG_FUNCTION_ARGS) properties = AG_GET_ARG_AGTYPE_P(0); constraints = AG_GET_ARG_AGTYPE_P(1); - if (AGT_ROOT_IS_SCALAR(properties) - && AGTE_IS_AGTYPE(properties->root.children[0])) - { - properties = - agtype_value_to_agtype(extract_entity_properties(properties, - false)); - } - - if (AGT_ROOT_IS_SCALAR(constraints) - && AGTE_IS_AGTYPE(constraints->root.children[0])) - { - constraints = - agtype_value_to_agtype(extract_entity_properties(constraints, - false)); - } + properties_container = get_entity_properties_container_no_copy( + properties, false, &properties_scalar, &properties_needs_free, + &owned_properties); + constraints_container = get_entity_properties_container_no_copy( + constraints, false, &constraints_scalar, &constraints_needs_free, + &owned_constraints); - if (AGT_ROOT_IS_OBJECT(properties) != AGT_ROOT_IS_OBJECT(constraints)) + if (properties_container == NULL || constraints_container == NULL || + AGTYPE_CONTAINER_IS_OBJECT(properties_container) != + AGTYPE_CONTAINER_IS_OBJECT(constraints_container)) { + free_agtype_value_no_copy(&properties_scalar, properties_needs_free); + free_agtype_value_no_copy(&constraints_scalar, constraints_needs_free); PG_RETURN_BOOL(false); } - property_it = agtype_iterator_init(&properties->root); - constraint_it = agtype_iterator_init(&constraints->root); + property_it = agtype_iterator_init(properties_container); + constraint_it = agtype_iterator_init(constraints_container); + result = agtype_deep_contains(&property_it, &constraint_it, false); - PG_RETURN_BOOL(agtype_deep_contains(&property_it, &constraint_it, false)); + free_agtype_value_no_copy(&properties_scalar, properties_needs_free); + free_agtype_value_no_copy(&constraints_scalar, constraints_needs_free); + PG_RETURN_BOOL(result); } PG_FUNCTION_INFO_V1(agtype_contained_by_top_level); @@ -1527,6 +1900,15 @@ Datum agtype_contained_by_top_level(PG_FUNCTION_ARGS) { agtype_iterator *constraint_it, *property_it; agtype *properties, *constraints; + agtype *owned_properties = NULL; + agtype *owned_constraints = NULL; + agtype_value properties_scalar; + agtype_value constraints_scalar; + agtype_container *properties_container; + agtype_container *constraints_container; + bool properties_needs_free = false; + bool constraints_needs_free = false; + bool result; if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) { @@ -1536,26 +1918,27 @@ Datum agtype_contained_by_top_level(PG_FUNCTION_ARGS) properties = AG_GET_ARG_AGTYPE_P(0); constraints = AG_GET_ARG_AGTYPE_P(1); - if (AGT_ROOT_IS_SCALAR(properties) - && AGTE_IS_AGTYPE(properties->root.children[0])) - { - properties = - agtype_value_to_agtype(extract_entity_properties(properties, - false)); - } + properties_container = get_entity_properties_container_no_copy( + properties, false, &properties_scalar, &properties_needs_free, + &owned_properties); + constraints_container = get_entity_properties_container_no_copy( + constraints, false, &constraints_scalar, &constraints_needs_free, + &owned_constraints); - if (AGT_ROOT_IS_SCALAR(constraints) - && AGTE_IS_AGTYPE(constraints->root.children[0])) + if (properties_container == NULL || constraints_container == NULL) { - constraints = - agtype_value_to_agtype(extract_entity_properties(constraints, - false)); + free_agtype_value_no_copy(&properties_scalar, properties_needs_free); + free_agtype_value_no_copy(&constraints_scalar, constraints_needs_free); + PG_RETURN_BOOL(false); } - constraint_it = agtype_iterator_init(&constraints->root); - property_it = agtype_iterator_init(&properties->root); + constraint_it = agtype_iterator_init(constraints_container); + property_it = agtype_iterator_init(properties_container); + result = agtype_deep_contains(&constraint_it, &property_it, true); - PG_RETURN_BOOL(agtype_deep_contains(&constraint_it, &property_it, true)); + free_agtype_value_no_copy(&properties_scalar, properties_needs_free); + free_agtype_value_no_copy(&constraints_scalar, constraints_needs_free); + PG_RETURN_BOOL(result); } @@ -1568,6 +1951,15 @@ Datum agtype_contained_by(PG_FUNCTION_ARGS) { agtype_iterator *constraint_it, *property_it; agtype *properties, *constraints; + agtype *owned_properties = NULL; + agtype *owned_constraints = NULL; + agtype_value properties_scalar; + agtype_value constraints_scalar; + agtype_container *properties_container; + agtype_container *constraints_container; + bool properties_needs_free = false; + bool constraints_needs_free = false; + bool result; if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) { @@ -1577,26 +1969,27 @@ Datum agtype_contained_by(PG_FUNCTION_ARGS) properties = AG_GET_ARG_AGTYPE_P(0); constraints = AG_GET_ARG_AGTYPE_P(1); - if (AGT_ROOT_IS_SCALAR(properties) - && AGTE_IS_AGTYPE(properties->root.children[0])) - { - properties = - agtype_value_to_agtype(extract_entity_properties(properties, - false)); - } + properties_container = get_entity_properties_container_no_copy( + properties, false, &properties_scalar, &properties_needs_free, + &owned_properties); + constraints_container = get_entity_properties_container_no_copy( + constraints, false, &constraints_scalar, &constraints_needs_free, + &owned_constraints); - if (AGT_ROOT_IS_SCALAR(constraints) - && AGTE_IS_AGTYPE(constraints->root.children[0])) + if (properties_container == NULL || constraints_container == NULL) { - constraints = - agtype_value_to_agtype(extract_entity_properties(constraints, - false)); + free_agtype_value_no_copy(&properties_scalar, properties_needs_free); + free_agtype_value_no_copy(&constraints_scalar, constraints_needs_free); + PG_RETURN_BOOL(false); } - constraint_it = agtype_iterator_init(&constraints->root); - property_it = agtype_iterator_init(&properties->root); + constraint_it = agtype_iterator_init(constraints_container); + property_it = agtype_iterator_init(properties_container); + result = agtype_deep_contains(&constraint_it, &property_it, false); - PG_RETURN_BOOL(agtype_deep_contains(&constraint_it, &property_it, false)); + free_agtype_value_no_copy(&properties_scalar, properties_needs_free); + free_agtype_value_no_copy(&constraints_scalar, constraints_needs_free); + PG_RETURN_BOOL(result); } PG_FUNCTION_INFO_V1(agtype_contains_top_level); @@ -1612,6 +2005,15 @@ Datum agtype_contains_top_level(PG_FUNCTION_ARGS) agtype_iterator *property_it = NULL; agtype *properties = NULL; agtype *constraints = NULL; + agtype *owned_properties = NULL; + agtype *owned_constraints = NULL; + agtype_value properties_scalar; + agtype_value constraints_scalar; + agtype_container *properties_container; + agtype_container *constraints_container; + bool properties_needs_free = false; + bool constraints_needs_free = false; + bool result; if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) { @@ -1621,31 +2023,29 @@ Datum agtype_contains_top_level(PG_FUNCTION_ARGS) properties = AG_GET_ARG_AGTYPE_P(0); constraints = AG_GET_ARG_AGTYPE_P(1); - if (AGT_ROOT_IS_SCALAR(properties) - && AGTE_IS_AGTYPE(properties->root.children[0])) - { - properties = - agtype_value_to_agtype(extract_entity_properties(properties, - false)); - } - - if (AGT_ROOT_IS_SCALAR(constraints) - && AGTE_IS_AGTYPE(constraints->root.children[0])) - { - constraints = - agtype_value_to_agtype(extract_entity_properties(constraints, - false)); - } + properties_container = get_entity_properties_container_no_copy( + properties, false, &properties_scalar, &properties_needs_free, + &owned_properties); + constraints_container = get_entity_properties_container_no_copy( + constraints, false, &constraints_scalar, &constraints_needs_free, + &owned_constraints); - if (AGT_ROOT_IS_OBJECT(properties) != AGT_ROOT_IS_OBJECT(constraints)) + if (properties_container == NULL || constraints_container == NULL || + AGTYPE_CONTAINER_IS_OBJECT(properties_container) != + AGTYPE_CONTAINER_IS_OBJECT(constraints_container)) { + free_agtype_value_no_copy(&properties_scalar, properties_needs_free); + free_agtype_value_no_copy(&constraints_scalar, constraints_needs_free); PG_RETURN_BOOL(false); } - property_it = agtype_iterator_init(&properties->root); - constraint_it = agtype_iterator_init(&constraints->root); + property_it = agtype_iterator_init(properties_container); + constraint_it = agtype_iterator_init(constraints_container); + result = agtype_deep_contains(&property_it, &constraint_it, true); - PG_RETURN_BOOL(agtype_deep_contains(&property_it, &constraint_it, true)); + free_agtype_value_no_copy(&properties_scalar, properties_needs_free); + free_agtype_value_no_copy(&constraints_scalar, constraints_needs_free); + PG_RETURN_BOOL(result); } PG_FUNCTION_INFO_V1(agtype_exists); @@ -1685,35 +2085,37 @@ Datum agtype_exists_any(PG_FUNCTION_ARGS) { agtype *agt = AG_GET_ARG_AGTYPE_P(0); ArrayType *keys = PG_GETARG_ARRAYTYPE_P(1); - int i; - Datum *key_datums; - bool *key_nulls; - int elem_count; + ArrayIterator iterator; + Datum key_datum; + bool key_null; - deconstruct_array(keys, TEXTOID, -1, false, 'i', &key_datums, &key_nulls, - &elem_count); + iterator = array_create_iterator(keys, 0, NULL); - for (i = 0; i < elem_count; i++) + while (array_iterate(iterator, &key_datum, &key_null)) { + text *key; agtype_value strVal; - if (key_nulls[i]) + if (key_null) { continue; } + key = DatumGetTextPP(key_datum); strVal.type = AGTV_STRING; - strVal.val.string.val = VARDATA(key_datums[i]); - strVal.val.string.len = VARSIZE(key_datums[i]) - VARHDRSZ; + strVal.val.string.val = VARDATA_ANY(key); + strVal.val.string.len = VARSIZE_ANY_EXHDR(key); if (find_agtype_value_from_container(&agt->root, AGT_FOBJECT | AGT_FARRAY, &strVal) != NULL) { + array_free_iterator(iterator); PG_RETURN_BOOL(true); } } + array_free_iterator(iterator); PG_RETURN_BOOL(false); } @@ -1726,35 +2128,37 @@ Datum agtype_exists_all(PG_FUNCTION_ARGS) { agtype *agt = AG_GET_ARG_AGTYPE_P(0); ArrayType *keys = PG_GETARG_ARRAYTYPE_P(1); - int i; - Datum *key_datums; - bool *key_nulls; - int elem_count; + ArrayIterator iterator; + Datum key_datum; + bool key_null; - deconstruct_array(keys, TEXTOID, -1, false, 'i', &key_datums, &key_nulls, - &elem_count); + iterator = array_create_iterator(keys, 0, NULL); - for (i = 0; i < elem_count; i++) + while (array_iterate(iterator, &key_datum, &key_null)) { + text *key; agtype_value strVal; - if (key_nulls[i]) + if (key_null) { continue; } + key = DatumGetTextPP(key_datum); strVal.type = AGTV_STRING; - strVal.val.string.val = VARDATA(key_datums[i]); - strVal.val.string.len = VARSIZE(key_datums[i]) - VARHDRSZ; + strVal.val.string.val = VARDATA_ANY(key); + strVal.val.string.len = VARSIZE_ANY_EXHDR(key); if (find_agtype_value_from_container(&agt->root, AGT_FOBJECT | AGT_FARRAY, &strVal) == NULL) { + array_free_iterator(iterator); PG_RETURN_BOOL(false); } } + array_free_iterator(iterator); PG_RETURN_BOOL(true); } @@ -2048,6 +2452,11 @@ static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text) int i; bool have_object = false, have_array = false; agtype_value *agtvp = NULL; + agtype_value root_scalar_value; + agtype_value scalar_value; + agtype *owned_agt = NULL; + bool root_scalar_needs_free = false; + bool scalar_needs_free = false; agtype_value tv; agtype_container *container; @@ -2057,20 +2466,39 @@ static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text) errmsg("right operand must be an array"))); } + npath = AGT_ROOT_COUNT(path); if (AGT_ROOT_IS_SCALAR(agt)) { - agt = agtype_value_to_agtype(extract_entity_properties(agt, true)); + if (!AGTE_IS_AGTYPE(agt->root.children[0]) || npath <= 0) + { + agt = agtype_value_to_agtype(extract_entity_properties(agt, true)); + container = &agt->root; + } + else + { + container = get_entity_properties_container_no_copy( + agt, true, &root_scalar_value, &root_scalar_needs_free, + &owned_agt); + } + } + else + { + container = &agt->root; } - npath = AGT_ROOT_COUNT(path); - container = &agt->root; + if (container == NULL) + { + free_agtype_value_no_copy(&root_scalar_value, root_scalar_needs_free); + PG_RETURN_NULL(); + } /* Identify whether we have object, array, or scalar at top-level */ - if (AGT_ROOT_IS_OBJECT(agt)) + if (AGTYPE_CONTAINER_IS_OBJECT(container)) { have_object = true; } - else if (AGT_ROOT_IS_ARRAY(agt) && !AGT_ROOT_IS_SCALAR(agt)) + else if (AGTYPE_CONTAINER_IS_ARRAY(container) && + !AGTYPE_CONTAINER_IS_SCALAR(container)) { have_array = true; } @@ -2081,7 +2509,10 @@ static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text) /* Extract the scalar value */ if (npath <= 0) { - agtvp = get_ith_agtype_value_from_container(container, 0); + (void)get_ith_agtype_value_from_container_no_copy(container, 0, + &scalar_value, + &scalar_needs_free); + agtvp = &scalar_value; } } @@ -2097,26 +2528,35 @@ static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text) { if (as_text) { - PG_RETURN_TEXT_P(cstring_to_text(agtype_to_cstring(NULL, container, - VARSIZE(agt)))); + text *result = agtype_container_to_text(container, VARSIZE(agt)); + + free_agtype_value_no_copy(&root_scalar_value, + root_scalar_needs_free); + PG_RETURN_TEXT_P(result); } else { /* not text mode - just hand back the agtype */ + free_agtype_value_no_copy(&root_scalar_value, + root_scalar_needs_free); AG_RETURN_AGTYPE_P(agt); } } for (i = 0; i < npath; i++) { - agtype_value *cur_key = - get_ith_agtype_value_from_container(&path->root, i); + agtype_value cur_key; + bool cur_key_needs_free = false; + + (void)get_ith_agtype_value_from_container_no_copy(&path->root, i, + &cur_key, + &cur_key_needs_free); - if (have_object && cur_key->type == AGTV_STRING) + if (have_object && cur_key.type == AGTV_STRING) { agtvp = find_agtype_value_from_container(container, - AGT_FOBJECT, - cur_key); + AGT_FOBJECT, + &cur_key); } else if (have_array) { @@ -2127,31 +2567,39 @@ static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text) * for array on LHS, there should be an integer or a * valid integer string on RHS */ - if (cur_key->type == AGTV_INTEGER) + if (cur_key.type == AGTV_INTEGER) { - lindex = cur_key->val.int_value; + lindex = cur_key.val.int_value; } - else if (cur_key->type == AGTV_STRING) + else if (cur_key.type == AGTV_STRING) { /* * extract the integer from the string, * if character other than a digit is found, return null */ - char* str = NULL; - lindex = strtol(cur_key->val.string.val, &str, 10); - - if (strcmp(str, "")) + if (!parse_agtype_index_string(cur_key.val.string.val, + cur_key.val.string.len, + &lindex)) { + free_agtype_value_no_copy(&root_scalar_value, + root_scalar_needs_free); + free_agtype_value_no_copy(&cur_key, cur_key_needs_free); PG_RETURN_NULL(); } } else { + free_agtype_value_no_copy(&root_scalar_value, + root_scalar_needs_free); + free_agtype_value_no_copy(&cur_key, cur_key_needs_free); PG_RETURN_NULL(); } if (lindex > INT_MAX || lindex < INT_MIN) { + free_agtype_value_no_copy(&root_scalar_value, + root_scalar_needs_free); + free_agtype_value_no_copy(&cur_key, cur_key_needs_free); PG_RETURN_NULL(); } @@ -2174,6 +2622,9 @@ static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text) if (-lindex > nelements) { + free_agtype_value_no_copy(&root_scalar_value, + root_scalar_needs_free); + free_agtype_value_no_copy(&cur_key, cur_key_needs_free); PG_RETURN_NULL(); } else @@ -2182,19 +2633,37 @@ static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text) } } - agtvp = get_ith_agtype_value_from_container(container, index); + if (get_ith_agtype_value_from_container_no_copy(container, index, + &scalar_value, + &scalar_needs_free)) + { + agtvp = &scalar_value; + } + else + { + agtvp = NULL; + } } else { + free_agtype_value_no_copy(agtvp, scalar_needs_free); + free_agtype_value_no_copy(&root_scalar_value, + root_scalar_needs_free); + free_agtype_value_no_copy(&cur_key, cur_key_needs_free); PG_RETURN_NULL(); } if (agtvp == NULL) { + free_agtype_value_no_copy(agtvp, scalar_needs_free); + free_agtype_value_no_copy(&root_scalar_value, + root_scalar_needs_free); + free_agtype_value_no_copy(&cur_key, cur_key_needs_free); PG_RETURN_NULL(); } else if (i == npath - 1) { + free_agtype_value_no_copy(&cur_key, cur_key_needs_free); break; } @@ -2215,6 +2684,8 @@ static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text) have_object = agtvp->type == AGTV_OBJECT; have_array = agtvp->type == AGTV_ARRAY; } + + free_agtype_value_no_copy(&cur_key, cur_key_needs_free); } if (as_text) @@ -2222,23 +2693,31 @@ static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text) /* special-case output for string and null values */ if (agtvp->type == AGTV_STRING) { - PG_RETURN_TEXT_P(cstring_to_text_with_len(agtvp->val.string.val, - agtvp->val.string.len)); + text *result = cstring_to_text_with_len(agtvp->val.string.val, + agtvp->val.string.len); + + free_agtype_value_no_copy(agtvp, scalar_needs_free); + free_agtype_value_no_copy(&root_scalar_value, + root_scalar_needs_free); + PG_RETURN_TEXT_P(result); } if (agtvp->type == AGTV_NULL) { + free_agtype_value_no_copy(agtvp, scalar_needs_free); + free_agtype_value_no_copy(&root_scalar_value, + root_scalar_needs_free); PG_RETURN_NULL(); } } res = agtype_value_to_agtype(agtvp); + free_agtype_value_no_copy(agtvp, scalar_needs_free); + free_agtype_value_no_copy(&root_scalar_value, root_scalar_needs_free); if (as_text) { - PG_RETURN_TEXT_P(cstring_to_text(agtype_to_cstring(NULL, - &res->root, - VARSIZE(res)))); + PG_RETURN_TEXT_P(agtype_container_to_text(&res->root, VARSIZE(res))); } else { diff --git a/src/backend/utils/adt/agtype_parser.c b/src/backend/utils/adt/agtype_parser.c index 40fc8d8c5..623c14650 100644 --- a/src/backend/utils/adt/agtype_parser.c +++ b/src/backend/utils/adt/agtype_parser.c @@ -204,13 +204,16 @@ agtype_lex_context *make_agtype_lex_context(text *t, bool need_escapes) agtype_lex_context *make_agtype_lex_context_cstring_len(char *str, int len, bool need_escapes) { - agtype_lex_context *lex = palloc0(sizeof(agtype_lex_context)); + agtype_lex_context *lex = palloc(sizeof(agtype_lex_context)); lex->input = lex->token_terminator = lex->line_start = str; - lex->line_number = 1; lex->input_length = len; - if (need_escapes) - lex->strval = makeStringInfo(); + lex->token_start = NULL; + lex->prev_token_terminator = NULL; + lex->token_type = AGTYPE_TOKEN_INVALID; + lex->lex_level = 0; + lex->line_number = 1; + lex->strval = need_escapes ? makeStringInfo() : NULL; return lex; } diff --git a/src/backend/utils/adt/agtype_raw.c b/src/backend/utils/adt/agtype_raw.c index d8bad3d24..e012fa3f4 100644 --- a/src/backend/utils/adt/agtype_raw.c +++ b/src/backend/utils/adt/agtype_raw.c @@ -122,7 +122,7 @@ agtype_build_state *init_agtype_build_state(uint32 size, uint32 header_flag) int agtentry_len; agtype_build_state *bstate; - bstate = palloc0(sizeof(agtype_build_state)); + bstate = palloc(sizeof(agtype_build_state)); bstate->buffer = makeStringInfo(); bstate->a_offset = 0; bstate->i = 0; diff --git a/src/backend/utils/adt/agtype_util.c b/src/backend/utils/adt/agtype_util.c index b39723413..752e6ffee 100644 --- a/src/backend/utils/adt/agtype_util.c +++ b/src/backend/utils/adt/agtype_util.c @@ -592,6 +592,119 @@ agtype_value *find_agtype_value_from_container(agtype_container *container, return NULL; } +/* + * No-copy variant of find_agtype_value_from_container(). + * + * Strings, numerics, and nested binary containers point into the original + * container. Extended composite values still allocate during deserialization; + * needs_free tells callers when pfree_agtype_value_content() is required. + */ +bool find_agtype_value_from_container_no_copy(agtype_container *container, + uint32 flags, + agtype_value *key, + agtype_value *result, + bool *needs_free) +{ + agtentry *children = container->children; + int count = AGTYPE_CONTAINER_SIZE(container); + + Assert((flags & ~(AGT_FARRAY | AGT_FOBJECT)) == 0); + Assert(result != NULL); + + if (needs_free != NULL) + { + *needs_free = false; + } + + if (count <= 0) + { + return false; + } + + if ((flags & AGT_FARRAY) && AGTYPE_CONTAINER_IS_ARRAY(container)) + { + char *base_addr = (char *)(children + count); + uint32 offset = 0; + int i; + + for (i = 0; i < count; i++) + { + fill_agtype_value_no_copy(container, i, base_addr, offset, result); + + if (key->type == result->type && + compare_agtype_scalar_values(key, result) == 0) + { + if (needs_free != NULL) + { + *needs_free = result->type == AGTV_VERTEX || + result->type == AGTV_EDGE || + result->type == AGTV_PATH; + } + return true; + } + + if (result->type == AGTV_VERTEX || + result->type == AGTV_EDGE || + result->type == AGTV_PATH) + { + pfree_agtype_value_content(result); + } + AGTE_ADVANCE_OFFSET(offset, children[i]); + } + } + else if ((flags & AGT_FOBJECT) && AGTYPE_CONTAINER_IS_OBJECT(container)) + { + char *base_addr = (char *)(children + count * 2); + uint32 stop_low = 0; + uint32 stop_high = count; + + Assert(key->type == AGTV_STRING); + + while (stop_low < stop_high) + { + uint32 stop_middle; + int difference; + agtype_value candidate; + + stop_middle = stop_low + (stop_high - stop_low) / 2; + + candidate.type = AGTV_STRING; + candidate.val.string.val = + base_addr + get_agtype_offset(container, stop_middle); + candidate.val.string.len = get_agtype_length(container, + stop_middle); + + difference = length_compare_agtype_string_value(&candidate, key); + + if (difference == 0) + { + int index = stop_middle + count; + + fill_agtype_value_no_copy(container, index, base_addr, + get_agtype_offset(container, index), + result); + if (needs_free != NULL) + { + *needs_free = result->type == AGTV_VERTEX || + result->type == AGTV_EDGE || + result->type == AGTV_PATH; + } + return true; + } + else if (difference < 0) + { + stop_low = stop_middle + 1; + } + else + { + stop_high = stop_middle; + } + } + } + + return false; +} + /* * Get i-th value of an agtype array. * @@ -621,6 +734,43 @@ agtype_value *get_ith_agtype_value_from_container(agtype_container *container, return result; } +/* + * Get i-th value of an agtype array without copying the value contents when + * that is safe. Strings, numerics, and nested containers point into the + * original container. Extended composite values still allocate while + * deserializing, and needs_free tells the caller when the returned value + * content must be released with pfree_agtype_value_content(). + */ +bool get_ith_agtype_value_from_container_no_copy(agtype_container *container, + uint32 i, + agtype_value *result, + bool *needs_free) +{ + char *base_addr; + uint32 nelements; + + if (!AGTYPE_CONTAINER_IS_ARRAY(container)) + ereport(ERROR, (errmsg("container is not an agtype array"))); + + nelements = AGTYPE_CONTAINER_SIZE(container); + + if (i >= nelements) + return false; + + base_addr = (char *)&container->children[nelements]; + fill_agtype_value_no_copy(container, i, base_addr, + get_agtype_offset(container, i), result); + + if (needs_free != NULL) + { + *needs_free = result->type == AGTV_VERTEX || + result->type == AGTV_EDGE || + result->type == AGTV_PATH; + } + + return true; +} + /* * Get type of i-th value of an agtype array. */ @@ -1372,10 +1522,13 @@ static agtype_iterator *iterator_from_container(agtype_container *container, { agtype_iterator *it; - it = palloc0(sizeof(agtype_iterator)); + it = palloc(sizeof(agtype_iterator)); it->container = container; it->parent = parent; it->num_elems = AGTYPE_CONTAINER_SIZE(container); + it->curr_index = 0; + it->curr_data_offset = 0; + it->curr_value_offset = 0; /* Array starts just after header */ it->children = container->children; @@ -1395,6 +1548,7 @@ static agtype_iterator *iterator_from_container(agtype_container *container, case AGT_FOBJECT: it->data_proper = (char *)it->children + it->num_elems * sizeof(agtentry) * 2; + it->is_scalar = false; it->state = AGTI_OBJECT_START; break; diff --git a/src/backend/utils/adt/graphid.c b/src/backend/utils/adt/graphid.c index 71f41093b..43777dfb1 100644 --- a/src/backend/utils/adt/graphid.c +++ b/src/backend/utils/adt/graphid.c @@ -20,6 +20,7 @@ #include "postgres.h" #include "utils/builtins.h" +#include "utils/inval.h" #include "utils/sortsupport.h" #include "utils/graphid.h" @@ -29,10 +30,17 @@ static int graphid_btree_fast_cmp(Datum x, Datum y, SortSupport ssup); /* global storage of OID for graphid and _graphid */ static Oid g_GRAPHIDOID = InvalidOid; static Oid g_GRAPHIDARRAYOID = InvalidOid; +static bool graphid_oid_callback_registered = false; + +static void initialize_graphid_oid_cache(void); +static void invalidate_graphid_oid_cache(Datum arg, int cache_id, + uint32 hash_value); /* helper function to quickly set, if necessary, and retrieve GRAPHIDOID */ Oid get_GRAPHIDOID(void) { + initialize_graphid_oid_cache(); + if (g_GRAPHIDOID == InvalidOid) { g_GRAPHIDOID = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid, @@ -46,6 +54,8 @@ Oid get_GRAPHIDOID(void) /* helper function to quickly set, if necessary, and retrieve GRAPHIDARRAYOID */ Oid get_GRAPHIDARRAYOID(void) { + initialize_graphid_oid_cache(); + if (g_GRAPHIDARRAYOID == InvalidOid) { g_GRAPHIDARRAYOID = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid, @@ -63,6 +73,24 @@ void clear_global_Oids_GRAPHID(void) g_GRAPHIDARRAYOID = InvalidOid; } +static void initialize_graphid_oid_cache(void) +{ + if (graphid_oid_callback_registered) + { + return; + } + + CacheRegisterSyscacheCallback(TYPENAMENSP, invalidate_graphid_oid_cache, + (Datum)0); + graphid_oid_callback_registered = true; +} + +static void invalidate_graphid_oid_cache(Datum arg, int cache_id, + uint32 hash_value) +{ + clear_global_Oids_GRAPHID(); +} + PG_FUNCTION_INFO_V1(graphid_in); /* graphid type input function */ @@ -92,9 +120,10 @@ Datum graphid_out(PG_FUNCTION_ARGS) graphid gid = AG_GETARG_GRAPHID(0); char buf[32]; /* greater than MAXINT8LEN+1 */ char *out; + int len; - pg_lltoa(gid, buf); - out = pstrdup(buf); + len = pg_lltoa(gid, buf); + out = pnstrdup(buf, len); PG_RETURN_CSTRING(out); } diff --git a/src/backend/utils/ag_func.c b/src/backend/utils/ag_func.c index 1762286c3..2eed1b1ab 100644 --- a/src/backend/utils/ag_func.c +++ b/src/backend/utils/ag_func.c @@ -27,19 +27,43 @@ #include "access/htup_details.h" #include "catalog/pg_proc.h" #include "utils/builtins.h" +#include "utils/catcache.h" +#include "utils/hsearch.h" +#include "utils/inval.h" #include "utils/lsyscache.h" +#include "utils/memutils.h" #include "utils/syscache.h" #include "catalog/ag_namespace.h" #include "utils/ag_func.h" +typedef struct ag_func_cache_key +{ + NameData name; + int nargs; + Oid args[FUNC_MAX_ARGS]; +} ag_func_cache_key; + +typedef struct ag_func_cache_entry +{ + ag_func_cache_key key; + Oid func_oid; +} ag_func_cache_entry; + +static HTAB *ag_func_oid_cache = NULL; +static HTAB *pg_func_oid_cache = NULL; +static bool ag_func_oid_callback_registered = false; + +static void initialize_ag_func_oid_cache(void); +static void invalidate_ag_func_oid_cache(Datum arg, int cache_id, + uint32 hash_value); + /* checks that func_oid is of func_name function in ag_catalog */ bool is_oid_ag_func(Oid func_oid, const char *func_name) { HeapTuple proctup; Form_pg_proc proc; Oid nspid; - const char *nspname; Assert(OidIsValid(func_oid)); Assert(func_name); @@ -56,70 +80,170 @@ bool is_oid_ag_func(Oid func_oid, const char *func_name) nspid = proc->pronamespace; ReleaseSysCache(proctup); - nspname = get_namespace_name_or_temp(nspid); - Assert(nspname); - return (strcmp(nspname, "ag_catalog") == 0); + return nspid == ag_catalog_namespace_id(); } /* gets the function OID that matches with func_name and argument types */ Oid get_ag_func_oid(const char *func_name, const int nargs, ...) { - Oid oids[FUNC_MAX_ARGS]; - va_list ap; - int i; + ag_func_cache_key key; + ag_func_cache_entry *entry; oidvector *arg_types; Oid func_oid; + va_list ap; + int i; + bool found; Assert(func_name); Assert(nargs >= 0 && nargs <= FUNC_MAX_ARGS); + initialize_ag_func_oid_cache(); + MemSet(&key, 0, sizeof(key)); + namestrcpy(&key.name, func_name); + key.nargs = nargs; + va_start(ap, nargs); for (i = 0; i < nargs; i++) - oids[i] = va_arg(ap, Oid); + key.args[i] = va_arg(ap, Oid); va_end(ap); - arg_types = buildoidvector(oids, nargs); + entry = hash_search(ag_func_oid_cache, &key, HASH_FIND, NULL); + if (entry != NULL) + { + return entry->func_oid; + } + arg_types = buildoidvector(key.args, nargs); func_oid = GetSysCacheOid3(PROCNAMEARGSNSP, Anum_pg_proc_oid, CStringGetDatum(func_name), PointerGetDatum(arg_types), ObjectIdGetDatum(ag_catalog_namespace_id())); + pfree(arg_types); + if (!OidIsValid(func_oid)) { ereport(ERROR, (errmsg_internal("ag function does not exist"), errdetail_internal("%s(%d)", func_name, nargs))); } - return func_oid; + if (ag_func_oid_cache == NULL) + { + initialize_ag_func_oid_cache(); + } + + entry = hash_search(ag_func_oid_cache, &key, HASH_ENTER, &found); + if (!found) + { + entry->func_oid = func_oid; + } + + return entry->func_oid; +} + +static void initialize_ag_func_oid_cache(void) +{ + HASHCTL hash_ctl; + + if (ag_func_oid_cache != NULL) + { + return; + } + + if (!CacheMemoryContext) + { + CreateCacheMemoryContext(); + } + + MemSet(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(ag_func_cache_key); + hash_ctl.entrysize = sizeof(ag_func_cache_entry); + hash_ctl.hcxt = CacheMemoryContext; + + ag_func_oid_cache = hash_create("ag function oid cache", 32, &hash_ctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + pg_func_oid_cache = hash_create("pg function oid cache", 16, &hash_ctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + + if (!ag_func_oid_callback_registered) + { + CacheRegisterSyscacheCallback(PROCOID, invalidate_ag_func_oid_cache, + (Datum)0); + CacheRegisterSyscacheCallback(PROCNAMEARGSNSP, + invalidate_ag_func_oid_cache, + (Datum)0); + ag_func_oid_callback_registered = true; + } +} + +static void invalidate_ag_func_oid_cache(Datum arg, int cache_id, + uint32 hash_value) +{ + if (ag_func_oid_cache != NULL) + { + hash_destroy(ag_func_oid_cache); + ag_func_oid_cache = NULL; + } + + if (pg_func_oid_cache != NULL) + { + hash_destroy(pg_func_oid_cache); + pg_func_oid_cache = NULL; + } } Oid get_pg_func_oid(const char *func_name, const int nargs, ...) { - Oid oids[FUNC_MAX_ARGS]; + ag_func_cache_key key; + ag_func_cache_entry *entry; va_list ap; int i; oidvector *arg_types; Oid func_oid; + bool found; Assert(func_name); Assert(nargs >= 0 && nargs <= FUNC_MAX_ARGS); + initialize_ag_func_oid_cache(); + MemSet(&key, 0, sizeof(key)); + namestrcpy(&key.name, func_name); + key.nargs = nargs; + va_start(ap, nargs); for (i = 0; i < nargs; i++) - oids[i] = va_arg(ap, Oid); + key.args[i] = va_arg(ap, Oid); va_end(ap); - arg_types = buildoidvector(oids, nargs); + entry = hash_search(pg_func_oid_cache, &key, HASH_FIND, NULL); + if (entry != NULL) + { + return entry->func_oid; + } + + arg_types = buildoidvector(key.args, nargs); func_oid = GetSysCacheOid3(PROCNAMEARGSNSP, Anum_pg_proc_oid, CStringGetDatum(func_name), PointerGetDatum(arg_types), ObjectIdGetDatum(pg_catalog_namespace_id())); + pfree(arg_types); + if (!OidIsValid(func_oid)) { ereport(ERROR, (errmsg_internal("pg function does not exist"), errdetail_internal("%s(%d)", func_name, nargs))); } - return func_oid; + if (pg_func_oid_cache == NULL) + { + initialize_ag_func_oid_cache(); + } + + entry = hash_search(pg_func_oid_cache, &key, HASH_ENTER, &found); + if (!found) + { + entry->func_oid = func_oid; + } + + return entry->func_oid; } diff --git a/src/backend/utils/cache/ag_cache.c b/src/backend/utils/cache/ag_cache.c index 493ffcfa9..b205a0884 100644 --- a/src/backend/utils/cache/ag_cache.c +++ b/src/backend/utils/cache/ag_cache.c @@ -25,11 +25,15 @@ #include "utils/builtins.h" #include "utils/catcache.h" #include "utils/inval.h" +#include "utils/lsyscache.h" #include "catalog/ag_graph.h" #include "catalog/ag_label.h" #include "utils/ag_cache.h" +#define LAST_GRAPH_CACHE_SIZE 8 +#define LAST_LABEL_CACHE_SIZE 8 + typedef struct graph_name_cache_entry { NameData name; /* hash key */ @@ -87,6 +91,7 @@ typedef struct label_seq_name_graph_cache_entry /* ag_graph.name */ static HTAB *graph_name_cache_hash = NULL; static ScanKeyData graph_name_scan_keys[1]; +static uint64 graph_cache_generation = 1; /* ag_graph.namespace */ static HTAB *graph_namespace_cache_hash = NULL; @@ -95,6 +100,16 @@ static ScanKeyData graph_namespace_scan_keys[1]; /* ag_label.name, ag_label.graph */ static HTAB *label_name_graph_cache_hash = NULL; static ScanKeyData label_name_graph_scan_keys[2]; +static uint64 label_cache_generation = 1; +static uint64 label_seq_relation_cache_generation = 1; +static NameData label_seq_relation_cached_names[LAST_LABEL_CACHE_SIZE]; +static Oid label_seq_relation_cached_namespaces[LAST_LABEL_CACHE_SIZE] = + {InvalidOid}; +static uint64 label_seq_relation_cached_generations[LAST_LABEL_CACHE_SIZE] = + {0}; +static Oid label_seq_relation_cached_relations[LAST_LABEL_CACHE_SIZE] = + {InvalidOid}; +static int label_seq_relation_next_slot = 0; /* ag_label.graph, ag_label.id */ static HTAB *label_graph_oid_cache_hash = NULL; @@ -127,6 +142,7 @@ static void flush_graph_name_cache(void); static void flush_graph_namespace_cache(void); static graph_cache_data *search_graph_name_cache_miss(Name name); static graph_cache_data *search_graph_namespace_cache_miss(Oid namespace); +static void cache_graph_data_in_all_caches(const graph_cache_data *data); static void fill_graph_cache_data(graph_cache_data *cache_data, HeapTuple tuple, TupleDesc tuple_desc); @@ -138,14 +154,15 @@ static void create_label_graph_oid_cache(void); static void create_label_relation_cache(void); static void create_label_seq_name_graph_cache(void); static void invalidate_label_caches(Datum arg, Oid relid); -static void invalidate_label_name_graph_cache(Oid relid); +static bool invalidate_label_name_graph_cache(Oid relid); static void flush_label_name_graph_cache(void); -static void invalidate_label_graph_oid_cache(Oid relid); +static bool invalidate_label_graph_oid_cache(Oid relid); static void flush_label_graph_oid_cache(void); -static void invalidate_label_relation_cache(Oid relid); +static bool invalidate_label_relation_cache(Oid relid); static void flush_label_relation_cache(void); -static void invalidate_label_seq_name_graph_cache(Oid relid); +static bool invalidate_label_seq_name_graph_cache(Oid relid); static void flush_label_seq_name_graph_cache(void); +static bool invalidate_label_seq_relation_cache(Oid relid); static label_cache_data *search_label_name_graph_cache_miss(Name name, Oid graph); @@ -163,6 +180,7 @@ static void *label_seq_name_graph_cache_hash_search(Name name, Oid graph, HASHACTION action, bool *found); +static void cache_label_data_in_all_caches(const label_cache_data *data); static void fill_label_cache_data(label_cache_data *cache_data, HeapTuple tuple, TupleDesc tuple_desc); @@ -275,6 +293,8 @@ static void invalidate_graph_caches(Datum arg, int cache_id, uint32 hash_value) { Assert(graph_name_cache_hash); + graph_cache_generation++; + /* * Currently, all entries in the graph caches are flushed because * hash_value is for an entry in NAMESPACEOID cache. Since the caches @@ -316,6 +336,114 @@ static void flush_graph_namespace_cache(void) create_graph_namespace_cache(); } +uint64 get_graph_cache_generation(void) +{ + initialize_caches(); + + return graph_cache_generation; +} + +void invalidate_graph_cache(void) +{ + initialize_caches(); + + graph_cache_generation++; +} + +graph_cache_data *search_graph_name_cache_cached(const char *name) +{ + static NameData cached_names[LAST_GRAPH_CACHE_SIZE]; + static uint64 cached_generations[LAST_GRAPH_CACHE_SIZE] = {0}; + static graph_cache_data *cached_graphs[LAST_GRAPH_CACHE_SIZE] = {NULL}; + static bool cached_valid[LAST_GRAPH_CACHE_SIZE] = {false}; + static int next_slot = 0; + uint64 current_generation = get_graph_cache_generation(); + int slot; + int i; + + Assert(name); + + for (i = 0; i < LAST_GRAPH_CACHE_SIZE; i++) + { + if (cached_valid[i] && + cached_generations[i] == current_generation && + namestrcmp(&cached_names[i], name) == 0) + { + return cached_graphs[i]; + } + } + + slot = -1; + for (i = 0; i < LAST_GRAPH_CACHE_SIZE; i++) + { + if (!cached_valid[i] || + cached_generations[i] != current_generation) + { + slot = i; + break; + } + } + + if (slot < 0) + { + slot = next_slot; + next_slot = (next_slot + 1) % LAST_GRAPH_CACHE_SIZE; + } + + cached_graphs[slot] = search_graph_name_cache(name); + namestrcpy(&cached_names[slot], name); + cached_generations[slot] = current_generation; + cached_valid[slot] = true; + + return cached_graphs[slot]; +} + +graph_cache_data *search_graph_namespace_cache_cached(Oid namespace) +{ + static Oid cached_namespaces[LAST_GRAPH_CACHE_SIZE] = {InvalidOid}; + static uint64 cached_generations[LAST_GRAPH_CACHE_SIZE] = {0}; + static graph_cache_data *cached_graphs[LAST_GRAPH_CACHE_SIZE] = {NULL}; + static bool cached_valid[LAST_GRAPH_CACHE_SIZE] = {false}; + static int next_slot = 0; + uint64 current_generation = get_graph_cache_generation(); + int slot; + int i; + + for (i = 0; i < LAST_GRAPH_CACHE_SIZE; i++) + { + if (cached_valid[i] && + cached_generations[i] == current_generation && + cached_namespaces[i] == namespace) + { + return cached_graphs[i]; + } + } + + slot = -1; + for (i = 0; i < LAST_GRAPH_CACHE_SIZE; i++) + { + if (!cached_valid[i] || + cached_generations[i] != current_generation) + { + slot = i; + break; + } + } + + if (slot < 0) + { + slot = next_slot; + next_slot = (next_slot + 1) % LAST_GRAPH_CACHE_SIZE; + } + + cached_graphs[slot] = search_graph_namespace_cache(namespace); + cached_namespaces[slot] = namespace; + cached_generations[slot] = current_generation; + cached_valid[slot] = true; + + return cached_graphs[slot]; +} + graph_cache_data *search_graph_name_cache(const char *name) { NameData name_key; @@ -372,6 +500,7 @@ static graph_cache_data *search_graph_name_cache_miss(Name name) /* fill the new entry with the retrieved tuple */ fill_graph_cache_data(&entry->data, tuple, RelationGetDescr(ag_graph)); + cache_graph_data_in_all_caches(&entry->data); systable_endscan(scan_desc); table_close(ag_graph, AccessShareLock); @@ -435,6 +564,7 @@ static graph_cache_data *search_graph_namespace_cache_miss(Oid namespace) /* fill the new entry with the retrieved tuple */ fill_graph_cache_data(&entry->data, tuple, RelationGetDescr(ag_graph)); + cache_graph_data_in_all_caches(&entry->data); systable_endscan(scan_desc); table_close(ag_graph, AccessShareLock); @@ -442,6 +572,27 @@ static graph_cache_data *search_graph_namespace_cache_miss(Oid namespace) return &entry->data; } +static void cache_graph_data_in_all_caches(const graph_cache_data *data) +{ + graph_name_cache_entry *name_entry; + graph_namespace_cache_entry *namespace_entry; + bool found; + + name_entry = hash_search(graph_name_cache_hash, &data->name, HASH_ENTER, + &found); + if (!found) + { + name_entry->data = *data; + } + + namespace_entry = hash_search(graph_namespace_cache_hash, &data->namespace, + HASH_ENTER, &found); + if (!found) + { + namespace_entry->data = *data; + } +} + static void fill_graph_cache_data(graph_cache_data *cache_data, HeapTuple tuple, TupleDesc tuple_desc) { @@ -471,29 +622,17 @@ static void initialize_label_caches(void) ag_cache_scan_key_init(&label_name_graph_scan_keys[0], Anum_ag_label_name, F_NAMEEQ); ag_cache_scan_key_init(&label_name_graph_scan_keys[1], Anum_ag_label_graph, - F_INT4EQ); + F_OIDEQ); /* ag_label.graph, ag_label.id */ ag_cache_scan_key_init(&label_graph_oid_scan_keys[0], Anum_ag_label_graph, - F_INT4EQ); + F_OIDEQ); ag_cache_scan_key_init(&label_graph_oid_scan_keys[1], Anum_ag_label_id, F_INT4EQ); /* ag_label.relation */ ag_cache_scan_key_init(&label_relation_scan_keys[0], Anum_ag_label_relation, F_OIDEQ); - - /* ag_label.seq_name, ag_label.graph */ - ag_cache_scan_key_init(&label_seq_name_graph_scan_keys[0], Anum_ag_label_seq_name, - F_NAMEEQ); - ag_cache_scan_key_init(&label_seq_name_graph_scan_keys[1], Anum_ag_label_graph, - F_OIDEQ); - - /* ag_label.seq_name, ag_label.graph */ - ag_cache_scan_key_init(&label_seq_name_graph_scan_keys[0], - Anum_ag_label_seq_name, F_NAMEEQ); - ag_cache_scan_key_init(&label_seq_name_graph_scan_keys[1], - Anum_ag_label_graph, F_OIDEQ); /* ag_label.seq_name, ag_label.graph */ ag_cache_scan_key_init(&label_seq_name_graph_scan_keys[0], @@ -594,16 +733,30 @@ static void invalidate_label_caches(Datum arg, Oid relid) Assert(label_name_graph_cache_hash); Assert(label_seq_name_graph_cache_hash); - if (OidIsValid(relid)) { - invalidate_label_name_graph_cache(relid); - invalidate_label_graph_oid_cache(relid); - invalidate_label_relation_cache(relid); - invalidate_label_seq_name_graph_cache(relid); + bool changed = false; + bool seq_relation_changed; + + changed |= invalidate_label_name_graph_cache(relid); + changed |= invalidate_label_graph_oid_cache(relid); + changed |= invalidate_label_relation_cache(relid); + changed |= invalidate_label_seq_name_graph_cache(relid); + seq_relation_changed = invalidate_label_seq_relation_cache(relid); + + if (changed) + { + label_cache_generation++; + } + if (changed || seq_relation_changed) + { + label_seq_relation_cache_generation++; + } } else { + label_cache_generation++; + label_seq_relation_cache_generation++; flush_label_name_graph_cache(); flush_label_graph_oid_cache(); flush_label_relation_cache(); @@ -611,9 +764,10 @@ static void invalidate_label_caches(Datum arg, Oid relid) } } -static void invalidate_label_name_graph_cache(Oid relid) +static bool invalidate_label_name_graph_cache(Oid relid) { HASH_SEQ_STATUS hash_seq; + bool changed = false; hash_seq_init(&hash_seq, label_name_graph_cache_hash); for (;;) @@ -640,8 +794,11 @@ static void invalidate_label_name_graph_cache(Oid relid) (errmsg_internal("label (name, graph) cache corrupted"))); } + changed = true; break; } + + return changed; } static void flush_label_name_graph_cache(void) @@ -660,9 +817,10 @@ static void flush_label_name_graph_cache(void) create_label_name_graph_cache(); } -static void invalidate_label_graph_oid_cache(Oid relid) +static bool invalidate_label_graph_oid_cache(Oid relid) { HASH_SEQ_STATUS hash_seq; + bool changed = false; hash_seq_init(&hash_seq, label_graph_oid_cache_hash); for (;;) @@ -689,8 +847,11 @@ static void invalidate_label_graph_oid_cache(Oid relid) (errmsg_internal("label (graph, id) cache corrupted"))); } + changed = true; break; } + + return changed; } static void flush_label_graph_oid_cache(void) @@ -709,7 +870,7 @@ static void flush_label_graph_oid_cache(void) create_label_graph_oid_cache(); } -static void invalidate_label_relation_cache(Oid relid) +static bool invalidate_label_relation_cache(Oid relid) { label_relation_cache_entry *entry; void *removed; @@ -717,7 +878,7 @@ static void invalidate_label_relation_cache(Oid relid) entry = hash_search(label_relation_cache_hash, &relid, HASH_FIND, NULL); if (!entry) { - return; + return false; } removed = hash_search(label_relation_cache_hash, &relid, HASH_REMOVE, NULL); @@ -725,6 +886,8 @@ static void invalidate_label_relation_cache(Oid relid) { ereport(ERROR, (errmsg_internal("label (namespace) cache corrupted"))); } + + return true; } static void flush_label_relation_cache(void) @@ -743,9 +906,10 @@ static void flush_label_relation_cache(void) create_label_relation_cache(); } -static void invalidate_label_seq_name_graph_cache(Oid relid) +static bool invalidate_label_seq_name_graph_cache(Oid relid) { HASH_SEQ_STATUS hash_seq; + bool changed = false; hash_seq_init(&hash_seq, label_seq_name_graph_cache_hash); for (;;) @@ -772,8 +936,11 @@ static void invalidate_label_seq_name_graph_cache(Oid relid) (errmsg_internal("label (seq_name, graph) cache corrupted"))); } + changed = true; break; } + + return changed; } static void flush_label_seq_name_graph_cache(void) @@ -792,6 +959,145 @@ static void flush_label_seq_name_graph_cache(void) create_label_seq_name_graph_cache(); } +static bool invalidate_label_seq_relation_cache(Oid relid) +{ + bool changed = false; + int i; + + for (i = 0; i < LAST_LABEL_CACHE_SIZE; i++) + { + if (label_seq_relation_cached_relations[i] == relid) + { + label_seq_relation_cached_relations[i] = InvalidOid; + label_seq_relation_cached_namespaces[i] = InvalidOid; + label_seq_relation_cached_generations[i] = 0; + changed = true; + } + } + + return changed; +} + +uint64 get_label_cache_generation(void) +{ + initialize_caches(); + + return label_cache_generation; +} + +void invalidate_label_cache(void) +{ + initialize_caches(); + + label_cache_generation++; + label_seq_relation_cache_generation++; +} + +label_cache_data *search_label_graph_oid_cache_cached(Oid graph, int32 id) +{ + static Oid cached_graphs[LAST_LABEL_CACHE_SIZE] = {InvalidOid}; + static int32 cached_ids[LAST_LABEL_CACHE_SIZE] = {-1, -1, -1, -1}; + static uint64 cached_generations[LAST_LABEL_CACHE_SIZE] = {0}; + static label_cache_data *cached_labels[LAST_LABEL_CACHE_SIZE] = {NULL}; + static bool cached_valid[LAST_LABEL_CACHE_SIZE] = {false}; + static int next_slot = 0; + uint64 current_generation = get_label_cache_generation(); + label_cache_data *label; + int slot; + int i; + + for (i = 0; i < LAST_LABEL_CACHE_SIZE; i++) + { + if (cached_valid[i] && + cached_graphs[i] == graph && + cached_ids[i] == id && + cached_generations[i] == current_generation) + { + return cached_labels[i]; + } + } + + label = search_label_graph_oid_cache(graph, id); + slot = -1; + for (i = 0; i < LAST_LABEL_CACHE_SIZE; i++) + { + if (!cached_valid[i] || + cached_generations[i] != current_generation) + { + slot = i; + break; + } + } + + if (slot < 0) + { + slot = next_slot; + next_slot = (next_slot + 1) % LAST_LABEL_CACHE_SIZE; + } + + cached_graphs[slot] = graph; + cached_ids[slot] = id; + cached_generations[slot] = current_generation; + cached_labels[slot] = label; + cached_valid[slot] = true; + + return label; +} + +label_cache_data *search_label_name_graph_cache_cached(const char *name, + Oid graph) +{ + static NameData cached_names[LAST_LABEL_CACHE_SIZE]; + static Oid cached_graphs[LAST_LABEL_CACHE_SIZE] = {InvalidOid}; + static uint64 cached_generations[LAST_LABEL_CACHE_SIZE] = {0}; + static label_cache_data *cached_labels[LAST_LABEL_CACHE_SIZE] = {NULL}; + static bool cached_valid[LAST_LABEL_CACHE_SIZE] = {false}; + static int next_slot = 0; + uint64 current_generation = get_label_cache_generation(); + label_cache_data *label; + int slot; + int i; + + Assert(name); + + for (i = 0; i < LAST_LABEL_CACHE_SIZE; i++) + { + if (cached_valid[i] && + cached_graphs[i] == graph && + cached_generations[i] == current_generation && + namestrcmp(&cached_names[i], name) == 0) + { + return cached_labels[i]; + } + } + + label = search_label_name_graph_cache(name, graph); + slot = -1; + for (i = 0; i < LAST_LABEL_CACHE_SIZE; i++) + { + if (!cached_valid[i] || + cached_generations[i] != current_generation) + { + slot = i; + break; + } + } + + if (slot < 0) + { + slot = next_slot; + next_slot = (next_slot + 1) % LAST_LABEL_CACHE_SIZE; + } + + namestrcpy(&cached_names[slot], name); + cached_graphs[slot] = graph; + cached_generations[slot] = current_generation; + cached_labels[slot] = label; + cached_valid[slot] = true; + + return label; +} + label_cache_data *search_label_name_graph_cache(const char *name, Oid graph) { NameData name_key; @@ -855,6 +1161,7 @@ static label_cache_data *search_label_name_graph_cache_miss(Name name, /* fill the new entry with the retrieved tuple */ fill_label_cache_data(&entry->data, tuple, RelationGetDescr(ag_label)); + cache_label_data_in_all_caches(&entry->data); systable_endscan(scan_desc); table_close(ag_label, AccessShareLock); @@ -868,7 +1175,7 @@ static void *label_name_graph_cache_hash_search(Name name, Oid graph, label_name_graph_cache_key key; /* initialize the hash key for label_name_graph_cache_hash */ - namestrcpy(&key.name, name->data); + key.name = *name; key.graph = graph; return hash_search(label_name_graph_cache_hash, &key, action, found); @@ -932,6 +1239,7 @@ static label_cache_data *search_label_graph_oid_cache_miss(Oid graph, uint32 id) /* fill the new entry with the retrieved tuple */ fill_label_cache_data(&entry->data, tuple, RelationGetDescr(ag_label)); + cache_label_data_in_all_caches(&entry->data); systable_endscan(scan_desc); table_close(ag_label, AccessShareLock); @@ -965,6 +1273,54 @@ label_cache_data *search_label_relation_cache(Oid relation) return search_label_relation_cache_miss(relation); } +label_cache_data *search_label_relation_cache_cached(Oid relation) +{ + static Oid cached_relations[LAST_LABEL_CACHE_SIZE] = {InvalidOid}; + static uint64 cached_generations[LAST_LABEL_CACHE_SIZE] = {0}; + static label_cache_data *cached_labels[LAST_LABEL_CACHE_SIZE] = {NULL}; + static bool cached_valid[LAST_LABEL_CACHE_SIZE] = {false}; + static int next_slot = 0; + uint64 current_generation = get_label_cache_generation(); + label_cache_data *label; + int slot; + int i; + + for (i = 0; i < LAST_LABEL_CACHE_SIZE; i++) + { + if (cached_valid[i] && + cached_relations[i] == relation && + cached_generations[i] == current_generation) + { + return cached_labels[i]; + } + } + + label = search_label_relation_cache(relation); + slot = -1; + for (i = 0; i < LAST_LABEL_CACHE_SIZE; i++) + { + if (!cached_valid[i] || + cached_generations[i] != current_generation) + { + slot = i; + break; + } + } + + if (slot < 0) + { + slot = next_slot; + next_slot = (next_slot + 1) % LAST_LABEL_CACHE_SIZE; + } + + cached_relations[slot] = relation; + cached_generations[slot] = current_generation; + cached_labels[slot] = label; + cached_valid[slot] = true; + + return label; +} + static label_cache_data *search_label_relation_cache_miss(Oid relation) { ScanKeyData scan_keys[1]; @@ -972,7 +1328,7 @@ static label_cache_data *search_label_relation_cache_miss(Oid relation) SysScanDesc scan_desc; HeapTuple tuple; bool found; - label_cache_data *entry; + label_relation_cache_entry *entry; memcpy(scan_keys, label_relation_scan_keys, sizeof(label_relation_scan_keys)); @@ -1004,12 +1360,13 @@ static label_cache_data *search_label_relation_cache_miss(Oid relation) Assert(!found); /* no concurrent update on label_relation_cache_hash */ /* fill the new entry with the retrieved tuple */ - fill_label_cache_data(entry, tuple, RelationGetDescr(ag_label)); + fill_label_cache_data(&entry->data, tuple, RelationGetDescr(ag_label)); + cache_label_data_in_all_caches(&entry->data); systable_endscan(scan_desc); table_close(ag_label, AccessShareLock); - return entry; + return &entry->data; } label_cache_data *search_label_seq_name_graph_cache(const char *name, Oid graph) @@ -1032,6 +1389,87 @@ label_cache_data *search_label_seq_name_graph_cache(const char *name, Oid graph) return search_label_seq_name_graph_cache_miss(&name_key, graph); } +const char *get_label_cache_relation_name(label_cache_data *label_cache) +{ + char *relname; + + Assert(label_cache != NULL); + + if (label_cache->relation_name.data[0] != '\0') + return NameStr(label_cache->relation_name); + + relname = get_rel_name(label_cache->relation); + if (relname == NULL) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("label relation %u does not exist", + label_cache->relation))); + } + + namestrcpy(&label_cache->relation_name, relname); + pfree(relname); + + return NameStr(label_cache->relation_name); +} + +Oid get_label_seq_relation_cached(const label_cache_data *label_cache, + Oid namespace) +{ + uint64 current_generation = label_seq_relation_cache_generation; + const char *seq_name; + Oid seq_relid; + int slot; + int i; + + Assert(label_cache != NULL); + Assert(OidIsValid(namespace)); + + seq_name = NameStr(label_cache->seq_name); + + for (i = 0; i < LAST_LABEL_CACHE_SIZE; i++) + { + if (OidIsValid(label_seq_relation_cached_relations[i]) && + label_seq_relation_cached_namespaces[i] == namespace && + label_seq_relation_cached_generations[i] == current_generation && + namestrcmp(&label_seq_relation_cached_names[i], seq_name) == 0) + { + return label_seq_relation_cached_relations[i]; + } + } + + seq_relid = get_relname_relid(seq_name, namespace); + if (!OidIsValid(seq_relid)) + { + return InvalidOid; + } + + slot = -1; + for (i = 0; i < LAST_LABEL_CACHE_SIZE; i++) + { + if (!OidIsValid(label_seq_relation_cached_relations[i]) || + label_seq_relation_cached_generations[i] != current_generation) + { + slot = i; + break; + } + } + + if (slot < 0) + { + slot = label_seq_relation_next_slot; + label_seq_relation_next_slot = + (label_seq_relation_next_slot + 1) % LAST_LABEL_CACHE_SIZE; + } + + namestrcpy(&label_seq_relation_cached_names[slot], seq_name); + label_seq_relation_cached_namespaces[slot] = namespace; + label_seq_relation_cached_generations[slot] = current_generation; + label_seq_relation_cached_relations[slot] = seq_relid; + + return seq_relid; +} + static label_cache_data *search_label_seq_name_graph_cache_miss(Name name, Oid graph) { @@ -1076,6 +1514,7 @@ static label_cache_data *search_label_seq_name_graph_cache_miss(Name name, /* fill the new entry with the retrieved tuple */ fill_label_cache_data(&entry->data, tuple, RelationGetDescr(ag_label)); + cache_label_data_in_all_caches(&entry->data); systable_endscan(scan_desc); table_close(ag_label, AccessShareLock); @@ -1090,12 +1529,51 @@ static void *label_seq_name_graph_cache_hash_search(Name name, Oid graph, label_seq_name_graph_cache_key key; /* initialize the hash key for label_seq_name_graph_cache_hash */ - namestrcpy(&key.name, name->data); + key.name = *name; key.graph = graph; return hash_search(label_seq_name_graph_cache_hash, &key, action, found); } +static void cache_label_data_in_all_caches(const label_cache_data *data) +{ + label_name_graph_cache_entry *name_entry; + label_graph_oid_cache_entry *id_entry; + label_relation_cache_entry *relation_entry; + label_seq_name_graph_cache_entry *seq_entry; + bool found; + + name_entry = label_name_graph_cache_hash_search((Name)&data->name, + data->graph, HASH_ENTER, + &found); + if (!found) + { + name_entry->data = *data; + } + + id_entry = label_graph_oid_cache_hash_search(data->graph, data->id, + HASH_ENTER, &found); + if (!found) + { + id_entry->data = *data; + } + + relation_entry = hash_search(label_relation_cache_hash, &data->relation, + HASH_ENTER, &found); + if (!found) + { + relation_entry->data = *data; + } + + seq_entry = label_seq_name_graph_cache_hash_search((Name)&data->seq_name, + data->graph, HASH_ENTER, + &found); + if (!found) + { + seq_entry->data = *data; + } +} + static void fill_label_cache_data(label_cache_data *cache_data, HeapTuple tuple, TupleDesc tuple_desc) { @@ -1125,6 +1603,7 @@ static void fill_label_cache_data(label_cache_data *cache_data, value = heap_getattr(tuple, Anum_ag_label_relation, tuple_desc, &is_null); Assert(!is_null); cache_data->relation = DatumGetObjectId(value); + cache_data->relation_name.data[0] = '\0'; /* ag_label.seq_name */ value = heap_getattr(tuple, Anum_ag_label_seq_name, tuple_desc, &is_null); Assert(!is_null); diff --git a/src/backend/utils/graph_generation.c b/src/backend/utils/graph_generation.c index ea8e1bd5b..bebe62902 100644 --- a/src/backend/utils/graph_generation.c +++ b/src/backend/utils/graph_generation.c @@ -20,12 +20,23 @@ #include "postgres.h" #include "access/genam.h" +#include "access/table.h" #include "commands/graph_commands.h" +#include "nodes/makefuncs.h" #include "utils/load/age_load.h" int64 get_nextval_internal(graph_cache_data* graph_cache, label_cache_data* label_cache); +static void queue_vertex_insert(batch_insert_state *batch_state, + graphid vertex_id, + agtype *vertex_properties); +static void queue_edge_insert(batch_insert_state *batch_state, + graphid edge_id, graphid start_id, + graphid end_id, agtype *edge_properties); +static void create_complete_graph_internal(Name graph_name, int64 no_vertices, + Name edge_label_name, + Name vtx_label_name); /* * Auxiliary function to get the next internal value in the graph, * so a new object (node or edge) graph id can be composed. @@ -35,31 +46,87 @@ int64 get_nextval_internal(graph_cache_data* graph_cache, label_cache_data* label_cache) { Oid obj_seq_id; - char* label_seq_name_str; - label_seq_name_str = NameStr(label_cache->seq_name); - obj_seq_id = get_relname_relid(label_seq_name_str, - graph_cache->namespace); + obj_seq_id = get_label_seq_relation_cached(label_cache, + graph_cache->namespace); return nextval_internal(obj_seq_id, true); } +static void queue_vertex_insert(batch_insert_state *batch_state, + graphid vertex_id, + agtype *vertex_properties) +{ + TupleTableSlot *slot; + + slot = batch_state->slots[batch_state->num_tuples]; + ExecClearTuple(slot); + + slot->tts_values[0] = GRAPHID_GET_DATUM(vertex_id); + slot->tts_values[1] = AGTYPE_P_GET_DATUM(vertex_properties); + slot->tts_isnull[0] = false; + slot->tts_isnull[1] = false; + + ExecStoreVirtualTuple(slot); + + batch_state->buffered_bytes += VARSIZE(vertex_properties); + batch_state->num_tuples++; + + if (batch_state->num_tuples >= BATCH_SIZE || + batch_state->buffered_bytes >= MAX_BUFFERED_BYTES) + { + insert_batch(batch_state); + batch_state->num_tuples = 0; + batch_state->buffered_bytes = 0; + } +} + +static void queue_edge_insert(batch_insert_state *batch_state, + graphid edge_id, graphid start_id, + graphid end_id, agtype *edge_properties) +{ + TupleTableSlot *slot; + + slot = batch_state->slots[batch_state->num_tuples]; + ExecClearTuple(slot); + + slot->tts_values[0] = GRAPHID_GET_DATUM(edge_id); + slot->tts_values[1] = GRAPHID_GET_DATUM(start_id); + slot->tts_values[2] = GRAPHID_GET_DATUM(end_id); + slot->tts_values[3] = AGTYPE_P_GET_DATUM(edge_properties); + slot->tts_isnull[0] = false; + slot->tts_isnull[1] = false; + slot->tts_isnull[2] = false; + slot->tts_isnull[3] = false; + + ExecStoreVirtualTuple(slot); + + batch_state->buffered_bytes += VARSIZE(edge_properties); + batch_state->num_tuples++; + + if (batch_state->num_tuples >= BATCH_SIZE || + batch_state->buffered_bytes >= MAX_BUFFERED_BYTES) + { + insert_batch(batch_state); + batch_state->num_tuples = 0; + batch_state->buffered_bytes = 0; + } +} + PG_FUNCTION_INFO_V1(create_complete_graph); /* * SELECT * FROM ag_catalog.create_complete_graph('graph_name',no_of_nodes, 'edge_label', 'node_label'=NULL); */ -Datum create_complete_graph(PG_FUNCTION_ARGS) +static void create_complete_graph_internal(Name graph_name, int64 no_vertices, + Name edge_label_name, + Name vtx_label_name) { Oid graph_oid; - Name graph_name; - int64 no_vertices; int64 i,j,vid = 1, eid, start_vid, end_vid; - Name vtx_label_name = NULL; - Name edge_label_name; int32 vtx_label_id; int32 edge_label_id; @@ -78,45 +145,20 @@ Datum create_complete_graph(PG_FUNCTION_ARGS) graph_cache_data *graph_cache; label_cache_data *vertex_cache; label_cache_data *edge_cache; + batch_insert_state *vtx_batch_state = NULL; + batch_insert_state *edge_batch_state = NULL; + Relation vtx_rel; + Relation edge_rel; Oid nsp_id; - Name vtx_seq_name; - char *vtx_seq_name_str; - - Name edge_seq_name; - char *edge_seq_name_str; - int64 lid; - if (PG_ARGISNULL(0)) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("graph name can not be NULL"))); - } - - if (PG_ARGISNULL(1)) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("number of nodes can not be NULL"))); - } - - if (PG_ARGISNULL(2)) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("edge label can not be NULL"))); - } - - graph_name = PG_GETARG_NAME(0); - no_vertices = (int64) PG_GETARG_INT64(1); - edge_label_name = PG_GETARG_NAME(2); - graph_name_str = NameStr(*graph_name); vtx_name_str = AG_DEFAULT_LABEL_VERTEX; edge_name_str = NameStr(*edge_label_name); - if (!PG_ARGISNULL(3)) + if (vtx_label_name != NULL) { - vtx_label_name = PG_GETARG_NAME(3); vtx_name_str = NameStr(*vtx_label_name); /* Check if vertex and edge label are same */ @@ -127,60 +169,94 @@ Datum create_complete_graph(PG_FUNCTION_ARGS) } } - if (!graph_exists(graph_name_str)) + graph_cache = search_graph_name_cache_cached(graph_name_str); + if (graph_cache == NULL) { - DirectFunctionCall1(create_graph, CStringGetDatum(graph_name->data)); - } + Oid created_graph_oid = create_graph_internal(graph_name); - graph_oid = get_graph_oid(graph_name_str); + graph_cache = search_graph_namespace_cache_cached(created_graph_oid); + if (graph_cache == NULL) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("graph \"%s\" was not found after creation", + graph_name_str))); + } + ereport(NOTICE, + (errmsg("graph \"%s\" has been created", graph_name_str))); + } - if (!PG_ARGISNULL(3)) + graph_oid = graph_cache->oid; + vertex_cache = search_label_name_graph_cache_cached(vtx_name_str, + graph_oid); + if (vertex_cache == NULL) { - /* Check if label with the input name already exists */ - if (!label_exists(vtx_name_str, graph_oid)) + List *parent = list_make1(makeRangeVar( + graph_name_str, pstrdup(AG_DEFAULT_LABEL_VERTEX), -1)); + Oid label_relid = create_label_with_graph_oid( + graph_name_str, graph_oid, vtx_name_str, LABEL_TYPE_VERTEX, + parent); + + vertex_cache = search_label_relation_cache_cached(label_relid); + if (vertex_cache == NULL) { - DirectFunctionCall2(create_vlabel, - CStringGetDatum(graph_name->data), - CStringGetDatum(vtx_label_name->data)); + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("vertex label \"%s\" was not found after creation", + vtx_name_str))); } + ereport(NOTICE, + (errmsg("VLabel \"%s\" has been created", vtx_name_str))); } - if (!label_exists(edge_name_str, graph_oid)) + edge_cache = search_label_name_graph_cache_cached(edge_name_str, graph_oid); + if (edge_cache == NULL) { - DirectFunctionCall2(create_elabel, - CStringGetDatum(graph_name->data), - CStringGetDatum(edge_label_name->data)); + List *parent = list_make1(makeRangeVar( + graph_name_str, pstrdup(AG_DEFAULT_LABEL_EDGE), -1)); + Oid label_relid = create_label_with_graph_oid( + graph_name_str, graph_oid, edge_name_str, LABEL_TYPE_EDGE, + parent); + + edge_cache = search_label_relation_cache_cached(label_relid); + if (edge_cache == NULL) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("edge label \"%s\" was not found after creation", + edge_name_str))); + } + ereport(NOTICE, + (errmsg("ELabel \"%s\" has been created", edge_name_str))); } - vtx_label_id = get_label_id(vtx_name_str, graph_oid); - edge_label_id = get_label_id(edge_name_str, graph_oid); - - graph_cache = search_graph_name_cache(graph_name_str); - vertex_cache = search_label_name_graph_cache(vtx_name_str, graph_oid); - edge_cache = search_label_name_graph_cache(edge_name_str, graph_oid); + vtx_label_id = vertex_cache->id; + edge_label_id = edge_cache->id; nsp_id = graph_cache->namespace; - vtx_seq_name = &(vertex_cache->seq_name); - vtx_seq_name_str = NameStr(*vtx_seq_name); - - edge_seq_name = &(edge_cache->seq_name); - edge_seq_name_str = NameStr(*edge_seq_name); - - vtx_seq_id = get_relname_relid(vtx_seq_name_str, nsp_id); - edge_seq_id = get_relname_relid(edge_seq_name_str, nsp_id); + vtx_seq_id = get_label_seq_relation_cached(vertex_cache, nsp_id); + edge_seq_id = get_label_seq_relation_cached(edge_cache, nsp_id); props = create_empty_agtype(); + vtx_rel = table_open(vertex_cache->relation, RowExclusiveLock); + init_batch_insert(&vtx_batch_state, vertex_cache->relation); + /* Creating vertices*/ for (i=(int64)1; i<=no_vertices; i++) { vid = nextval_internal(vtx_seq_id, true); object_graph_id = make_graphid(vtx_label_id, vid); - insert_vertex_simple(graph_oid, vtx_name_str, object_graph_id, props); + queue_vertex_insert(vtx_batch_state, object_graph_id, props); } + finish_batch_insert(&vtx_batch_state); + table_close(vtx_rel, RowExclusiveLock); lid = vid; + edge_rel = table_open(edge_cache->relation, RowExclusiveLock); + init_batch_insert(&edge_batch_state, edge_cache->relation); + /* Creating edges*/ for (i = 1; i<=no_vertices-1; i++) { @@ -194,11 +270,51 @@ Datum create_complete_graph(PG_FUNCTION_ARGS) start_vertex_graph_id = make_graphid(vtx_label_id, start_vid); end_vertex_graph_id = make_graphid(vtx_label_id, end_vid); - insert_edge_simple(graph_oid, edge_name_str, object_graph_id, - start_vertex_graph_id, end_vertex_graph_id, - props); + queue_edge_insert(edge_batch_state, object_graph_id, + start_vertex_graph_id, end_vertex_graph_id, + props); } } + finish_batch_insert(&edge_batch_state); + table_close(edge_rel, RowExclusiveLock); +} + +Datum create_complete_graph(PG_FUNCTION_ARGS) +{ + Name graph_name; + Name edge_label_name; + Name vtx_label_name = NULL; + int64 no_vertices; + + if (PG_ARGISNULL(0)) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("graph name can not be NULL"))); + } + + if (PG_ARGISNULL(1)) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("number of nodes can not be NULL"))); + } + + if (PG_ARGISNULL(2)) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("edge label can not be NULL"))); + } + + graph_name = PG_GETARG_NAME(0); + no_vertices = (int64) PG_GETARG_INT64(1); + edge_label_name = PG_GETARG_NAME(2); + + if (!PG_ARGISNULL(3)) + { + vtx_label_name = PG_GETARG_NAME(3); + } + + create_complete_graph_internal(graph_name, no_vertices, edge_label_name, + vtx_label_name); PG_RETURN_VOID(); } @@ -230,11 +346,11 @@ PG_FUNCTION_INFO_V1(age_create_barbell_graph); Datum age_create_barbell_graph(PG_FUNCTION_ARGS) { - FunctionCallInfo arguments; Oid graph_oid; Name graph_name; char* graph_name_str; + int64 graph_size; int64 start_node_index, end_node_index, nextval; Name node_label_name = NULL; @@ -250,12 +366,11 @@ Datum age_create_barbell_graph(PG_FUNCTION_ARGS) graphid end_node_graph_id; graph_cache_data* graph_cache; + label_cache_data* node_cache; label_cache_data* edge_cache; agtype* properties = NULL; - arguments = fcinfo; - /* Checking for possible NULL arguments */ /* Name graph_name */ if (PG_ARGISNULL(0)) @@ -268,11 +383,12 @@ Datum age_create_barbell_graph(PG_FUNCTION_ARGS) graph_name_str = NameStr(*graph_name); /* int graph size (number of nodes in each complete graph) */ - if (PG_ARGISNULL(1) && PG_GETARG_INT32(1) < 3) + if (PG_ARGISNULL(1) || PG_GETARG_INT32(1) < 3) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Graph size cannot be NULL or lower than 3"))); } + graph_size = (int64) PG_GETARG_INT32(1); /* * int64 bridge_size: currently only stays at zero. @@ -287,13 +403,13 @@ Datum age_create_barbell_graph(PG_FUNCTION_ARGS) /* node label: if null, gets default label, which is "_ag_label_vertex" */ if (PG_ARGISNULL(3)) { - namestrcpy(node_label_name, AG_DEFAULT_LABEL_VERTEX); + node_label_str = AG_DEFAULT_LABEL_VERTEX; } else { node_label_name = PG_GETARG_NAME(3); + node_label_str = NameStr(*node_label_name); } - node_label_str = NameStr(*node_label_name); /* Name edge_label */ if (PG_ARGISNULL(5)) @@ -307,31 +423,51 @@ Datum age_create_barbell_graph(PG_FUNCTION_ARGS) /* create two separate complete graphs */ - DirectFunctionCall4(create_complete_graph, arguments->args[0].value, - arguments->args[1].value, - arguments->args[5].value, - arguments->args[3].value); - DirectFunctionCall4(create_complete_graph, arguments->args[0].value, - arguments->args[1].value, - arguments->args[5].value, - arguments->args[3].value); - - graph_oid = get_graph_oid(graph_name_str); - node_label_id = get_label_id(node_label_str, graph_oid); - edge_label_id = get_label_id(edge_label_str, graph_oid); + create_complete_graph_internal(graph_name, graph_size, edge_label_name, + node_label_name); + create_complete_graph_internal(graph_name, graph_size, edge_label_name, + node_label_name); /* * Fetching caches to get next values for graph id's, and access nodes * to be connected with edges. */ - graph_cache = search_graph_name_cache(graph_name_str); - edge_cache = search_label_name_graph_cache(edge_label_str,graph_oid); + graph_cache = search_graph_name_cache_cached(graph_name_str); + if (graph_cache == NULL) + { + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("graph \"%s\" does not exist", + graph_name_str))); + } + + graph_oid = graph_cache->oid; + node_cache = search_label_name_graph_cache_cached(node_label_str, + graph_oid); + if (node_cache == NULL) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("vertex label \"%s\" does not exist", + node_label_str))); + } + + edge_cache = search_label_name_graph_cache_cached(edge_label_str, + graph_oid); + if (edge_cache == NULL) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("edge label \"%s\" does not exist", edge_label_str))); + } + + node_label_id = node_cache->id; + edge_label_id = edge_cache->id; /* connect a node from each graph */ /* first created node, from the first complete graph */ start_node_index = 1; /* last created node, second graph */ - end_node_index = arguments->args[1].value*2; + end_node_index = graph_size * 2; /* next index to be assigned to a node or edge */ nextval = get_nextval_internal(graph_cache, edge_cache); diff --git a/src/backend/utils/load/ag_load_edges.c b/src/backend/utils/load/ag_load_edges.c index c05bf3352..f51de4995 100644 --- a/src/backend/utils/load/ag_load_edges.c +++ b/src/backend/utils/load/ag_load_edges.c @@ -29,6 +29,28 @@ #include "utils/rel.h" #include "utils/load/ag_load_edges.h" +#include "utils/ag_cache.h" + +typedef struct vertex_label_id_cache_entry +{ + NameData name; + int32 id; +} vertex_label_id_cache_entry; + +typedef struct vertex_label_id_cache +{ + HTAB *entries; + NameData last_name; + int32 last_id; + bool last_valid; +} vertex_label_id_cache; + +static vertex_label_id_cache *create_vertex_label_id_cache(void); +static void destroy_vertex_label_id_cache(vertex_label_id_cache *cache); +static int32 get_cached_vertex_label_id(vertex_label_id_cache *cache, + const char *label_name, + Oid graph_oid); +static void trim_field_to_name(Name name, const char *str); /* * Process a single edge row from COPY's raw fields. @@ -38,7 +60,8 @@ static void process_edge_row(char **fields, int nfields, char **header, int header_count, int label_id, Oid label_seq_relid, Oid graph_oid, bool load_as_agtype, - batch_insert_state *batch_state) + batch_insert_state *batch_state, + vertex_label_id_cache *vertex_label_id_cache) { int64 start_id_int; graphid start_vertex_graph_id; @@ -52,8 +75,8 @@ static void process_edge_row(char **fields, int nfields, int64 entry_id; TupleTableSlot *slot; - char *start_vertex_type; - char *end_vertex_type; + NameData start_vertex_type; + NameData end_vertex_type; agtype *edge_properties; /* Generate edge ID */ @@ -61,16 +84,20 @@ static void process_edge_row(char **fields, int nfields, edge_id = make_graphid(label_id, entry_id); /* Trim whitespace from vertex type names */ - start_vertex_type = trim_whitespace(fields[1]); - end_vertex_type = trim_whitespace(fields[3]); + trim_field_to_name(&start_vertex_type, fields[1]); + trim_field_to_name(&end_vertex_type, fields[3]); /* Parse start vertex info */ start_id_int = strtol(fields[0], NULL, 10); - start_vertex_type_id = get_label_id(start_vertex_type, graph_oid); + start_vertex_type_id = get_cached_vertex_label_id(vertex_label_id_cache, + NameStr(start_vertex_type), + graph_oid); /* Parse end vertex info */ end_id_int = strtol(fields[2], NULL, 10); - end_vertex_type_id = get_label_id(end_vertex_type, graph_oid); + end_vertex_type_id = get_cached_vertex_label_id(vertex_label_id_cache, + NameStr(end_vertex_type), + graph_oid); /* Create graphids for start and end vertices */ start_vertex_graph_id = make_graphid(start_vertex_type_id, start_id_int); @@ -112,6 +139,113 @@ static void process_edge_row(char **fields, int nfields, } } +static void trim_field_to_name(Name name, const char *str) +{ + const char *start; + const char *p; + const char *last_non_space = NULL; + size_t len; + + if (str == NULL) + { + NameStr(*name)[0] = '\0'; + return; + } + + start = str; + while (*start == ' ' || *start == '\t' || + *start == '\n' || *start == '\r') + { + start++; + } + + if (*start == '\0') + { + NameStr(*name)[0] = '\0'; + return; + } + + for (p = start; *p != '\0'; p++) + { + if (*p != ' ' && *p != '\t' && + *p != '\n' && *p != '\r') + { + last_non_space = p; + } + } + + if (last_non_space == NULL) + { + NameStr(*name)[0] = '\0'; + return; + } + + len = last_non_space - start + 1; + if (len >= NAMEDATALEN) + { + len = NAMEDATALEN - 1; + } + + memcpy(NameStr(*name), start, len); + NameStr(*name)[len] = '\0'; +} + +static vertex_label_id_cache *create_vertex_label_id_cache(void) +{ + HASHCTL hashctl; + vertex_label_id_cache *cache; + + MemSet(&hashctl, 0, sizeof(hashctl)); + hashctl.keysize = sizeof(NameData); + hashctl.entrysize = sizeof(vertex_label_id_cache_entry); + + cache = palloc(sizeof(vertex_label_id_cache)); + cache->entries = hash_create("age edge load vertex label id cache", 16, + &hashctl, HASH_ELEM | HASH_BLOBS); + cache->last_valid = false; + cache->last_id = -1; + + return cache; +} + +static void destroy_vertex_label_id_cache(vertex_label_id_cache *cache) +{ + hash_destroy(cache->entries); + pfree(cache); +} + +static int32 get_cached_vertex_label_id(vertex_label_id_cache *cache, + const char *label_name, + Oid graph_oid) +{ + NameData key; + vertex_label_id_cache_entry *entry; + bool found; + + if (cache->last_valid && + namestrcmp(&cache->last_name, label_name) == 0) + { + return cache->last_id; + } + + namestrcpy(&key, label_name); + entry = hash_search(cache->entries, &key, HASH_ENTER, &found); + if (!found) + { + label_cache_data *label_cache; + + label_cache = search_label_name_graph_cache_cached(label_name, + graph_oid); + entry->id = label_cache != NULL ? label_cache->id : -1; + } + + cache->last_name = key; + cache->last_id = entry->id; + cache->last_valid = true; + + return entry->id; +} + /* * Create COPY options for CSV parsing. * Returns a List of DefElem nodes. @@ -142,11 +276,12 @@ int create_edges_from_csv_file(char *file_path, char *graph_name, Oid graph_oid, char *label_name, + Oid label_relid, + Oid label_seq_relid, int label_id, bool load_as_agtype) { Relation label_rel; - Oid label_relid; CopyFromState cstate; List *copy_options; ParseState *pstate; @@ -155,27 +290,21 @@ int create_edges_from_csv_file(char *file_path, char **header = NULL; int header_count = 0; bool is_first_row = true; - char *label_seq_name; - Oid label_seq_relid; batch_insert_state *batch_state = NULL; MemoryContext batch_context; MemoryContext old_context; + vertex_label_id_cache *vertex_label_id_cache = NULL; /* Create a memory context for batch processing - reset after each batch */ batch_context = AllocSetContextCreate(CurrentMemoryContext, "AGE CSV Edge Load Batch Context", ALLOCSET_DEFAULT_SIZES); - /* Get the label relation */ - label_relid = get_label_relation(label_name, graph_oid); label_rel = table_open(label_relid, RowExclusiveLock); - /* Get sequence info */ - label_seq_name = get_label_seq_relation_name(label_name); - label_seq_relid = get_relname_relid(label_seq_name, graph_oid); - /* Initialize the batch insert state */ - init_batch_insert(&batch_state, label_name, graph_oid); + init_batch_insert(&batch_state, label_relid); + vertex_label_id_cache = create_vertex_label_id_cache(); /* Create COPY options for CSV parsing */ copy_options = create_copy_options(); @@ -231,7 +360,7 @@ int create_edges_from_csv_file(char *file_path, header, header_count, label_id, label_seq_relid, graph_oid, load_as_agtype, - batch_state); + batch_state, vertex_label_id_cache); /* Switch back to main context */ MemoryContextSwitchTo(old_context); @@ -247,6 +376,8 @@ int create_edges_from_csv_file(char *file_path, /* Finish any remaining batch inserts */ finish_batch_insert(&batch_state); MemoryContextReset(batch_context); + destroy_vertex_label_id_cache(vertex_label_id_cache); + vertex_label_id_cache = NULL; /* Clean up COPY state */ EndCopyFrom(cstate); @@ -267,6 +398,11 @@ int create_edges_from_csv_file(char *file_path, /* Close the relation */ table_close(label_rel, RowExclusiveLock); + if (vertex_label_id_cache != NULL) + { + destroy_vertex_label_id_cache(vertex_label_id_cache); + } + /* Delete batch context */ MemoryContextDelete(batch_context); diff --git a/src/backend/utils/load/ag_load_labels.c b/src/backend/utils/load/ag_load_labels.c index 5b11f68b8..68ec22d82 100644 --- a/src/backend/utils/load/ag_load_labels.c +++ b/src/backend/utils/load/ag_load_labels.c @@ -29,6 +29,7 @@ #include "utils/rel.h" #include "utils/load/ag_load_labels.h" +#include "utils/ag_cache.h" /* * Process a single vertex row from COPY's raw fields. @@ -129,12 +130,13 @@ int create_labels_from_csv_file(char *file_path, char *graph_name, Oid graph_oid, char *label_name, + Oid label_relid, + Oid label_seq_relid, int label_id, bool id_field_exists, bool load_as_agtype) { Relation label_rel; - Oid label_relid; CopyFromState cstate; List *copy_options; ParseState *pstate; @@ -143,8 +145,6 @@ int create_labels_from_csv_file(char *file_path, char **header = NULL; int header_count = 0; bool is_first_row = true; - char *label_seq_name; - Oid label_seq_relid; int64 curr_seq_num = 0; batch_insert_state *batch_state = NULL; MemoryContext batch_context; @@ -155,14 +155,8 @@ int create_labels_from_csv_file(char *file_path, "AGE CSV Load Batch Context", ALLOCSET_DEFAULT_SIZES); - /* Get the label relation */ - label_relid = get_label_relation(label_name, graph_oid); label_rel = table_open(label_relid, RowExclusiveLock); - /* Get sequence info */ - label_seq_name = get_label_seq_relation_name(label_name); - label_seq_relid = get_relname_relid(label_seq_name, graph_oid); - if (id_field_exists) { /* @@ -173,7 +167,7 @@ int create_labels_from_csv_file(char *file_path, } /* Initialize the batch insert state */ - init_batch_insert(&batch_state, label_name, graph_oid); + init_batch_insert(&batch_state, label_relid); /* Create COPY options for CSV parsing */ copy_options = create_copy_options(); diff --git a/src/backend/utils/load/age_load.c b/src/backend/utils/load/age_load.c index e4f10d7e4..a407787aa 100644 --- a/src/backend/utils/load/age_load.c +++ b/src/backend/utils/load/age_load.c @@ -27,6 +27,7 @@ #include "catalog/pg_authid.h" #include "executor/executor.h" #include "miscadmin.h" +#include "nodes/makefuncs.h" #include "nodes/parsenodes.h" #include "parser/parse_relation.h" #include "utils/acl.h" @@ -37,11 +38,16 @@ #include "utils/load/ag_load_edges.h" #include "utils/load/ag_load_labels.h" #include "utils/load/age_load.h" +#include "utils/ag_cache.h" -static agtype_value *csv_value_to_agtype_value(char *csv_val); +static agtype_value *csv_value_to_agtype_value(const char *csv_val, + size_t csv_len); +static agtype_value *trimmed_field_to_string_agtype_value(const char *field); +static const char *trimmed_string_span(const char *str, size_t *len); static Oid get_or_create_graph(const Name graph_name); -static int32 get_or_create_label(Oid graph_oid, char *graph_name, - char *label_name, char label_kind); +static label_cache_data *get_or_create_label(Oid graph_oid, char *graph_name, + char *label_name, + char label_kind); static char *build_safe_filename(char *name); static void check_file_read_permission(void); static void check_table_permissions(Oid relid); @@ -49,6 +55,8 @@ static void check_rls_for_load(Oid relid); #define AGE_BASE_CSV_DIRECTORY "/tmp/age/" #define AGE_CSV_FILE_EXTENSION ".csv" +#define AGE_BASE_CSV_DIRECTORY_LEN (sizeof(AGE_BASE_CSV_DIRECTORY) - 1) +#define AGE_CSV_FILE_EXTENSION_LEN (sizeof(AGE_CSV_FILE_EXTENSION) - 1) /* * Trim leading and trailing whitespace from a string. @@ -58,39 +66,48 @@ static void check_rls_for_load(Oid relid); char *trim_whitespace(const char *str) { const char *start; - const char *end; size_t len; + start = trimmed_string_span(str, &len); + return pnstrdup(start, len); +} + +static const char *trimmed_string_span(const char *str, size_t *len) +{ + const char *start; + const char *p; + const char *last_non_space = NULL; + if (str == NULL) { - return pstrdup(""); + *len = 0; + return ""; } - /* Find first non-whitespace character */ start = str; - while (*start && (*start == ' ' || *start == '\t' || - *start == '\n' || *start == '\r')) + while (*start == ' ' || *start == '\t' || + *start == '\n' || *start == '\r') { start++; } - /* If string is all whitespace, return empty string */ - if (*start == '\0') + for (p = start; *p != '\0'; p++) { - return pstrdup(""); + if (*p != ' ' && *p != '\t' && + *p != '\n' && *p != '\r') + { + last_non_space = p; + } } - /* Find last non-whitespace character */ - end = str + strlen(str) - 1; - while (end > start && (*end == ' ' || *end == '\t' || - *end == '\n' || *end == '\r')) + if (last_non_space == NULL) { - end--; + *len = 0; + return ""; } - /* Copy the trimmed string */ - len = end - start + 1; - return pnstrdup(start, len); + *len = last_non_space - start + 1; + return start; } static char *build_safe_filename(char *name) @@ -126,16 +143,16 @@ static char *build_safe_filename(char *name) } if (strncmp(resolved, AGE_BASE_CSV_DIRECTORY, - strlen(AGE_BASE_CSV_DIRECTORY)) != 0) + AGE_BASE_CSV_DIRECTORY_LEN) != 0) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("You can only load files located in [%s].", AGE_BASE_CSV_DIRECTORY))); } - length = strlen(resolved) - 4; + length = strlen(resolved) - AGE_CSV_FILE_EXTENSION_LEN; if (strncmp(resolved+length, AGE_CSV_FILE_EXTENSION, - strlen(AGE_CSV_FILE_EXTENSION)) != 0) + AGE_CSV_FILE_EXTENSION_LEN) != 0) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("You can only load files with extension [%s].", @@ -215,40 +232,39 @@ agtype *create_empty_agtype(void) * json in order to be parsed into an agtype_value of appropriate type. * Finally, agtype_value_from_cstring() is called for parsing. */ -static agtype_value *csv_value_to_agtype_value(char *csv_val) +static agtype_value *csv_value_to_agtype_value(const char *csv_val, + size_t csv_len) { char *new_csv_val; + size_t new_csv_len; agtype_value *res; /* Handle NULL or empty input - return null agtype value */ - if (csv_val == NULL || csv_val[0] == '\0') + if (csv_val == NULL || csv_len == 0) { res = palloc(sizeof(agtype_value)); res->type = AGTV_NULL; return res; } - if (!json_validate(cstring_to_text(csv_val), false, false)) + if (!json_validate(cstring_to_text_with_len(csv_val, csv_len), false, false)) { /* wrap the string with double-quote */ - int oldlen; - int newlen; - - oldlen = strlen(csv_val); - newlen = oldlen + 2; /* +2 for double-quotes */ - new_csv_val = (char *)palloc(sizeof(char) * (newlen + 1)); + new_csv_len = csv_len + 2; /* +2 for double-quotes */ + new_csv_val = (char *)palloc(sizeof(char) * (new_csv_len + 1)); new_csv_val[0] = '"'; - strncpy(&new_csv_val[1], csv_val, oldlen); - new_csv_val[oldlen + 1] = '"'; - new_csv_val[oldlen + 2] = '\0'; + memcpy(&new_csv_val[1], csv_val, csv_len); + new_csv_val[csv_len + 1] = '"'; + new_csv_val[csv_len + 2] = '\0'; } else { - new_csv_val = csv_val; + new_csv_val = (char *)csv_val; + new_csv_len = csv_len; } - res = agtype_value_from_cstring(new_csv_val, strlen(new_csv_val)); + res = agtype_value_from_cstring(new_csv_val, new_csv_len); /* extract from top-level row scalar array */ if (res->type == AGTV_ARRAY && res->val.array.raw_scalar) @@ -259,6 +275,23 @@ static agtype_value *csv_value_to_agtype_value(char *csv_val) return res; } +static agtype_value *trimmed_field_to_string_agtype_value(const char *field) +{ + const char *start; + size_t len; + agtype_value *agtv; + + agtv = palloc(sizeof(agtype_value)); + agtv->type = AGTV_STRING; + + start = trimmed_string_span(field, &len); + len = check_string_length(len); + agtv->val.string.len = len; + agtv->val.string.val = pnstrdup(start, len); + + return agtv; +} + agtype *create_agtype_from_list(char **header, char **fields, size_t fields_len, int64 vertex_id, bool load_as_agtype) { @@ -285,8 +318,6 @@ agtype *create_agtype_from_list(char **header, char **fields, size_t fields_len, for (i = 0; itype = AGTV_STRING; - value_agtype->val.string.len = 0; - value_agtype->val.string.val = pstrdup(""); - } - else - { - value_agtype = string_to_agtype_value(trimmed_value); - } + value_agtype = trimmed_field_to_string_agtype_value(fields[i]); } result.res = push_agtype_value(&result.parse_state, @@ -360,8 +383,6 @@ agtype* create_agtype_from_list_i(char **header, char **fields, for (i = start_index; i < fields_len; i++) { - char *trimmed_value; - /* Skip empty header fields (e.g., from trailing commas) */ if (header[i] == NULL || header[i][0] == '\0') { @@ -373,27 +394,19 @@ agtype* create_agtype_from_list_i(char **header, char **fields, WAGT_KEY, key_agtype); - /* Trim whitespace from field value */ - trimmed_value = trim_whitespace(fields[i]); - if (load_as_agtype) { - value_agtype = csv_value_to_agtype_value(trimmed_value); + const char *trimmed_value; + size_t trimmed_len; + + /* Trim whitespace from field value */ + trimmed_value = trimmed_string_span(fields[i], &trimmed_len); + value_agtype = csv_value_to_agtype_value(trimmed_value, + trimmed_len); } else { - /* Handle empty field values */ - if (trimmed_value[0] == '\0') - { - value_agtype = palloc(sizeof(agtype_value)); - value_agtype->type = AGTV_STRING; - value_agtype->val.string.len = 0; - value_agtype->val.string.val = pstrdup(""); - } - else - { - value_agtype = string_to_agtype_value(trimmed_value); - } + value_agtype = trimmed_field_to_string_agtype_value(fields[i]); } result.res = push_agtype_value(&result.parse_state, @@ -422,16 +435,19 @@ void insert_edge_simple(Oid graph_oid, char *label_name, graphid edge_id, bool nulls[4] = {false, false, false, false}; Relation label_relation; HeapTuple tuple; + label_cache_data *label_cache; + + label_cache = search_label_name_graph_cache_cached(label_name, graph_oid); /* Check if label provided exists as vertex label, then throw error */ - if (get_label_kind(label_name, graph_oid) == LABEL_KIND_VERTEX) + if (label_cache != NULL && label_cache->kind == LABEL_KIND_VERTEX) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("label %s already exists as vertex label", label_name))); } /* Open the relation */ - label_relation = table_open(get_label_relation(label_name, graph_oid), + label_relation = table_open(label_cache != NULL ? label_cache->relation : InvalidOid, RowExclusiveLock); /* Form the tuple */ @@ -471,9 +487,12 @@ void insert_vertex_simple(Oid graph_oid, char *label_name, graphid vertex_id, bool nulls[2] = {false, false}; Relation label_relation; HeapTuple tuple; + label_cache_data *label_cache; + + label_cache = search_label_name_graph_cache_cached(label_name, graph_oid); /* Check if label provided exists as edge label, then throw error */ - if (get_label_kind(label_name, graph_oid) == LABEL_KIND_EDGE) + if (label_cache != NULL && label_cache->kind == LABEL_KIND_EDGE) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("label %s already exists as edge label", @@ -481,7 +500,7 @@ void insert_vertex_simple(Oid graph_oid, char *label_name, graphid vertex_id, } /* Open the relation */ - label_relation = table_open(get_label_relation(label_name, graph_oid), + label_relation = table_open(label_cache != NULL ? label_cache->relation : InvalidOid, RowExclusiveLock); /* Form the tuple */ @@ -545,7 +564,7 @@ void insert_batch(batch_insert_state *batch_state) true, NULL, NIL, false); /* Check if the unique constraint is violated */ - if (list_length(result) != 0) + if (result != NIL) { Datum id; bool isnull; @@ -573,9 +592,11 @@ Datum load_labels_from_file(PG_FUNCTION_ARGS) char* file_path_str; Oid graph_oid; Oid label_relid; + Oid label_seq_relid; int32 label_id; bool id_field_exists; bool load_as_agtype; + label_cache_data *label_cache; if (PG_ARGISNULL(0)) { @@ -607,7 +628,7 @@ Datum load_labels_from_file(PG_FUNCTION_ARGS) graph_name_str = NameStr(*graph_name); label_name_str = NameStr(*label_name); - if (strcmp(label_name_str, "") == 0) + if (label_name_str[0] == '\0') { label_name_str = AG_DEFAULT_LABEL_VERTEX; } @@ -615,17 +636,19 @@ Datum load_labels_from_file(PG_FUNCTION_ARGS) file_path_str = build_safe_filename(text_to_cstring(file_name)); graph_oid = get_or_create_graph(graph_name); - label_id = get_or_create_label(graph_oid, graph_name_str, - label_name_str, LABEL_KIND_VERTEX); + label_cache = get_or_create_label(graph_oid, graph_name_str, + label_name_str, LABEL_KIND_VERTEX); + label_id = label_cache->id; /* Get the label relation and check permissions */ - label_relid = get_label_relation(label_name_str, graph_oid); + label_relid = label_cache->relation; + label_seq_relid = get_label_seq_relation_cached(label_cache, graph_oid); check_table_permissions(label_relid); check_rls_for_load(label_relid); create_labels_from_csv_file(file_path_str, graph_name_str, graph_oid, - label_name_str, label_id, id_field_exists, - load_as_agtype); + label_name_str, label_relid, label_seq_relid, + label_id, id_field_exists, load_as_agtype); free(file_path_str); @@ -643,8 +666,10 @@ Datum load_edges_from_file(PG_FUNCTION_ARGS) char* file_path_str; Oid graph_oid; Oid label_relid; + Oid label_seq_relid; int32 label_id; bool load_as_agtype; + label_cache_data *label_cache; if (PG_ARGISNULL(0)) { @@ -675,7 +700,7 @@ Datum load_edges_from_file(PG_FUNCTION_ARGS) graph_name_str = NameStr(*graph_name); label_name_str = NameStr(*label_name); - if (strcmp(label_name_str, "") == 0) + if (label_name_str[0] == '\0') { label_name_str = AG_DEFAULT_LABEL_EDGE; } @@ -683,16 +708,19 @@ Datum load_edges_from_file(PG_FUNCTION_ARGS) file_path_str = build_safe_filename(text_to_cstring(file_name)); graph_oid = get_or_create_graph(graph_name); - label_id = get_or_create_label(graph_oid, graph_name_str, - label_name_str, LABEL_KIND_EDGE); + label_cache = get_or_create_label(graph_oid, graph_name_str, + label_name_str, LABEL_KIND_EDGE); + label_id = label_cache->id; /* Get the label relation and check permissions */ - label_relid = get_label_relation(label_name_str, graph_oid); + label_relid = label_cache->relation; + label_seq_relid = get_label_seq_relation_cached(label_cache, graph_oid); check_table_permissions(label_relid); check_rls_for_load(label_relid); create_edges_from_csv_file(file_path_str, graph_name_str, graph_oid, - label_name_str, label_id, load_as_agtype); + label_name_str, label_relid, label_seq_relid, + label_id, load_as_agtype); free(file_path_str); @@ -705,37 +733,45 @@ Datum load_edges_from_file(PG_FUNCTION_ARGS) */ static Oid get_or_create_graph(const Name graph_name) { - Oid graph_oid; + graph_cache_data *graph_cache; char *graph_name_str; graph_name_str = NameStr(*graph_name); - graph_oid = get_graph_oid(graph_name_str); - - if (OidIsValid(graph_oid)) + graph_cache = search_graph_name_cache_cached(graph_name_str); + if (graph_cache != NULL) { - return graph_oid; + return graph_cache->oid; } - graph_oid = create_graph_internal(graph_name); + graph_cache = search_graph_namespace_cache_cached( + create_graph_internal(graph_name)); + if (graph_cache == NULL) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("graph \"%s\" was not found after creation", + graph_name_str))); + } ereport(NOTICE, (errmsg("graph \"%s\" has been created", NameStr(*graph_name)))); - - return graph_oid; + + return graph_cache->oid; } /* * Helper function to create a label if it does not exist. * Just returns label_id of the label if it already exists. */ -static int32 get_or_create_label(Oid graph_oid, char *graph_name, - char *label_name, char label_kind) +static label_cache_data *get_or_create_label(Oid graph_oid, char *graph_name, + char *label_name, + char label_kind) { - int32 label_id; + label_cache_data *label_cache; - label_id = get_label_id(label_name, graph_oid); + label_cache = search_label_name_graph_cache_cached(label_name, graph_oid); /* Check if label exists */ - if (label_id_is_valid(label_id)) + if (label_cache != NULL) { char *label_kind_full = (label_kind == LABEL_KIND_VERTEX) ? "vertex" : "edge"; @@ -743,7 +779,7 @@ static int32 get_or_create_label(Oid graph_oid, char *graph_name, ? LABEL_KIND_EDGE : LABEL_KIND_VERTEX; /* If it exists, but as another label_kind, throw an error */ - if (get_label_kind(label_name, graph_oid) == opposite_label_kind) + if (label_cache->kind == opposite_label_kind) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -756,29 +792,37 @@ static int32 get_or_create_label(Oid graph_oid, char *graph_name, /* Create a label */ RangeVar *rv; List *parent; + Oid label_relid; char *default_label = (label_kind == LABEL_KIND_VERTEX) ? AG_DEFAULT_LABEL_VERTEX : AG_DEFAULT_LABEL_EDGE; - rv = get_label_range_var(graph_name, graph_oid, default_label); + rv = makeRangeVar(graph_name, pstrdup(default_label), -1); parent = list_make1(rv); - create_label(graph_name, label_name, label_kind, parent); - label_id = get_label_id(label_name, graph_oid); + label_relid = create_label_with_graph_oid(graph_name, graph_oid, + label_name, label_kind, + parent); + label_cache = search_label_relation_cache_cached(label_relid); + if (label_cache == NULL) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("label \"%s\" was not found after creation", + label_name))); + } ereport(NOTICE, (errmsg("VLabel \"%s\" has been created", label_name))); } - return label_id; + return label_cache; } /* * Initialize the batch insert state. */ -void init_batch_insert(batch_insert_state **batch_state, - char *label_name, Oid graph_oid) +void init_batch_insert(batch_insert_state **batch_state, Oid relid) { Relation relation; - Oid relid; EState *estate; ResultRelInfo *resultRelInfo; RangeTblEntry *rte; @@ -787,9 +831,6 @@ void init_batch_insert(batch_insert_state **batch_state, List *perminfos = NIL; int i; - /* Get the relation OID */ - relid = get_label_relation(label_name, graph_oid); - /* Initialize executor state */ estate = CreateExecutorState(); @@ -822,7 +863,7 @@ void init_batch_insert(batch_insert_state **batch_state, ExecOpenIndices(resultRelInfo, false); /* Initialize the batch insert state */ - *batch_state = (batch_insert_state *) palloc0(sizeof(batch_insert_state)); + *batch_state = (batch_insert_state *) palloc(sizeof(batch_insert_state)); (*batch_state)->slots = palloc(sizeof(TupleTableSlot *) * BATCH_SIZE); (*batch_state)->estate = estate; (*batch_state)->resultRelInfo = resultRelInfo; diff --git a/src/include/catalog/ag_label.h b/src/include/catalog/ag_label.h index 0a8480b1a..37377b485 100644 --- a/src/include/catalog/ag_label.h +++ b/src/include/catalog/ag_label.h @@ -65,6 +65,12 @@ #define LABEL_KIND_VERTEX 'v' #define LABEL_KIND_EDGE 'e' +typedef struct ag_label_info +{ + char *name; + Oid relation; +} ag_label_info; + void insert_label(const char *label_name, Oid graph_oid, int32 label_id, char label_kind, Oid label_relation, const char *seq_name); void delete_label(Oid relation); @@ -80,7 +86,7 @@ bool label_id_exists(Oid graph_oid, int32 label_id); RangeVar *get_label_range_var(char *graph_name, Oid graph_oid, char *label_name); -List *get_all_edge_labels_per_graph(EState *estate, Oid graph_oid); +List *get_all_edge_labels_per_graph(Snapshot snapshot, Oid graph_oid); #define label_exists(label_name, label_graph) \ OidIsValid(get_label_id(label_name, label_graph)) diff --git a/src/include/commands/label_commands.h b/src/include/commands/label_commands.h index cfbbdb336..dd8ce6132 100644 --- a/src/include/commands/label_commands.h +++ b/src/include/commands/label_commands.h @@ -52,8 +52,11 @@ #define IS_AG_DEFAULT_LABEL(x) \ (IS_DEFAULT_LABEL_EDGE(x) || IS_DEFAULT_LABEL_VERTEX(x)) -void create_label(char *graph_name, char *label_name, char label_type, - List *parents); +Oid create_label(char *graph_name, char *label_name, char label_type, + List *parents); +Oid create_label_with_graph_oid(char *graph_name, Oid graph_oid, + char *label_name, char label_type, + List *parents); Datum create_vlabel(PG_FUNCTION_ARGS); diff --git a/src/include/executor/cypher_utils.h b/src/include/executor/cypher_utils.h index ac4b5ea5a..fbea8b6b1 100644 --- a/src/include/executor/cypher_utils.h +++ b/src/include/executor/cypher_utils.h @@ -46,7 +46,7 @@ estate->es_snapshot->curcid--; #define DELETE_VERTEX_HTAB_NAME "delete_vertex_htab" -#define DELETE_VERTEX_HTAB_SIZE 1000000 +#define DELETE_VERTEX_HTAB_INITIAL_SIZE 1024 typedef struct cypher_create_custom_scan_state { @@ -57,6 +57,10 @@ typedef struct cypher_create_custom_scan_state uint32 flags; TupleTableSlot *slot; Oid graph_oid; + HTAB *entity_exists_index_cache; + HTAB *entity_exists_label_relation_cache; + HTAB *result_rel_info_cache; + bool graph_mutated; } cypher_create_custom_scan_state; typedef struct cypher_set_custom_scan_state @@ -64,7 +68,16 @@ typedef struct cypher_set_custom_scan_state CustomScanState css; CustomScan *cs; cypher_update_information *set_list; + int set_item_count; + HTAB *qual_cache; + HTAB *index_cache; + HTAB *result_rel_info_cache; + HTAB *label_relation_cache; + Oid graph_oid; int flags; + int *path_update_attrs; + int path_update_attr_count; + bool path_update_attrs_valid; } cypher_set_custom_scan_state; typedef struct cypher_delete_custom_scan_state @@ -92,6 +105,11 @@ typedef struct cypher_delete_custom_scan_state * and end_id column. */ HTAB *vertex_id_htab; + HTAB *qual_cache; + HTAB *index_cache; + HTAB *result_rel_info_cache; + HTAB *label_relation_cache; + bool graph_mutated; } cypher_delete_custom_scan_state; typedef struct cypher_merge_custom_scan_state @@ -105,19 +123,35 @@ typedef struct cypher_merge_custom_scan_state Oid graph_oid; AttrNumber merge_function_attr; bool created_new_path; + bool graph_mutated; bool found_a_path; CommandId base_currentCommandId; struct created_path *created_paths_list; + HTAB *created_paths_hash; + int path_length; List *eager_tuples; - int eager_tuples_index; + ListCell *eager_tuples_cursor; bool eager_buffer_filled; cypher_update_information *on_match_set_info; /* NULL if not specified */ cypher_update_information *on_create_set_info; /* NULL if not specified */ + int on_match_set_item_count; + int on_create_set_item_count; + int *path_update_attrs; + int path_update_attr_count; + bool path_update_attrs_valid; + HTAB *update_qual_cache; + HTAB *update_index_cache; + HTAB *update_result_rel_info_cache; + HTAB *update_label_relation_cache; + HTAB *entity_exists_index_cache; + HTAB *entity_exists_label_relation_cache; + HTAB *result_rel_info_cache; } cypher_merge_custom_scan_state; /* Reusable SET logic callable from MERGE executor */ -void apply_update_list(CustomScanState *node, - cypher_update_information *set_info); +bool apply_update_list(CustomScanState *node, + cypher_update_information *set_info, + int num_set_items); TupleTableSlot *populate_vertex_tts(TupleTableSlot *elemTupleSlot, agtype_value *id, agtype_value *properties); @@ -125,11 +159,27 @@ TupleTableSlot *populate_edge_tts( TupleTableSlot *elemTupleSlot, agtype_value *id, agtype_value *startid, agtype_value *endid, agtype_value *properties); -ResultRelInfo *create_entity_result_rel_info(EState *estate, char *graph_name, - char *label_name); +ResultRelInfo *create_entity_result_rel_info_by_oid(EState *estate, Oid relid); void destroy_entity_result_rel_info(ResultRelInfo *result_rel_info); - +HTAB *create_entity_result_rel_info_cache(const char *name); +ResultRelInfo *get_entity_result_rel_info(EState *estate, + HTAB *result_rel_info_cache, + Oid relid); +void destroy_entity_result_rel_info_cache(HTAB *result_rel_info_cache); + +HTAB *create_label_relation_cache(const char *name); +bool get_label_relation_from_cache(HTAB *label_relation_cache, Oid graph_oid, + int32 label_id, Oid *relation, + char **label_name); +void destroy_label_relation_cache(HTAB *label_relation_cache); + +HTAB *create_entity_exists_index_cache(const char *name); +void destroy_entity_exists_index_cache(HTAB *index_cache); +void destroy_index_cache(HTAB *index_cache, bool close_relations); bool entity_exists(EState *estate, Oid graph_oid, graphid id); +bool entity_exists_with_cache(EState *estate, Oid graph_oid, graphid id, + HTAB *index_cache, + HTAB *label_relation_cache); HeapTuple insert_entity_tuple(ResultRelInfo *resultRelInfo, TupleTableSlot *elemTupleSlot, EState *estate); @@ -150,6 +200,7 @@ bool check_rls_for_tuple(Relation rel, HeapTuple tuple, CmdType cmd); typedef struct RLSCacheEntry { Oid relid; /* hash key */ + bool rls_enabled; /* Security quals (USING policies) for UPDATE/DELETE */ List *qualExprs; TupleTableSlot *slot; /* slot for old tuple (RLS check) */ @@ -161,7 +212,28 @@ typedef struct RLSCacheEntry /* Hash table entry for caching index OIDs per label */ typedef struct IndexCacheEntry { Oid relid; /* hash key */ + Relation rel; + bool index_oid_cached; Oid index_oid; + bool start_index_oid_cached; + Oid start_index_oid; + bool end_index_oid_cached; + Oid end_index_oid; + TupleTableSlot *slot; + TupleTableSlot *update_slot; } IndexCacheEntry; +static inline void init_index_cache_entry(IndexCacheEntry *entry) +{ + entry->rel = NULL; + entry->index_oid_cached = false; + entry->index_oid = InvalidOid; + entry->start_index_oid_cached = false; + entry->start_index_oid = InvalidOid; + entry->end_index_oid_cached = false; + entry->end_index_oid = InvalidOid; + entry->slot = NULL; + entry->update_slot = NULL; +} + #endif diff --git a/src/include/nodes/cypher_nodes.h b/src/include/nodes/cypher_nodes.h index 3433bebb0..323d6b8d2 100644 --- a/src/include/nodes/cypher_nodes.h +++ b/src/include/nodes/cypher_nodes.h @@ -310,6 +310,7 @@ typedef struct cypher_create_path List *target_nodes; AttrNumber path_attr_num; char *var_name; + int path_length; } cypher_create_path; /* @@ -461,7 +462,12 @@ typedef struct cypher_update_information uint32 flags; AttrNumber tuple_position; char *graph_name; + uint32 graph_oid; char *clause_name; + int set_item_count; + int *last_update_indexes; + int last_update_index_count; + bool last_update_indexes_valid; } cypher_update_information; typedef struct cypher_update_item @@ -487,6 +493,9 @@ typedef struct cypher_delete_information char *graph_name; uint32 graph_oid; bool detach; + int *delete_item_positions; + int delete_item_count; + bool delete_item_positions_valid; } cypher_delete_information; typedef struct cypher_delete_item @@ -503,8 +512,11 @@ typedef struct cypher_merge_information uint32 graph_oid; AttrNumber merge_function_attr; cypher_create_path *path; + int path_length; cypher_update_information *on_match_set_info; /* NULL if no ON MATCH SET */ cypher_update_information *on_create_set_info; /* NULL if no ON CREATE SET */ + int on_match_set_item_count; + int on_create_set_item_count; } cypher_merge_information; /* grammar node for typecasts */ diff --git a/src/include/parser/cypher_parse_node.h b/src/include/parser/cypher_parse_node.h index 263ea197b..561aa62dc 100644 --- a/src/include/parser/cypher_parse_node.h +++ b/src/include/parser/cypher_parse_node.h @@ -21,6 +21,7 @@ #define AG_CYPHER_PARSE_NODE_H #include "nodes/cypher_nodes.h" +#include "utils/ag_cache.h" /* * Every internal alias or variable name should be prefixed @@ -39,6 +40,11 @@ typedef struct cypher_parsestate Param *params; int default_alias_num; List *entities; + List *label_cache_entries; + const char *last_label_cache_name; + Oid last_label_cache_graph_oid; + uint64 last_label_cache_generation; + label_cache_data *last_label_cache_data; List *property_constraint_quals; bool subquery_where_flag; /* flag for knowing we are in a subquery where */ /* @@ -67,5 +73,7 @@ void setup_errpos_ecb(errpos_ecb_state *ecb_state, ParseState *pstate, int query_loc); void cancel_errpos_ecb(errpos_ecb_state *ecb_state); char *get_next_default_alias(cypher_parsestate *cpstate); +label_cache_data *get_label_cache_data(cypher_parsestate *cpstate, + const char *label_name); #endif diff --git a/src/include/utils/ag_cache.h b/src/include/utils/ag_cache.h index 0e2c3a91f..347b33505 100644 --- a/src/include/utils/ag_cache.h +++ b/src/include/utils/ag_cache.h @@ -36,16 +36,30 @@ typedef struct label_cache_data int32 id; char kind; Oid relation; + NameData relation_name; NameData seq_name; } label_cache_data; /* callers of these functions must not modify the returned struct */ graph_cache_data *search_graph_name_cache(const char *name); +graph_cache_data *search_graph_name_cache_cached(const char *name); graph_cache_data *search_graph_namespace_cache(Oid namespace); +graph_cache_data *search_graph_namespace_cache_cached(Oid namespace); +uint64 get_graph_cache_generation(void); +void invalidate_graph_cache(void); +uint64 get_label_cache_generation(void); +void invalidate_label_cache(void); label_cache_data *search_label_oid_cache(Oid oid); label_cache_data *search_label_name_graph_cache(const char *name, Oid graph); +label_cache_data *search_label_name_graph_cache_cached(const char *name, + Oid graph); label_cache_data *search_label_graph_oid_cache(Oid graph, int32 id); +label_cache_data *search_label_graph_oid_cache_cached(Oid graph, int32 id); label_cache_data *search_label_relation_cache(Oid relation); +label_cache_data *search_label_relation_cache_cached(Oid relation); label_cache_data *search_label_seq_name_graph_cache(const char *name, Oid graph); +const char *get_label_cache_relation_name(label_cache_data *label_cache); +Oid get_label_seq_relation_cached(const label_cache_data *label_cache, + Oid namespace); #endif diff --git a/src/include/utils/age_global_graph.h b/src/include/utils/age_global_graph.h index 92044fc7e..f7442df7d 100644 --- a/src/include/utils/age_global_graph.h +++ b/src/include/utils/age_global_graph.h @@ -21,6 +21,7 @@ #define AG_AGE_GLOBAL_GRAPH_H #include "utils/age_graphid_ds.h" +#include "utils/hsearch.h" /* * We declare the graph nodes and edges here, and in this way, so that it may be @@ -39,10 +40,14 @@ typedef struct GRAPH_global_context GRAPH_global_context; /* GRAPH global context functions */ GRAPH_global_context *manage_GRAPH_global_contexts(char *graph_name, Oid graph_oid); +GRAPH_global_context *manage_GRAPH_global_contexts_len(const char *graph_name, + int graph_name_len, + Oid graph_oid); GRAPH_global_context *find_GRAPH_global_context(Oid graph_oid); bool is_ggctx_invalid(GRAPH_global_context *ggctx); /* GRAPH retrieval functions */ ListGraphId *get_graph_vertices(GRAPH_global_context *ggctx); +int64 get_graph_num_loaded_edges(GRAPH_global_context *ggctx); vertex_entry *get_vertex_entry(GRAPH_global_context *ggctx, graphid vertex_id); edge_entry *get_edge_entry(GRAPH_global_context *ggctx, graphid edge_id); @@ -51,14 +56,28 @@ graphid get_vertex_entry_id(vertex_entry *ve); ListGraphId *get_vertex_entry_edges_in(vertex_entry *ve); ListGraphId *get_vertex_entry_edges_out(vertex_entry *ve); ListGraphId *get_vertex_entry_edges_self(vertex_entry *ve); +ListGraphId *get_vertex_entry_edges_in_for_label(vertex_entry *ve, + Oid edge_label_table_oid); +ListGraphId *get_vertex_entry_edges_out_for_label(vertex_entry *ve, + Oid edge_label_table_oid); +ListGraphId *get_vertex_entry_edges_self_for_label(vertex_entry *ve, + Oid edge_label_table_oid); Oid get_vertex_entry_label_table_oid(vertex_entry *ve); +char *get_vertex_entry_label_name(vertex_entry *ve); Datum get_vertex_entry_properties(vertex_entry *ve); +Datum get_vertex_entry_properties_with_cache(vertex_entry *ve, + HTAB *relation_cache); /* edge entry accessor functions */ graphid get_edge_entry_id(edge_entry *ee); Oid get_edge_entry_label_table_oid(edge_entry *ee); +char *get_edge_entry_label_name(edge_entry *ee); Datum get_edge_entry_properties(edge_entry *ee); +Datum get_edge_entry_properties_with_cache(edge_entry *ee, + HTAB *relation_cache); graphid get_edge_entry_start_vertex_id(edge_entry *ee); graphid get_edge_entry_end_vertex_id(edge_entry *ee); +HTAB *create_entry_property_relation_cache(const char *name); +void destroy_entry_property_relation_cache(HTAB *relation_cache); /* Graph version counter functions — shared memory (DSM or shmem) */ uint64 get_graph_version(Oid graph_oid); diff --git a/src/include/utils/age_session_info.h b/src/include/utils/age_session_info.h index 5bd072fb6..e97dff59f 100644 --- a/src/include/utils/age_session_info.h +++ b/src/include/utils/age_session_info.h @@ -20,12 +20,10 @@ #ifndef AGE_SESSION_INFO_H #define AGE_SESSION_INFO_H -#include "utils/agtype.h" - -bool is_session_info_prepared(void); -char *get_session_info_graph_name(void); -char *get_session_info_cypher_statement(void); -void reset_session_info(void); +/* + * The old session-info side channel was removed from cypher() analysis. + * This header remains only as a compatibility include for code that has not + * been cleaned up yet. + */ #endif - diff --git a/src/include/utils/age_vle.h b/src/include/utils/age_vle.h index 27106d294..a6e2f2e70 100644 --- a/src/include/utils/age_vle.h +++ b/src/include/utils/age_vle.h @@ -40,10 +40,34 @@ agtype *agt_materialize_vle_path(agtype *agt_arg_vpc); * agtype_value. */ agtype_value *agtv_materialize_vle_path(agtype *agt_arg_vpc); +/* + * Append the interior edge/vertex values of a VLE path container into an + * existing path builder. Returns the number of appended values. + */ +int64 agt_vle_append_path_interior(agtype *agt_arg_vpc, + agtype_in_state *result); /* * Exposed helper function to make an agtype_value AGTV_ARRAY of edges from a * VLE_path_container. */ agtype_value *agtv_materialize_vle_edges(agtype *agt_arg_vpc); +/* + * Exposed helper function to materialize one edge from a VLE_path_container. + * Returns NULL when the index is out of bounds. + */ +agtype_value *agtv_materialize_vle_edge_at(agtype *agt_arg_vpc, + int64 edge_index); +/* + * Exposed helper function to materialize a normalized half-open edge slice + * from a VLE_path_container. + */ +agtype_value *agtv_materialize_vle_edges_slice(agtype *agt_arg_vpc, + int64 lower_index, + int64 upper_index); +agtype_value *agtv_materialize_vle_edges_reversed(agtype *agt_arg_vpc); +agtype_value *agtv_materialize_vle_nodes(agtype *agt_arg_vpc); +agtype *agt_vle_edge_properties_at(agtype *agt_arg_vpc, int64 edge_index); +bool agt_vle_contains_edge_id(agtype *agt_arg_vpc, graphid edge_id); +int64 agtv_vle_edge_count(agtype *agt_arg_vpc); #endif diff --git a/src/include/utils/agtype.h b/src/include/utils/agtype.h index 807d3795e..dcd5b26ee 100644 --- a/src/include/utils/agtype.h +++ b/src/include/utils/agtype.h @@ -567,8 +567,17 @@ int compare_agtype_containers_orderability(agtype_container *a, agtype_value *find_agtype_value_from_container(agtype_container *container, uint32 flags, agtype_value *key); +bool find_agtype_value_from_container_no_copy(agtype_container *container, + uint32 flags, + agtype_value *key, + agtype_value *result, + bool *needs_free); agtype_value *get_ith_agtype_value_from_container(agtype_container *container, uint32 i); +bool get_ith_agtype_value_from_container_no_copy(agtype_container *container, + uint32 i, + agtype_value *result, + bool *needs_free); enum agtype_value_type get_ith_agtype_value_type(agtype_container *container, uint32 i); agtype_value *push_agtype_value(agtype_parse_state **pstate, @@ -626,6 +635,7 @@ Datum boolean_to_agtype(bool b); void uniqueify_agtype_object(agtype_value *object); char *agtype_value_type_to_string(enum agtype_value_type type); bool is_decimal_needed(char *numstr); +bool is_decimal_needed_len(const char *numstr, int len); int compare_agtype_scalar_values(agtype_value *a, agtype_value *b); agtype_value *alter_property_value(agtype_value *properties, char *var_name, agtype *new_v, bool remove_property); @@ -639,8 +649,7 @@ Datum make_vertex(Datum id, Datum label, Datum properties); Datum make_edge(Datum id, Datum startid, Datum endid, Datum label, Datum properties); Datum make_path(List *path); -Datum column_get_datum(TupleDesc tupdesc, HeapTuple tuple, int column, - const char *attname, Oid typid, bool isnull); +Datum make_path_with_length(List *path, int path_len); agtype_value *agtype_value_build_vertex(graphid id, char *label, Datum properties); agtype_value *agtype_value_build_edge(graphid id, char *label, graphid end_id, diff --git a/src/include/utils/load/ag_load_edges.h b/src/include/utils/load/ag_load_edges.h index 4db00d93a..42c95d071 100644 --- a/src/include/utils/load/ag_load_edges.h +++ b/src/include/utils/load/ag_load_edges.h @@ -32,13 +32,16 @@ * graph_name - Name of the graph * graph_oid - OID of the graph * label_name - Name of the edge label + * label_relid - OID of the edge label relation + * label_seq_relid - OID of the edge label sequence relation * label_id - ID of the label * load_as_agtype - If true, parse CSV values as agtype (JSON-like) * * Returns EXIT_SUCCESS on success. */ int create_edges_from_csv_file(char *file_path, char *graph_name, Oid graph_oid, - char *label_name, int label_id, + char *label_name, Oid label_relid, + Oid label_seq_relid, int label_id, bool load_as_agtype); #endif /* AG_LOAD_EDGES_H */ diff --git a/src/include/utils/load/ag_load_labels.h b/src/include/utils/load/ag_load_labels.h index c3d517f30..c3656e12b 100644 --- a/src/include/utils/load/ag_load_labels.h +++ b/src/include/utils/load/ag_load_labels.h @@ -31,6 +31,8 @@ * graph_name - Name of the graph * graph_oid - OID of the graph * label_name - Name of the vertex label + * label_relid - OID of the vertex label relation + * label_seq_relid - OID of the vertex label sequence relation * label_id - ID of the label * id_field_exists - If true, first CSV column contains the vertex ID * load_as_agtype - If true, parse CSV values as agtype (JSON-like) @@ -38,7 +40,8 @@ * Returns EXIT_SUCCESS on success. */ int create_labels_from_csv_file(char *file_path, char *graph_name, Oid graph_oid, - char *label_name, int label_id, + char *label_name, Oid label_relid, + Oid label_seq_relid, int label_id, bool id_field_exists, bool load_as_agtype); #endif /* AG_LOAD_LABELS_H */ diff --git a/src/include/utils/load/age_load.h b/src/include/utils/load/age_load.h index 6573c79f3..3bd5b46ce 100644 --- a/src/include/utils/load/age_load.h +++ b/src/include/utils/load/age_load.h @@ -31,8 +31,13 @@ #include "commands/graph_commands.h" #include "utils/ag_cache.h" -#define BATCH_SIZE 1000 -#define MAX_BUFFERED_BYTES 65535 /* 64KB, same as pg COPY */ +/* + * Keep enough rows buffered for heap_multi_insert() to amortize executor, + * index, and command-counter overhead during CSV loads. The byte cap prevents + * very wide property maps from accumulating unbounded memory. + */ +#define BATCH_SIZE 4096 +#define MAX_BUFFERED_BYTES (4 * 1024 * 1024) typedef struct batch_insert_state { @@ -58,8 +63,7 @@ void insert_edge_simple(Oid graph_oid, char *label_name, graphid edge_id, graphid start_id, graphid end_id, agtype *edge_properties); -void init_batch_insert(batch_insert_state **batch_state, - char *label_name, Oid graph_oid); +void init_batch_insert(batch_insert_state **batch_state, Oid relid); void insert_batch(batch_insert_state *batch_state); void finish_batch_insert(batch_insert_state **batch_state);