1
0
mirror of https://github.com/postgres/postgres.git synced 2026-10-23 13:29:09 +03:00

Reject child partition FDWs in FOR PORTION OF

We should defer validating FDW usage until after analysis.  We have to
guard against not just the topmost table, but also individual child
partitions.  Added the check to CheckValidResultRel, because it is
called after looking up child partitions (accounting for pruning), but
before the FDW can run a DirectModify update, which would bypass
per-tuple executor work.

Author: jian he <jian.universality@gmail.com>
Reported-by: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: Paul A. Jungwirth <pj@illuminatedcomputing.com>
Discussion: https://www.postgresql.org/message-id/flat/CA%2BrenyUte0_UJsJiDJQi82oaBsMJn%3Dcct0Wn%3DvOqXtuDn%3DYYJA%40mail.gmail.com
This commit is contained in:
Peter Eisentraut
2026-06-27 19:34:40 +02:00
parent a272a58b94
commit a40fdf6588
8 changed files with 81 additions and 18 deletions

View File

@@ -6335,9 +6335,10 @@ DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass;
-- Test UPDATE FOR PORTION OF
UPDATE ft8 FOR PORTION OF c4 FROM '2005-01-01' TO '2006-01-01'
SET c2 = c2 + 1
WHERE c1 = '[1,2)';
SET c2 = c2 + 1
WHERE c1 = '[1,2)'; -- error
ERROR: foreign tables don't support FOR PORTION OF
DETAIL: "ft8" is a foreign table.
SELECT * FROM ft8 WHERE c1 = '[1,2)' ORDER BY c1, c4;
c1 | c2 | c3 | c4
-------+----+--------+-------------------------
@@ -6346,14 +6347,49 @@ SELECT * FROM ft8 WHERE c1 = '[1,2)' ORDER BY c1, c4;
-- Test DELETE FOR PORTION OF
DELETE FROM ft8 FOR PORTION OF c4 FROM '2005-01-01' TO '2006-01-01'
WHERE c1 = '[2,3)';
WHERE c1 = '[2,3)'; -- error
ERROR: foreign tables don't support FOR PORTION OF
DETAIL: "ft8" is a foreign table.
SELECT * FROM ft8 WHERE c1 = '[2,3)' ORDER BY c1, c4;
c1 | c2 | c3 | c4
-------+----+--------+-------------------------
[2,3) | 3 | AAA002 | [01-01-2000,01-01-2020)
(1 row)
-- FOR PORTION OF fails if a child partition is a foreign table, even if the
-- root is not. But a child partition that is pruned doesn't cause an error.
CREATE TABLE fpo_part_parent (
c1 int4range NOT NULL,
c2 int NOT NULL,
c3 text,
c4 daterange NOT NULL
) PARTITION BY LIST (c2);
CREATE TABLE fpo_part_local PARTITION OF fpo_part_parent FOR VALUES IN (1);
INSERT INTO fpo_part_local VALUES ('[1,2)', 1, 'one', '[2024-01-01,2024-12-31)');
CREATE FOREIGN TABLE fpo_part_foreign
PARTITION OF fpo_part_parent FOR VALUES IN (6)
SERVER loopback OPTIONS (schema_name 'S 1', table_name 'T 5');
DELETE FROM fpo_part_parent
FOR PORTION OF c4 FROM '2001-01-01' TO '2001-02-01' WHERE c2 = 6; -- error
ERROR: foreign tables don't support FOR PORTION OF
DETAIL: "fpo_part_foreign" is a foreign table.
UPDATE fpo_part_parent
FOR PORTION OF c4 FROM '2001-01-01' TO '2001-02-01' SET c3 = 'x' WHERE c2 = 6; -- error
ERROR: foreign tables don't support FOR PORTION OF
DETAIL: "fpo_part_foreign" is a foreign table.
UPDATE fpo_part_parent
FOR PORTION OF c4 FROM '2024-06-01' TO '2024-07-01' SET c3 = 'edited' WHERE c2 = 1; -- okay
DELETE FROM fpo_part_parent
FOR PORTION OF c4 FROM '2024-06-01' TO '2024-06-15' WHERE c2 = 1; -- okay
SELECT c1, c2, c3, c4 FROM fpo_part_local ORDER BY c4;
c1 | c2 | c3 | c4
-------+----+--------+-------------------------
[1,2) | 1 | one | [01-01-2024,06-01-2024)
[1,2) | 1 | edited | [06-15-2024,07-01-2024)
[1,2) | 1 | one | [07-01-2024,12-31-2024)
(3 rows)
DROP TABLE fpo_part_parent;
-- Test UPDATE/DELETE with RETURNING on a three-table join
INSERT INTO ft2 (c1,c2,c3)
SELECT id, id - 1200, to_char(id, 'FM00000') FROM generate_series(1201, 1300) id;

View File

