From f19b58e3df8f3c8bcc46574c1c0b0df10ad5e236 Mon Sep 17 00:00:00 2001 From: Shlomi Noach <2607934+shlomi-noach@users.noreply.github.com> Date: Mon, 18 Mar 2024 13:18:41 +0200 Subject: [PATCH] `schemadiff`: supporting textual diff (#15388) Signed-off-by: Shlomi Noach <2607934+shlomi-noach@users.noreply.github.com> --- go/vt/schemadiff/annotations.go | 239 +++++++++++ go/vt/schemadiff/annotations_test.go | 102 +++++ go/vt/schemadiff/diff_test.go | 78 +++- go/vt/schemadiff/table.go | 94 +++- go/vt/schemadiff/table_test.go | 615 +++++++++++++++++++++++++++ go/vt/schemadiff/types.go | 2 + go/vt/schemadiff/view.go | 12 + 7 files changed, 1113 insertions(+), 29 deletions(-) create mode 100644 go/vt/schemadiff/annotations.go create mode 100644 go/vt/schemadiff/annotations_test.go diff --git a/go/vt/schemadiff/annotations.go b/go/vt/schemadiff/annotations.go new file mode 100644 index 00000000000..1e0f1562348 --- /dev/null +++ b/go/vt/schemadiff/annotations.go @@ -0,0 +1,239 @@ +/* +Copyright 2024 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package schemadiff + +import ( + "strings" +) + +// TextualAnnotationType is an enum for the type of annotation that can be applied to a line of text. +type TextualAnnotationType int + +const ( + UnchangedTextualAnnotationType TextualAnnotationType = iota + AddedTextualAnnotationType + RemovedTextualAnnotationType +) + +// AnnotatedText is a some text and its annotation type. The text is usually single-line, but it +// can be multi-line, as in the case of partition specs. +type AnnotatedText struct { + text string + typ TextualAnnotationType +} + +// TextualAnnotations is a sequence of annotated texts. It is the annotated representation of a statement. +type TextualAnnotations struct { + texts []*AnnotatedText + hasAnyChanges bool +} + +func NewTextualAnnotations() *TextualAnnotations { + return &TextualAnnotations{} +} + +func (a *TextualAnnotations) Len() int { + return len(a.texts) +} + +func (a *TextualAnnotations) mark(text string, typ TextualAnnotationType) { + a.texts = append(a.texts, &AnnotatedText{text: text, typ: typ}) + if typ != UnchangedTextualAnnotationType { + a.hasAnyChanges = true + } +} + +func (a *TextualAnnotations) MarkAdded(text string) { + a.mark(text, AddedTextualAnnotationType) +} + +func (a *TextualAnnotations) MarkRemoved(text string) { + a.mark(text, RemovedTextualAnnotationType) +} + +func (a *TextualAnnotations) MarkUnchanged(text string) { + a.mark(text, UnchangedTextualAnnotationType) +} + +// ByType returns the subset of annotations by given type. +func (a *TextualAnnotations) ByType(typ TextualAnnotationType) (r []*AnnotatedText) { + for _, text := range a.texts { + if text.typ == typ { + r = append(r, text) + } + } + return r +} + +func (a *TextualAnnotations) Added() (r []*AnnotatedText) { + return a.ByType(AddedTextualAnnotationType) +} + +func (a *TextualAnnotations) Removed() (r []*AnnotatedText) { + return a.ByType(RemovedTextualAnnotationType) +} + +// Export beautifies the annotated text and returns it as a string. +func (a *TextualAnnotations) Export() string { + textLines := make([]string, 0, len(a.texts)) + for _, annotatedText := range a.texts { + switch annotatedText.typ { + case AddedTextualAnnotationType: + annotatedText.text = "+" + annotatedText.text + case RemovedTextualAnnotationType: + annotatedText.text = "-" + annotatedText.text + default: + // text unchanged + if a.hasAnyChanges { + // If there is absolutely no change, we don't add a space anywhere + annotatedText.text = " " + annotatedText.text + } + } + textLines = append(textLines, annotatedText.text) + } + return strings.Join(textLines, "\n") +} + +// annotatedStatement returns a new TextualAnnotations object that annotates the given statement with the given annotations. +// The given annotations were created by the diffing algorithm, and represent the CanonicalString of some node. +// However, the given statement is just some text, and we need to find the annotations (some of which may be multi-line) +// inside our text, and return a per-line annotation. +func annotatedStatement(stmt string, annotationType TextualAnnotationType, annotations *TextualAnnotations) *TextualAnnotations { + stmtLines := strings.Split(stmt, "\n") + result := NewTextualAnnotations() + annotationLines := map[string]bool{} // single-line breakdown of all annotations + for _, annotation := range annotations.ByType(annotationType) { + // An annotated text could be multiline. Partition specs are such. + lines := strings.Split(annotation.text, "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + if line != "" { + annotationLines[line] = true + } + } + } + annotationLinesMutations := map[string](map[string]bool){} + // Mutations are expected ways to find an annotation inside a `CREATE TABLE` statement. + for annotationLine := range annotationLines { + possibleMutations := map[string]bool{ + annotationLine: true, + ") " + annotationLine: true, // e.g. ") ENGINE=InnoDB" + ") " + annotationLine + ",": true, // e.g. ") ENGINE=InnoDB,[\n ROW_FORMAT=COMPRESSED]" + "(" + annotationLine + ")": true, // e.g. "(PARTITION p0 VALUES LESS THAN (10)) + "(" + annotationLine + ",": true, // e.g. "(PARTITION p0 VALUES LESS THAN (10), + annotationLine + ",": true, // e.g. "i int unsigned," + annotationLine + ")": true, // e.g. "PARTITION p9 VALUES LESS THAN (90))" + } + annotationLinesMutations[annotationLine] = possibleMutations + } + for i := range stmtLines { + lineAnnotated := false + trimmedLine := strings.TrimSpace(stmtLines[i]) + if trimmedLine == "" { + continue + } + for annotationLine := range annotationLines { + if lineAnnotated { + break + } + possibleMutations := annotationLinesMutations[annotationLine] + if possibleMutations[trimmedLine] { + // Annotate this line! + result.mark(stmtLines[i], annotationType) + lineAnnotated = true + // No need to match this annotation again + delete(annotationLines, annotationLine) + delete(possibleMutations, annotationLine) + } + } + if !lineAnnotated { + result.MarkUnchanged(stmtLines[i]) + } + } + return result +} + +// annotateAll blindly annotates all lines of the given statement with the given annotation type. +func annotateAll(stmt string, annotationType TextualAnnotationType) *TextualAnnotations { + stmtLines := strings.Split(stmt, "\n") + result := NewTextualAnnotations() + for _, line := range stmtLines { + result.mark(line, annotationType) + } + return result +} + +// unifiedAnnotated takes two annotations of from, to statements and returns a unified annotation. +func unifiedAnnotated(from *TextualAnnotations, to *TextualAnnotations) *TextualAnnotations { + unified := NewTextualAnnotations() + fromIndex := 0 + toIndex := 0 + for fromIndex < from.Len() || toIndex < to.Len() { + matchingLine := "" + if fromIndex < from.Len() { + fromLine := from.texts[fromIndex] + if fromLine.typ == RemovedTextualAnnotationType { + unified.MarkRemoved(fromLine.text) + fromIndex++ + continue + } + matchingLine = fromLine.text + } + if toIndex < to.Len() { + toLine := to.texts[toIndex] + if toLine.typ == AddedTextualAnnotationType { + unified.MarkAdded(toLine.text) + toIndex++ + continue + } + if matchingLine == "" { + matchingLine = toLine.text + } + } + unified.MarkUnchanged(matchingLine) + fromIndex++ + toIndex++ + } + return unified +} + +// annotatedDiff returns the annotated representations of the from and to entities, and their unified representation. +func annotatedDiff(diff EntityDiff, entityAnnotations *TextualAnnotations) (from *TextualAnnotations, to *TextualAnnotations, unified *TextualAnnotations) { + fromEntity, toEntity := diff.Entities() + switch { + case fromEntity == nil && toEntity == nil: + // Should never get here. + return nil, nil, nil + case fromEntity == nil: + // A new entity was created. + from = NewTextualAnnotations() + to = annotateAll(toEntity.Create().CanonicalStatementString(), AddedTextualAnnotationType) + case toEntity == nil: + // An entity was dropped. + from = annotateAll(fromEntity.Create().CanonicalStatementString(), RemovedTextualAnnotationType) + to = NewTextualAnnotations() + case entityAnnotations == nil: + // Entity was modified, and we have no prior info about entity annotations. Treat this is as a complete rewrite. + from = annotateAll(fromEntity.Create().CanonicalStatementString(), RemovedTextualAnnotationType) + to = annotateAll(toEntity.Create().CanonicalStatementString(), AddedTextualAnnotationType) + default: + // Entity was modified, and we have prior info about entity annotations. + from = annotatedStatement(fromEntity.Create().CanonicalStatementString(), RemovedTextualAnnotationType, entityAnnotations) + to = annotatedStatement(toEntity.Create().CanonicalStatementString(), AddedTextualAnnotationType, entityAnnotations) + } + return from, to, unifiedAnnotated(from, to) +} diff --git a/go/vt/schemadiff/annotations_test.go b/go/vt/schemadiff/annotations_test.go new file mode 100644 index 00000000000..8cba3c1ee90 --- /dev/null +++ b/go/vt/schemadiff/annotations_test.go @@ -0,0 +1,102 @@ +/* +Copyright 2024 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package schemadiff + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "vitess.io/vitess/go/vt/sqlparser" +) + +func TestAnnotateAll(t *testing.T) { + stmt := `create table t( + id int, + name varchar(100), + primary key(id) +) engine=innodb` + annotations := annotateAll(stmt, RemovedTextualAnnotationType) + assert.Equal(t, 5, annotations.Len()) + expect := `-create table t( +- id int, +- name varchar(100), +- primary key(id) +-) engine=innodb` + assert.Equal(t, expect, annotations.Export()) +} + +func TestUnifiedAnnotated(t *testing.T) { + tcases := []struct { + name string + from string + to string + fromAnnotations *TextualAnnotations + toAnnotations *TextualAnnotations + expected string + }{ + { + "no change", + "CREATE TABLE t1 (a int)", + "CREATE TABLE t1 (a int)", + &TextualAnnotations{}, + &TextualAnnotations{}, + "CREATE TABLE `t1` (\n\t`a` int\n)", + }, + { + "simple", + "CREATE TABLE t1 (a int)", + "CREATE TABLE t1 (a int, b int)", + &TextualAnnotations{}, + &TextualAnnotations{texts: []*AnnotatedText{{text: "`b` int", typ: AddedTextualAnnotationType}}}, + " CREATE TABLE `t1` (\n \t`a` int\n+\t`b` int\n )", + }, + } + parser := sqlparser.NewTestParser() + + for _, tcase := range tcases { + t.Run(tcase.name, func(t *testing.T) { + fromStmt, err := parser.ParseStrictDDL(tcase.from) + require.NoError(t, err) + annotatedFrom := annotatedStatement(sqlparser.CanonicalString(fromStmt), RemovedTextualAnnotationType, tcase.fromAnnotations) + toStmt, err := parser.ParseStrictDDL(tcase.to) + require.NoError(t, err) + annotatedTo := annotatedStatement(sqlparser.CanonicalString(toStmt), AddedTextualAnnotationType, tcase.toAnnotations) + unified := unifiedAnnotated(annotatedFrom, annotatedTo) + export := unified.Export() + assert.Equalf(t, tcase.expected, export, "from: %v, to: %v", annotatedFrom.Export(), annotatedTo.Export()) + }) + } +} + +func TestUnifiedAnnotatedAll(t *testing.T) { + stmt := `create table t( + id int, + name varchar(100), + primary key(id) +) engine=innodb` + annotatedTo := annotateAll(stmt, AddedTextualAnnotationType) + annotatedFrom := NewTextualAnnotations() + unified := unifiedAnnotated(annotatedFrom, annotatedTo) + expect := `+create table t( ++ id int, ++ name varchar(100), ++ primary key(id) ++) engine=innodb` + assert.Equal(t, expect, unified.Export()) +} diff --git a/go/vt/schemadiff/diff_test.go b/go/vt/schemadiff/diff_test.go index 3fe94e3b0b5..9a74e6c0a32 100644 --- a/go/vt/schemadiff/diff_test.go +++ b/go/vt/schemadiff/diff_test.go @@ -18,6 +18,7 @@ package schemadiff import ( "context" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -43,6 +44,7 @@ func TestDiffTables(t *testing.T) { expectError string hints *DiffHints env *Environment + annotated []string }{ { name: "identical", @@ -58,6 +60,9 @@ func TestDiffTables(t *testing.T) { action: "alter", fromName: "t", toName: "t", + annotated: []string{ + " CREATE TABLE `t` (", " \t`id` int,", "+\t`i` int,", " \tPRIMARY KEY (`id`)", " )", + }, }, { name: "change of columns, boolean type", @@ -106,6 +111,9 @@ func TestDiffTables(t *testing.T) { cdiff: "CREATE TABLE `t` (\n\t`id` int,\n\tPRIMARY KEY (`id`)\n)", action: "create", toName: "t", + annotated: []string{ + "+CREATE TABLE `t` (", "+\t`id` int,", "+\tPRIMARY KEY (`id`)", "+)", + }, }, { name: "drop", @@ -114,6 +122,9 @@ func TestDiffTables(t *testing.T) { cdiff: "DROP TABLE `t`", action: "drop", fromName: "t", + annotated: []string{ + "-CREATE TABLE `t` (", "-\t`id` int,", "-\tPRIMARY KEY (`id`)", "-)", + }, }, { name: "none", @@ -390,6 +401,12 @@ func TestDiffTables(t *testing.T) { _, err = env.Parser().ParseStrictDDL(canonicalDiff) assert.NoError(t, err) } + if ts.annotated != nil { + // Optional test for assorted scenarios. + _, _, unified := d.Annotated() + unifiedExport := unified.Export() + assert.Equal(t, ts.annotated, strings.Split(unifiedExport, "\n")) + } // let's also check dq, and also validate that dq's statement is identical to d's assert.NoError(t, dqerr) require.NotNil(t, dq) @@ -403,15 +420,16 @@ func TestDiffTables(t *testing.T) { func TestDiffViews(t *testing.T) { tt := []struct { - name string - from string - to string - diff string - cdiff string - fromName string - toName string - action string - isError bool + name string + from string + to string + diff string + cdiff string + fromName string + toName string + action string + isError bool + annotated []string }{ { name: "identical", @@ -427,6 +445,10 @@ func TestDiffViews(t *testing.T) { action: "alter", fromName: "v1", toName: "v1", + annotated: []string{ + "-CREATE VIEW `v1`(`col1`, `col2`, `col3`) AS SELECT `a`, `b`, `c` FROM `t`", + "+CREATE VIEW `v1`(`col1`, `col2`, `colother`) AS SELECT `a`, `b`, `c` FROM `t`", + }, }, { name: "create", @@ -435,6 +457,9 @@ func TestDiffViews(t *testing.T) { cdiff: "CREATE VIEW `v1` AS SELECT `a`, `b`, `c` FROM `t`", action: "create", toName: "v1", + annotated: []string{ + "+CREATE VIEW `v1` AS SELECT `a`, `b`, `c` FROM `t`", + }, }, { name: "drop", @@ -443,6 +468,9 @@ func TestDiffViews(t *testing.T) { cdiff: "DROP VIEW `v1`", action: "drop", fromName: "v1", + annotated: []string{ + "-CREATE VIEW `v1` AS SELECT `a`, `b`, `c` FROM `t`", + }, }, { name: "none", @@ -523,7 +551,12 @@ func TestDiffViews(t *testing.T) { _, err = env.Parser().ParseStrictDDL(canonicalDiff) assert.NoError(t, err) } - + if ts.annotated != nil { + // Optional test for assorted scenarios. + _, _, unified := d.Annotated() + unifiedExport := unified.Export() + assert.Equal(t, ts.annotated, strings.Split(unifiedExport, "\n")) + } // let's also check dq, and also validate that dq's statement is identical to d's assert.NoError(t, dqerr) require.NotNil(t, dq) @@ -545,6 +578,7 @@ func TestDiffSchemas(t *testing.T) { cdiffs []string expectError string tableRename int + annotated []string fkStrategy int }{ { @@ -682,6 +716,9 @@ func TestDiffSchemas(t *testing.T) { cdiffs: []string{ "CREATE TABLE `t` (\n\t`id` int,\n\tPRIMARY KEY (`id`)\n)", }, + annotated: []string{ + "+CREATE TABLE `t` (\n+\t`id` int,\n+\tPRIMARY KEY (`id`)\n+)", + }, }, { name: "drop table", @@ -692,6 +729,9 @@ func TestDiffSchemas(t *testing.T) { cdiffs: []string{ "DROP TABLE `t`", }, + annotated: []string{ + "-CREATE TABLE `t` (\n-\t`id` int,\n-\tPRIMARY KEY (`id`)\n-)", + }, }, { name: "create, alter, drop tables", @@ -707,6 +747,11 @@ func TestDiffSchemas(t *testing.T) { "ALTER TABLE `t2` MODIFY COLUMN `id` bigint", "CREATE TABLE `t4` (\n\t`id` int,\n\tPRIMARY KEY (`id`)\n)", }, + annotated: []string{ + "-CREATE TABLE `t1` (\n-\t`id` int,\n-\tPRIMARY KEY (`id`)\n-)", + " CREATE TABLE `t2` (\n-\t`id` int,\n+\t`id` bigint,\n \tPRIMARY KEY (`id`)\n )", + "+CREATE TABLE `t4` (\n+\t`id` int,\n+\tPRIMARY KEY (`id`)\n+)", + }, }, { name: "identical tables: drop and create", @@ -732,6 +777,9 @@ func TestDiffSchemas(t *testing.T) { "RENAME TABLE `t2a` TO `t2b`", }, tableRename: TableRenameHeuristicStatement, + annotated: []string{ + "-CREATE TABLE `t2a` (\n-\t`id` int unsigned,\n-\tPRIMARY KEY (`id`)\n-)\n+CREATE TABLE `t2b` (\n+\t`id` int unsigned,\n+\tPRIMARY KEY (`id`)\n+)", + }, }, { name: "drop and create all", @@ -1009,6 +1057,16 @@ func TestDiffSchemas(t *testing.T) { assert.NoError(t, err) } + if ts.annotated != nil { + // Optional test for assorted scenarios. + if assert.Equalf(t, len(diffs), len(ts.annotated), "%+v", cstatements) { + for i, d := range diffs { + _, _, unified := d.Annotated() + assert.Equal(t, ts.annotated[i], unified.Export()) + } + } + } + { // Validate "apply()" on "from" converges with "to" schema1, err := NewSchemaFromSQL(env, ts.from) diff --git a/go/vt/schemadiff/table.go b/go/vt/schemadiff/table.go index aab697c2bf0..4c971f12c21 100644 --- a/go/vt/schemadiff/table.go +++ b/go/vt/schemadiff/table.go @@ -35,9 +35,10 @@ type charsetCollate struct { } type AlterTableEntityDiff struct { - from *CreateTableEntity - to *CreateTableEntity - alterTable *sqlparser.AlterTable + from *CreateTableEntity + to *CreateTableEntity + alterTable *sqlparser.AlterTable + annotations *TextualAnnotations canonicalStatementString string subsequentDiff *AlterTableEntityDiff @@ -59,6 +60,10 @@ func (d *AlterTableEntityDiff) Entities() (from Entity, to Entity) { return d.from, d.to } +func (d *AlterTableEntityDiff) Annotated() (from *TextualAnnotations, to *TextualAnnotations, unified *TextualAnnotations) { + return annotatedDiff(d, d.annotations) +} + // Statement implements EntityDiff func (d *AlterTableEntityDiff) Statement() sqlparser.Statement { if d == nil { @@ -155,6 +160,10 @@ func (d *CreateTableEntityDiff) Entities() (from Entity, to Entity) { return nil, &CreateTableEntity{CreateTable: d.createTable} } +func (d *CreateTableEntityDiff) Annotated() (from *TextualAnnotations, to *TextualAnnotations, unified *TextualAnnotations) { + return annotatedDiff(d, nil) +} + // Statement implements EntityDiff func (d *CreateTableEntityDiff) Statement() sqlparser.Statement { if d == nil { @@ -228,6 +237,10 @@ func (d *DropTableEntityDiff) Entities() (from Entity, to Entity) { return d.from, nil } +func (d *DropTableEntityDiff) Annotated() (from *TextualAnnotations, to *TextualAnnotations, unified *TextualAnnotations) { + return annotatedDiff(d, nil) +} + // Statement implements EntityDiff func (d *DropTableEntityDiff) Statement() sqlparser.Statement { if d == nil { @@ -302,6 +315,10 @@ func (d *RenameTableEntityDiff) Entities() (from Entity, to Entity) { return d.from, d.to } +func (d *RenameTableEntityDiff) Annotated() (from *TextualAnnotations, to *TextualAnnotations, unified *TextualAnnotations) { + return annotatedDiff(d, nil) +} + // Statement implements EntityDiff func (d *RenameTableEntityDiff) Statement() sqlparser.Statement { if d == nil { @@ -837,13 +854,14 @@ func (c *CreateTableEntity) TableDiff(other *CreateTableEntity, hints *DiffHints var parentAlterTableEntityDiff *AlterTableEntityDiff var partitionSpecs []*sqlparser.PartitionSpec var superfluousFulltextKeys []*sqlparser.AddIndexDefinition + annotations := NewTextualAnnotations() { // diff columns // ordered columns for both tables: t1Columns := c.CreateTable.TableSpec.Columns t2Columns := other.CreateTable.TableSpec.Columns - if err := c.diffColumns(alterTable, t1Columns, t2Columns, hints, t1cc, t2cc); err != nil { + if err := c.diffColumns(alterTable, annotations, t1Columns, t2Columns, hints, t1cc, t2cc); err != nil { return nil, err } } @@ -852,14 +870,14 @@ func (c *CreateTableEntity) TableDiff(other *CreateTableEntity, hints *DiffHints // ordered keys for both tables: t1Keys := c.CreateTable.TableSpec.Indexes t2Keys := other.CreateTable.TableSpec.Indexes - superfluousFulltextKeys = c.diffKeys(alterTable, t1Keys, t2Keys, hints) + superfluousFulltextKeys = c.diffKeys(alterTable, annotations, t1Keys, t2Keys, hints) } { // diff constraints // ordered constraints for both tables: t1Constraints := c.CreateTable.TableSpec.Constraints t2Constraints := other.CreateTable.TableSpec.Constraints - c.diffConstraints(alterTable, c.Name(), t1Constraints, other.Name(), t2Constraints, hints) + c.diffConstraints(alterTable, annotations, c.Name(), t1Constraints, other.Name(), t2Constraints, hints) } { // diff partitions @@ -867,7 +885,7 @@ func (c *CreateTableEntity) TableDiff(other *CreateTableEntity, hints *DiffHints t1Partitions := c.CreateTable.TableSpec.PartitionOption t2Partitions := other.CreateTable.TableSpec.PartitionOption var err error - partitionSpecs, err = c.diffPartitions(alterTable, t1Partitions, t2Partitions, hints) + partitionSpecs, err = c.diffPartitions(alterTable, annotations, t1Partitions, t2Partitions, hints) if err != nil { return nil, err } @@ -877,14 +895,14 @@ func (c *CreateTableEntity) TableDiff(other *CreateTableEntity, hints *DiffHints // ordered keys for both tables: t1Options := c.CreateTable.TableSpec.Options t2Options := other.CreateTable.TableSpec.Options - if err := c.diffOptions(alterTable, t1Options, t2Options, hints); err != nil { + if err := c.diffOptions(alterTable, annotations, t1Options, t2Options, hints); err != nil { return nil, err } } tableSpecHasChanged := len(alterTable.AlterOptions) > 0 || alterTable.PartitionOption != nil || alterTable.PartitionSpec != nil newAlterTableEntityDiff := func(alterTable *sqlparser.AlterTable) *AlterTableEntityDiff { - d := &AlterTableEntityDiff{alterTable: alterTable, from: c, to: other} + d := &AlterTableEntityDiff{alterTable: alterTable, from: c, to: other, annotations: annotations} var algorithmValue sqlparser.AlgorithmValue @@ -986,6 +1004,7 @@ func isDefaultTableOptionValue(option *sqlparser.TableOption) bool { } func (c *CreateTableEntity) diffOptions(alterTable *sqlparser.AlterTable, + annotations *TextualAnnotations, t1Options sqlparser.TableOptions, t2Options sqlparser.TableOptions, hints *DiffHints, @@ -1071,11 +1090,16 @@ func (c *CreateTableEntity) diffOptions(alterTable *sqlparser.AlterTable, if tableOption != nil { tableOption.Name = t1Option.Name alterTableOptions = append(alterTableOptions, tableOption) + annotations.MarkRemoved(sqlparser.CanonicalString(sqlparser.TableOptions{t1Option})) } } - } // changed options + modifyTableOption := func(option1, option2 *sqlparser.TableOption) { + alterTableOptions = append(alterTableOptions, option2) + annotations.MarkRemoved(sqlparser.CanonicalString(sqlparser.TableOptions{option1})) + annotations.MarkAdded(sqlparser.CanonicalString(sqlparser.TableOptions{option2})) + } for _, t2Option := range t2Options { if t1Option, ok := t1OptionsMap[t2Option.Name]; ok { options1 := sqlparser.TableOptions{t1Option} @@ -1087,10 +1111,10 @@ func (c *CreateTableEntity) diffOptions(alterTable *sqlparser.AlterTable, case "CHARSET", "COLLATE": switch hints.TableCharsetCollateStrategy { case TableCharsetCollateStrict: - alterTableOptions = append(alterTableOptions, t2Option) + modifyTableOption(t1Option, t2Option) case TableCharsetCollateIgnoreEmpty: if t1Option.String != "" && t2Option.String != "" { - alterTableOptions = append(alterTableOptions, t2Option) + modifyTableOption(t1Option, t2Option) } // if one is empty, we ignore case TableCharsetCollateIgnoreAlways: @@ -1099,7 +1123,7 @@ func (c *CreateTableEntity) diffOptions(alterTable *sqlparser.AlterTable, case "AUTO_INCREMENT": switch hints.AutoIncrementStrategy { case AutoIncrementApplyAlways: - alterTableOptions = append(alterTableOptions, t2Option) + modifyTableOption(t1Option, t2Option) case AutoIncrementApplyHigher: option1AutoIncrement, err := strconv.ParseInt(t1Option.Value.Val, 10, 64) if err != nil { @@ -1111,18 +1135,22 @@ func (c *CreateTableEntity) diffOptions(alterTable *sqlparser.AlterTable, } if option2AutoIncrement > option1AutoIncrement { // never decrease AUTO_INCREMENT. Only increase - alterTableOptions = append(alterTableOptions, t2Option) + modifyTableOption(t1Option, t2Option) } case AutoIncrementIgnore: // do not apply } default: // Apply the new options - alterTableOptions = append(alterTableOptions, t2Option) + modifyTableOption(t1Option, t2Option) } } } } + addTableOption := func(option *sqlparser.TableOption) { + alterTableOptions = append(alterTableOptions, option) + annotations.MarkAdded(sqlparser.CanonicalString(sqlparser.TableOptions{option})) + } // added options for _, t2Option := range t2Options { if _, ok := t1OptionsMap[t2Option.Name]; !ok { @@ -1130,18 +1158,18 @@ func (c *CreateTableEntity) diffOptions(alterTable *sqlparser.AlterTable, case "CHARSET", "COLLATE": switch hints.TableCharsetCollateStrategy { case TableCharsetCollateStrict: - alterTableOptions = append(alterTableOptions, t2Option) + addTableOption(t2Option) // in all other strategies we ignore the charset } case "AUTO_INCREMENT": switch hints.AutoIncrementStrategy { case AutoIncrementApplyAlways, AutoIncrementApplyHigher: - alterTableOptions = append(alterTableOptions, t2Option) + addTableOption(t2Option) case AutoIncrementIgnore: // do not apply } default: - alterTableOptions = append(alterTableOptions, t2Option) + addTableOption(t2Option) } } } @@ -1158,6 +1186,7 @@ func (c *CreateTableEntity) diffOptions(alterTable *sqlparser.AlterTable, // - table1 may have non-empty list of partitions _preceding_ this sequence, and table2 may not // - table2 may have non-empty list of partitions _following_ this sequence, and table1 may not func (c *CreateTableEntity) isRangePartitionsRotation( + annotations *TextualAnnotations, t1Partitions *sqlparser.PartitionOption, t2Partitions *sqlparser.PartitionOption, ) (bool, []*sqlparser.PartitionSpec, error) { @@ -1212,6 +1241,7 @@ func (c *CreateTableEntity) isRangePartitionsRotation( Names: []sqlparser.IdentifierCI{p.Name}, } partitionSpecs = append(partitionSpecs, partitionSpec) + annotations.MarkRemoved(sqlparser.CanonicalString(p)) } for _, p := range addedPartitions2 { partitionSpec := &sqlparser.PartitionSpec{ @@ -1219,11 +1249,13 @@ func (c *CreateTableEntity) isRangePartitionsRotation( Definitions: []*sqlparser.PartitionDefinition{p}, } partitionSpecs = append(partitionSpecs, partitionSpec) + annotations.MarkAdded(sqlparser.CanonicalString(p)) } return true, partitionSpecs, nil } func (c *CreateTableEntity) diffPartitions(alterTable *sqlparser.AlterTable, + annotations *TextualAnnotations, t1Partitions *sqlparser.PartitionOption, t2Partitions *sqlparser.PartitionOption, hints *DiffHints, @@ -1234,6 +1266,7 @@ func (c *CreateTableEntity) diffPartitions(alterTable *sqlparser.AlterTable, case t1Partitions == nil: // add partitioning alterTable.PartitionOption = t2Partitions + annotations.MarkAdded(sqlparser.CanonicalString(t2Partitions)) case t2Partitions == nil: // remove partitioning partitionSpec := &sqlparser.PartitionSpec{ @@ -1241,6 +1274,7 @@ func (c *CreateTableEntity) diffPartitions(alterTable *sqlparser.AlterTable, IsAll: true, } alterTable.PartitionSpec = partitionSpec + annotations.MarkRemoved(sqlparser.CanonicalString(t1Partitions)) case sqlparser.Equals.RefOfPartitionOption(t1Partitions, t2Partitions): // identical partitioning return nil, nil @@ -1256,7 +1290,7 @@ func (c *CreateTableEntity) diffPartitions(alterTable *sqlparser.AlterTable, // Having said that, we _do_ analyze the scenario of a RANGE partitioning rotation of partitions: // where zero or more partitions may have been dropped from the earlier range, and zero or more // partitions have been added with a later range: - isRotation, partitionSpecs, err := c.isRangePartitionsRotation(t1Partitions, t2Partitions) + isRotation, partitionSpecs, err := c.isRangePartitionsRotation(annotations, t1Partitions, t2Partitions) if err != nil { return nil, err } @@ -1271,11 +1305,14 @@ func (c *CreateTableEntity) diffPartitions(alterTable *sqlparser.AlterTable, } } alterTable.PartitionOption = t2Partitions + annotations.MarkRemoved(sqlparser.CanonicalString(t1Partitions)) + annotations.MarkAdded(sqlparser.CanonicalString(t2Partitions)) } return nil, nil } func (c *CreateTableEntity) diffConstraints(alterTable *sqlparser.AlterTable, + annotations *TextualAnnotations, t1Name string, t1Constraints []*sqlparser.ConstraintDefinition, t2Name string, @@ -1328,6 +1365,7 @@ func (c *CreateTableEntity) diffConstraints(alterTable *sqlparser.AlterTable, // constraint exists in t1 but not in t2, hence it is dropped dropConstraint := dropConstraintStatement(t1Constraint) alterTable.AlterOptions = append(alterTable.AlterOptions, dropConstraint) + annotations.MarkRemoved(sqlparser.CanonicalString(t1Constraint)) } else { t2ConstraintsCountMap[constraintName]-- } @@ -1354,6 +1392,8 @@ func (c *CreateTableEntity) diffConstraints(alterTable *sqlparser.AlterTable, Enforced: check2Details.Enforced, } alterTable.AlterOptions = append(alterTable.AlterOptions, alterConstraint) + annotations.MarkRemoved(sqlparser.CanonicalString(t1Constraint)) + annotations.MarkAdded(sqlparser.CanonicalString(t2Constraint)) continue } @@ -1364,6 +1404,8 @@ func (c *CreateTableEntity) diffConstraints(alterTable *sqlparser.AlterTable, } alterTable.AlterOptions = append(alterTable.AlterOptions, dropConstraint) alterTable.AlterOptions = append(alterTable.AlterOptions, addConstraint) + annotations.MarkRemoved(sqlparser.CanonicalString(t1Constraint)) + annotations.MarkAdded(sqlparser.CanonicalString(t2Constraint)) } } else { // constraint exists in t2 but not in t1, hence it is added @@ -1371,11 +1413,13 @@ func (c *CreateTableEntity) diffConstraints(alterTable *sqlparser.AlterTable, ConstraintDefinition: t2Constraint, } alterTable.AlterOptions = append(alterTable.AlterOptions, addConstraint) + annotations.MarkAdded(sqlparser.CanonicalString(t2Constraint)) } } } func (c *CreateTableEntity) diffKeys(alterTable *sqlparser.AlterTable, + annotations *TextualAnnotations, t1Keys []*sqlparser.IndexDefinition, t2Keys []*sqlparser.IndexDefinition, hints *DiffHints, @@ -1407,6 +1451,7 @@ func (c *CreateTableEntity) diffKeys(alterTable *sqlparser.AlterTable, // column exists in t1 but not in t2, hence it is dropped dropKey := dropKeyStatement(t1Key.Info) alterTable.AlterOptions = append(alterTable.AlterOptions, dropKey) + annotations.MarkRemoved(sqlparser.CanonicalString(t1Key)) } } @@ -1425,6 +1470,8 @@ func (c *CreateTableEntity) diffKeys(alterTable *sqlparser.AlterTable, Name: t2Key.Info.Name, Invisible: newVisibility, }) + annotations.MarkRemoved(sqlparser.CanonicalString(t1Key)) + annotations.MarkAdded(sqlparser.CanonicalString(t2Key)) continue } @@ -1435,6 +1482,8 @@ func (c *CreateTableEntity) diffKeys(alterTable *sqlparser.AlterTable, } alterTable.AlterOptions = append(alterTable.AlterOptions, dropKey) alterTable.AlterOptions = append(alterTable.AlterOptions, addKey) + annotations.MarkRemoved(sqlparser.CanonicalString(t1Key)) + annotations.MarkAdded(sqlparser.CanonicalString(t2Key)) } } else { // key exists in t2 but not in t1, hence it is added @@ -1447,11 +1496,13 @@ func (c *CreateTableEntity) diffKeys(alterTable *sqlparser.AlterTable, // Special case: MySQL does not support multiple ADD FULLTEXT KEY statements in a single ALTER superfluousFulltextKeys = append(superfluousFulltextKeys, addKey) addedAsSuperfluousStatement = true + annotations.MarkAdded(sqlparser.CanonicalString(t2Key)) } addedFulltextKeys++ } if !addedAsSuperfluousStatement { alterTable.AlterOptions = append(alterTable.AlterOptions, addKey) + annotations.MarkAdded(sqlparser.CanonicalString(t2Key)) } } } @@ -1528,6 +1579,7 @@ func evaluateColumnReordering(t1SharedColumns, t2SharedColumns []*sqlparser.Colu // It returns an AlterTable statement if changes are found, or nil if not. // the other table may be of different name; its name is ignored. func (c *CreateTableEntity) diffColumns(alterTable *sqlparser.AlterTable, + annotations *TextualAnnotations, t1Columns []*sqlparser.ColumnDefinition, t2Columns []*sqlparser.ColumnDefinition, hints *DiffHints, @@ -1570,6 +1622,7 @@ func (c *CreateTableEntity) diffColumns(alterTable *sqlparser.AlterTable, Name: getColName(&t1Col.Name), } dropColumns = append(dropColumns, dropColumn) + annotations.MarkRemoved(sqlparser.CanonicalString(t1Col)) } } @@ -1625,6 +1678,8 @@ func (c *CreateTableEntity) diffColumns(alterTable *sqlparser.AlterTable, if modifyColumnDiff != nil { // column definition or ordering has changed modifyColumns = append(modifyColumns, modifyColumnDiff.modifyColumn) + annotations.MarkRemoved(sqlparser.CanonicalString(t1Col.col)) + annotations.MarkAdded(sqlparser.CanonicalString(t2Col)) } } // Evaluate added columns @@ -1650,6 +1705,7 @@ func (c *CreateTableEntity) diffColumns(alterTable *sqlparser.AlterTable, } expectAppendIndex++ addColumns = append(addColumns, addColumn) + annotations.MarkAdded(sqlparser.CanonicalString(t2Col)) } } dropColumns, addColumns, renameColumns := heuristicallyDetectColumnRenames(dropColumns, addColumns, t1ColumnsMap, t2ColumnsMap, hints) diff --git a/go/vt/schemadiff/table_test.go b/go/vt/schemadiff/table_test.go index af72f15776b..0e6ef9c12b2 100644 --- a/go/vt/schemadiff/table_test.go +++ b/go/vt/schemadiff/table_test.go @@ -46,6 +46,7 @@ func TestCreateTableDiff(t *testing.T) { charset int algorithm int enumreorder int + textdiffs []string }{ { name: "identical", @@ -70,6 +71,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t (Id int not null, primary key(id))", diff: "alter table t modify column Id int not null", cdiff: "ALTER TABLE `t` MODIFY COLUMN `Id` int NOT NULL", + textdiffs: []string{ + "- `id` int NOT NULL,", + "+ `Id` int NOT NULL,", + }, }, { name: "identical, name change", @@ -107,6 +112,9 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key, `i` int not null default 0)", diff: "alter table t1 add column i int not null default 0", cdiff: "ALTER TABLE `t1` ADD COLUMN `i` int NOT NULL DEFAULT 0", + textdiffs: []string{ + "+ `i` int NOT NULL DEFAULT 0,", + }, }, { name: "dropped column", @@ -116,6 +124,9 @@ func TestCreateTableDiff(t *testing.T) { cdiff: "ALTER TABLE `t1` DROP COLUMN `i`", fromName: "t1", toName: "t2", + textdiffs: []string{ + "- `i` int NOT NULL DEFAULT 0,", + }, }, { name: "modified column", @@ -123,6 +134,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key, `i` bigint unsigned default null)", diff: "alter table t1 modify column i bigint unsigned", cdiff: "ALTER TABLE `t1` MODIFY COLUMN `i` bigint unsigned", + textdiffs: []string{ + "- `i` int NOT NULL DEFAULT 0,", + "+ `i` bigint unsigned,", + }, }, { name: "added column, dropped column, modified column", @@ -130,6 +145,12 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key, ts timestamp null, `i` bigint unsigned default null)", diff: "alter table t1 drop column c, modify column i bigint unsigned, add column ts timestamp null after id", cdiff: "ALTER TABLE `t1` DROP COLUMN `c`, MODIFY COLUMN `i` bigint unsigned, ADD COLUMN `ts` timestamp NULL AFTER `id`", + textdiffs: []string{ + "- `c` char(3) DEFAULT '',", + "- `i` int NOT NULL DEFAULT 0,", + "+ `i` bigint unsigned,", + "+ `ts` timestamp NULL,", + }, }, // columns, rename { @@ -138,6 +159,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key, i2 int not null, c char(3) default '')", diff: "alter table t1 drop column i1, add column i2 int not null after id", cdiff: "ALTER TABLE `t1` DROP COLUMN `i1`, ADD COLUMN `i2` int NOT NULL AFTER `id`", + textdiffs: []string{ + "- `i1` int NOT NULL,", + "+ `i2` int NOT NULL,", + }, }, { name: "rename mid column. statement", @@ -146,6 +171,10 @@ func TestCreateTableDiff(t *testing.T) { colrename: ColumnRenameHeuristicStatement, diff: "alter table t1 rename column i1 to i2", cdiff: "ALTER TABLE `t1` RENAME COLUMN `i1` TO `i2`", + textdiffs: []string{ + "- `i1` int NOT NULL,", + "+ `i2` int NOT NULL,", + }, }, { name: "rename last column. statement", @@ -154,6 +183,10 @@ func TestCreateTableDiff(t *testing.T) { colrename: ColumnRenameHeuristicStatement, diff: "alter table t1 rename column i1 to i2", cdiff: "ALTER TABLE `t1` RENAME COLUMN `i1` TO `i2`", + textdiffs: []string{ + "- `i1` int NOT NULL,", + "+ `i2` int NOT NULL,", + }, }, { name: "rename two columns. statement", @@ -162,6 +195,12 @@ func TestCreateTableDiff(t *testing.T) { colrename: ColumnRenameHeuristicStatement, diff: "alter table t1 rename column i1 to i2, rename column v1 to v2", cdiff: "ALTER TABLE `t1` RENAME COLUMN `i1` TO `i2`, RENAME COLUMN `v1` TO `v2`", + textdiffs: []string{ + "- `i1` int NOT NULL,", + "- `v1` varchar(32),", + "+ `i2` int NOT NULL,", + "+ `v2` varchar(32),", + }, }, { name: "rename mid column and add an index. statement", @@ -170,6 +209,11 @@ func TestCreateTableDiff(t *testing.T) { colrename: ColumnRenameHeuristicStatement, diff: "alter table t1 rename column i1 to i2, add key i2_idx (i2)", cdiff: "ALTER TABLE `t1` RENAME COLUMN `i1` TO `i2`, ADD KEY `i2_idx` (`i2`)", + textdiffs: []string{ + "- `i1` int NOT NULL,", + "+ `i2` int NOT NULL,", + "+ KEY `i2_idx` (`i2`)", + }, }, { // in a future iteration, this will generate a RENAME for both column, like in the previous test. Until then, we do not RENAME two successive columns @@ -179,6 +223,12 @@ func TestCreateTableDiff(t *testing.T) { colrename: ColumnRenameHeuristicStatement, diff: "alter table t1 drop column i1, drop column v1, add column i2 int not null, add column v2 varchar(32)", cdiff: "ALTER TABLE `t1` DROP COLUMN `i1`, DROP COLUMN `v1`, ADD COLUMN `i2` int NOT NULL, ADD COLUMN `v2` varchar(32)", + textdiffs: []string{ + "- `i1` int NOT NULL,", + "- `v1` varchar(32),", + "+ `i2` int NOT NULL,", + "+ `v2` varchar(32),", + }, }, // columns, reordering { @@ -187,6 +237,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key, a int, c int, b int, d int)", diff: "alter table t1 modify column c int after a", cdiff: "ALTER TABLE `t1` MODIFY COLUMN `c` int AFTER `a`", + textdiffs: []string{ + "+ `c` int,", + "- `c` int,", + }, }, { name: "reorder column, far jump", @@ -194,6 +248,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (a int, b int, c int, d int, id int primary key)", diff: "alter table t1 modify column id int after d", cdiff: "ALTER TABLE `t1` MODIFY COLUMN `id` int AFTER `d`", + textdiffs: []string{ + "- `id` int,", + "+ `id` int,", + }, }, { name: "reorder column, far jump with case sentivity", @@ -201,6 +259,12 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (a int, B int, c int, d int, id int primary key)", diff: "alter table t1 modify column B int, modify column id int after d", cdiff: "ALTER TABLE `t1` MODIFY COLUMN `B` int, MODIFY COLUMN `id` int AFTER `d`", + textdiffs: []string{ + "- `id` int,", + "+ `id` int,", + "- `b` int,", + "+ `B` int,", + }, }, { name: "reorder column, far jump, another reorder", @@ -208,6 +272,12 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (a int, c int, b int, d int, id int primary key)", diff: "alter table t1 modify column c int after a, modify column id int after d", cdiff: "ALTER TABLE `t1` MODIFY COLUMN `c` int AFTER `a`, MODIFY COLUMN `id` int AFTER `d`", + textdiffs: []string{ + "- `id` int,", + "+ `id` int,", + "- `c` int,", + "+ `c` int,", + }, }, { name: "reorder column, far jump, another reorder 2", @@ -215,6 +285,12 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (c int, a int, b int, d int, id int primary key)", diff: "alter table t1 modify column c int first, modify column id int after d", cdiff: "ALTER TABLE `t1` MODIFY COLUMN `c` int FIRST, MODIFY COLUMN `id` int AFTER `d`", + textdiffs: []string{ + "- `id` int,", + "+ `id` int,", + "- `c` int,", + "+ `c` int,", + }, }, { name: "reorder column, far jump, another reorder 3", @@ -222,6 +298,12 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (a int, c int, b int, d int, id int primary key, e int, f int)", diff: "alter table t1 modify column c int after a, modify column id int after d", cdiff: "ALTER TABLE `t1` MODIFY COLUMN `c` int AFTER `a`, MODIFY COLUMN `id` int AFTER `d`", + textdiffs: []string{ + "- `id` int,", + "+ `id` int,", + "- `c` int,", + "+ `c` int,", + }, }, { name: "reorder column, far jump, another reorder, removed columns", @@ -229,6 +311,14 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (a int, c int, f int, e int, id int primary key, g int)", diff: "alter table t1 drop column b, drop column d, modify column f int after c, modify column id int after e", cdiff: "ALTER TABLE `t1` DROP COLUMN `b`, DROP COLUMN `d`, MODIFY COLUMN `f` int AFTER `c`, MODIFY COLUMN `id` int AFTER `e`", + textdiffs: []string{ + "- `b` int,", + "- `d` int,", + "- `id` int,", + "+ `id` int,", + "- `f` int,", + "+ `f` int,", + }, }, { name: "two reorders", @@ -236,6 +326,12 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key, b int, a int, c int, e int, d int, f int)", diff: "alter table t1 modify column b int after id, modify column e int after c", cdiff: "ALTER TABLE `t1` MODIFY COLUMN `b` int AFTER `id`, MODIFY COLUMN `e` int AFTER `c`", + textdiffs: []string{ + "- `b` int,", + "+ `b` int,", + "- `e` int,", + "+ `e` int,", + }, }, { name: "two reorders, added and removed columns", @@ -243,6 +339,18 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (g int, id int primary key, h int, b int, a int, i int, e int, d int, j int, f int, k int)", diff: "alter table t1 drop column c, modify column b int after id, modify column e int after a, add column g int first, add column h int after id, add column i int after a, add column j int after d, add column k int", cdiff: "ALTER TABLE `t1` DROP COLUMN `c`, MODIFY COLUMN `b` int AFTER `id`, MODIFY COLUMN `e` int AFTER `a`, ADD COLUMN `g` int FIRST, ADD COLUMN `h` int AFTER `id`, ADD COLUMN `i` int AFTER `a`, ADD COLUMN `j` int AFTER `d`, ADD COLUMN `k` int", + textdiffs: []string{ + "- `c` int,", + "- `b` int,", + "+ `b` int,", + "- `e` int,", + "+ `e` int,", + "+ `g` int,", + "+ `h` int,", + "+ `i` int,", + "+ `j` int,", + "+ `k` int,", + }, }, { name: "reorder column and change data type", @@ -250,6 +358,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key, a int, c bigint, b int, d int)", diff: "alter table t1 modify column c bigint after a", cdiff: "ALTER TABLE `t1` MODIFY COLUMN `c` bigint AFTER `a`", + textdiffs: []string{ + "- `c` int,", + "+ `c` bigint,", + }, }, { name: "reorder column, first", @@ -257,6 +369,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (c int, id int primary key, a int, b int, d int)", diff: "alter table t1 modify column c int first", cdiff: "ALTER TABLE `t1` MODIFY COLUMN `c` int FIRST", + textdiffs: []string{ + "- `c` int,", + "+ `c` int,", + }, }, { name: "add multiple columns", @@ -264,6 +380,11 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key, a int, b int, c int, d int)", diff: "alter table t1 add column b int, add column c int, add column d int", cdiff: "ALTER TABLE `t1` ADD COLUMN `b` int, ADD COLUMN `c` int, ADD COLUMN `d` int", + textdiffs: []string{ + "+ `b` int,", + "+ `c` int,", + "+ `d` int,", + }, }, { name: "added column in middle", @@ -271,6 +392,9 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key, a int, b int, x int, c int, d int)", diff: "alter table t1 add column x int after b", cdiff: "ALTER TABLE `t1` ADD COLUMN `x` int AFTER `b`", + textdiffs: []string{ + "+ `x` int,", + }, }, { name: "added multiple column in middle", @@ -278,6 +402,12 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (w int, x int, id int primary key, y int, a int, z int)", diff: "alter table t1 add column w int first, add column x int after w, add column y int after id, add column z int", cdiff: "ALTER TABLE `t1` ADD COLUMN `w` int FIRST, ADD COLUMN `x` int AFTER `w`, ADD COLUMN `y` int AFTER `id`, ADD COLUMN `z` int", + textdiffs: []string{ + "+ `w` int,", + "+ `x` int,", + "+ `y` int,", + "+ `z` int,", + }, }, { name: "added column first, reorder column", @@ -285,6 +415,11 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (x int, a int, id int primary key)", diff: "alter table t1 modify column a int first, add column x int first", cdiff: "ALTER TABLE `t1` MODIFY COLUMN `a` int FIRST, ADD COLUMN `x` int FIRST", + textdiffs: []string{ + "- `a` int,", + "+ `a` int,", + "+ `x` int,", + }, }, { name: "added column in middle, add column on end, reorder column", @@ -292,6 +427,12 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key, a int, b int, x int, d int, c int, y int)", diff: "alter table t1 modify column d int after b, add column x int after b, add column y int", cdiff: "ALTER TABLE `t1` MODIFY COLUMN `d` int AFTER `b`, ADD COLUMN `x` int AFTER `b`, ADD COLUMN `y` int", + textdiffs: []string{ + "- `d` int,", + "+ `d` int,", + "+ `x` int,", + "+ `y` int,", + }, }, { name: "added column in middle, add column on end, reorder column 2", @@ -299,6 +440,12 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key, a int, c int, x int, b int, d int, y int)", diff: "alter table t1 modify column c int after a, add column x int after c, add column y int", cdiff: "ALTER TABLE `t1` MODIFY COLUMN `c` int AFTER `a`, ADD COLUMN `x` int AFTER `c`, ADD COLUMN `y` int", + textdiffs: []string{ + "- `c` int,", + "+ `c` int,", + "+ `x` int,", + "+ `y` int,", + }, }, // enum { @@ -307,6 +454,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key, e enum('a', 'b', 'c', 'd'))", diff: "alter table t1 modify column e enum('a', 'b', 'c', 'd')", cdiff: "ALTER TABLE `t1` MODIFY COLUMN `e` enum('a', 'b', 'c', 'd')", + textdiffs: []string{ + "- `e` enum('a', 'b', 'c'),", + "+ `e` enum('a', 'b', 'c', 'd'),", + }, }, { name: "truncate enum", @@ -314,6 +465,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key, e enum('a', 'b'))", diff: "alter table t1 modify column e enum('a', 'b')", cdiff: "ALTER TABLE `t1` MODIFY COLUMN `e` enum('a', 'b')", + textdiffs: []string{ + "- `e` enum('a', 'b', 'c'),", + "+ `e` enum('a', 'b'),", + }, }, { name: "rename enum value", @@ -321,6 +476,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key, e enum('a', 'b', 'd'))", diff: "alter table t1 modify column e enum('a', 'b', 'd')", cdiff: "ALTER TABLE `t1` MODIFY COLUMN `e` enum('a', 'b', 'd')", + textdiffs: []string{ + "- `e` enum('a', 'b', 'c'),", + "+ `e` enum('a', 'b', 'd'),", + }, }, { name: "reorder enum, fail", @@ -336,6 +495,10 @@ func TestCreateTableDiff(t *testing.T) { diff: "alter table t1 modify column e enum('b', 'a', 'c')", cdiff: "ALTER TABLE `t1` MODIFY COLUMN `e` enum('b', 'a', 'c')", enumreorder: EnumReorderStrategyAllow, + textdiffs: []string{ + "- `e` enum('a', 'b', 'c'),", + "+ `e` enum('b', 'a', 'c'),", + }, }, { name: "expand set", @@ -343,6 +506,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key, e set('a', 'b', 'c', 'd'))", diff: "alter table t1 modify column e set('a', 'b', 'c', 'd')", cdiff: "ALTER TABLE `t1` MODIFY COLUMN `e` set('a', 'b', 'c', 'd')", + textdiffs: []string{ + "- `e` set('a', 'b', 'c'),", + "+ `e` set('a', 'b', 'c', 'd'),", + }, }, { name: "truncate set", @@ -350,6 +517,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key, e set('a', 'b'))", diff: "alter table t1 modify column e set('a', 'b')", cdiff: "ALTER TABLE `t1` MODIFY COLUMN `e` set('a', 'b')", + textdiffs: []string{ + "- `e` set('a', 'b', 'c'),", + "+ `e` set('a', 'b'),", + }, }, { name: "rename set value", @@ -357,6 +528,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key, e set('a', 'b', 'd'))", diff: "alter table t1 modify column e set('a', 'b', 'd')", cdiff: "ALTER TABLE `t1` MODIFY COLUMN `e` set('a', 'b', 'd')", + textdiffs: []string{ + "- `e` set('a', 'b', 'c'),", + "+ `e` set('a', 'b', 'd'),", + }, }, { name: "reorder set, fail", @@ -372,6 +547,10 @@ func TestCreateTableDiff(t *testing.T) { diff: "alter table t1 modify column e set('b', 'a', 'c')", cdiff: "ALTER TABLE `t1` MODIFY COLUMN `e` set('b', 'a', 'c')", enumreorder: EnumReorderStrategyAllow, + textdiffs: []string{ + "- `e` set('a', 'b', 'c'),", + "+ `e` set('b', 'a', 'c'),", + }, }, // keys @@ -381,6 +560,9 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key, `i` int, key `i_idx` (i))", diff: "alter table t1 add key i_idx (i)", cdiff: "ALTER TABLE `t1` ADD KEY `i_idx` (`i`)", + textdiffs: []string{ + "+ KEY `i_idx` (`i`)", + }, }, { name: "added key without name", @@ -388,6 +570,9 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key, `i` int, key (i))", diff: "alter table t1 add key i (i)", cdiff: "ALTER TABLE `t1` ADD KEY `i` (`i`)", + textdiffs: []string{ + "+ KEY `i` (`i`)", + }, }, { name: "added key without name, conflicting name", @@ -395,6 +580,9 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key, `i` int, key i(i), key (i))", diff: "alter table t1 add key i_2 (i)", cdiff: "ALTER TABLE `t1` ADD KEY `i_2` (`i`)", + textdiffs: []string{ + "+ KEY `i_2` (`i`)", + }, }, { name: "added key without name, conflicting name 2", @@ -402,6 +590,9 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key, `i` int, key i(i), key i_2(i), key (i))", diff: "alter table t1 add key i_3 (i)", cdiff: "ALTER TABLE `t1` ADD KEY `i_3` (`i`)", + textdiffs: []string{ + "+ KEY `i_3` (`i`)", + }, }, { name: "added column and key", @@ -409,6 +600,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key, `i` int, key `i_idx` (i))", diff: "alter table t1 add column i int, add key i_idx (i)", cdiff: "ALTER TABLE `t1` ADD COLUMN `i` int, ADD KEY `i_idx` (`i`)", + textdiffs: []string{ + "+ `i` int", + "+ KEY `i_idx` (`i`)", + }, }, { name: "modify column primary key", @@ -416,6 +611,9 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key)", diff: "alter table t1 add primary key (id)", cdiff: "ALTER TABLE `t1` ADD PRIMARY KEY (`id`)", + textdiffs: []string{ + "+ PRIMARY KEY (`id`)", + }, }, { name: "added primary key", @@ -423,6 +621,9 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int, primary key(id))", diff: "alter table t1 add primary key (id)", cdiff: "ALTER TABLE `t1` ADD PRIMARY KEY (`id`)", + textdiffs: []string{ + "+ PRIMARY KEY (`id`)", + }, }, { name: "dropped primary key", @@ -430,6 +631,9 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int)", diff: "alter table t1 drop primary key", cdiff: "ALTER TABLE `t1` DROP PRIMARY KEY", + textdiffs: []string{ + "- PRIMARY KEY (`id`)", + }, }, { name: "dropped key", @@ -437,6 +641,9 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (`id` int primary key, i int)", diff: "alter table t1 drop key i_idx", cdiff: "ALTER TABLE `t1` DROP KEY `i_idx`", + textdiffs: []string{ + "- KEY `i_idx` (`i`)", + }, }, { name: "dropped key 2", @@ -444,6 +651,9 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t1 (`id` int, i int, primary key (id))", diff: "alter table t1 drop key i_idx", cdiff: "ALTER TABLE `t1` DROP KEY `i_idx`", + textdiffs: []string{ + "- KEY `i_idx` (`i`)", + }, }, { name: "modified key", @@ -451,6 +661,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (`id` int primary key, i int, key i_idx(i, id))", diff: "alter table t1 drop key i_idx, add key i_idx (i, id)", cdiff: "ALTER TABLE `t1` DROP KEY `i_idx`, ADD KEY `i_idx` (`i`, `id`)", + textdiffs: []string{ + "- KEY `i_idx` (`i`)", + "+ KEY `i_idx` (`i`, `id`)", + }, }, { name: "modified primary key", @@ -458,6 +672,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (`id` int, i int, primary key(id, i),key i_idx(`i`))", diff: "alter table t1 drop primary key, add primary key (id, i)", cdiff: "ALTER TABLE `t1` DROP PRIMARY KEY, ADD PRIMARY KEY (`id`, `i`)", + textdiffs: []string{ + "- PRIMARY KEY (`id`)", + "+ PRIMARY KEY (`id`, `i`)", + }, }, { name: "alternative primary key definition, no diff", @@ -472,6 +690,12 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t1 (the_id int primary key, info int not null);", diff: "alter table t1 drop primary key, drop column id, add column the_id int first, add primary key (the_id)", cdiff: "ALTER TABLE `t1` DROP PRIMARY KEY, DROP COLUMN `id`, ADD COLUMN `the_id` int FIRST, ADD PRIMARY KEY (`the_id`)", + textdiffs: []string{ + "- PRIMARY KEY (`id`)", + "- `id` int,", + "+ `the_id` int,", + "+ PRIMARY KEY (`the_id`)", + }, }, { name: "reordered key, no diff", @@ -499,6 +723,9 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (`id` int primary key, i int, key i2_idx (`i`, id), key i_idx3(id), key i_idx ( i ) )", diff: "alter table t1 add key i_idx3 (id)", cdiff: "ALTER TABLE `t1` ADD KEY `i_idx3` (`id`)", + textdiffs: []string{ + "+ KEY `i_idx3` (`id`)", + }, }, { name: "key made visible", @@ -506,6 +733,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t1 (`id` int primary key, i int, key i_idx(i))", diff: "alter table t1 alter index i_idx visible", cdiff: "ALTER TABLE `t1` ALTER INDEX `i_idx` VISIBLE", + textdiffs: []string{ + "- KEY `i_idx` (`i`) INVISIBLE", + "+ KEY `i_idx` (`i`)", + }, }, { name: "key made invisible", @@ -513,6 +744,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t1 (`id` int primary key, i int, key i_idx(i) invisible)", diff: "alter table t1 alter index i_idx invisible", cdiff: "ALTER TABLE `t1` ALTER INDEX `i_idx` INVISIBLE", + textdiffs: []string{ + "- KEY `i_idx` (`i`)", + "+ KEY `i_idx` (`i`) INVISIBLE", + }, }, { name: "key made invisible with different case", @@ -520,6 +755,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t1 (`id` int primary key, i int, key i_idx(i) INVISIBLE)", diff: "alter table t1 alter index i_idx invisible", cdiff: "ALTER TABLE `t1` ALTER INDEX `i_idx` INVISIBLE", + textdiffs: []string{ + "- KEY `i_idx` (`i`)", + "+ KEY `i_idx` (`i`) INVISIBLE", + }, }, // FULLTEXT keys { @@ -528,6 +767,9 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t1 (id int primary key, name tinytext not null, fulltext key name_ft(name))", diff: "alter table t1 add fulltext key name_ft (`name`)", cdiff: "ALTER TABLE `t1` ADD FULLTEXT KEY `name_ft` (`name`)", + textdiffs: []string{ + "+ FULLTEXT KEY `name_ft` (`name`)", + }, }, { name: "add one fulltext key with explicit parser", @@ -535,6 +777,9 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t1 (id int primary key, name tinytext not null, fulltext key name_ft(name) with parser ngram)", diff: "alter table t1 add fulltext key name_ft (`name`) with parser ngram", cdiff: "ALTER TABLE `t1` ADD FULLTEXT KEY `name_ft` (`name`) WITH PARSER ngram", + textdiffs: []string{ + "+ FULLTEXT KEY `name_ft` (`name`) WITH PARSER ngram", + }, }, { name: "add one fulltext key and one normal key", @@ -542,6 +787,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t1 (id int primary key, name tinytext not null, key name_idx(name(32)), fulltext key name_ft(name))", diff: "alter table t1 add key name_idx (`name`(32)), add fulltext key name_ft (`name`)", cdiff: "ALTER TABLE `t1` ADD KEY `name_idx` (`name`(32)), ADD FULLTEXT KEY `name_ft` (`name`)", + textdiffs: []string{ + "+ KEY `name_idx` (`name`(32)),", + "+ FULLTEXT KEY `name_ft` (`name`)", + }, }, { name: "add two fulltext keys, distinct statements", @@ -549,6 +798,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t1 (id int primary key, name1 tinytext not null, name2 tinytext not null, fulltext key name1_ft(name1), fulltext key name2_ft(name2))", diffs: []string{"alter table t1 add fulltext key name1_ft (name1)", "alter table t1 add fulltext key name2_ft (name2)"}, cdiffs: []string{"ALTER TABLE `t1` ADD FULLTEXT KEY `name1_ft` (`name1`)", "ALTER TABLE `t1` ADD FULLTEXT KEY `name2_ft` (`name2`)"}, + textdiffs: []string{ + "+ FULLTEXT KEY `name1_ft` (`name1`)", + "+ FULLTEXT KEY `name2_ft` (`name2`)", + }, }, { name: "add two fulltext keys, unify statements", @@ -557,6 +810,10 @@ func TestCreateTableDiff(t *testing.T) { fulltext: FullTextKeyUnifyStatements, diff: "alter table t1 add fulltext key name1_ft (name1), add fulltext key name2_ft (name2)", cdiff: "ALTER TABLE `t1` ADD FULLTEXT KEY `name1_ft` (`name1`), ADD FULLTEXT KEY `name2_ft` (`name2`)", + textdiffs: []string{ + "+ FULLTEXT KEY `name1_ft` (`name1`)", + "+ FULLTEXT KEY `name2_ft` (`name2`)", + }, }, { name: "no fulltext diff", @@ -599,6 +856,10 @@ func TestCreateTableDiff(t *testing.T) { diff: "alter table t1 drop check check1, add constraint chk_abc123 check (i < 5)", cdiff: "ALTER TABLE `t1` DROP CHECK `check1`, ADD CONSTRAINT `chk_abc123` CHECK (`i` < 5)", constraint: ConstraintNamesStrict, + textdiffs: []string{ + "- CONSTRAINT `check1` CHECK (`i` < 5)", + "+ CONSTRAINT `chk_abc123` CHECK (`i` < 5)", + }, }, { name: "check constraints, different name, ignore vitess, non vitess names", @@ -607,6 +868,10 @@ func TestCreateTableDiff(t *testing.T) { diff: "alter table t1 drop check check1, add constraint chk_abc123 check (i < 5)", cdiff: "ALTER TABLE `t1` DROP CHECK `check1`, ADD CONSTRAINT `chk_abc123` CHECK (`i` < 5)", constraint: ConstraintNamesIgnoreVitess, + textdiffs: []string{ + "- CONSTRAINT `check1` CHECK (`i` < 5)", + "+ CONSTRAINT `chk_abc123` CHECK (`i` < 5)", + }, }, { name: "check constraints, different name, ignore vitess, vitess names, no match", @@ -615,6 +880,10 @@ func TestCreateTableDiff(t *testing.T) { diff: "alter table t1 drop check check1, add constraint check2_7fp024p4rxvr858tsaggvf9dw check (i < 5)", cdiff: "ALTER TABLE `t1` DROP CHECK `check1`, ADD CONSTRAINT `check2_7fp024p4rxvr858tsaggvf9dw` CHECK (`i` < 5)", constraint: ConstraintNamesIgnoreVitess, + textdiffs: []string{ + "- CONSTRAINT `check1` CHECK (`i` < 5)", + "+ CONSTRAINT `check2_7fp024p4rxvr858tsaggvf9dw` CHECK (`i` < 5)", + }, }, { name: "check constraints, different name, ignore vitess, vitess names match", @@ -672,6 +941,9 @@ func TestCreateTableDiff(t *testing.T) { diff: "alter table t1 add constraint check3 check (i != 3)", cdiff: "ALTER TABLE `t1` ADD CONSTRAINT `check3` CHECK (`i` != 3)", constraint: ConstraintNamesIgnoreAll, + textdiffs: []string{ + "+ CONSTRAINT `check3` CHECK (`i` != 3)", + }, }, { name: "check constraints, remove", @@ -680,6 +952,9 @@ func TestCreateTableDiff(t *testing.T) { diff: "alter table t1 drop check check3", cdiff: "ALTER TABLE `t1` DROP CHECK `check3`", constraint: ConstraintNamesIgnoreAll, + textdiffs: []string{ + "- CONSTRAINT `check3` CHECK (`i` != 3)", + }, }, { name: "check constraints, remove duplicate", @@ -688,6 +963,9 @@ func TestCreateTableDiff(t *testing.T) { diff: "alter table t1 drop check check3", cdiff: "ALTER TABLE `t1` DROP CHECK `check3`", constraint: ConstraintNamesIgnoreAll, + textdiffs: []string{ + "- CONSTRAINT `check3` CHECK (`i` > 2)", + }, }, { name: "check constraints, remove, ignore vitess, no match", @@ -696,6 +974,13 @@ func TestCreateTableDiff(t *testing.T) { diff: "alter table t1 drop check chk_123abc, drop check check3, drop check chk_789def, add constraint check1 check (i < 5), add constraint check2 check (i > 2)", cdiff: "ALTER TABLE `t1` DROP CHECK `chk_123abc`, DROP CHECK `check3`, DROP CHECK `chk_789def`, ADD CONSTRAINT `check1` CHECK (`i` < 5), ADD CONSTRAINT `check2` CHECK (`i` > 2)", constraint: ConstraintNamesIgnoreVitess, + textdiffs: []string{ + "- CONSTRAINT `chk_123abc` CHECK (`i` > 2)", + "- CONSTRAINT `check3` CHECK (`i` != 3)", + "- CONSTRAINT `chk_789def` CHECK (`i` < 5)", + "+ CONSTRAINT `check1` CHECK (`i` < 5)", + "+ CONSTRAINT `check2` CHECK (`i` > 2)", + }, }, { name: "check constraints, remove, ignore vitess, match", @@ -704,6 +989,9 @@ func TestCreateTableDiff(t *testing.T) { diff: "alter table t1 drop check check3", cdiff: "ALTER TABLE `t1` DROP CHECK `check3`", constraint: ConstraintNamesIgnoreVitess, + textdiffs: []string{ + "- CONSTRAINT `check3` CHECK (`i` != 3)", + }, }, { name: "check constraints, remove, strict", @@ -712,6 +1000,13 @@ func TestCreateTableDiff(t *testing.T) { diff: "alter table t1 drop check chk_123abc, drop check check3, drop check chk_789def, add constraint check1 check (i < 5), add constraint check2 check (i > 2)", cdiff: "ALTER TABLE `t1` DROP CHECK `chk_123abc`, DROP CHECK `check3`, DROP CHECK `chk_789def`, ADD CONSTRAINT `check1` CHECK (`i` < 5), ADD CONSTRAINT `check2` CHECK (`i` > 2)", constraint: ConstraintNamesStrict, + textdiffs: []string{ + "- CONSTRAINT `chk_123abc` CHECK (`i` > 2)", + "- CONSTRAINT `check3` CHECK (`i` != 3)", + "- CONSTRAINT `chk_789def` CHECK (`i` < 5)", + "+ CONSTRAINT `check1` CHECK (`i` < 5)", + "+ CONSTRAINT `check2` CHECK (`i` > 2)", + }, }, // foreign keys { @@ -720,6 +1015,9 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key, i int, key i_idex (i))", diff: "alter table t1 drop foreign key f", cdiff: "ALTER TABLE `t1` DROP FOREIGN KEY `f`", + textdiffs: []string{ + "- CONSTRAINT `f` FOREIGN KEY (`i`) REFERENCES `parent` (`id`)", + }, }, { name: "add foreign key", @@ -727,6 +1025,9 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key, i int, key ix(i), constraint f foreign key (i) references parent(id))", diff: "alter table t1 add constraint f foreign key (i) references parent (id)", cdiff: "ALTER TABLE `t1` ADD CONSTRAINT `f` FOREIGN KEY (`i`) REFERENCES `parent` (`id`)", + textdiffs: []string{ + "+ CONSTRAINT `f` FOREIGN KEY (`i`) REFERENCES `parent` (`id`)", + }, }, { name: "add foreign key and index", @@ -734,6 +1035,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key, i int, key ix(i), constraint f foreign key (i) references parent(id))", diff: "alter table t1 add key ix (i), add constraint f foreign key (i) references parent (id)", cdiff: "ALTER TABLE `t1` ADD KEY `ix` (`i`), ADD CONSTRAINT `f` FOREIGN KEY (`i`) REFERENCES `parent` (`id`)", + textdiffs: []string{ + "+ KEY `ix` (`i`)", + "+ CONSTRAINT `f` FOREIGN KEY (`i`) REFERENCES `parent` (`id`)", + }, }, { name: "identical foreign key", @@ -746,6 +1051,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key, i int, key ix(i), constraint f2 foreign key (i) references parent(id) on delete cascade)", diff: "alter table t1 drop foreign key f1, add constraint f2 foreign key (i) references parent (id) on delete cascade", cdiff: "ALTER TABLE `t1` DROP FOREIGN KEY `f1`, ADD CONSTRAINT `f2` FOREIGN KEY (`i`) REFERENCES `parent` (`id`) ON DELETE CASCADE", + textdiffs: []string{ + "- CONSTRAINT `f1` FOREIGN KEY (`i`) REFERENCES `parent` (`id`)", + "+ CONSTRAINT `f2` FOREIGN KEY (`i`) REFERENCES `parent` (`id`)", + }, }, { name: "similar foreign key under different name, ignore names", @@ -759,6 +1068,9 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key, i int, key i_idex (i), constraint f1 foreign key (i) references parent(id))", diff: "alter table t1 drop foreign key f2", cdiff: "ALTER TABLE `t1` DROP FOREIGN KEY `f2`", + textdiffs: []string{ + "- CONSTRAINT `f2` FOREIGN KEY (`i`) REFERENCES `parent` (`id`)", + }, }, { name: "two identical foreign keys, dropping one, ignore vitess names", @@ -767,6 +1079,9 @@ func TestCreateTableDiff(t *testing.T) { diff: "alter table t1 drop foreign key f2", cdiff: "ALTER TABLE `t1` DROP FOREIGN KEY `f2`", constraint: ConstraintNamesIgnoreVitess, + textdiffs: []string{ + "- CONSTRAINT `f2` FOREIGN KEY (`i`) REFERENCES `parent` (`id`)", + }, }, { name: "two identical foreign keys, dropping one, ignore all names", @@ -775,6 +1090,9 @@ func TestCreateTableDiff(t *testing.T) { diff: "alter table t1 drop foreign key f2", cdiff: "ALTER TABLE `t1` DROP FOREIGN KEY `f2`", constraint: ConstraintNamesIgnoreAll, + textdiffs: []string{ + "- CONSTRAINT `f2` FOREIGN KEY (`i`) REFERENCES `parent` (`id`)", + }, }, { name: "add two identical foreign key constraints, ignore all names", @@ -783,6 +1101,10 @@ func TestCreateTableDiff(t *testing.T) { diff: "alter table t1 add constraint f1 foreign key (i) references parent (id), add constraint f2 foreign key (i) references parent (id)", cdiff: "ALTER TABLE `t1` ADD CONSTRAINT `f1` FOREIGN KEY (`i`) REFERENCES `parent` (`id`), ADD CONSTRAINT `f2` FOREIGN KEY (`i`) REFERENCES `parent` (`id`)", constraint: ConstraintNamesIgnoreAll, + textdiffs: []string{ + "+ CONSTRAINT `f1` FOREIGN KEY (`i`) REFERENCES `parent` (`id`)", + "+ CONSTRAINT `f2` FOREIGN KEY (`i`) REFERENCES `parent` (`id`)", + }, }, { name: "implicit foreign key indexes", @@ -805,6 +1127,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key, i int, key ix(i), constraint f foreign key (i) references parent(id) on delete set null)", diff: "alter table t1 drop foreign key f, add constraint f foreign key (i) references parent (id) on delete set null", cdiff: "ALTER TABLE `t1` DROP FOREIGN KEY `f`, ADD CONSTRAINT `f` FOREIGN KEY (`i`) REFERENCES `parent` (`id`) ON DELETE SET NULL", + textdiffs: []string{ + "- CONSTRAINT `f` FOREIGN KEY (`i`) REFERENCES `parent` (`id`) ON DELETE CASCADE", + "+ CONSTRAINT `f` FOREIGN KEY (`i`) REFERENCES `parent` (`id`) ON DELETE SET NULL", + }, }, { name: "drop and add foreign key", @@ -812,6 +1138,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key, i int, key ix(i), constraint f2 foreign key (i) references parent(id) on delete set null)", diff: "alter table t1 drop foreign key f, add constraint f2 foreign key (i) references parent (id) on delete set null", cdiff: "ALTER TABLE `t1` DROP FOREIGN KEY `f`, ADD CONSTRAINT `f2` FOREIGN KEY (`i`) REFERENCES `parent` (`id`) ON DELETE SET NULL", + textdiffs: []string{ + "- CONSTRAINT `f` FOREIGN KEY (`i`) REFERENCES `parent` (`id`) ON DELETE CASCADE", + "+ CONSTRAINT `f2` FOREIGN KEY (`i`) REFERENCES `parent` (`id`) ON DELETE SET NULL", + }, }, { name: "ignore different foreign key order", @@ -825,6 +1155,9 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t2 (id int primary key, i int, key f(i))", diff: "alter table t1 drop foreign key f", cdiff: "ALTER TABLE `t1` DROP FOREIGN KEY `f`", + textdiffs: []string{ + "- CONSTRAINT `f` FOREIGN KEY (`i`) REFERENCES `parent` (`id`) ON DELETE CASCADE", + }, }, // partitions { @@ -833,6 +1166,9 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t1 (id int primary key, a int) partition by hash (id) partitions 4", diff: "alter table t1 add column a int", cdiff: "ALTER TABLE `t1` ADD COLUMN `a` int", + textdiffs: []string{ + "+ `a` int", + }, }, { name: "partitioning, column case", @@ -840,13 +1176,33 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t1 (id int primary key, a int) partition by hash (ID) partitions 4", diff: "alter table t1 add column a int \npartition by hash (ID) partitions 4", cdiff: "ALTER TABLE `t1` ADD COLUMN `a` int \nPARTITION BY HASH (`ID`) PARTITIONS 4", + textdiffs: []string{ + "+ `a` int", + "-PARTITION BY HASH (`id`) PARTITIONS 4", + "+PARTITION BY HASH (`ID`) PARTITIONS 4", + }, }, + { + name: "add partitioning", + from: "create table t1 (id int primary key, a int)", + to: "create table t1 (id int primary key, a int) partition by hash (id) partitions 4", + diff: "alter table t1 \npartition by hash (id) partitions 4", + cdiff: "ALTER TABLE `t1` \nPARTITION BY HASH (`id`) PARTITIONS 4", + textdiffs: []string{ + "+PARTITION BY HASH (`id`) PARTITIONS 4", + }, + }, + { name: "remove partitioning", from: "create table t1 (id int primary key) partition by hash (id) partitions 4", to: "create table t1 (id int primary key, a int)", diff: "alter table t1 add column a int remove partitioning", cdiff: "ALTER TABLE `t1` ADD COLUMN `a` int REMOVE PARTITIONING", + textdiffs: []string{ + "+ `a` int", + "-PARTITION BY HASH (`id`) PARTITIONS 4", + }, }, { name: "remove partitioning 2", @@ -854,6 +1210,9 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t1 (id int primary key)", diff: "alter table t1 remove partitioning", cdiff: "ALTER TABLE `t1` REMOVE PARTITIONING", + textdiffs: []string{ + "-PARTITION BY HASH (`id`) PARTITIONS 4", + }, }, { name: "change partitioning hash", @@ -861,6 +1220,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t1 (id int primary key) partition by hash (id) partitions 5", diff: "alter table t1 \npartition by hash (id) partitions 5", cdiff: "ALTER TABLE `t1` \nPARTITION BY HASH (`id`) PARTITIONS 5", + textdiffs: []string{ + "-PARTITION BY HASH (`id`) PARTITIONS 4", + "+PARTITION BY HASH (`id`) PARTITIONS 5", + }, }, { name: "change partitioning key", @@ -868,6 +1231,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t1 (id int primary key) partition by hash (id) partitions 5", diff: "alter table t1 \npartition by hash (id) partitions 5", cdiff: "ALTER TABLE `t1` \nPARTITION BY HASH (`id`) PARTITIONS 5", + textdiffs: []string{ + "-PARTITION BY KEY (`id`) PARTITIONS 2", + "+PARTITION BY HASH (`id`) PARTITIONS 5", + }, }, { name: "change partitioning list", @@ -875,6 +1242,12 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t1 (id int primary key) partition by list (id) (partition p1 values in(11,21), partition p2 values in (12,22))", diff: "alter table t1 \npartition by list (id)\n(partition p1 values in (11, 21),\n partition p2 values in (12, 22))", cdiff: "ALTER TABLE `t1` \nPARTITION BY LIST (`id`)\n(PARTITION `p1` VALUES IN (11, 21),\n PARTITION `p2` VALUES IN (12, 22))", + textdiffs: []string{ + "-PARTITION BY KEY (`id`) PARTITIONS 2", + "+PARTITION BY LIST (`id`)", + "+(PARTITION `p1` VALUES IN (11, 21),", + "+ PARTITION `p2` VALUES IN (12, 22))", + }, }, { name: "change partitioning range: rotate", @@ -882,6 +1255,16 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t1 (id int primary key) partition by range (id) (partition p2 values less than (20), partition p3 values less than (30), partition p4 values less than (40))", diff: "alter table t1 \npartition by range (id)\n(partition p2 values less than (20),\n partition p3 values less than (30),\n partition p4 values less than (40))", cdiff: "ALTER TABLE `t1` \nPARTITION BY RANGE (`id`)\n(PARTITION `p2` VALUES LESS THAN (20),\n PARTITION `p3` VALUES LESS THAN (30),\n PARTITION `p4` VALUES LESS THAN (40))", + textdiffs: []string{ + "-PARTITION BY RANGE (`id`)", + "-(PARTITION `p1` VALUES LESS THAN (10),", + "- PARTITION `p2` VALUES LESS THAN (20),", + "- PARTITION `p3` VALUES LESS THAN (30))", + "+PARTITION BY RANGE (`id`)", + "+(PARTITION `p2` VALUES LESS THAN (20),", + "+ PARTITION `p3` VALUES LESS THAN (30),", + "+ PARTITION `p4` VALUES LESS THAN (40))", + }, }, { name: "change partitioning range: ignore rotate", @@ -889,6 +1272,34 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t1 (id int primary key) partition by range (id) (partition p2 values less than (20), partition p3 values less than (30), partition p4 values less than (40))", rotation: RangeRotationIgnore, }, + { + name: "change partitioning range: don't rotate, single partition", + from: "create table t1 (id int primary key) partition by range (id) (partition p1 values less than (10))", + to: "create table t1 (id int primary key) partition by range (id) (partition p2 values less than (20))", + rotation: RangeRotationFullSpec, + diff: "alter table t1 \npartition by range (id)\n(partition p2 values less than (20))", + cdiff: "ALTER TABLE `t1` \nPARTITION BY RANGE (`id`)\n(PARTITION `p2` VALUES LESS THAN (20))", + textdiffs: []string{ + "-PARTITION BY RANGE (`id`)", + "-(PARTITION `p1` VALUES LESS THAN (10))", + "+PARTITION BY RANGE (`id`)", + "+(PARTITION `p2` VALUES LESS THAN (20))", + }, + }, + { + name: "change partitioning range: don't rotate, single partition", + from: "create table t1 (id int primary key) partition by range (id) (partition p1 values less than (10))", + to: "create table t1 (id int primary key) partition by range (id) (partition p2 values less than (20))", + rotation: RangeRotationDistinctStatements, + diff: "alter table t1 \npartition by range (id)\n(partition p2 values less than (20))", + cdiff: "ALTER TABLE `t1` \nPARTITION BY RANGE (`id`)\n(PARTITION `p2` VALUES LESS THAN (20))", + textdiffs: []string{ + "-PARTITION BY RANGE (`id`)", + "-(PARTITION `p1` VALUES LESS THAN (10))", + "+PARTITION BY RANGE (`id`)", + "+(PARTITION `p2` VALUES LESS THAN (20))", + }, + }, { name: "change partitioning range: statements, drop", from: "create table t1 (id int primary key) partition by range (id) (partition p1 values less than (10), partition p2 values less than (20), partition p3 values less than (30))", @@ -896,6 +1307,9 @@ func TestCreateTableDiff(t *testing.T) { rotation: RangeRotationDistinctStatements, diff: "alter table t1 drop partition p1", cdiff: "ALTER TABLE `t1` DROP PARTITION `p1`", + textdiffs: []string{ + "-(PARTITION `p1` VALUES LESS THAN (10),", + }, }, { name: "change partitioning range: statements, add", @@ -904,6 +1318,9 @@ func TestCreateTableDiff(t *testing.T) { rotation: RangeRotationDistinctStatements, diff: "alter table t1 add partition (partition p3 values less than (30))", cdiff: "ALTER TABLE `t1` ADD PARTITION (PARTITION `p3` VALUES LESS THAN (30))", + textdiffs: []string{ + "+ PARTITION `p3` VALUES LESS THAN (30)", + }, }, { name: "change partitioning range: statements, multiple drops", @@ -912,6 +1329,10 @@ func TestCreateTableDiff(t *testing.T) { rotation: RangeRotationDistinctStatements, diffs: []string{"alter table t1 drop partition p1", "alter table t1 drop partition p2"}, cdiffs: []string{"ALTER TABLE `t1` DROP PARTITION `p1`", "ALTER TABLE `t1` DROP PARTITION `p2`"}, + textdiffs: []string{ + "-(PARTITION `p1` VALUES LESS THAN (10),", + "- PARTITION `p2` VALUES LESS THAN (20),", + }, }, { name: "change partitioning range: statements, multiple adds", @@ -920,6 +1341,10 @@ func TestCreateTableDiff(t *testing.T) { rotation: RangeRotationDistinctStatements, diffs: []string{"alter table t1 add partition (partition p2 values less than (20))", "alter table t1 add partition (partition p3 values less than (30))"}, cdiffs: []string{"ALTER TABLE `t1` ADD PARTITION (PARTITION `p2` VALUES LESS THAN (20))", "ALTER TABLE `t1` ADD PARTITION (PARTITION `p3` VALUES LESS THAN (30))"}, + textdiffs: []string{ + "+ PARTITION `p2` VALUES LESS THAN (20),", + "+ PARTITION `p3` VALUES LESS THAN (30)", + }, }, { name: "change partitioning range: statements, multiple, assorted", @@ -928,6 +1353,10 @@ func TestCreateTableDiff(t *testing.T) { rotation: RangeRotationDistinctStatements, diffs: []string{"alter table t1 drop partition p1", "alter table t1 add partition (partition p4 values less than (40))"}, cdiffs: []string{"ALTER TABLE `t1` DROP PARTITION `p1`", "ALTER TABLE `t1` ADD PARTITION (PARTITION `p4` VALUES LESS THAN (40))"}, + textdiffs: []string{ + "-(PARTITION `p1` VALUES LESS THAN (10),", + "+ PARTITION `p4` VALUES LESS THAN (40)", + }, }, { name: "change partitioning range: mixed with nonpartition changes", @@ -936,6 +1365,11 @@ func TestCreateTableDiff(t *testing.T) { rotation: RangeRotationDistinctStatements, diffs: []string{"alter table t1 add column i int", "alter table t1 drop partition p1", "alter table t1 drop partition p2"}, cdiffs: []string{"ALTER TABLE `t1` ADD COLUMN `i` int", "ALTER TABLE `t1` DROP PARTITION `p1`", "ALTER TABLE `t1` DROP PARTITION `p2`"}, + textdiffs: []string{ + "+ `i` int", + "-(PARTITION `p1` VALUES LESS THAN (10),", + "- PARTITION `p2` VALUES LESS THAN (20),", + }, }, { name: "change partitioning range: single partition change, mixed with nonpartition changes", @@ -944,6 +1378,10 @@ func TestCreateTableDiff(t *testing.T) { rotation: RangeRotationDistinctStatements, diffs: []string{"alter table t1 add column i int", "alter table t1 drop partition p1"}, cdiffs: []string{"ALTER TABLE `t1` ADD COLUMN `i` int", "ALTER TABLE `t1` DROP PARTITION `p1`"}, + textdiffs: []string{ + "+ `i` int", + "-(PARTITION `p1` VALUES LESS THAN (10),", + }, }, { name: "change partitioning range: mixed with nonpartition changes, full spec", @@ -952,6 +1390,15 @@ func TestCreateTableDiff(t *testing.T) { rotation: RangeRotationFullSpec, diff: "alter table t1 add column i int \npartition by range (id)\n(partition p3 values less than (30))", cdiff: "ALTER TABLE `t1` ADD COLUMN `i` int \nPARTITION BY RANGE (`id`)\n(PARTITION `p3` VALUES LESS THAN (30))", + textdiffs: []string{ + "+ `i` int", + "-PARTITION BY RANGE (`id`)", + "-(PARTITION `p1` VALUES LESS THAN (10),", + "- PARTITION `p2` VALUES LESS THAN (20),", + "- PARTITION `p3` VALUES LESS THAN (30))", + "+PARTITION BY RANGE (`id`)", + "+(PARTITION `p3` VALUES LESS THAN (30))", + }, }, { name: "change partitioning range: ignore rotate, not a rotation", @@ -960,6 +1407,16 @@ func TestCreateTableDiff(t *testing.T) { rotation: RangeRotationIgnore, diff: "alter table t1 \npartition by range (id)\n(partition p2 values less than (25),\n partition p3 values less than (30),\n partition p4 values less than (40))", cdiff: "ALTER TABLE `t1` \nPARTITION BY RANGE (`id`)\n(PARTITION `p2` VALUES LESS THAN (25),\n PARTITION `p3` VALUES LESS THAN (30),\n PARTITION `p4` VALUES LESS THAN (40))", + textdiffs: []string{ + "-PARTITION BY RANGE (`id`)", + "-(PARTITION `p1` VALUES LESS THAN (10),", + "- PARTITION `p2` VALUES LESS THAN (20),", + "- PARTITION `p3` VALUES LESS THAN (30))", + "+PARTITION BY RANGE (`id`)", + "+(PARTITION `p2` VALUES LESS THAN (25)", + "+ PARTITION `p3` VALUES LESS THAN (30),", + "+ PARTITION `p4` VALUES LESS THAN (40))", + }, }, { name: "change partitioning range: ignore rotate, not a rotation 2", @@ -968,6 +1425,16 @@ func TestCreateTableDiff(t *testing.T) { rotation: RangeRotationIgnore, diff: "alter table t1 \npartition by range (id)\n(partition p2 values less than (20),\n partition p3 values less than (35),\n partition p4 values less than (40))", cdiff: "ALTER TABLE `t1` \nPARTITION BY RANGE (`id`)\n(PARTITION `p2` VALUES LESS THAN (20),\n PARTITION `p3` VALUES LESS THAN (35),\n PARTITION `p4` VALUES LESS THAN (40))", + textdiffs: []string{ + "-PARTITION BY RANGE (`id`)", + "-(PARTITION `p1` VALUES LESS THAN (10),", + "- PARTITION `p2` VALUES LESS THAN (20),", + "- PARTITION `p3` VALUES LESS THAN (30))", + "+PARTITION BY RANGE (`id`)", + "+(PARTITION `p2` VALUES LESS THAN (20)", + "+ PARTITION `p3` VALUES LESS THAN (35),", + "+ PARTITION `p4` VALUES LESS THAN (40))", + }, }, { name: "change partitioning range: ignore rotate, not a rotation 3", @@ -976,6 +1443,16 @@ func TestCreateTableDiff(t *testing.T) { rotation: RangeRotationIgnore, diff: "alter table t1 \npartition by range (id)\n(partition p2 values less than (20),\n partition pX values less than (30),\n partition p4 values less than (40))", cdiff: "ALTER TABLE `t1` \nPARTITION BY RANGE (`id`)\n(PARTITION `p2` VALUES LESS THAN (20),\n PARTITION `pX` VALUES LESS THAN (30),\n PARTITION `p4` VALUES LESS THAN (40))", + textdiffs: []string{ + "-PARTITION BY RANGE (`id`)", + "-(PARTITION `p1` VALUES LESS THAN (10),", + "- PARTITION `p2` VALUES LESS THAN (20),", + "- PARTITION `p3` VALUES LESS THAN (30))", + "+PARTITION BY RANGE (`id`)", + "+(PARTITION `p2` VALUES LESS THAN (20)", + "+ PARTITION `pX` VALUES LESS THAN (30),", + "+ PARTITION `p4` VALUES LESS THAN (40))", + }, }, { name: "change partitioning range: ignore rotate, not a rotation 4", @@ -984,6 +1461,16 @@ func TestCreateTableDiff(t *testing.T) { rotation: RangeRotationIgnore, diff: "alter table t1 \npartition by range (id)\n(partition pX values less than (20),\n partition p3 values less than (30),\n partition p4 values less than (40))", cdiff: "ALTER TABLE `t1` \nPARTITION BY RANGE (`id`)\n(PARTITION `pX` VALUES LESS THAN (20),\n PARTITION `p3` VALUES LESS THAN (30),\n PARTITION `p4` VALUES LESS THAN (40))", + textdiffs: []string{ + "-PARTITION BY RANGE (`id`)", + "-(PARTITION `p1` VALUES LESS THAN (10),", + "- PARTITION `p2` VALUES LESS THAN (20),", + "- PARTITION `p3` VALUES LESS THAN (30))", + "+PARTITION BY RANGE (`id`)", + "+(PARTITION `pX` VALUES LESS THAN (20)", + "+ PARTITION `p3` VALUES LESS THAN (30),", + "+ PARTITION `p4` VALUES LESS THAN (40))", + }, }, { name: "change partitioning range: ignore rotate, nothing shared", @@ -992,6 +1479,16 @@ func TestCreateTableDiff(t *testing.T) { rotation: RangeRotationIgnore, diff: "alter table t1 \npartition by range (id)\n(partition p4 values less than (40),\n partition p5 values less than (50),\n partition p6 values less than (60))", cdiff: "ALTER TABLE `t1` \nPARTITION BY RANGE (`id`)\n(PARTITION `p4` VALUES LESS THAN (40),\n PARTITION `p5` VALUES LESS THAN (50),\n PARTITION `p6` VALUES LESS THAN (60))", + textdiffs: []string{ + "-PARTITION BY RANGE (`id`)", + "-(PARTITION `p1` VALUES LESS THAN (10),", + "- PARTITION `p2` VALUES LESS THAN (20),", + "- PARTITION `p3` VALUES LESS THAN (30))", + "+PARTITION BY RANGE (`id`)", + "+(PARTITION `p4` VALUES LESS THAN (40)", + "+ PARTITION `p5` VALUES LESS THAN (50),", + "+ PARTITION `p6` VALUES LESS THAN (60))", + }, }, { name: "change partitioning range: ignore rotate, no names shared, definitions shared", @@ -1000,6 +1497,16 @@ func TestCreateTableDiff(t *testing.T) { rotation: RangeRotationIgnore, diff: "alter table t1 \npartition by range (id)\n(partition pA values less than (20),\n partition pB values less than (30),\n partition pC values less than (40))", cdiff: "ALTER TABLE `t1` \nPARTITION BY RANGE (`id`)\n(PARTITION `pA` VALUES LESS THAN (20),\n PARTITION `pB` VALUES LESS THAN (30),\n PARTITION `pC` VALUES LESS THAN (40))", + textdiffs: []string{ + "-PARTITION BY RANGE (`id`)", + "-(PARTITION `p1` VALUES LESS THAN (10),", + "- PARTITION `p2` VALUES LESS THAN (20),", + "- PARTITION `p3` VALUES LESS THAN (30))", + "+PARTITION BY RANGE (`id`)", + "+(PARTITION `pA` VALUES LESS THAN (20)", + "+ PARTITION `pB` VALUES LESS THAN (30),", + "+ PARTITION `pC` VALUES LESS THAN (40))", + }, }, // @@ -1030,6 +1537,9 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t1 (id int primary key) row_format=compressed", diff: "alter table t1 row_format COMPRESSED", cdiff: "ALTER TABLE `t1` ROW_FORMAT COMPRESSED", + textdiffs: []string{ + "+) ROW_FORMAT COMPRESSED", + }, }, { name: "add table option 2", @@ -1037,6 +1547,9 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t1 (id int primary key) character set=utf8, row_format=compressed", diff: "alter table t1 row_format COMPRESSED", cdiff: "ALTER TABLE `t1` ROW_FORMAT COMPRESSED", + textdiffs: []string{ + "+ ROW_FORMAT COMPRESSED", + }, }, { name: "add table option 3", @@ -1044,6 +1557,9 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t1 (id int primary key) row_format=compressed, character set=utf8", diff: "alter table t1 row_format COMPRESSED", cdiff: "ALTER TABLE `t1` ROW_FORMAT COMPRESSED", + textdiffs: []string{ + "+) ROW_FORMAT COMPRESSED", + }, }, { name: "add table option 3", @@ -1051,6 +1567,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t1 (id int primary key) row_format=compressed, character set=utf8, checksum=1", diff: "alter table t1 row_format COMPRESSED checksum 1", cdiff: "ALTER TABLE `t1` ROW_FORMAT COMPRESSED CHECKSUM 1", + textdiffs: []string{ + "+) ROW_FORMAT COMPRESSED", + "+ CHECKSUM 1", + }, }, { name: "modify table option 1", @@ -1058,6 +1578,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t1 (id int primary key) character set=utf8mb4", diff: "alter table t1 charset utf8mb4", cdiff: "ALTER TABLE `t1` CHARSET utf8mb4", + textdiffs: []string{ + "-) CHARSET utf8mb3", + "+) CHARSET utf8mb4", + }, }, { name: "modify table option 2", @@ -1065,6 +1589,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t1 (id int primary key) character set=utf8mb4", diff: "alter table t1 charset utf8mb4", cdiff: "ALTER TABLE `t1` CHARSET utf8mb4", + textdiffs: []string{ + "-) CHARSET utf8mb3", + "+) CHARSET utf8mb4", + }, }, { name: "modify table option 3", @@ -1072,6 +1600,10 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t1 (id int primary key) charset=utf8mb4", diff: "alter table t1 charset utf8mb4", cdiff: "ALTER TABLE `t1` CHARSET utf8mb4", + textdiffs: []string{ + "-) CHARSET utf8mb3", + "+) CHARSET utf8mb4", + }, }, { name: "modify table option 4", @@ -1079,6 +1611,12 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t1 (id int primary key) row_format=compressed, character set=utf8mb4, checksum=1", diff: "alter table t1 charset utf8mb4 row_format COMPRESSED checksum 1", cdiff: "ALTER TABLE `t1` CHARSET utf8mb4 ROW_FORMAT COMPRESSED CHECKSUM 1", + textdiffs: []string{ + "-) CHARSET utf8mb3", + "+) ROW_FORMAT COMPRESSED,", + "+ CHARSET utf8mb4,", + "+ CHECKSUM 1", + }, }, { name: "remove table option 1", @@ -1191,6 +1729,14 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t (id int, primary key(id)) COLLATE utf8mb4_0900_ai_ci", charset: TableCharsetCollateIgnoreEmpty, }, + { + name: "non empty collate with ignore empty table collate", + from: "create table t (id int, primary key(id)) COLLATE utf8mb4_0900_bin", + to: "create table t (id int, primary key(id)) COLLATE utf8mb4_0900_ai_ci", + charset: TableCharsetCollateIgnoreEmpty, + diff: "alter table t collate utf8mb4_0900_ai_ci", + cdiff: "ALTER TABLE `t` COLLATE utf8mb4_0900_ai_ci", + }, { name: "ignore empty table charset and collate in target", from: "create table t (id int, primary key(id)) DEFAULT CHARSET = utf8mb4 COLLATE utf8mb4_0900_ai_ci", @@ -1257,6 +1803,9 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t1 (id int primary key) engine=innodb, character set=utf8", diff: "alter table t1 engine InnoDB", cdiff: "ALTER TABLE `t1` ENGINE InnoDB", + textdiffs: []string{ + "+) ENGINE InnoDB", + }, }, { name: "normalized ENGINE MyISAM value", @@ -1264,6 +1813,20 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t1 (id int primary key) engine=myisam, character set=utf8", diff: "alter table t1 engine MyISAM", cdiff: "ALTER TABLE `t1` ENGINE MyISAM", + textdiffs: []string{ + "+) ENGINE MyISAM", + }, + }, + { + name: "modify ENGINE option", + from: "create table t1 (id int primary key) engine=myisam", + to: "create table t1 (id int primary key) engine=InnoDB", + diff: "alter table t1 engine InnoDB", + cdiff: "ALTER TABLE `t1` ENGINE InnoDB", + textdiffs: []string{ + "-) ENGINE MyISAM", + "+) ENGINE InnoDB", + }, }, { name: "normalized ENGINE MEMORY value", @@ -1299,6 +1862,9 @@ func TestCreateTableDiff(t *testing.T) { to: "create table t1 (id int primary key)", diff: "alter table t1 comment ''", cdiff: "ALTER TABLE `t1` COMMENT ''", + textdiffs: []string{ + "-) COMMENT 'foo'", + }, }, // expressions { @@ -1416,6 +1982,7 @@ func TestCreateTableDiff(t *testing.T) { t.Logf("c: %v", sqlparser.CanonicalString(c.CreateTable)) t.Logf("other: %v", sqlparser.CanonicalString(other.CreateTable)) } + assert.Empty(t, ts.textdiffs) return } @@ -1465,6 +2032,54 @@ func TestCreateTableDiff(t *testing.T) { applied.Create().CanonicalStatementString(), ) } + { // Validate annotations + alterEntityDiff, ok := alter.(*AlterTableEntityDiff) + require.True(t, ok) + annotatedFrom, annotatedTo, annotatedUnified := alterEntityDiff.Annotated() + annotatedFromString := annotatedFrom.Export() + annotatedToString := annotatedTo.Export() + annotatedUnifiedString := annotatedUnified.Export() + { + eFromStatementString := eFrom.Create().CanonicalStatementString() + for _, annotation := range alterEntityDiff.annotations.Removed() { + require.NotEmpty(t, annotation.text) + assert.Contains(t, eFromStatementString, annotation.text) + } + if len(alterEntityDiff.annotations.Removed()) == 0 { + assert.Empty(t, annotatedFrom.Removed()) + assert.Equal(t, eFromStatementString, annotatedFromString) + } else { + assert.NotEmpty(t, annotatedFrom.Removed()) + assert.NotEqual(t, eFromStatementString, annotatedFromString) + } + } + { + eToStatementString := eTo.Create().CanonicalStatementString() + for _, annotation := range alterEntityDiff.annotations.Added() { + require.NotEmpty(t, annotation.text) + assert.Contains(t, eToStatementString, annotation.text) + } + if len(alterEntityDiff.annotations.Added()) == 0 { + assert.Empty(t, annotatedTo.Added()) + assert.Equal(t, eToStatementString, annotatedToString) + } else { + assert.NotEmpty(t, annotatedTo.Added()) + assert.NotEqual(t, eToStatementString, annotatedToString) + } + } + if len(ts.textdiffs) > 0 { // Still incomplete. + // For this test, we should validate the given diffs + uniqueDiffs := make(map[string]bool) + for _, textdiff := range ts.textdiffs { + uniqueDiffs[textdiff] = true + } + require.Equal(t, len(uniqueDiffs), len(ts.textdiffs)) // integrity of test + for _, textdiff := range ts.textdiffs { + assert.Containsf(t, annotatedUnifiedString, textdiff, annotatedUnifiedString) + } + assert.Equalf(t, len(annotatedUnified.Removed())+len(annotatedUnified.Added()), len(ts.textdiffs), annotatedUnifiedString) + } + } } { cdiff := alter.CanonicalStatementString() diff --git a/go/vt/schemadiff/types.go b/go/vt/schemadiff/types.go index b42408376b8..e32882cc6b7 100644 --- a/go/vt/schemadiff/types.go +++ b/go/vt/schemadiff/types.go @@ -69,6 +69,8 @@ type EntityDiff interface { SetSubsequentDiff(EntityDiff) // InstantDDLCapability returns the ability of this diff to run with ALGORITHM=INSTANT InstantDDLCapability() InstantDDLCapability + + Annotated() (from *TextualAnnotations, to *TextualAnnotations, unified *TextualAnnotations) } const ( diff --git a/go/vt/schemadiff/view.go b/go/vt/schemadiff/view.go index c28be710116..823efc241ea 100644 --- a/go/vt/schemadiff/view.go +++ b/go/vt/schemadiff/view.go @@ -45,6 +45,10 @@ func (d *AlterViewEntityDiff) Entities() (from Entity, to Entity) { return d.from, d.to } +func (d *AlterViewEntityDiff) Annotated() (from *TextualAnnotations, to *TextualAnnotations, unified *TextualAnnotations) { + return annotatedDiff(d, nil) +} + // Statement implements EntityDiff func (d *AlterViewEntityDiff) Statement() sqlparser.Statement { if d == nil { @@ -118,6 +122,10 @@ func (d *CreateViewEntityDiff) Entities() (from Entity, to Entity) { return nil, &CreateViewEntity{CreateView: d.createView} } +func (d *CreateViewEntityDiff) Annotated() (from *TextualAnnotations, to *TextualAnnotations, unified *TextualAnnotations) { + return annotatedDiff(d, nil) +} + // Statement implements EntityDiff func (d *CreateViewEntityDiff) Statement() sqlparser.Statement { if d == nil { @@ -191,6 +199,10 @@ func (d *DropViewEntityDiff) Entities() (from Entity, to Entity) { return d.from, nil } +func (d *DropViewEntityDiff) Annotated() (from *TextualAnnotations, to *TextualAnnotations, unified *TextualAnnotations) { + return annotatedDiff(d, nil) +} + // Statement implements EntityDiff func (d *DropViewEntityDiff) Statement() sqlparser.Statement { if d == nil {