Skip to content

Commit c70099d

Browse files
authored
Add CLI flag for specifying default SKIP RANGE and START COUNTER WITH values for IDENTITY columns (#1236)
* Add default identity options to Conv struct for use when converting to IDENTITY auto gen columns The actual setting of the default identity options from CLI will come in a later commit. * Add target profile flags for setting the default skip range and start counter with values for IDENTITY columns * Populate DefaultIdentityOptions in conv from values read in via target profile * Update docs * Ensure default identity options are used for postgres IDENTITY columns as well * Update docs * Add integration test for default identity options and fix issue with DDL generation * Add comment describing expected format
1 parent 8314fd4 commit c70099d

17 files changed

Lines changed: 463 additions & 75 deletions

File tree

conversion/conversion.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ func (ci *ConvImpl) SchemaConv(migrationProjectId string, sourceProfile profiles
8484
conv, err = schemaFromSource.schemaFromDatabase(migrationProjectId, sourceProfile, targetProfile, &GetInfoImpl{}, &common.ProcessSchemaImpl{})
8585
case constants.PGDUMP, constants.MYSQLDUMP:
8686
expressionVerificationAccessor, _ := expressions_api.NewExpressionVerificationAccessorImpl(context.Background(), targetProfile.Conn.Sp.Project, targetProfile.Conn.Sp.Instance)
87-
conv, err = schemaFromSource.SchemaFromDump(targetProfile.Conn.Sp.Project, targetProfile.Conn.Sp.Instance, sourceProfile.Driver, targetProfile.Conn.Sp.Dialect, ioHelper, &ProcessDumpByDialectImpl{ExpressionVerificationAccessor: expressionVerificationAccessor})
87+
conv, err = schemaFromSource.SchemaFromDump(targetProfile.Conn.Sp.Project, targetProfile.Conn.Sp.Instance, sourceProfile.Driver, targetProfile.Conn.Sp.Dialect, ioHelper, &ProcessDumpByDialectImpl{ExpressionVerificationAccessor: expressionVerificationAccessor}, targetProfile.DefaultIdentityOptions)
8888
default:
8989
return nil, fmt.Errorf("schema conversion for driver %s not supported", sourceProfile.Driver)
9090
}

conversion/conversion_from_source.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,15 @@ import (
3232
"github.com/GoogleCloudPlatform/spanner-migration-tool/profiles"
3333
"github.com/GoogleCloudPlatform/spanner-migration-tool/sources/common"
3434
"github.com/GoogleCloudPlatform/spanner-migration-tool/sources/csv"
35+
"github.com/GoogleCloudPlatform/spanner-migration-tool/spanner/ddl"
3536
"github.com/GoogleCloudPlatform/spanner-migration-tool/spanner/writer"
3637
"github.com/GoogleCloudPlatform/spanner-migration-tool/streaming"
3738
"go.uber.org/zap"
3839
)
3940

4041
type SchemaFromSourceInterface interface {
4142
schemaFromDatabase(migrationProjectId string, sourceProfile profiles.SourceProfile, targetProfile profiles.TargetProfile, getInfo GetInfoInterface, processSchema common.ProcessSchemaInterface) (*internal.Conv, error)
42-
SchemaFromDump(SpProjectId string, SpInstanceId string, driver string, spDialect string, ioHelper *utils.IOStreams, processDump ProcessDumpByDialectInterface) (*internal.Conv, error)
43+
SchemaFromDump(SpProjectId string, SpInstanceId string, driver string, spDialect string, ioHelper *utils.IOStreams, processDump ProcessDumpByDialectInterface, defaultIdentityOptions profiles.DefaultIdentityOptions) (*internal.Conv, error)
4344
}
4445

4546
type SchemaFromSourceImpl struct {
@@ -60,6 +61,11 @@ func (sads *SchemaFromSourceImpl) schemaFromDatabase(migrationProjectId string,
6061
conv.SpProjectId = targetProfile.Conn.Sp.Project
6162
conv.SpInstanceId = targetProfile.Conn.Sp.Instance
6263
conv.Source = sourceProfile.Driver
64+
conv.DefaultIdentityOptions = ddl.IdentityOptions{
65+
SkipRangeMin: targetProfile.DefaultIdentityOptions.SkipRangeMin,
66+
SkipRangeMax: targetProfile.DefaultIdentityOptions.SkipRangeMax,
67+
StartCounterWith: targetProfile.DefaultIdentityOptions.StartCounterWith,
68+
}
6369
//handle fetching schema differently for sharded migrations, we only connect to the primary shard to
6470
//fetch the schema. We reuse the SourceProfileConnection object for this purpose.
6571
var infoSchema common.InfoSchema
@@ -112,7 +118,7 @@ func (sads *SchemaFromSourceImpl) schemaFromDatabase(migrationProjectId string,
112118
return conv, processSchema.ProcessSchema(conv, infoSchema, common.DefaultWorkers, additionalSchemaAttributes, &schemaToSpanner, &common.UtilsOrderImpl{}, &common.InfoSchemaImpl{})
113119
}
114120

115-
func (sads *SchemaFromSourceImpl) SchemaFromDump(SpProjectId string, SpInstanceId string, driver string, spDialect string, ioHelper *utils.IOStreams, processDump ProcessDumpByDialectInterface) (*internal.Conv, error) {
121+
func (sads *SchemaFromSourceImpl) SchemaFromDump(SpProjectId string, SpInstanceId string, driver string, spDialect string, ioHelper *utils.IOStreams, processDump ProcessDumpByDialectInterface, defaultIdentityOptions profiles.DefaultIdentityOptions) (*internal.Conv, error) {
116122
f, n, err := getSeekable(ioHelper.In)
117123
if err != nil {
118124
utils.PrintSeekError(driver, err, ioHelper.Out)
@@ -125,6 +131,11 @@ func (sads *SchemaFromSourceImpl) SchemaFromDump(SpProjectId string, SpInstanceI
125131
conv.Source = driver
126132
conv.SpProjectId = SpProjectId
127133
conv.SpInstanceId = SpInstanceId
134+
conv.DefaultIdentityOptions = ddl.IdentityOptions{
135+
SkipRangeMin: defaultIdentityOptions.SkipRangeMin,
136+
SkipRangeMax: defaultIdentityOptions.SkipRangeMax,
137+
StartCounterWith: defaultIdentityOptions.StartCounterWith,
138+
}
128139
p := internal.NewProgress(n, "Generating schema", internal.Verbose(), false, int(internal.SchemaCreationInProgress))
129140
r := internal.NewReader(bufio.NewReader(f), p)
130141
conv.SetSchemaMode() // Build schema and ignore data in dump.

conversion/mocks.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func (msads *MockSchemaFromSource) schemaFromDatabase(migrationProjectId string,
6565
args := msads.Called(migrationProjectId, sourceProfile, targetProfile, getInfo, processSchema)
6666
return args.Get(0).(*internal.Conv), args.Error(1)
6767
}
68-
func (msads *MockSchemaFromSource) SchemaFromDump(SpProjectId string, SpInstanceId string, driver string, spDialect string, ioHelper *utils.IOStreams, processDump ProcessDumpByDialectInterface) (*internal.Conv, error) {
68+
func (msads *MockSchemaFromSource) SchemaFromDump(SpProjectId string, SpInstanceId string, driver string, spDialect string, ioHelper *utils.IOStreams, processDump ProcessDumpByDialectInterface, defaultIdentityOptions profiles.DefaultIdentityOptions) (*internal.Conv, error) {
6969
args := msads.Called(driver, spDialect, ioHelper, processDump)
7070
return args.Get(0).(*internal.Conv), args.Error(1)
7171
}

docs/cli/flags.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,11 @@ Time Zone Database](https://www.iana.org/time-zones). If not specified, the defa
7878
the Spanner database, and the database will therefore default to `America/Los_Angeles` (the default timezone for
7979
Spanner databases). Note, the default timezone can only be set on an empty Spanner database without any tables; a
8080
warning will be logged and this setting will be ignored if the database already includes tables.
81+
82+
* **`defaultIdentitySkipRange`**: Optional flag. Specifies the default SKIP RANGE values to use for IDENTITY columns. Specified as `<min>-<max>`, where both `<min>` and `<max>` are positive integers and `<min>` must be less than `<max>`. For example, `defaultIdentitySkipRange=10-50`. For
83+
instructions on setting SKIP RANGE values for individual columns, see
84+
[here](../data-types/mysql.md#auto-increment-columns).
85+
86+
* **`defaultIdentityStartCounterWith`**: Optional flag. Specifies the default START COUNTER WITH value to use for IDENTITY columns. This should be a positive integer. For example, `defaultIdentityStartCounterWith=1000`. For
87+
instructions on setting the START COUNTER WITH value for individual columns, see
88+
[here](../data-types/mysql.md#auto-increment-columns).

docs/data-types/mysql.md

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -176,37 +176,46 @@ The tool maps auto-increment columns to [Spanner IDENTITY
176176
columns](https://cloud.google.com/spanner/docs/primary-key-default-value#identity-columns).
177177
Users need to set the SKIP RANGE and/or START COUNTER WITH values to avoid duplicate key errors.
178178

179-
The SKIP RANGE and START COUNTER WITH values can be set most via both the web UI (recommended) and the CLI.
179+
The SKIP RANGE and START COUNTER WITH values can be set via both the web UI (recommended) and the CLI.
180180

181181
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).
182182

183-
To set the SKIP RANGE and/or START COUNTER WITH values via the CLI, the recommended steps are as follows:
183+
To set the SKIP RANGE and/or START COUNTER WITH values via the CLI, there are two options: either specify default
184+
values to be used by all IDENTITY columns, or specify values on a per-column basis. Both options can be used in
185+
conjuction with one another.
186+
187+
To specify default SKIP RANGE and/or START COUNTER WITH values to be used by all columns, include the following flags
188+
in the `targetProfile` parameter: `defaultIdentitySkipRange` and `defaultIdentityStartCounterWith`, for example:
189+
```sh
190+
--targetProfile="instance=my-instance,defaultIdentitySkipRange=1000-5000,defaultIdentityStartCounterWith=100"
191+
```
192+
For more details on both flags, see [here](../cli/flags.md#target-profile).
193+
194+
To specify SKIP RANGE and/or START COUNTER WITH values on per-column basis via the CLI, do the following:
184195
- Do a dry-run schema-only migration to generate a session JSON file:
185196
```sh
186-
spanner-migration-tool schema -dry-run ...
197+
spanner-migration-tool schema -dry-run ...
187198
```
188199
- Open the resulting session file and find the relevant column definition(s) in the `ColDefs` collection of the table
189200
it belongs to
190201
- Set the appropriate fields in that column's `AutoGen.AutoIncrementOptions` node. All three values are expected to be
191202
strings containing a numeric value. For example:
192203
```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-
...
204+
{
205+
"SpSchema": {
206+
"table1": {
207+
"Name": "SomeTable",
208+
"ColDefs": {
209+
"column1": {
210+
"Name": "some_column",
211+
"AutoGen": {
212+
"Name": "Auto Increment",
213+
"GenerationType": "Auto Increment",
214+
"AutoIncrementOptions": {
215+
"SkipRangeMin": "1000",
216+
"SkipRangeMax": "10000",
217+
"StartCounterWith": "500"
218+
}
210219
},
211220
...
212221
},
@@ -215,11 +224,13 @@ To set the SKIP RANGE and/or START COUNTER WITH values via the CLI, the recommen
215224
...
216225
},
217226
...
218-
}
227+
},
228+
...
229+
}
219230
```
220231
- Save the session file and run your desired migration using the updated session file:
221232
```sh
222-
spanner-migration-tool schema -session=<path to session file> ...
233+
spanner-migration-tool schema -session=<path to session file> ...
223234
```
224235

225236
## Other MySQL features

docs/data-types/postgres.md

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -66,37 +66,46 @@ with type `INT64`.
6666

6767
Users need to set the SKIP RANGE and/or START COUNTER WITH values to avoid duplicate key errors.
6868

69-
The SKIP RANGE and START COUNTER WITH values can be set most via both the web UI (recommended) and the CLI.
69+
The SKIP RANGE and START COUNTER WITH values can be set via both the web UI (recommended) and the CLI.
7070

7171
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).
7272

73-
To set the SKIP RANGE and/or START COUNTER WITH values via the CLI, the recommended steps are as follows:
73+
To set the SKIP RANGE and/or START COUNTER WITH values via the CLI, there are two options: either specify default
74+
values to be used by all IDENTITY columns, or specify values on a per-column basis. Both options can be used in
75+
conjuction with one another.
76+
77+
To specify default SKIP RANGE and/or START COUNTER WITH values to be used by all columns, include the following flags
78+
in the `targetProfile` parameter: `defaultIdentitySkipRange` and `defaultIdentityStartCounterWith`, for example:
79+
```sh
80+
--targetProfile="instance=my-instance,defaultIdentitySkipRange=1000-5000,defaultIdentityStartCounterWith=100"
81+
```
82+
For more details on both flags, see [here](../cli/flags.md#target-profile).
83+
84+
To specify SKIP RANGE and/or START COUNTER WITH values on per-column basis via the CLI, do the following:
7485
- Do a dry-run schema-only migration to generate a session JSON file:
7586
```sh
76-
spanner-migration-tool schema -dry-run ...
87+
spanner-migration-tool schema -dry-run ...
7788
```
7889
- Open the resulting session file and find the relevant column definition(s) in the `ColDefs` collection of the table
7990
it belongs to
8091
- Set the appropriate fields in that column's `AutoGen.AutoIncrementOptions` node. All three values are expected to be
8192
strings containing a numeric value. For example:
8293
```json
83-
{
84-
"SpSchema": {
85-
"table1": {
86-
"Name": "SomeTable",
87-
"ColDefs": {
88-
"column1": {
89-
"Name": "some_column",
90-
"AutoGen": {
91-
"Name": "Auto Increment",
92-
"GenerationType": "Auto Increment",
93-
"AutoIncrementOptions": {
94-
"SkipRangeMin": "1000",
95-
"SkipRangeMax": "10000",
96-
"StartCounterWith": "500"
97-
}
98-
},
99-
...
94+
{
95+
"SpSchema": {
96+
"table1": {
97+
"Name": "SomeTable",
98+
"ColDefs": {
99+
"column1": {
100+
"Name": "some_column",
101+
"AutoGen": {
102+
"Name": "Auto Increment",
103+
"GenerationType": "Auto Increment",
104+
"AutoIncrementOptions": {
105+
"SkipRangeMin": "1000",
106+
"SkipRangeMax": "10000",
107+
"StartCounterWith": "500"
108+
}
100109
},
101110
...
102111
},
@@ -105,11 +114,13 @@ To set the SKIP RANGE and/or START COUNTER WITH values via the CLI, the recommen
105114
...
106115
},
107116
...
108-
}
117+
},
118+
...
119+
}
109120
```
110121
- Save the session file and run your desired migration using the updated session file:
111122
```sh
112-
spanner-migration-tool schema -session=<path to session file> ...
123+
spanner-migration-tool schema -session=<path to session file> ...
113124
```
114125

115126
## TIMESTAMP

internal/convert.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ type Conv struct {
5959
SpInstanceId string // Spanner Instance Id
6060
Source string // Source Database type being migrated
6161
DatabaseOptions ddl.DatabaseOptions
62+
DefaultIdentityOptions ddl.IdentityOptions // Default values to use for IDENTITY columns
6263
}
6364

6465
type InvalidCheckExp struct {

profiles/target_profile.go

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package profiles
1717
import (
1818
"fmt"
1919
"os"
20+
"strconv"
2021
"strings"
2122
"time"
2223

@@ -57,6 +58,13 @@ type TargetProfileConnection struct {
5758
type TargetProfile struct {
5859
Ty TargetProfileType
5960
Conn TargetProfileConnection
61+
DefaultIdentityOptions DefaultIdentityOptions
62+
}
63+
64+
type DefaultIdentityOptions struct {
65+
SkipRangeMin string
66+
SkipRangeMax string
67+
StartCounterWith string
6068
}
6169

6270
// This expects that GetResourceIds has already been called once and the project, instance and dbName
@@ -156,6 +164,11 @@ func NewTargetProfile(s string) (TargetProfile, error) {
156164
return TargetProfile{}, fmt.Errorf("dialect not supported %v", sp.Dialect)
157165
}
158166

167+
defaultIdentityOptions, err := extractDefaultIdentityOptions(params)
168+
if err != nil {
169+
return TargetProfile{}, err
170+
}
171+
159172
// if target-profile is not empty, it must contain spanner instance
160173
if s != "" && sp.Instance == "" {
161174
return TargetProfile{}, fmt.Errorf("found empty string for instance. please specify instance (spanner instance) in the target-profile")
@@ -169,5 +182,51 @@ func NewTargetProfile(s string) (TargetProfile, error) {
169182
}
170183

171184
conn := TargetProfileConnection{Ty: TargetProfileConnectionTypeSpanner, Sp: sp}
172-
return TargetProfile{Ty: TargetProfileTypeConnection, Conn: conn}, nil
185+
return TargetProfile{Ty: TargetProfileTypeConnection, Conn: conn, DefaultIdentityOptions: defaultIdentityOptions}, nil
186+
}
187+
188+
func extractDefaultIdentityOptions(params map[string]string) (DefaultIdentityOptions, error) {
189+
defaultIdentityOptions := DefaultIdentityOptions{}
190+
if defaultSkipRangeStr, ok := params["defaultIdentitySkipRange"]; ok {
191+
skipRangeMin, skipRangeMax, err := parseDefaultSkipRange(defaultSkipRangeStr)
192+
if err != nil {
193+
return DefaultIdentityOptions{}, fmt.Errorf(fmt.Sprintf("Invalid value for defaultIdentitySkipRange: %s, expected <min>-<max> where both <min> and <max> are integers and <min> is less than <max>. %s", defaultSkipRangeStr, err))
194+
}
195+
defaultIdentityOptions.SkipRangeMin = skipRangeMin
196+
defaultIdentityOptions.SkipRangeMax = skipRangeMax
197+
}
198+
if defaultStartCounterWith, ok := params["defaultIdentityStartCounterWith"]; ok {
199+
startCounterWithInt, err := strconv.Atoi(defaultStartCounterWith)
200+
if err != nil || startCounterWithInt <= 0 {
201+
return DefaultIdentityOptions{}, fmt.Errorf("Invalid value for defaultIdentityStartCounterWith: %s, expected a positive integer.", defaultStartCounterWith)
202+
}
203+
defaultIdentityOptions.StartCounterWith = defaultStartCounterWith
204+
}
205+
return defaultIdentityOptions, nil
206+
}
207+
208+
// This attempts to parse the skip range assuming the following format: "<min>-<max>" where both <min> and <max> are
209+
// positive integers and <min> is less than <max>.
210+
func parseDefaultSkipRange(defaultSkipRangeStr string) (string, string, error) {
211+
parts := strings.Split(defaultSkipRangeStr, "-")
212+
if len(parts) < 2 {
213+
return "", "", fmt.Errorf("No range specified")
214+
}
215+
if len(parts) > 2 {
216+
return "", "", fmt.Errorf("Too many elements in range")
217+
}
218+
skipRangeMinStr := parts[0]
219+
skipRangeMaxStr := parts[1]
220+
skipRangeMinInt, err := strconv.Atoi(skipRangeMinStr)
221+
if err != nil {
222+
return "", "", fmt.Errorf("Failed to convert min value to integer: %s", err)
223+
}
224+
skipRangeMaxInt, err := strconv.Atoi(skipRangeMaxStr)
225+
if err != nil {
226+
return "", "", fmt.Errorf("Failed to convert max value to integer: %s", err)
227+
}
228+
if skipRangeMinInt >= skipRangeMaxInt {
229+
return "", "", fmt.Errorf("Min value must be less than max value")
230+
}
231+
return skipRangeMinStr, skipRangeMaxStr, nil
173232
}

0 commit comments

Comments
 (0)