Skip to content

Commit

Permalink
Extend EXPLAIN and add config param to switch transformation of prope…
Browse files Browse the repository at this point in the history
…rty filter (apache#1262)

* Add explain options

Explain command in the following format is supported
now:
    `EXPLAIN (VERBOSE, COSTS OFF, FORMAT XML) ...`

Note that, this is basically Postgres' EXPLAIN command,
and the purpose of this is to support debugging and
regression tests.

* Add config param to switch transformation method of property filter

When the `age.enable_containment` parameter is on, the agtype
containment operator is used to transform property filter. When off,
access operator is used instead. The former case is preferable for
GIN index, and the later for BTREE expression index.

The idea of replacing containment with access operator in order to
support BTREE index is taken from a patch by Josh Innis.

A note on regression testing- although there are test cases for the
`age.enable_containment` parameter, sometimes it may be useful to
set this parameter before running any tests (not just the ones related
to it). For example, when the logic related to property transformation
changes. It can be set in the `age_regression.conf` file.
  • Loading branch information
rafsun42 authored and Zainab-Saad committed Mar 6, 2024
1 parent 30d9399 commit 3bef830
Show file tree
Hide file tree
Showing 9 changed files with 572 additions and 8 deletions.
5 changes: 3 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ OBJS = src/backend/age.o \
src/backend/utils/load/ag_load_edges.o \
src/backend/utils/load/age_load.o \
src/backend/utils/load/libcsv.o \
src/backend/utils/name_validation.o
src/backend/utils/name_validation.o \
src/backend/utils/ag_guc.o

EXTENSION = age

Expand Down Expand Up @@ -105,7 +106,7 @@ REGRESS = scan \
srcdir=`pwd`

ag_regress_dir = $(srcdir)/regress
REGRESS_OPTS = --load-extension=age --inputdir=$(ag_regress_dir) --outputdir=$(ag_regress_dir) --temp-instance=$(ag_regress_dir)/instance --port=61958 --encoding=UTF-8
REGRESS_OPTS = --load-extension=age --inputdir=$(ag_regress_dir) --outputdir=$(ag_regress_dir) --temp-instance=$(ag_regress_dir)/instance --port=61958 --encoding=UTF-8 --temp-config $(ag_regress_dir)/age_regression.conf

ag_regress_out = instance/ log/ results/ regression.*
EXTRA_CLEAN = $(addprefix $(ag_regress_dir)/, $(ag_regress_out)) src/backend/parser/cypher_gram.c src/include/parser/cypher_gram_def.h src/include/parser/cypher_kwlist_d.h
Expand Down
1 change: 1 addition & 0 deletions regress/age_regression.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#age.enable_containment = on
175 changes: 175 additions & 0 deletions regress/expected/cypher_match.out
Original file line number Diff line number Diff line change
Expand Up @@ -2419,6 +2419,170 @@ SELECT * FROM cypher('cypher_match', $$ MATCH p=(x:r)-[]->(x:R) RETURN p, x $$)
ERROR: multiple labels for variable 'x' are not supported
LINE 1: ...FROM cypher('cypher_match', $$ MATCH p=(x:r)-[]->(x:R) RETUR...
^
--
-- Test age.enable_containment configuration parameter
--
-- Test queries are run before and after switching off this parameter.
-- When on, the containment operator should be used to filter properties.
-- When off, the access operator should be used.
--
SELECT create_graph('test_enable_containment');
NOTICE: graph "test_enable_containment" has been created
create_graph
--------------

(1 row)

SELECT * FROM cypher('test_enable_containment',
$$
CREATE (x:Customer {
name: 'Bob',
school: {
name: 'XYZ College',
program: {
major: 'Psyc',
degree: 'BSc'
}
},
phone: [ 123456789, 987654321, 456987123 ],
addr: [
{city: 'Vancouver', street: 30},
{city: 'Toronto', street: 40}
]
})
RETURN x
$$) as (a agtype);
a
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
{"id": 844424930131969, "label": "Customer", "properties": {"addr": [{"city": "Vancouver", "street": 30}, {"city": "Toronto", "street": 40}], "name": "Bob", "phone": [123456789, 987654321, 456987123], "school": {"name": "XYZ College", "program": {"major": "Psyc", "degree": "BSc"}}}}::vertex
(1 row)

-- With enable_containment on
SET age.enable_containment = on;
SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {addr:[{city:'Toronto'}]}) RETURN x $$) as (a agtype);
count
-------
1
(1 row)

SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {addr:[{city:'Toronto'}, {city: 'Vancouver'}]}) RETURN x $$) as (a agtype);
count
-------
1
(1 row)

SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {addr:[{city:'Alberta'}]}) RETURN x $$) as (a agtype);
count
-------
0
(1 row)

SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {school:{program:{major:'Psyc'}}}) RETURN x $$) as (a agtype);
count
-------
1
(1 row)

SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {name:'Bob',school:{program:{degree:'BSc'}}}) RETURN x $$) as (a agtype);
count
-------
1
(1 row)

SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {school:{program:{major:'Cs'}}}) RETURN x $$) as (a agtype);
count
-------
0
(1 row)

SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {name:'Bob',school:{program:{degree:'PHd'}}}) RETURN x $$) as (a agtype);
count
-------
0
(1 row)

SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {phone:[987654321]}) RETURN x $$) as (a agtype);
count
-------
1
(1 row)

SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {phone:[654765876]}) RETURN x $$) as (a agtype);
count
-------
0
(1 row)

SELECT * FROM cypher('test_enable_containment', $$ EXPLAIN (COSTS OFF) MATCH (x:Customer {school:{name:'XYZ',program:{degree:'BSc'}},phone:[987654321],parents:{}}) RETURN x $$) as (a agtype);
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------
Seq Scan on "Customer" x
Filter: (properties @> '{"phone": [987654321], "school": {"name": "XYZ", "program": {"degree": "BSc"}}, "parents": {}}'::agtype)
(2 rows)

-- Previous set of queries, with enable_containment off
SET age.enable_containment = off;
SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {addr:[{city:'Toronto'}]}) RETURN x $$) as (a agtype);
count
-------
1
(1 row)

SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {addr:[{city:'Toronto'}, {city: 'Vancouver'}]}) RETURN x $$) as (a agtype);
count
-------
1
(1 row)

SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {addr:[{city:'Alberta'}]}) RETURN x $$) as (a agtype);
count
-------
0
(1 row)

SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {school:{program:{major:'Psyc'}}}) RETURN x $$) as (a agtype);
count
-------
1
(1 row)

SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {name:'Bob',school:{program:{degree:'BSc'}}}) RETURN x $$) as (a agtype);
count
-------
1
(1 row)

SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {school:{program:{major:'Cs'}}}) RETURN x $$) as (a agtype);
count
-------
0
(1 row)

SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {name:'Bob',school:{program:{degree:'PHd'}}}) RETURN x $$) as (a agtype);
count
-------
0
(1 row)

SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {phone:[987654321]}) RETURN x $$) as (a agtype);
count
-------
1
(1 row)

SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {phone:[654765876]}) RETURN x $$) as (a agtype);
count
-------
0
(1 row)

SELECT * FROM cypher('test_enable_containment', $$ EXPLAIN (COSTS OFF) MATCH (x:Customer {school:{name:'XYZ',program:{degree:'BSc'}},phone:[987654321],parents:{}}) RETURN x $$) as (a agtype);
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Seq Scan on "Customer" x
Filter: ((agtype_access_operator(VARIADIC ARRAY[properties, '"school"'::agtype, '"name"'::agtype]) = '"XYZ"'::agtype) AND (agtype_access_operator(VARIADIC ARRAY[properties, '"school"'::agtype, '"program"'::agtype, '"degree"'::agtype]) = '"BSc"'::agtype) AND (agtype_access_operator(VARIADIC ARRAY[properties, '"phone"'::agtype]) @> '[987654321]'::agtype) AND (agtype_access_operator(VARIADIC ARRAY[properties, '"parents"'::agtype]) @> '{}'::agtype))
(2 rows)

--
-- Clean up
--
Expand Down Expand Up @@ -2460,6 +2624,17 @@ NOTICE: graph "test_retrieve_var" has been dropped

(1 row)

SELECT drop_graph('test_enable_containment', true);
NOTICE: drop cascades to 3 other objects
DETAIL: drop cascades to table test_enable_containment._ag_label_vertex
drop cascades to table test_enable_containment._ag_label_edge
drop cascades to table test_enable_containment."Customer"
NOTICE: graph "test_enable_containment" has been dropped
drop_graph
------------

(1 row)

