diff --git a/src/backend/optimizer/util/orclauses.c b/src/backend/optimizer/util/orclauses.c index 6ef9d14b902..cbb187229fe 100644 --- a/src/backend/optimizer/util/orclauses.c +++ b/src/backend/optimizer/util/orclauses.c @@ -22,6 +22,10 @@ #include "optimizer/optimizer.h" #include "optimizer/orclauses.h" #include "optimizer/restrictinfo.h" +#include "utils/lsyscache.h" +#include "parser/parse_expr.h" +#include "parser/parse_coerce.h" +#include "parser/parse_oper.h" static bool is_safe_restriction_clause_for(RestrictInfo *rinfo, RelOptInfo *rel); @@ -29,6 +33,307 @@ static Expr *extract_or_clause(RestrictInfo *or_rinfo, RelOptInfo *rel); static void consider_new_or_clause(PlannerInfo *root, RelOptInfo *rel, Expr *orclause, RestrictInfo *join_or_rinfo); +typedef struct OrClauseGroupEntry +{ + Node *node; + List *consts; + Oid collation; + Oid opno; + RestrictInfo *rinfo; +} OrClauseGroupEntry; + +int or_transform_limit = 500; + +/* + * Pass through baserestrictinfo clauses and try to convert OR clauses into IN + * Return a modified clause list or just the same baserestrictinfo, if no + * changes have made. + * XXX: do not change source list of clauses at all. + */ +static List * +transform_ors(PlannerInfo *root, List *baserestrictinfo) +{ + ListCell *lc; + ListCell *lc_cp; + List *modified_rinfo = NIL; + bool something_changed = false; + List *baserestrictinfo_origin = list_copy(baserestrictinfo); + + /* + * Complexity of a clause could be arbitrarily sophisticated. Here, we will + * look up only on the top level of clause list. + * XXX: It is substantiated? Could we change something here? + */ + forboth (lc, baserestrictinfo, lc_cp, baserestrictinfo_origin) + { + RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc); + RestrictInfo *rinfo_base = lfirst_node(RestrictInfo, lc_cp); + List *or_list = NIL; + ListCell *lc_eargs, + *lc_rargs, + *lc_args; + List *groups_list = NIL; + bool change_apply = false; + + if (!restriction_is_or_clause(rinfo) || + list_length(((BoolExpr *) rinfo->clause)->args) < or_transform_limit) + { + /* Add a clause without changes */ + modified_rinfo = lappend(modified_rinfo, rinfo); + continue; + } + + /* + * NOTE: + * It is an OR-clause. So, rinfo->orclause is a BoolExpr node, contains + * a list of sub-restrictinfo args, and rinfo->clause - which is the + * same expression, made from bare clauses. To not break selectivity + * caches and other optimizations, use both: + * - use rinfos from orclause if no transformation needed + * - use bare quals from rinfo->clause in the case of transformation, + * to create new RestrictInfo: in this case we have no options to avoid + * selectivity estimation procedure. + */ + forboth(lc_eargs, ((BoolExpr *) rinfo->clause)->args, + lc_rargs, ((BoolExpr *) rinfo->orclause)->args) + { + Expr *orqual = (Expr *) lfirst(lc_eargs); + RestrictInfo *sub_rinfo; + Node *const_expr; + Node *non_const_expr; + ListCell *lc_groups; + OrClauseGroupEntry *gentry; + + /* It may be one more boolean expression, skip it for now */ + if (!IsA(lfirst(lc_rargs), RestrictInfo)) + { + or_list = lappend(or_list, (void *) orqual); + continue; + } + + sub_rinfo = lfirst_node(RestrictInfo, lc_rargs); + + /* Check: it is an expr of the form 'F(x) oper ConstExpr' */ + if (!IsA(orqual, OpExpr) || + !(bms_is_empty(sub_rinfo->left_relids) ^ + bms_is_empty(sub_rinfo->right_relids)) || + contain_volatile_functions((Node *) orqual)) + { + /* Again, it's not the expr we can transform */ + or_list = lappend(or_list, (void *) orqual); + continue; + } + + /* + * Detect the constant side of the clause. Recall non-constant + * expression can be made not only with Vars, but also with Params, + * which is not bonded with any relation. Thus, we detect the const + * side - if another side is constant too, the orqual couldn't be + * an OpExpr. + * Get pointers to constant and expression sides of the qual. + */ + const_expr =bms_is_empty(sub_rinfo->left_relids) ? + get_leftop(sub_rinfo->clause) : + get_rightop(sub_rinfo->clause); + non_const_expr = bms_is_empty(sub_rinfo->left_relids) ? + get_rightop(sub_rinfo->clause) : + get_leftop(sub_rinfo->clause); + + if (!op_mergejoinable(((OpExpr *) sub_rinfo->clause)->opno, exprType(non_const_expr))) + { + /* And again, filter out non-equality operators */ + or_list = lappend(or_list, (void *) orqual); + continue; + } + + /* + * At this point we definitely have a transformable clause. + * Classify it and add into specific group of clauses, or create new + * group. + * TODO: to manage complexity in the case of many different clauses + * (X1=C1) OR (X2=C2 OR) ... (XN = CN) we could invent something + * like a hash table (htab key ???). + */ + foreach(lc_groups, groups_list) + { + OrClauseGroupEntry *v = (OrClauseGroupEntry *) lfirst(lc_groups); + + Assert(v->node != NULL); + + if (equal(v->node, non_const_expr)) + { + v->consts = lappend(v->consts, const_expr); + non_const_expr = NULL; + break; + } + } + + if (non_const_expr == NULL) + /* + * The clause classified successfully and added into existed + * clause group. + */ + continue; + + /* New clause group needed */ + gentry = palloc(sizeof(OrClauseGroupEntry)); + gentry->node = non_const_expr; + gentry->consts = list_make1(const_expr); + gentry->rinfo = sub_rinfo; + groups_list = lappend(groups_list, (void *) gentry); + } + + if (groups_list == NIL) + { + /* + * No any transformations possible with this rinfo, just add itself + * to the list and go further. + */ + modified_rinfo = lappend(modified_rinfo, rinfo); + continue; + } + + /* Let's convert each group of clauses to an IN operation. */ + + /* + * Go through the list of groups and convert each, where number of + * consts more than 1. trivial groups move to OR-list again + */ + + foreach(lc_args, groups_list) + { + OrClauseGroupEntry *gentry = (OrClauseGroupEntry *) lfirst(lc_args); + ScalarArrayOpExpr *saopexpr; + List *allexprs; + ArrayExpr *newa; + Oid scalar_type; + Oid array_type; + + Assert(list_length(gentry->consts) > 0); + + if (list_length(gentry->consts) == 1) + { + /* + * Only one element in the class. Return rinfo into the BoolExpr + * args list unchanged. + */ + list_free(gentry->consts); + or_list = lappend(or_list, (void *) gentry->rinfo->clause); + continue; + } + if (list_length(gentry->consts) == 1) + { + /* + * Only one element in the class. Return rinfo into the BoolExpr + * args list unchanged. + */ + list_free(gentry->consts); + or_list = lappend(or_list, gentry->rinfo->clause); + continue; + } + allexprs = list_concat(list_make1(gentry->node), gentry->consts); + scalar_type = select_common_type(NULL, allexprs, NULL, NULL); + + if (scalar_type != RECORDOID && OidIsValid(scalar_type)) + array_type = get_array_type(scalar_type); + else + array_type = InvalidOid; + if (array_type != InvalidOid && scalar_type != InvalidOid) + { + /* + * OK: coerce all the right-hand non-Var inputs to the common + * type and build an ArrayExpr for them. + */ + List *aexprs; + ListCell *l; + + aexprs = NIL; + + foreach(l, gentry->consts) + { + Node *rexpr = (Node *) lfirst(l); + + rexpr = coerce_to_common_type(NULL, rexpr, + scalar_type, + "IN"); + aexprs = lappend(aexprs, rexpr); + } + + newa = makeNode(ArrayExpr); + /* array_collid will be set by parse_collate.c */ + newa->element_typeid = scalar_type; + newa->array_typeid = array_type; + newa->multidims = false; + newa->elements = aexprs; + newa->location = -1; + + saopexpr = + (ScalarArrayOpExpr *) + make_scalar_array_op(NULL, + list_make1(makeString((char *) "=")), + true, + gentry->node, + (Node *) newa, + -1); + //saopexpr->inputcollid = exprInputCollation((Node *)gentry->rinfo->clause); + + or_list = lappend(or_list, (void *) saopexpr); + change_apply = true; + } + } + + if (!change_apply) + { + /* + * Each group contains only one element - use rinfo as is. + */ + modified_rinfo = lappend(modified_rinfo, rinfo); + list_free(or_list); + list_free_deep(groups_list); + continue; + } + + /* + * Make a new version of the restriction. Remember source restriction + * can be used in another path (SeqScan, for example). + */ + + /* One more trick: assemble correct clause */ + rinfo = make_restrictinfo(root, + list_length(or_list) > 1 ? make_orclause(or_list) : + (Expr *) linitial(or_list), + rinfo->is_pushed_down, + rinfo->has_clone, + rinfo->is_clone, + rinfo->pseudoconstant, + rinfo->security_level, + rinfo->required_relids, + rinfo->incompatible_relids, + rinfo->outer_relids); + rinfo->eval_cost=rinfo_base->eval_cost; + rinfo->norm_selec=rinfo_base->norm_selec; + rinfo->outer_selec=rinfo_base->outer_selec; + rinfo->left_bucketsize=rinfo_base->left_bucketsize; + rinfo->right_bucketsize=rinfo_base->right_bucketsize; + rinfo->left_mcvfreq=rinfo_base->left_mcvfreq; + rinfo->right_mcvfreq=rinfo_base->right_mcvfreq; + modified_rinfo = lappend(modified_rinfo, rinfo); + list_free_deep(groups_list); + something_changed = true; + } + + /* + * Check if transformation has made. If nothing changed - return + * baserestrictinfo as is. + */ + if (something_changed) + { + return modified_rinfo; + } + + list_free(modified_rinfo); + return baserestrictinfo; +} /* * extract_restriction_or_clauses @@ -93,6 +398,8 @@ extract_restriction_or_clauses(PlannerInfo *root) if (rel->reloptkind != RELOPT_BASEREL) continue; + rel->baserestrictinfo = transform_ors(root, rel->baserestrictinfo); + /* * Find potentially interesting OR joinclauses. We can use any * joinclause that is considered safe to move to this rel by the diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index 16ec6c5ef02..a4f04d84021 100644 --- a/src/backend/utils/misc/guc_tables.c +++ b/src/backend/utils/misc/guc_tables.c @@ -2052,6 +2052,16 @@ struct config_int ConfigureNamesInt[] = 100, 1, MAX_STATISTICS_TARGET, NULL, NULL, NULL }, + { + {"or_transform_limit", PGC_USERSET, QUERY_TUNING_OTHER, + gettext_noop("Transform a sequence of OR clauses to an IN expression."), + gettext_noop("The planner will replace clauses like 'x=c1 OR x=c2 .." + "to the clause 'x IN (c1,c2,...)'") + }, + &or_transform_limit, + 500, 0, INT_MAX, + NULL, NULL, NULL + }, { {"from_collapse_limit", PGC_USERSET, QUERY_TUNING_OTHER, gettext_noop("Sets the FROM-list size beyond which subqueries " diff --git a/src/include/parser/parse_expr.h b/src/include/parser/parse_expr.h index 7d38ca75f7b..891e6a462b9 100644 --- a/src/include/parser/parse_expr.h +++ b/src/include/parser/parse_expr.h @@ -17,6 +17,7 @@ /* GUC parameters */ extern PGDLLIMPORT bool Transform_null_equals; +extern PGDLLIMPORT int or_transform_limit; extern Node *transformExpr(ParseState *pstate, Node *expr, ParseExprKind exprKind); diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out index acfd9d1f4f7..a397137ecb6 100644 --- a/src/test/regress/expected/create_index.out +++ b/src/test/regress/expected/create_index.out @@ -1883,6 +1883,150 @@ SELECT count(*) FROM tenk1 10 (1 row) +SET or_transform_limit = 0; +EXPLAIN (COSTS OFF) +SELECT * FROM tenk1 + WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42); + QUERY PLAN +------------------------------------------------------------------------ + Index Scan using tenk1_thous_tenthous on tenk1 + Index Cond: ((thousand = 42) AND (tenthous = ANY (ARRAY[1, 3, 42]))) +(2 rows) + +SELECT * FROM tenk1 + WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42); + unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 +---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- + 42 | 5530 | 0 | 2 | 2 | 2 | 42 | 42 | 42 | 42 | 42 | 84 | 85 | QBAAAA | SEIAAA | OOOOxx +(1 row) + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM tenk1 + WHERE hundred = 42 AND (thousand = 42 OR thousand = 99); + QUERY PLAN +----------------------------------------------------------------------------- + Aggregate + -> Bitmap Heap Scan on tenk1 + Recheck Cond: ((hundred = 42) AND (thousand = ANY (ARRAY[42, 99]))) + -> BitmapAnd + -> Bitmap Index Scan on tenk1_hundred + Index Cond: (hundred = 42) + -> Bitmap Index Scan on tenk1_thous_tenthous + Index Cond: (thousand = ANY (ARRAY[42, 99])) +(8 rows) + +SELECT count(*) FROM tenk1 + WHERE hundred = 42 AND (thousand = 42 OR thousand = 99); + count +------- + 10 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM tenk1 + WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3) OR thousand = 41; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------- + Aggregate + -> Bitmap Heap Scan on tenk1 + Recheck Cond: ((((thousand = 42) AND (tenthous = 1)) OR ((thousand = 42) AND (tenthous = 3))) OR (thousand = 41)) + -> BitmapOr + -> BitmapOr + -> Bitmap Index Scan on tenk1_thous_tenthous + Index Cond: ((thousand = 42) AND (tenthous = 1)) + -> Bitmap Index Scan on tenk1_thous_tenthous + Index Cond: ((thousand = 42) AND (tenthous = 3)) + -> Bitmap Index Scan on tenk1_thous_tenthous + Index Cond: (thousand = 41) +(11 rows) + +SELECT count(*) FROM tenk1 + WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3) OR thousand = 41; + count +------- + 10 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM tenk1 + WHERE thousand = 42 OR tenthous = 1 AND thousand = 42 OR tenthous = 1; + QUERY PLAN +--------------------------------------------------------------------------------------------------- + Aggregate + -> Bitmap Heap Scan on tenk1 + Recheck Cond: ((thousand = 42) OR ((thousand = 42) AND (tenthous = 1)) OR (tenthous = 1)) + -> BitmapOr + -> Bitmap Index Scan on tenk1_thous_tenthous + Index Cond: (thousand = 42) + -> Bitmap Index Scan on tenk1_thous_tenthous + Index Cond: ((thousand = 42) AND (tenthous = 1)) + -> Bitmap Index Scan on tenk1_thous_tenthous + Index Cond: (tenthous = 1) +(10 rows) + +SELECT count(*) FROM tenk1 + WHERE thousand = 42 OR tenthous = 1 AND thousand = 42 OR tenthous = 1; + count +------- + 11 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM tenk1 + WHERE hundred = 42 AND (thousand = 42 OR thousand = 99 OR tenthous < 2) OR thousand = 41; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------ + Aggregate + -> Bitmap Heap Scan on tenk1 + Recheck Cond: (((hundred = 42) AND ((thousand = 42) OR (thousand = 99) OR (tenthous < 2))) OR (thousand = 41)) + -> BitmapOr + -> BitmapAnd + -> Bitmap Index Scan on tenk1_hundred + Index Cond: (hundred = 42) + -> BitmapOr + -> Bitmap Index Scan on tenk1_thous_tenthous + Index Cond: (thousand = 42) + -> Bitmap Index Scan on tenk1_thous_tenthous + Index Cond: (thousand = 99) + -> Bitmap Index Scan on tenk1_thous_tenthous + Index Cond: (tenthous < 2) + -> Bitmap Index Scan on tenk1_thous_tenthous + Index Cond: (thousand = 41) +(16 rows) + +SELECT count(*) FROM tenk1 + WHERE hundred = 42 AND (thousand = 42 OR thousand = 99 OR tenthous < 2) OR thousand = 41; + count +------- + 20 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM tenk1 + WHERE hundred = 42 AND (thousand = 42 OR thousand = 41 OR thousand = 99 AND tenthous = 2); + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------- + Aggregate + -> Bitmap Heap Scan on tenk1 + Recheck Cond: ((hundred = 42) AND (((thousand = 99) AND (tenthous = 2)) OR (thousand = ANY (ARRAY[42, 41])))) + -> BitmapAnd + -> Bitmap Index Scan on tenk1_hundred + Index Cond: (hundred = 42) + -> BitmapOr + -> Bitmap Index Scan on tenk1_thous_tenthous + Index Cond: ((thousand = 99) AND (tenthous = 2)) + -> Bitmap Index Scan on tenk1_thous_tenthous + Index Cond: (thousand = ANY (ARRAY[42, 41])) +(11 rows) + +SELECT count(*) FROM tenk1 + WHERE hundred = 42 AND (thousand = 42 OR thousand = 41 OR thousand = 99 AND tenthous = 2); + count +------- + 10 +(1 row) + +RESET or_transform_limit; -- -- Check behavior with duplicate index column contents -- diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out index 127c9532976..c052b113eea 100644 --- a/src/test/regress/expected/guc.out +++ b/src/test/regress/expected/guc.out @@ -861,7 +861,8 @@ SELECT name FROM tab_settings_flags name --------------------------- default_statistics_target -(1 row) + or_transform_limit +(2 rows) -- Runtime-computed GUCs should be part of the preset category. SELECT name FROM tab_settings_flags diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index 9b8638f286a..f22e4524099 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -4207,6 +4207,60 @@ select * from tenk1 a join tenk1 b on Index Cond: (unique2 = 7) (19 rows) +SET or_transform_limit = 0; +explain (costs off) +select * from tenk1 a join tenk1 b on + (a.unique1 = 1 and b.unique1 = 2) or + ((a.unique2 = 3 or a.unique2 = 7) and b.hundred = 4); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------- + Nested Loop + Join Filter: (((a.unique1 = 1) AND (b.unique1 = 2)) OR (((a.unique2 = 3) OR (a.unique2 = 7)) AND (b.hundred = 4))) + -> Bitmap Heap Scan on tenk1 b + Recheck Cond: ((unique1 = 2) OR (hundred = 4)) + -> BitmapOr + -> Bitmap Index Scan on tenk1_unique1 + Index Cond: (unique1 = 2) + -> Bitmap Index Scan on tenk1_hundred + Index Cond: (hundred = 4) + -> Materialize + -> Bitmap Heap Scan on tenk1 a + Recheck Cond: ((unique1 = 1) OR (unique2 = 3) OR (unique2 = 7)) + -> BitmapOr + -> Bitmap Index Scan on tenk1_unique1 + Index Cond: (unique1 = 1) + -> Bitmap Index Scan on tenk1_unique2 + Index Cond: (unique2 = 3) + -> Bitmap Index Scan on tenk1_unique2 + Index Cond: (unique2 = 7) +(19 rows) + +explain (costs off) +select * from tenk1 a join tenk1 b on + (a.unique1 < 20 or a.unique1 = 3 or a.unique1 = 1 and b.unique1 = 2) or + ((a.unique2 = 3 or a.unique2 = 7) and b.hundred = 4); + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------- + Nested Loop + Join Filter: ((a.unique1 < 20) OR (a.unique1 = 3) OR ((a.unique1 = 1) AND (b.unique1 = 2)) OR (((a.unique2 = 3) OR (a.unique2 = 7)) AND (b.hundred = 4))) + -> Seq Scan on tenk1 b + -> Materialize + -> Bitmap Heap Scan on tenk1 a + Recheck Cond: ((unique1 < 20) OR (unique1 = 3) OR (unique1 = 1) OR (unique2 = 3) OR (unique2 = 7)) + -> BitmapOr + -> Bitmap Index Scan on tenk1_unique1 + Index Cond: (unique1 < 20) + -> Bitmap Index Scan on tenk1_unique1 + Index Cond: (unique1 = 3) + -> Bitmap Index Scan on tenk1_unique1 + Index Cond: (unique1 = 1) + -> Bitmap Index Scan on tenk1_unique2 + Index Cond: (unique2 = 3) + -> Bitmap Index Scan on tenk1_unique2 + Index Cond: (unique2 = 7) +(17 rows) + +RESET or_transform_limit; -- -- test placement of movable quals in a parameterized join tree -- diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index bb1223e2b13..ad4c4dae81e 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -101,6 +101,42 @@ explain (costs off) select * from lp where a is not null and (a = 'a' or a = 'c' Filter: ((a IS NOT NULL) AND ((a = 'a'::bpchar) OR (a = 'c'::bpchar))) (5 rows) +SET or_transform_limit = 0; +explain (costs off) select * from lp where a = 'a' or a = 'c'; + QUERY PLAN +----------------------------------------------- + Append + -> Seq Scan on lp_ad lp_1 + Filter: (a = ANY ('{a,c}'::bpchar[])) + -> Seq Scan on lp_bc lp_2 + Filter: (a = ANY ('{a,c}'::bpchar[])) + -> Seq Scan on lp_ef lp_3 + Filter: (a = ANY ('{a,c}'::bpchar[])) + -> Seq Scan on lp_g lp_4 + Filter: (a = ANY ('{a,c}'::bpchar[])) + -> Seq Scan on lp_null lp_5 + Filter: (a = ANY ('{a,c}'::bpchar[])) + -> Seq Scan on lp_default lp_6 + Filter: (a = ANY ('{a,c}'::bpchar[])) +(13 rows) + +explain (costs off) select * from lp where a is not null and (a = 'a' or a = 'c'); + QUERY PLAN +--------------------------------------------------------------------- + Append + -> Seq Scan on lp_ad lp_1 + Filter: ((a IS NOT NULL) AND (a = ANY ('{a,c}'::bpchar[]))) + -> Seq Scan on lp_bc lp_2 + Filter: ((a IS NOT NULL) AND (a = ANY ('{a,c}'::bpchar[]))) + -> Seq Scan on lp_ef lp_3 + Filter: ((a IS NOT NULL) AND (a = ANY ('{a,c}'::bpchar[]))) + -> Seq Scan on lp_g lp_4 + Filter: ((a IS NOT NULL) AND (a = ANY ('{a,c}'::bpchar[]))) + -> Seq Scan on lp_default lp_5 + Filter: ((a IS NOT NULL) AND (a = ANY ('{a,c}'::bpchar[]))) +(11 rows) + +RESET or_transform_limit; explain (costs off) select * from lp where a <> 'g'; QUERY PLAN ------------------------------------ @@ -671,6 +707,166 @@ explain (costs off) select * from rlp where (a = 1 and a = 3) or (a > 1 and a = Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15))) (11 rows) +SET or_transform_limit = 0; +explain (costs off) select * from rlp where a = 1 or a = 7; + QUERY PLAN +------------------------------------------------ + Append + Subplans Removed: 1 + -> Seq Scan on rlp2 rlp_1 + Filter: (a = ANY ('{1,7}'::integer[])) +(4 rows) + +explain (costs off) select * from rlp where a = 1 or b = 'ab'; + QUERY PLAN +------------------------------------------------------- + Append + -> Seq Scan on rlp1 rlp_1 + Filter: ((a = 1) OR ((b)::text = 'ab'::text)) + -> Seq Scan on rlp2 rlp_2 + Filter: ((a = 1) OR ((b)::text = 'ab'::text)) + -> Seq Scan on rlp3abcd rlp_3 + Filter: ((a = 1) OR ((b)::text = 'ab'::text)) + -> Seq Scan on rlp4_1 rlp_4 + Filter: ((a = 1) OR ((b)::text = 'ab'::text)) + -> Seq Scan on rlp4_2 rlp_5 + Filter: ((a = 1) OR ((b)::text = 'ab'::text)) + -> Seq Scan on rlp4_default rlp_6 + Filter: ((a = 1) OR ((b)::text = 'ab'::text)) + -> Seq Scan on rlp5_1 rlp_7 + Filter: ((a = 1) OR ((b)::text = 'ab'::text)) + -> Seq Scan on rlp5_default rlp_8 + Filter: ((a = 1) OR ((b)::text = 'ab'::text)) + -> Seq Scan on rlp_default_10 rlp_9 + Filter: ((a = 1) OR ((b)::text = 'ab'::text)) + -> Seq Scan on rlp_default_30 rlp_10 + Filter: ((a = 1) OR ((b)::text = 'ab'::text)) + -> Seq Scan on rlp_default_null rlp_11 + Filter: ((a = 1) OR ((b)::text = 'ab'::text)) + -> Seq Scan on rlp_default_default rlp_12 + Filter: ((a = 1) OR ((b)::text = 'ab'::text)) +(25 rows) + +explain (costs off) select * from rlp where a > 20 and a < 27; + QUERY PLAN +----------------------------------------- + Append + -> Seq Scan on rlp4_1 rlp_1 + Filter: ((a > 20) AND (a < 27)) + -> Seq Scan on rlp4_2 rlp_2 + Filter: ((a > 20) AND (a < 27)) +(5 rows) + +explain (costs off) select * from rlp where a = 29; + QUERY PLAN +------------------------------ + Seq Scan on rlp4_default rlp + Filter: (a = 29) +(2 rows) + +explain (costs off) select * from rlp where a >= 29; + QUERY PLAN +--------------------------------------------- + Append + -> Seq Scan on rlp4_default rlp_1 + Filter: (a >= 29) + -> Seq Scan on rlp5_1 rlp_2 + Filter: (a >= 29) + -> Seq Scan on rlp5_default rlp_3 + Filter: (a >= 29) + -> Seq Scan on rlp_default_30 rlp_4 + Filter: (a >= 29) + -> Seq Scan on rlp_default_default rlp_5 + Filter: (a >= 29) +(11 rows) + +explain (costs off) select * from rlp where a < 1 or (a > 20 and a < 25); + QUERY PLAN +------------------------------------------------------ + Append + -> Seq Scan on rlp1 rlp_1 + Filter: ((a < 1) OR ((a > 20) AND (a < 25))) + -> Seq Scan on rlp4_1 rlp_2 + Filter: ((a < 1) OR ((a > 20) AND (a < 25))) +(5 rows) + +explain (costs off) select * from rlp where a = 20 or a = 40; + QUERY PLAN +-------------------------------------------------- + Append + Subplans Removed: 2 + -> Seq Scan on rlp4_1 rlp_1 + Filter: (a = ANY ('{20,40}'::integer[])) + -> Seq Scan on rlp5_default rlp_2 + Filter: (a = ANY ('{20,40}'::integer[])) +(6 rows) + +explain (costs off) select * from rlp3 where a = 20; /* empty */ + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +explain (costs off) select * from rlp where a > 1 and a = 10; /* only default */ + QUERY PLAN +---------------------------------- + Seq Scan on rlp_default_10 rlp + Filter: ((a > 1) AND (a = 10)) +(2 rows) + +explain (costs off) select * from rlp where a > 1 and a >=15; /* rlp3 onwards, including default */ + QUERY PLAN +---------------------------------------------- + Append + -> Seq Scan on rlp3abcd rlp_1 + Filter: ((a > 1) AND (a >= 15)) + -> Seq Scan on rlp3efgh rlp_2 + Filter: ((a > 1) AND (a >= 15)) + -> Seq Scan on rlp3nullxy rlp_3 + Filter: ((a > 1) AND (a >= 15)) + -> Seq Scan on rlp3_default rlp_4 + Filter: ((a > 1) AND (a >= 15)) + -> Seq Scan on rlp4_1 rlp_5 + Filter: ((a > 1) AND (a >= 15)) + -> Seq Scan on rlp4_2 rlp_6 + Filter: ((a > 1) AND (a >= 15)) + -> Seq Scan on rlp4_default rlp_7 + Filter: ((a > 1) AND (a >= 15)) + -> Seq Scan on rlp5_1 rlp_8 + Filter: ((a > 1) AND (a >= 15)) + -> Seq Scan on rlp5_default rlp_9 + Filter: ((a > 1) AND (a >= 15)) + -> Seq Scan on rlp_default_30 rlp_10 + Filter: ((a > 1) AND (a >= 15)) + -> Seq Scan on rlp_default_default rlp_11 + Filter: ((a > 1) AND (a >= 15)) +(23 rows) + +explain (costs off) select * from rlp where a = 1 and a = 3; /* empty */ + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +explain (costs off) select * from rlp where (a = 1 and a = 3) or (a > 1 and a = 15); + QUERY PLAN +------------------------------------------------------------------- + Append + -> Seq Scan on rlp2 rlp_1 + Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15))) + -> Seq Scan on rlp3abcd rlp_2 + Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15))) + -> Seq Scan on rlp3efgh rlp_3 + Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15))) + -> Seq Scan on rlp3nullxy rlp_4 + Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15))) + -> Seq Scan on rlp3_default rlp_5 + Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15))) +(11 rows) + +RESET or_transform_limit; -- multi-column keys create table mc3p (a int, b int, c int) partition by range (a, abs(b), c); create table mc3p_default partition of mc3p default; diff --git a/src/test/regress/expected/tidscan.out b/src/test/regress/expected/tidscan.out index f133b5a4ac7..e462212aa54 100644 --- a/src/test/regress/expected/tidscan.out +++ b/src/test/regress/expected/tidscan.out @@ -56,6 +56,23 @@ SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid; (0,2) | 2 (2 rows) +SET or_transform_limit = 0; +EXPLAIN (COSTS OFF) +SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid; + QUERY PLAN +-------------------------------------------------------------- + Tid Scan on tidscan + TID Cond: (ctid = ANY (ARRAY['(0,2)'::tid, '(0,1)'::tid])) +(2 rows) + +SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid; + ctid | id +-------+---- + (0,1) | 1 + (0,2) | 2 +(2 rows) + +RESET or_transform_limit; -- ctid = ScalarArrayOp - implemented as tidscan EXPLAIN (COSTS OFF) SELECT ctid, * FROM tidscan WHERE ctid = ANY(ARRAY['(0,1)', '(0,2)']::tid[]); diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/create_index.sql index d49ce9f3007..514e4f0da48 100644 --- a/src/test/regress/sql/create_index.sql +++ b/src/test/regress/sql/create_index.sql @@ -737,6 +737,44 @@ SELECT count(*) FROM tenk1 SELECT count(*) FROM tenk1 WHERE hundred = 42 AND (thousand = 42 OR thousand = 99); +SET or_transform_limit = 0; +EXPLAIN (COSTS OFF) +SELECT * FROM tenk1 + WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42); +SELECT * FROM tenk1 + WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42); + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM tenk1 + WHERE hundred = 42 AND (thousand = 42 OR thousand = 99); +SELECT count(*) FROM tenk1 + WHERE hundred = 42 AND (thousand = 42 OR thousand = 99); + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM tenk1 + WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3) OR thousand = 41; +SELECT count(*) FROM tenk1 + WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3) OR thousand = 41; + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM tenk1 + WHERE thousand = 42 OR tenthous = 1 AND thousand = 42 OR tenthous = 1; +SELECT count(*) FROM tenk1 + WHERE thousand = 42 OR tenthous = 1 AND thousand = 42 OR tenthous = 1; + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM tenk1 + WHERE hundred = 42 AND (thousand = 42 OR thousand = 99 OR tenthous < 2) OR thousand = 41; +SELECT count(*) FROM tenk1 + WHERE hundred = 42 AND (thousand = 42 OR thousand = 99 OR tenthous < 2) OR thousand = 41; + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM tenk1 + WHERE hundred = 42 AND (thousand = 42 OR thousand = 41 OR thousand = 99 AND tenthous = 2); +SELECT count(*) FROM tenk1 + WHERE hundred = 42 AND (thousand = 42 OR thousand = 41 OR thousand = 99 AND tenthous = 2); +RESET or_transform_limit; + -- -- Check behavior with duplicate index column contents -- diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index 3e5032b04dd..272ff7c5d90 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -1397,6 +1397,17 @@ select * from tenk1 a join tenk1 b on (a.unique1 = 1 and b.unique1 = 2) or ((a.unique2 = 3 or a.unique2 = 7) and b.hundred = 4); +SET or_transform_limit = 0; +explain (costs off) +select * from tenk1 a join tenk1 b on + (a.unique1 = 1 and b.unique1 = 2) or + ((a.unique2 = 3 or a.unique2 = 7) and b.hundred = 4); +explain (costs off) +select * from tenk1 a join tenk1 b on + (a.unique1 < 20 or a.unique1 = 3 or a.unique1 = 1 and b.unique1 = 2) or + ((a.unique2 = 3 or a.unique2 = 7) and b.hundred = 4); +RESET or_transform_limit; + -- -- test placement of movable quals in a parameterized join tree -- diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql index 83fed54b8c6..068eed3499c 100644 --- a/src/test/regress/sql/partition_prune.sql +++ b/src/test/regress/sql/partition_prune.sql @@ -21,6 +21,12 @@ explain (costs off) select * from lp where a is not null; explain (costs off) select * from lp where a is null; explain (costs off) select * from lp where a = 'a' or a = 'c'; explain (costs off) select * from lp where a is not null and (a = 'a' or a = 'c'); + +SET or_transform_limit = 0; +explain (costs off) select * from lp where a = 'a' or a = 'c'; +explain (costs off) select * from lp where a is not null and (a = 'a' or a = 'c'); +RESET or_transform_limit; + explain (costs off) select * from lp where a <> 'g'; explain (costs off) select * from lp where a <> 'a' and a <> 'd'; explain (costs off) select * from lp where a not in ('a', 'd'); @@ -99,6 +105,21 @@ explain (costs off) select * from rlp where a > 1 and a >=15; /* rlp3 onwards, i explain (costs off) select * from rlp where a = 1 and a = 3; /* empty */ explain (costs off) select * from rlp where (a = 1 and a = 3) or (a > 1 and a = 15); +SET or_transform_limit = 0; +explain (costs off) select * from rlp where a = 1 or a = 7; +explain (costs off) select * from rlp where a = 1 or b = 'ab'; +explain (costs off) select * from rlp where a > 20 and a < 27; +explain (costs off) select * from rlp where a = 29; +explain (costs off) select * from rlp where a >= 29; +explain (costs off) select * from rlp where a < 1 or (a > 20 and a < 25); +explain (costs off) select * from rlp where a = 20 or a = 40; +explain (costs off) select * from rlp3 where a = 20; /* empty */ +explain (costs off) select * from rlp where a > 1 and a = 10; /* only default */ +explain (costs off) select * from rlp where a > 1 and a >=15; /* rlp3 onwards, including default */ +explain (costs off) select * from rlp where a = 1 and a = 3; /* empty */ +explain (costs off) select * from rlp where (a = 1 and a = 3) or (a > 1 and a = 15); +RESET or_transform_limit; + -- multi-column keys create table mc3p (a int, b int, c int) partition by range (a, abs(b), c); create table mc3p_default partition of mc3p default; diff --git a/src/test/regress/sql/tidscan.sql b/src/test/regress/sql/tidscan.sql index 313e0fb9b67..634bf08e5fc 100644 --- a/src/test/regress/sql/tidscan.sql +++ b/src/test/regress/sql/tidscan.sql @@ -22,6 +22,12 @@ EXPLAIN (COSTS OFF) SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid; SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid; +SET or_transform_limit = 0; +EXPLAIN (COSTS OFF) +SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid; +SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid; +RESET or_transform_limit; + -- ctid = ScalarArrayOp - implemented as tidscan EXPLAIN (COSTS OFF) SELECT ctid, * FROM tidscan WHERE ctid = ANY(ARRAY['(0,1)', '(0,2)']::tid[]); diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8de90c49585..b40b58124b4 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1635,6 +1635,7 @@ NumericVar OM_uint32 OP OSAPerGroupState +OrClauseGroupEntry OSAPerQueryState OSInfo OSSLCipher