=== Applying patches on top of PostgreSQL commit ID 952bab8b74d40fcfd25e1949865c3907e676504e === /etc/rc.d/jail: WARNING: Per-jail configuration via jail_* variables is obsolete. Please consider migrating to /etc/jail.conf. Tue Nov 10 17:28:30 UTC 2026 On branch cf/7452 nothing to commit, working tree clean === using 'git am' to apply patch ./v3-0001-Add-tests-for-deparsing-record-comparisons-of-ROW.patch === Applying: Add tests for deparsing record comparisons of ROW()s Using index info to reconstruct a base tree... M src/test/regress/expected/rowtypes.out M src/test/regress/sql/rowtypes.sql Falling back to patching base and 3-way merge... Auto-merging src/test/regress/sql/rowtypes.sql CONFLICT (content): Merge conflict in src/test/regress/sql/rowtypes.sql Auto-merging src/test/regress/expected/rowtypes.out CONFLICT (content): Merge conflict in src/test/regress/expected/rowtypes.out error: Failed to merge in the changes. hint: Use 'git am --show-current-patch=diff' to see the failed patch Patch failed at 0001 Add tests for deparsing record comparisons of ROW()s When you have resolved this problem, run "git am --continue". If you prefer to skip this patch, run "git am --skip" instead. To restore the original branch and stop patching, run "git am --abort". === using patch(1) to apply patch ./v3-0001-Add-tests-for-deparsing-record-comparisons-of-ROW.patch === patching file src/test/regress/expected/rowtypes.out Hunk #1 FAILED at 1408. 1 out of 1 hunk FAILED -- saving rejects to file src/test/regress/expected/rowtypes.out.rej patching file src/test/regress/sql/rowtypes.sql Hunk #1 FAILED at 562. 1 out of 1 hunk FAILED -- saving rejects to file src/test/regress/sql/rowtypes.sql.rej Removing src/test/regress/expected/rowtypes.out.rej Removing src/test/regress/sql/rowtypes.sql.rej === using 'git apply' to apply patch ./v3-0001-Add-tests-for-deparsing-record-comparisons-of-ROW.patch === Applied patch to 'src/test/regress/expected/rowtypes.out' with conflicts. Applied patch to 'src/test/regress/sql/rowtypes.sql' with conflicts. U src/test/regress/expected/rowtypes.out U src/test/regress/sql/rowtypes.sql diff --cc src/test/regress/expected/rowtypes.out index c402160d488,16a735df0ba..00000000000 --- a/src/test/regress/expected/rowtypes.out +++ b/src/test/regress/expected/rowtypes.out @@@ -1408,175 -1408,73 +1408,248 @@@ ERROR: column "oid" not found in data LINE 1: SELECT (NULL::compositetable).oid; ^ DROP TABLE compositetable; ++<<<<<<< ours +-- +-- Test for mutating the composite type while it's used in a FieldStore +-- +-- Table, which we'll mutate concurrently with a updating an existing value +-- of that type. We'll add BIGINT columns, which are unlikely to crash but +-- could leak some data, and then TEXT columns, which are more likely to +-- cause crashes. +CREATE TABLE compositetable_type (a int); +CREATE TABLE compositetable_holder (id int, c compositetable_type); +INSERT INTO compositetable_holder VALUES (1, ROW(1)::compositetable_type); +CREATE FUNCTION compositetable_grow() RETURNS int VOLATILE LANGUAGE plpgsql AS $$ +BEGIN + FOR i IN 1..100 LOOP + EXECUTE format('ALTER TABLE compositetable_type ADD COLUMN x%s bigint', i); + END LOOP; + RETURN 7; +END $$; +UPDATE compositetable_holder SET c.a = abs(compositetable_grow()); +SELECT c::text FROM compositetable_holder; + c +--------------------------------------------------------------------------------------------------------- + (7,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,) +(1 row) + +CREATE OR REPLACE FUNCTION compositetable_grow() RETURNS int VOLATILE LANGUAGE plpgsql AS $$ +BEGIN + FOR i IN 1..100 LOOP + EXECUTE format('ALTER TABLE compositetable_type ADD COLUMN y%s text', i); + END LOOP; + RETURN 7; +END $$; +UPDATE compositetable_holder SET c.a = abs(compositetable_grow()); +SELECT c::text FROM compositetable_holder; + c +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + (7,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,) +(1 row) + +DROP FUNCTION compositetable_grow(); +DROP TABLE compositetable_holder; +DROP TABLE compositetable_type; +-- Similar pattern, but we'll have an array of the composite type, so that +-- we reach the code through SubscriptingRef. +CREATE TABLE compositetable_type (a int); +CREATE TABLE compositetable_holder (id int, c compositetable_type[]); +INSERT INTO compositetable_holder VALUES (1, ARRAY[ROW(1)]::compositetable_type[]); +CREATE FUNCTION compositetable_grow() RETURNS int VOLATILE LANGUAGE plpgsql AS $$ +BEGIN + FOR i IN 1..100 LOOP + EXECUTE format('ALTER TABLE compositetable_type ADD COLUMN y%s text', i); + END LOOP; + RETURN 7; +END $$; +UPDATE compositetable_holder SET c[1].a = abs(compositetable_grow()); +SELECT c::text FROM compositetable_holder; + c +------------------------------------------------------------------------------------------------------------- + {"(7,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,)"} +(1 row) + +DROP FUNCTION compositetable_grow(); +DROP TABLE compositetable_holder; +DROP TABLE compositetable_type; +-- This time we start with a NULL value, and call the function from a WHERE +-- condition, to trigger the NULL case in ExecEvalFieldStoreDeForm. +CREATE TABLE compositetable_type (a int); +CREATE TABLE compositetable_holder (id int, c compositetable_type); +INSERT INTO compositetable_holder VALUES (1, NULL); +CREATE FUNCTION compositetable_grow() RETURNS int VOLATILE LANGUAGE plpgsql AS $$ +BEGIN + FOR i IN 1..100 LOOP + EXECUTE format('ALTER TABLE compositetable_type ADD COLUMN y%s text', i); + END LOOP; + RETURN 7; +END $$; +-- This time we start with a NULL value to trigger the NULL case in +-- ExecEvalFieldStoreDeForm. Mutate the type from the WHERE clause, +-- because the new value won't be evaluated when starting from a NULL. +UPDATE compositetable_holder SET c.a = 1 WHERE abs(compositetable_grow()) > 5; +SELECT c::text FROM compositetable_holder; + c +--------------------------------------------------------------------------------------------------------- + (1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,) +(1 row) + +DROP FUNCTION compositetable_grow(); +DROP TABLE compositetable_holder; +DROP TABLE compositetable_type; +-- Two nested composite types. +CREATE TABLE compositetable_type_1 (a int); +CREATE TABLE compositetable_type_2 (id int, b compositetable_type_1); +CREATE TABLE compositetable_holder (id int, c compositetable_type_2); +INSERT INTO compositetable_holder VALUES (1, ROW(1, ROW(1)::compositetable_type_1)::compositetable_type_2); +CREATE FUNCTION compositetable_grow() RETURNS int VOLATILE LANGUAGE plpgsql AS $$ +BEGIN + FOR i IN 1..100 LOOP + EXECUTE format('ALTER TABLE compositetable_type_1 ADD COLUMN y%s text', i); + END LOOP; + RETURN 7; +END $$; +UPDATE compositetable_holder SET c.b.a = abs(compositetable_grow()); +SELECT c::text FROM compositetable_holder; + c +--------------------------------------------------------------------------------------------------------------- + (1,"(7,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,)") +(1 row) + +DROP FUNCTION compositetable_grow(); +DROP TABLE compositetable_holder; +DROP TABLE compositetable_type_2; +DROP TABLE compositetable_type_1; +-- removing an attribute from a composite type +CREATE TABLE compositetable_type_1 (a int, b int); +CREATE TABLE compositetable_holder (v compositetable_type_1); +INSERT INTO compositetable_holder VALUES (ROW(1, 2)::compositetable_type_1); +CREATE FUNCTION compositetable_drop() RETURNS int LANGUAGE plpgsql AS $$ +BEGIN + EXECUTE 'ALTER TABLE compositetable_type_1 DROP COLUMN b'; + RETURN 12345; +END $$; +-- assign to v.b while b is dropped - the value disappears +UPDATE compositetable_holder SET v.b = compositetable_drop(); +-- 12345 is nowhere to be found +SELECT v::text AS stored_value FROM compositetable_holder; + stored_value +-------------- + (1) +(1 row) + +DROP FUNCTION compositetable_drop(); +DROP TABLE compositetable_holder; +DROP TABLE compositetable_type_1; +-- +-- Tests for expanded records built from a flat composite datum, which core +-- code never does, see make_expanded_record_from_datum(). +-- +CREATE TYPE expanded_comp AS (a int, b text); +-- A named rowtype must keep typmod -1, a RECORD must keep its blessed typmod. +SELECT expanded_record_info(ROW(1, 'named')::expanded_comp); + expanded_record_info +----------------------------------------------------- + type expanded_comp, typmod is default: yes, natts 2 +(1 row) + +SELECT expanded_record_info(ROW(2, 'anonymous')); + expanded_record_info +--------------------------------------------- + type record, typmod is default: no, natts 2 +(1 row) + +-- Round-tripping through an expanded record must preserve the value. +SELECT expanded_record_identity(ROW(3, 'roundtrip')::expanded_comp); + expanded_record_identity +-------------------------- + (3,roundtrip) +(1 row) + +SELECT expanded_record_identity(ROW(4, 'anonymous roundtrip')); + expanded_record_identity +--------------------------- + (4,"anonymous roundtrip") +(1 row) + +-- Reading a field forces a deform against the resolved descriptor. +SELECT expanded_record_field_text(ROW(5, 'field')::expanded_comp, 1), + expanded_record_field_text(ROW(5, 'field')::expanded_comp, 2); + expanded_record_field_text | expanded_record_field_text +----------------------------+---------------------------- + 5 | field +(1 row) + +DROP TYPE expanded_comp; ++======= + -- A record operator between ROW()s cast to record must not be read back as a + -- row comparison, which gives NULL here + CREATE VIEW rowtypes_cmp_v1 AS + SELECT (ROW(1, NULL::int)::record = ROW(1, NULL::int)::record) AS r; + SELECT pg_get_viewdef('rowtypes_cmp_v1'::regclass); + pg_get_viewdef + --------------------------------------------------------------- + SELECT (ROW(1, NULL::integer) = ROW(1, NULL::integer)) AS r; + (1 row) + + -- Nor as a row comparison with the columns of a scalar subquery + CREATE VIEW rowtypes_cmp_v2 AS + SELECT (ROW(1, NULL::int)::record = (SELECT ROW(1, NULL::int)::record)) AS r; + SELECT pg_get_viewdef('rowtypes_cmp_v2'::regclass); + pg_get_viewdef + ---------------------------------------------------------------------------------- + SELECT (ROW(1, NULL::integer) = ( SELECT ROW(1, NULL::integer) AS "row")) AS r; + (1 row) + + -- The parser expands a whole-row reference to a JOIN USING alias into a ROW() + CREATE TABLE rowtypes_cmp_a (x int); + CREATE TABLE rowtypes_cmp_b (x int); + CREATE VIEW rowtypes_cmp_v3 AS + SELECT x, (j.* = (SELECT ROW(1))) AS r + FROM rowtypes_cmp_a JOIN rowtypes_cmp_b USING (x) AS j; + SELECT pg_get_viewdef('rowtypes_cmp_v3'::regclass); + pg_get_viewdef + -------------------------------------------------------------- + SELECT rowtypes_cmp_a.x, + + (ROW(rowtypes_cmp_a.x) = ( SELECT ROW(1) AS "row")) AS r+ + FROM (rowtypes_cmp_a + + JOIN rowtypes_cmp_b USING (x) AS j); + (1 row) + + -- So does coercing a whole-row reference to an operator's composite input + -- type + CREATE TYPE rowtypes_cmp_t AS (a int, b int); + CREATE FUNCTION rowtypes_cmp_eq(rowtypes_cmp_t, rowtypes_cmp_t) RETURNS bool + LANGUAGE sql IMMUTABLE + AS 'SELECT $1.a IS NOT DISTINCT FROM $2.a AND $1.b IS NOT DISTINCT FROM $2.b'; + CREATE OPERATOR =#= (LEFTARG = rowtypes_cmp_t, RIGHTARG = rowtypes_cmp_t, + FUNCTION = rowtypes_cmp_eq); + CREATE VIEW rowtypes_cmp_v4 AS + SELECT (s1 =#= s2) AS r + FROM (SELECT 1 AS a, NULL::int AS b) s1, (SELECT 1, NULL::int) s2; + SELECT pg_get_viewdef('rowtypes_cmp_v4'::regclass); + pg_get_viewdef + ---------------------------------------------------------------- + SELECT (ROW(s1.a, s1.b) =#= ROW(s2."?column?", s2.int4)) AS r+ + FROM ( SELECT 1 AS a, + + NULL::integer AS b) s1, + + ( SELECT 1 AS "?column?", + + NULL::integer AS int4) s2; + (1 row) + + -- A generated column reaches the same code through pg_get_expr() + CREATE TABLE rowtypes_cmp_g (a int, + r bool GENERATED ALWAYS AS + (ROW(a, NULL::int)::record = ROW(a, NULL::int)::record) STORED); + SELECT pg_get_expr(adbin, adrelid) FROM pg_attrdef + WHERE adrelid = 'rowtypes_cmp_g'::regclass; + pg_get_expr + ------------------------------------------------- + (ROW(a, NULL::integer) = ROW(a, NULL::integer)) + (1 row) + + -- leave the objects around for the pg_upgrade test, except the ones that do + -- not survive dump and restore yet + DROP VIEW rowtypes_cmp_v1, rowtypes_cmp_v2, rowtypes_cmp_v3, rowtypes_cmp_v4; + DROP TABLE rowtypes_cmp_g; ++>>>>>>> theirs diff --cc src/test/regress/sql/rowtypes.sql index 5aef9f5a3c3,f09b7211987..00000000000 --- a/src/test/regress/sql/rowtypes.sql +++ b/src/test/regress/sql/rowtypes.sql @@@ -563,154 -563,46 +563,200 @@@ SELECT (NULL::compositetable).oid DROP TABLE compositetable; ++<<<<<<< ours +-- +-- Test for mutating the composite type while it's used in a FieldStore +-- + +-- Table, which we'll mutate concurrently with a updating an existing value +-- of that type. We'll add BIGINT columns, which are unlikely to crash but +-- could leak some data, and then TEXT columns, which are more likely to +-- cause crashes. +CREATE TABLE compositetable_type (a int); +CREATE TABLE compositetable_holder (id int, c compositetable_type); +INSERT INTO compositetable_holder VALUES (1, ROW(1)::compositetable_type); + +CREATE FUNCTION compositetable_grow() RETURNS int VOLATILE LANGUAGE plpgsql AS $$ +BEGIN + FOR i IN 1..100 LOOP + EXECUTE format('ALTER TABLE compositetable_type ADD COLUMN x%s bigint', i); + END LOOP; + RETURN 7; +END $$; + +UPDATE compositetable_holder SET c.a = abs(compositetable_grow()); + +SELECT c::text FROM compositetable_holder; + +CREATE OR REPLACE FUNCTION compositetable_grow() RETURNS int VOLATILE LANGUAGE plpgsql AS $$ +BEGIN + FOR i IN 1..100 LOOP + EXECUTE format('ALTER TABLE compositetable_type ADD COLUMN y%s text', i); + END LOOP; + RETURN 7; +END $$; + +UPDATE compositetable_holder SET c.a = abs(compositetable_grow()); + +SELECT c::text FROM compositetable_holder; + +DROP FUNCTION compositetable_grow(); +DROP TABLE compositetable_holder; +DROP TABLE compositetable_type; + +-- Similar pattern, but we'll have an array of the composite type, so that +-- we reach the code through SubscriptingRef. +CREATE TABLE compositetable_type (a int); +CREATE TABLE compositetable_holder (id int, c compositetable_type[]); +INSERT INTO compositetable_holder VALUES (1, ARRAY[ROW(1)]::compositetable_type[]); + +CREATE FUNCTION compositetable_grow() RETURNS int VOLATILE LANGUAGE plpgsql AS $$ +BEGIN + FOR i IN 1..100 LOOP + EXECUTE format('ALTER TABLE compositetable_type ADD COLUMN y%s text', i); + END LOOP; + RETURN 7; +END $$; + +UPDATE compositetable_holder SET c[1].a = abs(compositetable_grow()); + +SELECT c::text FROM compositetable_holder; + +DROP FUNCTION compositetable_grow(); +DROP TABLE compositetable_holder; +DROP TABLE compositetable_type; + +-- This time we start with a NULL value, and call the function from a WHERE +-- condition, to trigger the NULL case in ExecEvalFieldStoreDeForm. +CREATE TABLE compositetable_type (a int); +CREATE TABLE compositetable_holder (id int, c compositetable_type); +INSERT INTO compositetable_holder VALUES (1, NULL); + +CREATE FUNCTION compositetable_grow() RETURNS int VOLATILE LANGUAGE plpgsql AS $$ +BEGIN + FOR i IN 1..100 LOOP + EXECUTE format('ALTER TABLE compositetable_type ADD COLUMN y%s text', i); + END LOOP; + RETURN 7; +END $$; + +-- This time we start with a NULL value to trigger the NULL case in +-- ExecEvalFieldStoreDeForm. Mutate the type from the WHERE clause, +-- because the new value won't be evaluated when starting from a NULL. +UPDATE compositetable_holder SET c.a = 1 WHERE abs(compositetable_grow()) > 5; + +SELECT c::text FROM compositetable_holder; + +DROP FUNCTION compositetable_grow(); +DROP TABLE compositetable_holder; +DROP TABLE compositetable_type; + +-- Two nested composite types. +CREATE TABLE compositetable_type_1 (a int); +CREATE TABLE compositetable_type_2 (id int, b compositetable_type_1); +CREATE TABLE compositetable_holder (id int, c compositetable_type_2); +INSERT INTO compositetable_holder VALUES (1, ROW(1, ROW(1)::compositetable_type_1)::compositetable_type_2); + +CREATE FUNCTION compositetable_grow() RETURNS int VOLATILE LANGUAGE plpgsql AS $$ +BEGIN + FOR i IN 1..100 LOOP + EXECUTE format('ALTER TABLE compositetable_type_1 ADD COLUMN y%s text', i); + END LOOP; + RETURN 7; +END $$; + +UPDATE compositetable_holder SET c.b.a = abs(compositetable_grow()); + +SELECT c::text FROM compositetable_holder; + +DROP FUNCTION compositetable_grow(); +DROP TABLE compositetable_holder; +DROP TABLE compositetable_type_2; +DROP TABLE compositetable_type_1; + +-- removing an attribute from a composite type +CREATE TABLE compositetable_type_1 (a int, b int); +CREATE TABLE compositetable_holder (v compositetable_type_1); +INSERT INTO compositetable_holder VALUES (ROW(1, 2)::compositetable_type_1); + +CREATE FUNCTION compositetable_drop() RETURNS int LANGUAGE plpgsql AS $$ +BEGIN + EXECUTE 'ALTER TABLE compositetable_type_1 DROP COLUMN b'; + RETURN 12345; +END $$; + +-- assign to v.b while b is dropped - the value disappears +UPDATE compositetable_holder SET v.b = compositetable_drop(); + +-- 12345 is nowhere to be found +SELECT v::text AS stored_value FROM compositetable_holder; + +DROP FUNCTION compositetable_drop(); +DROP TABLE compositetable_holder; +DROP TABLE compositetable_type_1; + +-- +-- Tests for expanded records built from a flat composite datum, which core +-- code never does, see make_expanded_record_from_datum(). +-- + +CREATE TYPE expanded_comp AS (a int, b text); + +-- A named rowtype must keep typmod -1, a RECORD must keep its blessed typmod. +SELECT expanded_record_info(ROW(1, 'named')::expanded_comp); +SELECT expanded_record_info(ROW(2, 'anonymous')); + +-- Round-tripping through an expanded record must preserve the value. +SELECT expanded_record_identity(ROW(3, 'roundtrip')::expanded_comp); +SELECT expanded_record_identity(ROW(4, 'anonymous roundtrip')); + +-- Reading a field forces a deform against the resolved descriptor. +SELECT expanded_record_field_text(ROW(5, 'field')::expanded_comp, 1), + expanded_record_field_text(ROW(5, 'field')::expanded_comp, 2); + +DROP TYPE expanded_comp; ++======= + -- A record operator between ROW()s cast to record must not be read back as a + -- row comparison, which gives NULL here + CREATE VIEW rowtypes_cmp_v1 AS + SELECT (ROW(1, NULL::int)::record = ROW(1, NULL::int)::record) AS r; + SELECT pg_get_viewdef('rowtypes_cmp_v1'::regclass); + + -- Nor as a row comparison with the columns of a scalar subquery + CREATE VIEW rowtypes_cmp_v2 AS + SELECT (ROW(1, NULL::int)::record = (SELECT ROW(1, NULL::int)::record)) AS r; + SELECT pg_get_viewdef('rowtypes_cmp_v2'::regclass); + + -- The parser expands a whole-row reference to a JOIN USING alias into a ROW() + CREATE TABLE rowtypes_cmp_a (x int); + CREATE TABLE rowtypes_cmp_b (x int); + CREATE VIEW rowtypes_cmp_v3 AS + SELECT x, (j.* = (SELECT ROW(1))) AS r + FROM rowtypes_cmp_a JOIN rowtypes_cmp_b USING (x) AS j; + SELECT pg_get_viewdef('rowtypes_cmp_v3'::regclass); + + -- So does coercing a whole-row reference to an operator's composite input + -- type + CREATE TYPE rowtypes_cmp_t AS (a int, b int); + CREATE FUNCTION rowtypes_cmp_eq(rowtypes_cmp_t, rowtypes_cmp_t) RETURNS bool + LANGUAGE sql IMMUTABLE + AS 'SELECT $1.a IS NOT DISTINCT FROM $2.a AND $1.b IS NOT DISTINCT FROM $2.b'; + CREATE OPERATOR =#= (LEFTARG = rowtypes_cmp_t, RIGHTARG = rowtypes_cmp_t, + FUNCTION = rowtypes_cmp_eq); + CREATE VIEW rowtypes_cmp_v4 AS + SELECT (s1 =#= s2) AS r + FROM (SELECT 1 AS a, NULL::int AS b) s1, (SELECT 1, NULL::int) s2; + SELECT pg_get_viewdef('rowtypes_cmp_v4'::regclass); + + -- A generated column reaches the same code through pg_get_expr() + CREATE TABLE rowtypes_cmp_g (a int, + r bool GENERATED ALWAYS AS + (ROW(a, NULL::int)::record = ROW(a, NULL::int)::record) STORED); + SELECT pg_get_expr(adbin, adrelid) FROM pg_attrdef + WHERE adrelid = 'rowtypes_cmp_g'::regclass; + + -- leave the objects around for the pg_upgrade test, except the ones that do + -- not survive dump and restore yet + DROP VIEW rowtypes_cmp_v1, rowtypes_cmp_v2, rowtypes_cmp_v3, rowtypes_cmp_v4; + DROP TABLE rowtypes_cmp_g; ++>>>>>>> theirs