--
-- End
--
56 changes: 56 additions & 0 deletions regress/sql/cypher_match.sql
Original file line number Diff line number Diff line change
Expand Up @@ -1077,11 +1077,67 @@ SELECT * FROM cypher('cypher_match', $$ CREATE () WITH * MATCH (x{n0:x.n1}) RETU
SELECT * FROM cypher('cypher_match', $$ MATCH p=(x)-[]->(x:R) RETURN p, x $$) AS (p agtype, x agtype);
SELECT * FROM cypher('cypher_match', $$ MATCH p=(x:r)-[]->(x:R) RETURN p, x $$) AS (p agtype, x agtype);

--
-- Test age.enable_containment configuration parameter
--
-- Test queries are run before and after switching off this parameter.
-- When on, the containment operator should be used to filter properties.
-- When off, the access operator should be used.
--

SELECT create_graph('test_enable_containment');
SELECT * FROM cypher('test_enable_containment',
$$
CREATE (x:Customer {
name: 'Bob',
school: {
name: 'XYZ College',
program: {
major: 'Psyc',
degree: 'BSc'
}
},
phone: [ 123456789, 987654321, 456987123 ],
addr: [
{city: 'Vancouver', street: 30},
{city: 'Toronto', street: 40}
]
})
RETURN x
$$) as (a agtype);

-- With enable_containment on
SET age.enable_containment = on;
SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {addr:[{city:'Toronto'}]}) RETURN x $$) as (a agtype);
SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {addr:[{city:'Toronto'}, {city: 'Vancouver'}]}) RETURN x $$) as (a agtype);
SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {addr:[{city:'Alberta'}]}) RETURN x $$) as (a agtype);
SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {school:{program:{major:'Psyc'}}}) RETURN x $$) as (a agtype);
SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {name:'Bob',school:{program:{degree:'BSc'}}}) RETURN x $$) as (a agtype);
SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {school:{program:{major:'Cs'}}}) RETURN x $$) as (a agtype);
SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {name:'Bob',school:{program:{degree:'PHd'}}}) RETURN x $$) as (a agtype);
SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {phone:[987654321]}) RETURN x $$) as (a agtype);
SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {phone:[654765876]}) RETURN x $$) as (a agtype);
SELECT * FROM cypher('test_enable_containment', $$ EXPLAIN (COSTS OFF) MATCH (x:Customer {school:{name:'XYZ',program:{degree:'BSc'}},phone:[987654321],parents:{}}) RETURN x $$) as (a agtype);

-- Previous set of queries, with enable_containment off
SET age.enable_containment = off;
SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {addr:[{city:'Toronto'}]}) RETURN x $$) as (a agtype);
SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {addr:[{city:'Toronto'}, {city: 'Vancouver'}]}) RETURN x $$) as (a agtype);
SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {addr:[{city:'Alberta'}]}) RETURN x $$) as (a agtype);
SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {school:{program:{major:'Psyc'}}}) RETURN x $$) as (a agtype);
SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {name:'Bob',school:{program:{degree:'BSc'}}}) RETURN x $$) as (a agtype);
SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {school:{program:{major:'Cs'}}}) RETURN x $$) as (a agtype);
SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {name:'Bob',school:{program:{degree:'PHd'}}}) RETURN x $$) as (a agtype);
SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {phone:[987654321]}) RETURN x $$) as (a agtype);
SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH (x:Customer {phone:[654765876]}) RETURN x $$) as (a agtype);
SELECT * FROM cypher('test_enable_containment', $$ EXPLAIN (COSTS OFF) MATCH (x:Customer {school:{name:'XYZ',program:{degree:'BSc'}},phone:[987654321],parents:{}}) RETURN x $$) as (a agtype);

--
-- Clean up
--
SELECT drop_graph('cypher_match', true);
SELECT drop_graph('test_retrieve_var', true);
SELECT drop_graph('test_enable_containment', true);

--
-- End
Expand Down
2 changes: 2 additions & 0 deletions src/backend/age.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "nodes/ag_nodes.h"
#include "optimizer/cypher_paths.h"
#include "parser/cypher_analyze.h"
#include "utils/ag_guc.h"

PG_MODULE_MAGIC;

Expand All @@ -37,6 +38,7 @@ void _PG_init(void)
object_access_hook_init();
process_utility_hook_init();
post_parse_analyze_init();
define_config_params();
}

void _PG_fini(void);
Expand Down
Loading

0 comments on commit 3bef830

Please sign in to comment.