Skip to content

Commit e306fc9

Browse files
committed
schemachanger: handle manual schema_locked setting in declarative
This storage parameter is special, so this requires logic to use the pre-existing TableSchemaLocked element rather than the normal storage param element. As part of this, we partially revert bc3f228 and refactor the check to be a little simpler since we don't actually need to handle the mixed version case now. Release note: None
1 parent f0c3852 commit e306fc9

File tree

6 files changed

+147
-39
lines changed

6 files changed

+147
-39
lines changed

pkg/sql/alter_table.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2519,10 +2519,8 @@ func (p *planner) checkSchemaChangeIsAllowed(
25192519
return nil
25202520
}
25212521
// Check if this schema change is on the allowed list, which will only
2522-
// be simple non-back filling schema changes. All commands except set/reset
2523-
// schema_locked are unsupported before 25.2
2524-
preventedBySchemaLocked := !p.ExecCfg().Settings.Version.IsActive(ctx, clusterversion.V25_3) &&
2525-
!tree.IsSetOrResetSchemaLocked(n)
2522+
// be simple non-back filling schema changes.
2523+
preventedBySchemaLocked := false
25262524
// These schema changes are allowed because the events generated will always
25272525
// be ignored by the schema_locked. The tableEventFilter (in CDC schemafeed)
25282526
// only cares about a limited number of events:

pkg/sql/logictest/testdata/logic_test/distsql_stats

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1900,14 +1900,14 @@ ALTER TABLE greeting_stats INJECT STATISTICS '$stats'
19001900
query T
19011901
SELECT feature_name FROM crdb_internal.feature_usage
19021902
WHERE feature_name in ('job.typedesc_schema_change.successful',
1903-
'job.schema_change.successful',
1903+
'job.new_schema_change.successful',
19041904
'job.create_stats.successful',
19051905
'job.auto_create_stats.successful') AND
19061906
usage_count > 0
19071907
ORDER BY feature_name DESC
19081908
----
19091909
job.typedesc_schema_change.successful
1910-
job.schema_change.successful
1910+
job.new_schema_change.successful
19111911
job.create_stats.successful
19121912
job.auto_create_stats.successful
19131913

pkg/sql/logictest/testdata/logic_test/schema_locked

Lines changed: 104 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,71 @@ ALTER TABLE ref CONFIGURE ZONE USING num_replicas = 11;
174174

175175
subtest end
176176

177+
# Test SET/RESET schema_locked with varying session settings.
178+
subtest set_reset_schema_locked_session_settings
179+
180+
statement ok
181+
CREATE TABLE t_sl (i INT PRIMARY KEY) WITH (schema_locked = false)
182+
183+
# Test SET schema_locked=true
184+
statement ok
185+
ALTER TABLE t_sl SET (schema_locked=true)
186+
187+
query TT
188+
SHOW CREATE TABLE t_sl
189+
----
190+
t_sl CREATE TABLE public.t_sl (
191+
i INT8 NOT NULL,
192+
CONSTRAINT t_sl_pkey PRIMARY KEY (i ASC)
193+
) WITH (schema_locked = true);
194+
195+
# Test SET schema_locked=false
196+
statement ok
197+
ALTER TABLE t_sl SET (schema_locked=false)
198+
199+
query TT
200+
SHOW CREATE TABLE t_sl
201+
----
202+
t_sl CREATE TABLE public.t_sl (
203+
i INT8 NOT NULL,
204+
CONSTRAINT t_sl_pkey PRIMARY KEY (i ASC)
205+
);
206+
207+
statement ok
208+
SET create_table_with_schema_locked = 'true'
209+
210+
# Test RESET schema_locked - should reset to session setting (true).
211+
statement ok
212+
ALTER TABLE t_sl RESET (schema_locked)
213+
214+
query TT
215+
SHOW CREATE TABLE t_sl
216+
----
217+
t_sl CREATE TABLE public.t_sl (
218+
i INT8 NOT NULL,
219+
CONSTRAINT t_sl_pkey PRIMARY KEY (i ASC)
220+
) WITH (schema_locked = true);
221+
222+
statement ok
223+
SET create_table_with_schema_locked = 'false'
224+
225+
# Test RESET schema_locked - should reset to session setting (false).
226+
statement ok
227+
ALTER TABLE t_sl RESET (schema_locked)
228+
229+
query TT
230+
SHOW CREATE TABLE t_sl
231+
----
232+
t_sl CREATE TABLE public.t_sl (
233+
i INT8 NOT NULL,
234+
CONSTRAINT t_sl_pkey PRIMARY KEY (i ASC)
235+
);
236+
237+
statement ok
238+
DROP TABLE t_sl
239+
240+
subtest end
241+
177242

178243
# Validate schema_locked can be unset properly in add column txns.
179244
subtest regression_147993
@@ -228,15 +293,18 @@ subtest regression_150484
228293
statement ok
229294
CREATE TABLE t_150484 (n INT, m INT, FAMILY (n, m)) WITH (schema_locked=true);
230295

