diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 65a9b0d802..db00a593a6 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -6306,10 +6306,18 @@ plan_create_index_workers(Oid tableOid, Oid indexOid) * Currently, parallel workers can't access the leader's temporary tables. * Furthermore, any index predicate or index expressions must be parallel * safe. + * + * Fetch the list of expressions and predicates directly from the + * catalogs. Retrieving this information from the relcache would cause + * the expressions and predicates to be flattened, losing properties that + * can be important to check if parallel workers can be used. For + * example, immutable parallel-unsafe functions, that cannot be used in + * parallel workers, would be changed to Const nodes, that are safe in + * parallel workers. */ if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP || - !is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) || - !is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index))) + !is_parallel_safe(root, (Node *) get_index_expressions(indexOid)) || + !is_parallel_safe(root, (Node *) get_index_predicate(indexOid))) { parallel_workers = 0; goto done; diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 13f8dafa30..db6b8a36ec 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -3255,6 +3255,74 @@ get_index_column_opclass(Oid index_oid, int attno) return opclass; } +/* + * get_index_expressions + * + * Given the index OID, its a List of its expressions or NIL if none. + */ +List * +get_index_expressions(Oid index_oid) +{ + List *result; + HeapTuple tuple; + Datum exprDatum; + bool isnull; + char *exprString; + + tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for index %u", index_oid); + + exprDatum = SysCacheGetAttr(INDEXRELID, tuple, + Anum_pg_index_indexprs, &isnull); + if (isnull) + { + ReleaseSysCache(tuple); + return NIL; + } + + exprString = TextDatumGetCString(exprDatum); + result = (List *) stringToNode(exprString); + pfree(exprString); + ReleaseSysCache(tuple); + + return result; +} + +/* + * get_index_predicate + * + * Given the index OID, return a List of its predicate or NIL if none. + */ +List * +get_index_predicate(Oid index_oid) +{ + List *result; + HeapTuple tuple; + Datum predDatum; + bool isnull; + char *predString; + + tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for index %u", index_oid); + + predDatum = SysCacheGetAttr(INDEXRELID, tuple, + Anum_pg_index_indpred, &isnull); + if (isnull) + { + ReleaseSysCache(tuple); + return NIL; + } + + predString = TextDatumGetCString(predDatum); + result = (List *) stringToNode(predString); + pfree(predString); + ReleaseSysCache(tuple); + + return result; +} + /* * get_index_isreplident * diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index 53d4a2d109..32ca83736e 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -182,6 +182,8 @@ extern char *get_namespace_name_or_temp(Oid nspid); extern Oid get_range_subtype(Oid rangeOid); extern Oid get_range_collation(Oid rangeOid); extern Oid get_index_column_opclass(Oid index_oid, int attno); +extern List *get_index_expressions(Oid index_oid); +extern List *get_index_predicate(Oid index_oid); extern bool get_index_isreplident(Oid index_oid); extern bool get_index_isvalid(Oid index_oid); extern bool get_index_isclustered(Oid index_oid); diff --git a/src/test/regress/expected/btree_index.out b/src/test/regress/expected/btree_index.out index f567117a46..8d0240f79b 100644 --- a/src/test/regress/expected/btree_index.out +++ b/src/test/regress/expected/btree_index.out @@ -330,3 +330,22 @@ VACUUM delete_test_table; -- The vacuum above should've turned the leaf page into a fast root. We just -- need to insert some rows to cause the fast root page to split. INSERT INTO delete_test_table SELECT i, 1, 2, 3 FROM generate_series(1,1000) i; +-- Test with index expression and predicate that include a parallel unsafe +-- function. +CREATE FUNCTION para_unsafe_f() RETURNS int IMMUTABLE PARALLEL UNSAFE +AS $$ +BEGIN + RETURN 0; +EXCEPTION WHEN OTHERS THEN + RETURN 1; +END$$ LANGUAGE plpgsql; +CREATE TABLE btree_para_bld(i int); +ALTER TABLE btree_para_bld SET (parallel_workers = 4); +SET max_parallel_maintenance_workers TO 4; +-- With parallel-unsafe expression +CREATE INDEX ON btree_para_bld((i + para_unsafe_f())); +-- With parallel-unsafe predicate +CREATE INDEX ON btree_para_bld(i) WHERE i > para_unsafe_f(); +RESET max_parallel_maintenance_workers; +DROP TABLE btree_para_bld; +DROP FUNCTION para_unsafe_f; diff --git a/src/test/regress/sql/btree_index.sql b/src/test/regress/sql/btree_index.sql index 558dcae0ec..c80165c6ee 100644 --- a/src/test/regress/sql/btree_index.sql +++ b/src/test/regress/sql/btree_index.sql @@ -160,3 +160,25 @@ VACUUM delete_test_table; -- The vacuum above should've turned the leaf page into a fast root. We just -- need to insert some rows to cause the fast root page to split. INSERT INTO delete_test_table SELECT i, 1, 2, 3 FROM generate_series(1,1000) i; + +-- Test with index expression and predicate that include a parallel unsafe +-- function. +CREATE FUNCTION para_unsafe_f() RETURNS int IMMUTABLE PARALLEL UNSAFE +AS $$ +BEGIN + RETURN 0; +EXCEPTION WHEN OTHERS THEN + RETURN 1; +END$$ LANGUAGE plpgsql; + +CREATE TABLE btree_para_bld(i int); +ALTER TABLE btree_para_bld SET (parallel_workers = 4); +SET max_parallel_maintenance_workers TO 4; +-- With parallel-unsafe expression +CREATE INDEX ON btree_para_bld((i + para_unsafe_f())); +-- With parallel-unsafe predicate +CREATE INDEX ON btree_para_bld(i) WHERE i > para_unsafe_f(); + +RESET max_parallel_maintenance_workers; +DROP TABLE btree_para_bld; +DROP FUNCTION para_unsafe_f;