Skip to content

Commit e5a6ded

Browse files
committed
sql/schemachanger: add cluster setting to enabled/disable statements
Previously, we had no way to control individual statements enabled / disable in the declarative schema changer. This meant that the only option due to bugs was to fully disable the decalrative schema changer. To address this, this patch adds support for control statements in the decalrative schema changer using the statement tags. Epic: none Release note (sql change): Added a cluster setting sql.schema.force_declarative_statements to enable / disable DDL in the declartive schema changer .
1 parent 66c9e4f commit e5a6ded

File tree

7 files changed

+279
-45
lines changed

7 files changed

+279
-45
lines changed

pkg/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,7 @@ ALL_TESTS = [
521521
"//pkg/sql/schemachanger/corpus:corpus_test",
522522
"//pkg/sql/schemachanger/rel:rel_test",
523523
"//pkg/sql/schemachanger/scbackup:scbackup_test",
524+
"//pkg/sql/schemachanger/scbuild/internal/scbuildstmt:scbuildstmt_test",
524525
"//pkg/sql/schemachanger/scbuild:scbuild_test",
525526
"//pkg/sql/schemachanger/scdecomp:scdecomp_test",
526527
"//pkg/sql/schemachanger/scexec/backfiller:backfiller_test",
@@ -1974,6 +1975,7 @@ GO_TARGETS = [
19741975
"//pkg/sql/schemachanger/scbackup:scbackup",
19751976
"//pkg/sql/schemachanger/scbackup:scbackup_test",
19761977
"//pkg/sql/schemachanger/scbuild/internal/scbuildstmt:scbuildstmt",
1978+
"//pkg/sql/schemachanger/scbuild/internal/scbuildstmt:scbuildstmt_test",
19771979
"//pkg/sql/schemachanger/scbuild:scbuild",
19781980
"//pkg/sql/schemachanger/scbuild:scbuild_test",
19791981
"//pkg/sql/schemachanger/scdecomp:scdecomp",

pkg/sql/logictest/testdata/logic_test/new_schema_changer

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1501,3 +1501,33 @@ public admin
15011501

15021502
statement ok
15031503
COMMIT
1504+
1505+
1506+
subtest statement-control
1507+
1508+
# Disable ALTER table completely and validate it works
1509+
statement ok
1510+
SET use_declarative_schema_changer = 'on'
1511+
1512+
statement ok
1513+
SET CLUSTER SETTING sql.schema.force_declarative_statements='!ALTER TABLE'
1514+
1515+
statement ok
1516+
CREATE TABLE stmt_ctrl(n int primary key)
1517+
1518+
statement error pgcode 0A000 cannot explain a statement which is not supported by the declarative schema changer
1519+
EXPLAIN (DDL) ALTER TABLE stmt_ctrl ADD COLUMN j BOOL
1520+
1521+
statement ok
1522+
ALTER TABLE stmt_ctrl ADD COLUMN fallback_works BOOL
1523+
1524+
# Validate that CREATE SEQUENCE is disabled and can be re-enabled with the same flag.
1525+
statement error pgcode 0A000 cannot explain a statement which is not supported by the declarative schema changer
1526+
EXPLAIN (DDL) CREATE SEQUENCE sq2
1527+
1528+
statement ok
1529+
SET CLUSTER SETTING sql.schema.force_declarative_statements='+CREATE SEQUENCE'
1530+
1531+
skipif config local-mixed-22.2-23.1
1532+
statement ok
1533+
EXPLAIN (DDL) CREATE SEQUENCE sq2

pkg/sql/schemachanger/scbuild/internal/scbuildstmt/BUILD.bazel

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
load("@io_bazel_rules_go//go:def.bzl", "go_library")
1+
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
22

33
go_library(
44
name = "scbuildstmt",
@@ -28,6 +28,7 @@ go_library(
2828
"drop_view.go",
2929
"helpers.go",
3030
"process.go",
31+
"statement_control.go",
3132
],
3233
importpath = "github.com/cockroachdb/cockroach/pkg/sql/schemachanger/scbuild/internal/scbuildstmt",
3334
visibility = ["//pkg/sql/schemachanger/scbuild:__subpackages__"],
@@ -38,6 +39,7 @@ go_library(
3839
"//pkg/keys",
3940
"//pkg/security/username",
4041
"//pkg/server/telemetry",
42+
"//pkg/settings",
4143
"//pkg/settings/cluster",
4244
"//pkg/sql/catalog",
4345
"//pkg/sql/catalog/catenumpb",
@@ -78,3 +80,14 @@ go_library(
7880
"@com_github_lib_pq//oid",
7981
],
8082
)
83+
84+
go_test(
85+
name = "scbuildstmt_test",
86+
srcs = ["process_test.go"],
87+
args = ["-test.timeout=295s"],
88+
embed = [":scbuildstmt"],
89+
deps = [
90+
"//pkg/settings",
91+
"@com_github_stretchr_testify//require",
92+
],
93+
)

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

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import (
2525
type supportedStatement struct {
2626
// fn is a function to perform a schema change.
2727
fn interface{}
28+
// statementTag tag for this statement.
29+
statementTag string
2830
// checks contains a coarse-grained function to filter out most
2931
// unsupported statements.
3032
// It's possible for certain unsupported statements to pass it but will
@@ -44,28 +46,32 @@ var supportedStatements = map[reflect.Type]supportedStatement{
4446
// Alter table will have commands individually whitelisted via the
4547
// supportedAlterTableStatements list, so wwe will consider it fully supported
4648
// here.
47-
reflect.TypeOf((*tree.AlterTable)(nil)): {fn: AlterTable, on: true, checks: alterTableChecks},
48-
reflect.TypeOf((*tree.CreateIndex)(nil)): {fn: CreateIndex, on: true, checks: isV231Active},
49-
reflect.TypeOf((*tree.DropDatabase)(nil)): {fn: DropDatabase, on: true, checks: isV221Active},
50-
reflect.TypeOf((*tree.DropOwnedBy)(nil)): {fn: DropOwnedBy, on: true, checks: isV222Active},
51-
reflect.TypeOf((*tree.DropSchema)(nil)): {fn: DropSchema, on: true, checks: isV221Active},
52-
reflect.TypeOf((*tree.DropSequence)(nil)): {fn: DropSequence, on: true, checks: isV221Active},
53-
reflect.TypeOf((*tree.DropTable)(nil)): {fn: DropTable, on: true, checks: isV221Active},
54-
reflect.TypeOf((*tree.DropType)(nil)): {fn: DropType, on: true, checks: isV221Active},
55-
reflect.TypeOf((*tree.DropView)(nil)): {fn: DropView, on: true, checks: isV221Active},
56-
reflect.TypeOf((*tree.CommentOnDatabase)(nil)): {fn: CommentOnDatabase, on: true, checks: isV222Active},
57-
reflect.TypeOf((*tree.CommentOnSchema)(nil)): {fn: CommentOnSchema, on: true, checks: isV222Active},
58-
reflect.TypeOf((*tree.CommentOnTable)(nil)): {fn: CommentOnTable, on: true, checks: isV222Active},
59-
reflect.TypeOf((*tree.CommentOnColumn)(nil)): {fn: CommentOnColumn, on: true, checks: isV222Active},
60-
reflect.TypeOf((*tree.CommentOnIndex)(nil)): {fn: CommentOnIndex, on: true, checks: isV222Active},
61-
reflect.TypeOf((*tree.CommentOnConstraint)(nil)): {fn: CommentOnConstraint, on: true, checks: isV222Active},
62-
reflect.TypeOf((*tree.DropIndex)(nil)): {fn: DropIndex, on: true, checks: isV231Active},
63-
reflect.TypeOf((*tree.DropFunction)(nil)): {fn: DropFunction, on: true, checks: isV231Active},
64-
reflect.TypeOf((*tree.CreateRoutine)(nil)): {fn: CreateFunction, on: true, checks: isV231Active},
65-
reflect.TypeOf((*tree.CreateSchema)(nil)): {fn: CreateSchema, on: true, checks: isV232Active},
66-
reflect.TypeOf((*tree.CreateSequence)(nil)): {fn: CreateSequence, on: true, checks: isV232Active},
49+
reflect.TypeOf((*tree.AlterTable)(nil)): {fn: AlterTable, statementTag: tree.AlterTableTag, on: true, checks: alterTableChecks},
50+
reflect.TypeOf((*tree.CreateIndex)(nil)): {fn: CreateIndex, statementTag: tree.CreateIndexTag, on: true, checks: isV231Active},
51+
reflect.TypeOf((*tree.DropDatabase)(nil)): {fn: DropDatabase, statementTag: tree.DropDatabaseTag, on: true, checks: isV221Active},
52+
reflect.TypeOf((*tree.DropOwnedBy)(nil)): {fn: DropOwnedBy, statementTag: tree.DropOwnedByTag, on: true, checks: isV222Active},
53+
reflect.TypeOf((*tree.DropSchema)(nil)): {fn: DropSchema, statementTag: tree.DropSchemaTag, on: true, checks: isV221Active},
54+
reflect.TypeOf((*tree.DropSequence)(nil)): {fn: DropSequence, statementTag: tree.DropSequenceTag, on: true, checks: isV221Active},
55+
reflect.TypeOf((*tree.DropTable)(nil)): {fn: DropTable, statementTag: tree.DropTableTag, on: true, checks: isV221Active},
56+
reflect.TypeOf((*tree.DropType)(nil)): {fn: DropType, statementTag: tree.DropTypeTag, on: true, checks: isV221Active},
57+
reflect.TypeOf((*tree.DropView)(nil)): {fn: DropView, statementTag: tree.DropViewTag, on: true, checks: isV221Active},
58+
reflect.TypeOf((*tree.CommentOnConstraint)(nil)): {fn: CommentOnConstraint, statementTag: tree.CommentOnConstraintTag, on: true, checks: isV222Active},
59+
reflect.TypeOf((*tree.CommentOnDatabase)(nil)): {fn: CommentOnDatabase, statementTag: tree.CommentOnDatabaseTag, on: true, checks: isV222Active},
60+
reflect.TypeOf((*tree.CommentOnSchema)(nil)): {fn: CommentOnSchema, statementTag: tree.CommentOnSchemaTag, on: true, checks: isV222Active},
61+
reflect.TypeOf((*tree.CommentOnTable)(nil)): {fn: CommentOnTable, statementTag: tree.CommentOnTableTag, on: true, checks: isV222Active},
62+
reflect.TypeOf((*tree.CommentOnColumn)(nil)): {fn: CommentOnColumn, statementTag: tree.CommentOnColumnTag, on: true, checks: isV222Active},
63+
reflect.TypeOf((*tree.CommentOnIndex)(nil)): {fn: CommentOnIndex, statementTag: tree.CommentOnIndexTag, on: true, checks: isV222Active},
64+
reflect.TypeOf((*tree.DropIndex)(nil)): {fn: DropIndex, statementTag: tree.DropIndexTag, on: true, checks: isV231Active},
65+
reflect.TypeOf((*tree.DropFunction)(nil)): {fn: DropFunction, statementTag: tree.DropFunctionTag, on: true, checks: isV231Active},
66+
reflect.TypeOf((*tree.CreateRoutine)(nil)): {fn: CreateFunction, statementTag: tree.CreateRoutineTag, on: true, checks: isV231Active},
67+
reflect.TypeOf((*tree.CreateSchema)(nil)): {fn: CreateSchema, statementTag: tree.CreateSchemaTag, on: true, checks: isV232Active},
68+
reflect.TypeOf((*tree.CreateSequence)(nil)): {fn: CreateSequence, statementTag: tree.CreateSequenceTag, on: true, checks: isV232Active},
6769
}
6870

71+
// supportedStatementTags tracks statement tags which are implemented
72+
// by the declarative schema changer.
73+
var supportedStatementTags = map[string]struct{}{}
74+
6975
func init() {
7076
boolType := reflect.TypeOf((*bool)(nil)).Elem()
7177
// Check function signatures inside the supportedStatements map.
@@ -99,6 +105,9 @@ func init() {
99105
statementType, checks))
100106
}
101107
}
108+
// Fetch the statement tag using the statement tag method on the type,
109+
// we can use this as a blacklist of blocked schema changes.
110+
supportedStatementTags[statementEntry.statementTag] = struct{}{}
102111
}
103112
}
104113

@@ -157,9 +166,16 @@ func isFullySupportedWithFalsePositiveInternal(
157166
// Process dispatches on the statement type to populate the BuilderState
158167
// embedded in the BuildCtx. Any error will be panicked.
159168
func Process(b BuildCtx, n tree.Statement) {
169+
newSchemaChangerMode := b.EvalCtx().SessionData().NewSchemaChangerMode
170+
// Check if the feature is either forced disabled or enabled,
171+
// using a cluster setting.
172+
disabledStatements := getSchemaChangerStatementControl(&b.ClusterSettings().SV)
173+
if forcedEnabled := disabledStatements.CheckStatementControl(n); forcedEnabled {
174+
newSchemaChangerMode = sessiondatapb.UseNewSchemaChangerUnsafe
175+
}
160176
// Run a few "quick checks" to see if the statement is not supported.
161177
if !IsFullySupportedWithFalsePositive(n, b.EvalCtx().Settings.Version.ActiveVersion(b),
162-
b.EvalCtx().SessionData().NewSchemaChangerMode) {
178+
newSchemaChangerMode) {
163179
panic(scerrors.NotImplementedError(n))
164180
}
165181

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright 2023 The Cockroach Authors.
2+
//
3+
// Use of this software is governed by the Business Source License
4+
// included in the file licenses/BSL.txt.
5+
//
6+
// As of the Change Date specified in that file, in accordance with
7+
// the Business Source License, use of this software will be governed
8+
// by the Apache License, Version 2.0, included in the file
9+
// licenses/APL.txt.
10+
11+
package scbuildstmt
12+
13+
import (
14+
"reflect"
15+
"strings"
16+
"testing"
17+
18+
"github.com/cockroachdb/cockroach/pkg/settings"
19+
"github.com/stretchr/testify/require"
20+
)
21+
22+
func TestSupportedStatements(t *testing.T) {
23+
sv := &settings.Values{}
24+
// Non-existent tags should error out.
25+
require.Error(t, schemaChangerDisabledStatements.Validate(sv, "FAKE STATEMENT"))
26+
// Generate the full set of statements
27+
allTags := strings.Builder{}
28+
noTags := strings.Builder{}
29+
first := true
30+
for typ, stmt := range supportedStatements {
31+
require.Greaterf(t, len(stmt.statementTag), 0, "statement tag is missing %v %v", typ, stmt)
32+
// Validate tags matches the statement tag
33+
typTag, found := typ.MethodByName("StatementTag")
34+
require.True(t, found, "unable to find stmt: %v %v", typ, stmt)
35+
ret := typTag.Func.Call([]reflect.Value{reflect.New(typ.Elem())})
36+
require.Equal(t, ret[0].String(), stmt.statementTag, "statement tag is different in AST")
37+
// Validate all tags are supported.
38+
require.NoError(t, schemaChangerDisabledStatements.Validate(sv, "+"+stmt.statementTag))
39+
require.NoError(t, schemaChangerDisabledStatements.Validate(sv, "!"+stmt.statementTag))
40+
// Validate all of them can be specified at once.
41+
if !first {
42+
allTags.WriteString(",")
43+
noTags.WriteString(",")
44+
}
45+
first = false
46+
allTags.WriteString("+")
47+
allTags.WriteString(stmt.statementTag)
48+
noTags.WriteString("!")
49+
noTags.WriteString(stmt.statementTag)
50+
}
51+
require.NoError(t, schemaChangerDisabledStatements.Validate(sv, allTags.String()))
52+
require.NoError(t, schemaChangerDisabledStatements.Validate(sv, noTags.String()))
53+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Copyright 2023 The Cockroach Authors.
2+
//
3+
// Use of this software is governed by the Business Source License
4+
// included in the file licenses/BSL.txt.
5+
//
6+
// As of the Change Date specified in that file, in accordance with
7+
// the Business Source License, use of this software will be governed
8+
// by the Apache License, Version 2.0, included in the file
9+
// licenses/APL.txt.
10+
11+
package scbuildstmt
12+
13+
import (
14+
"strings"
15+
16+
"github.com/cockroachdb/cockroach/pkg/settings"
17+
"github.com/cockroachdb/cockroach/pkg/sql/schemachanger/scerrors"
18+
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
19+
"github.com/cockroachdb/errors"
20+
)
21+
22+
// schemaStatementControl track if a statement tag is enabled or disabled
23+
// forcefully by the user.
24+
type schemaStatementControl map[string]bool
25+
26+
// schemaChangerDisabledStatements statements which are disabled
27+
// for the declarative schema changer. Users can specify statement
28+
// tags for each statement and a "!" symbol in front can have the opposite
29+
// effect to force enable fully unimplemented features.
30+
var schemaChangerDisabledStatements = func() *settings.StringSetting {
31+
return settings.RegisterValidatedStringSetting(
32+
settings.TenantWritable,
33+
"sql.schema.force_declarative_statements",
34+
"allows force enabling / disabling declarative schema changer for specific statements",
35+
"",
36+
func(values *settings.Values, s string) error {
37+
if s == "" {
38+
return nil
39+
}
40+
// First split the string into individual tags.
41+
tags := strings.Split(s, ",")
42+
for _, tag := range tags {
43+
tag = strings.ToUpper(strings.TrimSpace(tag))
44+
if len(tag) > 0 && (tag[0] == '+' || tag[0] == '!') {
45+
tag = tag[1:]
46+
} else {
47+
return errors.Errorf("tag is not properly formatted, must start with '+' or '!' (%s)", tag)
48+
}
49+
if _, ok := supportedStatementTags[tag]; !ok {
50+
return errors.Errorf("statement tag %q is not controlled by the declarative schema changer", tag)
51+
}
52+
}
53+
return nil
54+
})
55+
}()
56+
57+
// CheckStatementControl if a statement is forced to disabled or enabled. If a
58+
// statement is disabled then an not implemented error will be panicked. Otherwise,
59+
// a flag is returned indicating if this statement has been *forced* to be enabled.
60+
func (c schemaStatementControl) CheckStatementControl(n tree.Statement) (forceEnabled bool) {
61+
// This map is only created *if* any force flags are set.
62+
if c == nil {
63+
return false
64+
}
65+
enabledOrDisabled, found := c[n.StatementTag()]
66+
if !found {
67+
return false
68+
}
69+
if !enabledOrDisabled {
70+
panic(scerrors.NotImplementedErrorf(n,
71+
"statement has been disabled via cluster setting"))
72+
}
73+
return enabledOrDisabled
74+
}
75+
76+
// GetSchemaChangerStatementControl returns a map of statements that
77+
// are explicitly disabled by administrators for the declarative schema
78+
// changer.
79+
func getSchemaChangerStatementControl(sv *settings.Values) schemaStatementControl {
80+
statements := schemaChangerDisabledStatements.Get(sv)
81+
var statementMap schemaStatementControl
82+
for _, tag := range strings.Split(statements, ",") {
83+
tag = strings.ToUpper(strings.TrimSpace(tag))
84+
if len(tag) == 0 {
85+
continue
86+
}
87+
enabledOrDisabled := true
88+
tagStart := tag[0]
89+
tag = tag[1:]
90+
// If the tag starts with a ! its disabled.
91+
if tagStart == '!' {
92+
enabledOrDisabled = false
93+
}
94+
if statementMap == nil {
95+
statementMap = make(schemaStatementControl)
96+
}
97+
statementMap[tag] = enabledOrDisabled
98+
}
99+
return statementMap
100+
}

0 commit comments

Comments
 (0)