Skip to content

Commit e6883dd

Browse files
Generated Columns Support
- Added generated column type in Mysql Info schema. - Serialized generated column expression.
1 parent f3614aa commit e6883dd

File tree

22 files changed

+442
-96
lines changed

22 files changed

+442
-96
lines changed

common/constants/constants.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ const (
131131
CHECK_EXPRESSION = "CHECK"
132132
DEFAULT_EXPRESSION = "DEFAULT"
133133
DEFAULT_GENERATED = "DEFAULT_GENERATED"
134+
STORED_GENERATED = "STORED"
135+
VIRTUAL_GENERATED = "VIRTUAL"
134136
TEMP_DB = "smt-staging-db"
135137
DB_URI = "projects/%s/instances/%s/databases/%s"
136138

expressions_api/expression_verify.go

Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"github.com/GoogleCloudPlatform/spanner-migration-tool/logger"
78
mtrand "math/rand"
89
"sync"
910
"time"
@@ -129,6 +130,9 @@ func (ev *ExpressionVerificationAccessorImpl) verifyExpressionInternal(expressio
129130
sqlStatement = fmt.Sprintf("SELECT 1 from %s where %s;", expressionDetail.ReferenceElement.Name, expressionDetail.Expression)
130131
case constants.DEFAULT_EXPRESSION:
131132
sqlStatement = fmt.Sprintf("SELECT CAST(%s as %s)", expressionDetail.Expression, expressionDetail.ReferenceElement.Name)
133+
case constants.STORED_GENERATED, constants.VIRTUAL_GENERATED:
134+
sqlStatement = fmt.Sprintf("SELECT %s as %s FROM %s", expressionDetail.Expression, expressionDetail.ReferenceElement.Name, expressionDetail.SpTableName)
135+
logger.Log.Info(fmt.Sprintf("Expression Statemnet: %s", sqlStatement))
132136
default:
133137
return task.TaskResult[internal.ExpressionVerificationOutput]{Result: internal.ExpressionVerificationOutput{Result: false, Err: fmt.Errorf("invalid expression type requested")}, Err: nil}
134138
}
@@ -171,6 +175,7 @@ func (ev *ExpressionVerificationAccessorImpl) removeExpressions(inputConv *inter
171175
for colName, colDef := range table.ColDefs {
172176
colDef.AutoGen = ddl.AutoGenCol{}
173177
colDef.DefaultValue = ddl.DefaultValue{}
178+
colDef.GeneratedColumn = ddl.GeneratedColumn{}
174179
table.ColDefs[colName] = colDef
175180
}
176181
}
@@ -197,27 +202,64 @@ func (ddlv *DDLVerifierImpl) GetSourceExpressionDetails(conv *internal.Conv, tab
197202
srcTable := conv.SrcSchema[tableId]
198203
for _, srcColId := range srcTable.ColIds {
199204
srcCol := srcTable.ColDefs[srcColId]
205+
var expression ddl.Expression
206+
var expressionType string
207+
isExpressionAvailable := false
200208
if srcCol.DefaultValue.IsPresent {
209+
expression = srcCol.DefaultValue.Value
210+
isExpressionAvailable = true
211+
expressionType = constants.DEFAULT_EXPRESSION
212+
} else if srcCol.GeneratedColumn.IsPresent {
213+
expression = srcCol.GeneratedColumn.Value
214+
isExpressionAvailable = true
215+
if srcCol.GeneratedColumn.Type == ddl.GeneratedColStored {
216+
expressionType = constants.STORED_GENERATED
217+
} else {
218+
expressionType = constants.VIRTUAL_GENERATED
219+
}
220+
}
221+
if isExpressionAvailable {
201222
tyName := conv.SpSchema[tableId].ColDefs[srcColId].T.Name
202223
if conv.SpDialect == constants.DIALECT_POSTGRESQL {
203224
tyName = ddl.GetPGType(conv.SpSchema[tableId].ColDefs[srcColId].T)
204225
}
205-
defaultValueExp := internal.ExpressionDetail{
226+
expressionDetail := internal.ExpressionDetail{
206227
ReferenceElement: internal.ReferenceElement{
207228
Name: tyName,
208229
},
209-
ExpressionId: srcCol.DefaultValue.Value.ExpressionId,
210-
Expression: srcCol.DefaultValue.Value.Statement,
211-
Type: constants.DEFAULT_EXPRESSION,
230+
ExpressionId: expression.ExpressionId,
231+
Expression: expression.Statement,
232+
Type: expressionType,
233+
SpTableName: conv.ToSpanner[srcTable.Name].Name,
212234
Metadata: map[string]string{"TableId": tableId, "ColId": srcColId},
213235
}
214-
expressionDetails = append(expressionDetails, defaultValueExp)
236+
logger.Log.Info(fmt.Sprintf("AExpression Details: %v", expressionDetail))
237+
expressionDetails = append(expressionDetails, expressionDetail)
215238
}
216239
}
217240
}
218241
return expressionDetails
219242
}
220243

244+
func (ddlv *DDLVerifierImpl) getExpressionDetail(
245+
conv *internal.Conv, tableId, spColId, expressionType, expressionId, expression string, spCol ddl.ColumnDef) internal.ExpressionDetail {
246+
tyName := conv.SpSchema[tableId].ColDefs[spColId].T.Name
247+
if conv.SpDialect == constants.DIALECT_POSTGRESQL {
248+
tyName = ddl.GetPGType(conv.SpSchema[tableId].ColDefs[spColId].T)
249+
}
250+
expressionDetail := internal.ExpressionDetail{
251+
ReferenceElement: internal.ReferenceElement{
252+
Name: tyName,
253+
},
254+
ExpressionId: expressionId,
255+
Expression: expression,
256+
Type: expressionType,
257+
Metadata: map[string]string{"TableId": tableId, "ColId": spColId},
258+
}
259+
return expressionDetail
260+
261+
}
262+
221263
func (ddlv *DDLVerifierImpl) GetSpannerExpressionDetails(conv *internal.Conv, tableIds []string) []internal.ExpressionDetail {
222264
expressionDetails := []internal.ExpressionDetail{}
223265
// Collect default values for verification
@@ -226,20 +268,15 @@ func (ddlv *DDLVerifierImpl) GetSpannerExpressionDetails(conv *internal.Conv, ta
226268
for _, spColId := range spTable.ColIds {
227269
spCol := spTable.ColDefs[spColId]
228270
if spCol.DefaultValue.IsPresent {
229-
tyName := conv.SpSchema[tableId].ColDefs[spColId].T.Name
230-
if conv.SpDialect == constants.DIALECT_POSTGRESQL {
231-
tyName = ddl.GetPGType(conv.SpSchema[tableId].ColDefs[spColId].T)
232-
}
233-
defaultValueExp := internal.ExpressionDetail{
234-
ReferenceElement: internal.ReferenceElement{
235-
Name: tyName,
236-
},
237-
ExpressionId: spCol.DefaultValue.Value.ExpressionId,
238-
Expression: spCol.DefaultValue.Value.Statement,
239-
Type: constants.DEFAULT_EXPRESSION,
240-
Metadata: map[string]string{"TableId": tableId, "ColId": spColId},
241-
}
271+
defaultValueExp := ddlv.getExpressionDetail(
272+
conv, tableId, spColId, constants.DEFAULT_EXPRESSION, spCol.DefaultValue.Value.ExpressionId,
273+
spCol.DefaultValue.Value.Statement, spCol)
242274
expressionDetails = append(expressionDetails, defaultValueExp)
275+
} else if spCol.GeneratedColumn.IsPresent {
276+
generatedColExp := ddlv.getExpressionDetail(
277+
conv, tableId, spColId, string(spCol.GeneratedColumn.Type), spCol.GeneratedColumn.Value.ExpressionId,
278+
spCol.GeneratedColumn.Value.Statement, spCol)
279+
expressionDetails = append(expressionDetails, generatedColExp)
243280
}
244281
}
245282
}

internal/convert.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ const (
158158
CassandraMAP
159159
PossibleOverflow
160160
IdentitySkipRange
161+
GeneratedColumnValueError
161162
)
162163

163164
const (
@@ -331,6 +332,7 @@ type ExpressionDetail struct {
331332
ExpressionId string
332333
Expression string
333334
Type string
335+
SpTableName string
334336
Metadata map[string]string
335337
}
336338

internal/reports/report_helpers.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ func buildTableReportBody(conv *internal.Conv, tableId string, issues map[string
306306
// on case of srcType.
307307
spColType = strings.ToLower(spColType)
308308
switch i {
309-
case internal.DefaultValue:
309+
case internal.DefaultValue, internal.GeneratedColumnValueError:
310310
toAppend := Issue{
311311
Category: IssueDB[i].Category,
312312
Description: fmt.Sprintf("%s for table '%s' e.g. column '%s'", IssueDB[i].Brief, conv.SpSchema[tableId].Name, spColName),
@@ -621,6 +621,7 @@ var IssueDB = map[internal.SchemaIssue]struct {
621621
CategoryDescription string
622622
}{
623623
internal.DefaultValue: {Brief: "Some columns have default values which Spanner migration tool does not migrate. Please add the default constraints manually after the migration is complete", Severity: note, batch: true, Category: "MISSING_DEFAULT_VALUE_CONSTRAINTS"},
624+
internal.GeneratedColumnValueError: {Brief: "Some columns have generated expression which Spanner migration tool does not migrate. Please add the expressions manually after the migration is complete", Severity: Errors, batch: true, Category: "MISSING_GENERATED_COL_VALUE_CONSTRAINTS"},
624625
internal.TypeMismatch: {Brief: "Type mismatch in check constraint mention in table", Severity: warning, Category: "TYPE_MISMATCH"},
625626
internal.TypeMismatchError: {Brief: "Type mismatch in check constraint mention in table", Severity: Errors, Category: "TYPE_MISMATCH_ERROR"},
626627
internal.InvalidCondition: {Brief: "Invalid condition in check constraint mention in table", Severity: warning, Category: "INVALID_CONDITION"},

schema/schema.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,14 @@ type Table struct {
5050
// Column represents a database column.
5151
// TODO: add support for foreign keys.
5252
type Column struct {
53-
Name string
54-
Type Type
55-
NotNull bool
56-
Ignored Ignored
57-
Id string
58-
AutoGen ddl.AutoGenCol
59-
DefaultValue ddl.DefaultValue
53+
Name string
54+
Type Type
55+
NotNull bool
56+
Ignored Ignored
57+
Id string
58+
AutoGen ddl.AutoGenCol
59+
DefaultValue ddl.DefaultValue
60+
GeneratedColumn ddl.GeneratedColumn
6061
}
6162

6263
// ForeignKey represents a foreign key.
@@ -129,6 +130,7 @@ type Ignored struct {
129130
Exclusion bool
130131
ForeignKey bool
131132
AutoIncrement bool
133+
GeneratedCol bool
132134
}
133135

134136
// Print converts ty to a string suitable for printing.

sources/common/toddl.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ package common
3131

3232
import (
3333
"context"
34+
"encoding/json"
3435
"fmt"
36+
"github.com/GoogleCloudPlatform/spanner-migration-tool/logger"
3537
"reflect"
3638
"strconv"
3739
"strings"
@@ -377,6 +379,9 @@ func (ss *SchemaToSpannerImpl) SchemaToSpannerDDLHelper(conv *internal.Conv, tod
377379
if srcCol.Ignored.Default {
378380
issues = append(issues, internal.DefaultValue)
379381
}
382+
if srcCol.Ignored.GeneratedCol {
383+
issues = append(issues, internal.GeneratedColumnValueError)
384+
}
380385
if srcCol.Ignored.AutoIncrement { // TODO(adibh) - check why this is not there in postgres
381386
issues = append(issues, internal.AutoIncrement)
382387
}
@@ -673,6 +678,9 @@ func CvtIndexHelper(conv *internal.Conv, tableId string, srcIndex schema.Index,
673678

674679
// Applies all valid expressions which can be migrated to spanner conv object
675680
func spannerSchemaApplyExpressions(conv *internal.Conv, expressions internal.VerifyExpressionsOutput) {
681+
// Log expression verication output list
682+
expression_str, _ := json.Marshal(expressions.ExpressionVerificationOutputList)
683+
logger.Log.Info(fmt.Sprintf("Expressions: %s", string(expression_str)))
676684
for _, expression := range expressions.ExpressionVerificationOutputList {
677685
switch expression.ExpressionDetail.Type {
678686
case "DEFAULT":
@@ -696,6 +704,29 @@ func spannerSchemaApplyExpressions(conv *internal.Conv, expressions internal.Ver
696704
conv.SchemaIssues[tableId].ColumnLevelIssues[columnId] = colIssues
697705
}
698706
}
707+
case constants.VIRTUAL_GENERATED, constants.STORED_GENERATED:
708+
{
709+
tableId := expression.ExpressionDetail.Metadata["TableId"]
710+
columnId := expression.ExpressionDetail.Metadata["ColId"]
711+
712+
if expression.Result {
713+
col := conv.SpSchema[tableId].ColDefs[columnId]
714+
col.GeneratedColumn = ddl.GeneratedColumn{
715+
IsPresent: true,
716+
Value: ddl.Expression{
717+
ExpressionId: expression.ExpressionDetail.ExpressionId,
718+
Statement: expression.ExpressionDetail.Expression,
719+
},
720+
Type: ddl.GeneratedColType(expression.ExpressionDetail.Type),
721+
}
722+
conv.SpSchema[tableId].ColDefs[columnId] = col
723+
} else {
724+
colIssues := conv.SchemaIssues[tableId].ColumnLevelIssues[columnId]
725+
colIssues = append(colIssues, internal.GeneratedColumnValueError)
726+
conv.SchemaIssues[tableId].ColumnLevelIssues[columnId] = colIssues
727+
}
728+
729+
}
699730
}
700731
}
701732
}

sources/mysql/infoschema.go

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ func (isi InfoSchemaImpl) GetTables() ([]common.SchemaAndName, error) {
176176

177177
// GetColumns returns a list of Column objects and names// ProcessColumns
178178
func (isi InfoSchemaImpl) GetColumns(conv *internal.Conv, table common.SchemaAndName, constraints map[string][]string, primaryKeys []string) (map[string]schema.Column, []string, error) {
179-
q := `SELECT c.column_name, c.data_type, c.column_type, c.is_nullable, c.column_default, c.character_maximum_length, c.numeric_precision, c.numeric_scale, c.extra
179+
q := `SELECT c.column_name, c.data_type, c.column_type, c.is_nullable, c.column_default, c.character_maximum_length, c.numeric_precision, c.numeric_scale, c.generation_expression, c.extra
180180
FROM information_schema.COLUMNS c
181181
where table_schema = ? and table_name = ? ORDER BY c.ordinal_position;`
182182
cols, err := isi.Db.Query(q, table.Schema, table.Name)
@@ -187,15 +187,20 @@ func (isi InfoSchemaImpl) GetColumns(conv *internal.Conv, table common.SchemaAnd
187187
colDefs := make(map[string]schema.Column)
188188
var colIds []string
189189
var colName, dataType, isNullable, columnType string
190-
var colDefault, colExtra sql.NullString
190+
var colDefault, colExtra, colGeneratedExpression sql.NullString
191191
var charMaxLen, numericPrecision, numericScale sql.NullInt64
192192
var colAutoGen ddl.AutoGenCol
193193
for cols.Next() {
194-
err := cols.Scan(&colName, &dataType, &columnType, &isNullable, &colDefault, &charMaxLen, &numericPrecision, &numericScale, &colExtra)
194+
err := cols.Scan(&colName, &dataType, &columnType, &isNullable, &colDefault, &charMaxLen, &numericPrecision, &numericScale, &colGeneratedExpression, &colExtra)
195195
if err != nil {
196196
conv.Unexpected(fmt.Sprintf("Can't scan: %v", err))
197197
continue
198198
}
199+
200+
// It's required as empty string is considered as valid within Database SQL.
201+
if colGeneratedExpression.String == "" {
202+
colGeneratedExpression.Valid = false
203+
}
199204
ignored := schema.Ignored{}
200205
ignored.Default = colDefault.Valid
201206
colId := internal.GenerateColumnId()
@@ -223,14 +228,32 @@ func (isi InfoSchemaImpl) GetColumns(conv *internal.Conv, table common.SchemaAnd
223228
}
224229
}
225230

231+
generatedColumn := ddl.GeneratedColumn{
232+
IsPresent: colGeneratedExpression.Valid,
233+
Value: ddl.Expression{},
234+
}
235+
if colGeneratedExpression.Valid {
236+
ignored.GeneratedCol = colGeneratedExpression.Valid
237+
// Defaults to STORED type
238+
generatedColumn.Type = ddl.GeneratedColStored
239+
if colExtra.String == constants.VIRTUAL_GENERATED {
240+
generatedColumn.Type = ddl.GeneratedColVirtual
241+
}
242+
generatedColumn.Value = ddl.Expression{
243+
ExpressionId: internal.GenerateExpressionId(),
244+
Statement: common.SanitizeDefaultValue(colGeneratedExpression.String, "", false),
245+
}
246+
}
247+
226248
c := schema.Column{
227-
Id: colId,
228-
Name: colName,
229-
Type: toType(dataType, columnType, charMaxLen, numericPrecision, numericScale),
230-
NotNull: common.ToNotNull(conv, isNullable),
231-
Ignored: ignored,
232-
AutoGen: colAutoGen,
233-
DefaultValue: defaultVal,
249+
Id: colId,
250+
Name: colName,
251+
Type: toType(dataType, columnType, charMaxLen, numericPrecision, numericScale),
252+
NotNull: common.ToNotNull(conv, isNullable),
253+
Ignored: ignored,
254+
AutoGen: colAutoGen,
255+
DefaultValue: defaultVal,
256+
GeneratedColumn: generatedColumn,
234257
}
235258
colDefs[colId] = c
236259
colIds = append(colIds, colId)

0 commit comments

Comments
 (0)