231-
# Combining SET (schema_locked=false) with DROP COLUMN should fail.
296+
# Combining SET (schema_locked=false) with DROP COLUMN should fail in the legacy schema changer.
297+
onlyif config local-legacy-schema-changer
232298
statement error pgcode 57000 pq: this schema change is disallowed because table "t_150484" is locked
233299
ALTER TABLE t_150484 SET (schema_locked=false), DROP COLUMN n;
234300

235-
# Combining RESET (schema_locked) with ADD COLUMN should fail.
301+
# Combining RESET (schema_locked) with ADD COLUMN should fail in the legacy schema changer.
302+
onlyif config local-legacy-schema-changer
236303
statement error pgcode 57000 pq: this schema change is disallowed because table "t_150484" is locked
237304
ALTER TABLE t_150484 RESET (schema_locked), ADD COLUMN k INT;
238305

239-
# Verify the table is still locked and unchanged.
306+
# Verify the table is still locked and unchanged in the legacy schema changer.
307+
onlyif config local-legacy-schema-changer
240308
query TT
241309
SHOW CREATE TABLE t_150484
242310
----
@@ -248,13 +316,42 @@ t_150484 CREATE TABLE public.t_150484 (
248316
FAMILY fam_0_n_m_rowid (n, m, rowid)
249317
) WITH (schema_locked = true);
250318

251-
# Confirm that SET (schema_locked=false) alone still works.
319+
# In the declarative schema changer, the above statements are allowed, as we
320+
# take care to always unlock the table first.
321+
skipif config local-legacy-schema-changer local-mixed-25.4
252322
statement ok
253-
ALTER TABLE t_150484 SET (schema_locked=false);
323+
ALTER TABLE t_150484 SET (schema_locked=false), DROP COLUMN n;
254324

255-
# Now schema changes should work.
325+
skipif config local-legacy-schema-changer local-mixed-25.4
326+
query TT
327+
SHOW CREATE TABLE t_150484
328+
----
329+
t_150484 CREATE TABLE public.t_150484 (
330+
m INT8 NULL,
331+
rowid INT8 NOT VISIBLE NOT NULL DEFAULT unique_rowid(),
332+
CONSTRAINT t_150484_pkey PRIMARY KEY (rowid ASC),
333+
FAMILY fam_0_n_m_rowid (m, rowid)
334+
);
335+
336+
skipif config local-legacy-schema-changer local-mixed-25.4
337+
statement ok
338+
ALTER TABLE t_150484 SET (schema_locked=true)
339+
340+
skipif config local-legacy-schema-changer local-mixed-25.4
256341
statement ok
257-
ALTER TABLE t_150484 DROP COLUMN n;
342+
ALTER TABLE t_150484 RESET (schema_locked), ADD COLUMN k INT;
343+
344+
skipif config local-legacy-schema-changer local-mixed-25.4
345+
query TT
346+
SHOW CREATE TABLE t_150484
347+
----
348+
t_150484 CREATE TABLE public.t_150484 (
349+
m INT8 NULL,
350+
rowid INT8 NOT VISIBLE NOT NULL DEFAULT unique_rowid(),
351+
k INT8 NULL,
352+
CONSTRAINT t_150484_pkey PRIMARY KEY (rowid ASC),
353+
FAMILY fam_0_n_m_rowid (m, rowid, k)
354+
);
258355

259356
statement ok
260357
DROP TABLE t_150484;

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

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

