-- aliases for the UPDATE target table UPDATE update_test AS t SET b = 10WHERE t.a = 10;
SELECT * FROM update_test;
UPDATE update_test t SET b = t.b + 10WHERE t.a = 10;
SELECT * FROM update_test;
-- error, you're not supposed to qualify the target column UPDATE update_test t SET t.b = t.b + 10WHERE t.a = 10;
-- -- Test VALUES in FROM --
UPDATE update_test SET a=v.i FROM (VALUES(100, 20)) AS v(i, j) WHERE update_test.b = v.j;
SELECT * FROM update_test;
-- fail, wrong data type: UPDATE update_test SET a = v.* FROM (VALUES(100, 20)) AS v(i, j) WHERE update_test.b = v.j;
-- -- Test multiple-set-clause syntax --
INSERTINTO update_test SELECT a,b+1,c FROM update_test; SELECT * FROM update_test;
UPDATE update_test SET (c,b,a) = ('bugle', b+11, DEFAULT) WHERE c = 'foo'; SELECT * FROM update_test; UPDATE update_test SET (c,b) = ('car', a+b), a = a + 1WHERE a = 10; SELECT * FROM update_test; -- fail, multi assignment to same column: UPDATE update_test SET (c,b) = ('car', a+b), b = a + 1WHERE a = 10;
-- uncorrelated sub-select: UPDATE update_test SET (b,a) = (select a,b from update_test where b = 41and c = 'car') WHERE a = 100AND b = 20; SELECT * FROM update_test; -- correlated sub-select: UPDATE update_test o SET (b,a) = (select a+1,b from update_test i where i.a=o.a and i.b=o.b and i.c isnotdistinctfrom o.c); SELECT * FROM update_test; -- fail, multiple rows supplied: UPDATE update_test SET (b,a) = (select a+1,b from update_test); -- set to null if no rows supplied: UPDATE update_test SET (b,a) = (select a+1,b from update_test where a = 1000) WHERE a = 11; SELECT * FROM update_test; -- *-expansion should work in this context: UPDATE update_test SET (a,b) = ROW(v.*) FROM (VALUES(21, 100)) AS v(i, j) WHERE update_test.a = v.i; -- you might expect this to work, but syntactically it's not a RowExpr: UPDATE update_test SET (a,b) = (v.*) FROM (VALUES(21, 101)) AS v(i, j) WHERE update_test.a = v.i;
-- if an alias for the target table is specified, don't allow references -- to the original table name UPDATE update_test AS t SET b = update_test.b + 10WHERE t.a = 10;
-- Make sure that we can update to a TOASTed value. UPDATE update_test SET c = repeat('x', 10000) WHERE c = 'car'; SELECT a, b, char_length(c) FROM update_test;
-- Check multi-assignment with a Result node to handle a one-time filter. EXPLAIN (VERBOSE, COSTS OFF) UPDATE update_test t SET (a, b) = (SELECT b, a FROM update_test s WHERE s.a = t.a) WHERECURRENT_USER = SESSION_USER; UPDATE update_test t SET (a, b) = (SELECT b, a FROM update_test s WHERE s.a = t.a) WHERECURRENT_USER = SESSION_USER; SELECT a, b, char_length(c) FROM update_test;
-- Test ON CONFLICT DO UPDATE
INSERTINTO upsert_test VALUES(1, 'Boo'), (3, 'Zoo'); -- uncorrelated sub-select: WITH aaa AS (SELECT1AS a, 'Foo'AS b) INSERTINTO upsert_test VALUES (1, 'Bar') ON CONFLICT(a)
DO UPDATESET (b, a) = (SELECT b, a FROM aaa) RETURNING *; -- correlated sub-select: INSERTINTO upsert_test VALUES (1, 'Baz'), (3, 'Zaz') ON CONFLICT(a)
DO UPDATESET (b, a) = (SELECT b || ', Correlated', a from upsert_test i WHERE i.a = upsert_test.a)
RETURNING *; -- correlated sub-select (EXCLUDED.* alias): INSERTINTO upsert_test VALUES (1, 'Bat'), (3, 'Zot') ON CONFLICT(a)
DO UPDATESET (b, a) = (SELECT b || ', Excluded', a from upsert_test i WHERE i.a = excluded.a)
RETURNING *;
-- ON CONFLICT using system attributes in RETURNING, testing both the -- inserting and updating paths. See bug report at: -- https://www.postgresql.org/message-id/73436355-6432-49B1-92ED-1FE4F7E7E100%40finefun.com.au INSERTINTO upsert_test VALUES (2, 'Beeble') ON CONFLICT(a)
DO UPDATESET (b, a) = (SELECT b || ', Excluded', a from upsert_test i WHERE i.a = excluded.a)
RETURNING tableoid::regclass, xmin = pg_current_xact_id()::xid AS xmin_correct, xmax = 0AS xmax_correct; -- currently xmax is set after a conflict - that's probably not good, -- but it seems worthwhile to have to be explicit if that changes. INSERTINTO upsert_test VALUES (2, 'Brox') ON CONFLICT(a)
DO UPDATESET (b, a) = (SELECT b || ', Excluded', a from upsert_test i WHERE i.a = excluded.a)
RETURNING tableoid::regclass, xmin = pg_current_xact_id()::xid AS xmin_correct, xmax = pg_current_xact_id()::xid AS xmax_correct;
DROPTABLE update_test; DROPTABLE upsert_test;
-- Test ON CONFLICT DO UPDATE with partitioned table and non-identical children
CREATETABLE upsert_test (
a INTPRIMARYKEY,
b TEXT
) PARTITION BY LIST (a);
CREATETABLE upsert_test_1 PARTITION OF upsert_test FORVALUESIN (1); CREATETABLE upsert_test_2 (b TEXT, a INTPRIMARYKEY); ALTERTABLE upsert_test ATTACH PARTITION upsert_test_2 FORVALUESIN (2);
INSERTINTO upsert_test VALUES(1, 'Boo'), (2, 'Zoo'); -- uncorrelated sub-select: WITH aaa AS (SELECT1AS a, 'Foo'AS b) INSERTINTO upsert_test VALUES (1, 'Bar') ON CONFLICT(a)
DO UPDATESET (b, a) = (SELECT b, a FROM aaa) RETURNING *; -- correlated sub-select: WITH aaa AS (SELECT1AS ctea, ' Foo'AS cteb) INSERTINTO upsert_test VALUES (1, 'Bar'), (2, 'Baz') ON CONFLICT(a)
DO UPDATESET (b, a) = (SELECT upsert_test.b||cteb, upsert_test.a FROM aaa) RETURNING *;
DROPTABLE upsert_test;
--------------------------- -- UPDATE with row movement ---------------------------
-- When a partitioned table receives an UPDATE to the partitioned key and the -- new values no longer meet the partition's bound, the row must be moved to -- the correct partition for the new partition key (if one exists). We must -- also ensure that updatable views on partitioned tables properly enforce any -- WITH CHECK OPTION that is defined. The situation with triggers in this case -- also requires thorough testing as partition key updates causing row -- movement convert UPDATEs into DELETE+INSERT.
CREATETABLE range_parted (
a text,
b bigint,
c numeric,
d int,
e varchar
) PARTITION BY RANGE (a, b);
-- Create partitions intentionally in descending bound order, so as to test -- that update-row-movement works with the leaf partitions not in bound order. CREATETABLE part_b_20_b_30 (e varchar, c numeric, a text, b bigint, d int); ALTERTABLE range_parted ATTACH PARTITION part_b_20_b_30 FORVALUESFROM ('b', 20) TO ('b', 30); CREATETABLE part_b_10_b_20 (e varchar, c numeric, a text, b bigint, d int) PARTITION BY RANGE (c); CREATETABLE part_b_1_b_10 PARTITION OF range_parted FORVALUESFROM ('b', 1) TO ('b', 10); ALTERTABLE range_parted ATTACH PARTITION part_b_10_b_20 FORVALUESFROM ('b', 10) TO ('b', 20); CREATETABLE part_a_10_a_20 PARTITION OF range_parted FORVALUESFROM ('a', 10) TO ('a', 20); CREATETABLE part_a_1_a_10 PARTITION OF range_parted FORVALUESFROM ('a', 1) TO ('a', 10);
-- Check that partition-key UPDATE works sanely on a partitioned table that -- does not have any child partitions. UPDATE part_b_10_b_20 set b = b - 6;
-- Create some more partitions following the above pattern of descending bound -- order, but let's make the situation a bit more complex by having the -- attribute numbers of the columns vary from their parent partition. CREATETABLE part_c_100_200 (e varchar, c numeric, a text, b bigint, d int) PARTITION BY range (abs(d)); ALTERTABLE part_c_100_200 DROPCOLUMN e, DROPCOLUMN c, DROPCOLUMN a; ALTERTABLE part_c_100_200 ADDCOLUMN c numeric, ADDCOLUMN e varchar, ADDCOLUMN a text; ALTERTABLE part_c_100_200 DROPCOLUMN b; ALTERTABLE part_c_100_200 ADDCOLUMN b bigint; CREATETABLE part_d_1_15 PARTITION OF part_c_100_200 FORVALUESFROM (1) TO (15); CREATETABLE part_d_15_20 PARTITION OF part_c_100_200 FORVALUESFROM (15) TO (20);
ALTERTABLE part_b_10_b_20 ATTACH PARTITION part_c_100_200 FORVALUESFROM (100) TO (200);
CREATETABLE part_c_1_100 (e varchar, d int, c numeric, b bigint, a text); ALTERTABLE part_b_10_b_20 ATTACH PARTITION part_c_1_100 FORVALUESFROM (1) TO (100);
-- The order of subplans should be in bound order EXPLAIN (costs off) UPDATE range_parted set c = c - 50WHERE c > 97;
-- fail, row movement happens only within the partition subtree. UPDATE part_c_100_200 set c = c - 20, d = c WHERE c = 105; -- fail, no partition key update, so no attempt to move tuple, -- but "a = 'a'" violates partition constraint enforced by root partition) UPDATE part_b_10_b_20 set a = 'a'; -- ok, partition key update, no constraint violation UPDATE range_parted set d = d - 10WHERE d > 10; -- ok, no partition key update, no constraint violation UPDATE range_parted set e = d; -- No row found UPDATE part_c_1_100 set c = c + 20WHERE c = 98; -- ok, row movement UPDATE part_b_10_b_20 set c = c + 20 returning c, b, a;
:show_data;
-- fail, row movement happens only within the partition subtree. UPDATE part_b_10_b_20 set b = b - 6WHERE c > 116 returning *; -- ok, row movement, with subset of rows moved into different partition. UPDATE range_parted set b = b - 6WHERE c > 116 returning a, b + c;
:show_data;
-- Common table needed for multiple test scenarios. CREATETABLE mintab(c1 int); INSERTinto mintab VALUES (120);
-- update partition key using updatable view. CREATE VIEW upview ASSELECT * FROM range_parted WHERE (select c > c1 FROM mintab) WITHCHECKOPTION; -- ok UPDATE upview set c = 199WHERE b = 4; -- fail, check option violation UPDATE upview set c = 120WHERE b = 4; -- fail, row movement with check option violation UPDATE upview set a = 'b', b = 15, c = 120WHERE b = 4; -- ok, row movement, check option passes UPDATE upview set a = 'b', b = 15WHERE b = 4;
:show_data;
-- cleanup DROP VIEW upview;
-- RETURNING having whole-row vars.
:init_range_parted; UPDATE range_parted set c = 95WHERE a = 'b'and b > 10and c > 100 returning (range_parted), *;
:show_data;
-- Transition tables with update row movement
:init_range_parted;
CREATE FUNCTION trans_updatetrigfunc() RETURNS trigger LANGUAGE plpgsql AS
$$
begin
raise notice 'trigger = %, old table = %, new table = %',
TG_NAME,
(select string_agg(old_table::text, ', 'ORDERBY a) FROM old_table),
(select string_agg(new_table::text, ', 'ORDERBY a) FROM new_table); returnnull;
end;
$$;
CREATETRIGGER trans_updatetrig
AFTER UPDATEON range_parted REFERENCING OLD TABLEAS old_table NEW TABLEAS new_table FOREACH STATEMENT EXECUTE PROCEDURE trans_updatetrigfunc();
UPDATE range_parted set c = (casewhen c = 96then110else c + 1 end ) WHERE a = 'b'and b > 10and c >= 96;
:show_data;
:init_range_parted;
-- Enabling OLD TABLE capture for both DELETE as well as UPDATE stmt triggers -- should not cause DELETEd rows to be captured twice. Similar thing for -- INSERT triggers and inserted rows. CREATETRIGGER trans_deletetrig
AFTER DELETEON range_parted REFERENCING OLD TABLEAS old_table FOREACH STATEMENT EXECUTE PROCEDURE trans_updatetrigfunc(); CREATETRIGGER trans_inserttrig
AFTER INSERTON range_parted REFERENCING NEW TABLEAS new_table FOREACH STATEMENT EXECUTE PROCEDURE trans_updatetrigfunc(); UPDATE range_parted set c = c + 50WHERE a = 'b'and b > 10and c >= 96;
:show_data; DROPTRIGGER trans_deletetrig ON range_parted; DROPTRIGGER trans_inserttrig ON range_parted; -- Don't drop trans_updatetrig yet. It is required below.
-- Test with transition tuple conversion happening for rows moved into the -- new partition. This requires a trigger that references transition table -- (we already have trans_updatetrig). For inserted rows, the conversion -- is not usually needed, because the original tuple is already compatible with -- the desired transition tuple format. But conversion happens when there is a -- BR trigger because the trigger can change the inserted row. So install a -- BR triggers on those child partitions where the rows will be moved. CREATE FUNCTION func_parted_mod_b() RETURNS triggerAS $$
BEGIN
NEW.b = NEW.b + 1; return NEW;
END $$ language plpgsql; CREATETRIGGER trig_c1_100 BEFOREUPDATEORINSERTON part_c_1_100 FOREACH ROW EXECUTE PROCEDURE func_parted_mod_b(); CREATETRIGGER trig_d1_15 BEFOREUPDATEORINSERTON part_d_1_15 FOREACH ROW EXECUTE PROCEDURE func_parted_mod_b(); CREATETRIGGER trig_d15_20 BEFOREUPDATEORINSERTON part_d_15_20 FOREACH ROW EXECUTE PROCEDURE func_parted_mod_b();
:init_range_parted; UPDATE range_parted set c = (casewhen c = 96then110else c + 1 end) WHERE a = 'b'and b > 10and c >= 96;
:show_data;
:init_range_parted; UPDATE range_parted set c = c + 50WHERE a = 'b'and b > 10and c >= 96;
:show_data;
-- Case where per-partition tuple conversion map array is allocated, but the -- map is not required for the particular tuple that is routed, thanks to -- matching table attributes of the partition and the target table.
:init_range_parted; UPDATE range_parted set b = 15WHERE b = 1;
:show_data;
DROPTRIGGER trans_updatetrig ON range_parted; DROPTRIGGER trig_c1_100 ON part_c_1_100; DROPTRIGGER trig_d1_15 ON part_d_1_15; DROPTRIGGER trig_d15_20 ON part_d_15_20; DROP FUNCTION func_parted_mod_b();
-- RLS policies with update-row-movement -----------------------------------------
ALTERTABLE range_parted ENABLE ROW LEVEL SECURITY; CREATE USER regress_range_parted_user; GRANTALLON range_parted, mintab TO regress_range_parted_user; CREATE POLICY seeall ON range_parted AS PERMISSIVE FORSELECTUSING (true); CREATE POLICY policy_range_parted ON range_parted forUPDATEUSING (true) WITHCHECK (c % 2 = 0);
:init_range_parted; SET SESSION AUTHORIZATION regress_range_parted_user; -- This should fail with RLS violation error while moving row from -- part_a_10_a_20 to part_d_1_15, because we are setting 'c' to an odd number. UPDATE range_parted set a = 'b', c = 151WHERE a = 'a'and c = 200;
RESET SESSION AUTHORIZATION; -- Create a trigger on part_d_1_15 CREATE FUNCTION func_d_1_15() RETURNS triggerAS $$
BEGIN
NEW.c = NEW.c + 1; -- Make even numbers odd, or vice versa return NEW;
END $$ LANGUAGE plpgsql; CREATETRIGGER trig_d_1_15 BEFOREINSERTON part_d_1_15 FOREACH ROW EXECUTE PROCEDURE func_d_1_15();
:init_range_parted; SET SESSION AUTHORIZATION regress_range_parted_user;
-- Here, RLS checks should succeed while moving row from part_a_10_a_20 to -- part_d_1_15. Even though the UPDATE is setting 'c' to an odd number, the -- trigger at the destination partition again makes it an even number. UPDATE range_parted set a = 'b', c = 151WHERE a = 'a'and c = 200;
RESET SESSION AUTHORIZATION;
:init_range_parted; SET SESSION AUTHORIZATION regress_range_parted_user; -- This should fail with RLS violation error. Even though the UPDATE is setting -- 'c' to an even number, the trigger at the destination partition again makes -- it an odd number. UPDATE range_parted set a = 'b', c = 150WHERE a = 'a'and c = 200;
-- Cleanup
RESET SESSION AUTHORIZATION; DROPTRIGGER trig_d_1_15 ON part_d_1_15; DROP FUNCTION func_d_1_15();
-- Policy expression contains SubPlan
RESET SESSION AUTHORIZATION;
:init_range_parted; CREATE POLICY policy_range_parted_subplan on range_parted AS RESTRICTIVE forUPDATEUSING (true) WITHCHECK ((SELECT range_parted.c <= c1 FROM mintab)); SET SESSION AUTHORIZATION regress_range_parted_user; -- fail, mintab has row with c1 = 120 UPDATE range_parted set a = 'b', c = 122WHERE a = 'a'and c = 200; -- ok UPDATE range_parted set a = 'b', c = 120WHERE a = 'a'and c = 200;
-- RLS policy expression contains whole row.
RESET SESSION AUTHORIZATION;
:init_range_parted; CREATE POLICY policy_range_parted_wholerow on range_parted AS RESTRICTIVE forUPDATEUSING (true) WITHCHECK (range_parted = row('b', 10, 112, 1, NULL)::range_parted); SET SESSION AUTHORIZATION regress_range_parted_user; -- ok, should pass the RLS check UPDATE range_parted set a = 'b', c = 112WHERE a = 'a'and c = 200;
RESET SESSION AUTHORIZATION;
:init_range_parted; SET SESSION AUTHORIZATION regress_range_parted_user; -- fail, the whole row RLS check should fail UPDATE range_parted set a = 'b', c = 116WHERE a = 'a'and c = 200;
-- Cleanup
RESET SESSION AUTHORIZATION; DROP POLICY policy_range_parted ON range_parted; DROP POLICY policy_range_parted_subplan ON range_parted; DROP POLICY policy_range_parted_wholerow ON range_parted; REVOKEALLON range_parted, mintab FROM regress_range_parted_user; DROP USER regress_range_parted_user; DROPTABLE mintab;
-- statement triggers with update row movement ---------------------------------------------------
:init_range_parted;
CREATE FUNCTION trigfunc() returns trigger language plpgsql as
$$
begin
raise notice 'trigger = % fired on table % during %',
TG_NAME, TG_TABLE_NAME, TG_OP; returnnull;
end;
$$; -- Triggers on root partition CREATETRIGGER parent_delete_trig
AFTER DELETEON range_parted foreach statement execute procedure trigfunc(); CREATETRIGGER parent_update_trig
AFTER UPDATEON range_parted foreach statement execute procedure trigfunc(); CREATETRIGGER parent_insert_trig
AFTER INSERTON range_parted foreach statement execute procedure trigfunc();
-- Triggers on leaf partition part_c_1_100 CREATETRIGGER c1_delete_trig
AFTER DELETEON part_c_1_100 foreach statement execute procedure trigfunc(); CREATETRIGGER c1_update_trig
AFTER UPDATEON part_c_1_100 foreach statement execute procedure trigfunc(); CREATETRIGGER c1_insert_trig
AFTER INSERTON part_c_1_100 foreach statement execute procedure trigfunc();
-- Triggers on leaf partition part_d_1_15 CREATETRIGGER d1_delete_trig
AFTER DELETEON part_d_1_15 foreach statement execute procedure trigfunc(); CREATETRIGGER d1_update_trig
AFTER UPDATEON part_d_1_15 foreach statement execute procedure trigfunc(); CREATETRIGGER d1_insert_trig
AFTER INSERTON part_d_1_15 foreach statement execute procedure trigfunc(); -- Triggers on leaf partition part_d_15_20 CREATETRIGGER d15_delete_trig
AFTER DELETEON part_d_15_20 foreach statement execute procedure trigfunc(); CREATETRIGGER d15_update_trig
AFTER UPDATEON part_d_15_20 foreach statement execute procedure trigfunc(); CREATETRIGGER d15_insert_trig
AFTER INSERTON part_d_15_20 foreach statement execute procedure trigfunc();
-- Move all rows from part_c_100_200 to part_c_1_100. None of the delete or -- insert statement triggers should be fired. UPDATE range_parted set c = c - 50WHERE c > 97;
:show_data;
DROPTRIGGER parent_delete_trig ON range_parted; DROPTRIGGER parent_update_trig ON range_parted; DROPTRIGGER parent_insert_trig ON range_parted; DROPTRIGGER c1_delete_trig ON part_c_1_100; DROPTRIGGER c1_update_trig ON part_c_1_100; DROPTRIGGER c1_insert_trig ON part_c_1_100; DROPTRIGGER d1_delete_trig ON part_d_1_15; DROPTRIGGER d1_update_trig ON part_d_1_15; DROPTRIGGER d1_insert_trig ON part_d_1_15; DROPTRIGGER d15_delete_trig ON part_d_15_20; DROPTRIGGER d15_update_trig ON part_d_15_20; DROPTRIGGER d15_insert_trig ON part_d_15_20;
-- Creating default partition for range
:init_range_parted; createtable part_def partition of range_parted default;
\d+ part_def insertinto range_parted values ('c', 9); -- ok update part_def set a = 'd'where a = 'c'; -- fail update part_def set a = 'a'where a = 'd';
:show_data;
-- Update row movement from non-default to default partition. -- fail, default partition is not under part_a_10_a_20; UPDATE part_a_10_a_20 set a = 'ad'WHERE a = 'a'; -- ok UPDATE range_parted set a = 'ad'WHERE a = 'a'; UPDATE range_parted set a = 'bd'WHERE a = 'b';
:show_data; -- Update row movement from default to non-default partitions. -- ok UPDATE range_parted set a = 'a'WHERE a = 'ad'; UPDATE range_parted set a = 'b'WHERE a = 'bd';
:show_data;
-- Cleanup: range_parted no longer needed. DROPTABLE range_parted;
CREATETABLE list_parted (
a text,
b int
) PARTITION BY list (a); CREATETABLE list_part1 PARTITION OF list_parted forVALUESin ('a', 'b'); CREATETABLE list_default PARTITION OF list_parted default; INSERTinto list_part1 VALUES ('a', 1); INSERTinto list_default VALUES ('d', 10);
-- fail UPDATE list_default set a = 'a'WHERE a = 'd'; -- ok UPDATE list_default set a = 'x'WHERE a = 'd';
DROPTABLE list_parted;
-- Test retrieval of system columns with non-consistent partition row types. -- This is only partially supported, as seen in the results.
createtable utrtest (a int, b text) partition by list (a); createtable utr1 (a intcheck (a in (1)), q text, b text); createtable utr2 (a intcheck (a in (2)), b text); altertable utr1 dropcolumn q; altertable utrtest attach partition utr1 forvaluesin (1); altertable utrtest attach partition utr2 forvaluesin (2);
update utrtest set b = b || b from (values (1), (2)) s(x) where a = s.x
returning *, tableoid::regclass, xmin = pg_current_xact_id()::xid as xmin_ok;
update utrtest set a = 3 - a from (values (1), (2)) s(x) where a = s.x
returning *, tableoid::regclass, xmin = pg_current_xact_id()::xid as xmin_ok; -- fails
update utrtest set a = 3 - a from (values (1), (2)) s(x) where a = s.x
returning *, tableoid::regclass;
deletefrom utrtest
returning *, tableoid::regclass, xmax = pg_current_xact_id()::xid as xmax_ok;
droptable utrtest;
-------------- -- Some more update-partition-key test scenarios below. This time use list -- partitions. --------------
-- Setup for list partitions CREATETABLE list_parted (a numeric, b int, c int8) PARTITION BY list (a); CREATETABLE sub_parted PARTITION OF list_parted forVALUESin (1) PARTITION BY list (b);
CREATETABLE sub_part1(b int, c int8, a numeric); ALTERTABLE sub_parted ATTACH PARTITION sub_part1 forVALUESin (1); CREATETABLE sub_part2(b int, c int8, a numeric); ALTERTABLE sub_parted ATTACH PARTITION sub_part2 forVALUESin (2);
CREATETABLE list_part1(a numeric, b int, c int8); ALTERTABLE list_parted ATTACH PARTITION list_part1 forVALUESin (2,3);
-- Test partition constraint violation when intermediate ancestor is used and -- constraint is inherited from upper root. UPDATE sub_parted set a = 2WHERE c = 10;
-- Test update-partition-key, where the unpruned partitions do not have their -- partition keys updated. SELECT tableoid::regclass::text, * FROM list_parted WHERE a = 2ORDERBY1; UPDATE list_parted set b = c + a WHERE a = 2; SELECT tableoid::regclass::text, * FROM list_parted WHERE a = 2ORDERBY1;
-- Test the case where BR UPDATE triggers change the partition key. CREATE FUNCTION func_parted_mod_b() returns triggeras $$
BEGIN
NEW.b = 2; -- This is changing partition key column. return NEW;
END $$ LANGUAGE plpgsql; CREATETRIGGER parted_mod_b beforeupdateon sub_part1 foreach row execute procedure func_parted_mod_b();
SELECT tableoid::regclass::text, * FROM list_parted ORDERBY1, 2, 3, 4;
-- This should do the tuple routing even though there is no explicit -- partition-key update, because there is a trigger on sub_part1. UPDATE list_parted set c = 70WHERE b = 1; SELECT tableoid::regclass::text, * FROM list_parted ORDERBY1, 2, 3, 4;
DROPTRIGGER parted_mod_b ON sub_part1;
-- If BR DELETE trigger prevented DELETE from happening, we should also skip -- the INSERT if that delete is part of UPDATE=>DELETE+INSERT. CREATEORREPLACE FUNCTION func_parted_mod_b() returns triggeras $$
BEGIN
raise notice 'Trigger: Got OLD row %, but returning NULL', OLD; returnNULL;
END $$ LANGUAGE plpgsql; CREATETRIGGER trig_skip_delete beforedeleteon sub_part2 foreach row execute procedure func_parted_mod_b(); UPDATE list_parted set b = 1WHERE c = 70; SELECT tableoid::regclass::text, * FROM list_parted ORDERBY1, 2, 3, 4; -- Drop the trigger. Now the row should be moved. DROPTRIGGER trig_skip_delete ON sub_part2; UPDATE list_parted set b = 1WHERE c = 70; SELECT tableoid::regclass::text, * FROM list_parted ORDERBY1, 2, 3, 4; DROP FUNCTION func_parted_mod_b();
-- UPDATE partition-key with FROM clause. If join produces multiple output -- rows for the same row to be modified, we should tuple-route the row only -- once. There should not be any rows inserted. CREATETABLE non_parted (id int); INSERTinto non_parted VALUES (1), (1), (1), (2), (2), (2), (3), (3), (3); UPDATE list_parted t1 set a = 2FROM non_parted t2 WHERE t1.a = t2.id and a = 1; SELECT tableoid::regclass::text, * FROM list_parted ORDERBY1, 2, 3, 4; DROPTABLE non_parted;
-- Cleanup: list_parted no longer needed. DROPTABLE list_parted;
-- create custom operator class and hash function, for the same reason -- explained in alter_table.sql createorreplace function dummy_hashint4(a int4, seed int8) returns int8as
$$ begin return (a + seed); end; $$ language 'plpgsql' immutable; create operator class custom_opclass for type int4using hash as
operator 1 = , function 2 dummy_hashint4(int4, int8);
createtable hash_parted (
a int,
b int
) partition by hash (a custom_opclass, b custom_opclass); createtable hpart1 partition of hash_parted forvalueswith (modulus 2, remainder 1); createtable hpart2 partition of hash_parted forvalueswith (modulus 4, remainder 2); createtable hpart3 partition of hash_parted forvalueswith (modulus 8, remainder 0); createtable hpart4 partition of hash_parted forvalueswith (modulus 8, remainder 4); insertinto hpart1 values (1, 1); insertinto hpart2 values (2, 5); insertinto hpart4 values (3, 4);
-- fail update hpart1 set a = 3, b=4where a = 1; -- ok, row movement update hash_parted set b = b - 1where b = 1; -- ok update hash_parted set b = b + 8where b = 1;
-- cleanup droptable hash_parted; drop operator class custom_opclass using hash; drop function dummy_hashint4(a int4, seed int8);
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.16 Sekunden
(vorverarbeitet am 2026-08-10)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.