Repository: cloudspannerecosystem/harbourbridge
Pull request #357 (2022-08-04 05:15:54)

Added migrate page in HarbourBridge UI
- Added a new prepare migration page to run end to end migration from UI
- Added support for both bulk and low downtime migration
- Added respective support for direct connect, dump file and session file mode


Patch:
diff --git a/Makefile b/Makefile
index ad832ec..adb8295 100644
--- a/Makefile
+++ b/Makefile
@@ -1,9 +1,34 @@
+# Set GOPATH env variable if not set
+ifndef $(GOPATH)
+    GOPATH=$(shell go env GOPATH)
+    export GOPATH
+endif
 # Build the default binary
 build:
 	go build
 # Build a static binary
 build-static:
 	go build -a -tags osusergo,netgo -ldflags '-w -extldflags "-static"' -o harbourbridge main.go
+# Build the default binary with vendored dependencies
+build-vendor:
+	go mod tidy
+# 	vendor the dependencies
+	go mod vendor
+# 	vendor non-go files
+	go install github.com/goware/modvendor@latest
+	$(GOPATH)/bin/modvendor -copy="**/*.c **/*.h **/*.proto" -v
+# 	build the binary
+	go build
+# Build the static binary with vendored dependencies
+build-static-vendor:
+	go mod tidy
+# 	vendor the dependencies
+	go mod vendor
+# 	vendor non-go files
+	go install github.com/goware/modvendor@latest
+	$(GOPATH)/bin/modvendor -copy="**/*.c **/*.h **/*.proto" -v
+# build the binary
+	go build -a -tags osusergo,netgo -ldflags '-w -extldflags "-static"' -o harbourbridge main.go
 # Run unit tests
 test:
 	go test -v ./...
diff --git a/cmd/cmd.go b/cmd/cmd.go
index 7896e7b..dd0c4f5 100644
--- a/cmd/cmd.go
+++ b/cmd/cmd.go
@@ -38,7 +38,7 @@ var (
 	sessionFile = "session.json"
 )
 
-const defaultWritersLimit = 40
+const DefaultWritersLimit = 40
 
 // CommandLine provides the core processing for HarbourBridge when run as a command-line tool.
 // It performs the following steps:
