assessment/report_generator.go (640 lines of code) (raw):
/* Copyright 2025 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 assessment
import (
"encoding/csv"
"encoding/json"
"fmt"
"os"
"slices"
"strconv"
"strings"
"github.com/GoogleCloudPlatform/spanner-migration-tool/assessment/utils"
"github.com/GoogleCloudPlatform/spanner-migration-tool/common/constants"
"github.com/GoogleCloudPlatform/spanner-migration-tool/logger"
"github.com/GoogleCloudPlatform/spanner-migration-tool/spanner/ddl"
)
type SchemaReportRow struct {
element string
elementType string // consider enum ?
sourceName string
sourceDefinition string
sourceTableName string //populate table name where applicable
targetName string
targetDefinition string
//DB
dbChangeType string
dbChangeEffort string
dbChanges string
dbImpact string
//Code
codeChangeType string // consider enum ?
codeChangeEffort string
codeImpactedFiles string
codeSnippets string
//Action Item
actionItems *[]string
}
type CodeReportRow struct {
snippetId string
relativeFilePath string
sourceDefinition string
suggestedDefinition string
loc int
schemaRelated string
explanation string
}
func dumpCsvReport(fileName string, records [][]string) {
f, err := os.Create(fileName)
if err != nil {
logger.Log.Error(fmt.Sprintf("Can't create csv file %s: %v", fileName, err))
return
}
defer f.Close()
w := csv.NewWriter(f)
w.Comma = '\t'
w.UseCRLF = true
w.WriteAll(records)
}
func writeRawSnippets(assessmentsFolder string, snippets []utils.Snippet) {
f, err := os.Create(assessmentsFolder + "raw_snippets.txt")
if err != nil {
logger.Log.Error(fmt.Sprintf("Can't create raw snippets file %s: %v", assessmentsFolder, err))
return
}
defer f.Close()
jsonWriter := json.NewEncoder(f)
jsonWriter.Encode(snippets)
logger.Log.Info("completed publishing raw snippets")
}
func generateCodeSummary(appAssessment *utils.AppCodeAssessmentOutput) [][]string {
//Add codebase details
var rows [][]string
rows = append(rows, []string{"Language", appAssessment.Language})
rows = append(rows, []string{"Framework", appAssessment.Framework})
rows = append(rows, []string{"App Code Files", fmt.Sprint(appAssessment.TotalFiles)})
rows = append(rows, []string{"Lines of code", fmt.Sprint(appAssessment.TotalLoc)})
rows = append(rows, getNonSchemaChangeHeaders())
codeReportRows := convertToCodeReportRows(appAssessment.CodeSnippets)
for _, codeReportRow := range codeReportRows {
var row []string
row = append(row, utils.SanitizeCsvRow(&codeReportRow.snippetId))
row = append(row, utils.SanitizeCsvRow(&codeReportRow.relativeFilePath))
row = append(row, utils.SanitizeCsvRow(&codeReportRow.sourceDefinition))
row = append(row, utils.SanitizeCsvRow(&codeReportRow.suggestedDefinition))
row = append(row, fmt.Sprint(codeReportRow.loc))
row = append(row, utils.SanitizeCsvRow(&codeReportRow.schemaRelated))
row = append(row, utils.SanitizeCsvRow(&codeReportRow.explanation))
rows = append(rows, row)
}
return rows
}
func convertToCodeReportRows(snippets *[]utils.Snippet) []CodeReportRow {
rows := []CodeReportRow{}
for _, snippet := range *snippets {
row := CodeReportRow{}
row.snippetId = snippet.Id
row.relativeFilePath = snippet.RelativeFilePath
if strings.TrimSpace(snippet.SourceMethodSignature) == "" {
row.sourceDefinition = strings.Join(snippet.SourceCodeSnippet, "\n")
row.suggestedDefinition = strings.Join(snippet.SuggestedCodeSnippet, "\n")
} else {
row.sourceDefinition = snippet.SourceMethodSignature
row.suggestedDefinition = snippet.SuggestedMethodSignature
}
if snippet.NumberOfAffectedLines > 0 {
row.loc = snippet.NumberOfAffectedLines
} else {
row.loc = len(snippet.SourceCodeSnippet)
}
if strings.TrimSpace(snippet.SchemaChange) == "" {
row.schemaRelated = "No"
} else {
row.schemaRelated = "Yes"
}
if strings.TrimSpace(snippet.Explanation) == "" {
if strings.TrimSpace(snippet.TableName) == "" {
row.explanation = ""
} else {
row.explanation = "changes to " + snippet.TableName
}
} else {
row.explanation = snippet.Explanation
}
if row.loc > 0 {
rows = append(rows, row)
}
}
return rows
}
func getNonSchemaChangeHeaders() []string {
headers := []string{
"Snippet Id",
"File",
"Source Definition",
"Suggested Definition",
"Number of Lines Affected",
"Related to schema change",
"Explanation",
}
return headers
}
func GenerateReport(dbName string, assessmentOutput utils.AssessmentOutput) {
folderPath := "assessment_" + dbName + "/"
err := os.Mkdir(folderPath, 0755)
if err != nil {
logger.Log.Warn("unable to create directory to dump assessment report")
return
}
logger.Log.Info("assessment reports will be saved in folder: " + folderPath)
schemaFile := folderPath + "schema.csv"
dumpCsvReport(schemaFile, generateSchemaReport(assessmentOutput))
logger.Log.Info("completed publishing schema report at: " + schemaFile)
if assessmentOutput.AppCodeAssessment != nil && assessmentOutput.AppCodeAssessment.TotalFiles > 0 {
codeChangesFile := folderPath + "code_changes.csv"
dumpCsvReport(codeChangesFile, generateCodeSummary(assessmentOutput.AppCodeAssessment))
logger.Log.Info("completed publishing code changes report: " + codeChangesFile)
writeRawSnippets(folderPath, *assessmentOutput.AppCodeAssessment.CodeSnippets)
logger.Log.Info("completed publishing code changes report")
} else {
logger.Log.Info("not performing application assessment as code is not detected")
}
logger.Log.Info("assessment complete!")
}
func generateSchemaReport(assessmentOutput utils.AssessmentOutput) [][]string {
var records [][]string
headers := getSchemaHeaders()
records = append(records, headers)
schemaReportRows := convertToSchemaReportRows(assessmentOutput)
for _, schemaRow := range schemaReportRows {
var row []string
//row = append(row, schemaRow.element)
row = append(row, utils.SanitizeCsvRow(&schemaRow.elementType))
row = append(row, utils.SanitizeCsvRow(&schemaRow.sourceTableName))
row = append(row, utils.SanitizeCsvRow(&schemaRow.sourceName))
row = append(row, utils.SanitizeCsvRow(&schemaRow.sourceDefinition))
row = append(row, utils.SanitizeCsvRow(&schemaRow.targetName))
row = append(row, utils.SanitizeCsvRow(&schemaRow.targetDefinition))
// DB
row = append(row, utils.SanitizeCsvRow(&schemaRow.dbChangeEffort))
row = append(row, utils.SanitizeCsvRow(&schemaRow.dbChanges))
row = append(row, utils.SanitizeCsvRow(&schemaRow.dbImpact))
// CODE
//row = append(row, utils.SanitizeCsvRow(schemaRow.codeChangeEffort)
row = append(row, utils.SanitizeCsvRow(&schemaRow.codeChangeType))
row = append(row, utils.SanitizeCsvRow(&schemaRow.codeImpactedFiles))
row = append(row, utils.SanitizeCsvRow(&schemaRow.codeSnippets))
actionItemsStr := utils.JoinString(schemaRow.actionItems, "None")
row = append(row, utils.SanitizeCsvRow(&actionItemsStr))
records = append(records, row)
}
return records
}
func getSchemaHeaders() []string {
headers := []string{
//"Element",
"Element Type",
"Source Table Name",
"Source Name",
"Source Definition",
"Target Name",
"Target Definition",
//DB
"DB Change Effort",
"DB Changes",
"DB Impact",
//CODE
"Code Change Type",
//"Code Change Effort",
"Impacted Files",
"Code Snippet References",
"Action Items",
}
return headers
}
func convertToSchemaReportRows(assessmentOutput utils.AssessmentOutput) []SchemaReportRow {
rows := []SchemaReportRow{}
//Populate table info
for _, tableAssessment := range assessmentOutput.SchemaAssessment.TableAssessmentOutput {
spTable := tableAssessment.SpannerTableDef
row := SchemaReportRow{}
row.element = tableAssessment.SourceTableDef.Name
row.elementType = "Table"
row.sourceTableName = tableAssessment.SourceTableDef.Name
row.sourceName = tableAssessment.SourceTableDef.Name
row.sourceDefinition = tableDefinitionToString(*tableAssessment.SourceTableDef)
row.targetName = spTable.Name
row.targetDefinition = "N/A"
row.dbChangeEffort = "Automatic"
row.dbChanges, row.dbImpact = calculateTableDbChangesAndImpact(tableAssessment)
//Populate code info
populateTableCodeImpact(*tableAssessment.SourceTableDef, *tableAssessment.SpannerTableDef, assessmentOutput.AppCodeAssessment.CodeSnippets, &row)
rows = append(rows, row)
//Populate column info
for _, columnAssessment := range tableAssessment.Columns {
spColumn := columnAssessment.SpannerColDef
column := columnAssessment.SourceColDef
row := SchemaReportRow{}
row.element = column.TableName + "." + column.Name
row.elementType = getElementTypeForColumn(*column)
row.sourceTableName = column.TableName
row.sourceName = column.Name
row.sourceDefinition = sourceColumnDefinitionToString(*column)
row.targetName = spColumn.TableName + "." + spColumn.Name
row.targetDefinition = spannerColumnDefinitionToString(*spColumn)
row.dbChanges, row.dbImpact, row.dbChangeEffort, row.actionItems = calculateColumnDbChangesAndImpact(columnAssessment)
//Populate code info
//logger.Log.Info(fmt.Sprintf("%s.%s", column.TableName, column.Name))
populateColumnCodeImpact(*column, *spColumn, assessmentOutput.AppCodeAssessment.CodeSnippets, &row, columnAssessment)
rows = append(rows, row)
}
populateCheckConstraints(tableAssessment, spTable.Name, &rows)
populateForeignKeys(tableAssessment, spTable.Name, &rows)
populateIndexes(tableAssessment, spTable.Name, &rows)
}
populateStoredProcedureInfo(assessmentOutput.SchemaAssessment.StoredProcedureAssessmentOutput, &rows)
populateTriggerInfo(assessmentOutput.SchemaAssessment.TriggerAssessmentOutput, &rows)
populateFunctionInfo(assessmentOutput.SchemaAssessment.FunctionAssessmentOutput, &rows)
populateViewInfo(assessmentOutput.SchemaAssessment.ViewAssessmentOutput, &rows)
populateSequenceInfo(assessmentOutput.SchemaAssessment.SpSequences, assessmentOutput.SchemaAssessment.TableAssessmentOutput, assessmentOutput.AppCodeAssessment.CodeSnippets, &rows)
return rows
}
func populateIndexes(tableAssessment utils.TableAssessment, spTableName string, rows *[]SchemaReportRow) {
for id := range tableAssessment.SourceIndexDef {
srcIndex := tableAssessment.SourceIndexDef[id]
row := SchemaReportRow{}
row.element = tableAssessment.SourceTableDef.Name + "." + srcIndex.Name
row.elementType = "Index"
// TODO : Right now we migrate all mysql indexes to spanner, we need to do it based on index type and then modify the fields here for unsupported index types
row.sourceTableName = tableAssessment.SourceTableDef.Name
row.sourceName = srcIndex.Name
row.sourceDefinition = srcIndex.Ddl
row.targetName = spTableName + "." + tableAssessment.SpannerIndexDef[id].Name
row.targetDefinition = tableAssessment.SpannerIndexDef[id].Ddl
row.dbChangeEffort = "Automatic"
row.dbChanges = "None"
row.dbImpact = "None"
row.codeChangeEffort = "None"
row.codeChangeType = "None"
row.codeImpactedFiles = "None"
row.codeSnippets = "None"
*rows = append(*rows, row)
}
}
func populateCheckConstraints(tableAssessment utils.TableAssessment, spTableName string, rows *[]SchemaReportRow) {
for id, srcConstraint := range tableAssessment.SourceTableDef.CheckConstraints {
row := SchemaReportRow{}
row.element = tableAssessment.SourceTableDef.Name + "." + srcConstraint.Name
row.elementType = "Check Constraint"
row.sourceTableName = tableAssessment.SourceTableDef.Name
row.sourceName = srcConstraint.Name
row.sourceDefinition = srcConstraint.Expr
if _, found := tableAssessment.SpannerTableDef.CheckConstraints[id]; !found {
row.targetName = "N/A"
row.targetDefinition = "N/A"
row.dbChangeEffort = "Small"
row.dbChanges = "Unknown"
row.dbImpact = ""
row.actionItems = &[]string{"Alter column to apply check constraint"}
} else {
row.targetName = spTableName + "." + tableAssessment.SpannerTableDef.CheckConstraints[id].Name
row.targetDefinition = tableAssessment.SpannerTableDef.CheckConstraints[id].Expr
row.dbChangeEffort = "Automatic"
row.dbChanges = "None"
row.dbImpact = "None"
}
row.codeChangeEffort = "None"
row.codeChangeType = "None"
row.codeImpactedFiles = "None"
row.codeSnippets = "None"
*rows = append(*rows, row)
}
}
func populateForeignKeys(tableAssessment utils.TableAssessment, spTableName string, rows *[]SchemaReportRow) {
for id, fk := range tableAssessment.SourceTableDef.SourceForeignKey {
spFk := tableAssessment.SpannerTableDef.SpannerForeignKey[id]
row := SchemaReportRow{}
row.element = tableAssessment.SourceTableDef.Name + "." + fk.Definition.Name
row.elementType = "Foreign Key"
row.sourceTableName = tableAssessment.SourceTableDef.Name
row.sourceName = fk.Definition.Name
row.sourceDefinition = fk.Ddl[strings.Index(fk.Ddl, "CONSTRAINT"):]
row.targetName = spTableName + "." + spFk.Definition.Name
row.targetDefinition = spFk.Ddl[strings.Index(spFk.Ddl, "CONSTRAINT"):]
if fk.Definition.OnDelete != spFk.Definition.OnDelete || fk.Definition.OnUpdate != spFk.Definition.OnUpdate {
row.dbChangeEffort = "Automatic"
row.dbChanges = "reference_option"
row.dbImpact = "None"
row.codeChangeEffort = "Modify" //TODO Check number of references in queries and modify
row.codeChangeType = "Manual"
row.codeImpactedFiles = "Unknown"
row.codeSnippets = "None"
} else {
row.dbChangeEffort = "Automatic"
row.dbChanges = "None"
row.dbImpact = "None"
row.codeChangeEffort = "None"
row.codeChangeType = "None"
row.codeImpactedFiles = "None"
row.codeSnippets = "None"
}
*rows = append(*rows, row)
}
}
func tableDefinitionToString(srcTable utils.SrcTableDetails) string {
sourceDefinition := ""
if strings.Contains(srcTable.Charset, "utf") {
sourceDefinition += "CHARSET=" + srcTable.Charset + " "
}
for k, v := range srcTable.Properties {
sourceDefinition += k + "=" + v + " "
}
return sourceDefinition
}
func spannerColumnDefinitionToString(columnDefinition utils.SpColumnDetails) string {
columnDef := ddl.ColumnDef{
Name: columnDefinition.Name,
DefaultValue: columnDefinition.DefaultValue,
AutoGen: columnDefinition.AutoGen,
T: ddl.Type{
Name: columnDefinition.Datatype,
Len: columnDefinition.Len,
IsArray: columnDefinition.IsArray,
},
NotNull: !columnDefinition.IsNull,
}
s, _ := columnDef.PrintColumnDef(ddl.Config{})
return s
}
func getElementTypeForColumn(columnDefinition utils.SrcColumnDetails) string {
if columnDefinition.GeneratedColumn.IsPresent {
return "Generated Column"
}
return "Column"
}
func sourceColumnDefinitionToString(columnDefinition utils.SrcColumnDetails) string {
s := columnDefinition.Datatype
if len(columnDefinition.Mods) > 0 {
var l []string
for _, x := range columnDefinition.Mods {
l = append(l, strconv.FormatInt(x, 10))
}
s = fmt.Sprintf("%s(%s)", s, strings.Join(l, ","))
}
if columnDefinition.IsUnsigned {
s += " UNSIGNED"
}
if columnDefinition.GeneratedColumn.IsPresent {
s += " GENERATED ALWAYS AS " + columnDefinition.GeneratedColumn.Statement
if columnDefinition.GeneratedColumn.IsVirtual {
s += " VIRTUAL"
} else {
s += " STORED"
}
}
if columnDefinition.DefaultValue.IsPresent {
s += " DEFAULT " + columnDefinition.DefaultValue.Value.Statement
}
if !columnDefinition.IsNull {
s += " NOT NULL"
}
if columnDefinition.IsOnUpdateTimestampSet {
s += " ON UPDATE CURRENT_TIMESTAMP"
}
if columnDefinition.AutoGen.Name != "" && columnDefinition.AutoGen.GenerationType == constants.AUTO_INCREMENT {
s += " AUTO_INCREMENT"
}
return s
}
// TODO move calculation logic to assessment engine
func calculateTableDbChangesAndImpact(tableAssessment utils.TableAssessment) (string, string) {
changes := []string{}
impact := []string{}
if !tableAssessment.CompatibleCharset {
changes = append(changes, "charset")
}
if tableAssessment.SizeIncreaseInBytes > 0 {
impact = append(impact, "storage increase")
} else if tableAssessment.SizeIncreaseInBytes < 0 {
impact = append(impact, "storage decrease")
}
if len(changes) == 0 {
changes = append(changes, "None")
}
if len(impact) == 0 {
impact = append(impact, "None")
}
return strings.Join(changes, ","), strings.Join(impact, ",")
}
func calculateColumnDbChangesAndImpact(columnAssessment utils.ColumnAssessment) (string, string, string, *[]string) {
changes := []string{}
impact := []string{}
changeEffort := "Automatic"
actionItems := []string{}
if !columnAssessment.CompatibleDataType { // TODO type specific checks on size
changes = append(changes, "type")
}
if columnAssessment.SourceColDef.IsOnUpdateTimestampSet { //TODO Add Code change effort for this
changes = append(changes, "feature")
changeEffort = "None"
actionItems = append(actionItems, "Update queries to include PENDING_COMMIT_TIMESTAMP")
}
if columnAssessment.SourceColDef.DefaultValue.IsPresent && !columnAssessment.SpannerColDef.DefaultValue.IsPresent {
switch columnAssessment.SourceColDef.DefaultValue.Value.Statement {
case "NULL":
//Nothing to do - equivalent
default:
changes = append(changes, "feature")
changeEffort = "Small"
actionItems = append(actionItems, "Alter column to apply default value")
}
}
if columnAssessment.SizeIncreaseInBytes > 0 {
impact = append(impact, "storage increase")
} else if columnAssessment.SizeIncreaseInBytes < 0 {
impact = append(impact, "storage decrease")
}
// TODO: fetch it from maxValue field in column definition
if columnAssessment.SourceColDef.Datatype == "bigint" && columnAssessment.SourceColDef.IsUnsigned {
impact = append(impact, "potential overflow")
}
if columnAssessment.SourceColDef.AutoGen.Name != "" && columnAssessment.SourceColDef.AutoGen.GenerationType == constants.AUTO_INCREMENT {
changes = append(changes, "feature")
}
if columnAssessment.SourceColDef.GeneratedColumn.IsPresent {
changeEffort = "Small"
actionItems = append(actionItems, "Update schema to add generated column")
}
//TODO add check for not null to null scenarios
if len(changes) == 0 {
changes = append(changes, "None")
}
if len(impact) == 0 {
impact = append(impact, "None")
}
return strings.Join(changes, ","), strings.Join(impact, ","), changeEffort, &actionItems
}
func populateTableCodeImpact(srcTableDef utils.SrcTableDetails, spTableDef utils.SpTableDetails, codeSnippets *[]utils.Snippet, row *SchemaReportRow) {
if srcTableDef.Name == spTableDef.Name {
row.codeChangeType = "None"
row.codeChangeEffort = "None"
row.codeImpactedFiles = "None"
row.codeSnippets = "None"
return
}
if codeSnippets == nil {
row.codeChangeType = "Unavailable"
row.codeChangeEffort = "Unavailable"
row.codeImpactedFiles = "Unavailable"
row.codeSnippets = "Unavailable"
return
}
impactedFiles := []string{}
relatedSnippets := []string{}
for _, snippet := range *codeSnippets {
if srcTableDef.Name == snippet.TableName { //TODO add check that column is empty here
if !slices.Contains(impactedFiles, snippet.RelativeFilePath) {
impactedFiles = append(impactedFiles, snippet.RelativeFilePath)
}
relatedSnippets = append(relatedSnippets, snippet.Id)
}
}
if len(impactedFiles) == 0 {
row.codeImpactedFiles = "None"
row.codeChangeType = "None"
row.codeChangeEffort = "None"
row.codeSnippets = ""
} else {
row.codeImpactedFiles = strings.Join(impactedFiles, ",")
row.codeChangeType = "Suggested"
row.codeChangeEffort = "TBD" //not implemented yet
row.codeSnippets = strings.Join(relatedSnippets, ",")
}
}
func populateColumnCodeImpact(srcColumnDef utils.SrcColumnDetails, spColumnDef utils.SpColumnDetails, codeSnippets *[]utils.Snippet, row *SchemaReportRow, columnAssessment utils.ColumnAssessment) {
if columnAssessment.CompatibleDataType {
row.codeChangeType = "None"
row.codeChangeEffort = "None"
row.codeImpactedFiles = "None"
row.codeSnippets = "None"
return
}
if codeSnippets == nil {
row.codeChangeType = "Unavailable"
row.codeChangeEffort = "Unavailable"
row.codeImpactedFiles = "Unavailable"
row.codeSnippets = "Unavailable"
return
}
if srcColumnDef.IsOnUpdateTimestampSet {
row.codeChangeEffort = "Large"
row.codeChangeType = "Manual"
row.codeImpactedFiles = "TBD" //not implemented yet
row.codeSnippets = ""
return
}
impactedFiles := []string{}
relatedSnippets := []string{}
for _, snippet := range *codeSnippets {
if srcColumnDef.TableName == snippet.TableName && srcColumnDef.Name == snippet.ColumnName {
if !slices.Contains(impactedFiles, snippet.RelativeFilePath) {
impactedFiles = append(impactedFiles, snippet.RelativeFilePath)
}
relatedSnippets = append(relatedSnippets, snippet.Id)
}
}
if len(impactedFiles) == 0 {
row.codeImpactedFiles = "None"
row.codeChangeType = "None"
row.codeChangeEffort = "None"
row.codeSnippets = ""
} else {
row.codeImpactedFiles = strings.Join(impactedFiles, ",")
row.codeChangeType = "Suggested"
row.codeChangeEffort = "Small"
row.codeSnippets = strings.Join(relatedSnippets, ",")
}
}
func populateStoredProcedureInfo(storedProcedureAssessmentOutput map[string]utils.StoredProcedureAssessment, rows *[]SchemaReportRow) {
for _, sproc := range storedProcedureAssessmentOutput {
row := SchemaReportRow{}
row.element = sproc.Name
row.elementType = "Stored Procedure"
row.sourceTableName = "N/A"
row.sourceName = sproc.Name
row.sourceDefinition = sproc.Definition
populateChangesForUnsupportedElements(&row)
*rows = append(*rows, row)
}
}
func populateTriggerInfo(triggerAssessmentOutput map[string]utils.TriggerAssessment, rows *[]SchemaReportRow) {
for _, trigger := range triggerAssessmentOutput {
row := SchemaReportRow{}
row.element = trigger.Name
row.elementType = "Trigger"
row.sourceTableName = trigger.TargetTable
row.sourceName = trigger.Name
row.sourceDefinition = trigger.Operation
populateChangesForUnsupportedElements(&row)
*rows = append(*rows, row)
}
}
func populateFunctionInfo(functionAssessmentOutput map[string]utils.FunctionAssessment, rows *[]SchemaReportRow) {
for _, function := range functionAssessmentOutput {
row := SchemaReportRow{}
row.element = function.Name
row.elementType = "Function"
row.sourceTableName = "N/A"
row.sourceName = function.Name
row.sourceDefinition = function.Definition
populateChangesForUnsupportedElements(&row)
*rows = append(*rows, row)
}
}
func populateViewInfo(viewAssessmentOutput map[string]utils.ViewAssessment, rows *[]SchemaReportRow) {
for _, view := range viewAssessmentOutput {
row := SchemaReportRow{}
row.element = view.SrcName
row.elementType = "View"
row.sourceTableName = "N/A"
row.sourceName = view.SrcName
row.sourceDefinition = view.SrcViewType
row.targetName = view.SpName
row.targetDefinition = "Unknown"
row.dbChangeEffort = "Small"
row.dbChanges = "Unknown"
row.dbImpact = "None"
row.codeChangeEffort = "Unknown" //Change based on availability of code
row.codeChangeType = "Manual"
row.codeImpactedFiles = "Unknown"
row.codeSnippets = ""
row.actionItems = &[]string{"Create view manually"}
*rows = append(*rows, row)
}
}
func populateChangesForUnsupportedElements(row *SchemaReportRow) {
row.targetName = "Not supported"
row.targetDefinition = "N/A"
row.dbChangeEffort = "Not Supported"
row.dbChanges = "Drop"
row.dbImpact = "Less Compute"
row.codeChangeEffort = "Rewrite"
row.codeChangeType = "Manual"
row.codeImpactedFiles = "Unknown"
row.codeSnippets = ""
row.actionItems = &[]string{"Rewrite in application code"}
}
func populateSequenceInfo(sequenceAssessmentOutput map[string]ddl.Sequence, tableAssessments []utils.TableAssessment, codeSnippets *[]utils.Snippet, rows *[]SchemaReportRow) {
srcTableIdToName := make(map[string]string)
for _, table := range tableAssessments {
srcTableIdToName[table.SourceTableDef.Id] = table.SourceTableDef.Name
}
for _, sequence := range sequenceAssessmentOutput {
row := SchemaReportRow{}
row.element = "N/A"
row.elementType = "Sequence"
row.sourceTableName = "N/A" // TO be corrected
if len(sequence.ColumnsUsingSeq) == 1 {
tableId := ""
for tableId, _ = range sequence.ColumnsUsingSeq {
//nothing to do
}
sourceTableName, found := srcTableIdToName[tableId]
if found {
row.sourceTableName = sourceTableName
}
}
row.sourceName = sequence.Name
row.sourceDefinition = "N/A"
row.targetName = sequence.Name
row.targetDefinition = sequence.PrintSequence(ddl.Config{})
row.dbChangeEffort = "Automatic"
row.dbChanges = "None"
row.dbImpact = "N/A"
row.codeChangeEffort = "Modify"
row.codeChangeType = "Manual"
if codeSnippets == nil {
row.codeImpactedFiles = "Unavailable"
row.codeSnippets = "Unavailable"
} else {
row.codeImpactedFiles = "Unknown"
row.codeSnippets = "Unkown"
}
*rows = append(*rows, row)
}
}