diff --git a/age--1.6.0--y.y.y.sql b/age--1.6.0--y.y.y.sql index 2d693a433..4bb79fe73 100644 --- a/age--1.6.0--y.y.y.sql +++ b/age--1.6.0--y.y.y.sql @@ -51,3 +51,223 @@ CREATE FUNCTION ag_catalog._ag_enforce_edge_uniqueness4(graphid, graphid, graphi STABLE PARALLEL SAFE as 'MODULE_PATHNAME'; + +-- +-- Composite types for vertex and edge +-- +CREATE TYPE ag_catalog.vertex AS ( + id graphid, + label agtype, + properties agtype +); + +CREATE TYPE ag_catalog.edge AS ( + id graphid, + label agtype, + end_id graphid, + start_id graphid, + properties agtype +); + +-- +-- vertex/edge to agtype cast functions +-- +CREATE FUNCTION ag_catalog.vertex_to_agtype(vertex) + RETURNS agtype + LANGUAGE c + IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION ag_catalog.edge_to_agtype(edge) + RETURNS agtype + LANGUAGE c + IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- +-- Implicit casts from vertex/edge to agtype +-- +CREATE CAST (vertex AS agtype) + WITH FUNCTION ag_catalog.vertex_to_agtype(vertex) +AS IMPLICIT; + +CREATE CAST (edge AS agtype) + WITH FUNCTION ag_catalog.edge_to_agtype(edge) +AS IMPLICIT; + +CREATE FUNCTION ag_catalog.vertex_to_json(vertex) + RETURNS json + LANGUAGE c + IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION ag_catalog.edge_to_json(edge) + RETURNS json + LANGUAGE c + IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (vertex AS json) + WITH FUNCTION ag_catalog.vertex_to_json(vertex); + +CREATE CAST (edge AS json) + WITH FUNCTION ag_catalog.edge_to_json(edge); + +CREATE FUNCTION ag_catalog.vertex_to_jsonb(vertex) + RETURNS jsonb + LANGUAGE c + IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION ag_catalog.edge_to_jsonb(edge) + RETURNS jsonb + LANGUAGE c + IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (vertex AS jsonb) + WITH FUNCTION ag_catalog.vertex_to_jsonb(vertex); + +CREATE CAST (edge AS jsonb) + WITH FUNCTION ag_catalog.edge_to_jsonb(edge); + +-- +-- Equality operators for vertex and edge (compare by id) +-- +CREATE FUNCTION ag_catalog.vertex_eq(vertex, vertex) + RETURNS boolean + LANGUAGE sql + IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS $$ SELECT $1.id = $2.id $$; + +CREATE OPERATOR = ( + FUNCTION = ag_catalog.vertex_eq, + LEFTARG = vertex, + RIGHTARG = vertex, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES, + MERGES +); + +CREATE FUNCTION ag_catalog.vertex_ne(vertex, vertex) + RETURNS boolean + LANGUAGE sql + IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS $$ SELECT $1.id <> $2.id $$; + +CREATE OPERATOR <> ( + FUNCTION = ag_catalog.vertex_ne, + LEFTARG = vertex, + RIGHTARG = vertex, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +CREATE FUNCTION ag_catalog.edge_eq(edge, edge) + RETURNS boolean + LANGUAGE sql + IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS $$ SELECT $1.id = $2.id $$; + +CREATE OPERATOR = ( + FUNCTION = ag_catalog.edge_eq, + LEFTARG = edge, + RIGHTARG = edge, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES, + MERGES +); + +CREATE FUNCTION ag_catalog.edge_ne(edge, edge) + RETURNS boolean + LANGUAGE sql + IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS $$ SELECT $1.id <> $2.id $$; + +CREATE OPERATOR <> ( + FUNCTION = ag_catalog.edge_ne, + LEFTARG = edge, + RIGHTARG = edge, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +-- +-- Drop and recreate _label_name with new return type (cstring -> agtype) +-- +DROP FUNCTION IF EXISTS ag_catalog._label_name(oid, graphid); + +CREATE FUNCTION ag_catalog._label_name(graph_oid oid, graphid) + RETURNS agtype + LANGUAGE c + IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- +-- Drop and recreate _agtype_build_vertex with new signature (cstring -> agtype for label) +-- +DROP FUNCTION IF EXISTS ag_catalog._agtype_build_vertex(graphid, cstring, agtype); + +CREATE FUNCTION ag_catalog._agtype_build_vertex(graphid, agtype, agtype) + RETURNS agtype + LANGUAGE c + IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- +-- Drop and recreate _agtype_build_edge with new signature (cstring -> agtype for label) +-- +DROP FUNCTION IF EXISTS ag_catalog._agtype_build_edge(graphid, graphid, graphid, cstring, agtype); + +CREATE FUNCTION ag_catalog._agtype_build_edge(graphid, graphid, graphid, + agtype, agtype) + RETURNS agtype + LANGUAGE c + IMMUTABLE +CALLED ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- +-- Helper function for optimized startNode/endNode +-- +CREATE FUNCTION ag_catalog._get_vertex_by_graphid(text, graphid) + RETURNS agtype + LANGUAGE c + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; diff --git a/regress/expected/agtype.out b/regress/expected/agtype.out index 065f357f1..110ea3637 100644 --- a/regress/expected/agtype.out +++ b/regress/expected/agtype.out @@ -3251,39 +3251,39 @@ NOTICE: graph "agtype_null_duplicate_test" has been dropped -- Vertex -- --Basic Vertex Creation -SELECT _agtype_build_vertex('1'::graphid, $$label_name$$, agtype_build_map()); +SELECT _agtype_build_vertex('1'::graphid, '"label_name"', agtype_build_map()); _agtype_build_vertex ------------------------------------------------------------ {"id": 1, "label": "label_name", "properties": {}}::vertex (1 row) -SELECT _agtype_build_vertex('1'::graphid, $$label$$, agtype_build_map('id', 2)); +SELECT _agtype_build_vertex('1'::graphid, '"label"', agtype_build_map('id', 2)); _agtype_build_vertex -------------------------------------------------------------- {"id": 1, "label": "label", "properties": {"id": 2}}::vertex (1 row) --Null properties -SELECT _agtype_build_vertex('1'::graphid, $$label_name$$, NULL); +SELECT _agtype_build_vertex('1'::graphid, '"label_name"', NULL); _agtype_build_vertex ------------------------------------------------------------ {"id": 1, "label": "label_name", "properties": {}}::vertex (1 row) --Test access operator -SELECT agtype_access_operator(_agtype_build_vertex('1'::graphid, $$label$$, +SELECT agtype_access_operator(_agtype_build_vertex('1'::graphid, '"label"', agtype_build_map('id', 2)), '"id"'); agtype_access_operator ------------------------ 2 (1 row) -SELECT _agtype_build_vertex('1'::graphid, $$label$$, agtype_build_list()); +SELECT _agtype_build_vertex('1'::graphid, '"label"', agtype_build_list()); ERROR: _agtype_build_vertex() properties argument must be an object --Vertex in a map SELECT agtype_build_map( 'vertex', - _agtype_build_vertex('1'::graphid, $$label_name$$, agtype_build_map())); + _agtype_build_vertex('1'::graphid, '"label_name"', agtype_build_map())); agtype_build_map ------------------------------------------------------------------------ {"vertex": {"id": 1, "label": "label_name", "properties": {}}::vertex} @@ -3291,9 +3291,9 @@ SELECT agtype_build_map( SELECT agtype_access_operator( agtype_build_map( - 'vertex', _agtype_build_vertex('1'::graphid, $$label_name$$, + 'vertex', _agtype_build_vertex('1'::graphid, '"label_name"', agtype_build_map('key', 'value')), - 'other_vertex', _agtype_build_vertex('1'::graphid, $$label_name$$, + 'other_vertex', _agtype_build_vertex('1'::graphid, '"label_name"', agtype_build_map('key', 'other_value'))), '"vertex"'); agtype_access_operator @@ -3303,8 +3303,8 @@ SELECT agtype_access_operator( --Vertex in a list SELECT agtype_build_list( - _agtype_build_vertex('1'::graphid, $$label_name$$, agtype_build_map()), - _agtype_build_vertex('2'::graphid, $$label_name$$, agtype_build_map())); + _agtype_build_vertex('1'::graphid, '"label_name"', agtype_build_map()), + _agtype_build_vertex('2'::graphid, '"label_name"', agtype_build_map())); agtype_build_list -------------------------------------------------------------------------------------------------------------------------- [{"id": 1, "label": "label_name", "properties": {}}::vertex, {"id": 2, "label": "label_name", "properties": {}}::vertex] @@ -3312,9 +3312,9 @@ SELECT agtype_build_list( SELECT agtype_access_operator( agtype_build_list( - _agtype_build_vertex('1'::graphid, $$label_name$$, + _agtype_build_vertex('1'::graphid, '"label_name"', agtype_build_map('id', 3)), - _agtype_build_vertex('2'::graphid, $$label_name$$, + _agtype_build_vertex('2'::graphid, '"label_name"', agtype_build_map('id', 4))), '0'); agtype_access_operator ------------------------------------------------------------------- @@ -3326,14 +3326,14 @@ SELECT agtype_access_operator( -- --Basic Edge Creation SELECT _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label_name$$, agtype_build_map()); + '"label_name"', agtype_build_map()); _agtype_build_edge -------------------------------------------------------------------------------------- {"id": 1, "label": "label_name", "end_id": 3, "start_id": 2, "properties": {}}::edge (1 row) SELECT _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label$$, agtype_build_map('id', 2)); + '"label"', agtype_build_map('id', 2)); _agtype_build_edge ---------------------------------------------------------------------------------------- {"id": 1, "label": "label", "end_id": 3, "start_id": 2, "properties": {"id": 2}}::edge @@ -3341,7 +3341,7 @@ SELECT _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, --Null properties SELECT _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label_name$$, NULL); + '"label_name"', NULL); _agtype_build_edge -------------------------------------------------------------------------------------- {"id": 1, "label": "label_name", "end_id": 3, "start_id": 2, "properties": {}}::edge @@ -3349,7 +3349,7 @@ SELECT _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, --Test access operator SELECT agtype_access_operator(_agtype_build_edge('1'::graphid, '2'::graphid, - '3'::graphid, $$label$$, agtype_build_map('id', 2)),'"id"'); + '3'::graphid, '"label"', agtype_build_map('id', 2)),'"id"'); agtype_access_operator ------------------------ 2 @@ -3359,7 +3359,7 @@ SELECT agtype_access_operator(_agtype_build_edge('1'::graphid, '2'::graphid, SELECT agtype_build_map( 'edge', _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label_name$$, agtype_build_map())); + '"label_name"', agtype_build_map())); agtype_build_map ------------------------------------------------------------------------------------------------ {"edge": {"id": 1, "label": "label_name", "end_id": 3, "start_id": 2, "properties": {}}::edge} @@ -3368,9 +3368,9 @@ SELECT agtype_build_map( SELECT agtype_access_operator( agtype_build_map( 'edge', _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label_name$$, agtype_build_map('key', 'value')), + '"label_name"', agtype_build_map('key', 'value')), 'other_edge', _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label_name$$, agtype_build_map('key', 'other_value'))), + '"label_name"', agtype_build_map('key', 'other_value'))), '"edge"'); agtype_access_operator ---------------------------------------------------------------------------------------------------- @@ -3380,9 +3380,9 @@ SELECT agtype_access_operator( --Edge in a list SELECT agtype_build_list( _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label_name$$, agtype_build_map()), + '"label_name"', agtype_build_map()), _agtype_build_edge('2'::graphid, '2'::graphid, '3'::graphid, - $$label_name$$, agtype_build_map())); + '"label_name"', agtype_build_map())); agtype_build_list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ [{"id": 1, "label": "label_name", "end_id": 3, "start_id": 2, "properties": {}}::edge, {"id": 2, "label": "label_name", "end_id": 3, "start_id": 2, "properties": {}}::edge] @@ -3390,9 +3390,9 @@ SELECT agtype_build_list( SELECT agtype_access_operator( agtype_build_list( - _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, $$label_name$$, + _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, '"label_name"', agtype_build_map('id', 3)), - _agtype_build_edge('2'::graphid, '2'::graphid, '3'::graphid, $$label_name$$, + _agtype_build_edge('2'::graphid, '2'::graphid, '3'::graphid, '"label_name"', agtype_build_map('id', 4))), '0'); agtype_access_operator --------------------------------------------------------------------------------------------- @@ -3401,10 +3401,10 @@ SELECT agtype_access_operator( -- Path SELECT _agtype_build_path( - _agtype_build_vertex('2'::graphid, $$label_name$$, agtype_build_map()), + _agtype_build_vertex('2'::graphid, '"label_name"', agtype_build_map()), _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label$$, agtype_build_map('id', 2)), - _agtype_build_vertex('3'::graphid, $$label_name$$, agtype_build_map()) + '"label"', agtype_build_map('id', 2)), + _agtype_build_vertex('3'::graphid, '"label_name"', agtype_build_map()) ); _agtype_build_path ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ @@ -3413,78 +3413,78 @@ SELECT _agtype_build_path( --All these paths should produce Errors SELECT _agtype_build_path( - _agtype_build_vertex('2'::graphid, $$label_name$$, agtype_build_map()), + _agtype_build_vertex('2'::graphid, '"label_name"', agtype_build_map()), _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label$$, agtype_build_map('id', 2)) + '"label"', agtype_build_map('id', 2)) ); ERROR: a path is of the form: [vertex, (edge, vertex)*i] where i >= 0 SELECT _agtype_build_path( - _agtype_build_vertex('2'::graphid, $$label_name$$, agtype_build_map()), + _agtype_build_vertex('2'::graphid, '"label_name"', agtype_build_map()), _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label$$, agtype_build_map('id', 2)), - _agtype_build_vertex('3'::graphid, $$label_name$$, agtype_build_map()), + '"label"', agtype_build_map('id', 2)), + _agtype_build_vertex('3'::graphid, '"label_name"', agtype_build_map()), _agtype_build_edge('1'::graphid, '4'::graphid, '5'::graphid, - $$label$$, agtype_build_map('id', 2)) + '"label"', agtype_build_map('id', 2)) ); ERROR: a path is of the form: [vertex, (edge, vertex)*i] where i >= 0 SELECT _agtype_build_path( - _agtype_build_vertex('2'::graphid, $$label_name$$, agtype_build_map()), + _agtype_build_vertex('2'::graphid, '"label_name"', agtype_build_map()), _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label$$, agtype_build_map('id', 2)), + '"label"', agtype_build_map('id', 2)), NULL ); ERROR: argument 3 must not be null SELECT _agtype_build_path( - _agtype_build_vertex('2'::graphid, $$label_name$$, agtype_build_map()), + _agtype_build_vertex('2'::graphid, '"label_name"', agtype_build_map()), _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label$$, agtype_build_map('id', 2)), + '"label"', agtype_build_map('id', 2)), 1 ); ERROR: argument 3 must be an agtype SELECT _agtype_build_path( - _agtype_build_vertex('2'::graphid, $$label_name$$, agtype_build_map()), + _agtype_build_vertex('2'::graphid, '"label_name"', agtype_build_map()), _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label$$, agtype_build_map('id', 2)), + '"label"', agtype_build_map('id', 2)), _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label$$, agtype_build_map('id', 2)) + '"label"', agtype_build_map('id', 2)) ); ERROR: paths consist of alternating vertices and edges HINT: argument 3 must be an vertex -- -- id, startid, endid -- -SELECT age_id(_agtype_build_vertex('1'::graphid, $$label_name$$, agtype_build_map())); +SELECT age_id(_agtype_build_vertex('1'::graphid, '"label_name"', agtype_build_map())); age_id -------- 1 (1 row) SELECT age_id(_agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label_name$$, agtype_build_map('id', 2))); + '"label_name"', agtype_build_map('id', 2))); age_id -------- 1 (1 row) SELECT age_start_id(_agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label_name$$, agtype_build_map('id', 2))); + '"label_name"', agtype_build_map('id', 2))); age_start_id -------------- 2 (1 row) SELECT age_end_id(_agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label_name$$, agtype_build_map('id', 2))); + '"label_name"', agtype_build_map('id', 2))); age_end_id ------------ 3 (1 row) SELECT age_id(_agtype_build_path( - _agtype_build_vertex('2'::graphid, $$label_name$$, agtype_build_map()), + _agtype_build_vertex('2'::graphid, '"label_name"', agtype_build_map()), _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label$$, agtype_build_map('id', 2)), - _agtype_build_vertex('3'::graphid, $$label$$, agtype_build_map('id', 2)) + '"label"', agtype_build_map('id', 2)), + _agtype_build_vertex('3'::graphid, '"label"', agtype_build_map('id', 2)) )); ERROR: id() argument must be a vertex, an edge or null SELECT age_id(agtype_in('1')); diff --git a/regress/expected/cypher_match.out b/regress/expected/cypher_match.out index a0e284beb..3b8a0fbc9 100644 --- a/regress/expected/cypher_match.out +++ b/regress/expected/cypher_match.out @@ -710,10 +710,10 @@ $$) AS (r0 agtype); --------------------------------------------------------------------------------------------------------------------------- {"id": 1407374883553282, "label": "e1", "end_id": 1125899906842626, "start_id": 1125899906842625, "properties": {}}::edge {"id": 1407374883553281, "label": "e1", "end_id": 1125899906842627, "start_id": 1125899906842626, "properties": {}}::edge - {"id": 1970324836974594, "label": "e2", "end_id": 1688849860263937, "start_id": 1688849860263938, "properties": {}}::edge {"id": 1970324836974593, "label": "e2", "end_id": 1688849860263939, "start_id": 1688849860263938, "properties": {}}::edge - {"id": 2533274790395905, "label": "e3", "end_id": 2251799813685250, "start_id": 2251799813685251, "properties": {}}::edge + {"id": 1970324836974594, "label": "e2", "end_id": 1688849860263937, "start_id": 1688849860263938, "properties": {}}::edge {"id": 2533274790395906, "label": "e3", "end_id": 2251799813685250, "start_id": 2251799813685249, "properties": {}}::edge + {"id": 2533274790395905, "label": "e3", "end_id": 2251799813685250, "start_id": 2251799813685251, "properties": {}}::edge (6 rows) SELECT * FROM cypher('cypher_match', $$ @@ -730,8 +730,8 @@ SELECT * FROM cypher('cypher_match', $$ $$) AS (r0 agtype); r0 --------------------------------------------------------------------------------------------------------------------------- - {"id": 1970324836974594, "label": "e2", "end_id": 1688849860263937, "start_id": 1688849860263938, "properties": {}}::edge {"id": 1970324836974593, "label": "e2", "end_id": 1688849860263939, "start_id": 1688849860263938, "properties": {}}::edge + {"id": 1970324836974594, "label": "e2", "end_id": 1688849860263937, "start_id": 1688849860263938, "properties": {}}::edge (2 rows) SELECT * FROM cypher('cypher_match', $$ diff --git a/regress/expected/cypher_with.out b/regress/expected/cypher_with.out index 99ea320a0..8864f026f 100644 --- a/regress/expected/cypher_with.out +++ b/regress/expected/cypher_with.out @@ -357,6 +357,318 @@ NOTICE: graph "graph" has been dropped (1 row) +-- +-- Test accessor optimizations in WITH clause +-- +SELECT * FROM create_graph('with_accessor_opt'); +NOTICE: graph "with_accessor_opt" has been created + create_graph +-------------- + +(1 row) + +SELECT * FROM cypher('with_accessor_opt', $$ + CREATE (a:Person {name: 'Alice', age: 30})-[r:KNOWS {since: 2020}]->(b:Person {name: 'Bob', age: 25}) +$$) AS (result agtype); + result +-------- +(0 rows) + +-- Single with +SELECT * FROM cypher('with_accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + WITH id(n) AS node_id + RETURN node_id +$$) AS (node_id agtype); + QUERY PLAN +------------------------------------------ + Seq Scan on with_accessor_opt."Person" n + Output: (n.id)::agtype +(2 rows) + +SELECT * FROM cypher('with_accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + WITH properties(n) AS props + RETURN props +$$) AS (props agtype); + QUERY PLAN +------------------------------------------ + Seq Scan on with_accessor_opt."Person" n + Output: n.properties +(2 rows) + +SELECT * FROM cypher('with_accessor_opt', $$ + MATCH (n:Person) + WITH label(n) AS lbl + RETURN lbl +$$) AS (lbl agtype); + lbl +---------- + "Person" + "Person" +(2 rows) + +SELECT * FROM cypher('with_accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + WITH n.name AS name, n.age AS age + RETURN name, age +$$) AS (name agtype, age agtype); + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------- + Seq Scan on with_accessor_opt."Person" n + Output: agtype_access_operator(VARIADIC ARRAY[n.properties, '"name"'::agtype]), agtype_access_operator(VARIADIC ARRAY[n.properties, '"age"'::agtype]) +(2 rows) + +SELECT * FROM cypher('with_accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH ()-[r:KNOWS]->() + WITH id(r) AS rid + RETURN rid +$$) AS (rid agtype); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------- + Merge Join + Output: (r.id)::agtype + Merge Cond: (_age_default_alias_1.id = r.end_id) + -> Merge Append + Sort Key: _age_default_alias_1.id + -> Index Only Scan using _ag_label_vertex_pkey on with_accessor_opt._ag_label_vertex _age_default_alias_1_1 + Output: _age_default_alias_1_1.id + -> Index Only Scan using "Person_pkey" on with_accessor_opt."Person" _age_default_alias_1_2 + Output: _age_default_alias_1_2.id + -> Materialize + Output: r.id, r.end_id + -> Nested Loop + Output: r.id, r.end_id + -> Index Scan using "KNOWS_end_id_idx" on with_accessor_opt."KNOWS" r + Output: r.id, r.start_id, r.end_id, r.properties + -> Append + -> Seq Scan on with_accessor_opt._ag_label_vertex _age_default_alias_0_1 + Output: _age_default_alias_0_1.id + Filter: (_age_default_alias_0_1.id = r.start_id) + -> Index Only Scan using "Person_pkey" on with_accessor_opt."Person" _age_default_alias_0_2 + Output: _age_default_alias_0_2.id + Index Cond: (_age_default_alias_0_2.id = r.start_id) +(22 rows) + +SELECT * FROM cypher('with_accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + WITH n, id(n) AS nid + WITH n.name AS name, nid + RETURN name, nid +$$) AS (name agtype, nid agtype); + QUERY PLAN +-------------------------------------------------------------------------------------------------- + Seq Scan on with_accessor_opt."Person" n + Output: agtype_access_operator(VARIADIC ARRAY[n.properties, '"name"'::agtype]), (n.id)::agtype +(2 rows) + +SELECT * FROM cypher('with_accessor_opt', $$ + MATCH (n:Person) + WITH n as m + RETURN m +$$) AS (n vertex); + n +--------------------------------------------------------------------- + (844424930131969,"""Person""","{""age"": 30, ""name"": ""Alice""}") + (844424930131970,"""Person""","{""age"": 25, ""name"": ""Bob""}") +(2 rows) + +SELECT * FROM cypher('with_accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + WITH n + RETURN id(n), n.name +$$) AS (id agtype, name agtype); + QUERY PLAN +-------------------------------------------------------------------------------------------------- + Seq Scan on with_accessor_opt."Person" n + Output: (n.id)::agtype, agtype_access_operator(VARIADIC ARRAY[n.properties, '"name"'::agtype]) +(2 rows) + +SELECT * FROM cypher('with_accessor_opt', $$ + MATCH ()-[r:KNOWS]->() + WITH r + RETURN id(r), type(r) +$$) AS (id agtype, typ agtype); + id | typ +------------------+--------- + 1125899906842625 | "KNOWS" +(1 row) + +-- +-- Chained WITH tests +-- +SELECT * FROM cypher('with_accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (a:Person)-[r:KNOWS]->(b:Person) + WITH a, b, id(a) AS aid + WITH a.name AS aname, b.name AS bname, aid + RETURN aname, bname, aid +$$) AS (aname agtype, bname agtype, aid agtype); + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Hash Join + Output: agtype_access_operator(VARIADIC ARRAY[a.properties, '"name"'::agtype]), agtype_access_operator(VARIADIC ARRAY[b.properties, '"name"'::agtype]), (a.id)::agtype + Inner Unique: true + Hash Cond: (r.end_id = b.id) + -> Hash Join + Output: a.properties, a.id, r.end_id + Inner Unique: true + Hash Cond: (r.start_id = a.id) + -> Seq Scan on with_accessor_opt."KNOWS" r + Output: r.id, r.start_id, r.end_id, r.properties + -> Hash + Output: a.properties, a.id + -> Seq Scan on with_accessor_opt."Person" a + Output: a.properties, a.id + -> Hash + Output: b.properties, b.id + -> Seq Scan on with_accessor_opt."Person" b + Output: b.properties, b.id +(18 rows) + +SELECT * FROM cypher('with_accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + WITH n, n.age AS age + WHERE age > 20 + RETURN n.name +$$) AS (name agtype); + QUERY PLAN +-------------------------------------------------------------------------------------------------- + Seq Scan on with_accessor_opt."Person" n + Output: agtype_access_operator(VARIADIC ARRAY[n.properties, '"name"'::agtype]) + Filter: (agtype_access_operator(VARIADIC ARRAY[n.properties, '"age"'::agtype]) > '20'::agtype) +(3 rows) + +SELECT * FROM cypher('with_accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + WITH n.name as name, id(n) AS nid + ORDER BY nid + RETURN name +$$) AS (name agtype); + QUERY PLAN +---------------------------------------------------------------------------------------------- + Subquery Scan on _age_default_alias_previous_cypher_clause + Output: _age_default_alias_previous_cypher_clause.name + -> Index Scan using "Person_pkey" on with_accessor_opt."Person" n + Output: agtype_access_operator(VARIADIC ARRAY[n.properties, '"name"'::agtype]), n.id +(4 rows) + +SELECT * FROM cypher('with_accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + WITH id(n) AS id, count(*) AS cnt + RETURN id, cnt +$$) AS (id agtype, cnt agtype); + QUERY PLAN +----------------------------------------------------------------------------------------------------------------- + Subquery Scan on _age_default_alias_previous_cypher_clause + Output: (_age_default_alias_previous_cypher_clause.id)::agtype, _age_default_alias_previous_cypher_clause.cnt + -> HashAggregate + Output: n.id, (count(*))::agtype + Group Key: n.id + -> Seq Scan on with_accessor_opt."Person" n + Output: n.id, n.properties +(7 rows) + +SELECT * FROM cypher('with_accessor_opt', $$ + MATCH (n:Person) + WITH DISTINCT label(n) AS lbl + RETURN lbl +$$) AS (lbl agtype); + lbl +---------- + "Person" +(1 row) + +SELECT * FROM cypher('with_accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + WITH n + WITH n, id(n) AS nid + WITH n.name AS name, nid + RETURN name, nid +$$) AS (name agtype, nid agtype); + QUERY PLAN +-------------------------------------------------------------------------------------------------- + Seq Scan on with_accessor_opt."Person" n + Output: agtype_access_operator(VARIADIC ARRAY[n.properties, '"name"'::agtype]), (n.id)::agtype +(2 rows) + +-- MATCH -> WITH accessors -> MATCH -> RETURN +SELECT * FROM cypher('with_accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (a:Person) + WITH a, id(a) AS aid + MATCH (a)-[r:KNOWS]->(b) + RETURN aid, b.name +$$) AS (aid agtype, bname agtype); + QUERY PLAN +-------------------------------------------------------------------------------------------------- + Merge Join + Output: (a.id)::agtype, agtype_access_operator(VARIADIC ARRAY[b.properties, '"name"'::agtype]) + Merge Cond: (b.id = r.end_id) + -> Merge Append + Sort Key: b.id + -> Index Scan using _ag_label_vertex_pkey on with_accessor_opt._ag_label_vertex b_1 + Output: b_1.properties, b_1.id + -> Index Scan using "Person_pkey" on with_accessor_opt."Person" b_2 + Output: b_2.properties, b_2.id + -> Sort + Output: a.id, r.end_id + Sort Key: r.end_id + -> Hash Join + Output: a.id, r.end_id + Inner Unique: true + Hash Cond: (r.start_id = a.id) + -> Seq Scan on with_accessor_opt."KNOWS" r + Output: r.id, r.start_id, r.end_id, r.properties + -> Hash + Output: a.id + -> Seq Scan on with_accessor_opt."Person" a + Output: a.id +(22 rows) + +-- WITH + UNWIND with accessors +SELECT * FROM cypher('with_accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + WITH collect(id(n)) AS ids + UNWIND ids AS nid + RETURN nid +$$) AS (nid agtype); + QUERY PLAN +--------------------------------------------------------------- + Subquery Scan on _age_default_alias_previous_cypher_clause + Output: _age_default_alias_previous_cypher_clause.nid + -> ProjectSet + Output: NULL::agtype, age_unnest((age_collect(n.id))) + -> Aggregate + Output: age_collect(n.id) + -> Seq Scan on with_accessor_opt."Person" n + Output: n.id, n.properties +(8 rows) + +-- Clean up +SELECT drop_graph('with_accessor_opt', true); +NOTICE: drop cascades to 4 other objects +DETAIL: drop cascades to table with_accessor_opt._ag_label_vertex +drop cascades to table with_accessor_opt._ag_label_edge +drop cascades to table with_accessor_opt."Person" +drop cascades to table with_accessor_opt."KNOWS" +NOTICE: graph "with_accessor_opt" has been dropped + drop_graph +------------ + +(1 row) + -- -- End of test -- diff --git a/regress/expected/expr.out b/regress/expected/expr.out index 6d9341451..99412ee98 100644 --- a/regress/expected/expr.out +++ b/regress/expected/expr.out @@ -1544,12 +1544,16 @@ SELECT * FROM cypher('type_coercion', $$ MATCH (v) RETURN v $$) AS (i bigint); -ERROR: cannot cast agtype vertex to type int +ERROR: cannot cast type vertex to bigint for column "i" +LINE 1: SELECT * FROM cypher('type_coercion', $$ + ^ SELECT * FROM cypher('type_coercion', $$ MATCH ()-[e]-() RETURN e $$) AS (i bigint); -ERROR: cannot cast agtype edge to type int +ERROR: cannot cast type edge to bigint for column "i" +LINE 1: SELECT * FROM cypher('type_coercion', $$ + ^ SELECT * FROM cypher('type_coercion', $$ MATCH p=()-[]-() RETURN p @@ -2762,6 +2766,8 @@ SELECT * FROM cypher('expr', $$ MATCH (v) RETURN start_id(v) $$) AS (start_id agtype); ERROR: start_id() argument must be an edge or null +LINE 2: MATCH (v) RETURN start_id(v) + ^ SELECT * FROM cypher('expr', $$ RETURN start_id() $$) AS (start_id agtype); @@ -2795,6 +2801,8 @@ SELECT * FROM cypher('expr', $$ MATCH (v) RETURN end_id(v) $$) AS (end_id agtype); ERROR: end_id() argument must be an edge or null +LINE 2: MATCH (v) RETURN end_id(v) + ^ SELECT * FROM cypher('expr', $$ RETURN end_id() $$) AS (end_id agtype); @@ -2828,6 +2836,8 @@ SELECT * FROM cypher('expr', $$ MATCH (v) RETURN startNode(v) $$) AS (startNode agtype); ERROR: startNode() argument must be an edge or null +LINE 2: MATCH (v) RETURN startNode(v) + ^ SELECT * FROM cypher('expr', $$ RETURN startNode() $$) AS (startNode agtype); @@ -2861,6 +2871,8 @@ SELECT * FROM cypher('expr', $$ MATCH (v) RETURN endNode(v) $$) AS (endNode agtype); ERROR: endNode() argument must be an edge or null +LINE 2: MATCH (v) RETURN endNode(v) + ^ SELECT * FROM cypher('expr', $$ RETURN endNode() $$) AS (endNode agtype); @@ -2894,6 +2906,8 @@ SELECT * FROM cypher('expr', $$ MATCH (v) RETURN type(v) $$) AS (type agtype); ERROR: type() argument must be an edge or null +LINE 2: MATCH (v) RETURN type(v) + ^ SELECT * FROM cypher('expr', $$ RETURN type() $$) AS (type agtype); @@ -4139,7 +4153,7 @@ SELECT * FROM cypher('expr', $$ MATCH (v) RETURN reverse(v) $$) AS (results agtype); -ERROR: reverse() unsupported argument agtype 6 +ERROR: reverse() unsupported argument type 16835 SELECT * FROM cypher('expr', $$ RETURN reverse({}) $$) AS (results agtype); @@ -7436,12 +7450,12 @@ SELECT * FROM cypher('case_statement', $$ $$ ) AS (n agtype, case_statement agtype); n | case_statement ------------------------------------------------------------------------------------------------+---------------- - {"id": 281474976710658, "label": "", "properties": {"i": "a", "j": "b", "id": 2}}::vertex | "not count" {"id": 281474976710661, "label": "", "properties": {"i": [], "j": [0, 1, 2], "id": 5}}::vertex | "not count" {"id": 281474976710657, "label": "", "properties": {"i": 1, "id": 1}}::vertex | "not count" {"id": 281474976710659, "label": "", "properties": {"i": 0, "j": 1, "id": 3}}::vertex | 1 - {"id": 281474976710660, "label": "", "properties": {"i": true, "j": false, "id": 4}}::vertex | "not count" + {"id": 281474976710658, "label": "", "properties": {"i": "a", "j": "b", "id": 2}}::vertex | "not count" {"id": 281474976710662, "label": "", "properties": {"i": {}, "j": {"i": 1}, "id": 6}}::vertex | "not count" + {"id": 281474976710660, "label": "", "properties": {"i": true, "j": false, "id": 4}}::vertex | "not count" (6 rows) --concatenated @@ -7454,12 +7468,12 @@ SELECT * FROM cypher('case_statement', $$ $$ ) AS (n agtype, case_statement agtype); n | case_statement ------------------------------------------------------------------------------------------------+---------------- - {"id": 281474976710658, "label": "", "properties": {"i": "a", "j": "b", "id": 2}}::vertex | "not count" {"id": 281474976710661, "label": "", "properties": {"i": [], "j": [0, 1, 2], "id": 5}}::vertex | "not count" {"id": 281474976710657, "label": "", "properties": {"i": 1, "id": 1}}::vertex | "not count" {"id": 281474976710659, "label": "", "properties": {"i": 0, "j": 1, "id": 3}}::vertex | 6 - {"id": 281474976710660, "label": "", "properties": {"i": true, "j": false, "id": 4}}::vertex | "not count" + {"id": 281474976710658, "label": "", "properties": {"i": "a", "j": "b", "id": 2}}::vertex | "not count" {"id": 281474976710662, "label": "", "properties": {"i": {}, "j": {"i": 1}, "id": 6}}::vertex | "not count" + {"id": 281474976710660, "label": "", "properties": {"i": true, "j": false, "id": 4}}::vertex | "not count" (6 rows) --count(n) @@ -7472,12 +7486,12 @@ SELECT * FROM cypher('case_statement', $$ $$ ) AS (n agtype, case_statement agtype); n | case_statement ------------------------------------------------------------------------------------------------+---------------- - {"id": 281474976710658, "label": "", "properties": {"i": "a", "j": "b", "id": 2}}::vertex | "not count" {"id": 281474976710661, "label": "", "properties": {"i": [], "j": [0, 1, 2], "id": 5}}::vertex | "not count" {"id": 281474976710657, "label": "", "properties": {"i": 1, "id": 1}}::vertex | "not count" {"id": 281474976710659, "label": "", "properties": {"i": 0, "j": 1, "id": 3}}::vertex | 1 - {"id": 281474976710660, "label": "", "properties": {"i": true, "j": false, "id": 4}}::vertex | "not count" + {"id": 281474976710658, "label": "", "properties": {"i": "a", "j": "b", "id": 2}}::vertex | "not count" {"id": 281474976710662, "label": "", "properties": {"i": {}, "j": {"i": 1}, "id": 6}}::vertex | "not count" + {"id": 281474976710660, "label": "", "properties": {"i": true, "j": false, "id": 4}}::vertex | "not count" (6 rows) --concatenated @@ -7490,12 +7504,12 @@ SELECT * FROM cypher('case_statement', $$ $$ ) AS (n agtype, case_statement agtype); n | case_statement ------------------------------------------------------------------------------------------------+---------------- - {"id": 281474976710658, "label": "", "properties": {"i": "a", "j": "b", "id": 2}}::vertex | "not count" {"id": 281474976710661, "label": "", "properties": {"i": [], "j": [0, 1, 2], "id": 5}}::vertex | "not count" {"id": 281474976710657, "label": "", "properties": {"i": 1, "id": 1}}::vertex | "not count" {"id": 281474976710659, "label": "", "properties": {"i": 0, "j": 1, "id": 3}}::vertex | 6 - {"id": 281474976710660, "label": "", "properties": {"i": true, "j": false, "id": 4}}::vertex | "not count" + {"id": 281474976710658, "label": "", "properties": {"i": "a", "j": "b", "id": 2}}::vertex | "not count" {"id": 281474976710662, "label": "", "properties": {"i": {}, "j": {"i": 1}, "id": 6}}::vertex | "not count" + {"id": 281474976710660, "label": "", "properties": {"i": true, "j": false, "id": 4}}::vertex | "not count" (6 rows) --count(1) @@ -7508,12 +7522,12 @@ SELECT * FROM cypher('case_statement', $$ $$ ) AS (n agtype, case_statement agtype); n | case_statement ------------------------------------------------------------------------------------------------+---------------- - {"id": 281474976710658, "label": "", "properties": {"i": "a", "j": "b", "id": 2}}::vertex | "not count" {"id": 281474976710661, "label": "", "properties": {"i": [], "j": [0, 1, 2], "id": 5}}::vertex | "not count" {"id": 281474976710657, "label": "", "properties": {"i": 1, "id": 1}}::vertex | "not count" {"id": 281474976710659, "label": "", "properties": {"i": 0, "j": 1, "id": 3}}::vertex | 1 - {"id": 281474976710660, "label": "", "properties": {"i": true, "j": false, "id": 4}}::vertex | "not count" + {"id": 281474976710658, "label": "", "properties": {"i": "a", "j": "b", "id": 2}}::vertex | "not count" {"id": 281474976710662, "label": "", "properties": {"i": {}, "j": {"i": 1}, "id": 6}}::vertex | "not count" + {"id": 281474976710660, "label": "", "properties": {"i": true, "j": false, "id": 4}}::vertex | "not count" (6 rows) --concatenated @@ -7526,12 +7540,12 @@ SELECT * FROM cypher('case_statement', $$ $$ ) AS (n agtype, case_statement agtype); n | case_statement ------------------------------------------------------------------------------------------------+---------------- - {"id": 281474976710658, "label": "", "properties": {"i": "a", "j": "b", "id": 2}}::vertex | "not count" {"id": 281474976710661, "label": "", "properties": {"i": [], "j": [0, 1, 2], "id": 5}}::vertex | "not count" {"id": 281474976710657, "label": "", "properties": {"i": 1, "id": 1}}::vertex | "not count" {"id": 281474976710659, "label": "", "properties": {"i": 0, "j": 1, "id": 3}}::vertex | 6 - {"id": 281474976710660, "label": "", "properties": {"i": true, "j": false, "id": 4}}::vertex | "not count" + {"id": 281474976710658, "label": "", "properties": {"i": "a", "j": "b", "id": 2}}::vertex | "not count" {"id": 281474976710662, "label": "", "properties": {"i": {}, "j": {"i": 1}, "id": 6}}::vertex | "not count" + {"id": 281474976710660, "label": "", "properties": {"i": true, "j": false, "id": 4}}::vertex | "not count" (6 rows) --CASE with EXISTS() @@ -9217,9 +9231,1103 @@ SELECT * FROM cypher('issue_2289', $$ RETURN (1 IN []) AS v $$) AS (v agtype); false (1 row) +-- +-- Accessor functions and properties extraction +-- without _agtype_build.. functions +-- +SELECT * FROM create_graph('accessor_opt'); +NOTICE: graph "accessor_opt" has been created + create_graph +-------------- + +(1 row) + +SELECT * FROM cypher('accessor_opt', $$ + CREATE (a:Person {name: 'Alice', age: 30})-[r:KNOWS {since: 2020}]->(b:Person {name: 'Bob', age: 25}) +$$) AS (a agtype); + a +--- +(0 rows) + +-- +-- Vertex accessor tests +-- +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + RETURN id(n) +$$) AS (plan agtype); + QUERY PLAN +------------------------------------- + Seq Scan on accessor_opt."Person" n + Output: (n.id)::agtype +(2 rows) + +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + RETURN properties(n) +$$) AS (plan agtype); + QUERY PLAN +------------------------------------- + Seq Scan on accessor_opt."Person" n + Output: n.properties +(2 rows) + +SELECT * FROM cypher('accessor_opt', $$ + MATCH (n:Person) + RETURN label(n) +$$) AS (plan agtype); + plan +---------- + "Person" + "Person" +(2 rows) + +-- should use n.properties in _agtype_access_operator +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + RETURN n.name, n.age +$$) AS (a agtype, b agtype); + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------- + Seq Scan on accessor_opt."Person" n + Output: agtype_access_operator(VARIADIC ARRAY[n.properties, '"name"'::agtype]), agtype_access_operator(VARIADIC ARRAY[n.properties, '"age"'::agtype]) +(2 rows) + +-- +-- Edge accessor tests +-- +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH ()-[r:KNOWS]->() + RETURN id(r) +$$) AS (plan agtype); + QUERY PLAN +----------------------------------------------------------------------------------------------------------------- + Merge Join + Output: (r.id)::agtype + Merge Cond: (_age_default_alias_1.id = r.end_id) + -> Merge Append + Sort Key: _age_default_alias_1.id + -> Index Only Scan using _ag_label_vertex_pkey on accessor_opt._ag_label_vertex _age_default_alias_1_1 + Output: _age_default_alias_1_1.id + -> Index Only Scan using "Person_pkey" on accessor_opt."Person" _age_default_alias_1_2 + Output: _age_default_alias_1_2.id + -> Materialize + Output: r.id, r.end_id + -> Nested Loop + Output: r.id, r.end_id + -> Index Scan using "KNOWS_end_id_idx" on accessor_opt."KNOWS" r + Output: r.id, r.start_id, r.end_id, r.properties + -> Append + -> Seq Scan on accessor_opt._ag_label_vertex _age_default_alias_0_1 + Output: _age_default_alias_0_1.id + Filter: (_age_default_alias_0_1.id = r.start_id) + -> Index Only Scan using "Person_pkey" on accessor_opt."Person" _age_default_alias_0_2 + Output: _age_default_alias_0_2.id + Index Cond: (_age_default_alias_0_2.id = r.start_id) +(22 rows) + +SELECT * FROM cypher('accessor_opt', $$ + MATCH ()-[r:KNOWS]->() + RETURN type(r) +$$) AS (plan agtype); + plan +--------- + "KNOWS" +(1 row) + +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH ()-[r:KNOWS]->() + RETURN start_id(r) +$$) AS (plan agtype); + QUERY PLAN +----------------------------------------------------------------------------------------------------------------- + Merge Join + Output: (r.start_id)::agtype + Merge Cond: (_age_default_alias_1.id = r.end_id) + -> Merge Append + Sort Key: _age_default_alias_1.id + -> Index Only Scan using _ag_label_vertex_pkey on accessor_opt._ag_label_vertex _age_default_alias_1_1 + Output: _age_default_alias_1_1.id + -> Index Only Scan using "Person_pkey" on accessor_opt."Person" _age_default_alias_1_2 + Output: _age_default_alias_1_2.id + -> Materialize + Output: r.start_id, r.end_id + -> Nested Loop + Output: r.start_id, r.end_id + -> Index Scan using "KNOWS_end_id_idx" on accessor_opt."KNOWS" r + Output: r.id, r.start_id, r.end_id, r.properties + -> Append + -> Seq Scan on accessor_opt._ag_label_vertex _age_default_alias_0_1 + Output: _age_default_alias_0_1.id + Filter: (_age_default_alias_0_1.id = r.start_id) + -> Index Only Scan using "Person_pkey" on accessor_opt."Person" _age_default_alias_0_2 + Output: _age_default_alias_0_2.id + Index Cond: (_age_default_alias_0_2.id = r.start_id) +(22 rows) + +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH ()-[r:KNOWS]->() + RETURN end_id(r) +$$) AS (plan agtype); + QUERY PLAN +----------------------------------------------------------------------------------------------------------------- + Merge Join + Output: (r.end_id)::agtype + Merge Cond: (_age_default_alias_1.id = r.end_id) + -> Merge Append + Sort Key: _age_default_alias_1.id + -> Index Only Scan using _ag_label_vertex_pkey on accessor_opt._ag_label_vertex _age_default_alias_1_1 + Output: _age_default_alias_1_1.id + -> Index Only Scan using "Person_pkey" on accessor_opt."Person" _age_default_alias_1_2 + Output: _age_default_alias_1_2.id + -> Materialize + Output: r.end_id + -> Nested Loop + Output: r.end_id + -> Index Scan using "KNOWS_end_id_idx" on accessor_opt."KNOWS" r + Output: r.id, r.start_id, r.end_id, r.properties + -> Append + -> Seq Scan on accessor_opt._ag_label_vertex _age_default_alias_0_1 + Output: _age_default_alias_0_1.id + Filter: (_age_default_alias_0_1.id = r.start_id) + -> Index Only Scan using "Person_pkey" on accessor_opt."Person" _age_default_alias_0_2 + Output: _age_default_alias_0_2.id + Index Cond: (_age_default_alias_0_2.id = r.start_id) +(22 rows) + +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH ()-[r:KNOWS]->() + RETURN properties(r) +$$) AS (plan agtype); + QUERY PLAN +----------------------------------------------------------------------------------------------------------------- + Merge Join + Output: r.properties + Merge Cond: (_age_default_alias_1.id = r.end_id) + -> Merge Append + Sort Key: _age_default_alias_1.id + -> Index Only Scan using _ag_label_vertex_pkey on accessor_opt._ag_label_vertex _age_default_alias_1_1 + Output: _age_default_alias_1_1.id + -> Index Only Scan using "Person_pkey" on accessor_opt."Person" _age_default_alias_1_2 + Output: _age_default_alias_1_2.id + -> Materialize + Output: r.properties, r.end_id + -> Nested Loop + Output: r.properties, r.end_id + -> Index Scan using "KNOWS_end_id_idx" on accessor_opt."KNOWS" r + Output: r.id, r.start_id, r.end_id, r.properties + -> Append + -> Seq Scan on accessor_opt._ag_label_vertex _age_default_alias_0_1 + Output: _age_default_alias_0_1.id + Filter: (_age_default_alias_0_1.id = r.start_id) + -> Index Only Scan using "Person_pkey" on accessor_opt."Person" _age_default_alias_0_2 + Output: _age_default_alias_0_2.id + Index Cond: (_age_default_alias_0_2.id = r.start_id) +(22 rows) + +-- should use r.properties in _agtype_access_operator +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH ()-[r:KNOWS]->() + RETURN r.since +$$) AS (plan agtype); + QUERY PLAN +----------------------------------------------------------------------------------------------------------------- + Merge Join + Output: agtype_access_operator(VARIADIC ARRAY[r.properties, '"since"'::agtype]) + Merge Cond: (_age_default_alias_1.id = r.end_id) + -> Merge Append + Sort Key: _age_default_alias_1.id + -> Index Only Scan using _ag_label_vertex_pkey on accessor_opt._ag_label_vertex _age_default_alias_1_1 + Output: _age_default_alias_1_1.id + -> Index Only Scan using "Person_pkey" on accessor_opt."Person" _age_default_alias_1_2 + Output: _age_default_alias_1_2.id + -> Materialize + Output: r.properties, r.end_id + -> Nested Loop + Output: r.properties, r.end_id + -> Index Scan using "KNOWS_end_id_idx" on accessor_opt."KNOWS" r + Output: r.id, r.start_id, r.end_id, r.properties + -> Append + -> Seq Scan on accessor_opt._ag_label_vertex _age_default_alias_0_1 + Output: _age_default_alias_0_1.id + Filter: (_age_default_alias_0_1.id = r.start_id) + -> Index Only Scan using "Person_pkey" on accessor_opt."Person" _age_default_alias_0_2 + Output: _age_default_alias_0_2.id + Index Cond: (_age_default_alias_0_2.id = r.start_id) +(22 rows) + +-- +-- Multiple accessors in same query +-- +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + RETURN id(n), properties(n), n.name +$$) AS (a agtype, c agtype, d agtype); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------- + Seq Scan on accessor_opt."Person" n + Output: (n.id)::agtype, n.properties, agtype_access_operator(VARIADIC ARRAY[n.properties, '"name"'::agtype]) +(2 rows) + +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH ()-[r:KNOWS]->() + RETURN id(r), start_id(r), end_id(r), properties(r) +$$) AS (a agtype, c agtype, d agtype, e agtype); + QUERY PLAN +----------------------------------------------------------------------------------------------------------------- + Merge Join + Output: (r.id)::agtype, (r.start_id)::agtype, (r.end_id)::agtype, r.properties + Merge Cond: (_age_default_alias_1.id = r.end_id) + -> Merge Append + Sort Key: _age_default_alias_1.id + -> Index Only Scan using _ag_label_vertex_pkey on accessor_opt._ag_label_vertex _age_default_alias_1_1 + Output: _age_default_alias_1_1.id + -> Index Only Scan using "Person_pkey" on accessor_opt."Person" _age_default_alias_1_2 + Output: _age_default_alias_1_2.id + -> Materialize + Output: r.id, r.start_id, r.end_id, r.properties + -> Nested Loop + Output: r.id, r.start_id, r.end_id, r.properties + -> Index Scan using "KNOWS_end_id_idx" on accessor_opt."KNOWS" r + Output: r.id, r.start_id, r.end_id, r.properties + -> Append + -> Seq Scan on accessor_opt._ag_label_vertex _age_default_alias_0_1 + Output: _age_default_alias_0_1.id + Filter: (_age_default_alias_0_1.id = r.start_id) + -> Index Only Scan using "Person_pkey" on accessor_opt."Person" _age_default_alias_0_2 + Output: _age_default_alias_0_2.id + Index Cond: (_age_default_alias_0_2.id = r.start_id) +(22 rows) + +-- +-- Accessors in WHERE clause +-- +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + WHERE id(n) > 0 + RETURN n.name +$$) AS (plan agtype); + QUERY PLAN +---------------------------------------------------------------------------------- + Bitmap Heap Scan on accessor_opt."Person" n + Output: agtype_access_operator(VARIADIC ARRAY[n.properties, '"name"'::agtype]) + Recheck Cond: (n.id > '0'::graphid) + -> Bitmap Index Scan on "Person_pkey" + Index Cond: (n.id > '0'::graphid) +(5 rows) + +-- Compare two node ids +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (a:Person)-[r:KNOWS]->(b:Person) + WHERE id(a) < id(b) + RETURN a.name, b.name +$$) AS (aname agtype, bname agtype); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------- + Hash Join + Output: agtype_access_operator(VARIADIC ARRAY[a.properties, '"name"'::agtype]), agtype_access_operator(VARIADIC ARRAY[b.properties, '"name"'::agtype]) + Inner Unique: true + Hash Cond: (r.end_id = b.id) + Join Filter: (a.id < b.id) + -> Hash Join + Output: a.properties, a.id, r.end_id + Inner Unique: true + Hash Cond: (r.start_id = a.id) + -> Seq Scan on accessor_opt."KNOWS" r + Output: r.id, r.start_id, r.end_id, r.properties + -> Hash + Output: a.properties, a.id + -> Seq Scan on accessor_opt."Person" a + Output: a.properties, a.id + -> Hash + Output: b.properties, b.id + -> Seq Scan on accessor_opt."Person" b + Output: b.properties, b.id +(19 rows) + +-- Compare edge start_id and end_id +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH ()-[r:KNOWS]->() + WHERE start_id(r) <> end_id(r) + RETURN id(r) +$$) AS (rid agtype); + QUERY PLAN +----------------------------------------------------------------------------------------------------------------- + Merge Join + Output: (r.id)::agtype + Merge Cond: (_age_default_alias_1.id = r.end_id) + -> Merge Append + Sort Key: _age_default_alias_1.id + -> Index Only Scan using _ag_label_vertex_pkey on accessor_opt._ag_label_vertex _age_default_alias_1_1 + Output: _age_default_alias_1_1.id + -> Index Only Scan using "Person_pkey" on accessor_opt."Person" _age_default_alias_1_2 + Output: _age_default_alias_1_2.id + -> Materialize + Output: r.id, r.end_id + -> Nested Loop + Output: r.id, r.end_id + -> Index Scan using "KNOWS_end_id_idx" on accessor_opt."KNOWS" r + Output: r.id, r.start_id, r.end_id, r.properties + Filter: (r.start_id <> r.end_id) + -> Append + -> Seq Scan on accessor_opt._ag_label_vertex _age_default_alias_0_1 + Output: _age_default_alias_0_1.id + Filter: (_age_default_alias_0_1.id = r.start_id) + -> Index Only Scan using "Person_pkey" on accessor_opt."Person" _age_default_alias_0_2 + Output: _age_default_alias_0_2.id + Index Cond: (_age_default_alias_0_2.id = r.start_id) +(23 rows) + +-- Property comparison +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + WHERE n.age >= 25 + RETURN n.name +$$) AS (name agtype); + QUERY PLAN +--------------------------------------------------------------------------------------------------- + Seq Scan on accessor_opt."Person" n + Output: agtype_access_operator(VARIADIC ARRAY[n.properties, '"name"'::agtype]) + Filter: (agtype_access_operator(VARIADIC ARRAY[n.properties, '"age"'::agtype]) >= '25'::agtype) +(3 rows) + +-- Multiple property comparisons +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + WHERE n.age > 20 AND n.name <> 'Unknown' + RETURN n.name +$$) AS (name agtype); + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Seq Scan on accessor_opt."Person" n + Output: agtype_access_operator(VARIADIC ARRAY[n.properties, '"name"'::agtype]) + Filter: ((agtype_access_operator(VARIADIC ARRAY[n.properties, '"age"'::agtype]) > '20'::agtype) AND (agtype_access_operator(VARIADIC ARRAY[n.properties, '"name"'::agtype]) <> '"Unknown"'::agtype)) +(3 rows) + +-- Compare properties of two nodes +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (a:Person)-[r:KNOWS]->(b:Person) + WHERE a.age > b.age + RETURN a.name, b.name +$$) AS (aname agtype, bname agtype); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------- + Hash Join + Output: agtype_access_operator(VARIADIC ARRAY[a.properties, '"name"'::agtype]), agtype_access_operator(VARIADIC ARRAY[b.properties, '"name"'::agtype]) + Inner Unique: true + Hash Cond: (r.end_id = b.id) + Join Filter: (agtype_access_operator(VARIADIC ARRAY[a.properties, '"age"'::agtype]) > agtype_access_operator(VARIADIC ARRAY[b.properties, '"age"'::agtype])) + -> Hash Join + Output: a.properties, r.end_id + Inner Unique: true + Hash Cond: (r.start_id = a.id) + -> Seq Scan on accessor_opt."KNOWS" r + Output: r.id, r.start_id, r.end_id, r.properties + -> Hash + Output: a.properties, a.id + -> Seq Scan on accessor_opt."Person" a + Output: a.properties, a.id + -> Hash + Output: b.properties, b.id + -> Seq Scan on accessor_opt."Person" b + Output: b.properties, b.id +(19 rows) + +-- Compare id with property +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + WHERE id(n) > n.age + RETURN n.name +$$) AS (name agtype); + QUERY PLAN +----------------------------------------------------------------------------------------------------- + Seq Scan on accessor_opt."Person" n + Output: agtype_access_operator(VARIADIC ARRAY[n.properties, '"name"'::agtype]) + Filter: (n.id > (agtype_access_operator(VARIADIC ARRAY[n.properties, '"age"'::agtype]))::graphid) +(3 rows) + +-- Edge property comparison +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH ()-[r:KNOWS]->() + WHERE r.since > 2015 + RETURN r.since +$$) AS (since agtype); + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------ + Merge Join + Output: agtype_access_operator(VARIADIC ARRAY[r.properties, '"since"'::agtype]) + Merge Cond: (_age_default_alias_1.id = r.end_id) + -> Merge Append + Sort Key: _age_default_alias_1.id + -> Index Only Scan using _ag_label_vertex_pkey on accessor_opt._ag_label_vertex _age_default_alias_1_1 + Output: _age_default_alias_1_1.id + -> Index Only Scan using "Person_pkey" on accessor_opt."Person" _age_default_alias_1_2 + Output: _age_default_alias_1_2.id + -> Materialize + Output: r.properties, r.end_id + -> Nested Loop + Output: r.properties, r.end_id + -> Index Scan using "KNOWS_end_id_idx" on accessor_opt."KNOWS" r + Output: r.id, r.start_id, r.end_id, r.properties + Filter: (agtype_access_operator(VARIADIC ARRAY[r.properties, '"since"'::agtype]) > '2015'::agtype) + -> Append + -> Seq Scan on accessor_opt._ag_label_vertex _age_default_alias_0_1 + Output: _age_default_alias_0_1.id + Filter: (_age_default_alias_0_1.id = r.start_id) + -> Index Only Scan using "Person_pkey" on accessor_opt."Person" _age_default_alias_0_2 + Output: _age_default_alias_0_2.id + Index Cond: (_age_default_alias_0_2.id = r.start_id) +(23 rows) + +-- IS NOT NULL on accessor +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + WHERE properties(n) IS NOT NULL + RETURN n.name +$$) AS (name agtype); + QUERY PLAN +---------------------------------------------------------------------------------- + Seq Scan on accessor_opt."Person" n + Output: agtype_access_operator(VARIADIC ARRAY[n.properties, '"name"'::agtype]) +(2 rows) + +-- label() comparison +SELECT * FROM cypher('accessor_opt', $$ + MATCH (n) + WHERE label(n) = 'Person' + RETURN n.name +$$) AS (name agtype); + name +--------- + "Alice" + "Bob" +(2 rows) + +-- type() comparison on edge +SELECT * FROM cypher('accessor_opt', $$ + MATCH ()-[r]->() + WHERE type(r) = 'KNOWS' + RETURN id(r) +$$) AS (rid agtype); + rid +------------------ + 1125899906842625 +(1 row) + +-- +-- Accessors in ORDER BY +-- +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + RETURN n.name + ORDER BY n.name +$$) AS (plan agtype); + QUERY PLAN +---------------------------------------------------------------------------------------- + Sort + Output: (agtype_access_operator(VARIADIC ARRAY[n.properties, '"name"'::agtype])) + Sort Key: (agtype_access_operator(VARIADIC ARRAY[n.properties, '"name"'::agtype])) + -> Seq Scan on accessor_opt."Person" n + Output: agtype_access_operator(VARIADIC ARRAY[n.properties, '"name"'::agtype]) +(5 rows) + +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + RETURN n.name + ORDER BY id(n) +$$) AS (plan agtype); + QUERY PLAN +---------------------------------------------------------------------------------------------- + Subquery Scan on _ + Output: _.name + -> Index Scan using "Person_pkey" on accessor_opt."Person" n + Output: agtype_access_operator(VARIADIC ARRAY[n.properties, '"name"'::agtype]), n.id +(4 rows) + +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + RETURN n.name, n.age + ORDER BY n.age, n.name +$$) AS (name agtype, age agtype); + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: (agtype_access_operator(VARIADIC ARRAY[n.properties, '"name"'::agtype])), (agtype_access_operator(VARIADIC ARRAY[n.properties, '"age"'::agtype])) + Sort Key: (agtype_access_operator(VARIADIC ARRAY[n.properties, '"age"'::agtype])), (agtype_access_operator(VARIADIC ARRAY[n.properties, '"name"'::agtype])) + -> Seq Scan on accessor_opt."Person" n + Output: agtype_access_operator(VARIADIC ARRAY[n.properties, '"name"'::agtype]), agtype_access_operator(VARIADIC ARRAY[n.properties, '"age"'::agtype]) +(5 rows) + +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH ()-[r:KNOWS]->() + RETURN r.since + ORDER BY start_id(r) +$$) AS (since agtype); + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------- + Subquery Scan on _ + Output: _.since + -> Merge Join + Output: agtype_access_operator(VARIADIC ARRAY[r.properties, '"since"'::agtype]), r.start_id + Merge Cond: (_age_default_alias_0.id = r.start_id) + -> Merge Append + Sort Key: _age_default_alias_0.id + -> Index Only Scan using _ag_label_vertex_pkey on accessor_opt._ag_label_vertex _age_default_alias_0_1 + Output: _age_default_alias_0_1.id + -> Index Only Scan using "Person_pkey" on accessor_opt."Person" _age_default_alias_0_2 + Output: _age_default_alias_0_2.id + -> Materialize + Output: r.properties, r.start_id + -> Nested Loop + Output: r.properties, r.start_id + -> Index Scan using "KNOWS_start_id_idx" on accessor_opt."KNOWS" r + Output: r.id, r.start_id, r.end_id, r.properties + -> Append + -> Seq Scan on accessor_opt._ag_label_vertex _age_default_alias_1_1 + Output: _age_default_alias_1_1.id + Filter: (_age_default_alias_1_1.id = r.end_id) + -> Index Only Scan using "Person_pkey" on accessor_opt."Person" _age_default_alias_1_2 + Output: _age_default_alias_1_2.id + Index Cond: (_age_default_alias_1_2.id = r.end_id) +(24 rows) + +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (a:Person)-[r:KNOWS]->(b:Person) + RETURN a.name, b.name + ORDER BY id(a), id(r), id(b) +$$) AS (aname agtype, bname agtype); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Subquery Scan on _ + Output: _.name, _.name_1 + -> Sort + Output: (agtype_access_operator(VARIADIC ARRAY[a.properties, '"name"'::agtype])), (agtype_access_operator(VARIADIC ARRAY[b.properties, '"name"'::agtype])), a.id, r.id, b.id + Sort Key: a.id, r.id, b.id + -> Hash Join + Output: agtype_access_operator(VARIADIC ARRAY[a.properties, '"name"'::agtype]), agtype_access_operator(VARIADIC ARRAY[b.properties, '"name"'::agtype]), a.id, r.id, b.id + Inner Unique: true + Hash Cond: (r.end_id = b.id) + -> Hash Join + Output: a.properties, a.id, r.id, r.end_id + Inner Unique: true + Hash Cond: (r.start_id = a.id) + -> Seq Scan on accessor_opt."KNOWS" r + Output: r.id, r.start_id, r.end_id, r.properties + -> Hash + Output: a.properties, a.id + -> Seq Scan on accessor_opt."Person" a + Output: a.properties, a.id + -> Hash + Output: b.properties, b.id + -> Seq Scan on accessor_opt."Person" b + Output: b.properties, b.id +(23 rows) + +-- +-- Accessors in expressions +-- +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + RETURN [id(n), n.name] +$$) AS (plan agtype); + QUERY PLAN +----------------------------------------------------------------------------------------------------------- + Seq Scan on accessor_opt."Person" n + Output: agtype_build_list(n.id, agtype_access_operator(VARIADIC ARRAY[n.properties, '"name"'::agtype])) +(2 rows) + +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + RETURN {id: id(n), name: n.name} +$$) AS (plan agtype); + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------- + Seq Scan on accessor_opt."Person" n + Output: agtype_build_map_nonull('id'::text, n.id, 'name'::text, agtype_access_operator(VARIADIC ARRAY[n.properties, '"name"'::agtype])) +(2 rows) + +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + RETURN n {.name, .age} +$$) AS (plan agtype); + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Seq Scan on accessor_opt."Person" n + Output: agtype_build_map_nonull('name'::text, agtype_access_operator(VARIADIC ARRAY[n.properties, '"name"'::agtype]), 'age'::text, agtype_access_operator(VARIADIC ARRAY[n.properties, '"age"'::agtype])) +(2 rows) + +-- +-- Accessor function in multiple match +-- +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (a:Person) + WHERE id(a) > 0 + MATCH (a)-[r]->(b) + WHERE id(b) > 0 + RETURN properties(a), start_id(r), end_id(r) +$$) AS (props_a agtype, sid agtype, eid agtype); + QUERY PLAN +------------------------------------------------------------------ + Hash Join + Output: a.properties, (r.start_id)::agtype, (r.end_id)::agtype + Hash Cond: (r.end_id = b.id) + -> Hash Join + Output: a.properties, r.start_id, r.end_id + Inner Unique: true + Hash Cond: (r.start_id = a.id) + -> Append + -> Seq Scan on accessor_opt._ag_label_edge r_1 + Output: r_1.start_id, r_1.end_id + -> Seq Scan on accessor_opt."KNOWS" r_2 + Output: r_2.start_id, r_2.end_id + -> Hash + Output: a.properties, a.id + -> Bitmap Heap Scan on accessor_opt."Person" a + Output: a.properties, a.id + Recheck Cond: (a.id > '0'::graphid) + -> Bitmap Index Scan on "Person_pkey" + Index Cond: (a.id > '0'::graphid) + -> Hash + Output: b.id + -> Append + -> Seq Scan on accessor_opt._ag_label_vertex b_1 + Output: b_1.id + Filter: (b_1.id > '0'::graphid) + -> Bitmap Heap Scan on accessor_opt."Person" b_2 + Output: b_2.id + Recheck Cond: (b_2.id > '0'::graphid) + -> Bitmap Index Scan on "Person_pkey" + Index Cond: (b_2.id > '0'::graphid) +(30 rows) + +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (a:Person) + MATCH (b:Person) + WHERE b.name <> a.name + RETURN a.name, b.name +$$) AS (a_name agtype, b_name agtype); + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Nested Loop + Output: agtype_access_operator(VARIADIC ARRAY[a.properties, '"name"'::agtype]), agtype_access_operator(VARIADIC ARRAY[b.properties, '"name"'::agtype]) + Join Filter: (agtype_access_operator(VARIADIC ARRAY[b.properties, '"name"'::agtype]) <> agtype_access_operator(VARIADIC ARRAY[a.properties, '"name"'::agtype])) + -> Seq Scan on accessor_opt."Person" a + Output: a.id, a.properties + -> Materialize + Output: b.properties + -> Seq Scan on accessor_opt."Person" b + Output: b.properties +(9 rows) + +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (a:Person) + MATCH (a)-[r1]->(b) + MATCH (b)-[r2]->(c) + RETURN id(a), id(b), id(c), a.name +$$) AS (a_id agtype, b_id agtype, c_id agtype, a_name agtype); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Merge Join + Output: (a.id)::agtype, (b.id)::agtype, (c.id)::agtype, agtype_access_operator(VARIADIC ARRAY[a.properties, '"name"'::agtype]) + Merge Cond: (r2.start_id = r1.end_id) + -> Nested Loop + Output: r2.start_id, c.id + -> Merge Append + Sort Key: r2.start_id + -> Index Scan using _ag_label_edge_start_id_idx on accessor_opt._ag_label_edge r2_1 + Output: r2_1.start_id, r2_1.end_id + -> Index Scan using "KNOWS_start_id_idx" on accessor_opt."KNOWS" r2_2 + Output: r2_2.start_id, r2_2.end_id + -> Append + -> Seq Scan on accessor_opt._ag_label_vertex c_1 + Output: c_1.id + Filter: (c_1.id = r2.end_id) + -> Index Only Scan using "Person_pkey" on accessor_opt."Person" c_2 + Output: c_2.id + Index Cond: (c_2.id = r2.end_id) + -> Materialize + Output: a.id, a.properties, r1.end_id, b.id + -> Merge Join + Output: a.id, a.properties, r1.end_id, b.id + Merge Cond: (b.id = r1.end_id) + -> Merge Append + Sort Key: b.id + -> Index Only Scan using _ag_label_vertex_pkey on accessor_opt._ag_label_vertex b_1 + Output: b_1.id + -> Index Only Scan using "Person_pkey" on accessor_opt."Person" b_2 + Output: b_2.id + -> Sort + Output: a.id, a.properties, r1.end_id + Sort Key: r1.end_id + -> Hash Join + Output: a.id, a.properties, r1.end_id + Inner Unique: true + Hash Cond: (r1.start_id = a.id) + -> Append + -> Seq Scan on accessor_opt._ag_label_edge r1_1 + Output: r1_1.start_id, r1_1.end_id + -> Seq Scan on accessor_opt."KNOWS" r1_2 + Output: r1_2.start_id, r1_2.end_id + -> Hash + Output: a.id, a.properties + -> Seq Scan on accessor_opt."Person" a + Output: a.id, a.properties +(45 rows) + +-- +-- Composite types, vertex and edge +-- +SELECT * FROM create_graph('composite_types'); +NOTICE: graph "composite_types" has been created + create_graph +-------------- + +(1 row) + +SELECT * FROM cypher('composite_types', $$ + CREATE (a:Person {name: 'Alice', age: 30})-[r:KNOWS {since: 2020}]->(b:Person {name: 'Bob', age: 25}) +$$) AS (result agtype); + result +-------- +(0 rows) + +-- Return vertex as vertex type +SELECT * FROM cypher('composite_types', $$ + MATCH (n:Person) + RETURN n +$$) AS (n vertex); + n +--------------------------------------------------------------------- + (844424930131969,"""Person""","{""age"": 30, ""name"": ""Alice""}") + (844424930131970,"""Person""","{""age"": 25, ""name"": ""Bob""}") +(2 rows) + +SELECT * FROM cypher('composite_types', $$ + MATCH (n:Person) + RETURN n + ORDER BY n.name +$$) AS (n vertex); + n +--------------------------------------------------------------------- + (844424930131969,"""Person""","{""age"": 30, ""name"": ""Alice""}") + (844424930131970,"""Person""","{""age"": 25, ""name"": ""Bob""}") +(2 rows) + +-- Return edge as edge type +SELECT * FROM cypher('composite_types', $$ + MATCH ()-[r:KNOWS]->() + RETURN r +$$) AS (r edge); + r +------------------------------------------------------------------------------------ + (1125899906842625,"""KNOWS""",844424930131970,844424930131969,"{""since"": 2020}") +(1 row) + +-- Return multiple entities +SELECT * FROM cypher('composite_types', $$ + MATCH (a:Person)-[r:KNOWS]->(b:Person) + RETURN a, b +$$) AS (a vertex, b vertex); + a | b +---------------------------------------------------------------------+------------------------------------------------------------------- + (844424930131969,"""Person""","{""age"": 30, ""name"": ""Alice""}") | (844424930131970,"""Person""","{""age"": 25, ""name"": ""Bob""}") +(1 row) + +SELECT * FROM cypher('composite_types', $$ + MATCH (a:Person)-[r:KNOWS]->(b:Person) + RETURN a, r, b +$$) AS (a vertex, r edge, b vertex); + a | r | b +---------------------------------------------------------------------+------------------------------------------------------------------------------------+------------------------------------------------------------------- + (844424930131969,"""Person""","{""age"": 30, ""name"": ""Alice""}") | (1125899906842625,"""KNOWS""",844424930131970,844424930131969,"{""since"": 2020}") | (844424930131970,"""Person""","{""age"": 25, ""name"": ""Bob""}") +(1 row) + +-- Mixed return: entity and agtype property +SELECT * FROM cypher('composite_types', $$ + MATCH (n:Person) + RETURN n, n.name +$$) AS (n vertex, name agtype); + n | name +---------------------------------------------------------------------+--------- + (844424930131969,"""Person""","{""age"": 30, ""name"": ""Alice""}") | "Alice" + (844424930131970,"""Person""","{""age"": 25, ""name"": ""Bob""}") | "Bob" +(2 rows) + +SELECT * FROM cypher('composite_types', $$ + MATCH (n:Person) + RETURN n, n.name + ORDER BY n.name +$$) AS (n vertex, name agtype); + n | name +---------------------------------------------------------------------+--------- + (844424930131969,"""Person""","{""age"": 30, ""name"": ""Alice""}") | "Alice" + (844424930131970,"""Person""","{""age"": 25, ""name"": ""Bob""}") | "Bob" +(2 rows) + +SELECT * FROM cypher('composite_types', $$ + MATCH ()-[r:KNOWS]->() + RETURN r, r.since +$$) AS (r edge, since agtype); + r | since +------------------------------------------------------------------------------------+------- + (1125899906842625,"""KNOWS""",844424930131970,844424930131969,"{""since"": 2020}") | 2020 +(1 row) + +-- IN operator with vertex and edge types +EXPLAIN (COSTS OFF) +SELECT * FROM cypher('composite_types', $$ + MATCH (a:Person), (b:Person) + WHERE a <> b AND a IN [a, b] + RETURN a.name +$$) AS (name agtype); + QUERY PLAN +---------------------------------------------------------------------- + Nested Loop + Join Filter: ((a.id <> b.id) AND ((a.id = a.id) OR (a.id = b.id))) + -> Seq Scan on "Person" a + -> Materialize + -> Seq Scan on "Person" b +(5 rows) + +SELECT * FROM cypher('composite_types', $$ + MATCH (a:Person), (b:Person) + WHERE a <> b AND a IN [a, b] + RETURN a.name +$$) AS (name agtype); + name +--------- + "Alice" + "Bob" +(2 rows) + +EXPLAIN (COSTS OFF) +SELECT * FROM cypher('composite_types', $$ + MATCH ()-[r1:KNOWS]->(), ()-[r2:KNOWS]->() + WHERE r1 IN [r1, r2] + RETURN id(r1) +$$) AS (id agtype); + QUERY PLAN +------------------------------------------------------------------------------------------------------------ + Merge Join + Merge Cond: (_age_default_alias_1.id = r1.end_id) + -> Merge Append + Sort Key: _age_default_alias_1.id + -> Index Only Scan using _ag_label_vertex_pkey on _ag_label_vertex _age_default_alias_1_1 + -> Index Only Scan using "Person_pkey" on "Person" _age_default_alias_1_2 + -> Materialize + -> Nested Loop + -> Nested Loop + -> Nested Loop + -> Nested Loop + Join Filter: ((r1.id = r1.id) OR (r1.id = r2.id)) + -> Index Scan using "KNOWS_end_id_idx" on "KNOWS" r1 + -> Materialize + -> Seq Scan on "KNOWS" r2 + -> Append + -> Seq Scan on _ag_label_vertex _age_default_alias_2_1 + Filter: (id = r2.start_id) + -> Index Only Scan using "Person_pkey" on "Person" _age_default_alias_2_2 + Index Cond: (id = r2.start_id) + -> Append + -> Seq Scan on _ag_label_vertex _age_default_alias_3_1 + Filter: (id = r2.end_id) + -> Index Only Scan using "Person_pkey" on "Person" _age_default_alias_3_2 + Index Cond: (id = r2.end_id) + -> Append + -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 + Filter: (id = r1.start_id) + -> Index Only Scan using "Person_pkey" on "Person" _age_default_alias_0_2 + Index Cond: (id = r1.start_id) +(30 rows) + +SELECT * FROM cypher('composite_types', $$ + MATCH ()-[r1:KNOWS]->(), ()-[r2:KNOWS]->() + WHERE r1 IN [r1, r2] + RETURN id(r1) +$$) AS (id agtype); + id +------------------ + 1125899906842625 +(1 row) + +-- Equality operators with vertex and edge types +EXPLAIN (COSTS OFF) +SELECT * FROM cypher('composite_types', $$ + MATCH (a:Person), (b:Person) + WHERE a = b + RETURN a.name +$$) AS (name agtype); + QUERY PLAN +------------------------------------ + Hash Join + Hash Cond: (a.id = b.id) + -> Seq Scan on "Person" a + -> Hash + -> Seq Scan on "Person" b +(5 rows) + +SELECT * FROM cypher('composite_types', $$ + MATCH (a:Person), (b:Person) + WHERE a = b + RETURN a.name +$$) AS (name agtype); + name +--------- + "Alice" + "Bob" +(2 rows) + +EXPLAIN (COSTS OFF) +SELECT * FROM cypher('composite_types', $$ + MATCH ()-[r1:KNOWS]->(), ()-[r2:KNOWS]->() + WHERE r1 = r2 + RETURN id(r1) +$$) AS (id agtype); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------- + Merge Join + Merge Cond: (r1.id = r2.id) + -> Sort + Sort Key: r1.id + -> Merge Join + Merge Cond: (_age_default_alias_1.id = r1.end_id) + -> Merge Append + Sort Key: _age_default_alias_1.id + -> Index Only Scan using _ag_label_vertex_pkey on _ag_label_vertex _age_default_alias_1_1 + -> Index Only Scan using "Person_pkey" on "Person" _age_default_alias_1_2 + -> Materialize + -> Nested Loop + -> Index Scan using "KNOWS_end_id_idx" on "KNOWS" r1 + -> Append + -> Seq Scan on _ag_label_vertex _age_default_alias_0_1 + Filter: (id = r1.start_id) + -> Index Only Scan using "Person_pkey" on "Person" _age_default_alias_0_2 + Index Cond: (id = r1.start_id) + -> Sort + Sort Key: r2.id + -> Merge Join + Merge Cond: (_age_default_alias_3.id = r2.end_id) + -> Merge Append + Sort Key: _age_default_alias_3.id + -> Index Only Scan using _ag_label_vertex_pkey on _ag_label_vertex _age_default_alias_3_1 + -> Index Only Scan using "Person_pkey" on "Person" _age_default_alias_3_2 + -> Materialize + -> Nested Loop + -> Index Scan using "KNOWS_end_id_idx" on "KNOWS" r2 + -> Append + -> Seq Scan on _ag_label_vertex _age_default_alias_2_1 + Filter: (id = r2.start_id) + -> Index Only Scan using "Person_pkey" on "Person" _age_default_alias_2_2 + Index Cond: (id = r2.start_id) +(34 rows) + +SELECT * FROM cypher('composite_types', $$ + MATCH ()-[r1:KNOWS]->(), ()-[r2:KNOWS]->() + WHERE r1 = r2 + RETURN id(r1) +$$) AS (id agtype); + id +------------------ + 1125899906842625 +(1 row) + +-- Cast vertex and edge to json +SELECT * FROM cypher('composite_types', $$ + MATCH (n:Person) + RETURN n +$$) AS (n json); + n +---------------------------------------------------------------------------------------- + {"id": 844424930131969, "label": "Person", "properties": {"age": 30, "name": "Alice"}} + {"id": 844424930131970, "label": "Person", "properties": {"age": 25, "name": "Bob"}} +(2 rows) + +SELECT * FROM cypher('composite_types', $$ + MATCH ()-[r:KNOWS]->() + RETURN r +$$) AS (r json); + r +----------------------------------------------------------------------------------------------------------------------------------- + {"id": 1125899906842625, "label": "KNOWS", "end_id": 844424930131970, "start_id": 844424930131969, "properties": {"since": 2020}} +(1 row) + +-- Cast vertex and edge to jsonb +SELECT * FROM cypher('composite_types', $$ + MATCH (n:Person) + RETURN n +$$) AS (n jsonb); + n +---------------------------------------------------------------------------------------- + {"id": 844424930131969, "label": "Person", "properties": {"age": 30, "name": "Alice"}} + {"id": 844424930131970, "label": "Person", "properties": {"age": 25, "name": "Bob"}} +(2 rows) + +SELECT * FROM cypher('composite_types', $$ + MATCH ()-[r:KNOWS]->() + RETURN r +$$) AS (r jsonb); + r +----------------------------------------------------------------------------------------------------------------------------------- + {"id": 1125899906842625, "label": "KNOWS", "end_id": 844424930131970, "start_id": 844424930131969, "properties": {"since": 2020}} +(1 row) + -- -- Cleanup -- +SELECT * FROM drop_graph('composite_types', true); +NOTICE: drop cascades to 4 other objects +DETAIL: drop cascades to table composite_types._ag_label_vertex +drop cascades to table composite_types._ag_label_edge +drop cascades to table composite_types."Person" +drop cascades to table composite_types."KNOWS" +NOTICE: graph "composite_types" has been dropped + drop_graph +------------ + +(1 row) + +SELECT * FROM drop_graph('accessor_opt', true); +NOTICE: drop cascades to 4 other objects +DETAIL: drop cascades to table accessor_opt._ag_label_vertex +drop cascades to table accessor_opt._ag_label_edge +drop cascades to table accessor_opt."Person" +drop cascades to table accessor_opt."KNOWS" +NOTICE: graph "accessor_opt" has been dropped + drop_graph +------------ + +(1 row) + SELECT * FROM drop_graph('issue_2289', true); NOTICE: drop cascades to 2 other objects DETAIL: drop cascades to table issue_2289._ag_label_vertex diff --git a/regress/expected/pgvector.out b/regress/expected/pgvector.out index bbc558349..4af6106ea 100644 --- a/regress/expected/pgvector.out +++ b/regress/expected/pgvector.out @@ -576,7 +576,7 @@ BEGIN USING hnsw ((( agtype_access_operator( VARIADIC ARRAY[ - _agtype_build_vertex(id, _label_name(%L::oid, id), properties), + properties, '"embedding"'::agtype ] )::text @@ -654,7 +654,7 @@ BEGIN USING hnsw (( agtype_access_operator( VARIADIC ARRAY[ - _agtype_build_vertex(id, _label_name(%L::oid, id), properties), + properties, '"embedding"'::agtype ] )::vector(4)) vector_cosine_ops); diff --git a/regress/sql/agtype.sql b/regress/sql/agtype.sql index 6dab6bc30..e0990ac40 100644 --- a/regress/sql/agtype.sql +++ b/regress/sql/agtype.sql @@ -823,40 +823,40 @@ SELECT * FROM drop_graph('agtype_null_duplicate_test', true); -- Vertex -- --Basic Vertex Creation -SELECT _agtype_build_vertex('1'::graphid, $$label_name$$, agtype_build_map()); -SELECT _agtype_build_vertex('1'::graphid, $$label$$, agtype_build_map('id', 2)); +SELECT _agtype_build_vertex('1'::graphid, '"label_name"', agtype_build_map()); +SELECT _agtype_build_vertex('1'::graphid, '"label"', agtype_build_map('id', 2)); --Null properties -SELECT _agtype_build_vertex('1'::graphid, $$label_name$$, NULL); +SELECT _agtype_build_vertex('1'::graphid, '"label_name"', NULL); --Test access operator -SELECT agtype_access_operator(_agtype_build_vertex('1'::graphid, $$label$$, +SELECT agtype_access_operator(_agtype_build_vertex('1'::graphid, '"label"', agtype_build_map('id', 2)), '"id"'); -SELECT _agtype_build_vertex('1'::graphid, $$label$$, agtype_build_list()); +SELECT _agtype_build_vertex('1'::graphid, '"label"', agtype_build_list()); --Vertex in a map SELECT agtype_build_map( 'vertex', - _agtype_build_vertex('1'::graphid, $$label_name$$, agtype_build_map())); + _agtype_build_vertex('1'::graphid, '"label_name"', agtype_build_map())); SELECT agtype_access_operator( agtype_build_map( - 'vertex', _agtype_build_vertex('1'::graphid, $$label_name$$, + 'vertex', _agtype_build_vertex('1'::graphid, '"label_name"', agtype_build_map('key', 'value')), - 'other_vertex', _agtype_build_vertex('1'::graphid, $$label_name$$, + 'other_vertex', _agtype_build_vertex('1'::graphid, '"label_name"', agtype_build_map('key', 'other_value'))), '"vertex"'); --Vertex in a list SELECT agtype_build_list( - _agtype_build_vertex('1'::graphid, $$label_name$$, agtype_build_map()), - _agtype_build_vertex('2'::graphid, $$label_name$$, agtype_build_map())); + _agtype_build_vertex('1'::graphid, '"label_name"', agtype_build_map()), + _agtype_build_vertex('2'::graphid, '"label_name"', agtype_build_map())); SELECT agtype_access_operator( agtype_build_list( - _agtype_build_vertex('1'::graphid, $$label_name$$, + _agtype_build_vertex('1'::graphid, '"label_name"', agtype_build_map('id', 3)), - _agtype_build_vertex('2'::graphid, $$label_name$$, + _agtype_build_vertex('2'::graphid, '"label_name"', agtype_build_map('id', 4))), '0'); -- @@ -864,18 +864,18 @@ SELECT agtype_access_operator( -- --Basic Edge Creation SELECT _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label_name$$, agtype_build_map()); + '"label_name"', agtype_build_map()); SELECT _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label$$, agtype_build_map('id', 2)); + '"label"', agtype_build_map('id', 2)); --Null properties SELECT _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label_name$$, NULL); + '"label_name"', NULL); --Test access operator SELECT agtype_access_operator(_agtype_build_edge('1'::graphid, '2'::graphid, - '3'::graphid, $$label$$, agtype_build_map('id', 2)),'"id"'); + '3'::graphid, '"label"', agtype_build_map('id', 2)),'"id"'); @@ -883,97 +883,97 @@ SELECT agtype_access_operator(_agtype_build_edge('1'::graphid, '2'::graphid, SELECT agtype_build_map( 'edge', _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label_name$$, agtype_build_map())); + '"label_name"', agtype_build_map())); SELECT agtype_access_operator( agtype_build_map( 'edge', _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label_name$$, agtype_build_map('key', 'value')), + '"label_name"', agtype_build_map('key', 'value')), 'other_edge', _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label_name$$, agtype_build_map('key', 'other_value'))), + '"label_name"', agtype_build_map('key', 'other_value'))), '"edge"'); --Edge in a list SELECT agtype_build_list( _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label_name$$, agtype_build_map()), + '"label_name"', agtype_build_map()), _agtype_build_edge('2'::graphid, '2'::graphid, '3'::graphid, - $$label_name$$, agtype_build_map())); + '"label_name"', agtype_build_map())); SELECT agtype_access_operator( agtype_build_list( - _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, $$label_name$$, + _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, '"label_name"', agtype_build_map('id', 3)), - _agtype_build_edge('2'::graphid, '2'::graphid, '3'::graphid, $$label_name$$, + _agtype_build_edge('2'::graphid, '2'::graphid, '3'::graphid, '"label_name"', agtype_build_map('id', 4))), '0'); -- Path SELECT _agtype_build_path( - _agtype_build_vertex('2'::graphid, $$label_name$$, agtype_build_map()), + _agtype_build_vertex('2'::graphid, '"label_name"', agtype_build_map()), _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label$$, agtype_build_map('id', 2)), - _agtype_build_vertex('3'::graphid, $$label_name$$, agtype_build_map()) + '"label"', agtype_build_map('id', 2)), + _agtype_build_vertex('3'::graphid, '"label_name"', agtype_build_map()) ); --All these paths should produce Errors SELECT _agtype_build_path( - _agtype_build_vertex('2'::graphid, $$label_name$$, agtype_build_map()), + _agtype_build_vertex('2'::graphid, '"label_name"', agtype_build_map()), _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label$$, agtype_build_map('id', 2)) + '"label"', agtype_build_map('id', 2)) ); SELECT _agtype_build_path( - _agtype_build_vertex('2'::graphid, $$label_name$$, agtype_build_map()), + _agtype_build_vertex('2'::graphid, '"label_name"', agtype_build_map()), _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label$$, agtype_build_map('id', 2)), - _agtype_build_vertex('3'::graphid, $$label_name$$, agtype_build_map()), + '"label"', agtype_build_map('id', 2)), + _agtype_build_vertex('3'::graphid, '"label_name"', agtype_build_map()), _agtype_build_edge('1'::graphid, '4'::graphid, '5'::graphid, - $$label$$, agtype_build_map('id', 2)) + '"label"', agtype_build_map('id', 2)) ); SELECT _agtype_build_path( - _agtype_build_vertex('2'::graphid, $$label_name$$, agtype_build_map()), + _agtype_build_vertex('2'::graphid, '"label_name"', agtype_build_map()), _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label$$, agtype_build_map('id', 2)), + '"label"', agtype_build_map('id', 2)), NULL ); SELECT _agtype_build_path( - _agtype_build_vertex('2'::graphid, $$label_name$$, agtype_build_map()), + _agtype_build_vertex('2'::graphid, '"label_name"', agtype_build_map()), _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label$$, agtype_build_map('id', 2)), + '"label"', agtype_build_map('id', 2)), 1 ); SELECT _agtype_build_path( - _agtype_build_vertex('2'::graphid, $$label_name$$, agtype_build_map()), + _agtype_build_vertex('2'::graphid, '"label_name"', agtype_build_map()), _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label$$, agtype_build_map('id', 2)), + '"label"', agtype_build_map('id', 2)), _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label$$, agtype_build_map('id', 2)) + '"label"', agtype_build_map('id', 2)) ); -- -- id, startid, endid -- -SELECT age_id(_agtype_build_vertex('1'::graphid, $$label_name$$, agtype_build_map())); +SELECT age_id(_agtype_build_vertex('1'::graphid, '"label_name"', agtype_build_map())); SELECT age_id(_agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label_name$$, agtype_build_map('id', 2))); + '"label_name"', agtype_build_map('id', 2))); SELECT age_start_id(_agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label_name$$, agtype_build_map('id', 2))); + '"label_name"', agtype_build_map('id', 2))); SELECT age_end_id(_agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label_name$$, agtype_build_map('id', 2))); + '"label_name"', agtype_build_map('id', 2))); SELECT age_id(_agtype_build_path( - _agtype_build_vertex('2'::graphid, $$label_name$$, agtype_build_map()), + _agtype_build_vertex('2'::graphid, '"label_name"', agtype_build_map()), _agtype_build_edge('1'::graphid, '2'::graphid, '3'::graphid, - $$label$$, agtype_build_map('id', 2)), - _agtype_build_vertex('3'::graphid, $$label$$, agtype_build_map('id', 2)) + '"label"', agtype_build_map('id', 2)), + _agtype_build_vertex('3'::graphid, '"label"', agtype_build_map('id', 2)) )); SELECT age_id(agtype_in('1')); diff --git a/regress/sql/cypher_with.sql b/regress/sql/cypher_with.sql index 93413f2c9..25e22b2a2 100644 --- a/regress/sql/cypher_with.sql +++ b/regress/sql/cypher_with.sql @@ -227,6 +227,149 @@ $$) as (a agtype,b agtype); SELECT drop_graph('graph', true); + +-- +-- Test accessor optimizations in WITH clause +-- +SELECT * FROM create_graph('with_accessor_opt'); + +SELECT * FROM cypher('with_accessor_opt', $$ + CREATE (a:Person {name: 'Alice', age: 30})-[r:KNOWS {since: 2020}]->(b:Person {name: 'Bob', age: 25}) +$$) AS (result agtype); + +-- Single with +SELECT * FROM cypher('with_accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + WITH id(n) AS node_id + RETURN node_id +$$) AS (node_id agtype); + +SELECT * FROM cypher('with_accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + WITH properties(n) AS props + RETURN props +$$) AS (props agtype); + +SELECT * FROM cypher('with_accessor_opt', $$ + MATCH (n:Person) + WITH label(n) AS lbl + RETURN lbl +$$) AS (lbl agtype); + +SELECT * FROM cypher('with_accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + WITH n.name AS name, n.age AS age + RETURN name, age +$$) AS (name agtype, age agtype); + +SELECT * FROM cypher('with_accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH ()-[r:KNOWS]->() + WITH id(r) AS rid + RETURN rid +$$) AS (rid agtype); + +SELECT * FROM cypher('with_accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + WITH n, id(n) AS nid + WITH n.name AS name, nid + RETURN name, nid +$$) AS (name agtype, nid agtype); + +SELECT * FROM cypher('with_accessor_opt', $$ + MATCH (n:Person) + WITH n as m + RETURN m +$$) AS (n vertex); + +SELECT * FROM cypher('with_accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + WITH n + RETURN id(n), n.name +$$) AS (id agtype, name agtype); + +SELECT * FROM cypher('with_accessor_opt', $$ + MATCH ()-[r:KNOWS]->() + WITH r + RETURN id(r), type(r) +$$) AS (id agtype, typ agtype); + +-- +-- Chained WITH tests +-- +SELECT * FROM cypher('with_accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (a:Person)-[r:KNOWS]->(b:Person) + WITH a, b, id(a) AS aid + WITH a.name AS aname, b.name AS bname, aid + RETURN aname, bname, aid +$$) AS (aname agtype, bname agtype, aid agtype); + +SELECT * FROM cypher('with_accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + WITH n, n.age AS age + WHERE age > 20 + RETURN n.name +$$) AS (name agtype); + +SELECT * FROM cypher('with_accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + WITH n.name as name, id(n) AS nid + ORDER BY nid + RETURN name +$$) AS (name agtype); + +SELECT * FROM cypher('with_accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + WITH id(n) AS id, count(*) AS cnt + RETURN id, cnt +$$) AS (id agtype, cnt agtype); + +SELECT * FROM cypher('with_accessor_opt', $$ + MATCH (n:Person) + WITH DISTINCT label(n) AS lbl + RETURN lbl +$$) AS (lbl agtype); + +SELECT * FROM cypher('with_accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + WITH n + WITH n, id(n) AS nid + WITH n.name AS name, nid + RETURN name, nid +$$) AS (name agtype, nid agtype); + +-- MATCH -> WITH accessors -> MATCH -> RETURN +SELECT * FROM cypher('with_accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (a:Person) + WITH a, id(a) AS aid + MATCH (a)-[r:KNOWS]->(b) + RETURN aid, b.name +$$) AS (aid agtype, bname agtype); + +-- WITH + UNWIND with accessors +SELECT * FROM cypher('with_accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + WITH collect(id(n)) AS ids + UNWIND ids AS nid + RETURN nid +$$) AS (nid agtype); + +-- Clean up +SELECT drop_graph('with_accessor_opt', true); + + -- -- End of test -- diff --git a/regress/sql/expr.sql b/regress/sql/expr.sql index 445e2d237..7549f8f88 100644 --- a/regress/sql/expr.sql +++ b/regress/sql/expr.sql @@ -3712,9 +3712,409 @@ $$) AS (out agtype); SELECT * FROM create_graph('issue_2289'); SELECT * FROM cypher('issue_2289', $$ RETURN (1 IN []) AS v $$) AS (v agtype); +-- +-- Accessor functions and properties extraction +-- without _agtype_build.. functions +-- +SELECT * FROM create_graph('accessor_opt'); + +SELECT * FROM cypher('accessor_opt', $$ + CREATE (a:Person {name: 'Alice', age: 30})-[r:KNOWS {since: 2020}]->(b:Person {name: 'Bob', age: 25}) +$$) AS (a agtype); + +-- +-- Vertex accessor tests +-- +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + RETURN id(n) +$$) AS (plan agtype); + +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + RETURN properties(n) +$$) AS (plan agtype); + +SELECT * FROM cypher('accessor_opt', $$ + MATCH (n:Person) + RETURN label(n) +$$) AS (plan agtype); + +-- should use n.properties in _agtype_access_operator +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + RETURN n.name, n.age +$$) AS (a agtype, b agtype); + +-- +-- Edge accessor tests +-- +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH ()-[r:KNOWS]->() + RETURN id(r) +$$) AS (plan agtype); + +SELECT * FROM cypher('accessor_opt', $$ + MATCH ()-[r:KNOWS]->() + RETURN type(r) +$$) AS (plan agtype); + +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH ()-[r:KNOWS]->() + RETURN start_id(r) +$$) AS (plan agtype); + +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH ()-[r:KNOWS]->() + RETURN end_id(r) +$$) AS (plan agtype); + +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH ()-[r:KNOWS]->() + RETURN properties(r) +$$) AS (plan agtype); + +-- should use r.properties in _agtype_access_operator +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH ()-[r:KNOWS]->() + RETURN r.since +$$) AS (plan agtype); + +-- +-- Multiple accessors in same query +-- +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + RETURN id(n), properties(n), n.name +$$) AS (a agtype, c agtype, d agtype); + +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH ()-[r:KNOWS]->() + RETURN id(r), start_id(r), end_id(r), properties(r) +$$) AS (a agtype, c agtype, d agtype, e agtype); + +-- +-- Accessors in WHERE clause +-- +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + WHERE id(n) > 0 + RETURN n.name +$$) AS (plan agtype); + +-- Compare two node ids +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (a:Person)-[r:KNOWS]->(b:Person) + WHERE id(a) < id(b) + RETURN a.name, b.name +$$) AS (aname agtype, bname agtype); + +-- Compare edge start_id and end_id +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH ()-[r:KNOWS]->() + WHERE start_id(r) <> end_id(r) + RETURN id(r) +$$) AS (rid agtype); + +-- Property comparison +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + WHERE n.age >= 25 + RETURN n.name +$$) AS (name agtype); + +-- Multiple property comparisons +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + WHERE n.age > 20 AND n.name <> 'Unknown' + RETURN n.name +$$) AS (name agtype); + +-- Compare properties of two nodes +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (a:Person)-[r:KNOWS]->(b:Person) + WHERE a.age > b.age + RETURN a.name, b.name +$$) AS (aname agtype, bname agtype); + +-- Compare id with property +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + WHERE id(n) > n.age + RETURN n.name +$$) AS (name agtype); + +-- Edge property comparison +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH ()-[r:KNOWS]->() + WHERE r.since > 2015 + RETURN r.since +$$) AS (since agtype); + +-- IS NOT NULL on accessor +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + WHERE properties(n) IS NOT NULL + RETURN n.name +$$) AS (name agtype); + +-- label() comparison +SELECT * FROM cypher('accessor_opt', $$ + MATCH (n) + WHERE label(n) = 'Person' + RETURN n.name +$$) AS (name agtype); + +-- type() comparison on edge +SELECT * FROM cypher('accessor_opt', $$ + MATCH ()-[r]->() + WHERE type(r) = 'KNOWS' + RETURN id(r) +$$) AS (rid agtype); + +-- +-- Accessors in ORDER BY +-- +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + RETURN n.name + ORDER BY n.name +$$) AS (plan agtype); + +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + RETURN n.name + ORDER BY id(n) +$$) AS (plan agtype); + +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + RETURN n.name, n.age + ORDER BY n.age, n.name +$$) AS (name agtype, age agtype); + +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH ()-[r:KNOWS]->() + RETURN r.since + ORDER BY start_id(r) +$$) AS (since agtype); + +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (a:Person)-[r:KNOWS]->(b:Person) + RETURN a.name, b.name + ORDER BY id(a), id(r), id(b) +$$) AS (aname agtype, bname agtype); + +-- +-- Accessors in expressions +-- +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + RETURN [id(n), n.name] +$$) AS (plan agtype); + +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + RETURN {id: id(n), name: n.name} +$$) AS (plan agtype); + +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (n:Person) + RETURN n {.name, .age} +$$) AS (plan agtype); + +-- +-- Accessor function in multiple match +-- +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (a:Person) + WHERE id(a) > 0 + MATCH (a)-[r]->(b) + WHERE id(b) > 0 + RETURN properties(a), start_id(r), end_id(r) +$$) AS (props_a agtype, sid agtype, eid agtype); + +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (a:Person) + MATCH (b:Person) + WHERE b.name <> a.name + RETURN a.name, b.name +$$) AS (a_name agtype, b_name agtype); + +SELECT * FROM cypher('accessor_opt', $$ + EXPLAIN (VERBOSE, COSTS OFF) + MATCH (a:Person) + MATCH (a)-[r1]->(b) + MATCH (b)-[r2]->(c) + RETURN id(a), id(b), id(c), a.name +$$) AS (a_id agtype, b_id agtype, c_id agtype, a_name agtype); + +-- +-- Composite types, vertex and edge +-- +SELECT * FROM create_graph('composite_types'); + +SELECT * FROM cypher('composite_types', $$ + CREATE (a:Person {name: 'Alice', age: 30})-[r:KNOWS {since: 2020}]->(b:Person {name: 'Bob', age: 25}) +$$) AS (result agtype); + +-- Return vertex as vertex type +SELECT * FROM cypher('composite_types', $$ + MATCH (n:Person) + RETURN n +$$) AS (n vertex); + +SELECT * FROM cypher('composite_types', $$ + MATCH (n:Person) + RETURN n + ORDER BY n.name +$$) AS (n vertex); + +-- Return edge as edge type +SELECT * FROM cypher('composite_types', $$ + MATCH ()-[r:KNOWS]->() + RETURN r +$$) AS (r edge); + +-- Return multiple entities +SELECT * FROM cypher('composite_types', $$ + MATCH (a:Person)-[r:KNOWS]->(b:Person) + RETURN a, b +$$) AS (a vertex, b vertex); + +SELECT * FROM cypher('composite_types', $$ + MATCH (a:Person)-[r:KNOWS]->(b:Person) + RETURN a, r, b +$$) AS (a vertex, r edge, b vertex); + +-- Mixed return: entity and agtype property +SELECT * FROM cypher('composite_types', $$ + MATCH (n:Person) + RETURN n, n.name +$$) AS (n vertex, name agtype); + +SELECT * FROM cypher('composite_types', $$ + MATCH (n:Person) + RETURN n, n.name + ORDER BY n.name +$$) AS (n vertex, name agtype); + +SELECT * FROM cypher('composite_types', $$ + MATCH ()-[r:KNOWS]->() + RETURN r, r.since +$$) AS (r edge, since agtype); + +-- IN operator with vertex and edge types +EXPLAIN (COSTS OFF) +SELECT * FROM cypher('composite_types', $$ + MATCH (a:Person), (b:Person) + WHERE a <> b AND a IN [a, b] + RETURN a.name +$$) AS (name agtype); + +SELECT * FROM cypher('composite_types', $$ + MATCH (a:Person), (b:Person) + WHERE a <> b AND a IN [a, b] + RETURN a.name +$$) AS (name agtype); + +EXPLAIN (COSTS OFF) +SELECT * FROM cypher('composite_types', $$ + MATCH ()-[r1:KNOWS]->(), ()-[r2:KNOWS]->() + WHERE r1 IN [r1, r2] + RETURN id(r1) +$$) AS (id agtype); + +SELECT * FROM cypher('composite_types', $$ + MATCH ()-[r1:KNOWS]->(), ()-[r2:KNOWS]->() + WHERE r1 IN [r1, r2] + RETURN id(r1) +$$) AS (id agtype); + +-- Equality operators with vertex and edge types +EXPLAIN (COSTS OFF) +SELECT * FROM cypher('composite_types', $$ + MATCH (a:Person), (b:Person) + WHERE a = b + RETURN a.name +$$) AS (name agtype); + +SELECT * FROM cypher('composite_types', $$ + MATCH (a:Person), (b:Person) + WHERE a = b + RETURN a.name +$$) AS (name agtype); + +EXPLAIN (COSTS OFF) +SELECT * FROM cypher('composite_types', $$ + MATCH ()-[r1:KNOWS]->(), ()-[r2:KNOWS]->() + WHERE r1 = r2 + RETURN id(r1) +$$) AS (id agtype); + +SELECT * FROM cypher('composite_types', $$ + MATCH ()-[r1:KNOWS]->(), ()-[r2:KNOWS]->() + WHERE r1 = r2 + RETURN id(r1) +$$) AS (id agtype); + +-- Cast vertex and edge to json +SELECT * FROM cypher('composite_types', $$ + MATCH (n:Person) + RETURN n +$$) AS (n json); + +SELECT * FROM cypher('composite_types', $$ + MATCH ()-[r:KNOWS]->() + RETURN r +$$) AS (r json); + +-- Cast vertex and edge to jsonb +SELECT * FROM cypher('composite_types', $$ + MATCH (n:Person) + RETURN n +$$) AS (n jsonb); + +SELECT * FROM cypher('composite_types', $$ + MATCH ()-[r:KNOWS]->() + RETURN r +$$) AS (r jsonb); + -- -- Cleanup -- + +SELECT * FROM drop_graph('composite_types', true); +SELECT * FROM drop_graph('accessor_opt', true); SELECT * FROM drop_graph('issue_2289', true); SELECT * FROM drop_graph('issue_2263', true); SELECT * FROM drop_graph('issue_1988', true); diff --git a/regress/sql/pgvector.sql b/regress/sql/pgvector.sql index 677e78586..9c6c2c412 100644 --- a/regress/sql/pgvector.sql +++ b/regress/sql/pgvector.sql @@ -219,7 +219,7 @@ BEGIN USING hnsw ((( agtype_access_operator( VARIADIC ARRAY[ - _agtype_build_vertex(id, _label_name(%L::oid, id), properties), + properties, '"embedding"'::agtype ] )::text @@ -274,7 +274,7 @@ BEGIN USING hnsw (( agtype_access_operator( VARIADIC ARRAY[ - _agtype_build_vertex(id, _label_name(%L::oid, id), properties), + properties, '"embedding"'::agtype ] )::vector(4)) vector_cosine_ops); diff --git a/sql/age_main.sql b/sql/age_main.sql index 59ada0f9f..3cce195b5 100644 --- a/sql/age_main.sql +++ b/sql/age_main.sql @@ -368,13 +368,6 @@ CREATE FUNCTION ag_catalog._graphid(label_id int, entry_id bigint) PARALLEL SAFE AS 'MODULE_PATHNAME'; -CREATE FUNCTION ag_catalog._label_name(graph_oid oid, graphid) - RETURNS cstring - LANGUAGE c - IMMUTABLE -PARALLEL SAFE -AS 'MODULE_PATHNAME'; - CREATE FUNCTION ag_catalog._extract_label_id(graphid) RETURNS label_id LANGUAGE c diff --git a/sql/age_scalar.sql b/sql/age_scalar.sql index 5014fbbe8..ec098e8ee 100644 --- a/sql/age_scalar.sql +++ b/sql/age_scalar.sql @@ -93,6 +93,15 @@ RETURNS NULL ON NULL INPUT PARALLEL SAFE AS 'MODULE_PATHNAME'; +-- Helper function for optimized startNode/endNode +CREATE FUNCTION ag_catalog._get_vertex_by_graphid(text, graphid) + RETURNS agtype + LANGUAGE c + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + CREATE FUNCTION ag_catalog.age_length(agtype) RETURNS agtype LANGUAGE c diff --git a/sql/agtype_graphid.sql b/sql/agtype_graphid.sql index 0887db8a9..565a16bd5 100644 --- a/sql/agtype_graphid.sql +++ b/sql/agtype_graphid.sql @@ -43,6 +43,33 @@ CREATE CAST (agtype AS graphid) WITH FUNCTION ag_catalog.agtype_to_graphid(agtype) AS IMPLICIT; +-- +-- Composite types for vertex and edge +-- +CREATE TYPE ag_catalog.vertex AS ( + id graphid, + label agtype, + properties agtype +); + +CREATE TYPE ag_catalog.edge AS ( + id graphid, + label agtype, + end_id graphid, + start_id graphid, + properties agtype +); + +-- +-- Label name function +-- +CREATE FUNCTION ag_catalog._label_name(graph_oid oid, graphid) + RETURNS agtype + LANGUAGE c + IMMUTABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + -- -- agtype - path -- @@ -57,7 +84,7 @@ AS 'MODULE_PATHNAME'; -- -- agtype - vertex -- -CREATE FUNCTION ag_catalog._agtype_build_vertex(graphid, cstring, agtype) +CREATE FUNCTION ag_catalog._agtype_build_vertex(graphid, agtype, agtype) RETURNS agtype LANGUAGE c IMMUTABLE @@ -69,7 +96,7 @@ AS 'MODULE_PATHNAME'; -- agtype - edge -- CREATE FUNCTION ag_catalog._agtype_build_edge(graphid, graphid, graphid, - cstring, agtype) + agtype, agtype) RETURNS agtype LANGUAGE c IMMUTABLE @@ -77,6 +104,159 @@ CALLED ON NULL INPUT PARALLEL SAFE AS 'MODULE_PATHNAME'; +-- +-- vertex/edge to agtype cast functions +-- +CREATE FUNCTION ag_catalog.vertex_to_agtype(vertex) + RETURNS agtype + LANGUAGE c + IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION ag_catalog.edge_to_agtype(edge) + RETURNS agtype + LANGUAGE c + IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- +-- Implicit casts from vertex/edge to agtype +-- +CREATE CAST (vertex AS agtype) + WITH FUNCTION ag_catalog.vertex_to_agtype(vertex) +AS IMPLICIT; + +CREATE CAST (edge AS agtype) + WITH FUNCTION ag_catalog.edge_to_agtype(edge) +AS IMPLICIT; + +CREATE FUNCTION ag_catalog.vertex_to_json(vertex) + RETURNS json + LANGUAGE c + IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION ag_catalog.edge_to_json(edge) + RETURNS json + LANGUAGE c + IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (vertex AS json) + WITH FUNCTION ag_catalog.vertex_to_json(vertex); + +CREATE CAST (edge AS json) + WITH FUNCTION ag_catalog.edge_to_json(edge); + +CREATE FUNCTION ag_catalog.vertex_to_jsonb(vertex) + RETURNS jsonb + LANGUAGE c + IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION ag_catalog.edge_to_jsonb(edge) + RETURNS jsonb + LANGUAGE c + IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +CREATE CAST (vertex AS jsonb) + WITH FUNCTION ag_catalog.vertex_to_jsonb(vertex); + +CREATE CAST (edge AS jsonb) + WITH FUNCTION ag_catalog.edge_to_jsonb(edge); + +-- +-- Equality operators for vertex and edge (compare by id) +-- +CREATE FUNCTION ag_catalog.vertex_eq(vertex, vertex) + RETURNS boolean + LANGUAGE sql + IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS $$ SELECT $1.id = $2.id $$; + +CREATE OPERATOR = ( + FUNCTION = ag_catalog.vertex_eq, + LEFTARG = vertex, + RIGHTARG = vertex, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES, + MERGES +); + +CREATE FUNCTION ag_catalog.vertex_ne(vertex, vertex) + RETURNS boolean + LANGUAGE sql + IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS $$ SELECT $1.id <> $2.id $$; + +CREATE OPERATOR <> ( + FUNCTION = ag_catalog.vertex_ne, + LEFTARG = vertex, + RIGHTARG = vertex, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +CREATE FUNCTION ag_catalog.edge_eq(edge, edge) + RETURNS boolean + LANGUAGE sql + IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS $$ SELECT $1.id = $2.id $$; + +CREATE OPERATOR = ( + FUNCTION = ag_catalog.edge_eq, + LEFTARG = edge, + RIGHTARG = edge, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES, + MERGES +); + +CREATE FUNCTION ag_catalog.edge_ne(edge, edge) + RETURNS boolean + LANGUAGE sql + IMMUTABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS $$ SELECT $1.id <> $2.id $$; + +CREATE OPERATOR <> ( + FUNCTION = ag_catalog.edge_ne, + LEFTARG = edge, + RIGHTARG = edge, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel +); + CREATE FUNCTION ag_catalog._ag_enforce_edge_uniqueness2(graphid, graphid) RETURNS bool LANGUAGE c diff --git a/src/backend/catalog/ag_label.c b/src/backend/catalog/ag_label.c index b6dcf77a3..f7f272588 100644 --- a/src/backend/catalog/ag_label.c +++ b/src/backend/catalog/ag_label.c @@ -166,6 +166,17 @@ char *get_label_seq_relation_name(const char *label_name) return psprintf("%s_id_seq", label_name); } +char *get_label_name(int32 label_id, Oid graph_oid) +{ + label_cache_data *cache_data; + + cache_data = search_label_graph_oid_cache(graph_oid, label_id); + if (cache_data) + return NameStr(cache_data->name); + else + return NULL; +} + PG_FUNCTION_INFO_V1(_label_name); /* @@ -175,9 +186,9 @@ PG_FUNCTION_INFO_V1(_label_name); Datum _label_name(PG_FUNCTION_ARGS) { char *label_name; - label_cache_data *label_cache; Oid graph; uint32 label_id; + agtype *result; if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) { @@ -186,7 +197,7 @@ Datum _label_name(PG_FUNCTION_ARGS) } graph = PG_GETARG_OID(0); - + /* Check if the graph OID is valid */ if (!graph_namespace_exists(graph)) { @@ -194,11 +205,8 @@ Datum _label_name(PG_FUNCTION_ARGS) 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); + label_id = get_graphid_label_id(AG_GETARG_GRAPHID(1)); + label_name = get_label_name(label_id, graph); /* If label_name is not found, error out */ if (label_name == NULL) @@ -208,10 +216,17 @@ Datum _label_name(PG_FUNCTION_ARGS) label_id, graph))); } + /* Convert cstring to agtype string */ if (IS_AG_DEFAULT_LABEL(label_name)) - PG_RETURN_CSTRING(""); + { + result = DATUM_GET_AGTYPE_P(string_to_agtype("")); + } + else + { + result = DATUM_GET_AGTYPE_P(string_to_agtype(label_name)); + } - PG_RETURN_CSTRING(label_name); + PG_RETURN_POINTER(result); } PG_FUNCTION_INFO_V1(_label_id); diff --git a/src/backend/executor/cypher_create.c b/src/backend/executor/cypher_create.c index 2091ea29c..38bfe7d16 100644 --- a/src/backend/executor/cypher_create.c +++ b/src/backend/executor/cypher_create.c @@ -423,7 +423,7 @@ static void create_edge(cypher_create_custom_scan_state *css, Datum result; result = make_edge( - id, start_id, end_id, CStringGetDatum(node->label_name), + id, start_id, end_id, string_to_agtype(node->label_name), scanTupleSlot->tts_values[node->prop_attr_num]); if (CYPHER_TARGET_NODE_IN_PATH(node->flags)) @@ -513,7 +513,7 @@ static Datum create_vertex(cypher_create_custom_scan_state *css, scantuple = ps->ps_ExprContext->ecxt_scantuple; /* make the vertex agtype */ - result = make_vertex(id, CStringGetDatum(node->label_name), + result = make_vertex(id, string_to_agtype(node->label_name), scanTupleSlot->tts_values[node->prop_attr_num]); /* append to the path list */ diff --git a/src/backend/executor/cypher_merge.c b/src/backend/executor/cypher_merge.c index 9136825ab..7fbe0e417 100644 --- a/src/backend/executor/cypher_merge.c +++ b/src/backend/executor/cypher_merge.c @@ -1119,7 +1119,7 @@ static Datum merge_vertex(cypher_merge_custom_scan_state *css, Datum result; /* make the vertex agtype */ - result = make_vertex(id, CStringGetDatum(node->label_name), prop); + result = make_vertex(id, string_to_agtype(node->label_name), prop); /* append to the path list */ if (CYPHER_TARGET_NODE_IN_PATH(node->flags)) @@ -1458,7 +1458,7 @@ static void merge_edge(cypher_merge_custom_scan_state *css, Datum result; result = make_edge(id, start_id, end_id, - CStringGetDatum(node->label_name), prop); + string_to_agtype(node->label_name), prop); /* add the Datum to the list of entities for creating the path variable */ if (CYPHER_TARGET_NODE_IN_PATH(node->flags)) diff --git a/src/backend/executor/cypher_set.c b/src/backend/executor/cypher_set.c index d1837fb16..db1850616 100644 --- a/src/backend/executor/cypher_set.c +++ b/src/backend/executor/cypher_set.c @@ -23,6 +23,7 @@ #include "executor/cypher_executor.h" #include "executor/cypher_utils.h" +#include "utils/agtype.h" static void begin_cypher_set(CustomScanState *node, EState *estate, int eflags); @@ -513,7 +514,7 @@ static void process_update_list(CustomScanState *node) if (original_entity_value->type == AGTV_VERTEX) { new_entity = make_vertex(GRAPHID_GET_DATUM(id->val.int_value), - CStringGetDatum(label_name), + string_to_agtype(label_name), AGTYPE_P_GET_DATUM(agtype_value_to_agtype(altered_properties))); slot = populate_vertex_tts(slot, id, altered_properties); @@ -526,7 +527,7 @@ static void process_update_list(CustomScanState *node) 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), + string_to_agtype(label_name), AGTYPE_P_GET_DATUM(agtype_value_to_agtype(altered_properties))); slot = populate_edge_tts(slot, id, startid, endid, diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index a5413bdaa..b362a93d2 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -38,6 +38,8 @@ #include "parser/parsetree.h" #include "parser/parse_relation.h" #include "rewrite/rewriteHandler.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" #include "catalog/ag_graph.h" #include "catalog/ag_label.h" @@ -909,6 +911,15 @@ transform_cypher_union_tree(cypher_parsestate *cpstate, cypher_clause *clause, int32 rescoltypmod; Oid rescolcoll; + /* Cast vertex/edge to agtype for UNION compatibility */ + lcolnode = coerce_entity_to_agtype(pstate, lcolnode); + ltle->expr = (Expr *) lcolnode; + lcoltype = exprType(lcolnode); + + rcolnode = coerce_entity_to_agtype(pstate, rcolnode); + rtle->expr = (Expr *) rcolnode; + rcoltype = exprType(rcolnode); + /* select common type, same as CASE et al */ rescoltype = select_common_type(pstate, list_make2(lcolnode, rcolnode), @@ -1315,6 +1326,9 @@ static Query *transform_cypher_delete(cypher_parsestate *cpstate, if (!clause->next) { delete_data->flags |= CYPHER_CLAUSE_FLAG_TERMINAL; + + /* query expects agtype from delete */ + coerce_target_entities_to_agtype(pstate, query->targetList); } func_expr = make_clause_func_expr(DELETE_CLAUSE_FUNCTION_NAME, @@ -1629,6 +1643,9 @@ static Query *transform_cypher_set(cypher_parsestate *cpstate, if (!clause->next) { set_items_target_list->flags |= CYPHER_CLAUSE_FLAG_TERMINAL; + + /* query expects agtype from set */ + coerce_target_entities_to_agtype(pstate, query->targetList); } func_expr = make_clause_func_expr(SET_CLAUSE_FUNCTION_NAME, @@ -1805,12 +1822,14 @@ cypher_update_information *transform_cypher_set_item_list( */ if (IsA(set_item->expr, ColumnRef)) { - List *qualified_name, *args; + List *fname; - qualified_name = list_make2(makeString("ag_catalog"), - makeString("age_properties")); - args = list_make1(set_item->expr); - set_item->expr = (Node *)makeFuncCall(qualified_name, args, + /* + * make unqualified function name so that potential + * optimization can kick in + */ + fname = list_make1(makeString("properties")); + set_item->expr = (Node *)makeFuncCall(fname, list_make1(set_item->expr), COERCE_SQL_SYNTAX, -1); } } @@ -4534,26 +4553,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_properties_expr(prop_var); } ((cypher_map*)node->props)->keep_null = true; @@ -4663,26 +4663,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_properties_expr(prop_var); } ((cypher_map*)rel->props)->keep_null = true; @@ -4838,17 +4819,25 @@ transform_match_create_path_variable(cypher_parsestate *cpstate, if (entity->expr != NULL) { + Node *entity_expr = (Node *)entity->expr; + /* * Is it a NULL constant, meaning there was an invalid label? * If so, flag it for later */ - if (IsA(entity->expr, Const) && - ((Const*)(entity->expr))->constisnull) + if (IsA(entity_expr, Const) && + ((Const*)(entity_expr))->constisnull) { null_path_entity = true; } - entity_exprs = lappend(entity_exprs, entity->expr); + /* + * If the entity is a vertex/edge, convert it to agtype since + * _agtype_build_path expects agtype arguments. + */ + entity_expr = coerce_entity_to_agtype(pstate, entity_expr); + + entity_exprs = lappend(entity_exprs, entity_expr); } } @@ -4944,8 +4933,36 @@ static Node *make_qual(cypher_parsestate *cpstate, { List *qualified_name, *args; Node *node; + char *entity_name; + + if (is_vertex_or_edge((Node *) entity->expr)) + { + A_Indirection *indir = makeNode(A_Indirection); + ColumnRef *cr = makeNode(ColumnRef); + + if (entity->type == ENT_VERTEX) + { + entity_name = entity->entity.node->name; + } + else if (entity->type == ENT_EDGE) + { + entity_name = entity->entity.rel->name; + } + else + { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unknown entity type"))); + } - if (IsA(entity->expr, Var)) + cr->fields = list_make1(makeString(entity_name)); + cr->location = -1; + + indir->arg = (Node *)cr; + indir->indirection = list_make1(makeString(col_name)); + + node = (Node *)indir; + } + else if (IsA(entity->expr, Var)) { char *function_name; @@ -4954,14 +4971,12 @@ static Node *make_qual(cypher_parsestate *cpstate, qualified_name = list_make2(makeString("ag_catalog"), makeString(function_name)); - args = list_make1(entity->expr); node = (Node *)makeFuncCall(qualified_name, args, COERCE_EXPLICIT_CALL, -1); } else { - char *entity_name; ColumnRef *cr = makeNode(ColumnRef); if (entity->type == ENT_EDGE) @@ -4986,6 +5001,87 @@ static Node *make_qual(cypher_parsestate *cpstate, return node; } +/* + * Helper function to get field number and type for vertex/edge field access. + * Vertex: (id(1), label(2), properties(3)) + * Edge: (id(1), label(2), end_id(3), start_id(4), properties(5)) + */ +void get_record_field_info(char *field_name, Oid entity_type, + AttrNumber *fieldnum, Oid *fieldtype) +{ + bool is_vertex = (entity_type == VERTEXOID); + bool is_edge = (entity_type == EDGEOID); + + Assert(field_name != NULL); + Assert(fieldnum != NULL); + Assert(fieldtype != NULL); + Assert(is_vertex || is_edge); + + *fieldnum = InvalidAttrNumber; + *fieldtype = InvalidOid; + + if (strcasecmp(field_name, "id") == 0) + { + *fieldnum = 1; + *fieldtype = GRAPHIDOID; + } + else if (is_vertex) + { + if (strcasecmp(field_name, "label") == 0) + { + *fieldnum = 2; + *fieldtype = AGTYPEOID; + } + else if (strcasecmp(field_name, "properties") == 0) + { + *fieldnum = 3; + *fieldtype = AGTYPEOID; + } + } + else if (is_edge) + { + if (strcasecmp(field_name, "label") == 0 || + strcasecmp(field_name, "type") == 0) + { + *fieldnum = 2; + *fieldtype = AGTYPEOID; + } + else if (strcasecmp(field_name, "end_id") == 0 || + strcasecmp(field_name, "endnode") == 0) + { + *fieldnum = 3; + *fieldtype = GRAPHIDOID; + } + else if (strcasecmp(field_name, "start_id") == 0 || + strcasecmp(field_name, "startnode") == 0) + { + *fieldnum = 4; + *fieldtype = GRAPHIDOID; + } + else if (strcasecmp(field_name, "properties") == 0) + { + *fieldnum = 5; + *fieldtype = AGTYPEOID; + } + } +} + +/* + * Helper function to build a FieldSelect node + */ +FieldSelect *make_field_select(Var *var, AttrNumber fieldnum, Oid resulttype) +{ + FieldSelect *fselect = makeNode(FieldSelect); + + fselect->arg = (Expr *)copyObject(var); + fselect->fieldnum = fieldnum; + fselect->resulttype = resulttype; + fselect->resulttypmod = -1; + fselect->resultcollid = InvalidOid; + + return fselect; +} + static Expr *transform_cypher_edge(cypher_parsestate *cpstate, cypher_relationship *rel, List **target_list, @@ -5536,17 +5632,14 @@ static Node *make_edge_expr(cypher_parsestate *cpstate, { ParseState *pstate = (ParseState *)cpstate; Oid label_name_func_oid; - Oid func_oid; Node *id, *start_id, *end_id; Const *graph_oid_const; Node *props; - List *args, *label_name_args; - FuncExpr *func_expr; + List *label_name_args; FuncExpr *label_name_func_expr; + RowExpr *row_expr; - func_oid = get_ag_func_oid("_agtype_build_edge", 5, GRAPHIDOID, GRAPHIDOID, - GRAPHIDOID, CSTRINGOID, AGTYPEOID); - + /* Get raw column references */ id = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_ID, -1); start_id = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_START_ID, -1); @@ -5562,40 +5655,45 @@ static Node *make_edge_expr(cypher_parsestate *cpstate, label_name_args = list_make2(graph_oid_const, id); - label_name_func_expr = makeFuncExpr(label_name_func_oid, CSTRINGOID, + label_name_func_expr = makeFuncExpr(label_name_func_oid, AGTYPEOID, label_name_args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); label_name_func_expr->location = -1; props = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_PROPERTIES, -1); - args = list_make4(id, start_id, end_id, label_name_func_expr); - args = lappend(args, props); - - func_expr = makeFuncExpr(func_oid, AGTYPEOID, args, InvalidOid, InvalidOid, - COERCE_EXPLICIT_CALL); - func_expr->location = -1; - - return (Node *)func_expr; + /* + * Create a RowExpr with the edge composite type. + * Edge format: (id, label, end_id, start_id, properties) + * Implicit cast to agtype is used when needed. + */ + row_expr = makeNode(RowExpr); + row_expr->args = list_make5(id, label_name_func_expr, end_id, start_id, props); + row_expr->row_typeid = EDGEOID; + row_expr->row_format = COERCE_EXPLICIT_CALL; + row_expr->colnames = list_make5(makeString("id"), + makeString("label"), + makeString("end_id"), + makeString("start_id"), + makeString("properties")); + row_expr->location = -1; + + return (Node *)row_expr; } static Node *make_vertex_expr(cypher_parsestate *cpstate, ParseNamespaceItem *pnsi) { ParseState *pstate = (ParseState *)cpstate; Oid label_name_func_oid; - Oid func_oid; Node *id; Const *graph_oid_const; Node *props; - List *args, *label_name_args; - FuncExpr *func_expr; + List *label_name_args; FuncExpr *label_name_func_expr; + RowExpr *row_expr; Assert(pnsi != NULL); - func_oid = get_ag_func_oid("_agtype_build_vertex", 3, GRAPHIDOID, - CSTRINGOID, AGTYPEOID); - id = scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_ID, -1); label_name_func_oid = get_ag_func_oid("_label_name", 2, OIDOID, @@ -5607,7 +5705,7 @@ static Node *make_vertex_expr(cypher_parsestate *cpstate, label_name_args = list_make2(graph_oid_const, id); - label_name_func_expr = makeFuncExpr(label_name_func_oid, CSTRINGOID, + label_name_func_expr = makeFuncExpr(label_name_func_oid, AGTYPEOID, label_name_args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); label_name_func_expr->location = -1; @@ -5615,13 +5713,21 @@ static Node *make_vertex_expr(cypher_parsestate *cpstate, props = scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_PROPERTIES, -1); - args = list_make3(id, label_name_func_expr, props); - - func_expr = makeFuncExpr(func_oid, AGTYPEOID, args, InvalidOid, InvalidOid, - COERCE_EXPLICIT_CALL); - func_expr->location = -1; - - return (Node *)func_expr; + /* + * Create a RowExpr with the vertex composite type. + * Vertex format: (id, label, properties) + * Implicit cast to agtype is used when needed. + */ + row_expr = makeNode(RowExpr); + row_expr->args = list_make3(id, label_name_func_expr, props); + row_expr->row_typeid = VERTEXOID; + row_expr->row_format = COERCE_EXPLICIT_CALL; + row_expr->colnames = list_make3(makeString("id"), + makeString("label"), + makeString("properties")); + row_expr->location = -1; + + return (Node *)row_expr; } static Query *transform_cypher_create(cypher_parsestate *cpstate, @@ -5668,8 +5774,10 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate, if (!clause->next) { target_nodes->flags |= CYPHER_CLAUSE_FLAG_TERMINAL; - } + /* query expects agtype from create */ + coerce_target_entities_to_agtype(pstate, query->targetList); + } func_expr = make_clause_func_expr(CREATE_CLAUSE_FUNCTION_NAME, (Node *)target_nodes); @@ -6084,7 +6192,6 @@ static void add_volatile_wrapper_to_target_entry(List *target_list, int resno) TargetEntry *te = (TargetEntry *)lfirst(lc); if (te->resno == resno) { - /* wrap it */ te->expr = add_volatile_wrapper(te->expr); return; } @@ -6680,6 +6787,9 @@ static Query *transform_cypher_merge(cypher_parsestate *cpstate, if (!clause->next) { merge_information->flags |= CYPHER_CLAUSE_FLAG_TERMINAL; + + /* query expects agtype from merge */ + coerce_target_entities_to_agtype(pstate, query->targetList); } /* @@ -6750,6 +6860,36 @@ transform_merge_make_lateral_join(cypher_parsestate *cpstate, Query *query, */ j->larg = transform_clause_for_join(cpstate, clause->prev, &l_rte, &l_nsitem, l_alias); + + /* + * Wrap vertex/edge columns in the left subquery with volatile wrapper + * before adding to namespace and building property expressions since + * our executors are strictly tied to agtype. + * + * This is necessary because: + * 1. The volatile wrapper will be applied to the final targetList later, + * changing column types from VERTEXOID/EDGEOID to AGTYPEOID + * 2. If we build property expressions before this, the Vars will have + * vartype=VERTEXOID but the execution slot will have AGTYPEOID + * 3. By wrapping now, the namespace lookups will see AGTYPEOID columns + * and build expressions with correct type expectations + */ + foreach(lc, l_rte->subquery->targetList) + { + TargetEntry *te = (TargetEntry *)lfirst(lc); + Oid te_type = exprType((Node *)te->expr); + + if (te_type == VERTEXOID || te_type == EDGEOID) + { + /* resno is 1-based */ + int col_idx = te->resno - 1; + + /* Wrap the expression in the subquery targetList */ + te->expr = add_volatile_wrapper(te->expr); + l_nsitem->p_nscolumns[col_idx].p_vartype = AGTYPEOID; + } + } + pstate->p_namespace = lappend(pstate->p_namespace, l_nsitem); /* diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c index fc0335def..32d2714b8 100644 --- a/src/backend/parser/cypher_expr.c +++ b/src/backend/parser/cypher_expr.c @@ -116,6 +116,7 @@ static bool function_exists(char *funcname, char *extension); static Node *coerce_expr_flexible(ParseState *pstate, Node *expr, Oid source_oid, Oid target_oid, int32 t_typemod, bool error_out); +static Oid get_entity_record_type(Node *node); /* transform a cypher expression */ Node *transform_cypher_expr(cypher_parsestate *cpstate, Node *expr, @@ -537,6 +538,25 @@ static Node *transform_AEXPR_OP(cypher_parsestate *cpstate, A_Expr *a) 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); + char *opname = strVal(linitial(a->name)); + Oid lexpr_type = exprType(lexpr); + Oid rexpr_type = exprType(rexpr); + bool is_eq_op = (strcmp(opname, "=") == 0 || strcmp(opname, "<>") == 0); + + /* + * For equality/inequality operators between same vertex/edge types, + * use native operators instead of coercing to agtype. + */ + if (is_eq_op && (lexpr_type == rexpr_type) && + (lexpr_type == VERTEXOID || lexpr_type == EDGEOID)) + { + return (Node *)make_op(pstate, a->name, lexpr, rexpr, last_srf, + a->location); + } + + /* Cast vertex/edge to agtype for operator compatibility */ + lexpr = coerce_entity_to_agtype(pstate, lexpr); + rexpr = coerce_entity_to_agtype(pstate, rexpr); return (Node *)make_op(pstate, a->name, lexpr, rexpr, last_srf, a->location); @@ -871,8 +891,7 @@ static Node *transform_cypher_map_projection(cypher_parsestate *cpstate, FuncExpr *fexpr_new_map; bool has_all_prop_selector; Node *transformed_map_var; - Oid foid_age_properties; - FuncExpr *fexpr_orig_map; + Node *fexpr_orig_map; pstate = (ParseState *)cpstate; keyvals = NIL; @@ -886,11 +905,8 @@ static Node *transform_cypher_map_projection(cypher_parsestate *cpstate, */ 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; + + fexpr_orig_map = make_properties_expr(transformed_map_var); /* * Builds a new map. Each map projection element is transformed into a key @@ -1161,6 +1177,67 @@ static Node *transform_cypher_map(cypher_parsestate *cpstate, cypher_map *cm) return (Node *)fexpr; } +/* + * Check if a node is a vertex or edge composite type. + */ +bool is_vertex_or_edge(Node *node) +{ + Oid typeid; + + if (node == NULL) + return false; + + if (IsA(node, RowExpr)) + typeid = ((RowExpr *)node)->row_typeid; + else if (IsA(node, Var)) + typeid = ((Var *)node)->vartype; + else + return false; + + return (typeid == VERTEXOID || typeid == EDGEOID); +} + +/* + * Extract a field from a vertex/edge Var or RowExpr. + */ +Node *extract_field_from_record(Node *node, char *field_name) +{ + AttrNumber fieldnum; + Oid fieldtype; + Oid type_id = get_entity_record_type(node); + + get_record_field_info(field_name, type_id, + &fieldnum, &fieldtype); + + if (fieldnum == InvalidAttrNumber || fieldtype == InvalidOid) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("fieldname '%s' does not exist for the entity", field_name))); + } + + if (IsA(node, Var)) + { + return (Node *)make_field_select((Var *)node, fieldnum, fieldtype); + } + else if (IsA(node, RowExpr)) + { + RowExpr *re = (RowExpr *)node; + + if (list_length(re->args) < fieldnum) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("field '%s' does not exist in RowExpr", field_name))); + } + + /* RowExpr args are 0-indexed, but fieldnum is 1-indexed */ + return (Node *)list_nth(re->args, fieldnum - 1); + } + + return NULL; +} + /* * 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 @@ -1198,9 +1275,10 @@ static Node *transform_cypher_list(cypher_parsestate *cpstate, cypher_list *cl) foreach (le, cl->elems) { Node *texpr = NULL; + Node *cexpr = lfirst(le); /* transform the argument */ - texpr = transform_cypher_expr_recurse(cpstate, lfirst(le)); + texpr = transform_cypher_expr_recurse(cpstate, cexpr); /* * If we have more than 100 elements we will need to add in the list @@ -1313,32 +1391,36 @@ static Node *transform_column_ref_for_indirection(cypher_parsestate *cpstate, 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) + if (pnsi) { - Node *prev_var = colNameToVar(pstate, relname, false, cr->location); + /* find the properties column of the NSI and return a var for it */ + node = scanNSItemForColumn(pstate, pnsi, levels_up, "properties", + cr->location); + /* + * Error out if we couldn't find it. + * + * TODO: Should we error out or return NULL for further processing? + * For now, just error out. + */ + if (!node) + { + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("could not find properties for %s", relname))); + } + + if (is_vertex_or_edge(node)) + { + node = extract_field_from_record(node, "properties"); + } - return prev_var; + return node; } - /* find the properties column of the NSI and return a var for it */ - node = scanNSItemForColumn(pstate, pnsi, levels_up, "properties", - cr->location); + node = colNameToVar(pstate, relname, false, cr->location); - /* - * Error out if we couldn't find it. - * - * TODO: Should we error out or return NULL for further processing? - * For now, just error out. - */ - if (!node) + if (node && is_vertex_or_edge(node)) { - ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("could not find properties for %s", relname))); + return extract_field_from_record(node, "properties"); } return node; @@ -2001,6 +2083,145 @@ static bool function_exists(char *funcname, char *extension) return found; } +/* + * Check if function name is an accessor function that can be optimized + */ +static bool is_accessor_function(const char *func_name) +{ + return (strcasecmp("id", func_name) == 0 || + strcasecmp("properties", func_name) == 0 || + strcasecmp("type", func_name) == 0 || + strcasecmp("label", func_name) == 0 || + strcasecmp("start_id", func_name) == 0 || + strcasecmp("end_id", func_name) == 0 || + strcasecmp("startnode", func_name) == 0 || + strcasecmp("endnode", func_name) == 0); +} + +static Oid get_entity_record_type(Node *node) +{ + Oid typeid = InvalidOid; + + if (IsA(node, RowExpr)) + { + typeid = ((RowExpr *)node)->row_typeid; + } + else if (IsA(node, Var)) + { + typeid = ((Var *)node)->vartype; + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("argument must be a vertex or edge record"))); + } + + if (typeid != VERTEXOID && typeid != EDGEOID) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("argument must be a vertex or edge record"))); + } + + return typeid; +} + +/* + * Optimize accessor functions when called on RECORD entities + * Extracts fields directly using FieldSelect instead of building and parsing agtype + */ +static Node *optimize_accessor_function(cypher_parsestate *cpstate, + const char *func_name, + FuncCall *fn, + List *targs) +{ + ParseState *pstate = &cpstate->pstate; + Node *arg; + AttrNumber fieldnum; + Oid fieldtype; + Node *result; + Oid entity_type; + bool is_node_func = (strcasecmp(func_name, "startNode") == 0 || + strcasecmp(func_name, "endNode") == 0); + + if (is_node_func) + { + if (list_length(targs) < 2) + return NULL; + arg = (Node *)lsecond(targs); + } + else + { + if (list_length(targs) < 1) + return NULL; + arg = (Node *)linitial(targs); + } + + if (!is_vertex_or_edge(arg)) + { + return NULL; + } + + entity_type = get_entity_record_type(arg); + get_record_field_info((char *)func_name, entity_type, &fieldnum, &fieldtype); + + if (fieldnum != InvalidAttrNumber && fieldtype != InvalidOid) + { + Node *field = NULL; + + /* Validate RowExpr has enough fields */ + if (IsA(arg, RowExpr)) + { + RowExpr *re = (RowExpr *)arg; + if (list_length(re->args) < fieldnum) + { + return NULL; + } + + /* RowExpr args are 0-indexed, but fieldnum is 1-indexed */ + field = (Node *)list_nth(re->args, fieldnum - 1); + } + else if (IsA(arg, Var)) + { + field = (Node *)make_field_select((Var *)arg, fieldnum, fieldtype); + } + + /* For startNode/endNode functions on edges, look up the vertex */ + if (is_node_func && entity_type == EDGEOID) + { + Oid func_oid; + Node *graph_name; + + graph_name = (Node *) makeConst(TEXTOID, -1, InvalidOid, -1, + CStringGetTextDatum(cpstate->graph_name), + false, false); + + /* Call _get_vertex_by_graphid(graph_name, vertex_id) */ + func_oid = get_ag_func_oid("_get_vertex_by_graphid", 2, TEXTOID, GRAPHIDOID); + result = (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(graph_name, field), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + } + else + { + result = field; + } + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s() argument must be an %s or null", + func_name, + (entity_type == VERTEXOID) ? "edge" : "vertex"), + parser_errposition(pstate, fn->location))); + } + + return result; +} + /* * Code borrowed from PG's transformFuncCall and updated for AGE */ @@ -2012,14 +2233,24 @@ static Node *transform_FuncCall(cypher_parsestate *cpstate, FuncCall *fn) List *fname = NIL; ListCell *arg; Node *retval = NULL; + bool is_accessor = false; + + /* Get function name to check if it has special optimizations */ + if (list_length(fn->funcname) == 1) + { + is_accessor = is_accessor_function(strVal(linitial(fn->funcname))); + } /* Transform the list of arguments ... */ foreach(arg, fn->args) { Node *farg = NULL; + Node *transformed_arg = NULL; farg = (Node *)lfirst(arg); - targs = lappend(targs, transform_cypher_expr_recurse(cpstate, farg)); + transformed_arg = transform_cypher_expr_recurse(cpstate, farg); + + targs = lappend(targs, transformed_arg); } /* within group should not happen */ @@ -2051,10 +2282,10 @@ static Node *transform_FuncCall(cypher_parsestate *cpstate, FuncCall *fn) * 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)) + (strcasecmp("startNode", name) == 0 || + strcasecmp("endNode", name) == 0 || + strcasecmp("vle", name) == 0 || + strcasecmp("vertex_stats", name) == 0)) { char *graph_name = cpstate->graph_name; Datum d = string_to_agtype(graph_name); @@ -2063,6 +2294,16 @@ static Node *transform_FuncCall(cypher_parsestate *cpstate, FuncCall *fn) targs = lcons(c, targs); } + + /* + * Try to optimize accessor functions on RECORD entities + */ + if (is_accessor) + { + Node *optimized = optimize_accessor_function(cpstate, name, fn, targs); + if (optimized != NULL) + return optimized; + } } /* * If it's not in age, check if it's a potential call to some function @@ -2193,8 +2434,16 @@ static Node *transform_CaseExpr(cypher_parsestate *cpstate, CaseExpr /* generate placeholder for test expression */ if (arg) { - if (exprType(arg) == UNKNOWNOID) + Oid argtype = exprType(arg); + + if (argtype == UNKNOWNOID) + { arg = coerce_to_common_type(pstate, arg, TEXTOID, "CASE"); + } + else if (argtype == VERTEXOID || argtype == EDGEOID) + { + arg = coerce_to_common_type(pstate, arg, AGTYPEOID, "CASE"); + } assign_expr_collations(pstate, arg); @@ -2284,15 +2533,33 @@ static Node *transform_CaseExpr(cypher_parsestate *cpstate, CaseExpr /* * we pass a NULL context to select_common_type because the common types can * only be AGTYPEOID or BOOLOID. If it returns invalidoid, we know there is a - * boolean involved. + * boolean or record involved. */ ptype = select_common_type(pstate, resultexprs, NULL, NULL); - /* InvalidOid shows that there is a boolean in the result expr. */ + /* InvalidOid shows that there is a boolean or record in the result expr. */ if (ptype == InvalidOid) { - /* we manually set the type to boolean here to handle the bool casting. */ - ptype = BOOLOID; + /* + * If we have record type, we need to cast it to agtype here, + * else return boolean + */ + ListCell *lc; + bool has_vertex_edge = false; + + foreach(lc, resultexprs) + { + Node *expr = (Node *) lfirst(lc); + Oid exprtype = exprType(expr); + + if (exprtype == VERTEXOID || exprtype == EDGEOID) + { + has_vertex_edge = true; + break; + } + } + + ptype = has_vertex_edge ? AGTYPEOID : BOOLOID; } Assert(OidIsValid(ptype)); @@ -2433,3 +2700,70 @@ static Node *transform_SubLink(cypher_parsestate *cpstate, SubLink *sublink) return result; } + +/* + * coerce_entity_to_agtype + * + * If the node is a vertex or edge type, coerce it to agtype. + * Returns the coerced node, or the original node if no coercion needed. + */ +Node *coerce_entity_to_agtype(ParseState *pstate, Node *node) +{ + Oid node_type = exprType(node); + + if (node_type == VERTEXOID || node_type == EDGEOID) + { + return coerce_to_target_type(pstate, node, node_type, + AGTYPEOID, -1, COERCION_EXPLICIT, + COERCE_IMPLICIT_CAST, -1); + } + + return node; +} + +/* + * coerce_target_entities_to_agtype + * + * Iterate through a target list and coerce any vertex/edge types to agtype. + * Used for terminal clauses (SET, DELETE, CREATE, MERGE) to ensure the + * executor receives agtype. + */ +void coerce_target_entities_to_agtype(ParseState *pstate, List *target_list) +{ + ListCell *lc; + + foreach(lc, target_list) + { + TargetEntry *te = (TargetEntry *)lfirst(lc); + Oid te_type = exprType((Node *)te->expr); + + if (te_type == VERTEXOID || te_type == EDGEOID) + { + te->expr = (Expr *)coerce_to_target_type( + pstate, (Node *)te->expr, te_type, + AGTYPEOID, -1, COERCION_EXPLICIT, + COERCE_IMPLICIT_CAST, -1); + } + } +} + +Node *make_properties_expr(Node *prop_var) +{ + Node *prop_expr; + + if (is_vertex_or_edge(prop_var)) + { + prop_expr = extract_field_from_record(prop_var, "properties"); + } + else + { + /* Call age_properties for non-RECORD types */ + Oid foid = get_ag_func_oid("age_properties", 1, AGTYPEOID); + prop_expr = (Node *)makeFuncExpr(foid, AGTYPEOID, + list_make1(prop_var), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + } + + return prop_expr; +} diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index 02fc3221c..a76c0b575 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -37,6 +37,7 @@ #include "access/genam.h" #include "access/heapam.h" +#include "access/htup_details.h" #include "catalog/namespace.h" #include "catalog/pg_collation_d.h" #include "catalog/pg_operator_d.h" @@ -55,6 +56,7 @@ #include "utils/agtype_raw.h" #include "catalog/ag_graph.h" #include "catalog/ag_label.h" +#include "utils/ag_func.h" /* State structure for Percentile aggregate functions */ typedef struct PercentileGroupAggState @@ -157,7 +159,6 @@ static bool is_array_path(agtype_value *agtv); /* graph entity retrieval */ static Datum get_vertex(const char *graph, const char *vertex_label, int64 graphid); -static char *get_label_name(const char *graph_name, graphid element_graphid); 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, @@ -242,6 +243,43 @@ void clear_global_Oids_AGTYPE(void) g_AGTYPEARRAYOID = InvalidOid; } +/* global storage of OID for vertex and edge composite types */ +static Oid g_VERTEXOID = InvalidOid; +static Oid g_EDGEOID = InvalidOid; + +/* helper function to quickly set, if necessary, and retrieve VERTEXOID */ +Oid get_VERTEXOID(void) +{ + if (g_VERTEXOID == InvalidOid) + { + g_VERTEXOID = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid, + CStringGetDatum("vertex"), + ObjectIdGetDatum(ag_catalog_namespace_id())); + } + + return g_VERTEXOID; +} + +/* helper function to quickly set, if necessary, and retrieve EDGEOID */ +Oid get_EDGEOID(void) +{ + if (g_EDGEOID == InvalidOid) + { + g_EDGEOID = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid, + CStringGetDatum("edge"), + ObjectIdGetDatum(ag_catalog_namespace_id())); + } + + return g_EDGEOID; +} + +/* helper function to clear the VERTEX/EDGE OIDs after a drop extension */ +void clear_global_Oids_VERTEX_EDGE(void) +{ + g_VERTEXOID = InvalidOid; + g_EDGEOID = InvalidOid; +} + /* fast helper function to test for AGTV_NULL in an agtype */ bool is_agtype_null(agtype *agt_arg) { @@ -1437,17 +1475,22 @@ static void agtype_categorize_type(Oid typoid, agt_type_category *tcategory, break; default: - /* Check for arrays and composites */ if (typoid == AGTYPEOID) { *tcategory = AGT_TYPE_AGTYPE; } + else if (typoid == VERTEXOID || typoid == EDGEOID) + { + *tcategory = AGT_TYPE_AGTYPE; + *outfuncoid = (typoid == VERTEXOID) ? + get_ag_func_oid("vertex_to_agtype", 1, VERTEXOID) : + get_ag_func_oid("edge_to_agtype", 1, EDGEOID); + } else if (OidIsValid(get_element_type(typoid)) || typoid == ANYARRAYOID || typoid == RECORDARRAYOID) { *tcategory = AGT_TYPE_ARRAY; } - /* includes RECORDOID */ else if (type_is_rowtype(typoid)) { *tcategory = AGT_TYPE_COMPOSITE; @@ -1678,14 +1721,17 @@ static void datum_to_agtype(Datum val, bool is_null, agtype_in_state *result, case AGT_TYPE_AGTYPE: case AGT_TYPE_JSONB: { - agtype *jsonb = DATUM_GET_AGTYPE_P(val); + agtype *jsonb; agtype_iterator *it; + if (OidIsValid(outfuncoid)) + val = OidFunctionCall1(outfuncoid, val); + /* * val is actually jsonb datum but we can handle it as an agtype * datum because agtype is currently an extension of jsonb. */ - + jsonb = DATUM_GET_AGTYPE_P(val); it = agtype_iterator_init(&jsonb->root); if (AGT_ROOT_IS_SCALAR(jsonb)) @@ -2282,12 +2328,14 @@ Datum make_path(List *path) PG_FUNCTION_INFO_V1(_agtype_build_vertex); /* - * SQL function agtype_build_vertex(graphid, cstring, agtype) + * SQL function agtype_build_vertex(graphid, agtype, agtype) */ Datum _agtype_build_vertex(PG_FUNCTION_ARGS) { graphid id; char *label; + agtype *label_agtype; + agtype_value *label_value; agtype *properties; agtype_build_state *bstate; agtype *rawscalar; @@ -2308,7 +2356,25 @@ Datum _agtype_build_vertex(PG_FUNCTION_ARGS) } id = AG_GETARG_GRAPHID(0); - label = PG_GETARG_CSTRING(1); + label_agtype = AG_GET_ARG_AGTYPE_P(1); + + /* Extract the string from the agtype label */ + if (!AGT_ROOT_IS_SCALAR(label_agtype)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("_agtype_build_vertex() label must be a scalar string"))); + } + + label_value = get_ith_agtype_value_from_container(&label_agtype->root, 0); + if (label_value->type != AGTV_STRING) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("_agtype_build_vertex() label must be a string"))); + } + + label = pnstrdup(label_value->val.string.val, label_value->val.string.len); if (fcinfo->args[2].isnull) { @@ -2343,7 +2409,8 @@ Datum _agtype_build_vertex(PG_FUNCTION_ARGS) rawscalar = build_agtype(bstate); pfree_agtype_build_state(bstate); - PG_FREE_IF_COPY(label, 1); + pfree(label); + PG_FREE_IF_COPY(label_agtype, 1); PG_FREE_IF_COPY(properties, 2); PG_RETURN_POINTER(rawscalar); @@ -2357,7 +2424,7 @@ Datum make_vertex(Datum id, Datum label, Datum properties) PG_FUNCTION_INFO_V1(_agtype_build_edge); /* - * SQL function agtype_build_edge(graphid, graphid, graphid, cstring, agtype) + * SQL function agtype_build_edge(graphid, graphid, graphid, agtype, agtype) */ Datum _agtype_build_edge(PG_FUNCTION_ARGS) { @@ -2365,6 +2432,8 @@ Datum _agtype_build_edge(PG_FUNCTION_ARGS) agtype *edge, *rawscalar; graphid id, start_id, end_id; char *label; + agtype *label_agtype; + agtype_value *label_value; agtype *properties; /* process graph id */ @@ -2381,10 +2450,28 @@ Datum _agtype_build_edge(PG_FUNCTION_ARGS) if (fcinfo->args[3].isnull) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("_agtype_build_vertex() label cannot be NULL"))); + errmsg("_agtype_build_edge() label cannot be NULL"))); + } + + label_agtype = AG_GET_ARG_AGTYPE_P(3); + + /* Extract the string from the agtype label */ + if (!AGT_ROOT_IS_SCALAR(label_agtype)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("_agtype_build_edge() label must be a scalar string"))); + } + + label_value = get_ith_agtype_value_from_container(&label_agtype->root, 0); + if (label_value->type != AGTV_STRING) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("_agtype_build_edge() label must be a string"))); } - label = PG_GETARG_CSTRING(3); + label = pnstrdup(label_value->val.string.val, label_value->val.string.len); /* process end_id */ if (fcinfo->args[2].isnull) @@ -2446,7 +2533,8 @@ Datum _agtype_build_edge(PG_FUNCTION_ARGS) rawscalar = build_agtype(bstate); pfree_agtype_build_state(bstate); - PG_FREE_IF_COPY(label, 3); + pfree(label); + PG_FREE_IF_COPY(label_agtype, 3); PG_FREE_IF_COPY(properties, 4); PG_RETURN_POINTER(rawscalar); @@ -2459,6 +2547,308 @@ Datum make_edge(Datum id, Datum startid, Datum endid, Datum label, properties); } +PG_FUNCTION_INFO_V1(vertex_to_agtype); + +/* + * Cast function: vertex -> agtype + * Vertex: (id graphid, label agtype, properties agtype) + */ +Datum vertex_to_agtype(PG_FUNCTION_ARGS) +{ + HeapTupleHeader rec; + TupleDesc tupdesc; + HeapTupleData tuple; + Datum *values; + bool *nulls; + graphid id; + agtype *label; + agtype *properties; + Datum result; + + rec = PG_GETARG_HEAPTUPLEHEADER(0); + + tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(rec), + HeapTupleHeaderGetTypMod(rec)); + + tuple.t_len = HeapTupleHeaderGetDatumLength(rec); + tuple.t_data = rec; + + values = (Datum *) palloc(3 * sizeof(Datum)); + nulls = (bool *) palloc(3 * sizeof(bool)); + heap_deform_tuple(&tuple, tupdesc, values, nulls); + + if (nulls[0]) + ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("vertex id cannot be NULL"))); + if (nulls[1]) + ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("vertex label cannot be NULL"))); + + id = DatumGetInt64(values[0]); + label = DATUM_GET_AGTYPE_P(values[1]); + properties = nulls[2] ? NULL : DATUM_GET_AGTYPE_P(values[2]); + + if (properties == NULL) + { + agtype_build_state *bstate = init_agtype_build_state(0, AGT_FOBJECT); + properties = build_agtype(bstate); + pfree_agtype_build_state(bstate); + } + + result = make_vertex(Int64GetDatum(id), PointerGetDatum(label), + PointerGetDatum(properties)); + + ReleaseTupleDesc(tupdesc); + pfree(values); + pfree(nulls); + + return result; +} + +PG_FUNCTION_INFO_V1(edge_to_agtype); + +/* + * Cast function: edge -> agtype + * Edge: (id graphid, label agtype, end_id graphid, start_id graphid, properties agtype) + */ +Datum edge_to_agtype(PG_FUNCTION_ARGS) +{ + HeapTupleHeader rec; + TupleDesc tupdesc; + HeapTupleData tuple; + Datum *values; + bool *nulls; + graphid id, start_id, end_id; + agtype *label; + agtype *properties; + Datum result; + + rec = PG_GETARG_HEAPTUPLEHEADER(0); + + tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(rec), + HeapTupleHeaderGetTypMod(rec)); + + tuple.t_len = HeapTupleHeaderGetDatumLength(rec); + tuple.t_data = rec; + + values = (Datum *) palloc(5 * sizeof(Datum)); + nulls = (bool *) palloc(5 * sizeof(bool)); + heap_deform_tuple(&tuple, tupdesc, values, nulls); + + if (nulls[0]) + ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("edge id cannot be NULL"))); + if (nulls[1]) + ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("edge label cannot be NULL"))); + if (nulls[2]) + ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("edge end_id cannot be NULL"))); + if (nulls[3]) + ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("edge start_id cannot be NULL"))); + + id = DatumGetInt64(values[0]); + label = DATUM_GET_AGTYPE_P(values[1]); + end_id = DatumGetInt64(values[2]); + start_id = DatumGetInt64(values[3]); + properties = nulls[4] ? NULL : DATUM_GET_AGTYPE_P(values[4]); + + if (properties == NULL) + { + agtype_build_state *bstate = init_agtype_build_state(0, AGT_FOBJECT); + properties = build_agtype(bstate); + pfree_agtype_build_state(bstate); + } + + result = make_edge(Int64GetDatum(id), Int64GetDatum(start_id), + Int64GetDatum(end_id), PointerGetDatum(label), + PointerGetDatum(properties)); + + ReleaseTupleDesc(tupdesc); + pfree(values); + pfree(nulls); + + return result; +} + +/* + * Helper function to build JSON string from vertex composite type + */ +static char *vertex_to_json_string(HeapTupleHeader rec) +{ + TupleDesc tupdesc; + HeapTupleData tuple; + Datum *values; + bool *nulls; + graphid id; + agtype *label; + agtype *properties; + StringInfoData buf; + char *label_str; + char *props_str; + + tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(rec), + HeapTupleHeaderGetTypMod(rec)); + + tuple.t_len = HeapTupleHeaderGetDatumLength(rec); + tuple.t_data = rec; + + values = (Datum *) palloc(3 * sizeof(Datum)); + nulls = (bool *) palloc(3 * sizeof(bool)); + heap_deform_tuple(&tuple, tupdesc, values, nulls); + + if (nulls[0]) + ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("vertex id cannot be NULL"))); + if (nulls[1]) + ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("vertex label cannot be NULL"))); + + id = DatumGetInt64(values[0]); + label = DATUM_GET_AGTYPE_P(values[1]); + properties = nulls[2] ? NULL : DATUM_GET_AGTYPE_P(values[2]); + + label_str = agtype_to_cstring(NULL, &label->root, VARSIZE(label)); + + if (properties != NULL) + { + props_str = agtype_to_cstring_worker(NULL, &properties->root, + VARSIZE(properties), false, false); + } + else + { + props_str = "{}"; + } + + initStringInfo(&buf); + appendStringInfo(&buf, "{\"id\": %ld, \"label\": %s, \"properties\": %s}", + id, label_str, props_str); + + ReleaseTupleDesc(tupdesc); + pfree(values); + pfree(nulls); + + return buf.data; +} + +/* + * Helper function to build JSON string from edge composite type + * Edge: (id graphid, label agtype, end_id graphid, start_id graphid, properties agtype) + */ +static char *edge_to_json_string(HeapTupleHeader rec) +{ + TupleDesc tupdesc; + HeapTupleData tuple; + Datum *values; + bool *nulls; + graphid id, start_id, end_id; + agtype *label; + agtype *properties; + StringInfoData buf; + char *label_str; + char *props_str; + + tupdesc = lookup_rowtype_tupdesc(HeapTupleHeaderGetTypeId(rec), + HeapTupleHeaderGetTypMod(rec)); + + tuple.t_len = HeapTupleHeaderGetDatumLength(rec); + tuple.t_data = rec; + + values = (Datum *) palloc(5 * sizeof(Datum)); + nulls = (bool *) palloc(5 * sizeof(bool)); + heap_deform_tuple(&tuple, tupdesc, values, nulls); + + if (nulls[0]) + ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("edge id cannot be NULL"))); + if (nulls[1]) + ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("edge label cannot be NULL"))); + if (nulls[2]) + ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("edge end_id cannot be NULL"))); + if (nulls[3]) + ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("edge start_id cannot be NULL"))); + + id = DatumGetInt64(values[0]); + label = DATUM_GET_AGTYPE_P(values[1]); + end_id = DatumGetInt64(values[2]); + start_id = DatumGetInt64(values[3]); + properties = nulls[4] ? NULL : DATUM_GET_AGTYPE_P(values[4]); + + label_str = agtype_to_cstring(NULL, &label->root, VARSIZE(label)); + + if (properties != NULL) + { + props_str = agtype_to_cstring_worker(NULL, &properties->root, + VARSIZE(properties), false, false); + } + else + { + props_str = "{}"; + } + + initStringInfo(&buf); + appendStringInfo(&buf, "{\"id\": %ld, \"label\": %s, \"end_id\": %ld, \"start_id\": %ld, \"properties\": %s}", + id, label_str, end_id, start_id, props_str); + + ReleaseTupleDesc(tupdesc); + pfree(values); + pfree(nulls); + + return buf.data; +} + +PG_FUNCTION_INFO_V1(vertex_to_json); + +Datum vertex_to_json(PG_FUNCTION_ARGS) +{ + char *json_str = vertex_to_json_string(PG_GETARG_HEAPTUPLEHEADER(0)); + Datum result = DirectFunctionCall1(json_in, CStringGetDatum(json_str)); + + pfree_if_not_null(json_str); + + PG_RETURN_DATUM(result); +} + +PG_FUNCTION_INFO_V1(vertex_to_jsonb); + +Datum vertex_to_jsonb(PG_FUNCTION_ARGS) +{ + char *json_str = vertex_to_json_string(PG_GETARG_HEAPTUPLEHEADER(0)); + Datum result = DirectFunctionCall1(jsonb_in, CStringGetDatum(json_str)); + + pfree_if_not_null(json_str); + + PG_RETURN_DATUM(result); +} + +PG_FUNCTION_INFO_V1(edge_to_json); + +Datum edge_to_json(PG_FUNCTION_ARGS) +{ + char *json_str = edge_to_json_string(PG_GETARG_HEAPTUPLEHEADER(0)); + Datum result = DirectFunctionCall1(json_in, CStringGetDatum(json_str)); + + pfree_if_not_null(json_str); + + PG_RETURN_DATUM(result); +} + +PG_FUNCTION_INFO_V1(edge_to_jsonb); + +Datum edge_to_jsonb(PG_FUNCTION_ARGS) +{ + char *json_str = edge_to_json_string(PG_GETARG_HEAPTUPLEHEADER(0)); + Datum result = DirectFunctionCall1(jsonb_in, CStringGetDatum(json_str)); + + pfree_if_not_null(json_str); + + PG_RETURN_DATUM(result); +} + static agtype_value *agtype_build_map_as_agtype_value(FunctionCallInfo fcinfo) { int nargs; @@ -5143,6 +5533,7 @@ Datum agtype_typecast_vertex(PG_FUNCTION_ARGS) agtype *arg_agt; agtype_value agtv_key; agtype_value *agtv_graphid, *agtv_label, *agtv_properties; + agtype *label_agtype; Datum result; int count; @@ -5201,9 +5592,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 */ + label_agtype = agtype_value_to_agtype(agtv_label); result = DirectFunctionCall3(_agtype_build_vertex, Int64GetDatum(agtv_graphid->val.int_value), - CStringGetDatum(agtv_label->val.string.val), + PointerGetDatum(label_agtype), PointerGetDatum(agtype_value_to_agtype(agtv_properties))); return result; } @@ -5218,6 +5610,7 @@ Datum agtype_typecast_edge(PG_FUNCTION_ARGS) agtype_value agtv_key; agtype_value *agtv_graphid, *agtv_label, *agtv_properties, *agtv_startid, *agtv_endid; + agtype *label_agtype; Datum result; int count; @@ -5294,11 +5687,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 */ + label_agtype = agtype_value_to_agtype(agtv_label); 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(label_agtype), PointerGetDatum(agtype_value_to_agtype(agtv_properties))); return result; } @@ -5540,66 +5934,6 @@ Datum column_get_datum(TupleDesc tupdesc, HeapTuple tuple, int column, return 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. - */ -static char *get_label_name(const char *graph_name, graphid element_graphid) -{ - 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); - int32 label_id = get_graphid_label_id(element_graphid); - - /* 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)); - - 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); - - tuple = systable_getnext(scan_desc); - if (!HeapTupleIsValid(tuple)) - { - 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); - - /* bail if the number of columns differs */ - if (tupdesc->natts != Natts_ag_label) - { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("Invalid number of attributes for ag_catalog.ag_label"))); - } - - /* get the label name */ - result = NameStr(*DatumGetName(heap_getattr(tuple, Anum_ag_label_name, - tupdesc, &column_is_null))); - /* duplicate it */ - result = pstrdup(result); - - /* end the scan and close the relation */ - systable_endscan(scan_desc); - table_close(ag_label, ShareLock); - - return result; -} - static Datum get_vertex(const char *graph, const char *vertex_label, int64 graphid) { @@ -5609,14 +5943,18 @@ static Datum get_vertex(const char *graph, const char *vertex_label, HeapTuple tuple; TupleDesc tupdesc; Datum id, properties, result; + agtype *label_agtype; + Oid graph_namespace_oid; + Oid vertex_label_table_oid; + Snapshot snapshot; /* get the specific graph namespace (schema) */ - Oid graph_namespace_oid = get_namespace_oid(graph, false); + 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, + vertex_label_table_oid = get_relname_relid(vertex_label, graph_namespace_oid); /* get the active snapshot */ - Snapshot snapshot = GetActiveSnapshot(); + snapshot = GetActiveSnapshot(); /* initialize the scan key */ ScanKeyInit(&scan_keys[0], 1, BTEqualStrategyNumber, F_OIDEQ, @@ -5650,8 +5988,9 @@ static Datum get_vertex(const char *graph, const char *vertex_label, properties = column_get_datum(tupdesc, tuple, 1, "properties", AGTYPEOID, true); /* reconstruct the vertex */ + label_agtype = DATUM_GET_AGTYPE_P(string_to_agtype((char *)vertex_label)); result = DirectFunctionCall3(_agtype_build_vertex, id, - CStringGetDatum(vertex_label), properties); + PointerGetDatum(label_agtype), properties); /* end the scan and close the relation */ table_endscan(scan_desc); table_close(graph_vertex_label, ShareLock); @@ -5669,6 +6008,7 @@ Datum age_startnode(PG_FUNCTION_ARGS) char *graph_name = NULL; char *label_name = NULL; graphid start_id; + int label_id; Datum result; /* we need the graph name */ @@ -5713,7 +6053,9 @@ Datum age_startnode(PG_FUNCTION_ARGS) start_id = agtv_value->val.int_value; /* get the label */ - label_name = get_label_name(graph_name, start_id); + label_id = get_graphid_label_id(start_id); + label_name = get_label_name(label_id, get_graph_oid(graph_name)); + /* it must not be null and must be a string */ Assert(label_name != NULL); @@ -5732,6 +6074,7 @@ Datum age_endnode(PG_FUNCTION_ARGS) char *graph_name = NULL; char *label_name = NULL; graphid end_id; + int32 label_id; Datum result; /* we need the graph name */ @@ -5776,7 +6119,9 @@ Datum age_endnode(PG_FUNCTION_ARGS) end_id = agtv_value->val.int_value; /* get the label */ - label_name = get_label_name(graph_name, end_id); + label_id = get_graphid_label_id(end_id); + label_name = get_label_name(label_id, get_graph_oid(graph_name)); + /* it must not be null and must be a string */ Assert(label_name != NULL); @@ -5785,6 +6130,42 @@ Datum age_endnode(PG_FUNCTION_ARGS) return result; } +PG_FUNCTION_INFO_V1(_get_vertex_by_graphid); + +/* + * Helper function for optimized startNode/endNode + * Fetches a vertex given graph name and vertex graphid + */ +Datum _get_vertex_by_graphid(PG_FUNCTION_ARGS) +{ + char *graph_name = NULL; + graphid vertex_id; + int32 label_id; + char *label_name = NULL; + Datum result; + + /* check for nulls */ + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + PG_RETURN_NULL(); + + /* get the graph name from text */ + graph_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + + /* get the vertex graphid */ + vertex_id = PG_GETARG_INT64(1); + + /* get the label name */ + label_id = get_graphid_label_id(vertex_id); + label_name = get_label_name(label_id, get_graph_oid(graph_name)); + if (label_name == NULL) + PG_RETURN_NULL(); + + /* fetch and return the vertex */ + result = get_vertex(graph_name, label_name, vertex_id); + + return result; +} + PG_FUNCTION_INFO_V1(age_head); Datum age_head(PG_FUNCTION_ARGS) @@ -12138,11 +12519,12 @@ Datum agtype_volatile_wrapper(PG_FUNCTION_ARGS) agtv_result.type = AGTV_BOOL; agtv_result.val.boolean = DatumGetBool(arg); } - else if (type == INT2OID || type == INT4OID || type == INT8OID) + else if (type == INT2OID || type == INT4OID || + type == INT8OID || type == GRAPHIDOID) { agtv_result.type = AGTV_INTEGER; - if (type == INT8OID) + if (type == INT8OID || type == GRAPHIDOID) { agtv_result.val.int_value = DatumGetInt64(arg); } @@ -12185,6 +12567,14 @@ Datum agtype_volatile_wrapper(PG_FUNCTION_ARGS) agtv_result.val.string.val = text_to_cstring(DatumGetTextPP(arg)); agtv_result.val.string.len = strlen(agtv_result.val.string.val); } + else if (type == VERTEXOID) + { + PG_RETURN_DATUM(DirectFunctionCall1(vertex_to_agtype, arg)); + } + else if (type == EDGEOID) + { + PG_RETURN_DATUM(DirectFunctionCall1(edge_to_agtype, arg)); + } else { ereport(ERROR, diff --git a/src/include/catalog/ag_label.h b/src/include/catalog/ag_label.h index 0a8480b1a..e6b9a1ca7 100644 --- a/src/include/catalog/ag_label.h +++ b/src/include/catalog/ag_label.h @@ -74,7 +74,7 @@ Oid get_label_relation(const char *label_name, Oid graph_oid); char *get_label_relation_name(const char *label_name, Oid graph_oid); char get_label_kind(const char *label_name, Oid label_graph); char *get_label_seq_relation_name(const char *label_name); - +char *get_label_name(int32 label_id, Oid graph_oid); bool label_id_exists(Oid graph_oid, int32 label_id); RangeVar *get_label_range_var(char *graph_name, Oid graph_oid, diff --git a/src/include/parser/cypher_clause.h b/src/include/parser/cypher_clause.h index 461a664ca..d0fc3ca22 100644 --- a/src/include/parser/cypher_clause.h +++ b/src/include/parser/cypher_clause.h @@ -21,6 +21,7 @@ #define AG_CYPHER_CLAUSE_H #include "parser/cypher_parse_node.h" +#include "parser/cypher_transform_entity.h" typedef struct cypher_clause cypher_clause; @@ -39,4 +40,12 @@ Query *cypher_parse_sub_analyze(Node *parseTree, CommonTableExpr *parentCTE, bool locked_from_parent, bool resolve_unknowns); + +/* + * Helper to get field info for vertex/edge entity field access + */ +void get_record_field_info(char *field_name, Oid entity_type, + AttrNumber *fieldnum, Oid *fieldtype); +FieldSelect *make_field_select(Var *var, AttrNumber fieldnum, Oid resulttype); + #endif diff --git a/src/include/parser/cypher_expr.h b/src/include/parser/cypher_expr.h index cde40cc87..e069abc6d 100644 --- a/src/include/parser/cypher_expr.h +++ b/src/include/parser/cypher_expr.h @@ -29,5 +29,10 @@ Node *transform_cypher_expr(cypher_parsestate *cpstate, Node *expr, ParseExprKind expr_kind); +Node *make_properties_expr(Node *prop_var); +bool is_vertex_or_edge(Node *node); +Node *extract_field_from_record(Node *node, char *field_name); +Node *coerce_entity_to_agtype(ParseState *pstate, Node *node); +void coerce_target_entities_to_agtype(ParseState *pstate, List *target_list); #endif diff --git a/src/include/utils/agtype.h b/src/include/utils/agtype.h index ab2ba08cc..0626704b9 100644 --- a/src/include/utils/agtype.h +++ b/src/include/utils/agtype.h @@ -565,4 +565,11 @@ void clear_global_Oids_AGTYPE(void); #define AGTYPEOID get_AGTYPEOID() #define AGTYPEARRAYOID get_AGTYPEARRAYOID() +/* Oid accessors for vertex and edge composite types */ +Oid get_VERTEXOID(void); +Oid get_EDGEOID(void); +void clear_global_Oids_VERTEX_EDGE(void); +#define VERTEXOID get_VERTEXOID() +#define EDGEOID get_EDGEOID() + #endif