Skip to content

Commit 98c3795

Browse files
committed
sql: block ALTER TABLE LOCALITY to RBR with sql_safe_updates
A bug was recently discovered that causes UPDATE and DELETE operations to fail on a table while it is being converted to REGIONAL BY ROW. To protect against this, this patch blocks the ALTER TABLE LOCALiTY statement if the sql_safe_updates flag is on. The error hint provides a workaround to safely convert the locality. Release note (sql change): When sql_safe_updates is enabled, the ALTER TABLE LOCALITY statement will now be blocked when trying to convert an existing table to REGIONAL BY ROW, unless a region column has already been added to the table. This protects against undesired behavior that caused UPDATE or DELETE queries to fail against the table while the locality change is in progress.
1 parent a6e4ba9 commit 98c3795

File tree

4 files changed

+130
-1
lines changed

4 files changed

+130
-1
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# LogicTest: multiregion-9node-3region-3azs
2+
#
3+
# Test for blocking ALTER TABLE LOCALITY to REGIONAL BY ROW with sql_safe_updates enabled
4+
# This addresses issue #150945 to prevent DML failures caused by the legacy schema
5+
# changer's simultaneous column addition and primary key alteration.
6+
7+
statement ok
8+
CREATE DATABASE safe_updates_test_db PRIMARY REGION "us-east-1" REGIONS "ap-southeast-2", "ca-central-1" SURVIVE REGION FAILURE
9+
10+
statement ok
11+
USE safe_updates_test_db
12+
13+
# Verify safe updates blocks REGIONAL BY ROW conversion for existing tables.
14+
subtest blocked_by_flag
15+
16+
statement ok
17+
CREATE TABLE t1 (id UUID PRIMARY KEY, s STRING NOT NULL) WITH (schema_locked=false)
18+
19+
statement ok
20+
INSERT INTO t1 SELECT gen_random_uuid(), 'test' FROM generate_series(1, 10)
21+
22+
statement ok
23+
SET sql_safe_updates = true
24+
25+
statement error pq: cannot convert table to REGIONAL BY ROW with sql_safe_updates enabled\nHINT:.*three-step workaround:\n.*1\. ALTER TABLE t1 ADD COLUMN crdb_region.*\n.*2\. ALTER TABLE t1 ALTER COLUMN crdb_region SET DEFAULT.*\n.*3\. ALTER TABLE t1 SET LOCALITY REGIONAL BY ROW;
26+
ALTER TABLE t1 SET LOCALITY REGIONAL BY ROW
27+
28+
subtest end
29+
30+
# Verify the operation works when safe updates are disabled.
31+
subtest works_with_flag_off
32+
33+
statement ok
34+
SET sql_safe_updates = false
35+
36+
statement ok
37+
ALTER TABLE t1 SET LOCALITY REGIONAL BY ROW
38+
39+
statement ok
40+
SET sql_safe_updates = true
41+
42+
# Verify DML operations work correctly after safe conversion
43+
query T
44+
SELECT DISTINCT s FROM t1
45+
----
46+
test
47+
48+
statement ok
49+
UPDATE t1 SET s = 'updated' WHERE s = 'test'
50+
51+
query T
52+
SELECT DISTINCT s FROM t1
53+
----
54+
updated
55+
56+
subtest end
57+
58+
# Verify the safe 3-step workaround works correctly.
59+
subtest not_blocked_if_column_exists_already
60+
61+
statement ok
62+
SET sql_safe_updates = true
63+
64+
statement ok
65+
CREATE TABLE t2 (id UUID PRIMARY KEY, s STRING NOT NULL) WITH (schema_locked=false)
66+
67+
statement ok
68+
INSERT INTO t2 SELECT gen_random_uuid(), 'test' FROM generate_series(1, 10)
69+
70+
# Step 1: Add the region column manually.
71+
statement ok
72+
ALTER TABLE t2 ADD COLUMN crdb_region public.crdb_internal_region NOT VISIBLE NOT NULL DEFAULT 'us-east-1'::public.crdb_internal_region
73+
74+
# Step 2: Update the default expression to use gateway_region().
75+
statement ok
76+
ALTER TABLE t2 ALTER COLUMN crdb_region SET DEFAULT default_to_database_primary_region(gateway_region())::public.crdb_internal_region
77+
78+
# Step 3: Convert to REGIONAL BY ROW (should work now).
79+
statement ok
80+
ALTER TABLE t2 SET LOCALITY REGIONAL BY ROW
81+
82+
# Verify DML operations work after manual workaround
83+
query T
84+
SELECT DISTINCT s FROM t2
85+
----
86+
test
87+
88+
statement ok
89+
UPDATE t2 SET s = 'updated_safe' WHERE s = 'test'
90+
91+
query T
92+
SELECT DISTINCT s FROM t2
93+
----
94+
updated_safe
95+
96+
subtest end

pkg/ccl/logictestccl/tests/multiregion-9node-3region-3azs/BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ go_test(
99
"//pkg/ccl/logictestccl:testdata", # keep
1010
],
1111
exec_properties = {"test.Pool": "large"},
12-
shard_count = 31,
12+
shard_count = 32,
1313
tags = ["cpu:4"],
1414
deps = [
1515
"//pkg/base",

pkg/ccl/logictestccl/tests/multiregion-9node-3region-3azs/generated_test.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/sql/alter_table_locality.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,32 @@ func (n *alterTableSetLocalityNode) alterTableLocalityToRegionalByRow(
281281
)
282282
}
283283
} else {
284+
// Block REGIONAL BY ROW conversion when safe updates are enabled to prevent
285+
// DML failures caused by the legacy schema changer's simultaneous column
286+
// addition and primary key alteration.
287+
// When this operation is implemented in the declarative schema changer, we
288+
// should not need this check since the declarative schema changer should
289+
// handle the element transitions correctly.
290+
if params.p.SessionData().SafeUpdates && !n.tableDesc.Adding() {
291+
primaryRegion, err := n.dbDesc.PrimaryRegionName()
292+
if err != nil {
293+
return err
294+
}
295+
296+
return errors.WithHintf(
297+
pgerror.Newf(
298+
pgcode.InvalidTableDefinition,
299+
"cannot convert table to REGIONAL BY ROW with sql_safe_updates enabled",
300+
),
301+
"To safely convert to REGIONAL BY ROW, either disable sql_safe_updates; or "+
302+
"use the following three-step workaround:\n"+
303+
" 1. ALTER TABLE %[1]s ADD COLUMN %[2]s public.crdb_internal_region NOT VISIBLE NOT NULL DEFAULT '%[3]s'::public.crdb_internal_region;\n"+
304+
" 2. ALTER TABLE %[1]s ALTER COLUMN %[2]s SET DEFAULT default_to_database_primary_region(gateway_region())::public.crdb_internal_region;\n"+
305+
" 3. ALTER TABLE %[1]s SET LOCALITY REGIONAL BY ROW;",
306+
tree.Name(n.tableDesc.GetName()), partColName, primaryRegion,
307+
)
308+
}
309+
284310
// No crdb_region column is found so we are implicitly creating it.
285311
// We insert the column definition before altering the primary key.
286312

0 commit comments

Comments
 (0)