Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[YSQL] Updating a row more than once in different parts of a wCTE fails #6782

Open
deeps1991 opened this issue Jan 4, 2021 · 2 comments
Open
Assignees
Labels
area/ysql Yugabyte SQL (YSQL) kind/bug This issue is a bug priority/medium Medium priority issue

Comments

@deeps1991
Copy link
Contributor

deeps1991 commented Jan 4, 2021

Jira Link: DB-1751
While enabling Postgres regression tests for with clause, the following test fails.

Pre-test

CREATE TABLE withz AS SELECT i AS k, (i || ' v')::text v FROM generate_series(1, 16, 3) i;
ALTER TABLE withz ADD UNIQUE (k);

WITH t AS (
    INSERT INTO withz SELECT i, 'insert'
    FROM generate_series(0, 16) i
    ON CONFLICT (k) DO UPDATE SET v = withz.v || ', now update'
    RETURNING *
)
SELECT * FROM t;

-- Test EXCLUDED.* reference within CTE
WITH aa AS (
    INSERT INTO withz VALUES(1, 5) ON CONFLICT (k) DO UPDATE SET v = EXCLUDED.v
    WHERE withz.k != EXCLUDED.k
    RETURNING *
)
SELECT * FROM aa;

Failing test:

-- Update a row more than once, in different parts of a wCTE. That is
-- an allowed, presumably very rare, edge case, but since it was
-- broken in the past, having a test seems worthwhile.
WITH simpletup AS (
  SELECT 2 k, 'Green' v),
upsert_cte AS (
  INSERT INTO withz VALUES(2, 'Blue') ON CONFLICT (k) DO
    UPDATE SET (k, v) = (SELECT k, v FROM simpletup WHERE simpletup.k = withz.k)
    RETURNING k, v)
INSERT INTO withz VALUES(2, 'Red') ON CONFLICT (k) DO
UPDATE SET (k, v) = (SELECT k, v FROM upsert_cte WHERE upsert_cte.k = withz.k)
RETURNING k, v;

The above should return 0 rows, but in YDB cluster, see the following output:

WARNING:  TupleDesc reference leak: TupleDesc 0x123ce9eb8 (16541,-1) still referenced
 k |   v
---+-------
 2 | Green
(1 row)
@deeps1991 deeps1991 added the area/ysql Yugabyte SQL (YSQL) label Jan 4, 2021
@deeps1991 deeps1991 self-assigned this Jan 4, 2021
deeps1991 added a commit that referenced this issue Feb 8, 2021
Summary:
In #6511, it was pointed out that in the following example:

```
with a as (
  select 1 as k)
delete from t
where k in (select k from a);
```

we throw the following warning:
```
WARNING:  0A000: WITH clause not supported yet
LINE 1: with a as (
        ^
HINT:  Please report the issue on https://github.com/YugaByte/yugabyte-db/issues
LOCATION:  raise_feature_not_supported_signal, gram.y:17327
```
However, we do support WITH clause, and this warning notwithstanding,
the delete does go through fine. As part of this change, removed this
warning message, and also imported PG tests for WITH clause.

The imported PG tests have the following major changes:
1) Temporary tables have been replaced by normal tables due to #6783
2) Disabled the failing for test for updating a row more than once in different parts of a wCTE, will be handled separately in #6782

Test Plan: ybd --scb --sj --java-test org.yb.pgsql.TestPgRegressWithClause

Reviewers: mihnea, neil

Reviewed By: neil

Subscribers: yql

Differential Revision: https://phabricator.dev.yugabyte.com/D10257
polarweasel pushed a commit to lizayugabyte/yugabyte-db that referenced this issue Mar 9, 2021
Summary:
In yugabyte#6511, it was pointed out that in the following example:

```
with a as (
  select 1 as k)
delete from t
where k in (select k from a);
```

we throw the following warning:
```
WARNING:  0A000: WITH clause not supported yet
LINE 1: with a as (
        ^
HINT:  Please report the issue on https://github.com/YugaByte/yugabyte-db/issues
LOCATION:  raise_feature_not_supported_signal, gram.y:17327
```
However, we do support WITH clause, and this warning notwithstanding,
the delete does go through fine. As part of this change, removed this
warning message, and also imported PG tests for WITH clause.

The imported PG tests have the following major changes:
1) Temporary tables have been replaced by normal tables due to yugabyte#6783
2) Disabled the failing for test for updating a row more than once in different parts of a wCTE, will be handled separately in yugabyte#6782

Test Plan: ybd --scb --sj --java-test org.yb.pgsql.TestPgRegressWithClause

Reviewers: mihnea, neil

Reviewed By: neil

Subscribers: yql