@@ -116,7 +116,7 @@ func CommandLine(ctx context.Context, driver, targetDb, dbURI string, dataOnly,
 
 	// We pass an empty string to the sqlConnectionStr parameter as this is the legacy codepath,
 	// which reads the environment variables and constructs the string later on.
-	bw, err := conversion.DataConv(ctx, sourceProfile, targetProfile, ioHelper, client, conv, dataOnly, defaultWritersLimit)
+	bw, err := conversion.DataConv(ctx, sourceProfile, targetProfile, ioHelper, client, conv, dataOnly, DefaultWritersLimit)
 	if err != nil {
 		return fmt.Errorf("can't finish data conversion for db %s: %v", dbURI, err)
 	}
diff --git a/cmd/data.go b/cmd/data.go
index 1fb1917..592016f 100644
--- a/cmd/data.go
+++ b/cmd/data.go
@@ -44,10 +44,10 @@ type DataCmd struct {
 	targetProfile   string
 	sessionJSON     string
 	filePrefix      string // TODO: move filePrefix to global flags
-	writeLimit      int64
+	WriteLimit      int64
 	dryRun          bool
 	logLevel        string
-	skipForeignKeys bool
+	SkipForeignKeys bool
 }
 
 // Name returns the name of operation.
@@ -79,10 +79,10 @@ func (cmd *DataCmd) SetFlags(f *flag.FlagSet) {
 	f.StringVar(&cmd.target, "target", "Spanner", "Specifies the target DB, defaults to Spanner (accepted values: `Spanner`)")
 	f.StringVar(&cmd.targetProfile, "target-profile", "", "Flag for specifying connection profile for target database e.g., \"dialect=postgresql\"")
 	f.StringVar(&cmd.filePrefix, "prefix", "", "File prefix for generated files")
-	f.Int64Var(&cmd.writeLimit, "write-limit", defaultWritersLimit, "Write limit for writes to spanner")
+	f.Int64Var(&cmd.WriteLimit, "write-limit", DefaultWritersLimit, "Write limit for writes to spanner")
 	f.BoolVar(&cmd.dryRun, "dry-run", false, "Flag for generating DDL and schema conversion report without creating a spanner database")
 	f.StringVar(&cmd.logLevel, "log-level", "INFO", "Configure the logging level for the command (INFO, DEBUG), defaults to INFO")
-	f.BoolVar(&cmd.skipForeignKeys, "skip-foreign-keys", false, "Skip creating foreign keys after data migration is complete (ddl statements for foreign keys can still be found in the downloaded schema.ddl.txt file and the same can be applied separately)")
+	f.BoolVar(&cmd.SkipForeignKeys, "skip-foreign-keys", false, "Skip creating foreign keys after data migration is complete (ddl statements for foreign keys can still be found in the downloaded schema.ddl.txt file and the same can be applied separately)")
 }
 
 func (cmd *DataCmd) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
@@ -128,42 +128,19 @@ func (cmd *DataCmd) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface
 	}
 
 	var (
-		dbURI       string
-		adminClient *database.DatabaseAdminClient
-		client      *sp.Client
+		dbURI string
 	)
 	if !cmd.dryRun {
 		now := time.Now()
-		adminClient, client, dbURI, err = CreateDatabaseClient(ctx, targetProfile, sourceProfile.Driver, dbName, ioHelper)
+		bw, err = MigrateDatabase(ctx, targetProfile, sourceProfile, dbName, &ioHelper, cmd, conv)
 		if err != nil {
-			err = fmt.Errorf("can't create database client: %v", err)
+			err = fmt.Errorf("can't finish database migration for db %s: %v", dbName, err)
 			return subcommands.ExitFailure
 		}
-		defer adminClient.Close()
-		defer client.Close()
-		if !sourceProfile.UseTargetSchema() {
-			err = validateExistingDb(ctx, conv.TargetDb, dbURI, adminClient, client, conv)
-			if err != nil {
-				err = fmt.Errorf("error while validating existing database: %v", err)
-				return subcommands.ExitFailure
-			}
-		}
-		bw, err = conversion.DataConv(ctx, sourceProfile, targetProfile, &ioHelper, client, conv, true, cmd.writeLimit)
-		if err != nil {
-			err = fmt.Errorf("can't finish data conversion for db %s: %v", dbURI, err)
-			return subcommands.ExitFailure
-		}
-
-		if !cmd.skipForeignKeys {
-			if err = conversion.UpdateDDLForeignKeys(ctx, adminClient, dbURI, conv, ioHelper.Out); err != nil {
-				err = fmt.Errorf("can't perform update schema on db %s with foreign keys: %v", dbURI, err)
-				return subcommands.ExitFailure
-			}
-		}
 		banner = utils.GetBanner(now, dbURI)
 	} else {
 		conv.Audit.DryRun = true
-		bw, err = conversion.DataConv(ctx, sourceProfile, targetProfile, &ioHelper, nil, conv, true, cmd.writeLimit)
+		bw, err = conversion.DataConv(ctx, sourceProfile, targetProfile, &ioHelper, nil, conv, true, cmd.WriteLimit)
 		if err != nil {
 			err = fmt.Errorf("can't finish data conversion for db %s: %v", dbName, err)
 			return subcommands.ExitFailure
diff --git a/cmd/schema.go b/cmd/schema.go
index 0a85965..d6eb2bc 100644
--- a/cmd/schema.go
+++ b/cmd/schema.go
@@ -22,7 +22,6 @@ import (
 	"path"
 	"time"
 
-	database "cloud.google.com/go/spanner/admin/database/apiv1"
 	"github.com/cloudspannerecosystem/harbourbridge/common/constants"
 	"github.com/cloudspannerecosystem/harbourbridge/common/utils"
 	"github.com/cloudspannerecosystem/harbourbridge/conversion"
@@ -118,22 +117,10 @@ func (cmd *SchemaCmd) Execute(ctx context.Context, f *flag.FlagSet, _ ...interfa
 	conv.Audit.MigrationRequestId = "HB-" + uuid.New().String()
 	conv.Audit.MigrationType = migration.MigrationData_SCHEMA_ONLY.Enum()
 
-	var (
-		adminClient *database.DatabaseAdminClient
-		dbURI       string
-	)
-
 	if !cmd.dryRun {
-		adminClient, _, dbURI, err = CreateDatabaseClient(ctx, targetProfile, sourceProfile.Driver, dbName, ioHelper)
-		if err != nil {
-			err = fmt.Errorf("can't create database client: %v", err)
-			return subcommands.ExitFailure
-		}
-		defer adminClient.Close()
-
-		err = conversion.CreateOrUpdateDatabase(ctx, adminClient, dbURI, sourceProfile.Driver, targetProfile.TargetDb, conv, ioHelper.Out)
+		_, err = MigrateDatabase(ctx, targetProfile, sourceProfile, dbName, &ioHelper, cmd, conv)
 		if err != nil {
-			err = fmt.Errorf("can't create/update database: %v", err)
+			err = fmt.Errorf("can't finish database migration for db %s: %v", dbName, err)
 			return subcommands.ExitFailure
 		}
 	}
diff --git a/cmd/schema_and_data.go b/cmd/schema_and_data.go
index e5517e6..ffc023a 100644
--- a/cmd/schema_and_data.go
+++ b/cmd/schema_and_data.go
@@ -22,8 +22,6 @@ import (
 	"path"
 	"time"
 
-	sp "cloud.google.com/go/spanner"
-	database "cloud.google.com/go/spanner/admin/database/apiv1"
 	"github.com/cloudspannerecosystem/harbourbridge/common/constants"
 	"github.com/cloudspannerecosystem/harbourbridge/common/utils"
 	"github.com/cloudspannerecosystem/harbourbridge/conversion"
@@ -42,9 +40,9 @@ type SchemaAndDataCmd struct {
 	sourceProfile   string
 	target          string
 	targetProfile   string
-	skipForeignKeys bool
+	SkipForeignKeys bool
 	filePrefix      string // TODO: move filePrefix to global flags
-	writeLimit      int64
+	WriteLimit      int64
 	dryRun          bool
 	logLevel        string
 }
@@ -76,9 +74,9 @@ func (cmd *SchemaAndDataCmd) SetFlags(f *flag.FlagSet) {
 	f.StringVar(&cmd.sourceProfile, "source-profile", "", "Flag for specifying connection profile for source database e.g., \"file=<path>,format=dump\"")
 	f.StringVar(&cmd.target, "target", "Spanner", "Specifies the target DB, defaults to Spanner (accepted values: `Spanner`)")
 	f.StringVar(&cmd.targetProfile, "target-profile", "", "Flag for specifying connection profile for target database e.g., \"dialect=postgresql\"")
-	f.BoolVar(&cmd.skipForeignKeys, "skip-foreign-keys", false, "Skip creating foreign keys after data migration is complete (ddl statements for foreign keys can still be found in the downloaded schema.ddl.txt file and the same can be applied separately)")
+	f.BoolVar(&cmd.SkipForeignKeys, "skip-foreign-keys", false, "Skip creating foreign keys after data migration is complete (ddl statements for foreign keys can still be found in the downloaded schema.ddl.txt file and the same can be applied separately)")
 	f.StringVar(&cmd.filePrefix, "prefix", "", "File prefix for generated files")
-	f.Int64Var(&cmd.writeLimit, "write-limit", defaultWritersLimit, "Write limit for writes to spanner")
+	f.Int64Var(&cmd.WriteLimit, "write-limit", DefaultWritersLimit, "Write limit for writes to spanner")
 	f.BoolVar(&cmd.dryRun, "dry-run", false, "Flag for generating DDL and schema conversion report without creating a spanner database")
 	f.StringVar(&cmd.logLevel, "log-level", "INFO", "Configure the logging level for the command (INFO, DEBUG), defaults to INFO")
 }
@@ -112,12 +110,10 @@ func (cmd *SchemaAndDataCmd) Execute(ctx context.Context, f *flag.FlagSet, _ ...
 	}
 
 	var (
-		conv        *internal.Conv
-		bw          *writer.BatchWriter
-		banner      string
-		dbURI       string
-		adminClient *database.DatabaseAdminClient
-		client      *sp.Client
+		conv   *internal.Conv
+		bw     *writer.BatchWriter
+		banner string
+		dbURI  string
 	)
 	conv, err = conversion.SchemaConv(sourceProfile, targetProfile, &ioHelper)
 	if err != nil {
@@ -135,33 +131,11 @@ func (cmd *SchemaAndDataCmd) Execute(ctx context.Context, f *flag.FlagSet, _ ...
 
 	if !cmd.dryRun {
 		conversion.Report(sourceProfile.Driver, nil, ioHelper.BytesRead, "", conv, cmd.filePrefix+reportFile, ioHelper.Out)
-		adminClient, client, dbURI, err = CreateDatabaseClient(ctx, targetProfile, sourceProfile.Driver, dbName, ioHelper)
+		bw, err = MigrateDatabase(ctx, targetProfile, sourceProfile, dbName, &ioHelper, cmd, conv)
 		if err != nil {
-			err = fmt.Errorf("can't create database client: %v", err)
+			err = fmt.Errorf("can't finish database migration for db %s: %v", dbName, err)
 			return subcommands.ExitFailure
 		}
-		defer adminClient.Close()
-		defer client.Close()
-
-		err = conversion.CreateOrUpdateDatabase(ctx, adminClient, dbURI, sourceProfile.Driver, targetProfile.TargetDb, conv, ioHelper.Out)
-		if err != nil {
-			err = fmt.Errorf("can't create/update database: %v", err)
-			return subcommands.ExitFailure
-		}
-		schemaCoversionEndTime := time.Now()
-		conv.Audit.SchemaConversionDuration = schemaCoversionEndTime.Sub(schemaConversionStartTime)
-
-		bw, err = conversion.DataConv(ctx, sourceProfile, targetProfile, &ioHelper, client, conv, true, cmd.writeLimit)
-		if err != nil {
-			err = fmt.Errorf("can't finish data conversion for db %s: %v", dbURI, err)
-			return subcommands.ExitFailure
-		}
-		if !cmd.skipForeignKeys {
-			if err = conversion.UpdateDDLForeignKeys(ctx, adminClient, dbURI, conv, ioHelper.Out); err != nil {
-				err = fmt.Errorf("can't perform update schema on db %s with foreign keys: %v", dbURI, err)
-				return subcommands.ExitFailure
-			}
-		}
 		dataCoversionEndTime := time.Now()
 		conv.Audit.DataConversionDuration = dataCoversionEndTime.Sub(schemaCoversionEndTime)
 		banner = utils.GetBanner(schemaConversionStartTime, dbURI)
@@ -170,7 +144,7 @@ func (cmd *SchemaAndDataCmd) Execute(ctx context.Context, f *flag.FlagSet, _ ...
 		conv.Audit.DryRun = true
 		schemaCoversionEndTime := time.Now()
 		conv.Audit.SchemaConversionDuration = schemaCoversionEndTime.Sub(schemaConversionStartTime)
-		bw, err = conversion.DataConv(ctx, sourceProfile, targetProfile, &ioHelper, nil, conv, true, cmd.writeLimit)
+		bw, err = conversion.DataConv(ctx, sourceProfile, targetProfile, &ioHelper, nil, conv, true, cmd.WriteLimit)
 		if err != nil {
 			err = fmt.Errorf("can't finish data conversion for db %s: %v", dbName, err)
 			return subcommands.ExitFailure
diff --git a/cmd/utils.go b/cmd/utils.go
index 143de93..ef17178 100644
--- a/cmd/utils.go
+++ b/cmd/utils.go
@@ -22,7 +22,10 @@ import (
 	sp "cloud.google.com/go/spanner"
 	database "cloud.google.com/go/spanner/admin/database/apiv1"
 	"github.com/cloudspannerecosystem/harbourbridge/common/utils"
+	"github.com/cloudspannerecosystem/harbourbridge/conversion"
+	"github.com/cloudspannerecosystem/harbourbridge/internal"
 	"github.com/cloudspannerecosystem/harbourbridge/profiles"
+	"github.com/cloudspannerecosystem/harbourbridge/spanner/writer"
 )
 
 // CreateDatabaseClient creates new database client and admin client.
@@ -54,20 +57,20 @@ func CreateDatabaseClient(ctx context.Context, targetProfile profiles.TargetProf
 
 // PrepareMigrationPrerequisites creates source and target profiles, opens a new IOStream and generates the database name.
 func PrepareMigrationPrerequisites(sourceProfileString, targetProfileString, source string) (profiles.SourceProfile, profiles.TargetProfile, utils.IOStreams, string, error) {
-	sourceProfile, err := profiles.NewSourceProfile(sourceProfileString, source)
+	targetProfile, err := profiles.NewTargetProfile(targetProfileString)
 	if err != nil {
 		return profiles.SourceProfile{}, profiles.TargetProfile{}, utils.IOStreams{}, "", err
 	}
-	sourceProfile.Driver, err = sourceProfile.ToLegacyDriver(source)
+	targetProfile.TargetDb = targetProfile.ToLegacyTargetDb()
+
+	sourceProfile, err := profiles.NewSourceProfile(sourceProfileString, source)
 	if err != nil {
-		return profiles.SourceProfile{}, profiles.TargetProfile{}, utils.IOStreams{}, "", err
+		return profiles.SourceProfile{}, targetProfile, utils.IOStreams{}, "", err
 	}
-
-	targetProfile, err := profiles.NewTargetProfile(targetProfileString)
+	sourceProfile.Driver, err = sourceProfile.ToLegacyDriver(source)
 	if err != nil {
-		return sourceProfile, profiles.TargetProfile{}, utils.IOStreams{}, "", err
+		return profiles.SourceProfile{}, targetProfile, utils.IOStreams{}, "", err
 	}
-	targetProfile.TargetDb = targetProfile.ToLegacyTargetDb()
 
 	dumpFilePath := ""
 	if sourceProfile.Ty == profiles.SourceProfileTypeFile && (sourceProfile.File.Format == "" || sourceProfile.File.Format == "dump") {
@@ -85,3 +88,86 @@ func PrepareMigrationPrerequisites(sourceProfileString, targetProfileString, sou
 	}
 	return sourceProfile, targetProfile, ioHelper, dbName, nil
 }
+
+// MigrateData creates database and populates data in it.
+func MigrateDatabase(ctx context.Context, targetProfile profiles.TargetProfile, sourceProfile profiles.SourceProfile, dbName string, ioHelper *utils.IOStreams, cmd interface{}, conv *internal.Conv) (*writer.BatchWriter, error) {
+	var bw *writer.BatchWriter
+	adminClient, client, dbURI, err := CreateDatabaseClient(ctx, targetProfile, sourceProfile.Driver, dbName, *ioHelper)
+	if err != nil {
+		err = fmt.Errorf("can't create database client: %v", err)
+		return nil, err
+	}
+	defer adminClient.Close()
+	defer client.Close()
+	switch v := cmd.(type) {
+	case *SchemaCmd:
+		err = migrateSchema(ctx, targetProfile, sourceProfile, ioHelper, conv, dbURI, adminClient)
+	case *DataCmd:
+		bw, err = migrateData(ctx, targetProfile, sourceProfile, ioHelper, conv, dbURI, adminClient, client, v)
+	case *SchemaAndDataCmd:
+		bw, err = migrateSchemaAndData(ctx, targetProfile, sourceProfile, ioHelper, conv, dbURI, adminClient, client, v)
+	}
+	if err != nil {
+		err = fmt.Errorf("can't migrate database: %v", err)
+		return nil, err
+	}
+	return bw, nil
+}
+
+func migrateSchema(ctx context.Context, targetProfile profiles.TargetProfile, sourceProfile profiles.SourceProfile,
+	ioHelper *utils.IOStreams, conv *internal.Conv, dbURI string, adminClient *database.DatabaseAdminClient) error {
+	err := conversion.CreateOrUpdateDatabase(ctx, adminClient, dbURI, sourceProfile.Driver, targetProfile.TargetDb, conv, ioHelper.Out)
+	if err != nil {
+		err = fmt.Errorf("can't create/update database: %v", err)
+		return err
+	}
+	return nil
+}
+
+func migrateData(ctx context.Context, targetProfile profiles.TargetProfile, sourceProfile profiles.SourceProfile,
+	ioHelper *utils.IOStreams, conv *internal.Conv, dbURI string, adminClient *database.DatabaseAdminClient, client *sp.Client, cmd *DataCmd) (*writer.BatchWriter, error) {
+	var (
+		bw  *writer.BatchWriter
+		err error
+	)
+	if !sourceProfile.UseTargetSchema() {
+		err = validateExistingDb(ctx, conv.TargetDb, dbURI, adminClient, client, conv)
+		if err != nil {
+			err = fmt.Errorf("error while validating existing database: %v", err)
+			return nil, err
+		}
+	}
+	bw, err = conversion.DataConv(ctx, sourceProfile, targetProfile, ioHelper, client, conv, true, cmd.WriteLimit)
+	if err != nil {
+		err = fmt.Errorf("can't finish data conversion for db %s: %v", dbURI, err)
+		return nil, err
+	}
+	if !cmd.SkipForeignKeys {
+		if err = conversion.UpdateDDLForeignKeys(ctx, adminClient, dbURI, conv, ioHelper.Out); err != nil {
+			err = fmt.Errorf("can't perform update schema on db %s with foreign keys: %v", dbURI, err)
+			return bw, err
+		}
+	}
+	return bw, nil
+}
+
+func migrateSchemaAndData(ctx context.Context, targetProfile profiles.TargetProfile, sourceProfile profiles.SourceProfile,
+	ioHelper *utils.IOStreams, conv *internal.Conv, dbURI string, adminClient *database.DatabaseAdminClient, client *sp.Client, cmd *SchemaAndDataCmd) (*writer.BatchWriter, error) {
+	err := conversion.CreateOrUpdateDatabase(ctx, adminClient, dbURI, sourceProfile.Driver, targetProfile.TargetDb, conv, ioHelper.Out)
+	if err != nil {
+		err = fmt.Errorf("can't create/update database: %v", err)
+		return nil, err
+	}
+	bw, err := conversion.DataConv(ctx, sourceProfile, targetProfile, ioHelper, client, conv, true, cmd.WriteLimit)
+	if err != nil {
+		err = fmt.Errorf("can't finish data conversion for db %s: %v", dbURI, err)
+		return nil, err
+	}
+	if !cmd.SkipForeignKeys {
+		if err = conversion.UpdateDDLForeignKeys(ctx, adminClient, dbURI, conv, ioHelper.Out); err != nil {
+			err = fmt.Errorf("can't perform update schema on db %s with foreign keys: %v", dbURI, err)
+			return bw, err
+		}
+	}
+	return bw, nil
+}
diff --git a/go.mod b/go.mod
index 8f5eb59..6b7123f 100644
--- a/go.mod
+++ b/go.mod
@@ -14,7 +14,6 @@ require (
 	github.com/aws/aws-sdk-go v1.35.3
 	github.com/basgys/goxml2json v1.1.0
 	github.com/bitly/go-simplejson v0.5.0 // indirect
-	github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
 	github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe // indirect
 	github.com/cncf/xds/go v0.0.0-20220121163655-4a2b9fdd466b // indirect
 	github.com/denisenkom/go-mssqldb v0.11.0
diff --git a/go.sum b/go.sum
index ddd8a83..4a52ff4 100644
--- a/go.sum
+++ b/go.sum
@@ -84,7 +84,6 @@ github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q
 github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
 github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM=
 github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
-github.com/Jeffail/gabs/v2 v2.5.1 h1:ANfZYjpMlfTTKebycu4X1AgkVWumFVDYQl7JwOr4mDk=
 github.com/Jeffail/gabs/v2 v2.5.1/go.mod h1:xCn81vdHKxFUuWWAaD5jCTQDNPBMh5pPs9IJ+NcziBI=
 github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
 github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM=
@@ -104,7 +103,6 @@ github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk5
 github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/apache/thrift v0.0.0-20181112125854-24918abba929/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
-github.com/apache/thrift v0.13.1-0.20201008052519-daf620915714 h1:Jz3KVLYY5+JO7rDiX0sAuRGtuv2vG01r17Y9nLMWNUw=
 github.com/apache/thrift v0.13.1-0.20201008052519-daf620915714/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
@@ -128,11 +126,7 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
 github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
 github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
 github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
-github.com/blacktear23/go-proxyprotocol v0.0.0-20180807104634-af7a81e8dd0d h1:rQlvB2AYWme2bIB18r/SipGiMEVJYE9U0z+MGoU/LtQ=
 github.com/blacktear23/go-proxyprotocol v0.0.0-20180807104634-af7a81e8dd0d/go.mod h1:VKt7CNAQxpFpSDz3sXyj9hY/GbVsQCr0sB3w59nE7lU=
-github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
-github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
-github.com/carlmjohnson/flagext v0.21.0 h1:/c4uK3ie786Z7caXLcIMvePNSSiH3bQVGDvmGLMme60=
 github.com/carlmjohnson/flagext v0.21.0/go.mod h1:Eenv0epIUAr4NuedNmkzI8WmBmjIxZC239XcKxYS2ac=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/census-instrumentation/opencensus-proto v0.3.0 h1:t/LhUZLVitR1Ow2YOnduCsavhwFUklBMoGVYUCqmCqk=
@@ -165,18 +159,13 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH
 github.com/cncf/xds/go v0.0.0-20220121163655-4a2b9fdd466b h1:+CVhWLkTEEGdjn4cRvVCk6epN2T8eVyrpE/s1U1Y/Cg=
 github.com/cncf/xds/go v0.0.0-20220121163655-4a2b9fdd466b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo=
-github.com/cockroachdb/datadriven v1.0.0 h1:uhZrAfEayBecH2w2tZmhe20HJ7hDvrrA4x2Bg9YdZKM=
 github.com/cockroachdb/datadriven v1.0.0/go.mod h1:5Ib8Meh+jk1RlHIXej6Pzevx/NLlNvQB9pmSBZErGA4=
 github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA=
 github.com/cockroachdb/errors v1.6.1/go.mod h1:tm6FTP5G81vwJ5lC0SizQo374JNCOPrHyXGitRJoDqM=
-github.com/cockroachdb/errors v1.8.1 h1:A5+txlVZfOqFBDa4mGz2bUWSp0aHElvHX2bKkdbQu+Y=
 github.com/cockroachdb/errors v1.8.1/go.mod h1:qGwQn6JmZ+oMjuLwjWzUNqblqk0xl4CVV3SQbGwK7Ac=
-github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f h1:o/kfcElHqOiXqcou5a3rIlMc7oJbMQkeLk0VQJ7zgqY=
 github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
 github.com/cockroachdb/pebble v0.0.0-20210719141320-8c3bd06debb5/go.mod h1:JXfQr3d+XO4bL1pxGwKKo09xylQSdZ/mpZ9b2wfVcPs=
-github.com/cockroachdb/redact v1.0.8 h1:8QG/764wK+vmEYoOlfobpe12EQcS81ukx/a4hdVMxNw=
 github.com/cockroachdb/redact v1.0.8/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
-github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2 h1:IKgmqgMQlVJIZj19CdocBeSfSaiCbEBZGKODaixqtHM=
 github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2/go.mod h1:8BT+cPK6xvFOcRlk0R8eg+OTkcqI6baNH4xAkpiYVvQ=
 github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
 github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
@@ -229,7 +218,6 @@ github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WA
 github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
-github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
 github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
 github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
 github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
@@ -265,15 +253,12 @@ github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw
 github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA=
 github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
 github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
-github.com/form3tech-oss/jwt-go v3.2.5+incompatible h1:/l4kBbb4/vGSsdtB5nUe8L7B9mImVMaBPw9L/0TBHU8=
 github.com/form3tech-oss/jwt-go v3.2.5+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
 github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
 github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
-github.com/fsouza/fake-gcs-server v1.19.0 h1:XyaGOlqo+R5sjT03x2ymk0xepaQlgwhRLTT2IopW0zA=
 github.com/fsouza/fake-gcs-server v1.19.0/go.mod h1:JtXHY/QzHhtyIxsNfIuQ+XgHtRb5B/w8nqbL5O8zqo0=
 github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASxc7x3E=
 github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
@@ -283,7 +268,6 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
 github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
 github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
 github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
-github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
 github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -337,7 +321,6 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
 github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
 github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
 github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
-github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
 github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
 github.com/golang/protobuf v0.0.0-20180814211427-aa810b61a9c7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -431,17 +414,14 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7
 github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
 github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
 github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
-github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
 github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE=
 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
-github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 github.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c=
-github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
 github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
@@ -493,12 +473,9 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
-github.com/joho/sqltocsv v0.0.0-20210428211105-a6d6801d59df h1:Zrb0IbuLOGHL7nrO2WrcuNWgDTlzFv3zY69QMx4ggQE=
 github.com/joho/sqltocsv v0.0.0-20210428211105-a6d6801d59df/go.mod h1:mAVCUAYtW9NG31eB30umMSLKcDt6mCUWSjoSn5qBh0k=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
-github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
 github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
-github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@@ -514,7 +491,6 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
 github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
 github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
 github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
-github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
 github.com/kataras/golog v0.0.9/go.mod h1:12HJgwBIZFNGL0EJnMRhmvGA0PQGx8VFwrZtM4CqbAk=
 github.com/kataras/iris/v12 v12.0.1/go.mod h1:udK4vLQKkdDqMGJJVd/msuMtN6hpYJhg/lSzuxjhO+U=
 github.com/kataras/neffos v0.0.10/go.mod h1:ZYmJC07hQPW67eKuzlfY7SO3bC0mw83A3j6im82hfqw=
@@ -572,8 +548,6 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
 github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxmAOow=
 github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
-github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
-github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
@@ -582,8 +556,6 @@ github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQ
 github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
 github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
-github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
-github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
 github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
@@ -600,7 +572,6 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
 github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
 github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM=
 github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4=
@@ -614,7 +585,6 @@ github.com/ngaut/sync2 v0.0.0-20141008032647-7a24ed77b2ef h1:K0Fn+DoFqNqktdZtdV3
 github.com/ngaut/sync2 v0.0.0-20141008032647-7a24ed77b2ef/go.mod h1:7WjlapSfwQyo6LNmIvEWzsW1hbBQfpUO4JWnuQRmva8=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
-github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
 github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
@@ -622,14 +592,11 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
 github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
 github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
-github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
 github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
-github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ=
 github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
 github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
-github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
 github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
 github.com/opentracing/basictracer-go v1.0.0 h1:YyUAhaEfjoWXclZVJ9sGoNct7j4TVk7lZWlQw5UXuoo=
 github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
@@ -659,7 +626,6 @@ github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c/go.mod h1:X2r9ue
 github.com/pingcap/failpoint v0.0.0-20210918120811-547c13e3eb00/go.mod h1:4qGtCB0QK0wBzKtFEGDhxXnSnbQApw1gc9siScUl8ew=
 github.com/pingcap/failpoint v0.0.0-20220303073211-00fea37feb66 h1:v+/0tBl7umw+ZxfTeAId/R9pmb8fn2MqsrTHAs8qFAk=
 github.com/pingcap/failpoint v0.0.0-20220303073211-00fea37feb66/go.mod h1:4qGtCB0QK0wBzKtFEGDhxXnSnbQApw1gc9siScUl8ew=
-github.com/pingcap/fn v0.0.0-20200306044125-d5540d389059 h1:Pe2LbxRmbTfAoKJ65bZLmhahmvHm7n9DUxGRQT00208=
 github.com/pingcap/fn v0.0.0-20200306044125-d5540d389059/go.mod h1:fMRU1BA1y+r89AxUoaAar4JjrhUkVDt0o0Np6V8XbDQ=
 github.com/pingcap/goleveldb v0.0.0-20191226122134-f82aafb29989 h1:surzm05a8C9dN8dIUmo4Be2+pMRb6f55i+UIYrluu2E=
 github.com/pingcap/goleveldb v0.0.0-20191226122134-f82aafb29989/go.mod h1:O17XtbryoCJhkKGbT62+L2OlrniwqiGLSqrmdHCMzZw=
@@ -743,11 +709,9 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg
 github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 github.com/shirou/gopsutil/v3 v3.21.12 h1:VoGxEW2hpmz0Vt3wUvHIl9fquzYLNpVpgNNB7pGJimA=
 github.com/shirou/gopsutil/v3 v3.21.12/go.mod h1:BToYZVTlSVlfazpDDYFnsVZLaoRG+g8ufT6fPQLdJzA=
-github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk=
 github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
 github.com/shurcooL/httpgzip v0.0.0-20190720172056-320755c1c1b0/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
-github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 h1:pXY9qYc/MP5zdvqWEUH6SjNiu7VhSjuVFTFiTcphaLU=
 github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
 github.com/sijms/go-ora/v2 v2.2.17 h1:7w1lkgxorhhx/xG5fS/hWhLqBw9BrSFxTvx9oBj0Z0E=
 github.com/sijms/go-ora/v2 v2.2.17/go.mod h1:jzfAFD+4CXHE+LjGWFl6cPrtiIpQVxakI2gvrMF2w6Y=
@@ -755,12 +719,10 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
 github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
-github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
 github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
-github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
 github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
@@ -788,7 +750,6 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
-github.com/tiancaiamao/appdash v0.0.0-20181126055449-889f96f722a2 h1:mbAskLJ0oJfDRtkanvQPiooDH8HvJ2FBh+iKT/OmiQQ=
 github.com/tiancaiamao/appdash v0.0.0-20181126055449-889f96f722a2/go.mod h1:2PfKggNGDuadAa0LElHrByyrz4JPZ9fFx6Gs7nx7ZZU=
 github.com/tikv/client-go/v2 v2.0.1-0.20220406091203-f73ec0e675f4 h1:bi/tuV42dQCu7TTTOwHQW6cHVrV1fhet+Hzo5CUODBQ=
 github.com/tikv/client-go/v2 v2.0.1-0.20220406091203-f73ec0e675f4/go.mod h1:0scaG+seu7L56apm+Gjz9vckyO7ABIzM6T7n00mrIXs=
@@ -799,7 +760,6 @@ github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ
 github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=
 github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/twmb/murmur3 v1.1.3 h1:D83U0XYKcHRYwYIpBKf3Pks91Z0Byda/9SJ8B6EMRcA=
 github.com/twmb/murmur3 v1.1.3/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
@@ -821,13 +781,10 @@ github.com/xdg/stringprep v1.0.3/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0
 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
-github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/xitongsys/parquet-go v1.5.1/go.mod h1:xUxwM8ELydxh4edHGegYq1pA8NnMKDx0K/GyB0o2bww=
-github.com/xitongsys/parquet-go v1.5.5-0.20201110004701-b09c49d6d457 h1:tBbuFCtyJNKT+BFAv6qjvTFpVdy97IYNaBwGUXifIUs=
 github.com/xitongsys/parquet-go v1.5.5-0.20201110004701-b09c49d6d457/go.mod h1:pheqtXeHQFzxJk45lRQ0UIGIivKnLXvialZSFWs81A8=
 github.com/xitongsys/parquet-go-source v0.0.0-20190524061010-2b72cbee77d5/go.mod h1:xxCx7Wpym/3QCo6JhujJX51dzSXrwmb0oH6FQb39SEA=
-github.com/xitongsys/parquet-go-source v0.0.0-20200817004010-026bad9b25d0 h1:a742S4V5A15F93smuVxA60LQWsrCnN8bKeWDBARU1/k=
 github.com/xitongsys/parquet-go-source v0.0.0-20200817004010-026bad9b25d0/go.mod h1:HYhIKsdns7xz80OgkbgJYrtQY7FjHWHKH6cvN7+czGE=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
@@ -843,24 +800,18 @@ github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
 github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
 github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
-go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
 go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
 go.etcd.io/etcd/api/v3 v3.5.2 h1:tXok5yLlKyuQ/SXSjtqHc4uzNaMqZi2XsoSPr/LlJXI=
 go.etcd.io/etcd/api/v3 v3.5.2/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
 go.etcd.io/etcd/client/pkg/v3 v3.5.2 h1:4hzqQ6hIb3blLyQ8usCU4h3NghkqcsohEQ3o3VetYxE=
 go.etcd.io/etcd/client/pkg/v3 v3.5.2/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
-go.etcd.io/etcd/client/v2 v2.305.2 h1:ymrVwTkefuqA/rPkSW7/B4ApijbPVefRumkY+stNfS0=
 go.etcd.io/etcd/client/v2 v2.305.2/go.mod h1:2D7ZejHVMIfog1221iLSYlQRzrtECw3kz4I4VAQm3qI=
 go.etcd.io/etcd/client/v3 v3.5.2 h1:WdnejrUtQC4nCxK0/dLTMqKOB+U5TP/2Ya0BJL+1otA=
 go.etcd.io/etcd/client/v3 v3.5.2/go.mod h1:kOOaWFFgHygyT0WlSmL8TJiXmMysO/nNUlEsSsN6W4o=
 go.etcd.io/etcd/etcdutl/v3 v3.5.2/go.mod h1:f+KEUNxRzqQGq1Y/SsaDN5cmlOGRWgfE3lXEDi5F1Ys=
-go.etcd.io/etcd/pkg/v3 v3.5.2 h1:YZUojdoPhOyl5QILYnR8LTUbbNefu/sV4ma+ZMr2tto=
 go.etcd.io/etcd/pkg/v3 v3.5.2/go.mod h1:zsXz+9D/kijzRiG/UnFGDTyHKcVp0orwiO8iMLAi+k0=
-go.etcd.io/etcd/raft/v3 v3.5.2 h1:uCC37qOXqBvKqTGHGyhASsaCsnTuJugl1GvneJNwHWo=
 go.etcd.io/etcd/raft/v3 v3.5.2/go.mod h1:G6pCP1sFgbjod7/KnEHY0vHUViqxjkdt6AiKsD0GRr8=
-go.etcd.io/etcd/server/v3 v3.5.2 h1:B6ytJvS4Fmt8nkjzS2/8POf4tuPhFMluE0lWd4dx/7U=
 go.etcd.io/etcd/server/v3 v3.5.2/go.mod h1:mlG8znIEz4N/28GABrohZCBM11FqgGVQcpbcyJgh0j0=
-go.etcd.io/etcd/tests/v3 v3.5.2 h1:uk7/uMGVebpBDl+roivowHt6gJ5Fnqwik3syDkoSKdo=
 go.etcd.io/etcd/tests/v3 v3.5.2/go.mod h1:Jdzbei4uFi9C3xDBfCwckRXjlX0UPooiP4g/zXgBMgQ=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
@@ -870,27 +821,16 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
 go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
 go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
-go.opentelemetry.io/contrib v0.20.0 h1:ubFQUn0VCZ0gPwIoJfBJVpeBlyRMxu8Mm/huKWYd9p0=
 go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0 h1:sO4WKdPAudZGKPcpZT4MJn6JaDmpyLrMPDGGyA1SttE=
 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E=
-go.opentelemetry.io/otel v0.20.0 h1:eaP0Fqu7SXHwvjiqDq83zImeehOHX8doTvU9AwXON8g=
 go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo=
-go.opentelemetry.io/otel/exporters/otlp v0.20.0 h1:PTNgq9MRmQqqJY0REVbZFvwkYOA85vbdQU/nVfxDyqg=
 go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM=
-go.opentelemetry.io/otel/metric v0.20.0 h1:4kzhXFP+btKm4jwxpjIqjs41A7MakRFUS86bqLHTIw8=
 go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU=
-go.opentelemetry.io/otel/oteltest v0.20.0 h1:HiITxCawalo5vQzdHfKeZurV8x7ljcqAgiWzF6Vaeaw=
 go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw=
-go.opentelemetry.io/otel/sdk v0.20.0 h1:JsxtGXd06J8jrnya7fdI/U/MR6yXA5DtbZy+qoHQlr8=
 go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc=
-go.opentelemetry.io/otel/sdk/export/metric v0.20.0 h1:c5VRjxCXdQlx1HjzwGdQHzZaVI82b5EbBgOu2ljD92g=
 go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE=
-go.opentelemetry.io/otel/sdk/metric v0.20.0 h1:7ao1wpzHRVKf0OQ7GIxiQJA6X7DLX9o14gmVon7mMK8=
 go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE=
-go.opentelemetry.io/otel/trace v0.20.0 h1:1DL6EXUdcg95gukhuRRvLDO/4X5THh/5dIV52lqtnbw=
 go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw=
-go.opentelemetry.io/proto/otlp v0.7.0 h1:rwOQPCuKAKmwGKq2aVNnYIibI6wnV7EvzgfTCzcdGg8=
 go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@@ -938,7 +878,6 @@ golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPh
 golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE=
 golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -955,7 +894,6 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/exp v0.0.0-20200513190911-00229845015e h1:rMqLP+9XLy+LdbCXHjJHAmTfXCr93W7oruWA6Hq1Alc=
 golang.org/x/exp v0.0.0-20200513190911-00229845015e/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
 golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
@@ -971,7 +909,6 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu
 golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
 golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
@@ -986,7 +923,6 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
 golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
-golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
 golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1265,9 +1201,7 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
-gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM=
 gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
-gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc=
 gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
 gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
@@ -1468,7 +1402,6 @@ gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3M
 gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
 gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -1495,7 +1428,6 @@ modernc.org/golex v1.0.1/go.mod h1:QCA53QtsT1NdGkaZZkF5ezFwk4IXh4BGNafAARTC254=
 modernc.org/lex v1.0.0/go.mod h1:G6rxMTy3cH2iA0iXL/HRRv4Znu8MK4higxph/lE7ypk=
 modernc.org/lexer v1.0.0/go.mod h1:F/Dld0YKYdZCLQ7bD0USbWL4YKCyTDRDHiDTOs0q0vk=
 modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
-modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8=
 modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
 modernc.org/parser v1.0.0/go.mod h1:H20AntYJ2cHHL6MHthJ8LZzXCdDCHMWt1KZXtIMjejA=
 modernc.org/parser v1.0.2/go.mod h1:TXNq3HABP3HMaqLK7brD1fLA/LfN0KS6JxZn71QdDqs=
@@ -1508,9 +1440,7 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8
 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
-sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
 sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
 sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0 h1:ucqkfpjg9WzSUubAO62csmucvxl4/JeW3F4I4909XkM=
 sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
-sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67 h1:e1sMhtVq9AfcEy8AXNb8eSg6gbzfdpYhoNqnPJa+GzI=
 sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67/go.mod h1:L5q+DGLGOQFpo1snNEkLOJT2d1YTW66rWNzatr3He1k=
diff --git a/internal/convert.go b/internal/convert.go
index c62b766..7ed555c 100644
--- a/internal/convert.go
+++ b/internal/convert.go
@@ -86,6 +86,9 @@ const (
 	StringOverflow
 	HotspotTimestamp
 	HotspotAutoIncrement
+	RedundantIndex
+	AutoIncrementIndex
+	InterleaveIndex
 	InterleavedNotInOrder
 	InterleavedOrder
 	InterleavedAddColumn
diff --git a/internal/report.go b/internal/report.go
index 215445a..a33a045 100644
--- a/internal/report.go
+++ b/internal/report.go
@@ -406,6 +406,27 @@ func buildTableReportBody(conv *Conv, srcTable string, issues map[string][]Schem
 						l = append(l, str)
 					}
 
+				case RedundantIndex:
+					str := fmt.Sprintf(" %s for Table %s and Column  %s", IssueDB[i].Brief, spSchema.Name, srcCol)
+
+					if !contains(l, str) {
+						l = append(l, str)
+					}
+
+				case AutoIncrementIndex:
+					str := fmt.Sprintf(" %s for Table %s and Column  %s", IssueDB[i].Brief, spSchema.Name, srcCol)
+
+					if !contains(l, str) {
+						l = append(l, str)
+					}
+
+				case InterleaveIndex:
+					str := fmt.Sprintf("Column %s of Table %s %s", srcCol, spSchema.Name, IssueDB[i].Brief)
+
+					if !contains(l, str) {
+						l = append(l, str)
+					}
+
 				case IllegalName:
 					l = append(l, fmt.Sprintf("%s, Column '%s' is mapped to '%s'", IssueDB[i].Brief, srcName, spName))
 				default:
@@ -461,20 +482,23 @@ var IssueDB = map[SchemaIssue]struct {
 	MultiDimensionalArray: {Brief: "Spanner doesn't support multi-dimensional arrays", severity: warning},
 	NoGoodType:            {Brief: "No appropriate Spanner type", severity: warning},
 	Numeric:               {Brief: "Spanner does not support numeric. This type mapping could lose precision and is not recommended for production use", severity: warning},
-	NumericThatFits:       {Brief: "Spanner does not support numeric, but this type mapping preserves the numeric's specified precision", severity: note},
+	NumericThatFits:       {Brief: "Spanner does not support numeric, but this type mapping preserves the numeric's specified precision", severity: suggestion},
 	Decimal:               {Brief: "Spanner does not support decimal. This type mapping could lose precision and is not recommended for production use", severity: warning},
-	DecimalThatFits:       {Brief: "Spanner does not support decimal, but this type mapping preserves the decimal's specified precision", severity: note},
+	DecimalThatFits:       {Brief: "Spanner does not support decimal, but this type mapping preserves the decimal's specified precision", severity: suggestion},
 	Serial:                {Brief: "Spanner does not support autoincrementing types", severity: warning},
 	AutoIncrement:         {Brief: "Spanner does not support auto_increment attribute", severity: warning},
-	Timestamp:             {Brief: "Spanner timestamp is closer to PostgreSQL timestamptz", severity: note, batch: true},
-	Datetime:              {Brief: "Spanner timestamp is closer to MySQL timestamp", severity: note, batch: true},
+	Timestamp:             {Brief: "Spanner timestamp is closer to PostgreSQL timestamptz", severity: suggestion, batch: true},
+	Datetime:              {Brief: "Spanner timestamp is closer to MySQL timestamp", severity: suggestion, batch: true},
 	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},
 	HotspotTimestamp:      {Brief: "Timestamp Hotspot Occured", severity: note},
-	HotspotAutoIncrement:  {Brief: "Autoincrement Hotspot Occured", severity: note},
-	InterleavedNotInOrder: {Brief: "Can be converted to interleaved table if primary key order parameter is changed for the table", severity: note},
-	InterleavedOrder:      {Brief: "Can be converted to Interleaved Table", severity: note},
+	HotspotAutoIncrement:  {Brief: "Autoincrement Hotspot Occured", severity: warning},
+	InterleavedOrder:      {Brief: "can be converted as Interleaved Table", severity: suggestion},
+	RedundantIndex:        {Brief: "Redundant Index", severity: warning},
+	AutoIncrementIndex:    {Brief: "Auto increment column in Index can create a Hotspot", severity: warning},
+	InterleaveIndex:       {Brief: "can be converted to an Interleave Index", severity: suggestion},
+	InterleavedNotInOrder: {Brief: "Can be converted to interleaved table if primary key order parameter is changed for the table", severity: suggestion},
 	InterleavedAddColumn:  {Brief: "Candidate for Interleaved Table", severity: note},
 	IllegalName:           {Brief: "Names must adhere to the spanner regular expression {a-z|A-Z}[{a-z|A-Z|0-9|_}+]", severity: note},
 }
diff --git a/schema/schema.go b/schema/schema.go
index 7a08cc9..511839e 100644
--- a/schema/schema.go
+++ b/schema/schema.go
@@ -89,10 +89,11 @@ type Key struct {
 // to handle lots of cases for the same concept. Our choice of an index representation for unique is largely
 // motivated by the fact that databases typically implement UNIQUE via an index.
 type Index struct {
-	Name   string
-	Unique bool
-	Keys   []Key
-	Id     string
+	Name          string
+	Unique        bool
+	Keys          []Key
+	Id            string
+	StoredColumns []string
 }
 
 // Type represents the type of a column.
diff --git a/sources/common/toddl.go b/sources/common/toddl.go
index 05b4f79..f6850f1 100644
--- a/sources/common/toddl.go
+++ b/sources/common/toddl.go
@@ -172,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
+		var spStoredColumns []string
 
 		for _, k := range srcIndex.Keys {
 			spCol, err := internal.GetSpannerCol(conv, srcTable, k.Column, true)
@@ -181,6 +182,14 @@ func cvtIndexes(conv *internal.Conv, spTableName string, srcTable string, srcInd
 			}
 			spKeys = append(spKeys, ddl.IndexKey{Col: spCol, Desc: k.Desc})
 		}
+		for _, k := range srcIndex.StoredColumns {
+			spCol, err := internal.GetSpannerCol(conv, srcTable, k, true)
+			if err != nil {
+				conv.Unexpected(fmt.Sprintf("Can't map index column name for table %s column %s", srcTable, k))
+				continue
+			}
+			spStoredColumns = append(spStoredColumns, spCol)
+		}
 		if srcIndex.Name == "" {
 			// Generate a name if index name is empty in MySQL.
 			// Collision of index name will be handled by ToSpannerIndexName.
@@ -188,10 +197,11 @@ func cvtIndexes(conv *internal.Conv, spTableName string, srcTable string, srcInd
 		}
 		spIndexName := internal.ToSpannerIndexName(conv, srcIndex.Name)
 		spIndex := ddl.CreateIndex{
-			Name:   spIndexName,
-			Table:  spTableName,
-			Unique: srcIndex.Unique,
-			Keys:   spKeys,
+			Name:          spIndexName,
+			Table:         spTableName,
+			Unique:        srcIndex.Unique,
+			Keys:          spKeys,
+			StoredColumns: spStoredColumns,
 		}
 		spIndexes = append(spIndexes, spIndex)
 		conv.Audit.ToSpannerFkIdx[srcTable].Index[srcIndex.Name] = spIndexName
diff --git a/sources/sqlserver/infoschema.go b/sources/sqlserver/infoschema.go
index 09cadba..5ba34bd 100644
--- a/sources/sqlserver/infoschema.go
+++ b/sources/sqlserver/infoschema.go
@@ -366,7 +366,8 @@ func (isi InfoSchemaImpl) GetIndexes(conv *internal.Conv, table common.SchemaAnd
 			IX.name, 
 			COL_NAME(IX.object_id, IXC.column_id) as [Column Name],
 			IX.is_unique,
-			IXC.is_descending_key 
+			IXC.is_descending_key,
+			IXC.is_included_column
 		FROM sys.indexes IX 
 		INNER JOIN sys.index_columns IXC 
 			ON  IX.object_id = IXC.object_id AND IX.index_id = IXC.index_id
@@ -385,12 +386,12 @@ func (isi InfoSchemaImpl) GetIndexes(conv *internal.Conv, table common.SchemaAnd
 		return nil, err
 	}
 	defer rows.Close()
-	var name, column, isUnique, collation string
+	var name, column, isUnique, collation, isStored string
 	indexMap := make(map[string]schema.Index)
 	var indexNames []string
 	var indexes []schema.Index
 	for rows.Next() {
-		if err := rows.Scan(&name, &column, &isUnique, &collation); err != nil {
+		if err := rows.Scan(&name, &column, &isUnique, &collation, &isStored); err != nil {
 			conv.Unexpected(fmt.Sprintf("Can't scan: %v", err))
 			continue
 		}
@@ -400,7 +401,11 @@ func (isi InfoSchemaImpl) GetIndexes(conv *internal.Conv, table common.SchemaAnd
 			indexMap[name] = schema.Index{Name: name, Unique: (isUnique == "true")}
 		}
 		index := indexMap[name]
-		index.Keys = append(index.Keys, schema.Key{Column: column, Desc: (collation == "DESC")})
+		if isStored == "false" {
+			index.Keys = append(index.Keys, schema.Key{Column: column, Desc: (collation == "DESC")})
+		} else {
+			index.StoredColumns = append(index.StoredColumns, column)
+		}
 		indexMap[name] = index
 	}
 	for _, k := range indexNames {
diff --git a/spanner/ddl/ast.go b/spanner/ddl/ast.go
index 254914c..2939f52 100644
--- a/spanner/ddl/ast.go
+++ b/spanner/ddl/ast.go
@@ -296,13 +296,14 @@ func (ct CreateTable) PrintCreateTable(config Config) string {
 // CreateIndex encodes the following DDL definition:
 //     create index: CREATE [UNIQUE] [NULL_FILTERED] INDEX index_name ON table_name ( key_part [, ...] ) [ storing_clause ] [ , interleave_clause ]
 type CreateIndex struct {
-	Name   string
-	Table  string
-	Unique bool
-	Keys   []IndexKey
-	Id     string
+	Name          string
+	Table         string
+	Unique        bool
+	Keys          []IndexKey
+	Id            string
+	StoredColumns []string
 	// We have no requirements for null-filtered option and
-	// storing/interleaving clauses yet, so we omit them for now.
+	// interleaving clauses yet, so we omit them for now.
 }
 
 // PrintCreateIndex unparses a CREATE INDEX statement.
@@ -311,11 +312,19 @@ func (ci CreateIndex) PrintCreateIndex(c Config) string {
 	for _, p := range ci.Keys {
 		keys = append(keys, p.PrintIndexKey(c))
 	}
-	var unique string
+	var unique, stored, storingClause string
 	if ci.Unique {
 		unique = "UNIQUE "
 	}
-	return fmt.Sprintf("CREATE %sINDEX %s ON %s (%s)", unique, c.quote(ci.Name), c.quote(ci.Table), strings.Join(keys, ", "))
+	if c.TargetDb == constants.TargetExperimentalPostgres {
+		stored = "INCLUDE"
+	} else {
+		stored = "STORING"
+	}
+	if ci.StoredColumns != nil {
+		storingClause = fmt.Sprintf(" %s (%s)", stored, strings.Join(ci.StoredColumns, ", "))
+	}
+	return fmt.Sprintf("CREATE %sINDEX %s ON %s (%s)%s", unique, c.quote(ci.Name), c.quote(ci.Table), strings.Join(keys, ", "), storingClause)
 }
 
 // PrintForeignKeyAlterTable unparses the foreign keys using ALTER TABLE.
diff --git a/ui/src/app/app-routing.module.ts b/ui/src/app/app-routing.module.ts
index 06f7481..aab8250 100644
--- a/ui/src/app/app-routing.module.ts
+++ b/ui/src/app/app-routing.module.ts
@@ -8,7 +8,7 @@ import { LoadSessionComponent } from './components/load-session/load-session.com
 import { SourceSelectionComponent } from './components/source-selection/source-selection.component'
 import { SummaryComponent } from './components/summary/summary.component'
 import { WorkspaceComponent } from './components/workspace/workspace.component'
-
+import { PrepareMigrationComponent } from './components/prepare-migration/prepare-migration.component'
 
 const routes: Routes = [
   {
@@ -46,6 +46,10 @@ const routes: Routes = [
     path: 'instruction',
     component: InstructionComponent,
   },
+  {
+    path: 'prepare-migration',
+    component: PrepareMigrationComponent,
+  },
   {
     path: '**',
     redirectTo : "/",
diff --git a/ui/src/app/app.constants.ts b/ui/src/app/app.constants.ts
index ea40f23..fb107e6 100644
--- a/ui/src/app/app.constants.ts
+++ b/ui/src/app/app.constants.ts
@@ -30,3 +30,9 @@ export enum RulesTypes {
   ChangeGlobalDataType = 'globalDatatype',
   AddIndex = 'addIndex',
 }
+
+export enum MigrationModes {
+  schemaOnly = 'Schema',
+  dataOnly = 'Data',
+  schemaAndData = 'Schema And Data'
+}
diff --git a/ui/src/app/app.module.ts b/ui/src/app/app.module.ts
index 3f0dcaa..b3702d6 100644
--- a/ui/src/app/app.module.ts
+++ b/ui/src/app/app.module.ts
@@ -31,7 +31,9 @@ import { EditGlobalDatatypeFormComponent } from './components/edit-global-dataty
 import { SidenavViewAssessmentComponent } from './components/sidenav-view-assessment/sidenav-view-assessment.component'
 import { SidenavSaveSessionComponent } from './components/sidenav-save-session/sidenav-save-session.component'
 import { DropIndexDialogComponent } from './components/drop-index-dialog/drop-index-dialog.component'
-import { DatabaseLoaderComponent } from './components/database-loader/database-loader.component'
+import { DatabaseLoaderComponent } from './components/database-loader/database-loader.component';
+import { PrepareMigrationComponent } from './components/prepare-migration/prepare-migration.component';
+import { TargetDetailsFormComponent } from './components/target-details-form/target-details-form.component'
 
 @NgModule({
   declarations: [
@@ -60,6 +62,8 @@ import { DatabaseLoaderComponent } from './components/database-loader/database-l
     SidenavSaveSessionComponent,
     DropIndexDialogComponent,
     DatabaseLoaderComponent,
+    PrepareMigrationComponent,
+    TargetDetailsFormComponent,
   ],
   imports: [
     BrowserModule,
diff --git a/ui/src/app/components/object-detail/object-detail.component.html b/ui/src/app/components/object-detail/object-detail.component.html
index 8d098cf..f545077 100644
--- a/ui/src/app/components/object-detail/object-detail.component.html
+++ b/ui/src/app/components/object-detail/object-detail.component.html
@@ -21,9 +21,12 @@
       </svg>
     </div>
     <div class="inner-text">
-      {{
-        'To view and modify an object details, click on the object name on the Spanner draft panel.'
-      }}
+      <span *ngIf="currentDatabase == 'spanner'">
+        To view and modify an object details, click on the object name on the Spanner draft panel.
+      </span>
+      <span *ngIf="currentDatabase == 'source'">
+        To view an object details, click on the object name on the {{ srcDbName }} panel.
+      </span>
     </div>
   </div>
 </div>
diff --git a/ui/src/app/components/object-detail/object-detail.component.scss b/ui/src/app/components/object-detail/object-detail.component.scss
index cc07001..690b928 100644
--- a/ui/src/app/components/object-detail/object-detail.component.scss
+++ b/ui/src/app/components/object-detail/object-detail.component.scss
@@ -55,7 +55,7 @@
 }
 
 .add-index-column-container {
-  margin-left: 40%;
+  margin-left: 45%;
   padding: 10px;
   .index-column-list {
     margin-right: 1rem;
@@ -206,6 +206,14 @@
   align-items: center;
 }
 
+.interleave-tab-container {
+  height: 200px;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+}
+
 .save-button {
   width: 100%;
   button {
diff --git a/ui/src/app/components/object-detail/object-detail.component.ts b/ui/src/app/components/object-detail/object-detail.component.ts
index ad5733a..d166483 100644
--- a/ui/src/app/components/object-detail/object-detail.component.ts
+++ b/ui/src/app/components/object-detail/object-detail.component.ts
@@ -33,6 +33,7 @@ export class ObjectDetailComponent implements OnInit {
   @Input() ddlStmts: any = {}
   @Input() fkData: IFkTabData[] = []
   @Input() tableData: IColumnTabData[] = []
+  @Input() currentDatabase: string = 'spanner'
   @Input() indexData: IIndexData[] = []
   @Output() updateSidebar = new EventEmitter<boolean>()
   ObjectExplorerNodeType = ObjectExplorerNodeType
@@ -129,6 +130,7 @@ export class ObjectDetailComponent implements OnInit {
     this.currentObject = changes['currentObject']?.currentValue || this.currentObject
     this.tableData = changes['tableData']?.currentValue || this.tableData
     this.indexData = changes['indexData']?.currentValue || this.indexData
+    this.currentDatabase = changes['currentDatabase']?.currentValue || this.currentDatabase
     this.currentTabIndex = this.currentObject?.type === ObjectExplorerNodeType.Table ? 0 : -1
     this.isObjectSelected = this.currentObject ? true : false
     this.isEditMode = false
@@ -162,28 +164,8 @@ export class ObjectDetailComponent implements OnInit {
         )
       })
     } else if (this.currentObject) {
-      const addedIndexColumns = this.indexData.map((data) => data.spColName)
-      this.indexColumnNames = this.conv.SpSchema[this.currentObject?.parent].ColNames.filter(
-        (columnName) => {
-          if (addedIndexColumns.includes(columnName)) {
-            return false
-          } else {
-            return true
-          }
-        }
-      )
-      this.indexData.forEach((row: IIndexData) => {
-        this.rowArray.push(
-          new FormGroup({
-            srcOrder: new FormControl(row.srcOrder),
-            srcColName: new FormControl(row.srcColName),
-            srcDesc: new FormControl(row.srcDesc),
-            spOrder: new FormControl(row.spOrder),
-            spColName: new FormControl(row.spColName),
-            spDesc: new FormControl(row.spDesc),
-          })
-        )
-      })
+      this.checkIsInterleave()
+      this.setIndexRows()
     }
 
     this.dataSource = this.rowArray.controls
@@ -277,7 +259,7 @@ export class ObjectDetailComponent implements OnInit {
       const srDataType = item.srcDataType
       const spDataType = item.spDataType
       let brief: string = ''
-      this.typeMap[srDataType].forEach((type: any) => {
+      this.typeMap[srDataType]?.forEach((type: any) => {
         if (spDataType == type.T) brief = type.Brief
       })
       this.isSpTableSuggesstionDisplay.push(brief !== '')
@@ -457,12 +439,27 @@ export class ObjectDetailComponent implements OnInit {
         })
       })
     }
+    if (arr[0] == 0 && arr[arr.length - 1] <= arr.length) {
+      let missingOrder: number
+      for (let i = 0; i < arr.length; i++) {
+        if (arr[i] != i) {
+          missingOrder = i
+          break
+        }
+        missingOrder = arr.length
+      }
+      this.pkData.forEach((pk: IColumnTabData) => {
+        if (pk.spOrder < missingOrder) {
+          pk.spOrder = Number(pk.spOrder) + 1
+        }
+      })
+    }
   }
 
   getPkRequestObj() {
     let tableId: string = this.conv.SpSchema[this.currentObject!.name].Id
     let Columns: { ColumnId: string; ColName: string; Desc: boolean; Order: number }[] = []
-    this.pkArray.value.forEach((row: IColumnTabData) => {
+    this.pkData.forEach((row: IColumnTabData) => {
       if (row.spIsPk)
         Columns.push({
           ColumnId: this.conv.SpSchema[this.currentObject!.name].ColDefs[row.spColName].Id,
@@ -485,13 +482,6 @@ export class ObjectDetailComponent implements OnInit {
   togglePkEdit() {
     this.currentTabIndex = 1
     if (this.isPkEditMode) {
-      this.getPkRequestObj()
-      if (this.pkObj.Columns.length == 0) {
-        this.dialog.open(InfodialogComponent, {
-          data: { message: 'Add columns to the primary key for saving', type: 'error' },
-          maxWidth: '500px',
-        })
-      }
       this.pkArray.value.forEach((pk: IColumnTabData) => {
         for (let i = 0; i < this.pkData.length; i++) {
           if (pk.spColName == this.pkData[i].spColName) {
@@ -500,6 +490,16 @@ export class ObjectDetailComponent implements OnInit {
           }
         }
       })
+      this.pkOrderValidation()
+
+      this.getPkRequestObj()
+      if (this.pkObj.Columns.length == 0) {
+        this.dialog.open(InfodialogComponent, {
+          data: { message: 'Add columns to the primary key for saving', type: 'error' },
+          maxWidth: '500px',
+        })
+      }
+
       this.isPkEditMode = false
       this.data.updatePk(this.pkObj).subscribe({
         next: (res: string) => {
@@ -633,8 +633,64 @@ export class ObjectDetailComponent implements OnInit {
     return null
   }
 
+  setIndexRows() {
+    this.rowArray = new FormArray([])
+    const addedIndexColumns: string[] = this.indexData
+      .map((data) => (data.spColName ? data.spColName : ''))
+      .filter((name) => name != '')
+    this.indexColumnNames = this.conv.SpSchema[this.currentObject!.parent]?.ColNames.filter(
+      (columnName) => {
+        if (addedIndexColumns.includes(columnName)) {
+          return false
+        } else {
+          return true
+        }
+      }
+    )
+
+    this.indexData.forEach((row: IIndexData) => {
+      this.rowArray.push(
+        new FormGroup({
+          srcOrder: new FormControl(row.srcOrder),
+          srcColName: new FormControl(row.srcColName),
+          srcDesc: new FormControl(row.srcDesc),
+          spOrder: new FormControl(row.spOrder),
+          spColName: new FormControl(row.spColName),
+          spDesc: new FormControl(row.spDesc),
+        })
+      )
+    })
+    this.dataSource = this.rowArray.controls
+  }
+
   toggleIndexEdit() {
     if (this.isIndexEditMode) {
+      let payload: ICreateIndex[] = []
+      const tableName = this.currentObject?.parent || ''
+      payload.push({
+        Name: this.currentObject?.name || '',
+        Table: this.currentObject?.parent || '',
+        Unique: false,
+        Keys: this.indexData
+          .filter((idx) => {
+            if (idx.spColName) return true
+            return false
+          })
+          .map((col: any) => {
+            return {
+              Col: col.spColName,
+              Desc: col.spDesc,
+              Order: col.spOrder,
+            }
+          }),
+        Id: '',
+      })
+
+      this.data.updateIndex(tableName, payload)
+      this.addIndexKeyForm.controls['columnName'].setValue('')
+      this.addIndexKeyForm.controls['ascOrDesc'].setValue('')
+      this.addIndexKeyForm.markAsUntouched()
+      this.data.getSummary()
       this.isIndexEditMode = false
     } else {
       this.isIndexEditMode = true
@@ -664,72 +720,29 @@ export class ObjectDetailComponent implements OnInit {
     })
   }
   dropIndexKey(index: number) {
-    let payload: ICreateIndex[] = []
-    const tableName = this.currentObject?.parent || ''
-    let spIndexCount = 0
-    this.indexData.forEach((idx) => {
-      if (idx.spColName) spIndexCount += 1
-    })
-    if (spIndexCount <= 1) {
-      this.dropIndex()
-    } else {
-      payload.push({
-        Name: this.currentObject?.name || '',
-        Table: this.currentObject?.parent || '',
-        Unique: false,
-        Keys: this.indexData
-          .filter((idx, i: number) => {
-            if (i === index || idx.spColName === undefined) return false
-            return true
-          })
-          .map((col: any) => {
-            return {
-              Col: col.spColName,
-              Desc: col.spDesc,
-              Order: col.spOrder,
-            }
-          }),
-        Id: '',
-      })
-      this.data.updateIndex(tableName, payload)
+    for (let i = 0; i < this.indexData.length; i++) {
+      if (i === index || this.indexData[i].spColName === undefined) {
+        this.indexData.splice(index, 1)
+      }
     }
+    this.setIndexRows()
   }
 
   addIndexKey() {
-    let payload: ICreateIndex[] = []
-    const tableName = this.currentObject?.parent || ''
     let spIndexCount = 0
     this.indexData.forEach((idx) => {
       if (idx.spColName) spIndexCount += 1
     })
-    payload.push({
-      Name: this.currentObject?.name || '',
-      Table: this.currentObject?.parent || '',
-      Unique: false,
-      Keys: this.indexData
-        .filter((idx) => {
-          if (idx.spColName) return true
-          return false
-        })
-        .map((col: any) => {
-          return {
-            Col: col.spColName,
-            Desc: col.spDesc,
-            Order: col.spOrder,
-          }
-        }),
-      Id: '',
-    })
-    payload[0].Keys.push({
-      Col: this.addIndexKeyForm.value.columnName,
-      Desc: this.addIndexKeyForm.value.ascOrDesc === 'desc',
-      Order: spIndexCount + 1,
+    this.indexData.push({
+      spColName: this.addIndexKeyForm.value.columnName,
+      spDesc: this.addIndexKeyForm.value.ascOrDesc === 'desc',
+      spOrder: spIndexCount + 1,
+      srcColName: '',
+      srcDesc: undefined,
+      srcOrder: '',
     })
 
-    this.data.updateIndex(tableName, payload)
-    this.addIndexKeyForm.controls['columnName'].setValue('')
-    this.addIndexKeyForm.controls['ascOrDesc'].setValue('')
-    this.addIndexKeyForm.markAsUntouched()
+    this.setIndexRows()
   }
 
   tabChanged(tabChangeEvent: MatTabChangeEvent): void {
diff --git a/ui/src/app/components/object-explorer/object-explorer.component.html b/ui/src/app/components/object-explorer/object-explorer.component.html
index 418b810..9443894 100644
--- a/ui/src/app/components/object-explorer/object-explorer.component.html
+++ b/ui/src/app/components/object-explorer/object-explorer.component.html
@@ -1,5 +1,9 @@
 <div class="container">
-  <mat-tab-group [ngClass]="[isLeftColumnCollapse ? 'hidden' : 'display']" selectedIndex="1">
+  <mat-tab-group
+    [ngClass]="[isLeftColumnCollapse ? 'hidden' : 'display']"
+    selectedIndex="1"
+    (selectedTabChange)="onTabChanged()"
+  >
     <mat-tab [label]="srcDbName.toUpperCase()">
       <div class="filter-wrapper">
         <div class="left">
diff --git a/ui/src/app/components/object-explorer/object-explorer.component.ts b/ui/src/app/components/object-explorer/object-explorer.component.ts
index e94bb11..a7f7111 100644
--- a/ui/src/app/components/object-explorer/object-explorer.component.ts
+++ b/ui/src/app/components/object-explorer/object-explorer.component.ts
@@ -20,6 +20,8 @@ export class ObjectExplorerComponent implements OnInit {
   spannerSortOrder: string = ''
   srcSearchText: string = ''
   spannerSearchText: string = ''
+  selectedTab: string = 'spanner'
+  @Output() selectedDatabase = new EventEmitter<string>()
   @Output() selectObject = new EventEmitter<FlatNode>()
   @Output() updateSpannerTable = new EventEmitter<IUpdateTableArgument>()
   @Output() updateSrcTable = new EventEmitter<IUpdateTableArgument>()
@@ -146,4 +148,14 @@ export class ObjectExplorerComponent implements OnInit {
       return false
     }
   }
+  onTabChanged() {
+    if (this.selectedTab == 'spanner') {
+      this.selectedTab = 'source'
+    } else {
+      this.selectedTab = 'spanner'
+    }
+    this.selectedDatabase.emit(this.selectedTab)
+    this.currentSelectedObject = null
+    this.selectObject.emit(undefined)
+  }
 }
diff --git a/ui/src/app/components/prepare-migration/prepare-migration.component.html b/ui/src/app/components/prepare-migration/prepare-migration.component.html
new file mode 100644
index 0000000..640b751
--- /dev/null
+++ b/ui/src/app/components/prepare-migration/prepare-migration.component.html
@@ -0,0 +1,81 @@
+<div class="header">
+    <div class="breadcrumb">
+        <a class="breadcrumb_source" mat-button [routerLink]="'/'">Select Source</a>
+        <span>></span>
+        <a class="breadcrumb_workspace" mat-button [routerLink]="'/workspace'">Configure Schema</a>
+        <span>></span>
+        <a class="breadcrumb_prepare_migration" mat-button [routerLink]="'/prepare-migration'">Prepare Migration</a>
+    </div>
+</div>
+<br />
+<div class="body">
+    <div class="definition-container">
+        <h2>Source and destination Database definitions</h2>
+        <div class="summary">
+            <table mat-table [dataSource]="dataSource">
+                <ng-container matColumnDef="Title">
+                    <th mat-header-cell *matHeaderCellDef>Title</th>
+                    <td mat-cell *matCellDef="let element">
+                        <b>{{ element.title }}</b>
+                    </td>
+                </ng-container>
+                <ng-container matColumnDef="Source">
+                    <th mat-header-cell *matHeaderCellDef>Source</th>
+                    <td mat-cell *matCellDef="let element">{{ element.source }}</td>
+                </ng-container>
+                <ng-container matColumnDef="Destination">
+                    <th mat-header-cell *matHeaderCellDef>Destination</th>
+                    <td mat-cell *matCellDef="let element">{{ element.target }}</td>
+                </ng-container>
+                <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
+                <tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
+            </table>
+        </div>
+    </div>
+    <br />
+    <mat-form-field appearance="outline">
+        <mat-label>Migration Mode:</mat-label>
+        <mat-select [(ngModel)]="selectedMigrationMode">
+            <mat-option *ngFor="let mode of migrationModes" [value]="mode">
+                {{mode}}
+            </mat-option>
+        </mat-select>
+    </mat-form-field>
+
+    <div *ngIf="!(selectedMigrationMode ==='Schema') && isStreamingSupported">
+        <mat-form-field appearance="outline">
+            <mat-label>Migration Type:</mat-label>
+            <mat-select [(ngModel)]="selectedMigrationType">
+                    <mat-option value="bulk">Bulk Migration</mat-option>
+                    <mat-option value="lowdt">Low downtime migration</mat-option>
+            </mat-select>
+        </mat-form-field>
+    </div>
+    <br />
+    <div class="prerequisites">
+        <mat-card>
+            <mat-card-title>Prerequisites</mat-card-title>
+            <mat-card-subtitle>Before we begin, please ensure you have done the following:</mat-card-subtitle>
+            <hr />
+            <div>
+                <p class="point">
+                    <span class="bullet">1</span>
+                    <span>Setup Target details</span><span><button mat-button class="configure"
+                            (click)="openTargetDetailsForm()">
+                            Configure
+                            <mat-icon>edit</mat-icon>
+                        </button></span>
+                </p>
+            </div>
+        </mat-card>
+        <br />
+    </div>
+    <div *ngIf="isTargetDetailSet">
+        <h3>Target Details</h3>
+        <span><b>Target Database:</b>{{ targetDetails.TargetDB }}</span><br />
+        <span><b>Spanner Dialect:</b>{{ targetDetails.Dialect }}</span><br />
+        <span *ngIf="isStreamingCfgSet"><b>Streaming Config:</b>{{ targetDetails.StreamingConfig }}</span><br />
+    </div>
+    <button mat-raised-button type="submit" color="primary" (click)="migrate()" class="migrate"
+        [disabled]="!isTargetDetailSet">Migrate</button>
+</div>
\ No newline at end of file
diff --git a/ui/src/app/components/prepare-migration/prepare-migration.component.scss b/ui/src/app/components/prepare-migration/prepare-migration.component.scss
new file mode 100644
index 0000000..6add83f
--- /dev/null
+++ b/ui/src/app/components/prepare-migration/prepare-migration.component.scss
@@ -0,0 +1,42 @@
+.header {
+  .breadcrumb {
+    .breadcrumb_workspace {
+      color: rgba(0, 0, 0, 0.56);
+    }
+
+    .breadcrumb_prepare_migration {
+      font-weight: 400;
+      font-size: 14px;
+    }
+  }
+}
+
+.definition-container {
+  max-height: 500px;
+  overflow: auto;
+
+  th {
+    font-size: 13px;
+  }
+}
+
+.body {
+  margin-left: 20px;
+}
+
+table {
+  min-width: 30%;
+  max-width: 50%;
+
+  th {
+    width: 10%;
+  }
+}
+
+.configure {
+  color: #1967d2;
+}
+
+.migrate {
+  margin-top: 10px;
+}
\ No newline at end of file
diff --git a/ui/src/app/components/prepare-migration/prepare-migration.component.spec.ts b/ui/src/app/components/prepare-migration/prepare-migration.component.spec.ts
new file mode 100644
index 0000000..2e4ec07
--- /dev/null
+++ b/ui/src/app/components/prepare-migration/prepare-migration.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { PrepareMigrationComponent } from './prepare-migration.component';
+
+describe('PrepareMigrationComponent', () => {
+  let component: PrepareMigrationComponent;
+  let fixture: ComponentFixture<PrepareMigrationComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ PrepareMigrationComponent ]
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(PrepareMigrationComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/ui/src/app/components/prepare-migration/prepare-migration.component.ts b/ui/src/app/components/prepare-migration/prepare-migration.component.ts
new file mode 100644
index 0000000..5bb7908
--- /dev/null
+++ b/ui/src/app/components/prepare-migration/prepare-migration.component.ts
@@ -0,0 +1,105 @@
+import { Component, OnInit } from '@angular/core'
+import { MatDialog } from '@angular/material/dialog'
+import { TargetDetailsFormComponent } from '../target-details-form/target-details-form.component'
+import { TargetDetailsService } from 'src/app/services/target-details/target-details.service'
+import { FetchService } from 'src/app/services/fetch/fetch.service'
+import { SnackbarService } from 'src/app/services/snackbar/snackbar.service'
+import ITargetDetails from 'src/app/model/target-details'
+import { ISessionSummary } from 'src/app/model/conv'
+import IMigrationDetails from 'src/app/model/migrate'
+import { InputType, MigrationModes, SourceDbNames } from 'src/app/app.constants'
+@Component({
+  selector: 'app-prepare-migration',
+  templateUrl: './prepare-migration.component.html',
+  styleUrls: ['./prepare-migration.component.scss'],
+})
+export class PrepareMigrationComponent implements OnInit {
+  displayedColumns = ['Title', 'Source', 'Destination']
+  dataSource: any = []
+  migrationModes: any = []
+  constructor(
+    private dialog: MatDialog,
+    private fetch: FetchService,
+    private snack: SnackbarService,
+    private targetDetailService: TargetDetailsService
+  ) {}
+
+  isTargetDetailSet: boolean = false
+  isStreamingCfgSet: boolean = false
+  isSchemaMigration: boolean = true
+  isStreamingSupported: boolean = false
+  selectedMigrationMode: string = MigrationModes.schemaOnly
+  selectedMigrationType: string = 'bulk'
+  targetDetails: ITargetDetails = this.targetDetailService.getTargetDetails()
+
+  ngOnInit(): void {
+    this.fetch.getSourceDestinationSummary().subscribe({
+      next: (res: ISessionSummary) => {
+        this.dataSource = [
+          { title: 'Database Type', source: res.DatabaseType, target: 'Spanner' },
+          {
+            title: 'Number of tables',
+            source: res.SourceTableCount,
+            target: res.SpannerTableCount,
+          },
+          {
+            title: 'Number of indexes',
+            source: res.SourceIndexCount,
+            target: res.SpannerIndexCount,
+          },
+        ]
+        if (res.ConnectionType == InputType.DumpFile) {
+          this.migrationModes = [MigrationModes.schemaAndData]
+          this.selectedMigrationMode = MigrationModes.schemaAndData
+        } else if (res.ConnectionType == InputType.SessionFile) {
+          this.migrationModes = [MigrationModes.schemaOnly]
+        } else {
+          this.migrationModes = [MigrationModes.schemaOnly, MigrationModes.dataOnly, MigrationModes.schemaAndData]
+        }
+        if (res.DatabaseType == SourceDbNames.MySQL.toLowerCase() || res.DatabaseType == SourceDbNames.Oracle.toLowerCase()) {
+          this.isStreamingSupported = true
+        }
+      },
+      error: (err: any) => {
+         this.snack.openSnackBar(err.error, 'Close')
+      },
+    })
+  }
+
+  openTargetDetailsForm() {
+    let dialogRef = this.dialog.open(TargetDetailsFormComponent, {
+      width: '30vw',
+      minWidth: '400px',
+      maxWidth: '500px',
+      data:this.selectedMigrationType=='lowdt',
+    })
+    dialogRef.afterClosed().subscribe(() => {
+      if (this.targetDetails.TargetDB != '') {
+        this.isTargetDetailSet = true
+      }
+      if (this.targetDetails.StreamingConfig != '') {
+        this.isStreamingCfgSet = true
+      }
+    })
+  }
+
+  migrate() {
+    let payload: IMigrationDetails = {
+      TargetDetails: this.targetDetailService.getTargetDetails(),
+      MigrationType: this.selectedMigrationType,
+      MigrationMode: this.selectedMigrationMode
+    }
+    this.fetch.migrate(payload).subscribe({
+      next: () => {
+        if (this.isStreamingCfgSet) {
+          this.snack.openSnackBar('Migration started successfully', 'Close', 5)
+        } else {
+          this.snack.openSnackBar('Migration completed successfully', 'Close', 5)
+        }
+      },
+      error: (err: any) => {
+        this.snack.openSnackBar(err.error, 'Close')
+      },
+    })
+  }
+}
diff --git a/ui/src/app/components/session-listing/session-listing.component.html b/ui/src/app/components/session-listing/session-listing.component.html
index fb1c9b0..3b416aa 100644
--- a/ui/src/app/components/session-listing/session-listing.component.html
+++ b/ui/src/app/components/session-listing/session-listing.component.html
@@ -11,7 +11,8 @@
           <div class="table-header-container">
             <span>Session Name</span>
             <button mat-icon-button (click)="toggleFilterDisplay('sessionName')">
-              <mat-icon>filter_list</mat-icon>
+              <mat-icon *ngIf="displayFilter['sessionName']">keyboard_arrow_up</mat-icon>
+              <mat-icon *ngIf="!displayFilter['sessionName']">filter_list</mat-icon>
             </button>
           </div>
           <mat-form-field
@@ -36,7 +37,8 @@
           <div class="table-header-container">
             <span>Editor</span>
             <button mat-icon-button (click)="toggleFilterDisplay('editorName')">
-              <mat-icon>filter_list</mat-icon>
+              <mat-icon *ngIf="displayFilter['editorName']">keyboard_arrow_up</mat-icon>
+              <mat-icon *ngIf="!displayFilter['editorName']">filter_list</mat-icon>
             </button>
           </div>
           <mat-form-field *ngIf="displayFilter.editorName" appearance="standard">
@@ -57,7 +59,8 @@
           <div class="table-header-container">
             <span>Database Type</span>
             <button mat-icon-button (click)="toggleFilterDisplay('databaseType')">
-              <mat-icon>filter_list</mat-icon>
+              <mat-icon *ngIf="displayFilter['databaseType']">keyboard_arrow_up</mat-icon>
+              <mat-icon *ngIf="!displayFilter['databaseType']">filter_list</mat-icon>
             </button>
           </div>
           <mat-form-field *ngIf="displayFilter.databaseType" appearance="standard">
@@ -78,7 +81,8 @@
           <div class="table-header-container">
             <span>Database Name</span>
             <button mat-icon-button (click)="toggleFilterDisplay('databaseName')">
-              <mat-icon>filter_list</mat-icon>
+              <mat-icon *ngIf="displayFilter['databaseName']">keyboard_arrow_up</mat-icon>
+              <mat-icon *ngIf="!displayFilter['databaseName']">filter_list</mat-icon>
             </button>
           </div>
           <mat-form-field *ngIf="displayFilter.databaseName" appearance="standard">
diff --git a/ui/src/app/components/session-listing/session-listing.component.scss b/ui/src/app/components/session-listing/session-listing.component.scss
index b9a9337..f0af4cc 100644
--- a/ui/src/app/components/session-listing/session-listing.component.scss
+++ b/ui/src/app/components/session-listing/session-listing.component.scss
@@ -55,3 +55,22 @@ table {
 .notes-wrapper {
   margin-top: 10px;
 }
+
+.mat-column-SessionName,
+.mat-column-EditorName,
+.mat-column-DatabaseType,
+.mat-column-DatabaseName {
+  width: 13vw;
+  min-width: 150px;
+}
+
+.mat-column-Action,
+.mat-column-Notes,
+.mat-column-CreateTimestamp {
+  width: 15vw;
+  min-width: 150px;
+}
+
+.mat-form-field {
+  width: 80%;
+}
diff --git a/ui/src/app/components/session-listing/session-listing.component.ts b/ui/src/app/components/session-listing/session-listing.component.ts
index 5a60a21..102316b 100644
--- a/ui/src/app/components/session-listing/session-listing.component.ts
+++ b/ui/src/app/components/session-listing/session-listing.component.ts
@@ -127,6 +127,7 @@ export class SessionListingComponent implements OnInit {
       this.router.navigate(['/workspace'])
     })
   }
+
   openSpannerConfigDialog() {
     this.clickEvent.openSpannerConfig()
   }
diff --git a/ui/src/app/components/source-selection/source-selection.component.scss b/ui/src/app/components/source-selection/source-selection.component.scss
index a21984e..3a679b5 100644
--- a/ui/src/app/components/source-selection/source-selection.component.scss
+++ b/ui/src/app/components/source-selection/source-selection.component.scss
@@ -1,36 +1,3 @@
 .prerequisites {
   padding: 0 20px;
-  width: 600px;
-  .mat-card-title {
-    font-size: 13pt;
-    font-weight: normal;
-  }
-  .mat-card-subtitle {
-    font-size: 9pt;
-    font-weight: normal;
-  }
-  p {
-    color: #5c5c5c;
-    .bullet {
-      position: absolute;
-      display: inline-block;
-      background-color: #e5e5e5;
-      padding: 0px 5px 10px 5px;
-      margin: 0;
-      border-radius: 10px;
-      font-size: 9px;
-      height: 9px;
-      width: 9px;
-      text-align: center;
-    }
-    span {
-      margin-left: 40px;
-      font-weight: lighter;
-      font-size: 13px;
-    }
-  }
-  hr {
-    border-color: #ffffff;
-    margin-bottom: 20px;
-  }
-}
+}
\ No newline at end of file
diff --git a/ui/src/app/components/summary/summary.component.ts b/ui/src/app/components/summary/summary.component.ts
index 4b4d966..a76a2b2 100644
--- a/ui/src/app/components/summary/summary.component.ts
+++ b/ui/src/app/components/summary/summary.component.ts
@@ -32,12 +32,21 @@ export class SummaryComponent implements OnInit {
       next: (summary: Map<string, ISummary>) => {
         this.summary = summary
         if (this.currentObject) {
-          let s = summary.get(this.currentObject.name)
+          let objectName = this.currentObject.name
+          if (this.currentObject.type == 'indexName') {
+            objectName = this.currentObject.parent
+          }
+          let s = this.summary.get(objectName)
           if (s) {
             this.initiateSummaryCollection(s)
-            this.summaryCount = s.NotesCount + s.WarningsCount
-            this.changeIssuesLabel.emit(s.NotesCount + s.WarningsCount)
+            this.summaryCount = s.NotesCount + s.WarningsCount + s.ErrorsCount + s.SuggestionsCount
+            this.changeIssuesLabel.emit(
+              s.NotesCount + s.WarningsCount + s.ErrorsCount + s.SuggestionsCount
+            )
           }
+        } else {
+          this.summaryCount = 0
+          this.changeIssuesLabel.emit(0)
         }
       },
     })
@@ -49,12 +58,21 @@ export class SummaryComponent implements OnInit {
     this.currentObject = changes?.['currentObject']?.currentValue || this.currentObject
     this.summaryRows = []
     if (this.currentObject) {
-      let s = this.summary.get(this.currentObject.name)
+      let objectName = this.currentObject.name
+      if (this.currentObject.type == 'indexName') {
+        objectName = this.currentObject.parent
+      }
+      let s = this.summary.get(objectName)
       if (s) {
         this.initiateSummaryCollection(s)
-        this.summaryCount = s.NotesCount + s.WarningsCount
-        this.changeIssuesLabel.emit(s.NotesCount + s.WarningsCount)
+        this.summaryCount = s.NotesCount + s.WarningsCount + s.ErrorsCount + s.SuggestionsCount
+        this.changeIssuesLabel.emit(
+          s.NotesCount + s.WarningsCount + s.ErrorsCount + s.SuggestionsCount
+        )
       }
+    } else {
+      this.summaryCount = 0
+      this.changeIssuesLabel.emit(0)
     }
   }
 
diff --git a/ui/src/app/components/target-details-form/target-details-form.component.html b/ui/src/app/components/target-details-form/target-details-form.component.html
new file mode 100644
index 0000000..369d8e9
--- /dev/null
+++ b/ui/src/app/components/target-details-form/target-details-form.component.html
@@ -0,0 +1,33 @@
+<div mat-dialog-content>
+    <form [formGroup]="targetDetailsForm" class="target-detail-form">
+        <h2>Target Details</h2>
+    <mat-form-field class="full-width" appearance="outline">
+        <mat-label>Target Database</mat-label>
+        <input matInput placeholder="Target Database" type="text" formControlName="targetDb" />
+      </mat-form-field>
+      <br>
+      <mat-form-field appearance="outline">
+        <mat-label>Target Dialect</mat-label>
+        <mat-select class="input-field" formControlName="dialect" required="true">
+          <mat-option value="google_standard_sql">Google Standard SQL</mat-option>
+        </mat-select>
+      </mat-form-field>
+      <br>
+      <mat-form-field class="full-width" appearance="outline">
+        <mat-label>Streaming config</mat-label>
+        <input matInput placeholder="File path" type="text" formControlName="streamingConfig" />
+      </mat-form-field>
+    </form>
+      <div mat-dialog-actions class="buttons-container">
+        <button mat-button color="primary" mat-dialog-close>Cancel</button>
+        <button
+          mat-button
+          type="submit"
+          color="primary"
+          [disabled]="!(targetDetailsForm.valid)"
+          (click)="updateTargetDetails()"
+        >
+          Save
+        </button>
+      </div>
+</div>
\ No newline at end of file
diff --git a/ui/src/app/components/target-details-form/target-details-form.component.scss b/ui/src/app/components/target-details-form/target-details-form.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/ui/src/app/components/target-details-form/target-details-form.component.spec.ts b/ui/src/app/components/target-details-form/target-details-form.component.spec.ts
new file mode 100644
index 0000000..47b7beb
--- /dev/null
+++ b/ui/src/app/components/target-details-form/target-details-form.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { TargetDetailsFormComponent } from './target-details-form.component';
+
+describe('TargetDetailsFormComponent', () => {
+  let component: TargetDetailsFormComponent;
+  let fixture: ComponentFixture<TargetDetailsFormComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ TargetDetailsFormComponent ]
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(TargetDetailsFormComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/ui/src/app/components/target-details-form/target-details-form.component.ts b/ui/src/app/components/target-details-form/target-details-form.component.ts
new file mode 100644
index 0000000..4276655
--- /dev/null
+++ b/ui/src/app/components/target-details-form/target-details-form.component.ts
@@ -0,0 +1,42 @@
+import { Component, Inject, OnInit } from '@angular/core'
+import { FormBuilder, FormGroup, Validators } from '@angular/forms'
+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'
+import ITargetDetails from 'src/app/model/target-details'
+import { TargetDetailsService } from 'src/app/services/target-details/target-details.service'
+
+@Component({
+  selector: 'app-target-details-form',
+  templateUrl: './target-details-form.component.html',
+  styleUrls: ['./target-details-form.component.scss'],
+})
+export class TargetDetailsFormComponent implements OnInit {
+  targetDetailsForm: FormGroup
+  constructor(
+    private formBuilder: FormBuilder,
+    private targetDetailService: TargetDetailsService,
+    private dialogRef: MatDialogRef<TargetDetailsFormComponent>,
+    @Inject(MAT_DIALOG_DATA) public data: boolean
+  ) {
+    this.targetDetailsForm = this.formBuilder.group({
+      targetDb: ['', Validators.required],
+      dialect: ['',Validators.required],
+      streamingConfig: [''],
+    })
+    if (data) {
+      this.targetDetailsForm.get('streamingConfig')?.setValidators(Validators.required)
+    }
+  }
+
+  ngOnInit(): void {}
+
+  updateTargetDetails() {
+    let formValue = this.targetDetailsForm.value
+    let payload: ITargetDetails = {
+      TargetDB: formValue.targetDb,
+      Dialect: formValue.dialect,
+      StreamingConfig: formValue.streamingConfig
+    }
+    this.targetDetailService.updateTargetDetails(payload)
+    this.dialogRef.close()
+  }
+}
\ No newline at end of file
diff --git a/ui/src/app/components/workspace/workspace.component.html b/ui/src/app/components/workspace/workspace.component.html
index dbbfd37..e784f9c 100644
--- a/ui/src/app/components/workspace/workspace.component.html
+++ b/ui/src/app/components/workspace/workspace.component.html
@@ -6,6 +6,7 @@
       <a class="breadcrumb_workspace" mat-button [routerLink]="'/workspace'">Configure Schema</a>
     </div>
     <div class="header_action">
+      <button mat-button (click)="prepareMigration()">PREPARE MIGRATION</button>
       <button mat-button (click)="openAssessment()">VIEW ASSESSMENT</button>
       <button mat-button (click)="openSaveSessionSidenav()" *ngIf="!isOfflineStatus">
         SAVE SESSION
@@ -28,6 +29,7 @@
       [ngClass]="[isLeftColumnCollapse ? 'left-column-collapse' : 'left-column-expand']"
     >
       <app-object-explorer
+        (selectedDatabase)="changeCurrentDatabase($event)"
         (selectObject)="changeCurrentObject($event)"
         (updateSpannerTable)="updateSpannerTable($event)"
         (updateSrcTable)="updateSrcTable($event)"
@@ -53,6 +55,7 @@
         [typeMap]="typeMap"
         [ddlStmts]="ddlStmts"
         [fkData]="fkData"
+        [currentDatabase]="currentDatabase"
       >
       </app-object-detail>
     </div>
diff --git a/ui/src/app/components/workspace/workspace.component.scss b/ui/src/app/components/workspace/workspace.component.scss
index 0cc5d10..b09cfa2 100644
--- a/ui/src/app/components/workspace/workspace.component.scss
+++ b/ui/src/app/components/workspace/workspace.component.scss
@@ -1,38 +1,3 @@
-.header {
-  padding: 0 20px;
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  border-bottom: 1px solid #cccccc;
-  padding-bottom: 10px;
-  .header_action {
-    button {
-      border: none;
-      background-color: inherit;
-      color: #1967d2;
-      font-size: 0.8rem;
-    }
-  }
-  .breadcrumb {
-    display: flex;
-    align-items: center;
-    .breadcrumb_source {
-      padding-left: 0;
-      color: rgba(0, 0, 0, 0.56);
-      font-weight: 400;
-      font-size: 14px;
-    }
-    .breadcrumb_workspace {
-      font-weight: 400;
-      font-size: 14px;
-    }
-    span {
-      font-size: 1.2rem;
-      color: rgb(143, 143, 143);
-    }
-  }
-}
-
 app-object-detail {
   height: 100%;
 }
diff --git a/ui/src/app/components/workspace/workspace.component.ts b/ui/src/app/components/workspace/workspace.component.ts
index adae54f..09ac86a 100644
--- a/ui/src/app/components/workspace/workspace.component.ts
+++ b/ui/src/app/components/workspace/workspace.component.ts
@@ -11,6 +11,7 @@ import ISchemaObjectNode, { FlatNode } from 'src/app/model/schema-object-node'
 import { ObjectExplorerNodeType, StorageKeys } from 'src/app/app.constants'
 import { IUpdateTableArgument } from 'src/app/model/update-table'
 import ConversionRate from 'src/app/model/conversion-rate'
+import { Router } from '@angular/router'
 
 @Component({
   selector: 'app-workspace',
@@ -40,11 +41,13 @@ export class WorkspaceComponent implements OnInit, OnDestroy {
   objectExplorerInitiallyRender: boolean = false
   srcDbName: string = localStorage.getItem(StorageKeys.SourceDbName) as string
   conversionRatePercentages: ConversionRate = { good: 0, ok: 0, bad: 0 }
+  currentDatabase: string = 'spanner'
   constructor(
     private data: DataService,
     private conversion: ConversionService,
     private dialog: MatDialog,
-    private sidenav: SidenavService
+    private sidenav: SidenavService,
+    private router: Router
   ) {
     this.currentObject = null
   }
@@ -151,7 +154,7 @@ export class WorkspaceComponent implements OnInit, OnDestroy {
   }
 
   changeCurrentObject(object: FlatNode) {
-    if (object.type === ObjectExplorerNodeType.Table) {
+    if (object?.type === ObjectExplorerNodeType.Table) {
       this.currentObject = object
       this.tableData = this.currentObject
         ? this.conversion.getColumnMapping(this.currentObject.name, this.conv)
@@ -161,12 +164,18 @@ export class WorkspaceComponent implements OnInit, OnDestroy {
       this.fkData = this.currentObject
         ? this.conversion.getFkMapping(this.currentObject.name, this.conv)
         : []
-    } else {
+    } else if (object?.type === ObjectExplorerNodeType.Index) {
       this.currentObject = object
       this.indexData = this.conversion.getIndexMapping(object.parent, this.conv, object.name)
+    } else {
+      this.currentObject = null
     }
   }
 
+  changeCurrentDatabase(database: string) {
+    this.currentDatabase = database
+  }
+
   updateIssuesLabel(count: number) {
     setTimeout(() => {
       this.issuesAndSuggestionsLabel = `ISSUES AND SUGGESTIONS (${count})`
@@ -233,4 +242,7 @@ export class WorkspaceComponent implements OnInit, OnDestroy {
     }
     return false
   }
+  prepareMigration() {
+    this.router.navigate(['/prepare-migration'])
+  }
 }
diff --git a/ui/src/app/model/conv.ts b/ui/src/app/model/conv.ts
index 4c735cf..4eee7c9 100644
--- a/ui/src/app/model/conv.ts
+++ b/ui/src/app/model/conv.ts
@@ -156,3 +156,13 @@ export interface IPkColumnDefs {
   Desc: boolean
   Order: number
 }
+
+export interface ISessionSummary {
+  DatabaseType: string
+  ConnectionDetail: string
+  SourceTableCount: number
+  SpannerTableCount: number
+  SourceIndexCount: number
+  SpannerIndexCount: number
+  ConnectionType: string
+}
\ No newline at end of file
diff --git a/ui/src/app/model/edit-table.ts b/ui/src/app/model/edit-table.ts
index 0882b94..1e7a526 100644
--- a/ui/src/app/model/edit-table.ts
+++ b/ui/src/app/model/edit-table.ts
@@ -1,6 +1,6 @@
 export default interface IColumnTabData {
   spOrder: number | string
-  srcOrder: number
+  srcOrder: number | string
   srcColName: string
   srcDataType: string
   spColName: string
diff --git a/ui/src/app/model/migrate.ts b/ui/src/app/model/migrate.ts
new file mode 100644
index 0000000..9f9e9c1
--- /dev/null
+++ b/ui/src/app/model/migrate.ts
@@ -0,0 +1,7 @@
+import ITargetDetails from "./target-details";
+
+export default interface IMigrationDetails {
+    TargetDetails: ITargetDetails
+    MigrationType: string
+    MigrationMode: string
+}
\ No newline at end of file
diff --git a/ui/src/app/model/target-details.ts b/ui/src/app/model/target-details.ts
new file mode 100644
index 0000000..273f768
--- /dev/null
+++ b/ui/src/app/model/target-details.ts
@@ -0,0 +1,5 @@
+export default interface ITargetDetails {
+    TargetDB: string
+    Dialect: string
+    StreamingConfig:string
+}
\ No newline at end of file
diff --git a/ui/src/app/services/conversion/conversion.service.ts b/ui/src/app/services/conversion/conversion.service.ts
index e61dfcd..e9f59dd 100644
--- a/ui/src/app/services/conversion/conversion.service.ts
+++ b/ui/src/app/services/conversion/conversion.service.ts
@@ -147,25 +147,47 @@ export class ConversionService {
 
   getColumnMapping(tableName: string, data: IConv): IColumnTabData[] {
     let srcTableName = data.ToSource[tableName].Name
-    return data.SrcSchema[srcTableName].ColNames.map((name: string, i: number) => {
-      let spColName = data.ToSpanner[srcTableName].Cols[name]
-      let srcPks = data.SrcSchema[srcTableName].PrimaryKeys
-      let spannerColDef = data.SpSchema[tableName].ColDefs[spColName]
-      return {
-        spOrder: spannerColDef ? i + 1 : '',
-        srcOrder: i + 1,
-        spColName: spannerColDef ? spColName : '',
-        spDataType: spannerColDef ? spannerColDef.T.Name : '',
-        srcColName: name,
-        srcDataType: data.SrcSchema[srcTableName].ColDefs[name].Type.Name,
-        spIsPk: spannerColDef
-          ? data.SpSchema[tableName].Pks.map((p) => p.Col).indexOf(spColName) !== -1
-          : false,
-        srcIsPk: srcPks ? srcPks.map((p) => p.Column).indexOf(name) !== -1 : false,
-        spIsNotNull: spannerColDef ? data.SpSchema[tableName].ColDefs[spColName].NotNull : false,
-        srcIsNotNull: data.SrcSchema[srcTableName].ColDefs[name].NotNull,
+    const res: IColumnTabData[] = data.SrcSchema[srcTableName].ColNames.map(
+      (name: string, i: number) => {
+        let spColName = data.ToSpanner[srcTableName].Cols[name]
+        let srcPks = data.SrcSchema[srcTableName].PrimaryKeys
+        let spannerColDef = data.SpSchema[tableName].ColDefs[spColName]
+        return {
+          spOrder: spannerColDef ? i + 1 : '',
+          srcOrder: i + 1,
+          spColName: spannerColDef ? spColName : '',
+          spDataType: spannerColDef ? spannerColDef.T.Name : '',
+          srcColName: name,
+          srcDataType: data.SrcSchema[srcTableName].ColDefs[name].Type.Name,
+          spIsPk: spannerColDef
+            ? data.SpSchema[tableName].Pks.map((p) => p.Col).indexOf(spColName) !== -1
+            : false,
+          srcIsPk: srcPks ? srcPks.map((p) => p.Column).indexOf(name) !== -1 : false,
+          spIsNotNull: spannerColDef ? data.SpSchema[tableName].ColDefs[spColName].NotNull : false,
+          srcIsNotNull: data.SrcSchema[srcTableName].ColDefs[name].NotNull,
+        }
+      }
+    )
+    data.SpSchema[srcTableName].ColNames.forEach((name: string, i: number) => {
+      if (data.SrcSchema[srcTableName].ColNames.indexOf(name) < 0) {
+        let spannerColDef = data.SpSchema[tableName].ColDefs[name]
+        res.push({
+          spOrder: i + 1,
+          srcOrder: '',
+          spColName: name,
+          spDataType: spannerColDef ? spannerColDef.T.Name : '',
+          srcColName: '',
+          srcDataType: '',
+          spIsPk: spannerColDef
+            ? data.SpSchema[tableName].Pks.map((p) => p.Col).indexOf(name) !== -1
+            : false,
+          srcIsPk: false,
+          spIsNotNull: spannerColDef ? data.SpSchema[tableName].ColDefs[name].NotNull : false,
+          srcIsNotNull: false,
+        })
       }
     })
+    return res
   }
 
   getPkMapping(tableData: IColumnTabData[]): IColumnTabData[] {
diff --git a/ui/src/app/services/fetch/fetch.service.ts b/ui/src/app/services/fetch/fetch.service.ts
index 6ec0f48..12b15f4 100644
--- a/ui/src/app/services/fetch/fetch.service.ts
+++ b/ui/src/app/services/fetch/fetch.service.ts
@@ -3,10 +3,12 @@ import { Injectable } from '@angular/core'
 import IDbConfig from 'src/app/model/db-config'
 import ISession, { ISaveSessionPayload } from '../../model/session'
 import IUpdateTable from '../../model/update-table'
-import IConv, { ICreateIndex, IInterleaveStatus, IPrimaryKey } from '../../model/conv'
+import IConv, { ICreateIndex, IInterleaveStatus, IPrimaryKey, ISessionSummary } from '../../model/conv'
 import IDumpConfig from '../../model/dump-config'
 import ISessionConfig from '../../model/session-config'
 import ISpannerConfig from '../../model/spanner-config'
+import ITargetDetails from '../../model/target-details'
+import IMigrationDetails from 'src/app/model/migrate'
 
 @Injectable({
   providedIn: 'root',
@@ -136,4 +138,12 @@ export class FetchService {
   setInterleave(tableName: string) {
     return this.http.get(`${this.url}/setparent?table=${tableName}&update=true`)
   }
+
+  getSourceDestinationSummary() {
+    return this.http.get<ISessionSummary>(`${this.url}/GetSourceDestinationSummary`)
+  }
+
+  migrate(payload: IMigrationDetails) {
+    return this.http.post(`${this.url}/Migrate`,payload)
+  }
 }
diff --git a/ui/src/app/services/target-details/target-details.service.spec.ts b/ui/src/app/services/target-details/target-details.service.spec.ts
new file mode 100644
index 0000000..2610d9a
--- /dev/null
+++ b/ui/src/app/services/target-details/target-details.service.spec.ts
@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { TargetDetailsService } from './target-details.service';
+
+describe('TargetDetailsService', () => {
+  let service: TargetDetailsService;
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({});
+    service = TestBed.inject(TargetDetailsService);
+  });
+
+  it('should be created', () => {
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/ui/src/app/services/target-details/target-details.service.ts b/ui/src/app/services/target-details/target-details.service.ts
new file mode 100644
index 0000000..f5b8753
--- /dev/null
+++ b/ui/src/app/services/target-details/target-details.service.ts
@@ -0,0 +1,18 @@
+import { Injectable } from '@angular/core';
+import ITargetDetails from 'src/app/model/target-details';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class TargetDetailsService {
+  targetDetail : ITargetDetails = {TargetDB:"", Dialect:"google_standard_sql",StreamingConfig:""};
+  constructor() { }
+  updateTargetDetails(details: ITargetDetails) {
+    this.targetDetail.TargetDB = details.TargetDB;
+    this.targetDetail.Dialect = details.Dialect;
+    this.targetDetail.StreamingConfig = details.StreamingConfig;
+  }
+  getTargetDetails() {
+    return this.targetDetail;
+  }
+}
\ No newline at end of file
diff --git a/ui/src/app/utils/utils.ts b/ui/src/app/utils/utils.ts
index 3fd6a78..ef8a238 100644
--- a/ui/src/app/utils/utils.ts
+++ b/ui/src/app/utils/utils.ts
@@ -4,7 +4,7 @@ export function extractSourceDbName(srcDbName: string) {
   if (srcDbName == 'mysql' || srcDbName == 'mysqldump') {
     return SourceDbNames.MySQL
   }
-  if (srcDbName === 'postgres' || srcDbName === 'pgdump') {
+  if (srcDbName === 'postgres' || srcDbName === 'pgdump' || srcDbName === 'pg_dump') {
     return SourceDbNames.Postgres
   }
   if (srcDbName === 'oracle') {
diff --git a/ui/src/styles.scss b/ui/src/styles.scss
index a15f6df..c5b337f 100644
--- a/ui/src/styles.scss
+++ b/ui/src/styles.scss
@@ -17,3 +17,75 @@ body {
 .cursor-pointer {
   cursor: pointer;
 }
+
+.header {
+  padding: 0 20px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  border-bottom: 1px solid #cccccc;
+  padding-bottom: 10px;
+  .header_action {
+    button {
+      border: none;
+      background-color: inherit;
+      color: #1967d2;
+      font-size: 0.8rem;
+    }
+  }
+  .breadcrumb {
+    display: flex;
+    align-items: center;
+    .breadcrumb_source {
+      padding-left: 0;
+      color: rgba(0, 0, 0, 0.56);
+      font-weight: 400;
+      font-size: 14px;
+    }
+    .breadcrumb_workspace {
+      font-weight: 400;
+      font-size: 14px;
+    }
+    span {
+      font-size: 1.2rem;
+      color: rgb(143, 143, 143);
+    }
+  }
+}
+
+.prerequisites {
+  padding: 0;
+  width: 600px;
+  .mat-card-title {
+    font-size: 13pt;
+    font-weight: normal;
+  }
+  .mat-card-subtitle {
+    font-size: 9pt;
+    font-weight: normal;
+  }
+  p {
+    color: #5c5c5c;
+    .bullet {
+      position: relative;
+      display: inline-block;
+      background-color: #e5e5e5;
+      padding: 0px 5px 10px 5px;
+      margin: 0;
+      border-radius: 12px;
+      font-size: 9px;
+      height: 9px;
+      width: 9px;
+      text-align: center;
+    }
+    span {
+      margin-left: 40px;
+      font-weight: lighter;
+      font-size: 13px;
+    }
+  }
+  hr {
+    border-color: #ffffff;
+    margin-bottom: 20px;
+  }
+}
\ No newline at end of file
diff --git a/webv2/config.json b/webv2/config.json
index c41b6f4..ca5c424 100755
--- a/webv2/config.json
+++ b/webv2/config.json
@@ -1,4 +1,4 @@
 {
-  "GCPProjectID": "",
-  "SpannerInstanceID": ""
-}
+    "GCPProjectID": "",
+    "SpannerInstanceID": ""
+}
\ No newline at end of file
diff --git a/webv2/helpers/helpers.go b/webv2/helpers/helpers.go
index 00da3a5..ff28d96 100644
--- a/webv2/helpers/helpers.go
+++ b/webv2/helpers/helpers.go
@@ -18,6 +18,7 @@ import (
 	"fmt"
 	"os"
 
+	"github.com/cloudspannerecosystem/harbourbridge/common/constants"
 	"github.com/cloudspannerecosystem/harbourbridge/common/utils"
 	"github.com/cloudspannerecosystem/harbourbridge/conversion"
 	"github.com/cloudspannerecosystem/harbourbridge/webv2/session"
@@ -35,3 +36,16 @@ func UpdateSessionFile() error {
 	}
 	return nil
 }
+
+func GetSourceDatabaseFromDriver(driver string) (string, error) {
+	switch driver {
+	case constants.MYSQLDUMP, constants.MYSQL:
+		return constants.MYSQL, nil
+	case constants.PGDUMP, constants.POSTGRES:
+		return constants.POSTGRES, nil
+	case constants.ORACLE, constants.SQLSERVER:
+		return driver, nil
+	default:
+		return "", fmt.Errorf("unsupported driver type: %v", driver)
+	}
+}
diff --git a/webv2/index/index.go b/webv2/index/index.go
new file mode 100644
index 0000000..3698e09
--- /dev/null
+++ b/webv2/index/index.go
@@ -0,0 +1,196 @@
+// Copyright 2022 Google LLC
+//
+// 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 index
+
+import (
+	"github.com/cloudspannerecosystem/harbourbridge/internal"
+	"github.com/cloudspannerecosystem/harbourbridge/spanner/ddl"
+	"github.com/cloudspannerecosystem/harbourbridge/webv2/session"
+	utilities "github.com/cloudspannerecosystem/harbourbridge/webv2/utilities"
+)
+
+// IndexSuggestion adds redundant index issue and interleved index suggestion in issues and suggestions tab.
+func IndexSuggestion() {
+
+	sessionState := session.GetSessionState()
+
+	for _, spannerTable := range sessionState.Conv.SpSchema {
+		CheckIndexSuggestion(spannerTable.Indexes, spannerTable)
+	}
+}
+
+// Helper method for checking Index Suggestion.
+func CheckIndexSuggestion(index []ddl.CreateIndex, spannerTable ddl.CreateTable) {
+
+	checkRedundantIndex(index, spannerTable)
+	checkInterleaveIndex(index, spannerTable)
+}
+
+// redundantIndex check for redundant Index.
+// If present adds Redundant as an issue in Issues.
+func checkRedundantIndex(index []ddl.CreateIndex, spannerTable ddl.CreateTable) {
+
+	var primaryKeyFirstColumn string
+	pks := spannerTable.Pks
+
+	for i := 0; i < len(index); i++ {
+
+		keys := index[i].Keys
+
+		for i := range pks {
+			if pks[i].Order == 1 {
+				primaryKeyFirstColumn = pks[i].Col
+				break
+			}
+		}
+
+		indexFirstColumn := index[i].Keys[0].Col
+
+		if primaryKeyFirstColumn == indexFirstColumn {
+			columnname := keys[i].Col
+			sessionState := session.GetSessionState()
+			schemaissue := sessionState.Conv.Issues[spannerTable.Name][columnname]
+			schemaissue = append(schemaissue, internal.RedundantIndex)
+			sessionState.Conv.Issues[spannerTable.Name][columnname] = schemaissue
+		}
+
+	}
+}
+
+// interleaveIndex suggests if an index can be converted to interleave.
+// If possible it gets added as a suggestion.
+func checkInterleaveIndex(index []ddl.CreateIndex, spannerTable ddl.CreateTable) {
+
+	// Suggestion gets added only if the table can be interleaved.
+	isInterleavable := spannerTable.Parent != ""
+
+	if isInterleavable {
+
+		var primaryKeyFirstColumn string
+		pks := spannerTable.Pks
+
+		for i := 0; i < len(index); i++ {
+
+			for i := range pks {
+				if pks[i].Order == 1 {
+					primaryKeyFirstColumn = pks[i].Col
+					break
+				}
+			}
+
+			indexFirstColumn := index[i].Keys[0].Col
+
+			sessionState := session.GetSessionState()
+
+			// Ensuring it is not a redundant index.
+			if primaryKeyFirstColumn != indexFirstColumn {
+
+				schemaissue := sessionState.Conv.Issues[spannerTable.Name][indexFirstColumn]
+				fks := spannerTable.Fks
+
+				for i := range fks {
+					if fks[i].Columns[0] == indexFirstColumn {
+						schemaissue = append(schemaissue, internal.InterleaveIndex)
+						sessionState.Conv.Issues[spannerTable.Name][indexFirstColumn] = schemaissue
+
+					}
+				}
+
+				// Interleave suggestion if the column is of type auto increment.
+				if utilities.IsSchemaIssuePresent(schemaissue, internal.AutoIncrement) {
+					schemaissue = append(schemaissue, internal.AutoIncrementIndex)
+					sessionState.Conv.Issues[spannerTable.Name][indexFirstColumn] = schemaissue
+				}
+
+				for _, c := range spannerTable.ColDefs {
+
+					if indexFirstColumn == c.Name {
+
+						if c.T.Name == ddl.Timestamp {
+
+							columnname := c.Name
+							sessionState := session.GetSessionState()
+							schemaissue := sessionState.Conv.Issues[spannerTable.Name][columnname]
+
+							schemaissue = append(schemaissue, internal.AutoIncrementIndex)
+							sessionState.Conv.Issues[spannerTable.Name][columnname] = schemaissue
+						}
+
+					}
+				}
+
+			}
+
+		}
+
+	}
+}
+
+// RemoveIndexIssues removes the issues in a column which is part of the passed Index.
+// This is called when we drop an index or make changes in the primarykey of the current table.
+// Editing the primary key can affect the issues in an index (eg. Changing pk order affects Redundant index issue).
+func RemoveIndexIssues(table string, Index ddl.CreateIndex) {
+
+	for i := 0; i < len(Index.Keys); i++ {
+
+		column := Index.Keys[i].Col
+
+		{
+			schemaissue := []internal.SchemaIssue{}
+			sessionState := session.GetSessionState()
+			if sessionState.Conv.Issues != nil {
+				schemaissue = sessionState.Conv.Issues[table][column]
+			}
+
+			if len(schemaissue) > 0 {
+
+				schemaissue = removeColumnIssue(schemaissue)
+
+				if sessionState.Conv.Issues[table][column] == nil {
+
+					s := map[string][]internal.SchemaIssue{
+						column: schemaissue,
+					}
+					sessionState.Conv.Issues = map[string]map[string][]internal.SchemaIssue{}
+
+					sessionState.Conv.Issues[table] = s
+
+				} else {
+
+					sessionState.Conv.Issues[table][column] = schemaissue
+
+				}
+			}
+		}
+	}
+}
+
+func removeColumnIssue(schemaissue []internal.SchemaIssue) []internal.SchemaIssue {
+
+	if utilities.IsSchemaIssuePresent(schemaissue, internal.RedundantIndex) {
+		schemaissue = utilities.RemoveSchemaIssue(schemaissue, internal.RedundantIndex)
+	}
+
+	if utilities.IsSchemaIssuePresent(schemaissue, internal.InterleaveIndex) {
+		schemaissue = utilities.RemoveSchemaIssue(schemaissue, internal.InterleaveIndex)
+	}
+
+	if utilities.IsSchemaIssuePresent(schemaissue, internal.AutoIncrementIndex) {
+		schemaissue = utilities.RemoveSchemaIssue(schemaissue, internal.AutoIncrementIndex)
+	}
+
+	return schemaissue
+
+}
diff --git a/webv2/primarykey/hotspot.go b/webv2/primarykey/hotspot.go
index c0de48a..8265ebf 100644
--- a/webv2/primarykey/hotspot.go
+++ b/webv2/primarykey/hotspot.go
@@ -91,12 +91,16 @@ func detecthotspotAutoincrement(spannerTable ddl.CreateTable, spannerColumnId st
 
 		if s.Id == spannerColumnId {
 
-			columnname := s.Name
-			sessionState := session.GetSessionState()
-			schemaissue := sessionState.Conv.Issues[spannerTable.Name][columnname]
+			if s.Ignored.AutoIncrement {
 
-			schemaissue = append(schemaissue, internal.HotspotAutoIncrement)
-			sessionState.Conv.Issues[spannerTable.Name][columnname] = schemaissue
+				columnname := s.Name
+				sessionState := session.GetSessionState()
+				schemaissue := sessionState.Conv.Issues[spannerTable.Name][columnname]
+
+				schemaissue = append(schemaissue, internal.HotspotAutoIncrement)
+				sessionState.Conv.Issues[spannerTable.Name][columnname] = schemaissue
+
+			}
 
 		}
 	}
diff --git a/webv2/primarykey/primarykey_handler.go b/webv2/primarykey/primarykey_handler.go
index 48ae943..a9307b6 100644
--- a/webv2/primarykey/primarykey_handler.go
+++ b/webv2/primarykey/primarykey_handler.go
@@ -23,6 +23,7 @@ import (
 
 	"github.com/cloudspannerecosystem/harbourbridge/spanner/ddl"
 	helpers "github.com/cloudspannerecosystem/harbourbridge/webv2/helpers"
+	"github.com/cloudspannerecosystem/harbourbridge/webv2/index"
 	"github.com/cloudspannerecosystem/harbourbridge/webv2/session"
 
 	"github.com/google/uuid"
@@ -111,6 +112,9 @@ func PrimaryKey(w http.ResponseWriter, r *http.Request) {
 	for _, table := range sessionState.Conv.SpSchema {
 		if pkRequest.TableId == table.Id {
 			sessionState.Conv.SpSchema[table.Name] = spannerTable
+			for _, ind := range spannerTable.Indexes {
+				index.RemoveIndexIssues(spannerTable.Name, ind)
+			}
 		}
 	}
 
diff --git a/webv2/routes.go b/webv2/routes.go
index a1915ab..e617da4 100644
--- a/webv2/routes.go
+++ b/webv2/routes.go
@@ -68,5 +68,10 @@ func getRoutes() *mux.Router {
 	router.HandleFunc("/GetConfig", config.GetConfig).Methods("GET")
 	router.HandleFunc("/SetSpannerConfig", config.SetSpannerConfig).Methods("POST")
 
+	// Run migration
+	router.HandleFunc("/Migrate", migrate).Methods("POST")
+
+	router.HandleFunc("/GetSourceDestinationSummary", getSourceDestinationSummary).Methods("GET")
+
 	return router
 }
diff --git a/webv2/session/session_handler.go b/webv2/session/session_handler.go
index c6e4aea..731761c 100644
--- a/webv2/session/session_handler.go
+++ b/webv2/session/session_handler.go
@@ -164,6 +164,9 @@ func ResumeSession(w http.ResponseWriter, r *http.Request) {
 	sessionState.Conv = &convm.Conv
 	sessionState.Driver = convm.DatabaseType
 	sessionState.DbName = convm.DatabaseName
+	sessionState.SourceDBConnDetails = SourceDBConnDetails{
+		ConnectionType: common.SESSION_FILE_MODE,
+	}
 
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(convm)
diff --git a/webv2/session/types.go b/webv2/session/types.go
index c9106e5..c510888 100644
--- a/webv2/session/types.go
+++ b/webv2/session/types.go
@@ -30,17 +30,27 @@ type ConvWithMetadata struct {
 	internal.Conv
 }
 
+type SourceDBConnDetails struct {
+	Host           string
+	Port           string
+	User           string
+	Password       string
+	Path           string
+	ConnectionType string
+}
+
 // SessionState stores information for the current migration session.
 type SessionState struct {
-	SourceDB          *sql.DB        // Connection to source database in case of direct connection
-	DbName            string         // Name of source database
-	Driver            string         // Name of HarbourBridge driver in use
-	Conv              *internal.Conv // Current conversion state
-	SessionFile       string         // Path to session file
-	IsOffline         bool           // True if the connection to remote metadata database is invalid
-	GCPProjectID      string
-	SpannerInstanceID string
-	SessionMetadata   SessionMetadata
+	SourceDB            *sql.DB             // Connection to source database in case of direct connection
+	SourceDBConnDetails SourceDBConnDetails // Connection details for source database
+	DbName              string              // Name of source database
+	Driver              string              // Name of HarbourBridge driver in use
+	Conv                *internal.Conv      // Current conversion state
+	SessionFile         string              // Path to session file
+	IsOffline           bool                // True if the connection to remote metadata database is invalid
+	GCPProjectID        string
+	SpannerInstanceID   string
+	SessionMetadata     SessionMetadata
 	Counter
 }
 
diff --git a/webv2/uniqueid/uniqueid_assignment.go b/webv2/uniqueid/uniqueid_assignment.go
index 0e8b0d1..5f24b86 100644
--- a/webv2/uniqueid/uniqueid_assignment.go
+++ b/webv2/uniqueid/uniqueid_assignment.go
@@ -124,7 +124,7 @@ func AssignUniqueId(conv *internal.Conv) {
 func updateSpannerTableIndexKeyOrder(spannertable ddl.CreateTable) {
 
 	for i := 0; i < len(spannertable.Pks); i++ {
-		for spannercolumnname, _ := range spannertable.ColDefs {
+		for spannercolumnname := range spannertable.ColDefs {
 			if spannertable.Pks[i].Col == spannercolumnname {
 
 				o := getSpannerColumnIndex(spannertable, spannercolumnname)
@@ -139,7 +139,7 @@ func updateSpannerTableIndexKeyOrder(spannertable ddl.CreateTable) {
 func updateSourceTableIndexKeyOrder(sourcetable schema.Table) {
 
 	for i := 0; i < len(sourcetable.PrimaryKeys); i++ {
-		for sourcecolumnname, _ := range sourcetable.ColDefs {
+		for sourcecolumnname := range sourcetable.ColDefs {
 			if sourcetable.PrimaryKeys[i].Column == sourcecolumnname {
 
 				o := getSourceColumnIndex(sourcetable, sourcecolumnname)
diff --git a/webv2/utilities/constants.go b/webv2/utilities/constants.go
new file mode 100644
index 0000000..6c65efa
--- /dev/null
+++ b/webv2/utilities/constants.go
@@ -0,0 +1,23 @@
+// Copyright 2022 Google LLC
+//
+// 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 common
+
+const (
+	DUMP_MODE           = "dumpFile"
+	DIRECT_CONNECT_MODE = "directConnect"
+	SESSION_FILE_MODE   = "sessionFile"
+	SCHEMA_ONLY         = "Schema"
+	DATA_ONLY           = "Data"
+)
diff --git a/webv2/utilities/utilities.go b/webv2/utilities/utilities.go
index 04b49ff..86fb0a3 100644
--- a/webv2/utilities/utilities.go
+++ b/webv2/utilities/utilities.go
@@ -164,12 +164,15 @@ func IsColumnPresent(columns []string, col string) string {
 // RemoveSchemaIssue removes issue from the given list.
 func RemoveSchemaIssue(schemaissue []internal.SchemaIssue, issue internal.SchemaIssue) []internal.SchemaIssue {
 
-	for i := 0; i < len(schemaissue); i++ {
-		if schemaissue[i] == issue {
-			schemaissue = append(schemaissue[:i], schemaissue[i+1:]...)
+	k := 0
+	for i := 0; i < len(schemaissue); {
+		if schemaissue[i] != issue {
+			schemaissue[k] = schemaissue[i]
+			k++
 		}
+		i++
 	}
-	return schemaissue
+	return schemaissue[0:k]
 }
 
 // IsSchemaIssuePresent checks if issue is present in the given schemaissue list.
diff --git a/webv2/web.go b/webv2/web.go
index fe3af70..6fab46a 100644
--- a/webv2/web.go
+++ b/webv2/web.go
@@ -20,6 +20,7 @@ package webv2
 import (
 	"bufio"
 	"bytes"
+	"context"
 	"database/sql"
 	"encoding/json"
 	"fmt"
@@ -35,6 +36,7 @@ import (
 	"strings"
 	"time"
 
+	"github.com/cloudspannerecosystem/harbourbridge/cmd"
 	"github.com/cloudspannerecosystem/harbourbridge/common/constants"
 	"github.com/cloudspannerecosystem/harbourbridge/common/utils"
 	"github.com/cloudspannerecosystem/harbourbridge/conversion"
@@ -54,6 +56,7 @@ import (
 	_ "github.com/go-sql-driver/mysql"
 	"github.com/gorilla/handlers"
 
+	index "github.com/cloudspannerecosystem/harbourbridge/webv2/index"
 	primarykey "github.com/cloudspannerecosystem/harbourbridge/webv2/primarykey"
 
 	uniqueid "github.com/cloudspannerecosystem/harbourbridge/webv2/uniqueid"
@@ -90,6 +93,27 @@ type driverConfig struct {
 	Password string `json:"Password"`
 }
 
+type sessionSummary struct {
+	DatabaseType      string
+	ConnectionDetail  string
+	SourceTableCount  int
+	SpannerTableCount int
+	SourceIndexCount  int
+	SpannerIndexCount int
+	ConnectionType    string
+}
+
+type migrationDetails struct {
+	TargetDetails targetDetails `json:"TargetDetails"`
+	MigrationMode string        `json:MigrationMode`
+	MigrationType string        `json:MigrationType`
+}
+
+type targetDetails struct {
+	TargetDB     string `json:"TargetDB"`
+	StreamingCfg string `json:"StreamingConfig"`
+}
+
 // databaseConnection creates connection with database when using
 // with postgres and mysql driver.
 func databaseConnection(w http.ResponseWriter, r *http.Request) {
@@ -140,6 +164,13 @@ func databaseConnection(w http.ResponseWriter, r *http.Request) {
 	}
 	sessionState.Driver = config.Driver
 	sessionState.SessionFile = ""
+	sessionState.SourceDBConnDetails = session.SourceDBConnDetails{
+		Host:           config.Host,
+		Port:           config.Port,
+		User:           config.User,
+		Password:       config.Password,
+		ConnectionType: utilities.DIRECT_CONNECT_MODE,
+	}
 	w.WriteHeader(http.StatusOK)
 }
 
@@ -179,6 +210,7 @@ func convertSchemaSQL(w http.ResponseWriter, r *http.Request) {
 	sessionState.Conv = conv
 
 	primarykey.DetectHotspot()
+	index.IndexSuggestion()
 
 	sessionMetadata := session.SessionMetadata{
 		SessionName:  "NewSession",
@@ -245,12 +277,17 @@ func convertSchemaDump(w http.ResponseWriter, r *http.Request) {
 	uniqueid.AssignUniqueId(conv)
 	sessionState.Conv = conv
 	primarykey.DetectHotspot()
+	index.IndexSuggestion()
 
 	sessionState.SessionMetadata = sessionMetadata
 	sessionState.Driver = dc.Driver
 	sessionState.DbName = ""
 	sessionState.SessionFile = ""
 	sessionState.SourceDB = nil
+	sessionState.SourceDBConnDetails = session.SourceDBConnDetails{
+		Path:           dc.FilePath,
+		ConnectionType: utilities.DUMP_MODE,
+	}
 
 	convm := session.ConvWithMetadata{
 		SessionMetadata: sessionMetadata,
@@ -302,10 +339,15 @@ func loadSession(w http.ResponseWriter, r *http.Request) {
 	sessionState.Conv = conv
 
 	primarykey.DetectHotspot()
+	index.IndexSuggestion()
 
 	sessionState.SessionMetadata = sessionMetadata
 	sessionState.Driver = s.Driver
 	sessionState.SessionFile = s.FilePath
+	sessionState.SourceDBConnDetails = session.SourceDBConnDetails{
+		Path:           s.FilePath,
+		ConnectionType: utilities.SESSION_FILE_MODE,
+	}
 
 	convm := session.ConvWithMetadata{
 		SessionMetadata: sessionMetadata,
@@ -613,6 +655,8 @@ func setParentTable(w http.ResponseWriter, r *http.Request) {
 		http.Error(w, fmt.Sprintf("Table name is empty"), http.StatusBadRequest)
 	}
 	tableInterleaveStatus := parentTableHelper(table, update)
+
+	index.IndexSuggestion()
 	helpers.UpdateSessionFile()
 	w.WriteHeader(http.StatusOK)
 
@@ -649,14 +693,13 @@ func parentTableHelper(table string, update bool) *TableInterleaveStatus {
 			if checkPrimaryKeyPrefix(table, refTable, fk, tableInterleaveStatus) {
 
 				tableInterleaveStatus.Parent = refTable
+				sp := sessionState.Conv.SpSchema[table]
 
 				if update {
-					sp := sessionState.Conv.SpSchema[table]
 					sp.Parent = refTable
 					sp.Fks = removeFk(sp.Fks, i)
-					sessionState.Conv.SpSchema[table] = sp
 				}
-
+				sessionState.Conv.SpSchema[table] = sp
 				break
 			}
 		}
@@ -946,6 +989,7 @@ func addIndexes(w http.ResponseWriter, r *http.Request) {
 
 	sp := sessionState.Conv.SpSchema[table]
 
+	index.CheckIndexSuggestion(newIndexes, sp)
 	for i := 0; i < len(newIndexes); i++ {
 		newIndexes[i].Id = uniqueid.GenerateIndexesId()
 	}
@@ -963,6 +1007,112 @@ func addIndexes(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(convm)
 }
 
+func getSourceDestinationSummary(w http.ResponseWriter, r *http.Request) {
+	sessionState := session.GetSessionState()
+	var sessionSummary sessionSummary
+	databaseType, err := helpers.GetSourceDatabaseFromDriver(sessionState.Driver)
+	if err != nil {
+		http.Error(w, fmt.Sprintf("Error while getting source database: %v", err), http.StatusBadRequest)
+		return
+	}
+	sessionSummary.DatabaseType = databaseType
+	sessionSummary.ConnectionType = sessionState.SourceDBConnDetails.ConnectionType
+	sessionSummary.SourceTableCount = len(sessionState.Conv.SrcSchema)
+	sessionSummary.SpannerTableCount = len(sessionState.Conv.SpSchema)
+
+	sourceIndexCount, spannerIndexCount := 0, 0
+	for _, spannerSchema := range sessionState.Conv.SpSchema {
+		spannerIndexCount = spannerIndexCount + len(spannerSchema.Indexes)
+	}
+	for _, sourceSchema := range sessionState.Conv.SrcSchema {
+		sourceIndexCount = sourceIndexCount + len(sourceSchema.Indexes)
+	}
+	sessionSummary.SourceIndexCount = sourceIndexCount
+	sessionSummary.SpannerIndexCount = spannerIndexCount
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(sessionSummary)
+}
+
+func migrate(w http.ResponseWriter, r *http.Request) {
+
+	log.Println("request started", "method", r.Method, "path", r.URL.Path)
+	reqBody, err := ioutil.ReadAll(r.Body)
+	if err != nil {
+		log.Println("request's body Read Error")
+		http.Error(w, fmt.Sprintf("Body Read Error : %v", err), http.StatusInternalServerError)
+	}
+
+	details := migrationDetails{}
+	err = json.Unmarshal(reqBody, &details)
+	if err != nil {
+		log.Println("request's Body parse error")
+		http.Error(w, fmt.Sprintf("Request Body parse error : %v", err), http.StatusBadRequest)
+		return
+	}
+
+	sessionState := session.GetSessionState()
+	ctx := context.Background()
+	sourceProfile, targetProfile, ioHelper, dbName, err := getSourceAndTargetProfiles(sessionState, details)
+	if err != nil {
+		log.Println("can't get source and target profile")
+		http.Error(w, fmt.Sprintf("Can't get source and target profiles: %v", err), http.StatusBadRequest)
+		return
+	}
+
+	if details.MigrationMode == utilities.SCHEMA_ONLY {
+		_, err = cmd.MigrateDatabase(ctx, targetProfile, sourceProfile, dbName, &ioHelper, &cmd.SchemaCmd{}, sessionState.Conv)
+	} else if details.MigrationMode == utilities.DATA_ONLY {
+		dataCmd := &cmd.DataCmd{
+			SkipForeignKeys: false,
+			WriteLimit:      cmd.DefaultWritersLimit,
+		}
+		_, err = cmd.MigrateDatabase(ctx, targetProfile, sourceProfile, dbName, &ioHelper, dataCmd, sessionState.Conv)
+	} else {
+		schemaAndDataCmd := &cmd.SchemaAndDataCmd{
+			SkipForeignKeys: false,
+			WriteLimit:      cmd.DefaultWritersLimit,
+		}
+		_, err = cmd.MigrateDatabase(ctx, targetProfile, sourceProfile, dbName, &ioHelper, schemaAndDataCmd, sessionState.Conv)
+	}
+	if err != nil {
+		log.Println("can't finish database migration")
+		http.Error(w, fmt.Sprintf("Can't finish database migration: %v", err), http.StatusBadRequest)
+		return
+	}
+	w.WriteHeader(http.StatusOK)
+	log.Println("migration completed", "method", r.Method, "path", r.URL.Path, "remoteaddr", r.RemoteAddr)
+}
+
+func getSourceAndTargetProfiles(sessionState *session.SessionState, details migrationDetails) (profiles.SourceProfile, profiles.TargetProfile, utils.IOStreams, string, error) {
+	var (
+		sourceProfileString string
+	)
+	sourceDBConnectionDetails := sessionState.SourceDBConnDetails
+	if sourceDBConnectionDetails.ConnectionType == utilities.DUMP_MODE {
+		sourceProfileString = fmt.Sprintf("file=%v,format=dump", sourceDBConnectionDetails.Path)
+	} else {
+		sourceProfileString = fmt.Sprintf("host=%v,port=%v,user=%v,password=%v,dbName=%v",
+			sourceDBConnectionDetails.Host, sourceDBConnectionDetails.Port, sourceDBConnectionDetails.User,
+			sourceDBConnectionDetails.Password, sessionState.DbName)
+	}
+	targetProfileString := fmt.Sprintf("project=%v,instance=%v,dbName=%v", sessionState.GCPProjectID, sessionState.SpannerInstanceID, details.TargetDetails.TargetDB)
+
+	if details.TargetDetails.StreamingCfg != "" {
+		sourceProfileString = sourceProfileString + fmt.Sprintf(",streamingCfg=%v", details.TargetDetails.StreamingCfg)
+	}
+	source, err := helpers.GetSourceDatabaseFromDriver(sessionState.Driver)
+	if err != nil {
+		return profiles.SourceProfile{}, profiles.TargetProfile{}, utils.IOStreams{}, "", fmt.Errorf("error while getting source database: %v", err)
+	}
+	sourceProfile, targetProfile, ioHelper, dbName, err := cmd.PrepareMigrationPrerequisites(sourceProfileString, targetProfileString, source)
+	if err != nil && sourceDBConnectionDetails.ConnectionType != utilities.SESSION_FILE_MODE {
+		return profiles.SourceProfile{}, profiles.TargetProfile{}, utils.IOStreams{}, "", fmt.Errorf("error while preparing prerequisites for migration: %v", err)
+	}
+	sourceProfile.Driver = sessionState.Driver
+	targetProfile.TargetDb = targetProfile.ToLegacyTargetDb()
+	return sourceProfile, targetProfile, ioHelper, dbName, nil
+}
+
 func updateIndexes(w http.ResponseWriter, r *http.Request) {
 	table := r.FormValue("table")
 	reqBody, err := ioutil.ReadAll(r.Body)
@@ -981,9 +1131,11 @@ func updateIndexes(w http.ResponseWriter, r *http.Request) {
 
 	st := sessionState.Conv.SrcSchema[table]
 
-	for i, index := range sp.Indexes {
+	for i, ind := range sp.Indexes {
 
-		if index.Table == newIndexes[0].Table && index.Name == newIndexes[0].Name {
+		if ind.Table == newIndexes[0].Table && ind.Name == newIndexes[0].Name {
+
+			index.RemoveIndexIssues(table, sp.Indexes[i])
 
 			sp.Indexes[i].Keys = newIndexes[0].Keys
 			sp.Indexes[i].Name = newIndexes[0].Name
@@ -1105,6 +1257,9 @@ func dropSecondaryIndex(w http.ResponseWriter, r *http.Request) {
 		http.Error(w, fmt.Sprintf("No secondary index found at position %d", position), http.StatusBadRequest)
 		return
 	}
+
+	index.RemoveIndexIssues(table, sp.Indexes[position])
+
 	sp.Indexes = removeSecondaryIndex(sp.Indexes, position)
 	sessionState.Conv.SpSchema[table] = sp
 	helpers.UpdateSessionFile()


Test patch:
diff --git a/.github/workflows/integration-tests-against-emulator.yaml b/.github/workflows/integration-tests-against-emulator.yaml
index da9fbce..7012da8 100644
--- a/.github/workflows/integration-tests-against-emulator.yaml
+++ b/.github/workflows/integration-tests-against-emulator.yaml
@@ -13,6 +13,8 @@
 # limitations under the License.
 
 on:
+  schedule:
+    - cron:  '0 0 * * *'
   push:
     branches:
       - master
diff --git a/sources/sqlserver/infoschema_test.go b/sources/sqlserver/infoschema_test.go
index fb2bc74..b85dac5 100644
--- a/sources/sqlserver/infoschema_test.go
+++ b/sources/sqlserver/infoschema_test.go
@@ -68,7 +68,7 @@ func TestProcessSchema(t *testing.T) {
 		}, {
 			query: "SELECT (.+) FROM sys.indexes (.+)",
 			args:  []driver.Value{"user", "dbo"},
-			cols:  []string{"index_name", "column_name", "column_position", "is_unique", "order"},
+			cols:  []string{"index_name", "column_name", "column_position", "is_unique", "order", "is_included_column"},
 		}, {
 			query: "SELECT (.+) FROM information_schema.COLUMNS (.+)",
 			args:  []driver.Value{"dbo", "user"},
@@ -92,7 +92,7 @@ func TestProcessSchema(t *testing.T) {
 		}, {
 			query: "SELECT (.+) FROM sys.indexes (.+)",
 			args:  []driver.Value{"test", "dbo"},
-			cols:  []string{"index_name", "column_name", "column_position", "is_unique", "order"},
+			cols:  []string{"index_name", "column_name", "column_position", "is_unique", "order", "is_included_column"},
 		}, {
 			query: "SELECT (.+) FROM information_schema.COLUMNS (.+)",
 			args:  []driver.Value{"dbo", "test"},
@@ -156,12 +156,12 @@ func TestProcessSchema(t *testing.T) {
 		}, {
 			query: "SELECT (.+) FROM sys.indexes (.+)",
 			args:  []driver.Value{"cart", "dbo"},
-			cols:  []string{"index_name", "column_name", "is_unique", "order"},
-			rows: [][]driver.Value{{"index1", "userid", "false", "ASC"},
-				{"index2", "userid", "true", "ASC"},
-				{"index2", "productid", "true", "DESC"},
-				{"index3", "productid", "true", "DESC"},
-				{"index3", "userid", "true", "ASC"},
+			cols:  []string{"index_name", "column_name", "is_unique", "order", "is_included_column"},
+			rows: [][]driver.Value{{"index1", "userid", "false", "ASC", "false"},
+				{"index2", "userid", "true", "ASC", "false"},
+				{"index2", "productid", "true", "DESC", "true"},
+				{"index3", "productid", "true", "DESC", "false"},
+				{"index3", "userid", "true", "ASC", "false"},
 			},
 		}, {
 			query: "SELECT (.+) FROM information_schema.COLUMNS (.+)",
@@ -187,7 +187,7 @@ func TestProcessSchema(t *testing.T) {
 		}, {
 			query: "SELECT (.+) FROM sys.indexes (.+)",
 			args:  []driver.Value{"product", "production"},
-			cols:  []string{"index_name", "column_name", "column_position", "is_unique", "order"},
+			cols:  []string{"index_name", "column_name", "column_position", "is_unique", "order", "is_included_column"},
 		}, {
 			query: "SELECT (.+) FROM information_schema.COLUMNS (.+)",
 			args:  []driver.Value{"production", "product"},
@@ -213,7 +213,7 @@ func TestProcessSchema(t *testing.T) {
 		}, {
 			query: "SELECT (.+) FROM sys.indexes (.+)",
 			args:  []driver.Value{"test_ref", "dbo"},
-			cols:  []string{"index_name", "column_name", "column_position", "is_unique", "order"},
+			cols:  []string{"index_name", "column_name", "column_position", "is_unique", "order", "is_included_column"},
 		}, {
 			query: "SELECT (.+) FROM information_schema.COLUMNS (.+)",
 			args:  []driver.Value{"dbo", "test_ref"},
@@ -301,7 +301,7 @@ func TestProcessSchema(t *testing.T) {
 			Fks: []ddl.Foreignkey{{Name: "fk_test2", Columns: []string{"productid"}, ReferTable: "production_product", ReferColumns: []string{"product_id"}},
 				{Name: "fk_test3", Columns: []string{"userid"}, ReferTable: "user", ReferColumns: []string{"user_id"}}},
 			Indexes: []ddl.CreateIndex{{Name: "index1", Table: "cart", Unique: false, Keys: []ddl.IndexKey{{Col: "userid", Desc: false}}},
-				{Name: "index2", Table: "cart", Unique: true, Keys: []ddl.IndexKey{{Col: "userid", Desc: false}, {Col: "productid", Desc: true}}},
+				{Name: "index2", Table: "cart", Unique: true, Keys: []ddl.IndexKey{{Col: "userid", Desc: false}}, StoredColumns: []string{"productid"}},
 				{Name: "index3", Table: "cart", Unique: true, Keys: []ddl.IndexKey{{Col: "productid", Desc: true}, {Col: "userid", Desc: false}}}}},
 		"production_product": {
 			Name:     "production_product",
diff --git a/spanner/ddl/ast_test.go b/spanner/ddl/ast_test.go
index fec721c..0a46e14 100644
--- a/spanner/ddl/ast_test.go
+++ b/spanner/ddl/ast_test.go
@@ -263,6 +263,7 @@ func TestPrintCreateIndex(t *testing.T) {
 			/*Unique =*/ false,
 			[]IndexKey{{Col: "col1", Desc: true}, {Col: "col2"}},
 			"1",
+			nil,
 		},
 		{
 			"myindex2",
@@ -270,6 +271,7 @@ func TestPrintCreateIndex(t *testing.T) {
 			/*Unique =*/ true,
 			[]IndexKey{{Col: "col1", Desc: true}, {Col: "col2"}},
 			"1",
+			nil,
 		}}
 	tests := []struct {
 		name       string
diff --git a/webv2/web_test.go b/webv2/web_test.go
index 1cff355..23c556f 100644
--- a/webv2/web_test.go
+++ b/webv2/web_test.go
@@ -2201,8 +2201,8 @@ func TestAddIndexes(t *testing.T) {
 			conv: &internal.Conv{
 				SpSchema: map[string]ddl.CreateTable{
 					"t1": {
-						Indexes: []ddl.CreateIndex{{Name: "idx1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}}},
-							{Name: "idx2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}}}},
+						Indexes: []ddl.CreateIndex{{Name: "idx1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+							{Name: "idx2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}}},
 					}},
 				Audit: internal.Audit{
 					MigrationType: migration.MigrationData_SCHEMA_ONLY.Enum(),
@@ -2211,8 +2211,8 @@ func TestAddIndexes(t *testing.T) {
 			expectedConv: &internal.Conv{
 				SpSchema: map[string]ddl.CreateTable{
 					"t1": {
-						Indexes: []ddl.CreateIndex{{Name: "idx1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}}},
-							{Name: "idx2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}}}},
+						Indexes: []ddl.CreateIndex{{Name: "idx1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+							{Name: "idx2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}}},
 					}},
 			},
 		},