Skip to content

Commit 886079f

Browse files
committed
scbuild: allow multiple column renames
This includes new dependency rules so that we fully remove the old name before allowing a new column to take on that name, and another one to make sure that a name is available before we create a computed column expression that uses that name. Release note: None
1 parent c5a3d18 commit 886079f

File tree

38 files changed

+2495
-180
lines changed

38 files changed

+2495
-180
lines changed

pkg/ccl/changefeedccl/changefeed_test.go

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10242,23 +10242,19 @@ func TestChangefeedPredicateWithSchemaChange(t *testing.T) {
1024210242
}
1024310243

1024410244
type testCase struct {
10245-
name string
10246-
disableSchemaLocked bool
10247-
createFeedStmt string // Create changefeed statement.
10248-
initialPayload []string // Expected payload after create.
10249-
alterStmt string // Alter statement to execute.
10250-
afterAlterStmt string // Execute after alter statement.
10251-
expectErr string // Alter may result in changefeed terminating with error.
10252-
payload []string // Expect the following payload after executing afterAlterStmt.
10245+
name string
10246+
createFeedStmt string // Create changefeed statement.
10247+
initialPayload []string // Expected payload after create.
10248+
alterStmt string // Alter statement to execute.
10249+
afterAlterStmt string // Execute after alter statement.
10250+
expectErr string // Alter may result in changefeed terminating with error.
10251+
payload []string // Expect the following payload after executing afterAlterStmt.
1025310252
}
1025410253

