Repository: cloudspannerecosystem/harbourbridge
Pull request #316 (2022-06-08 09:53:45)

Addition of implicit name changes to the report
Fixes [#309 ](https://github.com/cloudspannerecosystem/harbourbridge/issues/309)

This PR ensures  
- The conversion report notifies the following implicit changes that occur in migration from the source database to Spanner.
  - Table name changes
  - Column name changes
  - Foreign key name changes
  - Index name changes
  - Selection of a column with unique constraint as a primary key in case the table doesn't has one in the source database.
  - Addition of a synthetic primary key in case a table does not has a primary key or a column with unique constraint in the source database.
 
- Tabular report of all the name changes that occur in migration from the source database to Spanner.
  - Format : | Source Table | Change | Old Name | New Name |

- Central storage of the name mappings between the source database and Spanner for -
  - Table names
  - Column names
  - Foreign key names
  - Index names
 
- The _expected_ variables in the test files have been modified to be in sync with the changes to the report.


Patch:
diff --git a/cmd/cmd.go b/cmd/cmd.go
index 8f5079c..a0c59a9 100644
--- a/cmd/cmd.go
+++ b/cmd/cmd.go
@@ -89,11 +89,11 @@ func CommandLine(ctx context.Context, driver, targetDb, dbURI string, dataOnly,
 	}
 
 	// Populate migration request id and migration type in conv object
-	conv.MigrationRequestId = "HB-" + uuid.New().String()
+	conv.Audit.MigrationRequestId = "HB-" + uuid.New().String()
 	if dataOnly {
-		conv.MigrationType = migration.MigrationData_DATA_ONLY.Enum()
+		conv.Audit.MigrationType = migration.MigrationData_DATA_ONLY.Enum()
 	} else {
-		conv.MigrationType = migration.MigrationData_SCHEMA_AND_DATA.Enum()
+		conv.Audit.MigrationType = migration.MigrationData_SCHEMA_AND_DATA.Enum()
 	}
 
 	adminClient, err := utils.NewDatabaseAdminClient(ctx)
diff --git a/cmd/common.go b/cmd/common.go
index 32ab9e1..f359156 100644
--- a/cmd/common.go
+++ b/cmd/common.go
@@ -35,7 +35,7 @@ func performSnapshotMigration(ctx context.Context, sourceProfile profiles.Source
 	}
 	dataCoversionEndTime := time.Now()
 	dataCoversionDuration := dataCoversionEndTime.Sub(dataCoversionStartTime)
-	conv.DataConversionDuration = dataCoversionDuration
+	conv.Audit.DataConversionDuration = dataCoversionDuration
 	return bw, nil
 }
 
diff --git a/cmd/data.go b/cmd/data.go
index 79a848f..4d31caa 100644
--- a/cmd/data.go
+++ b/cmd/data.go
@@ -154,8 +154,8 @@ func (cmd *DataCmd) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface
 	defer adminClient.Close()
 
 	// Populate migration request id and migration type in conv object
