diff --git a/Makefile b/Makefile index fbd3f3b8f..deb5ea2ba 100644 --- a/Makefile +++ b/Makefile @@ -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 @@ -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 diff --git a/regress/age_regression.conf b/regress/age_regression.conf new file mode 100644 index 000000000..96f5b0892 --- /dev/null +++ b/regress/age_regression.conf @@ -0,0 +1 @@ +#age.enable_containment = on diff --git a/regress/expected/cypher_match.out b/regress/expected/cypher_match.out index ae8905c92..42a991521 100644 --- a/regress/expected/cypher_match.out +++ b/regress/expected/cypher_match.out @@ -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 -- @@ -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 -- diff --git a/regress/sql/cypher_match.sql b/regress/sql/cypher_match.sql index 739558d1f..e4db18ac1 100644 --- a/regress/sql/cypher_match.sql +++ b/regress/sql/cypher_match.sql @@ -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 diff --git a/src/backend/age.c b/src/backend/age.c index e342146df..a512f1888 100644 --- a/src/backend/age.c +++ b/src/backend/age.c @@ -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; @@ -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); diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 1f7e48aa3..8467cbbab 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -65,6 +65,7 @@ #include "utils/ag_func.h" #include "utils/agtype.h" #include "utils/graphid.h" +#include "utils/ag_guc.h" /* * Variable string names for makeTargetEntry. As they are going to be variable @@ -172,6 +173,12 @@ static List *make_edge_quals(cypher_parsestate *cpstate, enum transform_entity_join_side side); static A_Expr *filter_vertices_on_label_id(cypher_parsestate *cpstate, Node *id_field, char *label); +static Node *transform_map_to_ind(cypher_parsestate *cpstate, + transform_entity *entity, cypher_map *map); +static List *transform_map_to_ind_recursive(cypher_parsestate *cpstate, + transform_entity *entity, + cypher_map *map, + List *parent_fields); static Node *create_property_constraints(cypher_parsestate *cpstate, transform_entity *entity, Node *property_constraints, @@ -3558,9 +3565,156 @@ static A_Expr *filter_vertices_on_label_id(cypher_parsestate *cpstate, } /* - * Creates the Contains operator to process property constraints for a vertex/ - * edge in a MATCH clause. Creates the agtype @> with the entity's properties - * on the right and the constraints in the MATCH clause on the left. + * Makes property constraint using indirection(s). This is an + * alternative to using the containment operator (@>). + * + * In case of array and empty map, containment is used instead of equality. + * + * For example, the following query + * + * MATCH (x:Label{ + * name: 'xyz', + * address: { + * city: 'abc', + * street: { + * name: 'pqr', + * number: 123 + * } + * }, + * phone: [9, 8, 7], + * parents: {} + * }) + * + * is transformed to- + * + * x.name = 'xyz' AND + * x.address.city = 'abc' AND + * x.address.street.name = 'pqr' AND + * x.address.street.number = 123 AND + * x.phone @> [6, 4, 3] AND + * x.parents @> {} + */ +static Node *transform_map_to_ind(cypher_parsestate *cpstate, + transform_entity *entity, cypher_map *map) +{ + List *quals; // list of equality and/or containment qual node + + quals = transform_map_to_ind_recursive(cpstate, entity, map, NIL); + Assert(quals != NIL); + + if (list_length(quals) > 1) + { + return (Node *)makeBoolExpr(AND_EXPR, quals, -1); + } + else + { + return (Node *)linitial(quals); + } +} + +/* + * Helper function of `transform_map_to_ind`. + * + * This function is called when a value of the `map` is a non-empty map. + * For example, the key `address.street` has a non-empty map. The + * `parent_fields` parameter will be set to the list of parents of the + * key `street` in order. In this case, only `address`. If no parent + * fields, set it to NIL. + */ +static List *transform_map_to_ind_recursive(cypher_parsestate *cpstate, + transform_entity *entity, + cypher_map *map, + List *parent_fields) +{ + int i; + ParseState *pstate; + Node *last_srf; + List *quals; + + pstate = (ParseState *)cpstate; + last_srf = pstate->p_last_srf; + quals = NIL; + + /* since this function recurses, it could be driven to stack overflow */ + check_stack_depth(); + + Assert(list_length(map->keyvals) != 0); + + for (i = 0; i < map->keyvals->length; i += 2) + { + Node *key; + Node *val; + char *keystr; + + key = (Node *)map->keyvals->elements[i].ptr_value; + val = (Node *)map->keyvals->elements[i + 1].ptr_value; + Assert(IsA(key, String)); + keystr = ((String *)key)->sval; + + if (is_ag_node(val, cypher_map) && + list_length(((cypher_map *)val)->keyvals) != 0) + { + List *new_parent_fields; + List *recursive_quals; + + new_parent_fields = lappend(list_copy(parent_fields), + makeString(keystr)); + + recursive_quals = transform_map_to_ind_recursive( + cpstate, entity, (cypher_map *)val, new_parent_fields); + + quals = list_concat(quals, recursive_quals); + + list_free(new_parent_fields); + list_free(recursive_quals); + } + else + { + Node *qual; + Node *lhs; + Node *rhs; + List *op; + A_Indirection *indir; + ColumnRef *variable; + + /* + * Lists and empty maps are transformed to containment. If a map + * makes it here, then it must be empty. Because non-empty maps + * are processed in the upper if-block. + */ + if (is_ag_node(val, cypher_list) || is_ag_node(val, cypher_map)) + { + op = list_make1(makeString("@>")); + } + else + { + op = list_make1(makeString("=")); + } + + variable = makeNode(ColumnRef); + variable->fields = + list_make1(makeString(entity->entity.node->name)); + variable->location = -1; + + indir = makeNode(A_Indirection); + indir->arg = (Node *)variable; + indir->indirection = lappend(list_copy(parent_fields), + makeString(keystr)); + + lhs = transform_cypher_expr(cpstate, (Node *)indir, + EXPR_KIND_WHERE); + rhs = transform_cypher_expr(cpstate, val, EXPR_KIND_WHERE); + + qual = (Node *)make_op(pstate, op, lhs, rhs, last_srf, -1); + quals = lappend(quals, qual); + } + } + + return quals; +} + +/* + * Creates the property constraints for a vertex/edge in a MATCH clause. */ static Node *create_property_constraints(cypher_parsestate *cpstate, transform_entity *entity, @@ -3603,11 +3757,18 @@ static Node *create_property_constraints(cypher_parsestate *cpstate, const_expr = transform_cypher_expr(cpstate, property_constraints, EXPR_KIND_WHERE); - return (Node *)make_op(pstate, list_make1(makeString("@>")), prop_expr, - const_expr, last_srf, -1); + if (age_enable_containment) + { + return (Node *)make_op(pstate, list_make1(makeString("@>")), prop_expr, + const_expr, last_srf, -1); + } + else + { + return (Node *)transform_map_to_ind( + cpstate, entity, (cypher_map *)property_constraints); + } } - /* * For the given path, transform each entity within the path, create * the path variable if needed, and construct the quals to enforce the diff --git a/src/backend/parser/cypher_gram.y b/src/backend/parser/cypher_gram.y index ff0678649..10807441f 100644 --- a/src/backend/parser/cypher_gram.y +++ b/src/backend/parser/cypher_gram.y @@ -33,6 +33,7 @@ #include "parser/ag_scanner.h" #include "parser/cypher_gram.h" #include "parser/cypher_parse_node.h" +#include "parser/scansup.h" // override the default action for locations #define YYLLOC_DEFAULT(current, rhs, n) \ @@ -184,6 +185,11 @@ /*set operations*/ %type all_or_distinct +/* utility options */ +%type utility_option_list +%type utility_option_elem utility_option_arg +%type utility_option_name + %{ // // internal alias check @@ -321,6 +327,20 @@ stmt: makeDefElem("verbose", NULL, @3));; extra->extra = (Node *)estmt; } + | EXPLAIN '(' utility_option_list ')' cypher_stmt semicolon_opt + { + ExplainStmt *estmt = NULL; + + if (yychar != YYEOF) + yyerror(&yylloc, scanner, extra, "syntax error"); + + extra->result = $5; + + estmt = makeNode(ExplainStmt); + estmt->query = NULL; + estmt->options = $3; + extra->extra = (Node *)estmt; + } ; cypher_stmt: @@ -1092,6 +1112,66 @@ where_opt: } ; +utility_option_list: + utility_option_elem + { + $$ = list_make1($1); + } + | utility_option_list ',' utility_option_elem + { + $$ = lappend($1, $3); + } + ; + +utility_option_elem: + utility_option_name utility_option_arg + { + $$ = (Node *)makeDefElem($1, $2, @1); + } + ; + +utility_option_name: + IDENTIFIER + { + char *modified_name = downcase_truncate_identifier($1, strlen($1), + true); + $$ = modified_name; + } + | safe_keywords + { + char *name = pstrdup($1); + char *modified_name = downcase_truncate_identifier(name, + strlen(name), + true); + $$ = modified_name; + } + ; + +utility_option_arg: + IDENTIFIER + { + char *modified_val = downcase_truncate_identifier($1, strlen($1), + true); + $$ = (Node *)makeString(modified_val); + } + | INTEGER + { + $$ = (Node *)makeInteger($1); + } + | TRUE_P + { + $$ = (Node *)makeString("true"); + } + | FALSE_P + { + $$ = (Node *)makeString("false"); + } + | /* EMPTY */ + { + $$ = NULL; + } + ; + /* * pattern */ diff --git a/src/backend/utils/ag_guc.c b/src/backend/utils/ag_guc.c new file mode 100644 index 000000000..efbc61678 --- /dev/null +++ b/src/backend/utils/ag_guc.c @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "postgres.h" +#include "utils/guc.h" +#include "utils/ag_guc.h" + +bool age_enable_containment = true; + +/* + * Defines AGE's custom configuration parameters. + * + * The name of the parameter must be `age.*`. This name is used for setting + * value to the parameter. For example, `SET age.enable_containment = on;`. + */ +void define_config_params(void) +{ + DefineCustomBoolVariable("age.enable_containment", + "Use @> operator to transform MATCH's filter. Otherwise, use -> operator.", + NULL, + &age_enable_containment, + true, + PGC_SUSET, + 0, + NULL, + NULL, + NULL); +} diff --git a/src/include/utils/ag_guc.h b/src/include/utils/ag_guc.h new file mode 100644 index 000000000..52fab2b85 --- /dev/null +++ b/src/include/utils/ag_guc.h @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef AG_GUC_H +#define AG_GUC_H + +/* + * AGE configuration parameters. + * + * Ideally, these parameters should be documented in a .sgml file. + * + * To add a new parameter, add a global variable. Add its definition + * in the `define_config_params` function. Include this header file + * to use the global variable. The parameters can be set just like + * regular Postgres parameters. See guc.h for more details. + */ + +/* + * If set true, MATCH's property filter is transformed into the @> + * (containment) operator. Otherwise, the -> operator is used. The former case + * is useful when GIN index is desirable, the latter case is useful for Btree + * expression index. + */ +extern bool age_enable_containment; + +void define_config_params(void); + +#endif