88
import (
9+
"strconv"
910
"strings"
1011

1112
"github.com/cockroachdb/cockroach/pkg/sql/catalog/catpb"
@@ -30,10 +31,6 @@ func validateStorageParamKey(t tree.NodeFormatter, key string) {
3031
if key == catpb.RBRUsingConstraintTableSettingName {
3132
panic(scerrors.NotImplementedErrorf(t, "infer_rbr_region_col_using_constraint not implemented yet"))
3233
}
33-
if key == "schema_locked" {
34-
// schema_locked has a dedicated element and will be handled differently.
35-
panic(scerrors.NotImplementedErrorf(t, "schema_locked not implemented yet"))
36-
}
3734
}
3835

3936
// AlterTableSetStorageParams implements ALTER TABLE ... SET {storage_param} in the declarative schema changer.
@@ -60,6 +57,13 @@ func AlterTableSetStorageParams(
6057
}
6158
key := param.Key
6259
validateStorageParamKey(t, key)
60+
61+
// schema_locked uses a dedicated TableSchemaLocked element.
62+
if key == "schema_locked" {
63+
setSchemaLocked(b, tbl, val)
64+
continue
65+
}
66+
6367
// Do extra validation for exclude_data_from_backup
6468
validateExcludeDataFromBackup(b, tbl, key)
6569
currElem := b.QueryByID(tbl.TableID).FilterTableStorageParam().Filter(
@@ -149,6 +153,12 @@ func AlterTableResetStorageParams(
149153
panic(err)
150154
}
151155

156+
// schema_locked uses a dedicated TableSchemaLocked element.
157+
if key == "schema_locked" {
158+
setSchemaLocked(b, tbl, resetVal)
159+
continue
160+
}
161+
152162
// Find and drop the existing element.
153163
currElem := b.QueryByID(tbl.TableID).FilterTableStorageParam().Filter(
154164
func(_ scpb.Status, _ scpb.TargetStatus, e *scpb.TableStorageParam) bool {
@@ -170,3 +180,18 @@ func AlterTableResetStorageParams(
170180
}
171181
}
172182
}
183+
184+
// setSchemaLocked sets the schema_locked storage parameter using the dedicated
185+
// TableSchemaLocked element. The val is parsed as a boolean; empty string is
186+
// treated as false.
187+
func setSchemaLocked(b BuildCtx, tbl *scpb.Table, val string) {
188+
locked, _ := strconv.ParseBool(val)
189+
currElem := b.QueryByID(tbl.TableID).FilterTableSchemaLocked().MustGetZeroOrOneElement()
190+
if locked {
191+
// Setting schema_locked=true means TableSchemaLocked should be PUBLIC.
192+
b.Add(&scpb.TableSchemaLocked{TableID: tbl.TableID})
193+
} else if currElem != nil {
194+
// Setting schema_locked=false means TableSchemaLocked should be ABSENT.
195+
b.Drop(currElem)
196+
}
197+
}

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

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"sort"
1111

1212
"github.com/cockroachdb/cockroach/pkg/build"
13-
"github.com/cockroachdb/cockroach/pkg/clusterversion"
1413
"github.com/cockroachdb/cockroach/pkg/sql/catalog"
1514
"github.com/cockroachdb/cockroach/pkg/sql/catalog/catenumpb"
1615
"github.com/cockroachdb/cockroach/pkg/sql/catalog/catpb"
@@ -1191,13 +1190,10 @@ func checkTableSchemaChangePrerequisites(
11911190
schemaLocked := tableElements.FilterTableSchemaLocked().MustGetZeroOrOneElement()
11921191
// No-op by default unless schema_locked has been setup.
11931192
maybeCleanupSchemaLockedFn = func() {}
1194-
if schemaLocked != nil && !tree.IsSetOrResetSchemaLocked(n) {
1195-
// Before 25.2 we don't support auto-unsetting schema locked.
1196-
if !b.ClusterSettings().Version.IsActive(b, clusterversion.V25_2) {
1197-
ns := tableElements.FilterNamespace().MustGetOneElement()
1198-
panic(sqlerrors.NewSchemaChangeOnLockedTableErr(ns.Name))
1199-
}
1200-
// Unset schema_locked for the user.
1193+
hasSchemaLockedChange := tree.HasSetOrResetSchemaLocked(n)
1194+
if schemaLocked != nil && !hasSchemaLockedChange {
1195+
// If the user is not explicitly setting schema_locked, then we should
1196+
// use the auto-unset logic.
12011197
b.DropTransient(schemaLocked)
12021198
maybeCleanupSchemaLockedFn = func() {
12031199
maybeCleanupSchemaLocked(b, tableElements.FilterTable().MustGetOneElement().TableID)

pkg/sql/sem/tree/schema_helpers.go

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,34 +7,26 @@ package tree
77

88
import "slices"
99

10-
// IsSetOrResetSchemaLocked returns true if `n` contains only commands to
11-
// set/reset storage parameters including "schema_locked", with no other
12-
// schema-changing commands. This prevents bypassing schema_locked protection
13-
// by combining SET (schema_locked=false) with other operations like DROP
14-
// COLUMN in the same ALTER TABLE statement.
15-
func IsSetOrResetSchemaLocked(n Statement) bool {
10+
// HasSetOrResetSchemaLocked checks if the statement contains a command to
11+
// set/reset the "schema_locked" storage parameter.
12+
func HasSetOrResetSchemaLocked(n Statement) (hasSchemaLockedChange bool) {
1613
alterStmt, ok := n.(*AlterTable)
1714
if !ok {
1815
return false
1916
}
20-
hasSchemaLockedChange := false
2117
for _, cmd := range alterStmt.Cmds {
2218
switch cmd := cmd.(type) {
2319
case *AlterTableSetStorageParams:
2420
if cmd.StorageParams.GetVal("schema_locked") != nil {
25-
hasSchemaLockedChange = true
21+
return true
2622
}
2723
case *AlterTableResetStorageParams:
2824
if slices.Contains(cmd.Params, "schema_locked") {
29-
hasSchemaLockedChange = true
25+
return true
3026
}
31-
default:
32-
// Any other command type means this is not a pure storage parameter
33-
// change, so we should not allow bypassing schema_locked checks.
34-
return false
3527
}
3628
}
37-
return hasSchemaLockedChange
29+
return false
3830
}
3931

4032
// IsAllowedLDRSchemaChange returns true if the schema change statement is

0 commit comments

Comments
 (0)