Differential Revision: https://phabricator.dev.yugabyte.com/D10257
@yugabyte-ci yugabyte-ci added kind/bug This issue is a bug priority/medium Medium priority issue labels Jun 8, 2022
@jasonyb
Copy link
Contributor

jasonyb commented May 16, 2023

Found this existing issue after already doing a separate analysis:

When studying fields in EState, yb_conflict_slot looked suspicious. Came up with a multi-conflict plan that causes reference leak:

WARNING:  TupleDesc reference leak: TupleDesc 0x3653fe182208 (16399,-1) still referenced

Repro A:

drop table if exists a;
drop table if exists b;
create table a (i int unique);
create table b (i int unique);
insert into a values (1);
insert into b values (2);

EXPLAIN
with w(i) as (
    insert into a values (1) on conflict on constraint a_i_key do update set i = 10 returning i
) insert into b values (2) on conflict on constraint b_i_key do update set i = (select 20 from w);
with w(i) as (
    insert into a values (1) on conflict on constraint a_i_key do update set i = 10 returning i
) insert into b values (2) on conflict on constraint b_i_key do update set i = (select 20 from w);

Repro B:

drop table if exists a;
create table a (i int unique);
insert into a values (1), (2);

EXPLAIN
with w(i) as (
    insert into a values (1) on conflict on constraint a_i_key do update set i = 10 returning i
) insert into a values (2) on conflict on constraint a_i_key do update set i = (select 20 from w);
with w(i) as (
    insert into a values (1) on conflict on constraint a_i_key do update set i = 10 returning i
) insert into a values (2) on conflict on constraint a_i_key do update set i = (select 20 from w);

Repro C:

drop table if exists a;
create table a (i int unique);
insert into a values (1), (2), (3);

EXPLAIN
with w(i) as (
    insert into a values (1) on conflict on constraint a_i_key do update set i = 10 returning i
), x(i) as (
    insert into a values (2) on conflict on constraint a_i_key do update set i = 20 returning i
) insert into a values (3) on conflict on constraint a_i_key do update set i = (select 30 from w);
with w(i) as (
    insert into a values (1) on conflict on constraint a_i_key do update set i = 10 returning i
), x(i) as (
    insert into a values (2) on conflict on constraint a_i_key do update set i = 20 returning i
) insert into a values (3) on conflict on constraint a_i_key do update set i = (select 30 from w);

With repro A, I found the tuple it is complaining about is table b column i.

(gdb) p *res->attrs
$19 = {attrelid = 16397, attname = {data = "i", '\000' <repeats 62 times>}, atttypid = 23, attstattarget = -1, attlen = 4, attnum = 1, attndims = 0, attcacheoff = 0, 
  atttypmod = -1, attbyval = true, attstorage = 112 'p', attalign = 105 'i', attnotnull = false, atthasdef = false, atthasmissing = false, attidentity = 0 '\000', 
  attisdropped = false, attislocal = true, attinhcount = 0, attcollation = 0}

select 16399::regtype; and select 16397::regclass; give b.

And the explain is

                       QUERY PLAN                        
---------------------------------------------------------
 Insert on b  (cost=0.03..0.04 rows=1 width=4)
   Conflict Resolution: UPDATE
   Conflict Arbiter Indexes: b_i_key
   CTE w
     ->  Insert on a  (cost=0.00..0.01 rows=1 width=4)
           Conflict Resolution: UPDATE
           Conflict Arbiter Indexes: a_i_key
           ->  Result  (cost=0.00..0.01 rows=1 width=4)
   InitPlan 2 (returns $2)
     ->  CTE Scan on w  (cost=0.00..0.02 rows=1 width=4)
   ->  Result  (cost=0.00..0.01 rows=1 width=4)
(11 rows)

The logic is to first ExecInsert on b. The top of that function clears yb_conflict_slot. This ends up calling ExecOnConflictUpdate

  • ExecInsert on b
    • estate->yb_conflict_slot = NULL
    • ExecCheckIndexConstraints
      • estate->yb_conflict_slot = existing_slot -- save b's slot
    • ExecOnConflictUpdate
      • use yb_conflict_slot
      • ExecProject
        • ...
          • ExecInsert on a
            • estate->yb_conflict_slot = NULL -- this overwrites b's slot
            • ExecCheckIndexConstraints
              • estate->yb_conflict_slot = existing_slot -- save a's slot
            • ExecOnConflictUpdate
              • use yb_conflict_slot
              • ExecProject
              • ExecUpdate
            • ExecDropSingleTupleTableSlot(estate->yb_conflict_slot)
      • ExecUpdate
    • ExecDropSingleTupleTableSlot(estate->yb_conflict_slot)