-	conv.MigrationRequestId = "HB-" + uuid.New().String()
-	conv.MigrationType = migration.MigrationData_DATA_ONLY.Enum()
+	conv.Audit.MigrationRequestId = "HB-" + uuid.New().String()
+	conv.Audit.MigrationType = migration.MigrationData_DATA_ONLY.Enum()
 
 	if !sourceProfile.UseTargetSchema() {
 		err = conversion.CreateOrUpdateDatabase(ctx, adminClient, dbURI, sourceProfile.Driver, targetProfile.TargetDb, conv, ioHelper.Out)
diff --git a/cmd/schema.go b/cmd/schema.go
index 36565eb..eb02082 100644
--- a/cmd/schema.go
+++ b/cmd/schema.go
@@ -121,7 +121,7 @@ func (cmd *SchemaCmd) Execute(ctx context.Context, f *flag.FlagSet, _ ...interfa
 	}
 
 	schemaCoversionEndTime := time.Now()
-	conv.SchemaConversionDuration = schemaCoversionEndTime.Sub(schemaConversionStartTime)
+	conv.Audit.SchemaConversionDuration = schemaCoversionEndTime.Sub(schemaConversionStartTime)
 	conversion.WriteSchemaFile(conv, schemaCoversionEndTime, cmd.filePrefix+schemaFile, ioHelper.Out)
 	conversion.WriteSessionFile(conv, cmd.filePrefix+sessionFile, ioHelper.Out)
 	conversion.Report(sourceProfile.Driver, nil, ioHelper.BytesRead, "", conv, cmd.filePrefix+reportFile, ioHelper.Out)
diff --git a/cmd/schema_and_data.go b/cmd/schema_and_data.go
index caa958f..4cb8ea2 100644
--- a/cmd/schema_and_data.go
+++ b/cmd/schema_and_data.go
@@ -126,11 +126,11 @@ func (cmd *SchemaAndDataCmd) Execute(ctx context.Context, f *flag.FlagSet, _ ...
 		panic(err)
 	}
 	schemaCoversionEndTime := time.Now()
-	conv.SchemaConversionDuration = schemaCoversionEndTime.Sub(schemaConversionStartTime)
+	conv.Audit.SchemaConversionDuration = schemaCoversionEndTime.Sub(schemaConversionStartTime)
 
 	// Populate migration request id and migration type in conv object
-	conv.MigrationRequestId = "HB-" + uuid.New().String()
-	conv.MigrationType = migration.MigrationData_SCHEMA_AND_DATA.Enum()
+	conv.Audit.MigrationRequestId = "HB-" + uuid.New().String()
+	conv.Audit.MigrationType = migration.MigrationData_SCHEMA_AND_DATA.Enum()
 
 	conversion.WriteSchemaFile(conv, schemaConversionStartTime, cmd.filePrefix+schemaFile, ioHelper.Out)
 	conversion.WriteSessionFile(conv, cmd.filePrefix+sessionFile, ioHelper.Out)
diff --git a/common/metrics/metrics.go b/common/metrics/metrics.go
index acdab3f..46eeeef 100644
--- a/common/metrics/metrics.go
+++ b/common/metrics/metrics.go
@@ -14,12 +14,12 @@ import (
 func GetMigrationData(conv *internal.Conv, driver, targetDb, typeOfConv string) *migration.MigrationData {
 
 	migrationData := migration.MigrationData{
-		MigrationRequestId: &conv.MigrationRequestId,
+		MigrationRequestId: &conv.Audit.MigrationRequestId,
 	}
 	if typeOfConv == constants.DataConv {
 		return &migrationData
 	}
-	migrationData.MigrationType = conv.MigrationType
+	migrationData.MigrationType = conv.Audit.MigrationType
 	migrationData.SourceConnectionMechanism, migrationData.Source = getMigrationDataSourceDetails(driver, &migrationData)
 	migrationData.SchemaPatterns = getMigrationDataSchemaPatterns(conv, &migrationData)
 
diff --git a/internal/convert.go b/internal/convert.go
index 48d14b2..7ef775b 100644
--- a/internal/convert.go
+++ b/internal/convert.go
@@ -25,26 +25,23 @@ import (
 
 // Conv contains all schema and data conversion state.
 type Conv struct {
-	mode                     mode                                // Schema mode or data mode.
-	SpSchema                 ddl.Schema                          // Maps Spanner table name to Spanner schema.
-	SyntheticPKeys           map[string]SyntheticPKey            // Maps Spanner table name to synthetic primary key (if needed).
-	SrcSchema                map[string]schema.Table             // Maps source-DB table name to schema information.
-	Issues                   map[string]map[string][]SchemaIssue // Maps source-DB table/col to list of schema conversion issues.
-	ToSpanner                map[string]NameAndCols              // Maps from source-DB table name to Spanner name and column mapping.
-	ToSource                 map[string]NameAndCols              // Maps from Spanner table name to source-DB table name and column mapping.
-	UsedNames                map[string]bool                     // Map storing the names that are already assigned to tables, indices or foreign key contraints.
-	dataSink                 func(table string, cols []string, values []interface{})
-	DataFlush                func()         `json:"-"` // Data flush is used to flush out remaining writes and wait for them to complete.
-	Location                 *time.Location // Timezone (for timestamp conversion).
-	sampleBadRows            rowSamples     // Rows that generated errors during conversion.
-	Stats                    stats
-	TimezoneOffset           string                                 // Timezone offset for timestamp conversion.
-	TargetDb                 string                                 // The target database to which HarbourBridge is writing.
-	UniquePKey               map[string][]string                    // Maps Spanner table name to unique column name being used as primary key (if needed).
-	SchemaConversionDuration time.Duration                          `json:"-"` // Duration of schema conversion.
-	DataConversionDuration   time.Duration                          `json:"-"` // Duration of data conversion.
-	MigrationRequestId       string                                 `json:"-"` // Unique request id generated per migration
-	MigrationType            *migration.MigrationData_MigrationType `json:"-"` // Type of migration: Schema migration, data migration or schema and data migration
+	mode           mode                                // Schema mode or data mode.
+	SpSchema       ddl.Schema                          // Maps Spanner table name to Spanner schema.
+	SyntheticPKeys map[string]SyntheticPKey            // Maps Spanner table name to synthetic primary key (if needed).
+	SrcSchema      map[string]schema.Table             // Maps source-DB table name to schema information.
+	Issues         map[string]map[string][]SchemaIssue // Maps source-DB table/col to list of schema conversion issues.
+	ToSpanner      map[string]NameAndCols              // Maps from source-DB table name to Spanner name and column mapping.
+	ToSource       map[string]NameAndCols              // Maps from Spanner table name to source-DB table name and column mapping.
+	UsedNames      map[string]bool                     // Map storing the names that are already assigned to tables, indices or foreign key contraints.
+	dataSink       func(table string, cols []string, values []interface{})
+	DataFlush      func()         `json:"-"` // Data flush is used to flush out remaining writes and wait for them to complete.
+	Location       *time.Location // Timezone (for timestamp conversion).
+	sampleBadRows  rowSamples     // Rows that generated errors during conversion.
+	Stats          stats
+	TimezoneOffset string              // Timezone offset for timestamp conversion.
+	TargetDb       string              // The target database to which HarbourBridge is writing.
+	UniquePKey     map[string][]string // Maps Spanner table name to unique column name being used as primary key (if needed).
+	Audit          audit               // Stores the audit information for the database conversion
 }
 
 type mode int
@@ -85,6 +82,7 @@ const (
 	Widened
 	Time
 	StringOverflow
+	IllegalName
 )
 
 // NameAndCols contains the name of a table and its columns.
@@ -94,6 +92,13 @@ type NameAndCols struct {
 	Cols map[string]string
 }
 
+//FkeyAndIdxs contains the name of a table, its foreign keys and indexes
+//Used to map between source DB and spanner table name, foreign key name and index names.
+type FkeyAndIdxs struct {
+	Name       string
+	ForeignKey map[string]string
+	Index      map[string]string
+}
 type rowSamples struct {
 	rows       []*row
 	bytes      int64 // Bytes consumed by l.
@@ -128,6 +133,17 @@ type statementStat struct {
 	Error  int64
 }
 
+// Stores the audit information of conversion.
+// Elements that do not affect the migration functionality but are relevant for the conversion report.
+type audit struct {
+	ToSpannerFkIdx           map[string]FkeyAndIdxs                 `json:"-"` // Maps from source-DB table name to Spanner names for table name, foreign key and indexes.
+	ToSourceFkIdx            map[string]FkeyAndIdxs                 `json:"-"` // Maps from Spanner table name to source-DB names for table name, foreign key and indexes.
+	SchemaConversionDuration time.Duration                          `json:"-"` // Duration of schema conversion.
+	DataConversionDuration   time.Duration                          `json:"-"` // Duration of data conversion.
+	MigrationRequestId       string                                 `json:"-"` // Unique request id generated per migration
+	MigrationType            *migration.MigrationData_MigrationType `json:"-"` // Type of migration: Schema migration, data migration or schema and data migration
+}
+
 // MakeConv returns a default-configured Conv.
 func MakeConv() *Conv {
 	return &Conv{
@@ -149,6 +165,10 @@ func MakeConv() *Conv {
 		},
 		TimezoneOffset: "+00:00", // By default, use +00:00 offset which is equal to UTC timezone
 		UniquePKey:     make(map[string][]string),
+		Audit: audit{
+			ToSpannerFkIdx: make(map[string]FkeyAndIdxs),
+			ToSourceFkIdx:  make(map[string]FkeyAndIdxs),
+		},
 	}
 }
 
diff --git a/internal/mapping.go b/internal/mapping.go
index 5bdbfc6..386176a 100644
--- a/internal/mapping.go
+++ b/internal/mapping.go
@@ -49,6 +49,8 @@ func GetSpannerTable(conv *Conv, srcTable string) (string, error) {
 	}
 	conv.ToSpanner[srcTable] = NameAndCols{Name: spTable, Cols: make(map[string]string)}
 	conv.ToSource[spTable] = NameAndCols{Name: srcTable, Cols: make(map[string]string)}
+	conv.Audit.ToSpannerFkIdx[srcTable] = FkeyAndIdxs{Name: spTable, ForeignKey: make(map[string]string), Index: make(map[string]string)}
+	conv.Audit.ToSourceFkIdx[spTable] = FkeyAndIdxs{Name: srcTable, ForeignKey: make(map[string]string), Index: make(map[string]string)}
 	return spTable, nil
 }
 
diff --git a/internal/report.go b/internal/report.go
index 34d29ac..c5ae82d 100644
--- a/internal/report.go
+++ b/internal/report.go
@@ -58,6 +58,8 @@ func GenerateReport(driverName string, conv *Conv, w *bufio.Writer, badWrites ma
 	if isDump {
 		writeStmtStats(driverName, conv, w)
 	}
+	reportNameChanges(conv, w)
+
 	if printTableReports {
 		for _, t := range reports {
 			h := fmt.Sprintf("Table %s", t.SrcTable)
@@ -76,6 +78,7 @@ func GenerateReport(driverName string, conv *Conv, w *bufio.Writer, badWrites ma
 			}
 		}
 	}
+
 	if printUnexpecteds {
 		writeUnexpectedConditions(driverName, conv, w)
 	}
@@ -172,6 +175,20 @@ func buildTableReportBody(conv *Conv, srcTable string, issues map[string][]Schem
 				l = append(l, fmt.Sprintf("UNIQUE constraint on column(s) '%s' replaced with primary key since this table didn't have one. Spanner requires a primary key for every table", strings.Join(uniquePK, ", ")))
 			}
 		}
+
+		if p.severity == note {
+			for srcKeyName, spKeyName := range conv.Audit.ToSpannerFkIdx[srcTable].ForeignKey {
+				if srcKeyName != spKeyName {
+					l = append(l, fmt.Sprintf("%s, Foreign Key '%s' is mapped to '%s'", IssueDB[IllegalName].Brief, srcKeyName, spKeyName))
+				}
+			}
+			for srcIdxName, spIdxName := range conv.Audit.ToSpannerFkIdx[srcTable].Index {
+				if srcIdxName != spIdxName {
+					l = append(l, fmt.Sprintf("%s, Index '%s' is mapped to '%s'", IssueDB[IllegalName].Brief, srcIdxName, spIdxName))
+				}
+			}
+		}
+
 		issueBatcher := make(map[SchemaIssue]bool)
 		for _, srcCol := range cols {
 			for _, i := range issues[srcCol] {
@@ -192,6 +209,9 @@ func buildTableReportBody(conv *Conv, srcTable string, issues map[string][]Schem
 				}
 				srcType := srcSchema.ColDefs[srcCol].Type.Print()
 				spType := spSchema.ColDefs[spCol].T.PrintColumnDefType()
+				srcName := srcSchema.ColDefs[srcCol].Name
+				spName := spSchema.ColDefs[spCol].Name
+
 				// A note on case: Spanner types are case insensitive, but
 				// default to upper case. In particular, the Spanner AST uses
 				// upper case, so spType is upper case. Many source DBs
@@ -215,6 +235,8 @@ func buildTableReportBody(conv *Conv, srcTable string, issues map[string][]Schem
 					l = append(l, fmt.Sprintf("Some columns have source DB type 'datetime' which is mapped to Spanner type timestamp e.g. column '%s'. %s", srcCol, IssueDB[i].Brief))
 				case Widened:
 					l = append(l, fmt.Sprintf("%s e.g. for column '%s', source DB type %s is mapped to Spanner type %s", IssueDB[i].Brief, srcCol, srcType, spType))
+				case IllegalName:
+					l = append(l, fmt.Sprintf("%s, Column '%s' is mapped to '%s'", IssueDB[i].Brief, srcName, spName))
 				default:
 					l = append(l, fmt.Sprintf("Column '%s': type %s is mapped to %s. %s", srcCol, srcType, spType, IssueDB[i].Brief))
 				}
@@ -223,6 +245,7 @@ func buildTableReportBody(conv *Conv, srcTable string, issues map[string][]Schem
 		if len(l) == 0 {
 			continue
 		}
+
 		heading := p.heading
 		if len(l) > 1 {
 			heading = heading + "s"
@@ -277,6 +300,7 @@ var IssueDB = map[SchemaIssue]struct {
 	Time:                  {Brief: "Spanner does not support time/year types", severity: note, batch: true},
 	Widened:               {Brief: "Some columns will consume more storage in Spanner", severity: note, batch: true},
 	StringOverflow:        {Brief: "String overflow issue might occur as maximum supported length in Spanner is 2621440", severity: warning},
+	IllegalName:           {Brief: "Names must adhere to the spanner regular expression {a-z|A-Z}[{a-z|A-Z|0-9|_}+]", severity: note},
 }
 
 type severity int
@@ -434,6 +458,38 @@ func IgnoredStatements(conv *Conv) (l []string) {
 	return l
 }
 
+func reportNameChanges(conv *Conv, w *bufio.Writer) {
+
+	w.WriteString("-----------------------------------------------------------------------------------------------------\n")
+	w.WriteString("Name Changes in Migration\n")
+	w.WriteString("-----------------------------------------------------------------------------------------------------\n")
+	fmt.Fprintf(w, "%25s %15s %25s %25s\n", "Source Table", "Change", "Old Name", "New Name")
+	w.WriteString("-----------------------------------------------------------------------------------------------------\n")
+
+	for srcTableName, spTable := range conv.ToSpanner {
+		if srcTableName != spTable.Name {
+			fmt.Fprintf(w, "%25s %15s %25s %25s\n", srcTableName, "Table Name", srcTableName, spTable.Name)
+		}
+		for srcColName, spColName := range spTable.Cols {
+			if srcColName != spColName {
+				fmt.Fprintf(w, "%25s %15s %25s %25s\n", srcTableName, "Column Name", srcColName, spColName)
+			}
+		}
+		for srcFkName, spFkName := range conv.Audit.ToSpannerFkIdx[srcTableName].ForeignKey {
+			if srcFkName != spFkName {
+				fmt.Fprintf(w, "%25s %15s %25s %25s\n", srcTableName, "Foreign Key", srcFkName, spFkName)
+			}
+		}
+		for srcIdxName, spIdxName := range conv.Audit.ToSpannerFkIdx[srcTableName].Index {
+			if srcIdxName != spIdxName {
+				fmt.Fprintf(w, "%25s %15s %25s %25s\n", srcTableName, "Index", srcIdxName, spIdxName)
+			}
+		}
+	}
+	w.WriteString("-----------------------------------------------------------------------------------------------------\n\n\n")
+
+}
+
 func writeStmtStats(driverName string, conv *Conv, w *bufio.Writer) {
 	type stat struct {
 		statement string
@@ -562,13 +618,13 @@ func writeHeading(w *bufio.Writer, s string) {
 
 func conversionDuration(conv *Conv, w *bufio.Writer) string {
 	res := ""
-	if conv.DataConversionDuration.Microseconds() != 0 || conv.SchemaConversionDuration.Microseconds() != 0 {
+	if conv.Audit.DataConversionDuration.Microseconds() != 0 || conv.Audit.SchemaConversionDuration.Microseconds() != 0 {
 		writeHeading(w, "Time duration of Conversion")
-		if conv.SchemaConversionDuration.Microseconds() != 0 {
-			res += fmt.Sprintf("Schema conversion duration : %s \n", conv.SchemaConversionDuration)
+		if conv.Audit.SchemaConversionDuration.Microseconds() != 0 {
+			res += fmt.Sprintf("Schema conversion duration : %s \n", conv.Audit.SchemaConversionDuration)
 		}
-		if conv.DataConversionDuration.Microseconds() != 0 {
-			res += fmt.Sprintf("Data conversion duration : %s \n", conv.DataConversionDuration)
+		if conv.Audit.DataConversionDuration.Microseconds() != 0 {
+			res += fmt.Sprintf("Data conversion duration : %s \n", conv.Audit.DataConversionDuration)
 		}
 		res += "\n"
 	}
diff --git a/sources/common/toddl.go b/sources/common/toddl.go
index e10bb43..05b4f79 100644
--- a/sources/common/toddl.go
+++ b/sources/common/toddl.go
@@ -74,6 +74,9 @@ func SchemaToSpannerDDL(conv *internal.Conv, toddl ToDdl) error {
 			if srcCol.Ignored.ForeignKey {
 				issues = append(issues, internal.ForeignKey)
 			}
+			if srcCol.Name != colName {
+				issues = append(issues, internal.IllegalName)
+			}
 			if srcCol.Ignored.Default {
 				issues = append(issues, internal.DefaultValue)
 			}
@@ -96,7 +99,7 @@ func SchemaToSpannerDDL(conv *internal.Conv, toddl ToDdl) error {
 			ColNames: spColNames,
 			ColDefs:  spColDef,
 			Pks:      cvtPrimaryKeys(conv, srcTable.Name, srcTable.PrimaryKeys),
-			Fks:      cvtForeignKeys(conv, srcTable.Name, srcTable.ForeignKeys),
+			Fks:      cvtForeignKeys(conv, spTableName, srcTable.Name, srcTable.ForeignKeys),
 			Indexes:  cvtIndexes(conv, spTableName, srcTable.Name, srcTable.Indexes),
 			Comment:  comment}
 	}
@@ -127,7 +130,7 @@ func cvtPrimaryKeys(conv *internal.Conv, srcTable string, srcKeys []schema.Key)
 	return spKeys
 }
 
-func cvtForeignKeys(conv *internal.Conv, srcTable string, srcKeys []schema.ForeignKey) []ddl.Foreignkey {
+func cvtForeignKeys(conv *internal.Conv, spTableName string, srcTable string, srcKeys []schema.ForeignKey) []ddl.Foreignkey {
 	var spKeys []ddl.Foreignkey
 	for _, key := range srcKeys {
 		if len(key.Columns) != len(key.ReferColumns) {
@@ -151,13 +154,17 @@ func cvtForeignKeys(conv *internal.Conv, srcTable string, srcKeys []schema.Forei
 			spReferCols = append(spReferCols, spReferCol)
 		}
 		spKeyName := internal.ToSpannerForeignKey(conv, key.Name)
+
 		spKey := ddl.Foreignkey{
 			Name:         spKeyName,
 			Columns:      spCols,
 			ReferTable:   spReferTable,
 			ReferColumns: spReferCols}
 		spKeys = append(spKeys, spKey)
+		conv.Audit.ToSpannerFkIdx[srcTable].ForeignKey[key.Name] = spKeyName
+		conv.Audit.ToSourceFkIdx[spTableName].ForeignKey[spKeyName] = key.Name
 	}
+
 	return spKeys
 }
 
@@ -165,6 +172,7 @@ func cvtIndexes(conv *internal.Conv, spTableName string, srcTable string, srcInd
 	var spIndexes []ddl.CreateIndex
 	for _, srcIndex := range srcIndexes {
 		var spKeys []ddl.IndexKey
+
 		for _, k := range srcIndex.Keys {
 			spCol, err := internal.GetSpannerCol(conv, srcTable, k.Column, true)
 			if err != nil {
@@ -186,6 +194,8 @@ func cvtIndexes(conv *internal.Conv, spTableName string, srcTable string, srcInd
 			Keys:   spKeys,
 		}
 		spIndexes = append(spIndexes, spIndex)
+		conv.Audit.ToSpannerFkIdx[srcTable].Index[srcIndex.Name] = spIndexName
+		conv.Audit.ToSourceFkIdx[spTableName].Index[spIndexName] = srcIndex.Name
 	}
 	return spIndexes
 }


Test patch:
diff --git a/sources/mysql/report_test.go b/sources/mysql/report_test.go
index 0f6eb9b..764c29a 100644
--- a/sources/mysql/report_test.go
+++ b/sources/mysql/report_test.go
@@ -98,6 +98,14 @@ Analysis of statements in mysqldump output, broken down by statement type.
 See https://github.com/pingcap/parser for definitions of statement types
 (pingcap/tidb/parser is the library we use for parsing mysqldump output).
 
+-----------------------------------------------------------------------------------------------------
+Name Changes in Migration
+-----------------------------------------------------------------------------------------------------
+             Source Table          Change                  Old Name                  New Name
+-----------------------------------------------------------------------------------------------------
+-----------------------------------------------------------------------------------------------------
+
+
 ----------------------------
 Table bad_schema
 ----------------------------
diff --git a/sources/postgres/report_test.go b/sources/postgres/report_test.go
index 93c54b2..588c1c0 100644
--- a/sources/postgres/report_test.go
+++ b/sources/postgres/report_test.go
@@ -93,6 +93,14 @@ Analysis of statements in pg_dump output, broken down by statement type.
 See github.com/pganalyze/pg_query_go for definitions of statement types
 (pganalyze/pg_query_go is the library we use for parsing pg_dump output).
 
+-----------------------------------------------------------------------------------------------------
+Name Changes in Migration
+-----------------------------------------------------------------------------------------------------
+             Source Table          Change                  Old Name                  New Name
+-----------------------------------------------------------------------------------------------------
+-----------------------------------------------------------------------------------------------------
+
+
 ----------------------------
 Table bad_schema
 ----------------------------