@@ -1578,15 +1578,39 @@ DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass;
-- Test UPDATE FOR PORTION OF
UPDATE ft8 FOR PORTION OF c4 FROM '2005-01-01' TO '2006-01-01'
SET c2 = c2 + 1
WHERE c1 = '[1,2)';
SET c2 = c2 + 1
WHERE c1 = '[1,2)'; -- error
SELECT * FROM ft8 WHERE c1 = '[1,2)' ORDER BY c1, c4;
-- Test DELETE FOR PORTION OF
DELETE FROM ft8 FOR PORTION OF c4 FROM '2005-01-01' TO '2006-01-01'
WHERE c1 = '[2,3)';
WHERE c1 = '[2,3)'; -- error
SELECT * FROM ft8 WHERE c1 = '[2,3)' ORDER BY c1, c4;
-- FOR PORTION OF fails if a child partition is a foreign table, even if the
-- root is not. But a child partition that is pruned doesn't cause an error.
CREATE TABLE fpo_part_parent (
c1 int4range NOT NULL,
c2 int NOT NULL,
c3 text,
c4 daterange NOT NULL
) PARTITION BY LIST (c2);
CREATE TABLE fpo_part_local PARTITION OF fpo_part_parent FOR VALUES IN (1);
INSERT INTO fpo_part_local VALUES ('[1,2)', 1, 'one', '[2024-01-01,2024-12-31)');
CREATE FOREIGN TABLE fpo_part_foreign
PARTITION OF fpo_part_parent FOR VALUES IN (6)
SERVER loopback OPTIONS (schema_name 'S 1', table_name 'T 5');
DELETE FROM fpo_part_parent
FOR PORTION OF c4 FROM '2001-01-01' TO '2001-02-01' WHERE c2 = 6; -- error
UPDATE fpo_part_parent
FOR PORTION OF c4 FROM '2001-01-01' TO '2001-02-01' SET c3 = 'x' WHERE c2 = 6; -- error
UPDATE fpo_part_parent
FOR PORTION OF c4 FROM '2024-06-01' TO '2024-07-01' SET c3 = 'edited' WHERE c2 = 1; -- okay
DELETE FROM fpo_part_parent
FOR PORTION OF c4 FROM '2024-06-01' TO '2024-06-15' WHERE c2 = 1; -- okay
SELECT c1, c2, c3, c4 FROM fpo_part_local ORDER BY c4;
DROP TABLE fpo_part_parent;
-- Test UPDATE/DELETE with RETURNING on a three-table join
INSERT INTO ft2 (c1,c2,c3)
SELECT id, id - 1200, to_char(id, 'FM00000') FROM generate_series(1201, 1300) id;

View File

@@ -921,7 +921,7 @@ CopyFrom(CopyFromState cstate)
ExecInitResultRelation(estate, resultRelInfo, 1);
/* Verify the named relation is a valid target for INSERT */
CheckValidResultRel(resultRelInfo, CMD_INSERT, ONCONFLICT_NONE, NIL);
CheckValidResultRel(resultRelInfo, CMD_INSERT, ONCONFLICT_NONE, NIL, NULL);
ExecOpenIndices(resultRelInfo, false);

View File

@@ -1063,7 +1063,8 @@ InitPlan(QueryDesc *queryDesc, int eflags)
*/
void
CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation,
OnConflictAction onConflictAction, List *mergeActions)
OnConflictAction onConflictAction, List *mergeActions,
ModifyTable *mtnode)
{
Relation resultRel = resultRelInfo->ri_RelationDesc;
FdwRoutine *fdwroutine;
@@ -1126,6 +1127,14 @@ CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation,
RelationGetRelationName(resultRel))));
break;
case RELKIND_FOREIGN_TABLE:
/* We don't support FOR PORTION OF FDW queries. */
if (mtnode && mtnode->forPortionOf)
ereport(ERROR,
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("foreign tables don't support FOR PORTION OF"),
errdetail("\"%s\" is a foreign table.",
RelationGetRelationName(resultRel)));
/* Okay only if the FDW supports it */
fdwroutine = resultRelInfo->ri_FdwRoutine;
switch (operation)

View File

@@ -368,7 +368,7 @@ ExecFindPartition(ModifyTableState *mtstate,
/* Verify this ResultRelInfo allows INSERTs */
CheckValidResultRel(rri, CMD_INSERT,
node ? node->onConflictAction : ONCONFLICT_NONE,
NIL);
NIL, node);
/*
* Initialize information needed to insert this and
@@ -594,7 +594,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
* required when the operation is CMD_UPDATE.
*/
CheckValidResultRel(leaf_part_rri, CMD_INSERT,
node ? node->onConflictAction : ONCONFLICT_NONE, NIL);
node ? node->onConflictAction : ONCONFLICT_NONE, NIL, node);
/*
* Open partition indices. The user may have asked to check for conflicts

View File

@@ -5312,7 +5312,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
* Verify result relation is a valid target for the current operation
*/
CheckValidResultRel(resultRelInfo, operation, node->onConflictAction,
mergeActions);
mergeActions, node);
resultRelInfo++;
i++;

View File

@@ -1335,12 +1335,6 @@ transformForPortionOfClause(ParseState *pstate,
ForPortionOfExpr *result;
Var *rangeVar;
/* We don't support FOR PORTION OF FDW queries. */
if (targetrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("foreign tables don't support FOR PORTION OF")));
result = makeNode(ForPortionOfExpr);
/* Look up the FOR PORTION OF name requested. */

View File

@@ -249,7 +249,7 @@ extern bool ExecCheckPermissions(List *rangeTable,
extern bool ExecCheckOneRelPerms(RTEPermissionInfo *perminfo);
extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation,
OnConflictAction onConflictAction,
List *mergeActions);
List *mergeActions, ModifyTable *mtnode);
extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,