diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 655bdced4f1..2874e69c7e5 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -6486,6 +6486,29 @@ SELECT * FROM foreign_tbl; 20 | 30 (1 row) +-- We don't allow batch insert when there are any WCO constraints +ALTER SERVER loopback OPTIONS (ADD batch_size '10'); +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 15), (0, 5); + QUERY PLAN +-------------------------------------------------------------------------------- + Insert on public.foreign_tbl + Remote SQL: INSERT INTO public.base_tbl(a, b) VALUES ($1, $2) RETURNING a, b + Batch Size: 1 + -> Values Scan on "*VALUES*" + Output: "*VALUES*".column1, "*VALUES*".column2 +(5 rows) + +INSERT INTO rw_view VALUES (0, 15), (0, 5); -- should fail +ERROR: new row violates check option for view "rw_view" +DETAIL: Failing row contains (10, 5). +SELECT * FROM foreign_tbl; + a | b +----+---- + 20 | 30 +(1 row) + +ALTER SERVER loopback OPTIONS (DROP batch_size); DROP FOREIGN TABLE foreign_tbl CASCADE; NOTICE: drop cascades to view rw_view DROP TRIGGER row_before_insupd_trigger ON base_tbl; @@ -6578,6 +6601,27 @@ SELECT * FROM foreign_tbl; 20 | 30 (1 row) +-- We don't allow batch insert when there are any WCO constraints +ALTER SERVER loopback OPTIONS (ADD batch_size '10'); +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 15), (0, 5); + QUERY PLAN +-------------------------------------------------------- + Insert on public.parent_tbl + -> Values Scan on "*VALUES*" + Output: "*VALUES*".column1, "*VALUES*".column2 +(3 rows) + +INSERT INTO rw_view VALUES (0, 15), (0, 5); -- should fail +ERROR: new row violates check option for view "rw_view" +DETAIL: Failing row contains (10, 5). +SELECT * FROM foreign_tbl; + a | b +----+---- + 20 | 30 +(1 row) + +ALTER SERVER loopback OPTIONS (DROP batch_size); DROP FOREIGN TABLE foreign_tbl CASCADE; DROP TRIGGER row_before_insupd_trigger ON child_tbl; DROP TABLE parent_tbl CASCADE; diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index 58599c7aeaa..b5adcb86c09 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -2046,8 +2046,9 @@ postgresGetForeignModifyBatchSize(ResultRelInfo *resultRelInfo) batch_size = get_batch_size_option(resultRelInfo->ri_RelationDesc); /* - * Disable batching when we have to use RETURNING or there are any - * BEFORE/AFTER ROW INSERT triggers on the foreign table. + * Disable batching when we have to use RETURNING, there are any + * BEFORE/AFTER ROW INSERT triggers on the foreign table, or there are any + * WITH CHECK OPTION constraints from parent views. * * When there are any BEFORE ROW INSERT triggers on the table, we can't * support it, because such triggers might query the table we're inserting @@ -2055,6 +2056,7 @@ postgresGetForeignModifyBatchSize(ResultRelInfo *resultRelInfo) * and prepared for insertion are not there. */ if (resultRelInfo->ri_projectReturning != NULL || + resultRelInfo->ri_WithCheckOptions != NIL || (resultRelInfo->ri_TrigDesc && (resultRelInfo->ri_TrigDesc->trig_insert_before_row || resultRelInfo->ri_TrigDesc->trig_insert_after_row))) diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index 724c1802b28..3663ca3bdf4 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -1442,6 +1442,14 @@ UPDATE rw_view SET b = b + 15; UPDATE rw_view SET b = b + 15; -- ok SELECT * FROM foreign_tbl; +-- We don't allow batch insert when there are any WCO constraints +ALTER SERVER loopback OPTIONS (ADD batch_size '10'); +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 15), (0, 5); +INSERT INTO rw_view VALUES (0, 15), (0, 5); -- should fail +SELECT * FROM foreign_tbl; +ALTER SERVER loopback OPTIONS (DROP batch_size); + DROP FOREIGN TABLE foreign_tbl CASCADE; DROP TRIGGER row_before_insupd_trigger ON base_tbl; DROP TABLE base_tbl; @@ -1480,6 +1488,14 @@ UPDATE rw_view SET b = b + 15; UPDATE rw_view SET b = b + 15; -- ok SELECT * FROM foreign_tbl; +-- We don't allow batch insert when there are any WCO constraints +ALTER SERVER loopback OPTIONS (ADD batch_size '10'); +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 15), (0, 5); +INSERT INTO rw_view VALUES (0, 15), (0, 5); -- should fail +SELECT * FROM foreign_tbl; +ALTER SERVER loopback OPTIONS (DROP batch_size); + DROP FOREIGN TABLE foreign_tbl CASCADE; DROP TRIGGER row_before_insupd_trigger ON child_tbl; DROP TABLE parent_tbl CASCADE; diff --git a/doc/src/sgml/brin.sgml b/doc/src/sgml/brin.sgml index 4ee8908b65a..71697155d7c 100644 --- a/doc/src/sgml/brin.sgml +++ b/doc/src/sgml/brin.sgml @@ -75,7 +75,7 @@ summarized will cause the summary information to be updated with data from the new tuples. When a new page is created that does not fall within the last - summarized range, the range that the new page belongs into + summarized range, the range that the new page belongs to does not automatically acquire a summary tuple; those tuples remain unsummarized until a summarization run is invoked later, creating the initial summary for that range. diff --git a/doc/src/sgml/ref/drop_extension.sgml b/doc/src/sgml/ref/drop_extension.sgml index c01ddace84c..dcc52c2ced0 100644 --- a/doc/src/sgml/ref/drop_extension.sgml +++ b/doc/src/sgml/ref/drop_extension.sgml @@ -32,7 +32,8 @@ DROP EXTENSION [ IF EXISTS ] name [ DROP EXTENSION removes extensions from the database. Dropping an extension causes its component objects, and other explicitly dependent routines (see , - the depends on extension action), to be dropped as well. + the DEPENDS ON EXTENSION extension_name + action), to be dropped as well. @@ -80,8 +81,8 @@ DROP EXTENSION [ IF EXISTS ] name [ This option prevents the specified extensions from being dropped - if there exists non-extension-member objects that depends on any - the extensions. This is the default. + if there exist non-extension-member objects that depend on any + of the extensions. This is the default. diff --git a/doc/src/sgml/release-14.sgml b/doc/src/sgml/release-14.sgml index 95a6bfe037f..b5f91109812 100644 --- a/doc/src/sgml/release-14.sgml +++ b/doc/src/sgml/release-14.sgml @@ -1,6 +1,942 @@ + + Release 14.5 + + + Release date: + 2022-08-11 + + + + This release contains a variety of fixes from 14.4. + For information about new features in major release 14, see + . + + + + Migration to Version 14.5 + + + A dump/restore is not required for those running 14.X. + + + + However, if you are upgrading from a version earlier than 14.4, + see . + + + + + Changes + + + + + + + Do not let extension scripts replace objects not already belonging + to the extension (Tom Lane) + + + + This change prevents extension scripts from doing CREATE + OR REPLACE if there is an existing object that does not + belong to the extension. It also prevents CREATE IF NOT + EXISTS in the same situation. This prevents a form of + trojan-horse attack in which a hostile database user could become + the owner of an extension object and then modify it to compromise + future uses of the object by other users. As a side benefit, it + also reduces the risk of accidentally replacing objects one did + not mean to. + + + + The PostgreSQL Project thanks + Sven Klemm for reporting this problem. + (CVE-2022-2625) + + + + + + + Fix replay of CREATE DATABASE WAL + records on standby servers + (Kyotaro Horiguchi, Asim R Praveen, Paul Guo) + + + + Standby servers may encounter missing tablespace directories + when replaying database-creation WAL records. Prior to this + patch, a standby would fail to recover in such a case; + however, such directories could be legitimately missing. + Create the tablespace (as a plain directory), then check that it + has been dropped again once replay reaches a consistent state. + + + + + + + Support in place tablespaces + (Thomas Munro, Michael Paquier, Álvaro Herrera) + + + + Normally a Postgres tablespace is a symbolic link to a directory on + some other filesystem. This change allows it to just be a plain + directory. While this has no use for separating tables onto + different filesystems, it is a convenient setup for testing. + Moreover, it is necessary to support the CREATE + DATABASE replay fix, which transiently creates a missing + tablespace as an in place tablespace. + + + + + + + Fix permissions checks in CREATE INDEX (Nathan + Bossart, Noah Misch) + + + + The fix for CVE-2022-1552 caused CREATE INDEX to + apply the table owner's permissions while performing lookups of + operator classes and other objects, where formerly the calling + user's permissions were used. This broke dump/restore scenarios, + because pg_dump issues CREATE + INDEX before re-granting permissions. + + + + + + + In extended query protocol, force an immediate commit + after CREATE DATABASE and other commands that + can't run in a transaction block (Tom Lane) + + + + If the client does not send a Sync message immediately after such a + command, but instead sends another command, any failure in that + command would lead to rolling back the preceding command, typically + leaving inconsistent state on-disk (such as a missing or extra + database directory). The mechanisms intended to prevent that + situation turn out to work for multiple commands in a simple-Query + message, but not for a series of extended-protocol messages. To + prevent inconsistency without breaking use-cases that work today, + force an implicit commit after such commands. + + + + + + + Fix race condition when checking transaction visibility (Simon Riggs) + + + + TransactionIdIsInProgress could + report false before the subject transaction is + considered visible, leading to various misbehaviors. The race + condition window is normally very narrow, but use of synchronous + replication makes it much wider, because the wait for a synchronous + replica happens in that window. + + + + + + + Fix incorrect plans when sorting by an expression that contains a + non-top-level set-returning function (Richard Guo, Tom Lane) + + + + + + + Fix incorrect permissions-checking code for extended statistics + (Richard Guo) + + + + If there are extended statistics on a table that the user has only + partial SELECT permissions on, some queries would + fail with unrecognized node type errors. + + + + + + + Fix extended statistics machinery to handle MCV-type statistics on + boolean-valued expressions (Tom Lane) + + + + Statistics collection worked fine, but a query containing such an + expression in WHERE would fail + with unknown clause type. + + + + + + + Avoid planner core dump with constant + = ANY(array) clauses when + there are MCV-type extended statistics on + the array variable (Tom Lane) + + + + + + + Fix ALTER TABLE ... ENABLE/DISABLE TRIGGER to + handle recursion correctly for triggers on partitioned tables + (Álvaro Herrera, Amit Langote) + + + + In certain cases, a trigger does not exist failure + would occur because the command would try to adjust the trigger on a + child partition that doesn't have it. + + + + + + + Allow cancellation of ANALYZE while it is + computing extended statistics (Tom Lane, Justin Pryzby) + + + + In some scenarios with high statistics targets, it was possible to + spend many seconds in an un-cancellable sort operation. + + + + + + + Improve syntax error messages for type jsonpath + (Andrew Dunstan) + + + + + + + Ensure that pg_stop_backup() cleans up session + state properly (Fujii Masao) + + + + This omission could lead to assertion failures or crashes later in + the session. + + + + + + + Fix trim_array() to handle a zero-dimensional + array argument sanely (Martin Kalcher) + + + + + + + Fix join alias matching in FOR [KEY] UPDATE/SHARE + clauses (Dean Rasheed) + + + + In corner cases, a misleading error could be reported. + + + + + + + Reject ROW() expressions and functions + in FROM that have too many columns (Tom Lane) + + + + Cases with more than about 1600 columns are unsupported, and + have always failed at execution. However, it emerges that some + earlier code could be driven to assertion failures or crashes by + queries with more than 32K columns. Add a parse-time check to + prevent that. + + + + + + + Fix dumping of a view using a function in FROM + that returns a composite type, when column(s) of the composite type + have been dropped since the view was made (Tom Lane) + + + + This oversight could lead to dump/reload + or pg_upgrade failures, as the dumped + view would have too many column aliases for the function. + + + + + + + Disallow nested backup operations in logical replication walsenders + (Fujii Masao) + + + + + + + Fix memory leak in logical replication subscribers (Hou Zhijie) + + + + + + + Fix logical replication's checking of replica identity when the + target table is partitioned (Shi Yu, Hou Zhijie) + + + + The replica identity columns have to be re-identified for the child + partition. + + + + + + + Fix failures to update cached schema data in a logical replication + subscriber after a schema change on the publisher (Shi Yu, Hou + Zhijie) + + + + + + + Fix WAL consistency checking logic to correctly + handle BRIN_EVACUATE_PAGE flags (Haiyang Wang) + + + + + + + Fix erroneous assertion checks in shared hashtable management + (Thomas Munro) + + + + + + + Avoid assertion failure + when min_dynamic_shared_memory is set to a + non-default value (Thomas Munro) + + + + + + + Arrange to clean up after commit-time errors + within SPI_commit(), rather than expecting + callers to do that (Peter Eisentraut, Tom Lane) + + + + Proper cleanup is complicated and requires use of low-level + facilities, so it's not surprising that no known caller got it + right. This led to misbehaviors when a PL procedure + issued COMMIT but a failure occurred (such as a + deferred constraint check). To improve matters, + redefine SPI_commit() as starting a new + transaction, so that it becomes equivalent + to SPI_commit_and_chain() except that you get + default transaction characteristics instead of preserving the prior + transaction's characteristics. To make this somewhat transparent + API-wise, redefine SPI_start_transaction() as a + no-op. All known callers of SPI_commit() + immediately call SPI_start_transaction(), so + they will not notice any change. Similar remarks apply + to SPI_rollback(). + + + + Also fix PL/Python, which omitted any handling of such errors at all, + resulting in jumping out of the Python interpreter. This is + reported to crash Python 3.11. Older Python releases leak some + memory but seem okay with it otherwise. + + + + + + + Improve libpq's handling of idle states + in pipeline mode (Álvaro Herrera, Kyotaro Horiguchi) + + + + This fixes message type 0x33 arrived from server while + idle warnings, as well as possible loss of end-of-query NULL + results from PQgetResult(). + + + + + + + Avoid core dump in ecpglib with + unexpected orders of operations (Tom Lane) + + + + Certain operations such as EXEC SQL PREPARE would + crash (rather than reporting an error as expected) if called before + establishing any database connection. + + + + + + + In ecpglib, avoid + redundant newlocale() calls (Noah Misch) + + + + Allocate a C locale object once per process when first connecting, + rather than creating and freeing locale objects once per query. + This mitigates a libc memory leak on AIX, and may offer some + performance benefit everywhere. + + + + + + + In psql's \watch + command, echo a newline after cancellation with control-C + (Pavel Stehule) + + + + This prevents libedit (and possibly also libreadline) from becoming + confused about which column the cursor is in. + + + + + + + Fix pg_upgrade to detect non-upgradable + usages of functions taking anyarray (Justin Pryzby) + + + + Version 14 changed some built-in functions to take + type anycompatiblearray instead + of anyarray. While this is mostly transparent, + user-defined aggregates and operators built atop these functions + have to be declared with exactly matching types. The presence of an + object referencing the old signature will + cause pg_upgrade to fail, so change it to + detect and report such cases before beginning the upgrade. + + + + + + + Fix possible report of wrong error condition + after clone() failure + in pg_upgrade + with option (Justin Pryzby) + + + + + + + Fix contrib/pg_stat_statements to avoid + problems with very large query-text files on 32-bit platforms + (Tom Lane) + + + + + + + In contrib/postgres_fdw, prevent batch + insertion when there are WITH CHECK OPTION + constraints (Etsuro Fujita) + + + + Such constraints cannot be checked properly if more than one row is + inserted at a time. + + + + + + + Fix contrib/postgres_fdw to detect failure to + send an asynchronous data fetch query (Fujii Masao) + + + + + + + Ensure that contrib/postgres_fdw sends + constants of regconfig and other reg* + types with proper schema qualification (Tom Lane) + + + + + + + Block signals while allocating dynamic shared memory on Linux + (Thomas Munro) + + + + This avoids problems when a signal + interrupts posix_fallocate(). + + + + + + + Detect unexpected EEXIST error + from shm_open() (Thomas Munro) + + + + This avoids a possible crash on Solaris. + + + + + + + Avoid using signalfd() + on illumos systems (Thomas Munro) + + + + This appears to trigger hangs and kernel panics, so avoid the + function until a fix is available. + + + + + + + + Release 14.4 diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 8e2b9230cb3..6c45d349524 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -754,6 +754,12 @@ expression_returns_set_walker(Node *node, void *context) /* else fall through to check args */ } + /* + * If you add any more cases that return sets, also fix + * expression_returns_set_rows() in clauses.c and IS_SRF_CALL() in + * tlist.c. + */ + /* Avoid recursion for some cases that parser checks not to return a set */ if (IsA(node, Aggref)) return false; diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 547b2550917..6a941c662c7 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -1005,7 +1005,7 @@ relation_can_be_sorted_early(PlannerInfo *root, RelOptInfo *rel, * one are effectively checking properties of targetexpr, so there's * no point in asking whether some other EC member would be better.) */ - if (IS_SRF_CALL((Node *) em->em_expr)) + if (expression_returns_set((Node *) em->em_expr)) continue; /* @@ -1033,7 +1033,7 @@ relation_can_be_sorted_early(PlannerInfo *root, RelOptInfo *rel, * member in this case; since SRFs can't appear in WHERE, they cannot * belong to multi-member ECs.) */ - if (IS_SRF_CALL((Node *) em->em_expr)) + if (expression_returns_set((Node *) em->em_expr)) return false; return true; diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c index 9d0f3274dbe..bb16a8d2c38 100644 --- a/src/backend/optimizer/util/tlist.c +++ b/src/backend/optimizer/util/tlist.c @@ -32,6 +32,17 @@ typedef struct maxSortGroupRef_context static bool maxSortGroupRef_walker(Node *node, maxSortGroupRef_context *cxt); +/* + * Test if an expression node represents a SRF call. Beware multiple eval! + * + * Please note that this is only meant for use in split_pathtarget_at_srfs(); + * if you use it anywhere else, your code is almost certainly wrong for SRFs + * nested within expressions. Use expression_returns_set() instead. + */ +#define IS_SRF_CALL(node) \ + ((IsA(node, FuncExpr) && ((FuncExpr *) (node))->funcretset) || \ + (IsA(node, OpExpr) && ((OpExpr *) (node))->opretset)) + /* * Data structures for split_pathtarget_at_srfs(). To preserve the identity * of sortgroupref items even if they are textually equal(), what we track is diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c index ee1c25416bd..134a047f8df 100644 --- a/src/backend/statistics/extended_stats.c +++ b/src/backend/statistics/extended_stats.c @@ -1333,8 +1333,8 @@ choose_best_statistics(List *stats, char requiredkind, * * (c) combinations using AND/OR/NOT * - * (d) ScalarArrayOpExprs of the form (Var/Expr op ANY (array)) or (Var/Expr - * op ALL (array)) + * (d) ScalarArrayOpExprs of the form (Var/Expr op ANY (Const)) or + * (Var/Expr op ALL (Const)) * * In the future, the range of supported clauses may be expanded to more * complex cases, for example (Var op Var). @@ -1454,13 +1454,18 @@ statext_is_compatible_clause_internal(PlannerInfo *root, Node *clause, RangeTblEntry *rte = root->simple_rte_array[relid]; ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) clause; Node *clause_expr; + bool expronleft; /* Only expressions with two arguments are considered compatible. */ if (list_length(expr->args) != 2) return false; /* Check if the expression has the right shape (one Var, one Const) */ - if (!examine_opclause_args(expr->args, &clause_expr, NULL, NULL)) + if (!examine_opclause_args(expr->args, &clause_expr, NULL, &expronleft)) + return false; + + /* We only support Var on left, Const on right */ + if (!expronleft) return false; /* diff --git a/src/backend/statistics/mcv.c b/src/backend/statistics/mcv.c index e6a60865282..01837758534 100644 --- a/src/backend/statistics/mcv.c +++ b/src/backend/statistics/mcv.c @@ -1531,13 +1531,13 @@ pg_mcv_list_send(PG_FUNCTION_ARGS) /* * match the attribute/expression to a dimension of the statistic * - * Match the attribute/expression to statistics dimension. Optionally - * determine the collation. + * Returns the zero-based index of the matching statistics dimension. + * Optionally determines the collation. */ static int mcv_match_expression(Node *expr, Bitmapset *keys, List *exprs, Oid *collid) { - int idx = -1; + int idx; if (IsA(expr, Var)) { @@ -1549,20 +1549,19 @@ mcv_match_expression(Node *expr, Bitmapset *keys, List *exprs, Oid *collid) idx = bms_member_index(keys, var->varattno); - /* make sure the index is valid */ - Assert((idx >= 0) && (idx <= bms_num_members(keys))); + if (idx < 0) + elog(ERROR, "variable not found in statistics object"); } else { + /* expression - lookup in stats expressions */ ListCell *lc; - /* expressions are stored after the simple columns */ - idx = bms_num_members(keys); - if (collid) *collid = exprCollation(expr); - /* expression - lookup in stats expressions */ + /* expressions are stored after the simple columns */ + idx = bms_num_members(keys); foreach(lc, exprs) { Node *stat_expr = (Node *) lfirst(lc); @@ -1573,13 +1572,10 @@ mcv_match_expression(Node *expr, Bitmapset *keys, List *exprs, Oid *collid) idx++; } - /* make sure the index is valid */ - Assert((idx >= bms_num_members(keys)) && - (idx <= bms_num_members(keys) + list_length(exprs))); + if (lc == NULL) + elog(ERROR, "expression not found in statistics object"); } - Assert((idx >= 0) && (idx < bms_num_members(keys) + list_length(exprs))); - return idx; } @@ -1659,8 +1655,6 @@ mcv_get_match_bitmap(PlannerInfo *root, List *clauses, /* match the attribute/expression to a dimension of the statistic */ idx = mcv_match_expression(clause_expr, keys, exprs, &collid); - Assert((idx >= 0) && (idx < bms_num_members(keys) + list_length(exprs))); - /* * Walk through the MCV items and evaluate the current clause. We * can skip items that were already ruled out, and terminate if @@ -1746,10 +1740,14 @@ mcv_get_match_bitmap(PlannerInfo *root, List *clauses, if (!examine_opclause_args(expr->args, &clause_expr, &cst, &expronleft)) elog(ERROR, "incompatible clause"); - /* ScalarArrayOpExpr has the Var always on the left */ - Assert(expronleft); + /* We expect Var on left */ + if (!expronleft) + elog(ERROR, "incompatible clause"); - /* XXX what if (cst->constisnull == NULL)? */ + /* + * Deconstruct the array constant, unless it's NULL (we'll cover + * that case below) + */ if (!cst->constisnull) { arrayval = DatumGetArrayTypeP(cst->constvalue); @@ -1947,7 +1945,30 @@ mcv_get_match_bitmap(PlannerInfo *root, List *clauses, } } else - elog(ERROR, "unknown clause type: %d", clause->type); + { + /* Otherwise, it must be a bare boolean-returning expression */ + int idx; + + /* match the expression to a dimension of the statistic */ + idx = mcv_match_expression(clause, keys, exprs, NULL); + + /* + * Walk through the MCV items and evaluate the current clause. We + * can skip items that were already ruled out, and terminate if + * there are no remaining MCV items that might possibly match. + */ + for (i = 0; i < mcvlist->nitems; i++) + { + bool match; + MCVItem *item = &mcvlist->items[i]; + + /* "match" just means it's bool TRUE */ + match = !item->isnull[idx] && DatumGetBool(item->values[idx]); + + /* now, update the match bitmap, depending on OR/AND type */ + matches[i] = RESULT_MERGE(matches[i], is_or, match); + } + } } return matches; diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 26cd7458961..6d00be72b16 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -6802,7 +6802,7 @@ trim_array(PG_FUNCTION_ARGS) { ArrayType *v = PG_GETARG_ARRAYTYPE_P(0); int n = PG_GETARG_INT32(1); - int array_length = ARR_DIMS(v)[0]; + int array_length = (ARR_NDIM(v) > 0) ? ARR_DIMS(v)[0] : 0; int16 elmlen; bool elmbyval; char elmalign; @@ -6822,8 +6822,11 @@ trim_array(PG_FUNCTION_ARGS) /* Set all the bounds as unprovided except the first upper bound */ memset(lowerProvided, false, sizeof(lowerProvided)); memset(upperProvided, false, sizeof(upperProvided)); - upper[0] = ARR_LBOUND(v)[0] + array_length - n - 1; - upperProvided[0] = true; + if (ARR_NDIM(v) > 0) + { + upper[0] = ARR_LBOUND(v)[0] + array_length - n - 1; + upperProvided[0] = true; + } /* Fetch the needed information about the element type */ get_typlenbyvalalign(ARR_ELEMTYPE(v), &elmlen, &elmbyval, &elmalign); diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h index f8400206288..c2553891186 100644 --- a/src/include/optimizer/optimizer.h +++ b/src/include/optimizer/optimizer.h @@ -25,11 +25,6 @@ #include "nodes/parsenodes.h" #include "nodes/plannodes.h" -/* Test if an expression node represents a SRF call. Beware multiple eval! */ -#define IS_SRF_CALL(node) \ - ((IsA(node, FuncExpr) && ((FuncExpr *) (node))->funcretset) || \ - (IsA(node, OpExpr) && ((OpExpr *) (node))->opretset)) - /* * We don't want to include nodes/pathnodes.h here, because non-planner * code should generally treat PlannerInfo as an opaque typedef. diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index a8686405408..d6a8f8f8545 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -2586,3 +2586,5 @@ SELECT trim_array(ARRAY[1, 2, 3], -1); -- fail ERROR: number of elements to trim must be between 0 and 3 SELECT trim_array(ARRAY[1, 2, 3], 10); -- fail ERROR: number of elements to trim must be between 0 and 3 +SELECT trim_array(ARRAY[]::int[], 1); -- fail +ERROR: number of elements to trim must be between 0 and 0 diff --git a/src/test/regress/expected/incremental_sort.out b/src/test/regress/expected/incremental_sort.out index c5f52e1e97f..8a25141d8f5 100644 --- a/src/test/regress/expected/incremental_sort.out +++ b/src/test/regress/expected/incremental_sort.out @@ -1804,15 +1804,3 @@ order by 1, 2; -> Function Scan on generate_series (7 rows) --- Disallow pushing down sort when pathkey is an SRF. -explain (costs off) select unique1 from tenk1 order by unnest('{1,2}'::int[]); - QUERY PLAN -------------------------------------------------------------------------- - Gather Motion 3:1 (slice1; segments: 3) - Merge Key: (unnest('{1,2}'::anyarray)) - -> Sort - Sort Key: (unnest('{1,2}'::anyarray)) - -> ProjectSet - -> Index Only Scan using tenk1_unique1 on tenk1 -(6 rows) - diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out index 4de01f4f632..db579197c3a 100644 --- a/src/test/regress/expected/select_parallel.out +++ b/src/test/regress/expected/select_parallel.out @@ -1217,6 +1217,33 @@ SELECT generate_series(1, two), array(select generate_series(1, two)) Optimizer: Postgres query optimizer (18 rows) +-- must disallow pushing sort below gather when pathkey contains an SRF +EXPLAIN (VERBOSE, COSTS OFF) +SELECT unnest(ARRAY[]::integer[]) + 1 AS pathkey + FROM tenk1 t1 JOIN tenk1 t2 ON TRUE + ORDER BY pathkey; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Gather Motion 6:1 (slice1; segments: 6) + Output: (((unnest('{}'::integer[])) + 1)) + Merge Key: (((unnest('{}'::integer[])) + 1)) + -> Sort + Output: (((unnest('{}'::integer[])) + 1)) + Sort Key: (((unnest('{}'::integer[])) + 1)) + -> Result + Output: ((unnest('{}'::integer[])) + 1) + -> ProjectSet + Output: unnest('{}'::integer[]) + -> Nested Loop + -> Parallel Seq Scan on public.tenk1 t1 + Output: t1.unique1, t1.unique2, t1.two, t1.four, t1.ten, t1.twenty, t1.hundred, t1.thousand, t1.twothousand, t1.fivethous, t1.tenthous, t1.odd, t1.even, t1.stringu1, t1.stringu2, t1.string4 + -> Materialize + -> Broadcast Motion 3:6 (slice2; segments: 3) + -> Seq Scan on public.tenk1 t2 + Settings: enable_parallel = 'on', min_parallel_table_scan_size = '0', optimizer = 'off', parallel_setup_cost = '0', parallel_tuple_cost = '0' + Optimizer: Postgres query optimizer +(18 rows) + -- test passing expanded-value representations to workers CREATE FUNCTION make_some_array(int,int) returns int[] as $$declare x int[]; diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out index 06aff4d5bd0..ba2914d3c7a 100644 --- a/src/test/regress/expected/stats_ext.out +++ b/src/test/regress/expected/stats_ext.out @@ -270,14 +270,23 @@ SELECT stxkind FROM pg_statistic_ext WHERE stxname = 'ab1_exprstat_3'; CREATE STATISTICS ab1_exprstat_4 ON date_trunc('day', d) FROM ab1; -- date_trunc on timestamp is immutable CREATE STATISTICS ab1_exprstat_5 ON date_trunc('day', c) FROM ab1; +-- check use of a boolean-returning expression +CREATE STATISTICS ab1_exprstat_6 ON + (case a when 1 then true else false end), b FROM ab1; -- insert some data and run analyze, to test that these cases build properly INSERT INTO ab1 -SELECT - generate_series(1,10), - generate_series(1,10), - generate_series('2020-10-01'::timestamp, '2020-10-10'::timestamp, interval '1 day'), - generate_series('2020-10-01'::timestamptz, '2020-10-10'::timestamptz, interval '1 day'); +SELECT x / 10, x / 3, + '2020-10-01'::timestamp + x * interval '1 day', + '2020-10-01'::timestamptz + x * interval '1 day' +FROM generate_series(1, 100) x; ANALYZE ab1; +-- apply some stats +SELECT * FROM check_estimated_rows('SELECT * FROM ab1 WHERE (case a when 1 then true else false end) AND b=2'); + estimated | actual +-----------+-------- + 1 | 0 +(1 row) + DROP TABLE ab1; -- Verify supported object types for extended statistics CREATE schema tststats; @@ -1835,7 +1844,8 @@ CREATE TABLE mcv_lists ( b VARCHAR, filler3 DATE, c INT, - d TEXT + d TEXT, + ia INT[] ) WITH (autovacuum_enabled = off); -- random data (no MCV list) @@ -1905,8 +1915,9 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,7) = 1 A -- 100 distinct combinations, all in the MCV list TRUNCATE mcv_lists; DROP STATISTICS mcv_lists_stats; -INSERT INTO mcv_lists (a, b, c, filler1) - SELECT mod(i,100), mod(i,50), mod(i,25), i FROM generate_series(1,5000) s(i); +INSERT INTO mcv_lists (a, b, c, ia, filler1) + SELECT mod(i,100), mod(i,50), mod(i,25), array[mod(i,25)], i + FROM generate_series(1,5000) s(i); ANALYZE mcv_lists; SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1'''); estimated | actual @@ -2046,8 +2057,14 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY 1 | 100 (1 row) +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = ANY (ARRAY[4,5]) AND 4 = ANY(ia)'); + estimated | actual +-----------+-------- + 4 | 50 +(1 row) + -- create statistics -CREATE STATISTICS mcv_lists_stats (mcv) ON a, b, c FROM mcv_lists; +CREATE STATISTICS mcv_lists_stats (mcv) ON a, b, c, ia FROM mcv_lists; ANALYZE mcv_lists; SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1'''); estimated | actual @@ -2193,6 +2210,12 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY 100 | 100 (1 row) +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = ANY (ARRAY[4,5]) AND 4 = ANY(ia)'); + estimated | actual +-----------+-------- + 4 | 50 +(1 row) + -- check change of unrelated column type does not reset the MCV statistics ALTER TABLE mcv_lists ALTER COLUMN d TYPE VARCHAR(64); SELECT d.stxdmcv IS NOT NULL diff --git a/src/test/regress/expected/stats_ext_optimizer.out b/src/test/regress/expected/stats_ext_optimizer.out index dafbf0a28b4..9ad26109b59 100644 --- a/src/test/regress/expected/stats_ext_optimizer.out +++ b/src/test/regress/expected/stats_ext_optimizer.out @@ -1857,7 +1857,8 @@ CREATE TABLE mcv_lists ( b VARCHAR, filler3 DATE, c INT, - d TEXT + d TEXT, + ia INT[] ) WITH (autovacuum_enabled = off); NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'filler1' as the Apache Cloudberry data distribution key for this table. @@ -1929,8 +1930,9 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,7) = 1 A -- 100 distinct combinations, all in the MCV list TRUNCATE mcv_lists; DROP STATISTICS mcv_lists_stats; -INSERT INTO mcv_lists (a, b, c, filler1) - SELECT mod(i,100), mod(i,50), mod(i,25), i FROM generate_series(1,5000) s(i); +INSERT INTO mcv_lists (a, b, c, ia, filler1) + SELECT mod(i,100), mod(i,50), mod(i,25), array[mod(i,25)], i + FROM generate_series(1,5000) s(i); ANALYZE mcv_lists; SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1'''); estimated | actual @@ -2070,8 +2072,14 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY 22 | 100 (1 row) +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = ANY (ARRAY[4,5]) AND 4 = ANY(ia)'); + estimated | actual +-----------+-------- + 4 | 50 +(1 row) + -- create statistics -CREATE STATISTICS mcv_lists_stats (mcv) ON a, b, c FROM mcv_lists; +CREATE STATISTICS mcv_lists_stats (mcv) ON a, b, c, ia FROM mcv_lists; ANALYZE mcv_lists; SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1'''); estimated | actual @@ -2217,6 +2225,12 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY 22 | 100 (1 row) +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = ANY (ARRAY[4,5]) AND 4 = ANY(ia)'); + estimated | actual +-----------+-------- + 4 | 50 +(1 row) + -- check change of unrelated column type does not reset the MCV statistics ALTER TABLE mcv_lists ALTER COLUMN d TYPE VARCHAR(64); SELECT d.stxdmcv IS NOT NULL diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index d10278e32f0..cefe5b05b3b 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -875,3 +875,4 @@ FROM SELECT trim_array(ARRAY[1, 2, 3], -1); -- fail SELECT trim_array(ARRAY[1, 2, 3], 10); -- fail +SELECT trim_array(ARRAY[]::int[], 1); -- fail diff --git a/src/test/regress/sql/incremental_sort.sql b/src/test/regress/sql/incremental_sort.sql index afd1dab2045..648eced7e14 100644 --- a/src/test/regress/sql/incremental_sort.sql +++ b/src/test/regress/sql/incremental_sort.sql @@ -294,5 +294,3 @@ from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub; explain (costs off) select sub.unique1, stringu1 || random()::text from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub order by 1, 2; --- Disallow pushing down sort when pathkey is an SRF. -explain (costs off) select unique1 from tenk1 order by unnest('{1,2}'::int[]); diff --git a/src/test/regress/sql/select_parallel.sql b/src/test/regress/sql/select_parallel.sql index 846066fad05..4a22a001ef9 100644 --- a/src/test/regress/sql/select_parallel.sql +++ b/src/test/regress/sql/select_parallel.sql @@ -443,6 +443,12 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT generate_series(1, two), array(select generate_series(1, two)) FROM tenk1 ORDER BY tenthous; +-- must disallow pushing sort below gather when pathkey contains an SRF +EXPLAIN (VERBOSE, COSTS OFF) +SELECT unnest(ARRAY[]::integer[]) + 1 AS pathkey + FROM tenk1 t1 JOIN tenk1 t2 ON TRUE + ORDER BY pathkey; + -- test passing expanded-value representations to workers CREATE FUNCTION make_some_array(int,int) returns int[] as $$declare x int[]; diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql index 744bb00c161..c2a91675045 100644 --- a/src/test/regress/sql/stats_ext.sql +++ b/src/test/regress/sql/stats_ext.sql @@ -176,14 +176,21 @@ CREATE STATISTICS ab1_exprstat_4 ON date_trunc('day', d) FROM ab1; -- date_trunc on timestamp is immutable CREATE STATISTICS ab1_exprstat_5 ON date_trunc('day', c) FROM ab1; +-- check use of a boolean-returning expression +CREATE STATISTICS ab1_exprstat_6 ON + (case a when 1 then true else false end), b FROM ab1; + -- insert some data and run analyze, to test that these cases build properly INSERT INTO ab1 -SELECT - generate_series(1,10), - generate_series(1,10), - generate_series('2020-10-01'::timestamp, '2020-10-10'::timestamp, interval '1 day'), - generate_series('2020-10-01'::timestamptz, '2020-10-10'::timestamptz, interval '1 day'); +SELECT x / 10, x / 3, + '2020-10-01'::timestamp + x * interval '1 day', + '2020-10-01'::timestamptz + x * interval '1 day' +FROM generate_series(1, 100) x; ANALYZE ab1; + +-- apply some stats +SELECT * FROM check_estimated_rows('SELECT * FROM ab1 WHERE (case a when 1 then true else false end) AND b=2'); + DROP TABLE ab1; -- Verify supported object types for extended statistics @@ -923,7 +930,8 @@ CREATE TABLE mcv_lists ( b VARCHAR, filler3 DATE, c INT, - d TEXT + d TEXT, + ia INT[] ) WITH (autovacuum_enabled = off); @@ -972,8 +980,9 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,7) = 1 A TRUNCATE mcv_lists; DROP STATISTICS mcv_lists_stats; -INSERT INTO mcv_lists (a, b, c, filler1) - SELECT mod(i,100), mod(i,50), mod(i,25), i FROM generate_series(1,5000) s(i); +INSERT INTO mcv_lists (a, b, c, ia, filler1) + SELECT mod(i,100), mod(i,50), mod(i,25), array[mod(i,25)], i + FROM generate_series(1,5000) s(i); ANALYZE mcv_lists; @@ -1023,8 +1032,10 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY[4, 5]) AND b IN (''1'', ''2'', NULL, ''3'') AND c > ANY (ARRAY[1, 2, NULL, 3])'); +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = ANY (ARRAY[4,5]) AND 4 = ANY(ia)'); + -- create statistics -CREATE STATISTICS mcv_lists_stats (mcv) ON a, b, c FROM mcv_lists; +CREATE STATISTICS mcv_lists_stats (mcv) ON a, b, c, ia FROM mcv_lists; ANALYZE mcv_lists; @@ -1076,6 +1087,8 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY[4, 5]) AND b IN (''1'', ''2'', NULL, ''3'') AND c > ANY (ARRAY[1, 2, NULL, 3])'); +SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = ANY (ARRAY[4,5]) AND 4 = ANY(ia)'); + -- check change of unrelated column type does not reset the MCV statistics ALTER TABLE mcv_lists ALTER COLUMN d TYPE VARCHAR(64);