It did not look like there is an issue of cross-contamination between on-conflict-do-update plans since the slot stored into yb_conflict_slot is used soon after, but the original issue comment has a repro for wrong results. I haven't looked into it much further.

Perhaps yb_conflict_slot does not belong in EState but in a more local state.

@jasonyb
Copy link
Contributor

jasonyb commented Aug 8, 2024

This issue is now only about the incorrect (as in different from vanilla PG) output

 k |   v
---+-------
 2 | Green
(1 row)

TupleDesc reference leak is tracked in #23429.

jasonyb pushed a commit that referenced this issue Aug 9, 2024
Summary:
Nested INSERT ON CONFLICT causes TupleDesc reference leak warning.  The
cause is obvious: both the outer and inner INSERT ON CONFLICT use the
same estate->yb_conflict_slot:

1. outer: set estate->yb_conflict_slot
2. inner: set estate->yb_conflict_slot
3. inner: free estate->yb_conflict_slot
4. outer: free estate->yb_conflict_slot

The slot allocated in (1) is not freed.

Fix by removing yb_conflict_slot and using local variable
ybConflictSlot and passing it through functions like PG's conflictTid.
In future PG versions, resultRelInfo is local to the node, so maybe this
can be put into resultRelInfo to reduce modifications to the functions
signatures and make future merges easier.

Add test coverage using examples from issue #6782.
Jira: DB-12350

Test Plan:
On Almalinux 8:

    ./yb_build.sh fastdebug --gcc11 --java-test TestPgRegressPgMisc
    ./yb_build.sh fastdebug --gcc11 --java-test 'TestPgRegressMisc#testPgRegressMiscSerial4'

Close: #23429
Depends on D37104

Reviewers: kramanathan, aagrawal

Reviewed By: aagrawal

Subscribers: yql

Differential Revision: https://phorge.dev.yugabyte.com/D37151
jasonyb pushed a commit that referenced this issue Aug 13, 2024
…eDesc ref leak

Summary:
MERGE:
efd4cb7

YB pg15 initial merge refers to
55782d5.

- execIndexing.c:
  - function declarations: YB master
    efd4cb7 adds ybConflictSlot
    parameter; upstream PG changes indentation.
- nodeModifyTable.c:
  - function declarations: (same).
  - ExecCheckIndexConstraints function call: YB master
    efd4cb7 adds ybConflictSlot
    parameter; upstream PG a04daa97a4339c38e304cd6164d37da540d665a8
    adds resultRelInfo.
  - ExecOnConflictUpdate function call: YB master
    efd4cb7 adds ybConflictSlot
    parameter; upstream PG 25e777cf8e547d7423d2e1e9da71f98b9414d59e also
    touches parameters.
  - yb_skip_transaction_control_check: YB master
    efd4cb7 changes
    estate->yb_conflict_slot to ybConflictSlot and adds corresponding
    assert.  YB pg15 initial merge flips if and else cases, causing
    merge to get confused.  Carefully apply the same logic formerly in
    the if case now to the else case.
- executor.h:
  - function declarations: (same as execIndexing.c).
- yb_pg_with.out, yb_pg_with.sql:
  - Update a row more than once: YB pg15
    b68a24e adds "YB" to a comment, and
    YB master efd4cb7 deletes that
    comment.  Delete.

Nested INSERT ON CONFLICT causes TupleDesc reference leak warning.  The
cause is obvious: both the outer and inner INSERT ON CONFLICT use the
same estate->yb_conflict_slot:

1. outer: set estate->yb_conflict_slot
2. inner: set estate->yb_conflict_slot
3. inner: free estate->yb_conflict_slot
4. outer: free estate->yb_conflict_slot

The slot allocated in (1) is not freed.

Fix by removing yb_conflict_slot and using local variable
ybConflictSlot and passing it through functions like PG's conflictTid.
In future PG versions, resultRelInfo is local to the node, so maybe this
can be put into resultRelInfo to reduce modifications to the functions
signatures and make future merges easier.

Add test coverage using examples from issue #6782.
Jira: DB-12350

Test Plan:
On Almalinux 8:

    ./yb_build.sh fastdebug --gcc11 --java-test TestPgRegressPgMisc
    ./yb_build.sh fastdebug --gcc11 --java-test 'TestPgRegressMisc#testPgRegressMiscSerial4'

Jenkins: rebase: pg15-cherrypicks

Reviewers: kramanathan, aagrawal

Reviewed By: aagrawal

Subscribers: yql

Tags: #jenkins-ready

Differential Revision: https://phorge.dev.yugabyte.com/D37248
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/ysql Yugabyte SQL (YSQL) kind/bug This issue is a bug priority/medium Medium priority issue
Projects
Status: No status
Development

No branches or pull requests

3 participants