diff --git a/common/constants/constants.go b/common/constants/constants.go index 06d3d37428..71aef6e525 100644 --- a/common/constants/constants.go +++ b/common/constants/constants.go @@ -131,6 +131,8 @@ const ( CHECK_EXPRESSION = "CHECK" DEFAULT_EXPRESSION = "DEFAULT" DEFAULT_GENERATED = "DEFAULT_GENERATED" + STORED_GENERATED = "STORED" + VIRTUAL_GENERATED = "VIRTUAL" TEMP_DB = "smt-staging-db" DB_URI = "projects/%s/instances/%s/databases/%s" diff --git a/expressions_api/expression_verify.go b/expressions_api/expression_verify.go index e0419d2d0e..9d73e1eaf6 100644 --- a/expressions_api/expression_verify.go +++ b/expressions_api/expression_verify.go @@ -129,6 +129,8 @@ func (ev *ExpressionVerificationAccessorImpl) verifyExpressionInternal(expressio sqlStatement = fmt.Sprintf("SELECT 1 from %s where %s;", expressionDetail.ReferenceElement.Name, expressionDetail.Expression) case constants.DEFAULT_EXPRESSION: sqlStatement = fmt.Sprintf("SELECT CAST(%s as %s)", expressionDetail.Expression, expressionDetail.ReferenceElement.Name) + case constants.STORED_GENERATED, constants.VIRTUAL_GENERATED: + sqlStatement = fmt.Sprintf("SELECT %s as %s FROM %s", expressionDetail.Expression, expressionDetail.ReferenceElement.Name, expressionDetail.SpTableName) default: return task.TaskResult[internal.ExpressionVerificationOutput]{Result: internal.ExpressionVerificationOutput{Result: false, Err: fmt.Errorf("invalid expression type requested")}, Err: nil} } @@ -171,6 +173,7 @@ func (ev *ExpressionVerificationAccessorImpl) removeExpressions(inputConv *inter for colName, colDef := range table.ColDefs { colDef.AutoGen = ddl.AutoGenCol{} colDef.DefaultValue = ddl.DefaultValue{} + colDef.GeneratedColumn = ddl.GeneratedColumn{} table.ColDefs[colName] = colDef } } @@ -197,27 +200,63 @@ func (ddlv *DDLVerifierImpl) GetSourceExpressionDetails(conv *internal.Conv, tab srcTable := conv.SrcSchema[tableId] for _, srcColId := range srcTable.ColIds { srcCol := srcTable.ColDefs[srcColId] + var expression ddl.Expression + var expressionType string + isExpressionAvailable := false if srcCol.DefaultValue.IsPresent { + expression = srcCol.DefaultValue.Value + isExpressionAvailable = true + expressionType = constants.DEFAULT_EXPRESSION + } else if srcCol.GeneratedColumn.IsPresent { + expression = srcCol.GeneratedColumn.Value + isExpressionAvailable = true + if srcCol.GeneratedColumn.Type == ddl.GeneratedColStored { + expressionType = constants.STORED_GENERATED + } else { + expressionType = constants.VIRTUAL_GENERATED + } + } + if isExpressionAvailable { tyName := conv.SpSchema[tableId].ColDefs[srcColId].T.Name if conv.SpDialect == constants.DIALECT_POSTGRESQL { tyName = ddl.GetPGType(conv.SpSchema[tableId].ColDefs[srcColId].T) } - defaultValueExp := internal.ExpressionDetail{ + expressionDetail := internal.ExpressionDetail{ ReferenceElement: internal.ReferenceElement{ Name: tyName, }, - ExpressionId: srcCol.DefaultValue.Value.ExpressionId, - Expression: srcCol.DefaultValue.Value.Statement, - Type: constants.DEFAULT_EXPRESSION, + ExpressionId: expression.ExpressionId, + Expression: expression.Statement, + Type: expressionType, + SpTableName: conv.ToSpanner[srcTable.Name].Name, Metadata: map[string]string{"TableId": tableId, "ColId": srcColId}, } - expressionDetails = append(expressionDetails, defaultValueExp) + expressionDetails = append(expressionDetails, expressionDetail) } } } return expressionDetails } +func (ddlv *DDLVerifierImpl) getExpressionDetail( + conv *internal.Conv, tableId, spColId, expressionType, expressionId, expression string, spCol ddl.ColumnDef) internal.ExpressionDetail { + tyName := conv.SpSchema[tableId].ColDefs[spColId].T.Name + if conv.SpDialect == constants.DIALECT_POSTGRESQL { + tyName = ddl.GetPGType(conv.SpSchema[tableId].ColDefs[spColId].T) + } + expressionDetail := internal.ExpressionDetail{ + ReferenceElement: internal.ReferenceElement{ + Name: tyName, + }, + ExpressionId: expressionId, + Expression: expression, + Type: expressionType, + Metadata: map[string]string{"TableId": tableId, "ColId": spColId}, + } + return expressionDetail + +} + func (ddlv *DDLVerifierImpl) GetSpannerExpressionDetails(conv *internal.Conv, tableIds []string) []internal.ExpressionDetail { expressionDetails := []internal.ExpressionDetail{} // Collect default values for verification @@ -226,21 +265,17 @@ func (ddlv *DDLVerifierImpl) GetSpannerExpressionDetails(conv *internal.Conv, ta for _, spColId := range spTable.ColIds { spCol := spTable.ColDefs[spColId] if spCol.DefaultValue.IsPresent { - tyName := conv.SpSchema[tableId].ColDefs[spColId].T.Name - if conv.SpDialect == constants.DIALECT_POSTGRESQL { - tyName = ddl.GetPGType(conv.SpSchema[tableId].ColDefs[spColId].T) - } - defaultValueExp := internal.ExpressionDetail{ - ReferenceElement: internal.ReferenceElement{ - Name: tyName, - }, - ExpressionId: spCol.DefaultValue.Value.ExpressionId, - Expression: spCol.DefaultValue.Value.Statement, - Type: constants.DEFAULT_EXPRESSION, - Metadata: map[string]string{"TableId": tableId, "ColId": spColId}, - } + defaultValueExp := ddlv.getExpressionDetail( + conv, tableId, spColId, constants.DEFAULT_EXPRESSION, spCol.DefaultValue.Value.ExpressionId, + spCol.DefaultValue.Value.Statement, spCol) expressionDetails = append(expressionDetails, defaultValueExp) } + if spCol.GeneratedColumn.IsPresent { + generatedColExp := ddlv.getExpressionDetail( + conv, tableId, spColId, string(spCol.GeneratedColumn.Type), spCol.GeneratedColumn.Value.ExpressionId, + spCol.GeneratedColumn.Value.Statement, spCol) + expressionDetails = append(expressionDetails, generatedColExp) + } } } return expressionDetails diff --git a/internal/convert.go b/internal/convert.go index 3a01a9f2c4..44a9aa3375 100644 --- a/internal/convert.go +++ b/internal/convert.go @@ -158,6 +158,7 @@ const ( CassandraMAP PossibleOverflow IdentitySkipRange + GeneratedColumnValueError ) const ( @@ -331,6 +332,7 @@ type ExpressionDetail struct { ExpressionId string Expression string Type string + SpTableName string Metadata map[string]string } diff --git a/internal/reports/report_helpers.go b/internal/reports/report_helpers.go index 41073be38d..d5e4397256 100644 --- a/internal/reports/report_helpers.go +++ b/internal/reports/report_helpers.go @@ -306,7 +306,7 @@ func buildTableReportBody(conv *internal.Conv, tableId string, issues map[string // on case of srcType. spColType = strings.ToLower(spColType) switch i { - case internal.DefaultValue: + case internal.DefaultValue, internal.GeneratedColumnValueError: toAppend := Issue{ Category: IssueDB[i].Category, 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 { CategoryDescription string }{ 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"}, + internal.GeneratedColumnValueError: {Brief: "Some columns have generated expression which Spanner migration tool cannot not migrate. Please add the expressions manually", Severity: Errors, batch: true, Category: "MISSING_GENERATED_COL_VALUE_CONSTRAINTS"}, internal.TypeMismatch: {Brief: "Type mismatch in check constraint mention in table", Severity: warning, Category: "TYPE_MISMATCH"}, internal.TypeMismatchError: {Brief: "Type mismatch in check constraint mention in table", Severity: Errors, Category: "TYPE_MISMATCH_ERROR"}, internal.InvalidCondition: {Brief: "Invalid condition in check constraint mention in table", Severity: warning, Category: "INVALID_CONDITION"}, diff --git a/schema/schema.go b/schema/schema.go index 66408e33dc..4f93ebb77b 100644 --- a/schema/schema.go +++ b/schema/schema.go @@ -50,13 +50,14 @@ type Table struct { // Column represents a database column. // TODO: add support for foreign keys. type Column struct { - Name string - Type Type - NotNull bool - Ignored Ignored - Id string - AutoGen ddl.AutoGenCol - DefaultValue ddl.DefaultValue + Name string + Type Type + NotNull bool + Ignored Ignored + Id string + AutoGen ddl.AutoGenCol + DefaultValue ddl.DefaultValue + GeneratedColumn ddl.GeneratedColumn } // ForeignKey represents a foreign key. diff --git a/sources/common/infoschema.go b/sources/common/infoschema.go index b2880db44b..fa2fd1e209 100644 --- a/sources/common/infoschema.go +++ b/sources/common/infoschema.go @@ -256,8 +256,8 @@ func (is *InfoSchemaImpl) GetIncludedSrcTablesFromConv(conv *internal.Conv) (sch return schemaToTablesMap, nil } -// SanitizeDefaultValue removes extra characters added to Default Value in information schema in MySQL. -func SanitizeDefaultValue(defaultValue string, ty string, generated bool) string { +// SanitizeExpressionsValue removes extra characters added to Default Value in information schema in MySQL. +func SanitizeExpressionsValue(expressionValue string, ty string, generated bool) string { types := []string{"char", "varchar", "text", "varbinary", "tinyblob", "tinytext", "text", "blob", "mediumtext", "mediumblob", "longtext", "longblob", "STRING"} // Check if ty exists in the types array @@ -268,11 +268,11 @@ func SanitizeDefaultValue(defaultValue string, ty string, generated bool) string break } } - defaultValue = strings.ReplaceAll(defaultValue, "_utf8mb4", "") - defaultValue = strings.ReplaceAll(defaultValue, "\\\\", "\\") - defaultValue = strings.ReplaceAll(defaultValue, "\\'", "'") - if !generated && stringType && !strings.HasPrefix(defaultValue, "'") && !strings.HasSuffix(defaultValue, "'") { - defaultValue = "'" + defaultValue + "'" + expressionValue = strings.ReplaceAll(expressionValue, "_utf8mb4", "") + expressionValue = strings.ReplaceAll(expressionValue, "\\\\", "\\") + expressionValue = strings.ReplaceAll(expressionValue, "\\'", "'") + if !generated && stringType && !strings.HasPrefix(expressionValue, "'") && !strings.HasSuffix(expressionValue, "'") { + expressionValue = "'" + expressionValue + "'" } - return defaultValue + return expressionValue } diff --git a/sources/common/infoschema_test.go b/sources/common/infoschema_test.go index 0286823aac..047ec4a390 100644 --- a/sources/common/infoschema_test.go +++ b/sources/common/infoschema_test.go @@ -20,8 +20,7 @@ import ( "github.com/stretchr/testify/assert" ) - -func TestSanitizeDefaultValue(t *testing.T) { +func TestSanitizeExpressionsValue(t *testing.T) { tests := []struct { inputString string ty string @@ -41,7 +40,7 @@ func TestSanitizeDefaultValue(t *testing.T) { {"_utf8mb4\\'C:\\\\\\\\Users\\\\\\\\johndoe\\\\\\\\Documents\\\\\\\\myfile.txt\\'", "char", false, "'C:\\\\Users\\\\johndoe\\\\Documents\\\\myfile.txt'"}, } for _, test := range tests { - result := SanitizeDefaultValue(test.inputString, test.ty, test.generated) + result := SanitizeExpressionsValue(test.inputString, test.ty, test.generated) assert.Equal(t, test.expectedString, result) } } diff --git a/sources/common/toddl.go b/sources/common/toddl.go index c99a4d54bc..08a4962241 100644 --- a/sources/common/toddl.go +++ b/sources/common/toddl.go @@ -31,7 +31,9 @@ package common import ( "context" + "encoding/json" "fmt" + "github.com/GoogleCloudPlatform/spanner-migration-tool/logger" "reflect" "strconv" "strings" @@ -673,6 +675,9 @@ func CvtIndexHelper(conv *internal.Conv, tableId string, srcIndex schema.Index, // Applies all valid expressions which can be migrated to spanner conv object func spannerSchemaApplyExpressions(conv *internal.Conv, expressions internal.VerifyExpressionsOutput) { + // Log expression verication output list + expression_str, _ := json.Marshal(expressions.ExpressionVerificationOutputList) + logger.Log.Info(fmt.Sprintf("Expressions: %s", string(expression_str))) for _, expression := range expressions.ExpressionVerificationOutputList { switch expression.ExpressionDetail.Type { case "DEFAULT": @@ -696,6 +701,34 @@ func spannerSchemaApplyExpressions(conv *internal.Conv, expressions internal.Ver conv.SchemaIssues[tableId].ColumnLevelIssues[columnId] = colIssues } } + case constants.VIRTUAL_GENERATED, constants.STORED_GENERATED: + { + tableId := expression.ExpressionDetail.Metadata["TableId"] + columnId := expression.ExpressionDetail.Metadata["ColId"] + + cols := conv.SpSchema[tableId].ColDefs[columnId] + if expression.Result { + logger.Log.Info(fmt.Sprintf("Cat cat")) + col := conv.SpSchema[tableId].ColDefs[columnId] + col.GeneratedColumn = ddl.GeneratedColumn{ + IsPresent: true, + Value: ddl.Expression{ + ExpressionId: expression.ExpressionDetail.ExpressionId, + Statement: expression.ExpressionDetail.Expression, + }, + Type: ddl.GeneratedColType(expression.ExpressionDetail.Type), + } + conv.SpSchema[tableId].ColDefs[columnId] = col + } else { + logger.Log.Info(fmt.Sprintf("Bat Bat")) + colIssues := conv.SchemaIssues[tableId].ColumnLevelIssues[columnId] + colIssues = append(colIssues, internal.GeneratedColumnValueError) + conv.SchemaIssues[tableId].ColumnLevelIssues[columnId] = colIssues + } + expressionDetail, _ := json.Marshal(expression) + logger.Log.Info(fmt.Sprintf("[%s] Expression Detail: %v", cols.Name, string(expressionDetail))) + + } } } } diff --git a/sources/mysql/infoschema.go b/sources/mysql/infoschema.go index 6571a3a9bc..ca3b4192ee 100644 --- a/sources/mysql/infoschema.go +++ b/sources/mysql/infoschema.go @@ -176,7 +176,7 @@ func (isi InfoSchemaImpl) GetTables() ([]common.SchemaAndName, error) { // GetColumns returns a list of Column objects and names// ProcessColumns func (isi InfoSchemaImpl) GetColumns(conv *internal.Conv, table common.SchemaAndName, constraints map[string][]string, primaryKeys []string) (map[string]schema.Column, []string, error) { - 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 + 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 FROM information_schema.COLUMNS c where table_schema = ? and table_name = ? ORDER BY c.ordinal_position;` cols, err := isi.Db.Query(q, table.Schema, table.Name) @@ -187,15 +187,20 @@ func (isi InfoSchemaImpl) GetColumns(conv *internal.Conv, table common.SchemaAnd colDefs := make(map[string]schema.Column) var colIds []string var colName, dataType, isNullable, columnType string - var colDefault, colExtra sql.NullString + var colDefault, colExtra, colGeneratedExpression sql.NullString var charMaxLen, numericPrecision, numericScale sql.NullInt64 var colAutoGen ddl.AutoGenCol for cols.Next() { - err := cols.Scan(&colName, &dataType, &columnType, &isNullable, &colDefault, &charMaxLen, &numericPrecision, &numericScale, &colExtra) + err := cols.Scan(&colName, &dataType, &columnType, &isNullable, &colDefault, &charMaxLen, &numericPrecision, &numericScale, &colGeneratedExpression, &colExtra) if err != nil { conv.Unexpected(fmt.Sprintf("Can't scan: %v", err)) continue } + + // It's required as empty string is considered as valid within Database SQL. + if colGeneratedExpression.String == "" { + colGeneratedExpression.Valid = false + } ignored := schema.Ignored{} ignored.Default = colDefault.Valid colId := internal.GenerateColumnId() @@ -219,18 +224,35 @@ func (isi InfoSchemaImpl) GetColumns(conv *internal.Conv, table common.SchemaAnd } defaultVal.Value = ddl.Expression{ ExpressionId: internal.GenerateExpressionId(), - Statement: common.SanitizeDefaultValue(colDefault.String, ty, colExtra.String == constants.DEFAULT_GENERATED), + Statement: common.SanitizeExpressionsValue(colDefault.String, ty, colExtra.String == constants.DEFAULT_GENERATED), + } + } + + generatedColumn := ddl.GeneratedColumn{ + IsPresent: colGeneratedExpression.Valid, + Value: ddl.Expression{}, + } + if colGeneratedExpression.Valid { + // Defaults to STORED type + generatedColumn.Type = ddl.GeneratedColStored + if colExtra.String == constants.VIRTUAL_GENERATED { + generatedColumn.Type = ddl.GeneratedColVirtual + } + generatedColumn.Value = ddl.Expression{ + ExpressionId: internal.GenerateExpressionId(), + Statement: common.SanitizeExpressionsValue(colGeneratedExpression.String, "", false), } } c := schema.Column{ - Id: colId, - Name: colName, - Type: toType(dataType, columnType, charMaxLen, numericPrecision, numericScale), - NotNull: common.ToNotNull(conv, isNullable), - Ignored: ignored, - AutoGen: colAutoGen, - DefaultValue: defaultVal, + Id: colId, + Name: colName, + Type: toType(dataType, columnType, charMaxLen, numericPrecision, numericScale), + NotNull: common.ToNotNull(conv, isNullable), + Ignored: ignored, + AutoGen: colAutoGen, + DefaultValue: defaultVal, + GeneratedColumn: generatedColumn, } colDefs[colId] = c colIds = append(colIds, colId) diff --git a/spanner/ddl/ast.go b/spanner/ddl/ast.go index a4415f8dd9..a25173d2d5 100644 --- a/spanner/ddl/ast.go +++ b/spanner/ddl/ast.go @@ -31,6 +31,8 @@ import ( "github.com/GoogleCloudPlatform/spanner-migration-tool/logger" ) +type GeneratedColType string + const ( // Types supported by Spanner with google_standard_sql (default) dialect. // Bool represent BOOL type. @@ -78,7 +80,9 @@ const ( // Jsonb represents the PG.JSONB type PGJSONB string = "JSONB" // PGMaxLength represents sentinel for Type's Len field in PG. - PGMaxLength = 2621440 + PGMaxLength = 2621440 + GeneratedColStored GeneratedColType = "STORED" + GeneratedColVirtual GeneratedColType = "VIRTUAL" ) var STANDARD_TYPE_TO_PGSQL_TYPEMAP = map[string]string{ @@ -180,14 +184,15 @@ func (ty Type) PGPrintColumnDefType() string { // column_def: // column_name type [NOT NULL] [options_def] type ColumnDef struct { - Name string - T Type - NotNull bool - Comment string - Id string - AutoGen AutoGenCol - DefaultValue DefaultValue - Opts map[string]string + Name string + T Type + NotNull bool + Comment string + Id string + AutoGen AutoGenCol + DefaultValue DefaultValue + GeneratedColumn GeneratedColumn + Opts map[string]string } // Config controls how AST nodes are printed (aka unparsed). @@ -245,6 +250,7 @@ func (cd ColumnDef) PrintColumnDef(c Config) (string, string) { } s += cd.DefaultValue.PGPrintDefaultValue(cd.T) s += cd.AutoGen.PGPrintAutoGenCol(c) + s += cd.GeneratedColumn.PGPrintGeneratedColumn(cd.T) } else { s = fmt.Sprintf("%s %s", c.quote(cd.Name), cd.T.PrintColumnDefType()) if cd.NotNull { @@ -252,13 +258,14 @@ func (cd ColumnDef) PrintColumnDef(c Config) (string, string) { } s += cd.DefaultValue.PrintDefaultValue(cd.T) s += cd.AutoGen.PrintAutoGenCol(c) + s += cd.GeneratedColumn.PrintGeneratedColumn(cd.T) } - var opts []string + var opts []string if cd.Opts != nil { if opt, ok := cd.Opts["cassandra_type"]; ok && opt != "" { opts = append(opts, fmt.Sprintf("cassandra_type = '%s'", opt)) } - } + } if len(opts) > 0 { s += " OPTIONS (" + strings.Join(opts, ", ") + ")" } @@ -445,6 +452,13 @@ type CreateIndex struct { // interleaving clauses yet, so we omit them for now. } +// GeneratedColumn represents a Generated Column. +type GeneratedColumn struct { + IsPresent bool + Value Expression + Type GeneratedColType +} + type AutoGenCol struct { Name string // Type of autogenerated column, example, pre-defined(uuid) or user-defined(sequence) @@ -470,6 +484,23 @@ type Expression struct { Statement string } +func (gc GeneratedColumn) PrintGeneratedColumn(ty Type) string { + if !gc.IsPresent { + return "" + } + var value string + switch ty.Name { + case "FLOAT32", "NUMERIC", "BOOL", "BYTES": + value = fmt.Sprintf(" AS (CAST(%s AS %s))", gc.Value.Statement, ty.Name) + default: + value = " AS (" + gc.Value.Statement + ")" + } + if gc.Type == GeneratedColStored { + value += " " + string(GeneratedColStored) + } + return value +} + func (dv DefaultValue) PrintDefaultValue(ty Type) string { if !dv.IsPresent { return "" @@ -484,6 +515,21 @@ func (dv DefaultValue) PrintDefaultValue(ty Type) string { return value } +func (gc GeneratedColumn) PGPrintGeneratedColumn(ty Type) string { + if !gc.IsPresent { + return "" + } + var value string + switch GetPGType(ty) { + case "FLOAT8", "FLOAT4", "REAL", "NUMERIC", "DECIMAL", "BOOL", "BYTEA": + value = fmt.Sprintf(" GENERATED ALWAYS AS (CAST(%s AS %s))", gc.Value.Statement, GetPGType(ty)) + default: + value = " GENERATED ALWAYS AS (" + gc.Value.Statement + ")" + } + value += " " + string(gc.Type) + return value +} + func (dv DefaultValue) PGPrintDefaultValue(ty Type) string { if !dv.IsPresent { return "" @@ -697,7 +743,7 @@ func GetDDL(c Config, tableSchema Schema, sequenceSchema map[string]Sequence, db ddl = append(ddl, dbOptions.PGPrintDatabaseOptions()...) } else { dbOptionsDdl := dbOptions.PrintDatabaseOptions() - if (dbOptionsDdl != "") { + if dbOptionsDdl != "" { ddl = append(ddl, dbOptionsDdl) } } @@ -765,7 +811,7 @@ type Sequence struct { SkipRangeMax string StartWithCounter string ColumnsUsingSeq map[string][]string - ColumnsOwningSeq map[string][]string + ColumnsOwningSeq map[string][]string } func (seq Sequence) PrintSequence(c Config) string { @@ -816,7 +862,7 @@ func (seq Sequence) PGPrintSequence(c Config) string { } type DatabaseOptions struct { - DbName string + DbName string DefaultTimezone string } diff --git a/ui/src/app/app.constants.ts b/ui/src/app/app.constants.ts index 371540f40f..c653000af7 100644 --- a/ui/src/app/app.constants.ts +++ b/ui/src/app/app.constants.ts @@ -165,6 +165,7 @@ export enum PersistedFormValues { } export const defaultAndSequenceSupportedDbs: string[] = ['MySQL'] +export const generatedColSupportedDbs: string[] = ['MySQL'] export const identitySupportedDbs: string[] = ['MySQL', 'Postgres'] export const dialogConfigAddSequence: MatDialogConfig = { diff --git a/ui/src/app/components/object-detail/object-detail.component.html b/ui/src/app/components/object-detail/object-detail.component.html index f3a240bdaf..f61a881f8a 100644 --- a/ui/src/app/components/object-detail/object-detail.component.html +++ b/ui/src/app/components/object-detail/object-detail.component.html @@ -150,6 +150,25 @@

+ + Generated-Col (GC) + +
+ {{ element.get('srcGeneratedColExp').value }} +
+ +
+ + + GC Type + + {{ element.get('srcGeneratedColExpType').value }} + + + Pk @@ -375,18 +394,34 @@

+ + Generated-Col + +
+ {{ element.get('spGeneratedColumn').value }} +
+ +
+ +
+ +
+ Default Value
- {{ element.get('spDefaultValue').value }} + {{ element.get('spDefaultValue').value }}
- +
- +
@@ -1227,4 +1262,4 @@

- \ No newline at end of file + diff --git a/ui/src/app/components/object-detail/object-detail.component.scss b/ui/src/app/components/object-detail/object-detail.component.scss index fa176f99b3..2812080e00 100644 --- a/ui/src/app/components/object-detail/object-detail.component.scss +++ b/ui/src/app/components/object-detail/object-detail.component.scss @@ -207,8 +207,11 @@ .mat-column-spColName, .mat-column-srcDataType, .mat-column-spDataType, + .mat-column-srcGeneratedColExp, + .mat-column-srcGeneratedColExpType, .mat-column-spAutoGen, .mat-column-spDefaultValue, + .mat-column-spGeneratedColumn, .mat-column-spIndexColName, .mat-column-srcIndexColName, .mat-column-spSeqName, diff --git a/ui/src/app/components/object-detail/object-detail.component.spec.ts b/ui/src/app/components/object-detail/object-detail.component.spec.ts index 186bd0f8c5..7a7d1fa36b 100644 --- a/ui/src/app/components/object-detail/object-detail.component.spec.ts +++ b/ui/src/app/components/object-detail/object-detail.component.spec.ts @@ -117,6 +117,8 @@ describe('ObjectDetailComponent', () => { } }, srcDefaultValue: '', + srcGeneratedColExp: '', + srcGeneratedColExpType: '', spDefaultValue: { Value: { ExpressionId: '', @@ -124,6 +126,14 @@ describe('ObjectDetailComponent', () => { }, IsPresent: false }, + spGeneratedColumn: { + Value: { + ExpressionId: '', + Statement: '' + }, + IsPresent: false, + Type: "", + }, }, { spOrder: 2, @@ -162,6 +172,8 @@ describe('ObjectDetailComponent', () => { StartCounterWith: '' } }, + srcGeneratedColExp: '', + srcGeneratedColExpType: '', srcDefaultValue: '', spDefaultValue: { Value: { @@ -170,6 +182,14 @@ describe('ObjectDetailComponent', () => { }, IsPresent: false }, + spGeneratedColumn: { + Value: { + ExpressionId: '', + Statement: '' + }, + IsPresent: false, + Type: "", + }, }, ] component.fkData = [ @@ -503,6 +523,7 @@ describe('ObjectDetailComponent', () => { spSkipRangeMin: '', spSkipRangeMax: '', spStartCounterWith: '', + spGeneratedColumn: '', spDefaultValue: '', }), ]) @@ -526,6 +547,7 @@ describe('ObjectDetailComponent', () => { MaxColLength: '', AutoGen: { Name: '', GenerationType: '', IdentityOptions: { SkipRangeMin: '', SkipRangeMax: '', StartCounterWith: '' } }, DefaultValue: { IsPresent: false, Value: { ExpressionId: '', Statement: '' } }, + GeneratedColumn: { IsPresent: false, Type: '', Value: { ExpressionId: '', Statement: '' } }, }, }, } diff --git a/ui/src/app/components/object-detail/object-detail.component.ts b/ui/src/app/components/object-detail/object-detail.component.ts index 2675894690..81699a005c 100644 --- a/ui/src/app/components/object-detail/object-detail.component.ts +++ b/ui/src/app/components/object-detail/object-detail.component.ts @@ -29,7 +29,7 @@ import { AddNewSequenceComponent } from '../add-new-sequence/add-new-sequence.co import { linkedFieldsValidatorSequence } from 'src/app/utils/utils'; import { FetchService } from 'src/app/services/fetch/fetch.service' import ICreateSequence from 'src/app/model/auto-gen' -import { defaultAndSequenceSupportedDbs, identitySupportedDbs } from 'src/app/app.constants' +import { defaultAndSequenceSupportedDbs, identitySupportedDbs, generatedColSupportedDbs } from 'src/app/app.constants' import ICcTabData from 'src/app/model/cc-tab-data' import { title } from 'process' @Component({ @@ -79,6 +79,7 @@ export class ObjectDetailComponent implements OnInit, OnDestroy { processedAutoGenMap: GroupedAutoGens = {}; sequenceKinds: string[] = [] supportsDefaultAndSequence: boolean = false + supportsGeneratedColumn: boolean = false supportsAutoGen: boolean = false foreignKeyActionsSupported: boolean = false spTablesForInterleaving: { id: string; name: string }[] = []; @@ -93,6 +94,7 @@ export class ObjectDetailComponent implements OnInit, OnDestroy { this.srcDbName = extractSourceDbName(this.conv.DatabaseType) } this.supportsDefaultAndSequence = defaultAndSequenceSupportedDbs.includes(this.srcDbName) + this.supportsGeneratedColumn = generatedColSupportedDbs.includes(this.srcDbName) this.supportsAutoGen = identitySupportedDbs.includes(this.srcDbName) if (this.srcDbName == SourceDbNames.MySQL || this.srcDbName == SourceDbNames.Postgres) { this.foreignKeyActionsSupported = true @@ -227,7 +229,7 @@ export class ObjectDetailComponent implements OnInit, OnDestroy { this.interleaveType = this.getInterleaveTypeFromConv() this.onDeleteAction = this.getInterleaveOnDeleteActionFromConv() ?? '' - + this.isEditMode = false this.isFkEditMode = false this.isIndexEditMode = false @@ -256,8 +258,13 @@ export class ObjectDetailComponent implements OnInit, OnDestroy { this.spColspan+=1; this.srcColspan+=1; } + if (this.supportsGeneratedColumn && !this.spDisplayedColumns.includes("spGeneratedColumn")) { + this.srcDisplayedColumns.push("srcGeneratedColExp"); + this.srcDisplayedColumns.push("srcGeneratedColExpType"); + this.spDisplayedColumns.splice(6, 0,"spGeneratedColumn"); + } if (this.supportsDefaultAndSequence && !this.spDisplayedColumns.includes("spAutoGen")) { - this.srcDisplayedColumns.push("srcDefaultValue");; + this.srcDisplayedColumns.push("srcDefaultValue"); this.spDisplayedColumns.splice(7, 0,"spDefaultValue"); this.spColspan+=1; this.srcColspan+=1; @@ -306,6 +313,8 @@ export class ObjectDetailComponent implements OnInit, OnDestroy { srcIsPk: new FormControl(row.srcIsPk), srcIsNotNull: new FormControl(row.srcIsNotNull), srcDefaultValue: new FormControl(row.srcDefaultValue), + srcGeneratedColExp: new FormControl(row.srcGeneratedColExp), + srcGeneratedColExpType: new FormControl(row.srcGeneratedColExpType), srcColMaxLength: new FormControl(row.srcColMaxLength), srcAutoGen: new FormControl(row.srcAutoGen), spOrder: new FormControl(row.srcOrder), @@ -325,8 +334,9 @@ export class ObjectDetailComponent implements OnInit, OnDestroy { spSkipRangeMax: new FormControl(row.spSkipRangeMax, Validators.pattern('^[0-9]+$')), spStartCounterWith: new FormControl(row.spStartCounterWith, Validators.pattern('^[0-9]+$')), spDefaultValue: new FormControl(row.spDefaultValue ? row.spDefaultValue.Value.Statement : ''), + spGeneratedColumn: new FormControl(row.spGeneratedColumn ? row.spGeneratedColumn.Value.Statement : ''), }, { validators: linkedFieldsValidatorSequence('spSkipRangeMin', 'spSkipRangeMax') }) - // Disable spDefaultValue if spAutoGen is set + // Disable spDefaultValue and spGeneratedColumn if spAutoGen is set if (row.spAutoGen.Name !== '') { fb.get('spDefaultValue')?.disable(); } @@ -378,6 +388,8 @@ export class ObjectDetailComponent implements OnInit, OnDestroy { srcIsPk: new FormControl(col.srcIsPk), srcIsNotNull: new FormControl(col.srcIsNotNull), srcDefaultValue: new FormControl(col.srcDefaultValue), + srcGeneratedColExp: new FormControl(col.srcGeneratedColExp), + srcGeneratedColExpType: new FormControl(col.srcGeneratedColExpType), srcColMaxLength: new FormControl(col.srcColMaxLength), srcAutoGen: new FormControl(col.srcAutoGen), spOrder: new FormControl(col.spOrder), @@ -391,6 +403,7 @@ export class ObjectDetailComponent implements OnInit, OnDestroy { spCassandraOption: new FormControl(col.spCassandraOption), spAutoGen: new FormControl(col.spAutoGen), spDefaultValue: new FormControl(col.spDefaultValue ? col.spDefaultValue.Value.Statement : ''), + spGeneratedColumn: new FormControl(col.spGeneratedColumn ? col.spGeneratedColumn.Value.Statement : ''), }) ) } else { @@ -417,6 +430,8 @@ export class ObjectDetailComponent implements OnInit, OnDestroy { srcIsPk: new FormControl(col.srcIsPk), srcIsNotNull: new FormControl(col.srcIsNotNull), srcDefaultValue: new FormControl(col.srcDefaultValue), + srcGeneratedColExp: new FormControl(col.srcGeneratedColExp), + srcGeneratedColExpType: new FormControl(col.srcGeneratedColExpType), srcColMaxLength: new FormControl(col.srcColMaxLength), srcAutoGen: new FormControl(col.srcAutoGen), spOrder: new FormControl(col.srcOrder), @@ -430,6 +445,7 @@ export class ObjectDetailComponent implements OnInit, OnDestroy { spCassandraOption: new FormControl(col.spCassandraOption), spAutoGen: new FormControl(col.spAutoGen), spDefaultValue: new FormControl(col.spDefaultValue ? col.spDefaultValue.Value.Statement : ''), + spGeneratedColumn: new FormControl(col.spGeneratedColumn ? col.spGeneratedColumn.Value.Statement : ''), }) ) } @@ -512,11 +528,19 @@ export class ObjectDetailComponent implements OnInit, OnDestroy { MaxColLength: col.spColMaxLength, AutoGen: autoGen, DefaultValue: { - IsPresent: col.spDefaultValue ? true : false, + IsPresent: col.spDefaultValue ? col.spDefaultValue.IsPresent : false, Value: { ExpressionId: '', - Statement: String(col.spDefaultValue) + Statement: col.spDefaultValue ? col.spDefaultValue.Value.Statement : '' } + }, + GeneratedColumn: { + IsPresent: col.spGeneratedColumn ? col.spGeneratedColumn.IsPresent : false, + Value: { + ExpressionId: '', + Statement: col.spGeneratedColumn ? col.spGeneratedColumn.Value.Statement : '' + }, + Type: 'STORED' } } break @@ -531,11 +555,19 @@ export class ObjectDetailComponent implements OnInit, OnDestroy { MaxColLength: col.spColMaxLength, AutoGen: autoGen, DefaultValue: { - IsPresent: col.spDefaultValue ? true : false, + IsPresent: col.spDefaultValue ? col.spDefaultValue.IsPresent : false, Value: { ExpressionId: '', Statement: col.spDefaultValue? col.spDefaultValue.Value.Statement: '' } + }, + GeneratedColumn: { + IsPresent: col.spGeneratedColumn ? col.spGeneratedColumn.IsPresent : false, + Value: { + ExpressionId: '', + Statement: col.spGeneratedColumn ? col.spGeneratedColumn.Value.Statement : '' + }, + Type: 'STORED' } } } @@ -565,6 +597,14 @@ export class ObjectDetailComponent implements OnInit, OnDestroy { ExpressionId: '', Statement: '' } + }, + GeneratedColumn: { + IsPresent: false, + Value: { + ExpressionId: '', + Statement: '' + }, + Type: 'STORED' } } }) @@ -627,6 +667,7 @@ export class ObjectDetailComponent implements OnInit, OnDestroy { this.localTableData[index].spCassandraOption = this.droppedColumns[addedRowIndex].spCassandraOption this.localTableData[index].spAutoGen = this.droppedColumns[addedRowIndex].spAutoGen this.localTableData[index].spDefaultValue = this.droppedColumns[addedRowIndex].spDefaultValue + this.localTableData[index].spGeneratedColumn = this.droppedColumns[addedRowIndex].spGeneratedColumn let ind = this.droppedColumns .map((col: IColumnTabData) => col.spColName) .indexOf(this.addedColumnName) @@ -785,6 +826,14 @@ export class ObjectDetailComponent implements OnInit, OnDestroy { Statement: '' }, IsPresent: false + }, + col.spGeneratedColumn = { + Value: { + ExpressionId: '', + Statement: '' + }, + IsPresent: false, + Type: '', } } }) @@ -845,7 +894,8 @@ export class ObjectDetailComponent implements OnInit, OnDestroy { spOrder: row.spOrder, spId: row.spId, spAutoGen: row.spAutoGen, - spDefaultValue: row.spDefaultValue + spDefaultValue: row.spDefaultValue, + spGeneratedColumn: row.spGeneratedColumn, }) } }) @@ -876,6 +926,7 @@ export class ObjectDetailComponent implements OnInit, OnDestroy { spId: new FormControl(spArr[i].spId), spAutoGen: new FormControl(spArr[i].spAutoGen), spDefaultValue: new FormControl(spArr[i].spDefaultValue), + spGeneratedColumn: new FormControl(spArr[i].spGeneratedColumn), }) ) } @@ -1541,7 +1592,7 @@ export class ObjectDetailComponent implements OnInit, OnDestroy { console.error('Interleave type cannot be empty'); return; } - + let tableId = this.currentObject!.id; this.data @@ -1988,4 +2039,4 @@ export class ObjectDetailComponent implements OnInit, OnDestroy { ngOnDestroy(): void { this.subscriptions.unsubscribe() } -} \ No newline at end of file +} diff --git a/ui/src/app/model/conv.ts b/ui/src/app/model/conv.ts index 3f9a80fe30..1bfeb7e14f 100644 --- a/ui/src/app/model/conv.ts +++ b/ui/src/app/model/conv.ts @@ -29,6 +29,12 @@ export interface IDefaultValue { Value: IExpression } +export interface IGeneratedColumn { + IsPresent: boolean + Value: IExpression + Type: string +} + export interface IExpression { Statement: string ExpressionId: string @@ -74,6 +80,7 @@ export interface IColumn { Id: string AutoGen: AutoGen DefaultValue: IDefaultValue + GeneratedColumn: IGeneratedColumn } export interface IIgnored { @@ -165,6 +172,7 @@ export interface IColumnDef { Opts: { [key: string]: string } AutoGen: IAutoGen DefaultValue: IDefaultValue + GeneratedColumn: IGeneratedColumn } export interface IType { diff --git a/ui/src/app/model/edit-table.ts b/ui/src/app/model/edit-table.ts index 6e1c76624f..43d2d8bc2a 100644 --- a/ui/src/app/model/edit-table.ts +++ b/ui/src/app/model/edit-table.ts @@ -1,4 +1,4 @@ -import { IDefaultValue } from "./conv" +import {IDefaultValue, IGeneratedColumn} from "./conv" export default interface IColumnTabData { spOrder: number | string @@ -16,6 +16,8 @@ export default interface IColumnTabData { spIsNotNull: boolean srcIsNotNull: boolean srcDefaultValue: string + srcGeneratedColExp: string + srcGeneratedColExpType: string srcId: string spId: string srcColMaxLength: Number | string | undefined @@ -23,6 +25,7 @@ export default interface IColumnTabData { spCassandraOption: string srcAutoGen: AutoGen spDefaultValue: IDefaultValue + spGeneratedColumn: IGeneratedColumn } export interface AutoGen { @@ -64,4 +67,4 @@ export interface IColMaxLength { export interface IAddColumnProps { dialect: string tableId: string -} \ No newline at end of file +} diff --git a/ui/src/app/model/update-table.ts b/ui/src/app/model/update-table.ts index 548077655e..81fd9435b7 100644 --- a/ui/src/app/model/update-table.ts +++ b/ui/src/app/model/update-table.ts @@ -1,4 +1,4 @@ -import { IDefaultValue } from "./conv" +import {IDefaultValue, IGeneratedColumn} from "./conv" import { AutoGen } from "./edit-table" interface IUpdateCol { @@ -10,6 +10,7 @@ interface IUpdateCol { MaxColLength: string | undefined | Number AutoGen: AutoGen DefaultValue: IDefaultValue + GeneratedColumn: IGeneratedColumn } export interface ITableColumnChanges { ColumnId: string @@ -40,4 +41,4 @@ export interface IAddColumn { IsNullable: boolean AutoGen: AutoGen Option?: string -} \ No newline at end of file +} diff --git a/ui/src/app/services/conversion/conversion.service.spec.ts b/ui/src/app/services/conversion/conversion.service.spec.ts index f5316bc37a..a2e9f16476 100644 --- a/ui/src/app/services/conversion/conversion.service.spec.ts +++ b/ui/src/app/services/conversion/conversion.service.spec.ts @@ -320,6 +320,7 @@ describe('ConversionService', () => { Ignored: {Check: false, Identity: false, Default: false, Exclusion: false, ForeignKey: false, AutoIncrement: false}, DefaultValue: { IsPresent: false, Value: { Statement: '' , ExpressionId: '' } }, AutoGen: { Name: '', GenerationType: '', IdentityOptions: { SkipRangeMin: '', SkipRangeMax: '', StartCounterWith: '' } }, + GeneratedColumn: { IsPresent: false, Value: { Statement: '' , ExpressionId: '' }, Type: '' }, }, }, PrimaryKeys: [], @@ -342,7 +343,7 @@ describe('ConversionService', () => { NotNull: false, DefaultValue: { IsPresent: false, Value: { Statement: '', ExpressionId: '' } }, AutoGen: { Name: '', GenerationType: '', IdentityOptions: { SkipRangeMin: '', SkipRangeMax: '', StartCounterWith: '' } }, - Opts: {}, + GeneratedColumn: { IsPresent: false, Value: { Statement: '', ExpressionId: '' }, Type: '' }, Opts: {}, Comment: '', }, }, @@ -387,7 +388,17 @@ describe('ConversionService', () => { Statement: '', }, }, + spGeneratedColumn: { + IsPresent: false, + Value: { + ExpressionId: '', + Statement: '', + }, + Type: '', + }, spCassandraOption: '', + srcGeneratedColExp: '', + srcGeneratedColExpType: '' }, ] expect(result).toEqual(expected) @@ -410,6 +421,7 @@ describe('ConversionService', () => { Ignored: {Check: false, Identity: false, Default: false, Exclusion: false, ForeignKey: false, AutoIncrement: false}, DefaultValue: { IsPresent: false, Value: { Statement: '' , ExpressionId: '' } }, AutoGen: { Name: '', GenerationType: '', IdentityOptions: { SkipRangeMin: '', SkipRangeMax: '', StartCounterWith: '' } }, + GeneratedColumn: { IsPresent: false, Value: { Statement: '' , ExpressionId: '' }, Type: '' }, }, }, PrimaryKeys: [], @@ -432,6 +444,7 @@ describe('ConversionService', () => { NotNull: false, DefaultValue: { IsPresent: false, Value: { Statement: '', ExpressionId: '' } }, AutoGen: { Name: '', GenerationType: '', IdentityOptions: { SkipRangeMin: '', SkipRangeMax: '', StartCounterWith: '' } }, + GeneratedColumn: { IsPresent: false, Value: { Statement: '' , ExpressionId: '' }, Type: '' }, Opts: {}, Comment: '', }, @@ -442,6 +455,7 @@ describe('ConversionService', () => { NotNull: true, DefaultValue: { IsPresent: false, Value: { Statement: '', ExpressionId: '' } }, AutoGen: { Name: '', GenerationType: '', IdentityOptions: { SkipRangeMin: '', SkipRangeMax: '', StartCounterWith: '' } }, + GeneratedColumn: { IsPresent: false, Value: { Statement: '' , ExpressionId: '' }, Type: '' }, Opts: {}, Comment: '', }, @@ -459,8 +473,8 @@ describe('ConversionService', () => { const result = service.getColumnMapping('t1', conv); const expected: IColumnTabData[] = [ - { spOrder: 1, srcOrder: 1, spColName: 'sp_col', spDataType: 'STRING', srcColName: 'src_col', srcDataType: 'text', spIsPk: false, srcIsPk: false, spIsNotNull: false, srcIsNotNull: false, srcId: 'c1', srcDefaultValue: '', spId: 'c1', spColMaxLength: 255, srcColMaxLength: undefined, spAutoGen: { Name: '', GenerationType: '', IdentityOptions: { SkipRangeMin: '', SkipRangeMax: '', StartCounterWith: '' } }, spSkipRangeMin: '', spSkipRangeMax: '', spStartCounterWith: '', srcAutoGen: { Name: '', GenerationType: '', IdentityOptions: { SkipRangeMin: '', SkipRangeMax: '', StartCounterWith: '' } }, spDefaultValue: { IsPresent: false, Value: { ExpressionId: '', Statement: '' } }, spCassandraOption: '' }, - { spOrder: 2, srcOrder: '', spColName: 'new_sp_col', spDataType: 'INT64', srcColName: '', srcDataType: '', spIsPk: false, srcIsPk: false, spIsNotNull: true, srcIsNotNull: false, srcId: '', srcDefaultValue: '', spId: 'c2', spColMaxLength: 0, srcColMaxLength: '', spAutoGen: { Name: '', GenerationType: '', IdentityOptions: { SkipRangeMin: '', SkipRangeMax: '', StartCounterWith: '' } }, spSkipRangeMin: '', spSkipRangeMax: '', spStartCounterWith: '', srcAutoGen: { Name: '', GenerationType: '', IdentityOptions: { SkipRangeMin: '', SkipRangeMax: '', StartCounterWith: '' } }, spDefaultValue: { IsPresent: false, Value: { ExpressionId: '', Statement: '' } }, spCassandraOption: '' }, + { spOrder: 1, srcOrder: 1, spColName: 'sp_col', spDataType: 'STRING', srcColName: 'src_col', srcDataType: 'text', spIsPk: false, srcIsPk: false, spIsNotNull: false, srcIsNotNull: false, srcId: 'c1', srcDefaultValue: '', spId: 'c1', spColMaxLength: 255, srcColMaxLength: undefined, spAutoGen: { Name: '', GenerationType: '', IdentityOptions: { SkipRangeMin: '', SkipRangeMax: '', StartCounterWith: '' } }, spSkipRangeMin: '', spSkipRangeMax: '', spStartCounterWith: '', srcAutoGen: { Name: '', GenerationType: '', IdentityOptions: { SkipRangeMin: '', SkipRangeMax: '', StartCounterWith: '' } }, spGeneratedColumn: {IsPresent: false, Value: {ExpressionId: '', Statement: ''}, Type: ''}, spDefaultValue: { IsPresent: false, Value: { ExpressionId: '', Statement: '' } }, spCassandraOption: '', srcGeneratedColExp: '', srcGeneratedColExpType: '' }, + { spOrder: 2, srcOrder: '', spColName: 'new_sp_col', spDataType: 'INT64', srcColName: '', srcDataType: '', spIsPk: false, srcIsPk: false, spIsNotNull: true, srcIsNotNull: false, srcId: '', srcDefaultValue: '', spId: 'c2', spColMaxLength: 0, srcColMaxLength: '', spAutoGen: { Name: '', GenerationType: '', IdentityOptions: { SkipRangeMin: '', SkipRangeMax: '', StartCounterWith: '' } }, spSkipRangeMin: '', spSkipRangeMax: '', spStartCounterWith: '', srcAutoGen: { Name: '', GenerationType: '', IdentityOptions: { SkipRangeMin: '', SkipRangeMax: '', StartCounterWith: '' } }, spDefaultValue: { IsPresent: false, Value: { ExpressionId: '', Statement: '' } }, spCassandraOption: '', srcGeneratedColExp: '', srcGeneratedColExpType: '', spGeneratedColumn: {IsPresent: false, Value: {ExpressionId: '', Statement: ''}, Type: ''}, }, ] expect(result).toEqual(expected) }) diff --git a/ui/src/app/services/conversion/conversion.service.ts b/ui/src/app/services/conversion/conversion.service.ts index 57b53106ce..e892ca2956 100644 --- a/ui/src/app/services/conversion/conversion.service.ts +++ b/ui/src/app/services/conversion/conversion.service.ts @@ -371,6 +371,8 @@ export class ConversionService { srcIsNotNull: data.SrcSchema[tableId].ColDefs[colId].NotNull, srcId: colId, srcDefaultValue: data.SrcSchema[tableId].ColDefs[colId].DefaultValue.Value.Statement, + srcGeneratedColExp: data.SrcSchema[tableId].ColDefs[colId].GeneratedColumn.Value.Statement, + srcGeneratedColExpType: data.SrcSchema[tableId].ColDefs[colId].GeneratedColumn.Type, spId: spannerColDef ? colId : '', spColMaxLength: spannerColDef?.T.Len != 0 ? (spannerColDef?.T.Len != spColMax ? spannerColDef?.T.Len: 'MAX') : '', srcColMaxLength: data.SrcSchema[tableId].ColDefs[colId].Type.Mods != null ? data.SrcSchema[tableId].ColDefs[colId].Type.Mods[0] : '', @@ -394,6 +396,14 @@ export class ConversionService { Statement: '' } }, + spGeneratedColumn: spannerColDef?.GeneratedColumn != null ? spannerColDef?.GeneratedColumn : { + IsPresent: false, + Value: { + ExpressionId: '', + Statement: '' + }, + Type: '', + }, spCassandraOption: spannerColDef?.Opts?.["cassandra_type"] || '', } }) @@ -416,6 +426,8 @@ export class ConversionService { srcIsNotNull: false, srcId: '', srcDefaultValue: '', + srcGeneratedColExp: '', + srcGeneratedColExpType: '', spId: colId, srcColMaxLength: '', spColMaxLength: spannerColDef?.T.Len, @@ -440,6 +452,14 @@ export class ConversionService { Statement: '' } }, + spGeneratedColumn: spannerColDef?.GeneratedColumn != null ? spannerColDef?.GeneratedColumn : { + IsPresent: false, + Value: { + ExpressionId: '', + Statement: '' + }, + Type: '', + }, }) } }) diff --git a/ui/src/mocks/conv.ts b/ui/src/mocks/conv.ts index 21b2fb93d0..abf2d9dc77 100644 --- a/ui/src/mocks/conv.ts +++ b/ui/src/mocks/conv.ts @@ -34,6 +34,14 @@ export function createMockIConv(): IConv { ExpressionId: "" }, IsPresent: false + }, + GeneratedColumn: { + Value: { + Statement: "", + ExpressionId: "" + }, + IsPresent: false, + Type: '' } } }, @@ -100,6 +108,14 @@ export function createMockIConv(): IConv { }, IsPresent: false }, + GeneratedColumn: { + Value: { + Statement: "", + ExpressionId: "" + }, + IsPresent: false, + Type: '' + } } }, PrimaryKeys: [], @@ -249,6 +265,14 @@ export function createMockIConv2(): IConv { ExpressionId: "" }, IsPresent: false + }, + GeneratedColumn: { + Value: { + Statement: "", + ExpressionId: "" + }, + IsPresent: false, + Type: '' } } }, diff --git a/webv2/api/schema.go b/webv2/api/schema.go index 247a4219c7..9eb6875c78 100644 --- a/webv2/api/schema.go +++ b/webv2/api/schema.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "github.com/GoogleCloudPlatform/spanner-migration-tool/logger" "io/ioutil" "net/http" "os" @@ -156,6 +157,8 @@ func (expressionVerificationHandler *ExpressionsVerificationHandler) ConvertSche Dialect: sessionState.Dialect, } + schemaStr, _ := json.Marshal(sessionState.Conv.SpSchema) + logger.Log.Info(fmt.Sprintf("Output schema: %s", string(schemaStr))) convm := session.ConvWithMetadata{ SessionMetadata: sessionMetadata, Conv: sessionState.Conv, @@ -393,6 +396,18 @@ func GetAutoGenMap(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(autoGenMap) } +func (tableHandler *TableAPIHandler) handleExpressionColError( + exp *internal.ExpressionVerificationOutput, conv *internal.Conv, errorType internal.SchemaIssue) { + + if !exp.Result { + tableId := exp.ExpressionDetail.Metadata["TableId"] + columnId := exp.ExpressionDetail.Metadata["ColId"] + issues := conv.SchemaIssues[tableId].ColumnLevelIssues[columnId] + issues = append(issues, errorType) + conv.SchemaIssues[tableId].ColumnLevelIssues[columnId] = issues + } +} + // GetTableWithErrors checks the errors in the spanner schema // and returns a list of tables with errors func (tableHandler *TableAPIHandler) GetTableWithErrors(w http.ResponseWriter, r *http.Request) { @@ -408,15 +423,9 @@ func (tableHandler *TableAPIHandler) GetTableWithErrors(w http.ResponseWriter, r for _, exp := range expressions.ExpressionVerificationOutputList { switch exp.ExpressionDetail.Type { case "DEFAULT": - { - if !exp.Result { - tableId := exp.ExpressionDetail.Metadata["TableId"] - columnId := exp.ExpressionDetail.Metadata["ColId"] - issues := sessionState.Conv.SchemaIssues[tableId].ColumnLevelIssues[columnId] - issues = append(issues, internal.DefaultValueError) - sessionState.Conv.SchemaIssues[tableId].ColumnLevelIssues[columnId] = issues - } - } + tableHandler.handleExpressionColError(&exp, sessionState.Conv, internal.DefaultValueError) + case constants.VIRTUAL_GENERATED, constants.STORED_GENERATED: + tableHandler.handleExpressionColError(&exp, sessionState.Conv, internal.GeneratedColumnValueError) } } } else if err != nil { @@ -424,7 +433,7 @@ func (tableHandler *TableAPIHandler) GetTableWithErrors(w http.ResponseWriter, r srcTable := sessionState.Conv.SrcSchema[tableId] for _, srcColId := range srcTable.ColIds { srcCol := srcTable.ColDefs[srcColId] - if srcCol.DefaultValue.IsPresent { + if srcCol.DefaultValue.IsPresent || srcCol.GeneratedColumn.IsPresent { issues := sessionState.Conv.SchemaIssues[tableId] sessionState.Conv.SchemaIssues[tableId] = issues } @@ -915,7 +924,6 @@ func SetParentTable(w http.ResponseWriter, r *http.Request) { return } - sessionState.Conv.ConvLock.Lock() defer sessionState.Conv.ConvLock.Unlock() tableInterleaveStatus := parentTableHelper(tableId, parentTableId, interleaveType, onDelete, update) diff --git a/webv2/table/review_table_schema.go b/webv2/table/review_table_schema.go index 4fb5626a4d..982dc810fd 100644 --- a/webv2/table/review_table_schema.go +++ b/webv2/table/review_table_schema.go @@ -174,6 +174,7 @@ func ReviewTableSchema(w http.ResponseWriter, r *http.Request) { sequences := UpdateAutoGenCol(v.AutoGen, tableId, colId, conv) conv.SpSequences = sequences UpdateDefaultValue(v.DefaultValue, tableId, colId, conv) + UpdateGeneratedCol(v.GeneratedColumn, tableId, colId, conv) } } diff --git a/webv2/table/update_table_schema.go b/webv2/table/update_table_schema.go index 5039bd76f5..4ede0d7824 100644 --- a/webv2/table/update_table_schema.go +++ b/webv2/table/update_table_schema.go @@ -36,14 +36,15 @@ import ( // (4) NotNull: "ADDED", "REMOVED" or "". // (5) ToType: New type or empty string. type updateCol struct { - Add bool `json:"Add"` - Removed bool `json:"Removed"` - Rename string `json:"Rename"` - NotNull string `json:"NotNull"` - ToType string `json:"ToType"` - MaxColLength string `json:"MaxColLength"` - AutoGen ddl.AutoGenCol `json:"AutoGen"` - DefaultValue ddl.DefaultValue `json:"DefaultValue"` + Add bool `json:"Add"` + Removed bool `json:"Removed"` + Rename string `json:"Rename"` + NotNull string `json:"NotNull"` + ToType string `json:"ToType"` + MaxColLength string `json:"MaxColLength"` + AutoGen ddl.AutoGenCol `json:"AutoGen"` + DefaultValue ddl.DefaultValue `json:"DefaultValue"` + GeneratedColumn ddl.GeneratedColumn `json:"GeneratedColumn"` } type updateTable struct { @@ -137,6 +138,7 @@ func UpdateTableSchema(w http.ResponseWriter, r *http.Request) { sequences := UpdateAutoGenCol(v.AutoGen, tableId, colId, conv) conv.SpSequences = sequences UpdateDefaultValue(v.DefaultValue, tableId, colId, conv) + UpdateGeneratedCol(v.GeneratedColumn, tableId, colId, conv) } } diff --git a/webv2/table/utilities.go b/webv2/table/utilities.go index 0bd29fc279..04997f520f 100644 --- a/webv2/table/utilities.go +++ b/webv2/table/utilities.go @@ -34,15 +34,15 @@ const ( ) var SpannerToCassandra = map[string]string{ - ddl.Bool: "boolean", - ddl.Bytes: "blob", - ddl.Date: "date", - ddl.Float32: "float", - ddl.Float64: "double", - ddl.Int64: "bigint", - ddl.Numeric: "decimal", - ddl.String: "text", - ddl.Timestamp:"timestamp", + ddl.Bool: "boolean", + ddl.Bytes: "blob", + ddl.Date: "date", + ddl.Float32: "float", + ddl.Float64: "double", + ddl.Int64: "bigint", + ddl.Numeric: "decimal", + ddl.String: "text", + ddl.Timestamp: "timestamp", } // GetCassandraType returns default cassandra type for specified Spanner type @@ -75,7 +75,6 @@ func GetSpannerTableDDL(spannerTable ddl.CreateTable, spDialect string, driver s return ddl } - func UpdateNotNull(notNullChange, tableId, colId string, conv *internal.Conv) { sp := conv.SpSchema[tableId] @@ -168,6 +167,42 @@ func getSequenceId(sequenceName string, spSeq map[string]ddl.Sequence) string { return "" } +// Add, deletes and updates generated column associated with a column during edit column functionality +func UpdateGeneratedCol(gc ddl.GeneratedColumn, tableId, colId string, conv *internal.Conv) { + col := conv.SpSchema[tableId].ColDefs[colId] + if !gc.IsPresent { + col.GeneratedColumn = ddl.GeneratedColumn{} + conv.SpSchema[tableId].ColDefs[colId] = col + return + } + + var expressionId string + if gc.Value.ExpressionId == "" { + if _, exists := conv.SrcSchema[tableId]; exists { + if column, exists := conv.SrcSchema[tableId].ColDefs[colId]; exists { + if column.GeneratedColumn.Value.ExpressionId != "" { + expressionId = column.GeneratedColumn.Value.ExpressionId + } + } + } + if expressionId == "" { + expressionId = internal.GenerateExpressionId() + } + } else { + expressionId = gc.Value.ExpressionId + } + re := regexp.MustCompile(`\([^)]*\)`) + col.GeneratedColumn = ddl.GeneratedColumn{ + Value: ddl.Expression{ + ExpressionId: expressionId, + Statement: common.SanitizeExpressionsValue(gc.Value.Statement, col.T.Name, re.MatchString(gc.Value.Statement)), + }, + IsPresent: true, + Type: gc.Type, + } + conv.SpSchema[tableId].ColDefs[colId] = col +} + // Add, deletes and updates default value associated with a column during edit column functionality func UpdateDefaultValue(dv ddl.DefaultValue, tableId, colId string, conv *internal.Conv) { col := conv.SpSchema[tableId].ColDefs[colId] @@ -196,7 +231,7 @@ func UpdateDefaultValue(dv ddl.DefaultValue, tableId, colId string, conv *intern col.DefaultValue = ddl.DefaultValue{ Value: ddl.Expression{ ExpressionId: expressionId, - Statement: common.SanitizeDefaultValue(dv.Value.Statement, col.T.Name, re.MatchString(dv.Value.Statement)), + Statement: common.SanitizeExpressionsValue(dv.Value.Statement, col.T.Name, re.MatchString(dv.Value.Statement)), }, IsPresent: true, } @@ -238,7 +273,6 @@ func IsInterleavingImpacted(v updateCol, tableId string, colId string, conv *int if isModification { isParent, _ := utilities.IsParent(tableId) - isChild := conv.SpSchema[tableId].ParentTable.Id != "" // Rule 1: If it's a parent table, any change to a PK column is disallowed. @@ -264,4 +298,4 @@ func IsInterleavingImpacted(v updateCol, tableId string, colId string, conv *int } } return "" -} \ No newline at end of file +}