yugabyte / yugabyte-db

YugabyteDB - the cloud native distributed SQL database for mission-critical applications.
https://www.yugabyte.com
Other
8.88k stars 1.05k forks source link

[YSQL] ON CONFLICT UPDATE on partitioned primary key column crashes #14985

Open emhna opened 1 year ago

emhna commented 1 year ago

Jira Link: DB-4234

Description

CREATE TABLE p (a INT PRIMARY KEY, b TEXT) PARTITION BY LIST (a);
CREATE TABLE p1 (b TEXT, a INT PRIMARY KEY);
ALTER TABLE p ATTACH PARTITION p1 FOR VALUES IN (1);
INSERT INTO p (a, b) VALUES (1, 'foo');
INSERT INTO p (a, b) VALUES (1, 'bar') ON CONFLICT (a) DO UPDATE SET a=1;
> server closed the connection unexpectedly

The second insertion statement causes a conflict because a is a primary key so inserting (1,'bar') tuple violates the uniqueness of a. This should trigger the "on-conflict" portion of the statement which tries to update the row by setting a to 1. In postgres, this statement succeeds (although it's a redundant work since the tuple already has a set to 1) but in YB it crashes.

Looking at the stack trace, the following APIs get invoked:

YBCExecuteUpdate -> heap_getattr -> fastgetattr -> nocachegetattr -> att_addlength_pointer

The att_addlength_pointer is a macro API and fails with the following error: postgresExceptionalCondition(conditionName="1", errorType="unrecognized TOAST vartag", fileName="../../../../../../../src/postgres/src/backend/access/common/heaptuple.c", lineNumber=650) at assert.c:54:2`.

Note: The crash is only reproducible when the partition table has columns defined in different order than the parent table and when the column value types are different.

jasonyb commented 2 months ago

Taken from insert_conflict regress test:

drop table if exists parted_conflict_test;
create table parted_conflict_test (a int unique, b char) partition by list (a);
create table parted_conflict_test_2 (a int unique, b char);
alter table parted_conflict_test attach partition parted_conflict_test_2 for values in (3);
insert into parted_conflict_test values (3, 'a') on conflict (a) do update set b = excluded.b;
insert into parted_conflict_test values (3, 'b') on conflict (a) do update set b = excluded.b;
select * from parted_conflict_test;
 a | b 
---+---
 3 | b
(1 row)

That is expected. But if the order is flipped to (b char, a int unique) as the regress test explicitly wants to do, then we get bad results:

drop table if exists parted_conflict_test;
create table parted_conflict_test (a int unique, b char) partition by list (a);
create table parted_conflict_test_2 (b char, a int unique);
alter table parted_conflict_test attach partition parted_conflict_test_2 for values in (3);
insert into parted_conflict_test values (3, 'a') on conflict (a) do update set b = excluded.b;
insert into parted_conflict_test values (3, 'b') on conflict (a) do update set b = excluded.b;
select * from parted_conflict_test;
     a      | b 
------------+---
 2132110244 | a
(1 row)

running it again gives a different number for column a:

     a      | b 
------------+---
 2132126628 | a
(1 row)
jasonyb commented 2 months ago

On pg15 caa16a6fe3b538f787a28236c6659de3681623a0, it gives

 a | b
---+---
 3 | a
(1 row)

for the flipped order case.