Skip to content

Commit bf4382d

Browse files
committed
sql: add ALTER TABLE ... SET/ADD IDENTITY to the declarative schema changer
This change adds support for `ALTER TABLE ... SET/ADD IDENTITY` to the declarative schema changer. This schema change operation runs using the legacy schema changer in versions older than 26.1. Epic: CRDB-31283 Fixes #142918 Release note (sql change): `ALTER TABLE ... SET/ADD GENERATED AS IDENTITY` is supported by the declerative schema changer in 26.1 or later.
1 parent 6f0252f commit bf4382d

File tree

41 files changed

+1338
-50
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1338
-50
lines changed

pkg/ccl/schemachangerccl/sctestbackupccl/backup_base_generated_test.go

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

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ go_library(
77
"alter_table.go",
88
"alter_table_add_column.go",
99
"alter_table_add_constraint.go",
10+
"alter_table_alter_column_add_identity.go",
1011
"alter_table_alter_column_drop_not_null.go",
1112
"alter_table_alter_column_drop_stored.go",
1213
"alter_table_alter_column_set_default.go",
14+
"alter_table_alter_column_set_identity.go",
1315
"alter_table_alter_column_set_not_null.go",
1416
"alter_table_alter_column_set_on_update.go",
1517
"alter_table_alter_column_type.go",

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ var supportedAlterTableStatements = map[reflect.Type]supportedAlterTableCommand{
4545
reflect.TypeOf((*tree.AlterTableRenameColumn)(nil)): {fn: alterTableRenameColumn, on: true, checks: isV254Active},
4646
reflect.TypeOf((*tree.AlterTableDropStored)(nil)): {fn: alterTableDropStored, on: true, checks: isV261Active},
4747
reflect.TypeOf((*tree.AlterTableRenameConstraint)(nil)): {fn: alterTableRenameConstraint, on: true, checks: isV261Active},
48+
reflect.TypeOf((*tree.AlterTableSetIdentity)(nil)): {fn: alterTableSetIdentity, on: true, checks: isV261Active},
49+
reflect.TypeOf((*tree.AlterTableAddIdentity)(nil)): {fn: alterTableAddIdentity, on: true, checks: isV261Active},
4850
}
4951

5052
func init() {

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

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -399,27 +399,17 @@ func alterTableAddColumnSerialOrGeneratedIdentity(
399399
return catalog.UseUnorderedRowID(*d), nil
400400
}
401401

402-
// Start with a fixed sequence number and find the first one
403-
// that is free.
404-
nameBase := tree.Name(tn.Table() + "_" + string(d.Name) + "_seq")
405-
seqName := tree.NewTableNameWithSchema(
406-
tn.CatalogName,
407-
tn.SchemaName,
408-
nameBase)
402+
return alterTableCreateColumnSequence(b, d, tn, serialNormalizationMode, defType)
403+
}
409404

410-
for idx := 0; ; idx++ {
411-
ers := b.ResolveRelation(seqName.ToUnresolvedObjectName(),
412-
ResolveParams{
413-
IsExistenceOptional: true,
414-
RequiredPrivilege: privilege.USAGE,
415-
WithOffline: true, // We search sequence with provided name, including offline ones.
416-
ResolveTypes: true, // Check for collisions with type names.
417-
})
418-
if ers.IsEmpty() {
419-
break
420-
}
421-
seqName.ObjectName = tree.Name(fmt.Sprintf("%s%d", nameBase, idx))
422-
}
405+
func alterTableCreateColumnSequence(
406+
b BuildCtx,
407+
d *tree.ColumnTableDef,
408+
tn *tree.TableName,
409+
serialNormalizationMode sessiondatapb.SerialNormalizationMode,
410+
defType *types.T,
411+
) (newDef *tree.ColumnTableDef, colDefaultExpression *scpb.Expression) {
412+
seqName := getNextAvailableSeqName(b, d.Name, tn)
423413

424414
seqOptions, err := catalog.SequenceOptionsFromNormalizationMode(serialNormalizationMode, b.ClusterSettings(), d, defType)
425415
if err != nil {
@@ -452,6 +442,31 @@ func alterTableAddColumnSerialOrGeneratedIdentity(
452442
}
453443
}
454444

445+
func getNextAvailableSeqName(b BuildCtx, colName tree.Name, tn *tree.TableName) *tree.TableName {
446+
// Start with a fixed sequence number and find the first one
447+
// that is free.
448+
nameBase := tree.Name(tn.Table() + "_" + string(colName) + "_seq")
449+
seqName := tree.NewTableNameWithSchema(
450+
tn.CatalogName,
451+
tn.SchemaName,
452+
nameBase)
453+
454+
for idx := 0; ; idx++ {
455+
ers := b.ResolveRelation(seqName.ToUnresolvedObjectName(),
456+
ResolveParams{
457+
IsExistenceOptional: true,
458+
RequiredPrivilege: privilege.USAGE,
459+
WithOffline: true, // We search sequence with provided name, including offline ones.
460+
ResolveTypes: true, // Check for collisions with type names.
461+
})
462+
if ers.IsEmpty() {
463+
break
464+
}
465+
seqName.ObjectName = tree.Name(fmt.Sprintf("%s%d", nameBase, idx))
466+
}
467+
return seqName
468+
}
469+
455470
func columnNamesToIDs(b BuildCtx, tbl *scpb.Table) map[string]descpb.ColumnID {
456471
tableElts := b.QueryByID(tbl.TableID)
457472
namesToIDs := make(map[string]descpb.ColumnID)
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Copyright 2025 The Cockroach Authors.
2+
//
3+
// Use of this software is governed by the CockroachDB Software License
4+
// included in the /LICENSE file.
5+
6+
package scbuildstmt
7+
8+
import (
9+
"github.com/cockroachdb/cockroach/pkg/sql/catalog/catpb"
10+
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
11+
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
12+
"github.com/cockroachdb/cockroach/pkg/sql/privilege"
13+
"github.com/cockroachdb/cockroach/pkg/sql/schemachanger/scpb"
14+
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
15+
"github.com/cockroachdb/cockroach/pkg/sql/sessiondatapb"
16+
"github.com/cockroachdb/cockroach/pkg/sql/types"
17+
)
18+
19+
func alterTableAddIdentity(
20+
b BuildCtx,
21+
tn *tree.TableName,
22+
tbl *scpb.Table,
23+
stmt tree.Statement,
24+
t *tree.AlterTableAddIdentity,
25+
) {
26+
alterColumnPreChecks(b, tn, tbl, t.Column)
27+
colElems := b.ResolveColumn(tbl.TableID, t.Column, ResolveParams{
28+
RequiredPrivilege: privilege.CREATE,
29+
})
30+
colElem := colElems.FilterColumn().MustGetOneElement()
31+
columnID := colElem.ColumnID
32+
// Block alters on system columns.
33+
panicIfSystemColumn(colElem, t.Column)
34+
colTypeElem := mustRetrieveColumnTypeElem(b, tbl.TableID, columnID)
35+
// Ensure that column is an integer
36+
if colTypeElem.Type.InternalType.Family != types.IntFamily {
37+
panic(pgerror.Newf(
38+
pgcode.InvalidParameterValue,
39+
"column %q of relation %q type must be an integer type", t.Column, tn.ObjectName))
40+
}
41+
// Ensure that column is not already an identity column
42+
if isColumnGeneratedAsIdentity(b, tbl.TableID, columnID) {
43+
panic(pgerror.Newf(
44+
pgcode.ObjectNotInPrerequisiteState,
45+
"column %q of relation %q is already an identity column", t.Column, tn.ObjectName))
46+
}
47+
// Ensure that column does not have a default expression
48+
defaultExpr := retrieveColumnDefaultExpressionElem(b, tbl.TableID, columnID)
49+
if defaultExpr != nil {
50+
panic(pgerror.Newf(
51+
pgcode.ObjectNotInPrerequisiteState,
52+
"column %q of relation %q already has a default value", t.Column, tn.ObjectName))
53+
}
54+
// Ensure that column does not have a compute expression
55+
computeExpr := retrieveColumnComputeExpression(b, tbl.TableID, columnID)
56+
if computeExpr != nil {
57+
panic(pgerror.Newf(
58+
pgcode.ObjectNotInPrerequisiteState,
59+
"column %q of relation %q already has a computed value", t.Column, tn.ObjectName))
60+
}
61+
// Ensure that column is declared as not null
62+
columNotNull := b.QueryByID(tbl.TableID).FilterColumnNotNull().Filter(
63+
func(current scpb.Status, target scpb.TargetStatus, e *scpb.ColumnNotNull) bool {
64+
return e.ColumnID == columnID
65+
}).MustGetZeroOrOneElement()
66+
if columNotNull == nil {
67+
panic(pgerror.Newf(
68+
pgcode.ObjectNotInPrerequisiteState,
69+
"column %q of relation %q must be declared NOT NULL before identity can be added",
70+
t.Column, tn.ObjectName))
71+
}
72+
// Ensure that column does not have a on update expression
73+
onUpdate := retrieveColumnOnUpdateExpressionElem(b, tbl.TableID, columnID)
74+
if onUpdate != nil {
75+
panic(pgerror.Newf(
76+
pgcode.ObjectNotInPrerequisiteState,
77+
"column %q of relation %q already has an update expression", t.Column, tn.ObjectName))
78+
}
79+
// Create column definition for identity column
80+
q := []tree.NamedColumnQualification{{Qualification: t.Qualification}}
81+
colDef, err := tree.NewColumnTableDef(t.Column, colTypeElem.Type, false /* isSerial */, q)
82+
if err != nil {
83+
panic(err)
84+
}
85+
// Step 1: Get type and sequence options
86+
colDef.GeneratedIdentity.IsGeneratedAsIdentity = true
87+
var identityType catpb.GeneratedAsIdentityType
88+
switch q := (t.Qualification).(type) {
89+
case *tree.GeneratedAlwaysAsIdentity:
90+
identityType = catpb.GeneratedAsIdentityType_GENERATED_ALWAYS
91+
colDef.GeneratedIdentity.GeneratedAsIdentityType = tree.GeneratedAlways
92+
colDef.GeneratedIdentity.SeqOptions = q.SeqOptions
93+
case *tree.GeneratedByDefAsIdentity:
94+
identityType = catpb.GeneratedAsIdentityType_GENERATED_BY_DEFAULT
95+
colDef.GeneratedIdentity.GeneratedAsIdentityType = tree.GeneratedByDefault
96+
colDef.GeneratedIdentity.SeqOptions = q.SeqOptions
97+
}
98+
// Step 2. create a sequence and default expression
99+
serialNormalizationMode := sessiondatapb.SerialUsesSQLSequences
100+
colDef, expr := alterTableCreateColumnSequence(b, colDef, tn, serialNormalizationMode, colTypeElem.Type)
101+
if expr == nil || len(expr.UsesSequenceIDs) != 1 {
102+
panic(pgerror.Newf(
103+
pgcode.AssertFailure,
104+
"failed to create sequence for new identity column %q of relation %q",
105+
t.Column, tn.ObjectName))
106+
}
107+
// Step 3. add the sequence owner
108+
b.Add(&scpb.SequenceOwner{
109+
SequenceID: expr.UsesSequenceIDs[0],
110+
TableID: tbl.TableID,
111+
ColumnID: columnID,
112+
})
113+
// Step 4. add the default expression
114+
b.Add(&scpb.ColumnDefaultExpression{
115+
TableID: tbl.TableID,
116+
ColumnID: columnID,
117+
Expression: *expr,
118+
})
119+
// Step 5. add the GeneratedAsIdentity element
120+
b.Add(&scpb.ColumnGeneratedAsIdentity{
121+
TableID: tbl.TableID,
122+
ColumnID: columnID,
123+
Type: identityType,
124+
SequenceOption: tree.Serialize(&colDef.GeneratedIdentity.SeqOptions),
125+
})
126+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright 2025 The Cockroach Authors.
2+
//
3+
// Use of this software is governed by the CockroachDB Software License
4+
// included in the /LICENSE file.
5+
6+
package scbuildstmt
7+
8+
import (
9+
"github.com/cockroachdb/cockroach/pkg/sql/catalog/catpb"
10+
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
11+
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
12+
"github.com/cockroachdb/cockroach/pkg/sql/privilege"
13+
"github.com/cockroachdb/cockroach/pkg/sql/schemachanger/scpb"
14+
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
15+
)
16+
17+
func alterTableSetIdentity(
18+
b BuildCtx,
19+
tn *tree.TableName,
20+
tbl *scpb.Table,
21+
stmt tree.Statement,
22+
t *tree.AlterTableSetIdentity,
23+
) {
24+
alterColumnPreChecks(b, tn, tbl, t.Column)
25+
colElem := b.ResolveColumn(tbl.TableID, t.Column, ResolveParams{
26+
RequiredPrivilege: privilege.CREATE,
27+
}).FilterColumn().MustGetOneElement()
28+
columnID := colElem.ColumnID
29+
// Block alters on system columns.
30+
panicIfSystemColumn(colElem, t.Column)
31+
oldIdentityElem := retrieveColumnGeneratedAsIdentityElem(b, tbl.TableID, columnID)
32+
// Ensure that column is an identity column
33+
if oldIdentityElem == nil {
34+
panic(pgerror.Newf(
35+
pgcode.ObjectNotInPrerequisiteState,
36+
"column %q of relation %q is not an identity column", t.Column, tn.ObjectName))
37+
}
38+
// Create a copy of the old element and update the type if needed
39+
newIdentityElem := *oldIdentityElem
40+
switch t.GeneratedAsIdentityType {
41+
case tree.GeneratedAlways:
42+
newIdentityElem.Type = catpb.GeneratedAsIdentityType_GENERATED_ALWAYS
43+
case tree.GeneratedByDefault:
44+
newIdentityElem.Type = catpb.GeneratedAsIdentityType_GENERATED_BY_DEFAULT
45+
}
46+
// Skip operation if the new type is the same as the old one
47+
if newIdentityElem.Type == oldIdentityElem.Type {
48+
return
49+
}
50+
// Replace the old element with the new one
51+
b.Drop(oldIdentityElem)
52+
b.Add(&newIdentityElem)
53+
}

0 commit comments

Comments
 (0)