Skip to content

Commit 166a4a9

Browse files
Map MySQL AUTO_INCREMENT columns to Spanner IDENTITY columns (#1227)
* Map auto increment source columns to Spanner IDENTITY columns Note, I'm not using the AUTO_INCREMENT keyword or SERIAL type here because those don't allow setting of the skip range or start counter with values. Additionally, they both alias to the IDENTITY clause I'm using here anyways, so it's effectively the same, but this is more flexible. * Remove sequence creation for AUTO_INCREMENT columns The use of a sequence will no longer be needed. The next commit will update the AST printing to use the new AUTO_INCREMENT keyword (Spanner SQL) and SERIAL type (PGSQL). * Add support for migrating AUTO_INCREMENT columns from mysql dump files The AST mapping will be added in a subsequent commit. * Minor cleanup to align cassandra's GetColumnAutoGen impl with other DBs that don't support migration of auto incrementing columns * Add note to set skip range or start counter with to avoid collisions for identity columns * Add Identity as an option for Auto-Gen columns in the web UI * Allow for setting of the skip range and start counter with values via the web UI * Update docs * Fix notation for identity column type (needed to use underscores instead of spaces...) * Fix lint and integration test workflows * Allow Go version 1.24 for lint tool (latest staticcheck requires at least 1.24) --------- Co-authored-by: Vardhan Vinay Thigle <39047439+VardhanThigle@users.noreply.github.com>
1 parent f7a76f9 commit 166a4a9

29 files changed

Lines changed: 449 additions & 133 deletions

common/constants/constants.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ const (
111111
UUID string = "UUID"
112112
SEQUENCE string = "Sequence"
113113
AUTO_INCREMENT string = "Auto Increment"
114+
IDENTITY string = "Identity"
114115
// Default gcs path of the Dataflow template.
115116
DEFAULT_TEMPLATE_PATH string = "gs://dataflow-templates/latest/flex/Cloud_Datastream_to_Spanner"
116117

docs/data-types/mysql.md

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,9 +170,57 @@ maps `UNIQUE` constraint into `UNIQUE` secondary index. Note that due to limitat
170170
mysqldump parser, we are not able to handle key column ordering (i.e. ASC/DESC) in
171171
mysqldump files. All key columns in mysqldump files will be treated as ASC.
172172

173-
## Auto-Increment and Sequences
174-
175-
The tool creates a new sequence for auto-increment columns and maps the auto-generation of these columns to this sequence. The sequence type is of *bit reversed positive*. Users need to set skip range and/or start with counter to avoid duplicate key errors.
173+
## Auto-Increment Columns
174+
175+
The tool maps auto-increment columns to [Spanner IDENTITY
176+
columns](https://cloud.google.com/spanner/docs/primary-key-default-value#identity-columns).
177+
Users need to set the SKIP RANGE and/or START COUNTER WITH values to avoid duplicate key errors.
178+
179+
The SKIP RANGE and START COUNTER WITH values can be set most via both the web UI (recommended) and the CLI.
180+
181+
The Column tab of the web UI exposes fields to set the SKIP RANGE and START COUNTER WITH values. For more details, see [here](../ui/schema-conv/spanner-draft.md).
182+
183+
To set the SKIP RANGE and/or START COUNTER WITH values via the CLI, the recommended steps are as follows:
184+
- Do a dry-run schema-only migration to generate a session JSON file:
185+
```sh
186+
spanner-migration-tool schema -dry-run ...
187+
```
188+
- Open the resulting session file and find the relevant column definition(s) in the `ColDefs` collection of the table
189+
it belongs to
190+
- Set the appropriate fields in that column's `AutoGen.AutoIncrementOptions` node. All three values are expected to be
191+
strings containing a numeric value. For example:
192+
```json
193+
{
194+
"SpSchema": {
195+
"table1": {
196+
"Name": "SomeTable",
197+
"ColDefs": {
198+
"column1": {
199+
"Name": "some_column",
200+
"AutoGen": {
201+
"Name": "Auto Increment",
202+
"GenerationType": "Auto Increment",
203+
"AutoIncrementOptions": {
204+
"SkipRangeMin": "1000",
205+
"SkipRangeMax": "10000",
206+
"StartCounterWith": "500"
207+
}
208+
},
209+
...
210+
},
211+
...
212+
},
213+
...
214+
},
215+
...
216+
},
217+
...
218+
}
219+
```
220+
- Save the session file and run your desired migration using the updated session file:
221+
```sh
222+
spanner-migration-tool schema -session=<path to session file> ...
223+
```
176224

177225
## Other MySQL features
178226

docs/ui/schema-conv/spanner-draft.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Users can view detailed information for a table by selecting it from the **Spann
2626

2727
### Column
2828

29-
Column tab provides information on the columns that are a part of the selected table. It also provides the option to edit the column wherein a user can modify a column name, delete a column, change the data type of the column, add auto-generation to the column, modify the default value or modify the null property of the column. Once the user is done with required modifications, they can click on **SAVE & CONVERT **and the update would reflect in the session file and across all the components in the database.
29+
Column tab provides information on the columns that are a part of the selected table. It also provides the option to edit the column wherein a user can modify a column name, delete a column, change the data type of the column, add and configure auto-generation for the column, modify the default value or modify the null property of the column. Once the user is done with required modifications, they can click on **SAVE & CONVERT **and the update would reflect in the session file and across all the components in the database.
3030

3131
![](https://services.google.com/fh/files/misc/dv1.png)
3232

@@ -120,7 +120,8 @@ Apart from the existing indexes for the source database, users can also add seco
120120

121121
Auto-Generated Columns populate Spanner columns automatically if no value is provided. Currently Spanner Migration Tool support the following techniques for auto-generation:
122122
1. UUID function: Generate a UUID (v4) as part of a table’s primary key DEFAULT expression.
123-
2. Bit reverse function: Map existing integer keys using the same logic as a bit-reversed sequence to avoid hotspotting.
123+
2. Identity columns: automatically generate integer values without having to manually maintain an underlying sequence. Existing auto-incrementing columns are mapped to Identity columns.
124+
3. Bit reverse function: Map existing integer keys using the same logic as a bit-reversed sequence to avoid hotspotting.
124125

125126
Refer to [documentation](https://cloud.google.com/spanner/docs/primary-key-default-value).
126127

@@ -129,9 +130,17 @@ Refer to [documentation](https://cloud.google.com/spanner/docs/primary-key-defau
129130
The default recommendation for primary keys in Spanner is to use a Universally Unique Identifier, or UUID. Users can convert existing columns to be filled by UUID by choosing the **Edit** option in a table and under the **Auto-Generated** column choosing UUID.
130131
![](https://services.google.com/fh/files/misc/column-edit-uuid.png)
131132

133+
### Identity Columns
134+
135+
Identity columns automatically generate integer values without requiring manual maintenance or management of an underlying sequence by users.
136+
137+
By default, the Spanner Migration Tool maps auto-incrementing columns from the source database to Identity columns in Spanner, without setting any SKIP RANGE or START COUNTER WITH values. In order to avoid duplicates, it is recommended to set either a SKIP RANGE or a START COUNTER WITH value for all auto-incrementing columns that will be migrated.
138+
139+
See [documentation](https://cloud.google.com/spanner/docs/primary-key-default-value#identity-columns) for more details on Identity columns.
140+
132141
### Sequences
133142

134-
Spanner offers a SEQUENCE object that generates unique integers as part of a primary key DEFAULT expression. However, unlike a monotonic sequence, the values generated by a Spanner sequence are distributed uniformly and thus won’t hotspot at scale. Existing **Auto-Increment** columns will be mapped to a new Sequence.
143+
Spanner offers a SEQUENCE object that generates unique integers as part of a primary key DEFAULT expression. However, unlike a monotonic sequence, the values generated by a Spanner sequence are distributed uniformly and thus won’t hotspot at scale.
135144

136145
#### Create a new Sequence
137146
In order to add a sequence, the user needs to select the **Add Sequence** option and provide some details mandatory to create a sequence like **sequence name** and **sequence type**. An existing sequence can also be modified using the **Edit** button.

internal/convert.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ const (
155155
CassandraTIMEUUID
156156
CassandraMAP
157157
PossibleOverflow
158+
IdentitySkipRange
158159
)
159160

160161
const (

internal/reports/report_helpers.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,12 @@ func buildTableReportBody(conv *internal.Conv, tableId string, issues map[string
324324
Description: fmt.Sprintf("Column '%s' is an autoincrement column in table '%s'. %s", spColName, conv.SpSchema[tableId].Name, IssueDB[i].Brief),
325325
}
326326
l = append(l, toAppend)
327+
case internal.IdentitySkipRange:
328+
toAppend := Issue{
329+
Category: IssueDB[i].Category,
330+
Description: fmt.Sprintf("Column '%s' has been converted to an identity column in table '%s'. %s", spColName, conv.SpSchema[tableId].Name, IssueDB[i].Brief),
331+
}
332+
l = append(l, toAppend)
327333
case internal.SequenceCreated:
328334
toAppend := Issue{
329335
Category: IssueDB[i].Category,
@@ -635,6 +641,7 @@ var IssueDB = map[internal.SchemaIssue]struct {
635641
internal.DecimalThatFits: {Brief: "Spanner does not support decimal, but this type mapping preserves the decimal's specified precision", Severity: suggestion, Category: "DECIMAL_THAT_FITS"},
636642
internal.Serial: {Brief: "Spanner does not support autoincrementing types", Severity: warning, Category: "AUTOINCREMENTING_TYPE_USES"},
637643
internal.AutoIncrement: {Brief: "Spanner does not support auto_increment attribute", Severity: warning, Category: "AUTO_INCREMENT_ATTRIBUTE_USES"},
644+
internal.IdentitySkipRange: {Brief: "Set Skip Range or Start Counter With values to avoid duplicate value errors.", Severity: note, Category: "IDENTITY_SKIP_RANGE_SUGGESTION"},
638645
internal.Timestamp: {Brief: "Spanner timestamp is closer to PostgreSQL timestamptz", Severity: suggestion, batch: true, Category: "TIMESTAMP_SUGGESTION"},
639646
internal.Datetime: {Brief: "Spanner timestamp is closer to MySQL timestamp", Severity: warning, batch: true, Category: "TIMESTAMP_WARNING"},
640647
internal.Time: {Brief: "Spanner does not support time/year types", Severity: warning, batch: true, Category: "TIME_YEAR_TYPE_USES"},

sources/cassandra/toddl.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func (tdi ToDdlImpl) ToSpannerType(conv *internal.Conv, spType string, srcType s
3737
}
3838

3939
func (tdi ToDdlImpl) GetColumnAutoGen(conv *internal.Conv, autoGenCol ddl.AutoGenCol, colId string, tableId string) (*ddl.AutoGenCol, error) {
40-
return &ddl.AutoGenCol{}, nil
40+
return nil, nil
4141
}
4242

4343
func (tdi ToDdlImpl) GetTypeOption(srcTypeName string, spType ddl.Type) string {

sources/cassandra/toddl_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ func TestGetColumnAutoGen(t *testing.T) {
5959
tdi := &ToDdlImpl{}
6060
autoGenCol, err := tdi.GetColumnAutoGen(nil, ddl.AutoGenCol{}, "", "")
6161
assert.Nil(t, err)
62-
assert.Equal(t, &ddl.AutoGenCol{}, autoGenCol)
62+
assert.Nil(t, autoGenCol)
6363
}
6464

6565
func TestGetTypeOption(t *testing.T) {

sources/common/toddl.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,8 +400,10 @@ func (ss *SchemaToSpannerImpl) SchemaToSpannerDDLHelper(conv *internal.Conv, tod
400400
if err != nil {
401401
srcCol.Ignored.AutoIncrement = true
402402
issues = append(issues, internal.AutoIncrement)
403-
} else {
403+
} else if autoGenCol.GenerationType == constants.SEQUENCE {
404404
issues = append(issues, internal.SequenceCreated)
405+
} else if autoGenCol.GenerationType == constants.IDENTITY {
406+
issues = append(issues, internal.IdentitySkipRange)
405407
}
406408
}
407409
}

sources/mysql/infoschema.go

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -200,17 +200,10 @@ func (isi InfoSchemaImpl) GetColumns(conv *internal.Conv, table common.SchemaAnd
200200
ignored.Default = colDefault.Valid
201201
colId := internal.GenerateColumnId()
202202
if colExtra.String == "auto_increment" {
203-
sequence := createSequence(conv)
204203
colAutoGen = ddl.AutoGenCol{
205-
Name: sequence.Name,
204+
Name: constants.AUTO_INCREMENT,
206205
GenerationType: constants.AUTO_INCREMENT,
207206
}
208-
sequence.ColumnsUsingSeq = map[string][]string{
209-
table.Id: {colId},
210-
}
211-
conv.ConvLock.Lock()
212-
conv.SrcSequences[sequence.Id] = sequence
213-
conv.ConvLock.Unlock()
214207
} else {
215208
colAutoGen = ddl.AutoGenCol{}
216209
}

sources/mysql/infoschema_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -314,11 +314,11 @@ func TestProcessSchemaMYSQL(t *testing.T) {
314314
"f4": schema.Column{Name: "f4", Type: schema.Type{Name: "float", Mods: []int64{24}, ArrayBounds: []int64(nil)}, NotNull: false, Ignored: schema.Ignored{Check: false, Identity: false, Default: false, Exclusion: false, ForeignKey: false, AutoIncrement: false}, Id: ""},
315315
"f8": schema.Column{Name: "f8", Type: schema.Type{Name: "double", Mods: []int64{53}, ArrayBounds: []int64(nil)}, NotNull: false, Ignored: schema.Ignored{Check: false, Identity: false, Default: false, Exclusion: false, ForeignKey: false, AutoIncrement: false}, Id: ""},
316316
"i2": schema.Column{Name: "i2", Type: schema.Type{Name: "smallint", Mods: []int64{16}, ArrayBounds: []int64(nil)}, NotNull: false, Ignored: schema.Ignored{Check: false, Identity: false, Default: false, Exclusion: false, ForeignKey: false, AutoIncrement: false}, Id: ""},
317-
"i4": schema.Column{Name: "i4", Type: schema.Type{Name: "integer", Mods: []int64{32}, ArrayBounds: []int64(nil)}, NotNull: false, Ignored: schema.Ignored{Check: false, Identity: false, Default: false, Exclusion: false, ForeignKey: false, AutoIncrement: false}, Id: "", AutoGen: ddl.AutoGenCol{Name: "Sequence37", GenerationType: constants.AUTO_INCREMENT}},
317+
"i4": schema.Column{Name: "i4", Type: schema.Type{Name: "integer", Mods: []int64{32}, ArrayBounds: []int64(nil)}, NotNull: false, Ignored: schema.Ignored{Check: false, Identity: false, Default: false, Exclusion: false, ForeignKey: false, AutoIncrement: false}, Id: "", AutoGen: ddl.AutoGenCol{Name: constants.AUTO_INCREMENT, GenerationType: constants.AUTO_INCREMENT}},
318318
"i8": schema.Column{Name: "i8", Type: schema.Type{Name: "bigint", Mods: []int64{64}, ArrayBounds: []int64(nil)}, NotNull: false, Ignored: schema.Ignored{Check: false, Identity: false, Default: false, Exclusion: false, ForeignKey: false, AutoIncrement: false}, Id: ""},
319319
"id": schema.Column{Name: "id", Type: schema.Type{Name: "bigint", Mods: []int64{64}, ArrayBounds: []int64(nil)}, NotNull: true, Ignored: schema.Ignored{Check: false, Identity: false, Default: false, Exclusion: false, ForeignKey: false, AutoIncrement: false}, Id: ""},
320320
"s": schema.Column{Name: "s", Type: schema.Type{Name: "set", Mods: []int64(nil), ArrayBounds: []int64{-1}}, NotNull: false, Ignored: schema.Ignored{Check: false, Identity: false, Default: false, Exclusion: false, ForeignKey: false, AutoIncrement: false}, Id: ""},
321-
"si": schema.Column{Name: "si", Type: schema.Type{Name: "integer", Mods: []int64{32}, ArrayBounds: []int64(nil)}, NotNull: true, Ignored: schema.Ignored{Check: false, Identity: false, Default: true, Exclusion: false, ForeignKey: false, AutoIncrement: false}, Id: "", DefaultValue: ddl.DefaultValue{IsPresent: true, Value: ddl.Expression{ExpressionId: "e40", Statement: "nextval('test11_s_seq'::regclass)"}}},
321+
"si": schema.Column{Name: "si", Type: schema.Type{Name: "integer", Mods: []int64{32}, ArrayBounds: []int64(nil)}, NotNull: true, Ignored: schema.Ignored{Check: false, Identity: false, Default: true, Exclusion: false, ForeignKey: false, AutoIncrement: false}, Id: "", DefaultValue: ddl.DefaultValue{IsPresent: true, Value: ddl.Expression{ExpressionId: "e39", Statement: "nextval('test11_s_seq'::regclass)"}}},
322322
"ts": schema.Column{Name: "ts", Type: schema.Type{Name: "datetime", Mods: []int64(nil), ArrayBounds: []int64(nil)}, NotNull: false, Ignored: schema.Ignored{Check: false, Identity: false, Default: false, Exclusion: false, ForeignKey: false, AutoIncrement: false}, Id: ""},
323323
"txt": schema.Column{Name: "txt", Type: schema.Type{Name: "text", Mods: []int64(nil), ArrayBounds: []int64(nil)}, NotNull: true, Ignored: schema.Ignored{Check: false, Identity: false, Default: false, Exclusion: false, ForeignKey: false, AutoIncrement: false}, Id: ""},
324324
"tz": schema.Column{Name: "tz", Type: schema.Type{Name: "timestamp", Mods: []int64(nil), ArrayBounds: []int64(nil)}, NotNull: false, Ignored: schema.Ignored{Check: false, Identity: false, Default: false, Exclusion: false, ForeignKey: false, AutoIncrement: false}, Id: ""},
@@ -564,7 +564,7 @@ func TestProcessData_MultiCol(t *testing.T) {
564564
}
565565
internal.AssertSpSchema(conv, t, expectedSchema, stripSchemaComments(conv.SpSchema))
566566
columnLevelIssues := map[string][]internal.SchemaIssue{
567-
"c57": []internal.SchemaIssue{
567+
"c56": []internal.SchemaIssue{
568568
2,
569569
},
570570
}

0 commit comments

Comments
 (0)