1025510254
testFn := func(tc testCase) cdcTestFn {
1025610255
return func(t *testing.T, s TestServer, f cdctest.TestFeedFactory) {
1025710256
sqlDB := sqlutils.MakeSQLRunner(s.DB)
1025810257

10259-
if tc.disableSchemaLocked {
10260-
sqlDB.Exec(t, "SET create_table_with_schema_locked=false")
10261-
}
1026210258
sqlDB.ExecMultiple(t, setupSQL...)
1026310259
foo := feed(t, f, tc.createFeedStmt)
1026410260
feedJob := foo.(cdctest.EnterpriseTestFeed)
@@ -10364,7 +10360,7 @@ func TestChangefeedPredicateWithSchemaChange(t *testing.T) {
1036410360
},
1036510361
alterStmt: "ALTER TABLE foo RENAME COLUMN c TO c_new",
1036610362
afterAlterStmt: "INSERT INTO foo (a, b) VALUES (3, 'tres')",
10367-
expectErr: `column "c" does not exist`,
10363+
expectErr: `(column "c" does not exist|could not identify column "c")`,
1036810364
},
1036910365
{
1037010366
name: "alter enum",
@@ -10402,37 +10398,33 @@ func TestChangefeedPredicateWithSchemaChange(t *testing.T) {
1040210398
{
1040310399
// Alter and rename a column. The changefeed expression does not
1040410400
// explicitly involve the column in question (c) -- so, schema change works
10405-
// fine. Note: we get 2 backfill events -- one for each logical change
10406-
// (rename column, then add column).
10407-
name: "add and rename column",
10408-
disableSchemaLocked: true, // legacy schema change
10409-
createFeedStmt: "CREATE CHANGEFEED AS SELECT *, (cdc_prev).e as old_e FROM foo",
10401+
// fine. With the declarative schema changer, both operations are processed
10402+
// atomically, resulting in a single backfill event per row with the final state.
10403+
name: "add and rename column",
10404+
createFeedStmt: "CREATE CHANGEFEED AS SELECT *, (cdc_prev).e as old_e FROM foo",
1041010405
initialPayload: []string{
1041110406
`foo: [1, "one"]->{"a": 1, "b": "one", "c": null, "e": "inactive", "old_e": null}`,
1041210407
`foo: [2, "two"]->{"a": 2, "b": "two", "c": "c string", "e": "open", "old_e": null}`,
1041310408
},
1041410409
alterStmt: "ALTER TABLE foo RENAME COLUMN c to c_old, ADD COLUMN c int DEFAULT 42",
1041510410
payload: []string{
1041610411
`foo: [1, "one"]->{"a": 1, "b": "one", "c": 42, "c_old": null, "e": "inactive", "old_e": "inactive"}`,
10417-
`foo: [1, "one"]->{"a": 1, "b": "one", "c_old": null, "e": "inactive", "old_e": "inactive"}`,
1041810412
`foo: [2, "two"]->{"a": 2, "b": "two", "c": 42, "c_old": "c string", "e": "open", "old_e": "open"}`,
10419-
`foo: [2, "two"]->{"a": 2, "b": "two", "c_old": "c string", "e": "open", "old_e": "open"}`,
1042010413
},
1042110414
},
1042210415
{
1042310416
// Alter and rename a column. The changefeed expression does
1042410417
// explicitly involve the column in question (c) -- so we expect
1042510418
// to get an error because as soon as the first rename goes through, column
1042610419
// no longer exists.
10427-
name: "add and rename column error",
10428-
disableSchemaLocked: true, // legacy schema change
10429-
createFeedStmt: "CREATE CHANGEFEED AS SELECT c, (cdc_prev).c AS prev_c FROM foo",
10420+
name: "add and rename column error",
10421+
createFeedStmt: "CREATE CHANGEFEED AS SELECT c, (cdc_prev).c AS prev_c FROM foo",
1043010422
initialPayload: []string{
1043110423
`foo: [1, "one"]->{"c": null, "prev_c": null}`,
1043210424
`foo: [2, "two"]->{"c": "c string", "prev_c": null}`,
1043310425
},
1043410426
alterStmt: "ALTER TABLE foo RENAME COLUMN c to c_old, ADD COLUMN c int DEFAULT 42",
10435-
expectErr: `column "c" does not exist`,
10427+
expectErr: `(column "c" does not exist|could not identify column "c")`,
1043610428
},
1043710429
} {
1043810430
t.Run(tc.name, func(t *testing.T) {

pkg/ccl/schemachangerccl/sctestbackupccl/backup_base_generated_test.go

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

pkg/ccl/schemachangerccl/testdata/end_to_end/add_column_multiple_regional_by_row/add_column_multiple_regional_by_row.side_effects

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@ checking for feature: ALTER TABLE
2222
increment telemetry for sql.schema.alter_table
2323
increment telemetry for sql.schema.alter_table.drop_column
2424
increment telemetry for sql.schema.alter_table.add_column
25+
increment telemetry for sql.schema.qualifcation.unique
2526
increment telemetry for sql.schema.qualifcation.default_expr
2627
increment telemetry for sql.schema.new_column_type.int8
2728
increment telemetry for sql.schema.alter_table.drop_column
2829
increment telemetry for sql.schema.alter_table.add_column
30+
increment telemetry for sql.schema.qualifcation.unique
2931
increment telemetry for sql.schema.qualifcation.default_expr
3032
increment telemetry for sql.schema.new_column_type.int8
3133
write *eventpb.AlterTable to event log:

pkg/sql/logictest/testdata/logic_test/alter_table

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1221,9 +1221,9 @@ WHERE feature_name IN (
12211221
'sql.schema.new_column.qualification.unique'
12221222
)
12231223
----
1224-
sql.schema.new_column.qualification.unique
12251224
sql.schema.new_column.qualification.computed
12261225
sql.schema.new_column.qualification.default_expr
1226+
sql.schema.new_column.qualification.unique
12271227

12281228
statement ok
12291229
DROP TABLE telemetry_test

pkg/sql/logictest/testdata/logic_test/rename_column

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,3 +228,80 @@ column_name
228228
NewCamelCase
229229
SnakeCase
230230
decimal_value
231+
232+
# Test renaming a column, adding a new column with the old name, and altering
233+
# the primary key to use the renamed column in a single statement.
234+
subtest rename_add_alter_primary_key
235+
236+
statement ok
237+
CREATE TABLE rename_add_alter_pk_tbl (a INT PRIMARY KEY, b INT, FAMILY f (a, b));
238+
239+
statement ok
240+
INSERT INTO rename_add_alter_pk_tbl VALUES (1, 10), (2, 20);
241+
242+
skipif config local-legacy-schema-changer local-mixed-25.2 local-mixed-25.3 local-mixed-25.4
243+
statement ok
244+
ALTER TABLE rename_add_alter_pk_tbl RENAME COLUMN a TO b_old,
245+
ADD COLUMN a INT NOT NULL DEFAULT 0,
246+
ALTER PRIMARY KEY USING COLUMNS (b_old);
247+
248+
skipif config local-legacy-schema-changer local-mixed-25.2 local-mixed-25.3 local-mixed-25.4
249+
query III colnames,rowsort
250+
SELECT b_old, b, a FROM rename_add_alter_pk_tbl;
251+
----
252+
b_old b a
253+
1 10 0
254+
2 20 0
255+
256+
skipif config local-legacy-schema-changer local-mixed-25.2 local-mixed-25.3 local-mixed-25.4
257+
query TTBITTTBBBF colnames,rowsort
258+
SHOW INDEXES FROM rename_add_alter_pk_tbl;
259+
----
260+
table_name index_name non_unique seq_in_index column_name definition direction storing implicit visible visibility
261+
rename_add_alter_pk_tbl rename_add_alter_pk_tbl_pkey false 1 b_old b_old ASC false false true 1
262+
rename_add_alter_pk_tbl rename_add_alter_pk_tbl_pkey false 2 b b N/A true false true 1
263+
rename_add_alter_pk_tbl rename_add_alter_pk_tbl_pkey false 3 a a N/A true false true 1
264+
265+
skipif config local-legacy-schema-changer local-mixed-25.2 local-mixed-25.3 local-mixed-25.4
266+
query T
267+
SELECT create_statement FROM [SHOW CREATE TABLE rename_add_alter_pk_tbl];
268+
----
269+
CREATE TABLE public.rename_add_alter_pk_tbl (
270+
b_old INT8 NOT NULL,
271+
b INT8 NULL,
272+
a INT8 NOT NULL DEFAULT 0:::INT8,
273+
CONSTRAINT rename_add_alter_pk_tbl_pkey PRIMARY KEY (b_old ASC),
274+
FAMILY f (b_old, b, a)
275+
) WITH (schema_locked = true);
276+
277+
skipif config local-legacy-schema-changer local-mixed-25.2 local-mixed-25.3 local-mixed-25.4
278+
statement ok
279+
UPDATE rename_add_alter_pk_tbl SET a = 10 WHERE b_old = 2;
280+
281+
skipif config local-legacy-schema-changer local-mixed-25.2 local-mixed-25.3 local-mixed-25.4
282+
statement ok
283+
ALTER TABLE rename_add_alter_pk_tbl RENAME COLUMN b_old TO b_orig,
284+
ADD COLUMN b_old INT NOT NULL DEFAULT 100,
285+
ALTER PRIMARY KEY USING COLUMNS (a);
286+
287+
skipif config local-legacy-schema-changer local-mixed-25.2 local-mixed-25.3 local-mixed-25.4
288+
query IIII colnames,rowsort
289+
SELECT b_old, b_orig, a, b FROM rename_add_alter_pk_tbl;
290+
----
291+
b_old b_orig a b
292+
100 1 0 10
293+
100 2 10 20
294+
295+
skipif config local-legacy-schema-changer local-mixed-25.2 local-mixed-25.3 local-mixed-25.4
296+
query T
297+
SELECT create_statement FROM [SHOW CREATE TABLE rename_add_alter_pk_tbl];
298+
----
299+
CREATE TABLE public.rename_add_alter_pk_tbl (
300+
b_orig INT8 NOT NULL,
301+
b INT8 NULL,
302+
a INT8 NOT NULL DEFAULT 0:::INT8,
303+
b_old INT8 NOT NULL DEFAULT 100:::INT8,
304+
CONSTRAINT rename_add_alter_pk_tbl_pkey PRIMARY KEY (a ASC),
305+
UNIQUE INDEX rename_add_alter_pk_tbl_b_orig_key (b_orig ASC),
306+
FAMILY f (b_orig, b, a, b_old)
307+
) WITH (schema_locked = true);

pkg/sql/logictest/testdata/logic_test/virtual_columns

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1120,10 +1120,12 @@ subtest referencing_mutations
11201120
statement ok
11211121
CREATE TABLE t_ref (i INT PRIMARY KEY) WITH (schema_locked = false);
11221122

1123+
onlyif config local-legacy-schema-changer local-mixed-25.2 local-mixed-25.3
11231124
statement error pgcode 0A000 virtual computed column "k" referencing columns \("j"\) added in the current transaction
11241125
ALTER TABLE t_ref ADD COLUMN j INT NOT NULL DEFAULT 42,
11251126
ADD COLUMN k INT AS (i+j) VIRTUAL;
11261127

1128+
onlyif config local-legacy-schema-changer local-mixed-25.2 local-mixed-25.3
11271129
statement error pgcode 0A000 virtual computed column "l" referencing columns \("j", "k"\) added in the current transaction
11281130
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
11291131
SET LOCAL autocommit_before_ddl = false;
@@ -1135,6 +1137,12 @@ COMMIT;
11351137
statement ok
11361138
ROLLBACK;
11371139

1140+
# The declarative schema changer allows this.
1141+
skipif config local-legacy-schema-changer local-mixed-25.2 local-mixed-25.3 local-mixed-25.4
1142+
statement ok
1143+
ALTER TABLE t_ref ADD COLUMN j INT NOT NULL DEFAULT 42,
1144+
ADD COLUMN k INT AS (i+j) VIRTUAL;
1145+
11381146
# Test that adding virtual computed columns to tables which have been created
11391147
# in the current transaction is fine.
11401148

pkg/sql/schemachanger/dml_injection_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,14 @@ func TestAlterTableDMLInjection(t *testing.T) {
567567
},
568568
schemaChange: "ALTER TABLE tbl DROP CONSTRAINT c, DROP COLUMN i",
569569
},
570+
{
571+
desc: "verify that names can be swapped atomically",
572+
setup: []string{
573+
"ALTER TABLE tbl ADD COLUMN i INT DEFAULT 11, ADD COLUMN j INT DEFAULT 22",
574+
},
575+
schemaChange: "ALTER TABLE tbl RENAME COLUMN i TO i_old, RENAME COLUMN j TO i",
576+
query: "SELECT i FROM tbl LIMIT 1",
577+
},
570578
}
571579

572580
ctx := context.Background()

pkg/sql/schemachanger/scbuild/internal/scbuildstmt/alter_table_add_column.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,13 @@ func alterTableAddColumn(
5050
RequiredPrivilege: privilege.CREATE,
5151
})
5252
_, colTargetStatus, col := scpb.FindColumn(elts)
53-
columnAlreadyExists := col != nil && colTargetStatus != scpb.ToAbsent
53+
_, colNameTargetStatus, colName := scpb.FindColumnName(elts)
54+
// A column name already exists if both the Column and ColumnName elements exist
55+
// and are not transitioning to ABSENT. When a column is being renamed, the
56+
// Column remains but the old ColumnName transitions to ABSENT, freeing up the
57+
// name for reuse.
58+
columnAlreadyExists := col != nil && colTargetStatus != scpb.ToAbsent &&
59+
colName != nil && colNameTargetStatus != scpb.ToAbsent
5460
// If the column exists and IF NOT EXISTS is specified, continue parsing
5561
// to ensure there are no other errors before treating the operation as a no-op.
5662
if columnAlreadyExists && !t.IfNotExists {
@@ -119,6 +125,9 @@ func alterTableAddColumn(
119125
unique: d.Unique.IsUnique,
120126
notNull: !desc.Nullable,
121127
}
128+
if spec.unique {
129+
b.IncrementSchemaChangeAddColumnQualificationCounter("unique")
130+
}
122131

123132
idx := cdd.PrimaryKeyOrUniqueIndexDescriptor
124133
isRBR := b.QueryByID(tbl.TableID).FilterTableLocalityRegionalByRow().MustGetZeroOrOneElement() != nil

pkg/sql/schemachanger/scbuild/internal/scbuildstmt/alter_table_rename_column.go

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package scbuildstmt
77

88
import (
9+
"github.com/cockroachdb/cockroach/pkg/clusterversion"
910
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
1011
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
1112
"github.com/cockroachdb/cockroach/pkg/sql/privilege"
@@ -24,7 +25,7 @@ func alterTableRenameColumn(
2425
n *tree.AlterTable,
2526
t *tree.AlterTableRenameColumn,
2627
) {
27-
if len(n.Cmds) > 1 {
28+
if len(n.Cmds) > 1 && !b.EvalCtx().Settings.Version.IsActive(b, clusterversion.V26_1) {
2829
panic(scerrors.NotImplementedError(n))
2930
}
3031
alterColumnPreChecks(b, tn, tbl, t.Column)
@@ -123,16 +124,10 @@ func checkNewColumnNameConflicts(b BuildCtx, tableID catid.DescID, oldName, newN
123124
"column name %q conflicts with a system column name",
124125
tree.ErrString(&newName)))
125126
}
126-
if current == scpb.Status_PUBLIC {
127-
if target == scpb.ToAbsent {
128-
panic(pgerror.Newf(pgcode.ObjectNotInPrerequisiteState, "column %q being dropped, try again later", tree.ErrString(&newName)))
129-
}
127+
if current == scpb.Status_PUBLIC && target != scpb.ToAbsent {
130128
tableName := b.QueryByID(tableID).FilterNamespace().MustGetOneElement()
131129
panic(sqlerrors.NewColumnAlreadyExistsInRelationError(tree.ErrString(&newName), tableName.Name))
132130
}
133-
if current == scpb.Status_ABSENT && target == scpb.ToPublic {
134-
panic(pgerror.Newf(pgcode.ObjectNotInPrerequisiteState, "column %q being dropped, try again later", tree.ErrString(&newName)))
135-
}
136131
}
137132
})
138133
}

0 commit comments

Comments
 (0)