cockroachdb / cockroachdb/cockroach

sql: ALTER TYPE race with PL/pgSQL planning

Open
#167,610 1 comment 0 reactions 1 assignee Claimed by @ZhouXing19 View on GitHub
A-sql-plpgsql branch-master C-bug T-sql-queries
Dominant language
Go
Stars
32.5k
Forks
4.1k
PR merge metrics
PR metrics pending

Description

We saw `comparison of two different versions of enum` error in recent test failure (#167457), and claude helped come up with a logictest script that can reproduce the same error under stress condition. Though I'm not sure if this is indeed a race issue of "version mismatch between types resolved at different stages of PL/pgSQL planning" as claude pointed out, we shall start from this script for investigation.

**Run with**: `./dev testlogic base --config=local --files='enum_plpgsql_version' --stress`

```sql

# LogicTest: local

# Note: THIS ANALYSIS IS NOT CONFIRMED YET.
# Regression test for the "comparison of two different versions of enum" bug.
#
# The bug is a race condition: when a cancelled ALTER TYPE leaves a background
# schema change job running, the job may modify the type descriptor version
# while a concurrent query is being planned. Different parts of the query
# plan resolve the enum type at different versions, causing DEnum.Compare()
# to hit an assertion failure.
#
# This test cancels ALTER TYPE via statement_timeout to create a background
# schema change job, then immediately runs PL/pgSQL blocks that use the enum.
# Under --stress, multiple instances increase the chance of hitting the race.

statement ok
CREATE TYPE greeting AS ENUM ('hello', 'howdy', 'hi', 'good day', 'morning')

statement ok
CREATE TABLE seed (id INT8 PRIMARY KEY, val greeting)

statement ok
INSERT INTO seed SELECT g, enum_range('hello'::greeting)[(g % 5) + 1]
FROM generate_series(1, 10000) AS g

statement ok
SET CLUSTER SETTING sql.stats.automatic_collection.enabled = off

statement ok
ANALYZE seed

# Warm up the session's type cache.
query T
SELECT val FROM seed WHERE val = 'hi'::greeting LIMIT 1
----
hi

# Cancel ALTER TYPE DROP VALUE to create a background schema change job.
# The background job will attempt to verify the value isn't in use by
# scanning the table, bumping the type descriptor version in the process.
# After a previous cancelled DROP, the value may already be in "being dropped"
# state, so we accept that error too.
statement ok
SET statement_timeout = '1ms'

statement error pq: (query execution canceled due to statement timeout|enum value "howdy" is already being dropped)
ALTER TYPE greeting DROP VALUE 'howdy'

statement ok
SET statement_timeout = 0

# Immediately run a PL/pgSQL block. If the background schema change job
# is still running and modifies the type descriptor version during planning,
# variable types and table column types may end up with different versions.
statement ok
DO $$
DECLARE
v greeting := 'hi';
w greeting;
BEGIN
SELECT val INTO w FROM seed WHERE val = v LIMIT 1;
IF w = v THEN
NULL;
END IF;
END;
$$

# Try again with different timeout.
statement ok
SET statement_timeout = '5ms'

statement error pq: (query execution canceled due to statement timeout|enum value "howdy" is already being dropped)
ALTER TYPE greeting DROP VALUE 'howdy'

statement ok
SET statement_timeout = 0

statement ok
DO $$
DECLARE
v1 greeting := 'hi';
v2 greeting := 'hello';
v3 greeting := 'morning';
w greeting;
cnt INT8;
BEGIN
SELECT val INTO w FROM seed WHERE val = v1 LIMIT 1;
SELECT count(*) INTO cnt FROM seed WHERE val = v2;
SELECT val INTO w FROM seed WHERE val = v3 ORDER BY val LIMIT 1;
SELECT val INTO w FROM seed WHERE val IN (v1, v2, v3) ORDER BY val LIMIT 1;
END;
$$

# Cancel again, run query with enum comparison.
statement ok
SET statement_timeout = '1ms'

statement error pq: (query execution canceled due to statement timeout|enum value "howdy" is already being dropped)
ALTER TYPE greeting DROP VALUE 'howdy'

statement ok
SET statement_timeout = 0

query IB
SELECT id, val > 'hello'::greeting FROM seed WHERE val = 'hi'::greeting ORDER BY id LIMIT 1
----
2 true

# DO block with nested blocks and multiple enum resolutions.
statement ok
SET statement_timeout = '3ms'

statement error pq: (query execution canceled due to statement timeout|enum value "howdy" is already being dropped)
ALTER TYPE greeting DROP VALUE 'howdy'

statement ok
SET statement_timeout = 0

statement ok
DO $$
DECLARE
v greeting := 'hi';
w greeting;
BEGIN
SELECT val INTO w FROM seed WHERE val = v LIMIT 1;
DECLARE
x greeting := 'hello';
y greeting;
BEGIN
SELECT val INTO y FROM seed WHERE val = x LIMIT 1;
IF y = x THEN
NULL;
END IF;
IF w = v THEN
NULL;
END IF;
END;
END;
$$

# Repeat the cancel-and-query pattern more times.
statement ok
SET statement_timeout = '1ms'

statement error pq: (query execution canceled due to statement timeout|enum value "howdy" is already being dropped)
ALTER TYPE greeting DROP VALUE 'howdy'

statement ok
SET statement_timeout = 0

statement ok
DO $$
DECLARE v greeting := 'hi'; w greeting;
BEGIN SELECT val INTO w FROM seed WHERE val = v LIMIT 1; END;
$$

statement ok
SET statement_timeout = '1ms'

statement error pq: (query execution canceled due to statement timeout|enum value "howdy" is already being dropped)
ALTER TYPE greeting DROP VALUE 'howdy'

statement ok
SET statement_timeout = 0

statement ok
DO $$
DECLARE v greeting := 'hi'; w greeting;
BEGIN SELECT val INTO w FROM seed WHERE val = v LIMIT 1; END;
$$

statement ok
SET statement_timeout = '1ms'

statement error pq: (query execution canceled due to statement timeout|enum value "howdy" is already being dropped)
ALTER TYPE greeting DROP VALUE 'howdy'

statement ok
SET statement_timeout = 0

statement ok
DO $$
DECLARE v greeting := 'hi'; w greeting;
BEGIN SELECT val INTO w FROM seed WHERE val = v LIMIT 1; END;
$$

# Also try with successful ADD VALUE followed by DO block.
statement ok
ALTER TYPE greeting ADD VALUE IF NOT EXISTS 'sup'

statement ok
DO $$
DECLARE v greeting := 'hi'; w greeting;
BEGIN SELECT val INTO w FROM seed WHERE val = v LIMIT 1; END;
$$

statement ok
ALTER TYPE greeting ADD VALUE IF NOT EXISTS 'yo'

statement ok
DO $$
DECLARE v greeting := 'hi'; w greeting;
BEGIN SELECT val INTO w FROM seed WHERE val = v LIMIT 1; END;
$$
```

Jira issue: CRDB-62554

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.