From bed874e573dc82ba6d3e81fb6176dbb52b6190d3 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Mon, 11 Sep 2023 08:27:55 +0900 Subject: [PATCH] Fix crash with IndexScan hints and ordering properties of index AMs IndexScan hints on indexes that do not support ordering, like GiST or GIN would lead to crashes. The cause is a useless access to nonexistent arrays for ordering attributes in IndexOptInfo, as these indexes don't use that. Index AMs that exist out of PostgreSQL core could equally trigger this failure. Since we already confirmed that the attributes and the access methods are the same as the parent index, the two are assumed to have the same ordering properties, hence there should be no need to bother checking the ordering properties of the parent. This is a backpatch of the following commits, down to all supported versions: - 23cabaf902b0, to fix a crash with indexes that do not support ordered operations, like GIN or GiST. - b48c7ae99912, to disable COSTS in the test added in the first commit. - 297defe87653, as an extra cleanup patch for the test. Thanks to Yusuke Egashira for pointing out that this fix was not backpatched properly. Author: Masahiro Ikeda Backpatch-through: 11 --- expected/pg_hint_plan.out | 26 ++++++++++++++++++++++++++ pg_hint_plan.c | 23 ++++++++++++++++++----- sql/pg_hint_plan.sql | 9 +++++++++ 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/expected/pg_hint_plan.out b/expected/pg_hint_plan.out index b282bb97..6d9eae85 100644 --- a/expected/pg_hint_plan.out +++ b/expected/pg_hint_plan.out @@ -4418,6 +4418,32 @@ error hint: Index Cond: (id < 10) (7 rows) +-- IndexScan is safe for unordered indexes +CREATE TABLE ischk (a text, b tsvector) PARTITION BY LIST(a); +CREATE TABLE ischk_d1 PARTITION OF ischk FOR VALUES IN (0); +CREATE TABLE ischk_d2 PARTITION OF ischk FOR VALUES IN (1); +CREATE INDEX ischk_idx ON ischk USING gin (b); +/*+ IndexScan(ischk ischk_idx) */ +EXPLAIN (COSTS false) SELECT * FROM ischk WHERE b = 'x'; +LOG: available indexes for IndexScan(ischk_d1): ischk_d1_b_idx +LOG: available indexes for IndexScan(ischk_d2): ischk_d2_b_idx +LOG: pg_hint_plan: +used hint: +IndexScan(ischk ischk_idx) +not used hint: +duplication hint: +error hint: + + QUERY PLAN +----------------------------------------- + Append + -> Seq Scan on ischk_d1 ischk_1 + Filter: (b = '''x'''::tsvector) + -> Seq Scan on ischk_d2 ischk_2 + Filter: (b = '''x'''::tsvector) +(5 rows) + +DROP TABLE ischk; -- quote test /*+SeqScan("""t1 ) ")IndexScan("t 2 """)HashJoin("""t1 ) "T3"t 2 """)Leading("""t1 ) "T3"t 2 """)Set(application_name"a a a"" a A")*/ EXPLAIN (COSTS false) SELECT * FROM t1 """t1 ) ", t2 "t 2 """, t3 "T3" WHERE """t1 ) ".id = "t 2 """.id AND """t1 ) ".id = "T3".id; diff --git a/pg_hint_plan.c b/pg_hint_plan.c index 4ec55fd4..9e71cbdb 100644 --- a/pg_hint_plan.c +++ b/pg_hint_plan.c @@ -3583,11 +3583,24 @@ restrict_indexes(PlannerInfo *root, ScanMethodHint *hint, RelOptInfo *rel, /* deny if any of column attributes don't match */ if (strcmp(p_attname, c_attname) != 0 || p_info->indcollation[i] != info->indexcollations[i] || - p_info->opclass[i] != info->opcintype[i]|| - ((p_info->indoption[i] & INDOPTION_DESC) != 0) - != info->reverse_sort[i] || - ((p_info->indoption[i] & INDOPTION_NULLS_FIRST) != 0) - != info->nulls_first[i]) + p_info->opclass[i] != info->opcintype[i]) + break; + + /* + * Compare index ordering if this index is ordered. + * + * We already confirmed that this and the parent indexes + * share the same column set (actually only the length of + * the column set is compard, though.) and index access + * method. So if this index is unordered, the parent can be + * assumed to be be unodered. Thus no need to bother + * checking the parent's orderedness. + */ + if (info->sortopfamily != NULL && + (((p_info->indoption[i] & INDOPTION_DESC) != 0) + != info->reverse_sort[i] || + ((p_info->indoption[i] & INDOPTION_NULLS_FIRST) != 0) + != info->nulls_first[i])) break; } diff --git a/sql/pg_hint_plan.sql b/sql/pg_hint_plan.sql index 532dd933..619b23d2 100644 --- a/sql/pg_hint_plan.sql +++ b/sql/pg_hint_plan.sql @@ -460,6 +460,15 @@ EXPLAIN (COSTS false) SELECT * FROM ONLY p1, t1 WHERE p1.id >= 50 AND p1.id <= 5 /*+TidScan(p1)*/ EXPLAIN (COSTS false) SELECT * FROM ONLY p1, t1 WHERE p1.id >= 50 AND p1.id <= 51 AND p1.ctid = '(1,1)' AND p1.id = t1.id AND t1.id < 10; +-- IndexScan is safe for unordered indexes +CREATE TABLE ischk (a text, b tsvector) PARTITION BY LIST(a); +CREATE TABLE ischk_d1 PARTITION OF ischk FOR VALUES IN (0); +CREATE TABLE ischk_d2 PARTITION OF ischk FOR VALUES IN (1); +CREATE INDEX ischk_idx ON ischk USING gin (b); +/*+ IndexScan(ischk ischk_idx) */ +EXPLAIN (COSTS false) SELECT * FROM ischk WHERE b = 'x'; +DROP TABLE ischk; + -- quote test /*+SeqScan("""t1 ) ")IndexScan("t 2 """)HashJoin("""t1 ) "T3"t 2 """)Leading("""t1 ) "T3"t 2 """)Set(application_name"a a a"" a A")*/ EXPLAIN (COSTS false) SELECT * FROM t1 """t1 ) ", t2 "t 2 """, t3 "T3" WHERE """t1 ) ".id = "t 2 """.id AND """t1 ) ".id = "T3".id;