diff --git a/migtests/tests/analyze-schema/expected_issues.json b/migtests/tests/analyze-schema/expected_issues.json index b253f8210..07d302d22 100644 --- a/migtests/tests/analyze-schema/expected_issues.json +++ b/migtests/tests/analyze-schema/expected_issues.json @@ -103,7 +103,7 @@ "ObjectName": "test_1, constraint: (test_1_id_fkey)", "Reason": "Foreign key references to partitioned table are not yet supported in YugabyteDB.", "SqlStatement": "CREATE TABLE test_1 (\n\tid numeric NOT NULL REFERENCES sales_data(sales_id),\n\tcountry_code varchar(3),\n\trecord_type varchar(5),\n\tdescriptions varchar(50),\n\tPRIMARY KEY (id)\n) PARTITION BY LIST (country_code, record_type) ;", - "Suggestion": "No workaround available ", + "Suggestion": "No workaround available.", "GH": "https://github.com/yugabyte/yugabyte-db/issues/25575", "DocsLink": "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#postgresql-12-and-later-features", "MinimumVersionsFixedIn": { @@ -2093,7 +2093,7 @@ "ObjectName": "public.asterisks1", "Reason": "SQL Body for sql languages in function statement is not supported in YugabyteDB", "SqlStatement": "CREATE FUNCTION public.asterisks1(n integer) RETURNS text\n LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE\n RETURN repeat('*'::text, n);", - "Suggestion": "No workaround available", + "Suggestion": "No workaround available.", "GH": "https://github.com/yugabyte/yugabyte-db/issues/25575", "DocsLink": "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#postgresql-12-and-later-features", "MinimumVersionsFixedIn": { @@ -2106,7 +2106,7 @@ "ObjectName": "public.asterisks", "Reason": "SQL Body for sql languages in function statement is not supported in YugabyteDB", "SqlStatement": "CREATE FUNCTION public.asterisks(n integer) RETURNS SETOF text\n LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE\n BEGIN ATOMIC\n SELECT repeat('*'::text, g.g) AS repeat\n FROM generate_series(1, asterisks.n) g(g);\nEND;", - "Suggestion": "No workaround available", + "Suggestion": "No workaround available.", "GH": "https://github.com/yugabyte/yugabyte-db/issues/25575", "DocsLink": "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#postgresql-12-and-later-features", "MinimumVersionsFixedIn": { @@ -2119,7 +2119,7 @@ "ObjectName": "add", "Reason": "SQL Body for sql languages in function statement is not supported in YugabyteDB", "SqlStatement": "CREATE FUNCTION add(int, int) RETURNS int IMMUTABLE PARALLEL SAFE BEGIN ATOMIC; SELECT $1 + $2; END;", - "Suggestion": "No workaround available", + "Suggestion": "No workaround available.", "GH": "https://github.com/yugabyte/yugabyte-db/issues/25575", "DocsLink": "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#postgresql-12-and-later-features", "MinimumVersionsFixedIn": { diff --git a/yb-voyager/cmd/analyzeSchema.go b/yb-voyager/cmd/analyzeSchema.go index 3d6ac951f..56f5e2211 100644 --- a/yb-voyager/cmd/analyzeSchema.go +++ b/yb-voyager/cmd/analyzeSchema.go @@ -1274,7 +1274,6 @@ func packAndSendAnalyzeSchemaPayload(status string, errorMsg string) { if sensitiveReason == UNSUPPORTED_PG_SYNTAX_ISSUE_REASON && strings.HasPrefix(issue.Reason, sensitiveReason) { issue.Reason = sensitiveReason } else { - // TODO: should we just start sending issue type/name here instead of obfuscating the reason since that is anyways static match, err := utils.MatchesFormatString(sensitiveReason, issue.Reason) if match { issue.Reason, err = utils.ObfuscateFormatDetails(sensitiveReason, issue.Reason, constants.OBFUSCATE_STRING) diff --git a/yb-voyager/cmd/assessMigrationCommand.go b/yb-voyager/cmd/assessMigrationCommand.go index 33f6a422f..1ed6f9475 100644 --- a/yb-voyager/cmd/assessMigrationCommand.go +++ b/yb-voyager/cmd/assessMigrationCommand.go @@ -120,23 +120,29 @@ var assessMigrationCmd = &cobra.Command{ }, } -// Assessment feature names to send the object names for to callhome -var featuresToSendObjectsToCallhome = []string{} - func packAndSendAssessMigrationPayload(status string, errMsg string) { if !shouldSendCallhome() { return } - payload := createCallhomePayload() + payload := createCallhomePayload() payload.MigrationPhase = ASSESS_MIGRATION_PHASE + payload.Status = status + if assessmentMetadataDirFlag == "" { + sourceDBDetails := callhome.SourceDBDetails{ + DBType: source.DBType, + DBVersion: source.DBVersion, + DBSize: source.DBSize, + } + payload.SourceDBDetails = callhome.MarshalledJsonString(sourceDBDetails) + } var tableSizingStats, indexSizingStats []callhome.ObjectSizingStats if assessmentReport.TableIndexStats != nil { for _, stat := range *assessmentReport.TableIndexStats { newStat := callhome.ObjectSizingStats{ //redacting schema and object name - ObjectName: "XXX", + ObjectName: constants.OBFUSCATE_STRING, ReadsPerSecond: utils.SafeDereferenceInt64(stat.ReadsPerSecond), WritesPerSecond: utils.SafeDereferenceInt64(stat.WritesPerSecond), SizeInBytes: utils.SafeDereferenceInt64(stat.SizeInBytes), @@ -156,87 +162,51 @@ func packAndSendAssessMigrationPayload(status string, errMsg string) { }), } - unsupportedDatatypesList := lo.Map(assessmentReport.UnsupportedDataTypes, func(datatype utils.TableColumnsDataTypes, _ int) string { - return datatype.DataType - }) + // we will build this twice for json and html reports both, at the time of report generation. + // whatever happens later will be stored in the struct's field. so to be on safer side, we will build it again here as per required format. + explanation, err := buildMigrationComplexityExplanation(source.DBType, assessmentReport, "") + if err != nil { + log.Errorf("failed to build migration complexity explanation: %v", err) + } + + var obfuscatedIssues []callhome.AssessmentIssueCallhome + for _, issue := range assessmentReport.Issues { + obfuscatedIssue := callhome.AssessmentIssueCallhome{ + Category: issue.Category, + CategoryDescription: issue.CategoryDescription, + Type: issue.Type, + Name: issue.Name, + Impact: issue.Impact, + ObjectType: issue.ObjectType, + } - groupedByConstructType := lo.GroupBy(assessmentReport.UnsupportedQueryConstructs, func(q utils.UnsupportedQueryConstruct) string { - return q.ConstructTypeName - }) - countByConstructType := lo.MapValues(groupedByConstructType, func(constructs []utils.UnsupportedQueryConstruct, _ string) int { - return len(constructs) - }) + // TODO: object name(for extensions) and other details like datatype/indextype(present in description) for callhome will be covered in next release - var unsupportedFeatures []callhome.UnsupportedFeature - for _, feature := range assessmentReport.UnsupportedFeatures { - if feature.FeatureName == EXTENSION_FEATURE { - // For extensions, we need to send the extension name in the feature name - // for better categorization in callhome - for _, object := range feature.Objects { - unsupportedFeatures = append(unsupportedFeatures, callhome.UnsupportedFeature{ - FeatureName: fmt.Sprintf("%s - %s", feature.FeatureName, object.ObjectName), - ObjectCount: 1, - TotalOccurrences: 1, - }) - } - } else { - var objects []string - if slices.Contains(featuresToSendObjectsToCallhome, feature.FeatureName) { - objects = lo.Map(feature.Objects, func(o ObjectInfo, _ int) string { - return o.ObjectName - }) - } - unsupportedFeatures = append(unsupportedFeatures, callhome.UnsupportedFeature{ - FeatureName: feature.FeatureName, - ObjectCount: len(feature.Objects), - Objects: objects, - TotalOccurrences: len(feature.Objects), - }) - } + // if issue.Type == UNSUPPORTED_EXTENSION_ISSUE_TYPE { + // object name(i.e. extension name here) might be qualified with schema so just taking the last part + // obfuscatedIssue.ObjectName = strings.Split(issue.ObjectName, ".")[len(strings.Split(issue.ObjectName, "."))-1] + // } + + // appending the issue after obfuscating sensitive information + obfuscatedIssues = append(obfuscatedIssues, obfuscatedIssue) } assessPayload := callhome.AssessMigrationPhasePayload{ - TargetDBVersion: assessmentReport.TargetDBVersion, - MigrationComplexity: assessmentReport.MigrationComplexity, - UnsupportedFeatures: callhome.MarshalledJsonString(unsupportedFeatures), - UnsupportedQueryConstructs: callhome.MarshalledJsonString(countByConstructType), - UnsupportedDatatypes: callhome.MarshalledJsonString(unsupportedDatatypesList), - MigrationCaveats: callhome.MarshalledJsonString(lo.Map(assessmentReport.MigrationCaveats, func(feature UnsupportedFeature, _ int) callhome.UnsupportedFeature { - return callhome.UnsupportedFeature{ - FeatureName: feature.FeatureName, - ObjectCount: len(feature.Objects), - TotalOccurrences: len(feature.Objects), - } - })), - UnsupportedPlPgSqlObjects: callhome.MarshalledJsonString(lo.Map(assessmentReport.UnsupportedPlPgSqlObjects, func(plpgsql UnsupportedFeature, _ int) callhome.UnsupportedFeature { - groupedObjects := groupByObjectName(plpgsql.Objects) - return callhome.UnsupportedFeature{ - FeatureName: plpgsql.FeatureName, - ObjectCount: len(lo.Keys(groupedObjects)), - TotalOccurrences: len(plpgsql.Objects), - } - })), - TableSizingStats: callhome.MarshalledJsonString(tableSizingStats), - IndexSizingStats: callhome.MarshalledJsonString(indexSizingStats), - SchemaSummary: callhome.MarshalledJsonString(schemaSummaryCopy), - IopsInterval: intervalForCapturingIOPS, - Error: callhome.SanitizeErrorMsg(errMsg), + PayloadVersion: callhome.ASSESS_MIGRATION_CALLHOME_PAYLOAD_VERSION, + TargetDBVersion: assessmentReport.TargetDBVersion, + MigrationComplexity: assessmentReport.MigrationComplexity, + MigrationComplexityExplanation: explanation, + SchemaSummary: callhome.MarshalledJsonString(schemaSummaryCopy), + Issues: callhome.MarshalledJsonString(obfuscatedIssues), + Error: callhome.SanitizeErrorMsg(errMsg), + TableSizingStats: callhome.MarshalledJsonString(tableSizingStats), + IndexSizingStats: callhome.MarshalledJsonString(indexSizingStats), + SourceConnectivity: assessmentMetadataDirFlag == "", + IopsInterval: intervalForCapturingIOPS, } - if assessmentMetadataDirFlag == "" { - sourceDBDetails := callhome.SourceDBDetails{ - DBType: source.DBType, - DBVersion: source.DBVersion, - DBSize: source.DBSize, - } - payload.SourceDBDetails = callhome.MarshalledJsonString(sourceDBDetails) - assessPayload.SourceConnectivity = true - } else { - assessPayload.SourceConnectivity = false - } payload.PhasePayload = callhome.MarshalledJsonString(assessPayload) - payload.Status = status - err := callhome.SendPayload(&payload) + err = callhome.SendPayload(&payload) if err == nil && (status == COMPLETE || status == ERROR) { callHomeErrorOrCompletePayloadSent = true } @@ -495,7 +465,7 @@ func createMigrationAssessmentCompletedEvent() *cp.MigrationAssessmentCompletedE assessmentIssues := flattenAssessmentReportToAssessmentIssues(assessmentReport) payload := AssessMigrationPayload{ - PayloadVersion: ASSESS_MIGRATION_PAYLOAD_VERSION, + PayloadVersion: ASSESS_MIGRATION_YBD_PAYLOAD_VERSION, VoyagerVersion: assessmentReport.VoyagerVersion, TargetDBVersion: assessmentReport.TargetDBVersion, MigrationComplexity: assessmentReport.MigrationComplexity, @@ -1038,12 +1008,12 @@ func convertAnalyzeSchemaIssueToAssessmentIssue(analyzeSchemaIssue utils.Analyze // and we don't use any Suggestion field in AssessmentIssue. Combination of Description + DocsLink should be enough Description: analyzeSchemaIssue.Reason + " " + analyzeSchemaIssue.Suggestion, - Impact: analyzeSchemaIssue.Impact, - ObjectType: analyzeSchemaIssue.ObjectType, - ObjectName: analyzeSchemaIssue.ObjectName, - SqlStatement: analyzeSchemaIssue.SqlStatement, - DocsLink: analyzeSchemaIssue.DocsLink, - MinimumVersionFixedIn: minVersionsFixedIn, + Impact: analyzeSchemaIssue.Impact, + ObjectType: analyzeSchemaIssue.ObjectType, + ObjectName: analyzeSchemaIssue.ObjectName, + SqlStatement: analyzeSchemaIssue.SqlStatement, + DocsLink: analyzeSchemaIssue.DocsLink, + MinimumVersionsFixedIn: minVersionsFixedIn, } } @@ -1246,16 +1216,16 @@ func fetchUnsupportedPlPgSQLObjects(schemaAnalysisReport utils.SchemaReport) []U docsLink = issue.DocsLink assessmentReport.AppendIssues(AssessmentIssue{ - Category: UNSUPPORTED_PLPGSQL_OBJECTS_CATEGORY, - Type: issue.Type, - Name: issue.Name, - Impact: issue.Impact, - Description: issue.Reason, - ObjectType: issue.ObjectType, - ObjectName: issue.ObjectName, - SqlStatement: issue.SqlStatement, - DocsLink: issue.DocsLink, - MinimumVersionFixedIn: issue.MinimumVersionsFixedIn, + Category: UNSUPPORTED_PLPGSQL_OBJECTS_CATEGORY, + Type: issue.Type, + Name: issue.Name, + Impact: issue.Impact, + Description: issue.Reason, + ObjectType: issue.ObjectType, + ObjectName: issue.ObjectName, + SqlStatement: issue.SqlStatement, + DocsLink: issue.DocsLink, + MinimumVersionsFixedIn: issue.MinimumVersionsFixedIn, }) } feature := UnsupportedFeature{ @@ -1335,14 +1305,14 @@ func fetchUnsupportedQueryConstructs() ([]utils.UnsupportedQueryConstruct, error result = append(result, uqc) assessmentReport.AppendIssues(AssessmentIssue{ - Category: UNSUPPORTED_QUERY_CONSTRUCTS_CATEGORY, - Type: issue.Type, - Name: issue.Name, - Impact: issue.Impact, - Description: issue.Description, - SqlStatement: issue.SqlStatement, - DocsLink: issue.DocsLink, - MinimumVersionFixedIn: issue.MinimumVersionsFixedIn, + Category: UNSUPPORTED_QUERY_CONSTRUCTS_CATEGORY, + Type: issue.Type, + Name: issue.Name, + Impact: issue.Impact, + Description: issue.Description, + SqlStatement: issue.SqlStatement, + DocsLink: issue.DocsLink, + MinimumVersionsFixedIn: issue.MinimumVersionsFixedIn, }) } } @@ -1441,16 +1411,16 @@ func getAssessmentIssuesForUnsupportedDatatypes(unsupportedDatatypes []utils.Tab for _, colInfo := range unsupportedDatatypes { qualifiedColName := fmt.Sprintf("%s.%s.%s", colInfo.SchemaName, colInfo.TableName, colInfo.ColumnName) issue := AssessmentIssue{ - Category: UNSUPPORTED_DATATYPES_CATEGORY, - CategoryDescription: GetCategoryDescription(UNSUPPORTED_DATATYPES_CATEGORY), - Type: colInfo.DataType, // TODO: maybe name it like "unsupported datatype - geometry" - Name: colInfo.DataType, // TODO: maybe name it like "unsupported datatype - geometry" - Description: "", // TODO - Impact: constants.IMPACT_LEVEL_3, - ObjectType: constants.COLUMN, - ObjectName: qualifiedColName, - DocsLink: "", // TODO - MinimumVersionFixedIn: nil, // TODO + Category: UNSUPPORTED_DATATYPES_CATEGORY, + CategoryDescription: GetCategoryDescription(UNSUPPORTED_DATATYPES_CATEGORY), + Type: colInfo.DataType, // TODO: maybe name it like "unsupported datatype - geometry" + Name: colInfo.DataType, // TODO: maybe name it like "unsupported datatype - geometry" + Description: "", // TODO + Impact: constants.IMPACT_LEVEL_3, + ObjectType: constants.COLUMN, + ObjectName: qualifiedColName, + DocsLink: "", // TODO + MinimumVersionsFixedIn: nil, // TODO } assessmentIssues = append(assessmentIssues, issue) } diff --git a/yb-voyager/cmd/common.go b/yb-voyager/cmd/common.go index a8272d7b5..36da5774a 100644 --- a/yb-voyager/cmd/common.go +++ b/yb-voyager/cmd/common.go @@ -1073,17 +1073,17 @@ type AssessmentReport struct { // Fields apart from Category, CategoryDescription, TypeName and Impact will be populated only if/when available type AssessmentIssue struct { - Category string `json:"Category"` // expected values: unsupported_features, unsupported_query_constructs, migration_caveats, unsupported_plpgsql_objects, unsupported_datatype - CategoryDescription string `json:"CategoryDescription"` - Type string `json:"Type"` // Ex: GIN_INDEXES, SECURITY_INVOKER_VIEWS, STORED_GENERATED_COLUMNS - Name string `json:"Name"` // Ex: GIN Indexes, Security Invoker Views, Stored Generated Columns - Description string `json:"Description"` - Impact string `json:"Impact"` // // Level-1, Level-2, Level-3 (no default: need to be assigned for each issue) - ObjectType string `json:"ObjectType"` // For datatype category, ObjectType will be datatype (for eg "geometry") - ObjectName string `json:"ObjectName"` - SqlStatement string `json:"SqlStatement"` - DocsLink string `json:"DocsLink"` - MinimumVersionFixedIn map[string]*ybversion.YBVersion `json:"MinimumVersionFixedIn"` + Category string `json:"Category"` // expected values: unsupported_features, unsupported_query_constructs, migration_caveats, unsupported_plpgsql_objects, unsupported_datatype + CategoryDescription string `json:"CategoryDescription"` + Type string `json:"Type"` // Ex: GIN_INDEXES, SECURITY_INVOKER_VIEWS, STORED_GENERATED_COLUMNS + Name string `json:"Name"` // Ex: GIN Indexes, Security Invoker Views, Stored Generated Columns + Description string `json:"Description"` + Impact string `json:"Impact"` // // Level-1, Level-2, Level-3 (no default: need to be assigned for each issue) + ObjectType string `json:"ObjectType"` // For datatype category, ObjectType will be datatype (for eg "geometry") + ObjectName string `json:"ObjectName"` + SqlStatement string `json:"SqlStatement"` + DocsLink string `json:"DocsLink"` + MinimumVersionsFixedIn map[string]*ybversion.YBVersion `json:"MinimumVersionsFixedIn"` } type UnsupportedFeature struct { @@ -1128,6 +1128,9 @@ type AssessMigrationDBConfig struct { // =============== for yugabyted controlplane ==============// // TODO: see if this can be accommodated in controlplane pkg, facing pkg cyclic dependency issue + +var ASSESS_MIGRATION_YBD_PAYLOAD_VERSION = "1.1" // version(s) till now: 1.0, 1.1 + type AssessMigrationPayload struct { PayloadVersion string VoyagerVersion string @@ -1194,8 +1197,6 @@ type TargetSizingRecommendations struct { TotalShardedSize int64 } -var ASSESS_MIGRATION_PAYLOAD_VERSION = "1.1" - //====== AssesmentReport struct methods ======// func ParseJSONToAssessmentReport(reportPath string) (*AssessmentReport, error) { diff --git a/yb-voyager/cmd/common_test.go b/yb-voyager/cmd/common_test.go index 05c536f75..a2a139760 100644 --- a/yb-voyager/cmd/common_test.go +++ b/yb-voyager/cmd/common_test.go @@ -129,17 +129,17 @@ func TestAssessmentReportStructs(t *testing.T) { name: "Validate Assessment Issue Struct Definition", actualType: reflect.TypeOf(AssessmentIssue{}), expectedType: struct { - Category string `json:"Category"` - CategoryDescription string `json:"CategoryDescription"` - Type string `json:"Type"` - Name string `json:"Name"` - Description string `json:"Description"` - Impact string `json:"Impact"` - ObjectType string `json:"ObjectType"` - ObjectName string `json:"ObjectName"` - SqlStatement string `json:"SqlStatement"` - DocsLink string `json:"DocsLink"` - MinimumVersionFixedIn map[string]*ybversion.YBVersion `json:"MinimumVersionFixedIn"` + Category string `json:"Category"` + CategoryDescription string `json:"CategoryDescription"` + Type string `json:"Type"` + Name string `json:"Name"` + Description string `json:"Description"` + Impact string `json:"Impact"` + ObjectType string `json:"ObjectType"` + ObjectName string `json:"ObjectName"` + SqlStatement string `json:"SqlStatement"` + DocsLink string `json:"DocsLink"` + MinimumVersionsFixedIn map[string]*ybversion.YBVersion `json:"MinimumVersionsFixedIn"` }{}, }, { diff --git a/yb-voyager/cmd/templates/migration_assessment_report.template b/yb-voyager/cmd/templates/migration_assessment_report.template index 242022f60..eb6f43c70 100644 --- a/yb-voyager/cmd/templates/migration_assessment_report.template +++ b/yb-voyager/cmd/templates/migration_assessment_report.template @@ -403,7 +403,7 @@ - {{ $verStr := getSupportedVersionString $issue.MinimumVersionFixedIn }} + {{ $verStr := getSupportedVersionString $issue.MinimumVersionsFixedIn }} {{ if $verStr }} Supported In (versions) diff --git a/yb-voyager/src/callhome/diagnostics.go b/yb-voyager/src/callhome/diagnostics.go index 7bf14d6d8..988f0425c 100644 --- a/yb-voyager/src/callhome/diagnostics.go +++ b/yb-voyager/src/callhome/diagnostics.go @@ -96,32 +96,29 @@ type TargetDBDetails struct { Cores int `json:"total_cores"` } -type UnsupportedFeature struct { - FeatureName string `json:"FeatureName"` - Objects []string `json:"Objects,omitempty"` - ObjectCount int `json:"ObjectCount"` - TotalOccurrences int `json:"TotalOccurrences"` -} +var ASSESS_MIGRATION_CALLHOME_PAYLOAD_VERSION = "1.0" type AssessMigrationPhasePayload struct { - TargetDBVersion *ybversion.YBVersion `json:"target_db_version"` - MigrationComplexity string `json:"migration_complexity"` - UnsupportedFeatures string `json:"unsupported_features"` - UnsupportedDatatypes string `json:"unsupported_datatypes"` - UnsupportedQueryConstructs string `json:"unsupported_query_constructs"` - MigrationCaveats string `json:"migration_caveats"` - UnsupportedPlPgSqlObjects string `json:"unsupported_plpgsql_objects"` - Error string `json:"error"` - TableSizingStats string `json:"table_sizing_stats"` - IndexSizingStats string `json:"index_sizing_stats"` - SchemaSummary string `json:"schema_summary"` - SourceConnectivity bool `json:"source_connectivity"` - IopsInterval int64 `json:"iops_interval"` + PayloadVersion string `json:"payload_version"` + TargetDBVersion *ybversion.YBVersion `json:"target_db_version"` + MigrationComplexity string `json:"migration_complexity"` + MigrationComplexityExplanation string `json:"migration_complexity_explanation"` + SchemaSummary string `json:"schema_summary"` + Issues string `json:"assessment_issues"` + Error string `json:"error"` + TableSizingStats string `json:"table_sizing_stats"` + IndexSizingStats string `json:"index_sizing_stats"` + SourceConnectivity bool `json:"source_connectivity"` + IopsInterval int64 `json:"iops_interval"` } -type AssessMigrationBulkPhasePayload struct { - FleetConfigCount int `json:"fleet_config_count"` // Not storing any source info just the count of db configs passed to bulk cmd - Error string `json:"error"` +type AssessmentIssueCallhome struct { + Category string `json:"category"` + CategoryDescription string `json:"category_description"` + Type string `json:"type"` + Name string `json:"name"` + Impact string `json:"impact"` + ObjectType string `json:"object_type"` } type ObjectSizingStats struct { @@ -132,6 +129,11 @@ type ObjectSizingStats struct { SizeInBytes int64 `json:"size_in_bytes"` } +type AssessMigrationBulkPhasePayload struct { + FleetConfigCount int `json:"fleet_config_count"` // Not storing any source info just the count of db configs passed to bulk cmd + Error string `json:"error"` +} + type ExportSchemaPhasePayload struct { StartClean bool `json:"start_clean"` AppliedRecommendations bool `json:"applied_recommendations"` diff --git a/yb-voyager/src/callhome/diagnostics_test.go b/yb-voyager/src/callhome/diagnostics_test.go index b20b6a27d..66426c803 100644 --- a/yb-voyager/src/callhome/diagnostics_test.go +++ b/yb-voyager/src/callhome/diagnostics_test.go @@ -72,32 +72,32 @@ func TestCallhomeStructs(t *testing.T) { }{}, }, { - name: "Validate UnsupportedFeature Struct Definition", - actualType: reflect.TypeOf(UnsupportedFeature{}), + name: "Validate AssessMigrationPhasePayload Struct Definition", + actualType: reflect.TypeOf(AssessMigrationPhasePayload{}), expectedType: struct { - FeatureName string `json:"FeatureName"` - Objects []string `json:"Objects,omitempty"` - ObjectCount int `json:"ObjectCount"` - TotalOccurrences int `json:"TotalOccurrences"` + PayloadVersion string `json:"payload_version"` + TargetDBVersion *ybversion.YBVersion `json:"target_db_version"` + MigrationComplexity string `json:"migration_complexity"` + MigrationComplexityExplanation string `json:"migration_complexity_explanation"` + SchemaSummary string `json:"schema_summary"` + Issues string `json:"assessment_issues"` + Error string `json:"error"` + TableSizingStats string `json:"table_sizing_stats"` + IndexSizingStats string `json:"index_sizing_stats"` + SourceConnectivity bool `json:"source_connectivity"` + IopsInterval int64 `json:"iops_interval"` }{}, }, { - name: "Validate AssessMigrationPhasePayload Struct Definition", - actualType: reflect.TypeOf(AssessMigrationPhasePayload{}), + name: "Validate AssessmentIssueCallhome Struct Definition", + actualType: reflect.TypeOf(AssessmentIssueCallhome{}), expectedType: struct { - TargetDBVersion *ybversion.YBVersion `json:"target_db_version"` - MigrationComplexity string `json:"migration_complexity"` - UnsupportedFeatures string `json:"unsupported_features"` - UnsupportedDatatypes string `json:"unsupported_datatypes"` - UnsupportedQueryConstructs string `json:"unsupported_query_constructs"` - MigrationCaveats string `json:"migration_caveats"` - UnsupportedPlPgSqlObjects string `json:"unsupported_plpgsql_objects"` - Error string `json:"error"` - TableSizingStats string `json:"table_sizing_stats"` - IndexSizingStats string `json:"index_sizing_stats"` - SchemaSummary string `json:"schema_summary"` - SourceConnectivity bool `json:"source_connectivity"` - IopsInterval int64 `json:"iops_interval"` + Category string `json:"category"` + CategoryDescription string `json:"category_description"` + Type string `json:"type"` + Name string `json:"name"` + Impact string `json:"impact"` + ObjectType string `json:"object_type"` }{}, }, { diff --git a/yb-voyager/src/query/queryissue/constants.go b/yb-voyager/src/query/queryissue/constants.go index 468555351..ab919765d 100644 --- a/yb-voyager/src/query/queryissue/constants.go +++ b/yb-voyager/src/query/queryissue/constants.go @@ -109,7 +109,7 @@ const ( ) // Issues Description -// Note: Any issue description added here should be updated in reasonsIncludingSensitiveInformationToCallhome slice in analyzeSchema.go +// Note: Any issue description added here should be updated in reasonsIncludingSensitiveInformationToCallhome and descriptionsIncludingSensitiveInformationToCallhome const ( // for DMLs ADVISORY_LOCKS_ISSUE_DESCRIPTION = "Advisory locks are not yet implemented in YugabyteDB." @@ -182,3 +182,34 @@ const ( TRIGGER_OBJECT_TYPE = "TRIGGER" DML_QUERY_OBJECT_TYPE = "DML_QUERY" ) + +// Issue Suggestions +// Note: Any issue description added here should be updated in reasonsIncludingSensitiveInformationToCallhome and descriptionsIncludingSensitiveInformationToCallhome +const ( + STORED_GENERATED_COLUMN_ISSUE_SUGGESTION = "Using Triggers to update the generated columns is one way to work around this issue, refer docs link for more details." + UNLOGGED_TABLES_ISSUE_SUGGESTION = "Remove UNLOGGED keyword to make it work" + STORAGE_PARAMETERS_ISSUE_SUGGESTION = "Remove the storage parameters from the DDL" + ALTER_TABLE_SET_COLUMN_ATTRIBUTE_ISSUE_SUGGESTION = "Remove it from the exported schema" + ALTER_TABLE_CLUSTER_ON_ISSUE_SUGGESTION = "Remove it from the exported schema." + ALTER_TABLE_DISABLE_RULE_ISSUE_SUGGESTION = "Remove this and the rule '%s' from the exported schema to be not enabled on the table." + EXCLUSION_CONSTRAINT_ISSUE_SUGGESTION = "Refer docs link for details on possible workaround" + DEFERRABLE_CONSTRAINT_ISSUE_SUGGESTION = "Remove these constraints from the exported schema and make the neccessary changes to the application to work on target seamlessly" + POLICY_ROLE_ISSUE_SUGGESTION = "Create the Users manually to make the policies work." + BEFORE_ROW_TRIGGER_ON_PARTITION_TABLE_ISSUE_SUGGESTION = "Create the triggers on individual partitions." + ALTER_TABLE_ADD_PK_ON_PARTITION_ISSUE_SUGGESTION = "After export schema, the ALTER table should be merged with CREATE table for partitioned tables." + EXPRESSION_PARTITION_ISSUE_SUGGESTION = "Remove the Constriant from the table definition" + MULTI_COLUMN_LIST_PARTITION_ISSUE_SUGGESTION = "Make it a single column partition by list or choose other supported Partitioning methods" + INSUFFICIENT_COLUMNS_IN_PK_FOR_PARTITION_ISSUE_SUGGESTION = "Add all Partition columns to Primary Key" + XML_DATATYPE_ISSUE_SUGGESTION = "Data ingestion is not supported for this type in YugabyteDB so handle this type in different way. Refer link for more details." + XID_DATATYPE_ISSUE_SUGGESTION = "Functions for this type e.g. txid_current are not supported in YugabyteDB yet" + PK_UK_ON_COMPLEX_DATATYPE_ISSUE_SUGGESTION = "Refer to the docs link for the workaround" + INDEX_ON_COMPLEX_DATATYPE_ISSUE_SUGGESTION = "Refer to the docs link for the workaround" + FOREIGN_TABLE_ISSUE_SUGGESTION = "SERVER '%s', and USER MAPPING should be created manually on the target to create and use the foreign table" + REFERENCED_TYPE_DECLARATION_ISSUE_SUGGESTION = "Fix the syntax to include the actual type name instead of referencing the type of a column" + LARGE_OBJECT_DATATYPE_ISSUE_SUGGESTION = "Large objects are not yet supported in YugabyteDB, no workaround available currently" + MULTI_RANGE_DATATYPE_ISSUE_SUGGESTION = "Multirange data type is not yet supported in YugabyteDB, no workaround available currently" + SECURITY_INVOKER_VIEWS_ISSUE_SUGGESTION = "Security Invoker Views are not yet supported in YugabyteDB, no workaround available currently" + DETERMINISTIC_OPTION_WITH_COLLATION_ISSUE_SUGGESTION = "This feature is not supported in YugabyteDB yet" + FOREIGN_KEY_REFERENCES_PARTITIONED_TABLE_ISSUE_SUGGESTION = "No workaround available." + SQL_BODY_IN_FUNCTION_ISSUE_SUGGESTION = "No workaround available." +) diff --git a/yb-voyager/src/query/queryissue/issues_ddl.go b/yb-voyager/src/query/queryissue/issues_ddl.go index a05baa50d..8f7e750a0 100644 --- a/yb-voyager/src/query/queryissue/issues_ddl.go +++ b/yb-voyager/src/query/queryissue/issues_ddl.go @@ -31,7 +31,7 @@ var generatedColumnsIssue = issue.Issue{ Impact: constants.IMPACT_LEVEL_1, Description: STORED_GENERATED_COLUMNS_ISSUE_DESCRIPTION, GH: "https://github.com/yugabyte/yugabyte-db/issues/10695", - Suggestion: "Using Triggers to update the generated columns is one way to work around this issue, refer docs link for more details.", + Suggestion: STORED_GENERATED_COLUMN_ISSUE_SUGGESTION, DocsLink: "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#generated-always-as-stored-type-column-is-not-supported", MinimumVersionsFixedIn: map[string]*ybversion.YBVersion{ ybversion.SERIES_2_25: ybversion.V2_25_0_0, @@ -50,7 +50,7 @@ var unloggedTableIssue = issue.Issue{ Impact: constants.IMPACT_LEVEL_1, Description: UNLOGGED_TABLES_ISSUE_DESCRIPTION, GH: "https://github.com/yugabyte/yugabyte-db/issues/1129/", - Suggestion: "Remove UNLOGGED keyword to make it work", + Suggestion: UNLOGGED_TABLES_ISSUE_SUGGESTION, DocsLink: "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#unlogged-table-is-not-supported", MinimumVersionsFixedIn: map[string]*ybversion.YBVersion{ ybversion.SERIES_2024_2: ybversion.V2024_2_0_0, @@ -84,7 +84,7 @@ var storageParameterIssue = issue.Issue{ Impact: constants.IMPACT_LEVEL_1, Description: STORAGE_PARAMETERS_ISSUE_DESCRIPTION, GH: "https://github.com/yugabyte/yugabyte-db/issues/23467", - Suggestion: "Remove the storage parameters from the DDL", + Suggestion: STORAGE_PARAMETERS_ISSUE_SUGGESTION, DocsLink: "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#storage-parameters-on-indexes-or-constraints-in-the-source-postgresql", } @@ -99,7 +99,7 @@ var setColumnAttributeIssue = issue.Issue{ Impact: constants.IMPACT_LEVEL_1, Description: ALTER_TABLE_SET_COLUMN_ATTRIBUTE_ISSUE_DESCRIPTION, GH: "https://github.com/yugabyte/yugabyte-db/issues/1124", - Suggestion: "Remove it from the exported schema", + Suggestion: ALTER_TABLE_SET_COLUMN_ATTRIBUTE_ISSUE_SUGGESTION, DocsLink: "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#unsupported-alter-table-ddl-variants-in-source-schema", } @@ -114,7 +114,7 @@ var alterTableClusterOnIssue = issue.Issue{ Impact: constants.IMPACT_LEVEL_1, Description: ALTER_TABLE_CLUSTER_ON_ISSUE_DESCRIPTION, GH: "https://github.com/YugaByte/yugabyte-db/issues/1124", - Suggestion: "Remove it from the exported schema.", + Suggestion: ALTER_TABLE_CLUSTER_ON_ISSUE_SUGGESTION, DocsLink: "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#unsupported-alter-table-ddl-variants-in-source-schema", } @@ -146,7 +146,7 @@ var exclusionConstraintIssue = issue.Issue{ Impact: constants.IMPACT_LEVEL_1, Description: EXCLUSION_CONSTRAINT_ISSUE_DESCRIPTION, GH: "https://github.com/yugabyte/yugabyte-db/issues/3944", - Suggestion: "Refer docs link for details on possible workaround", + Suggestion: EXCLUSION_CONSTRAINT_ISSUE_SUGGESTION, DocsLink: "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#exclusion-constraints-is-not-supported", } @@ -163,7 +163,7 @@ var deferrableConstraintIssue = issue.Issue{ Impact: constants.IMPACT_LEVEL_3, Description: DEFERRABLE_CONSTRAINT_ISSUE_DESCRIPTION, GH: "https://github.com/yugabyte/yugabyte-db/issues/1709", - Suggestion: "Remove these constraints from the exported schema and make the neccessary changes to the application to work on target seamlessly", + Suggestion: DEFERRABLE_CONSTRAINT_ISSUE_SUGGESTION, DocsLink: "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#deferrable-constraint-on-constraints-other-than-foreign-keys-is-not-supported", } @@ -205,7 +205,7 @@ var policyRoleIssue = issue.Issue{ Name: "Policy with Roles", Impact: constants.IMPACT_LEVEL_1, Description: POLICY_ROLE_ISSUE_DESCRIPTION, - Suggestion: "Create the Users manually to make the policies work.", + Suggestion: POLICY_ROLE_ISSUE_SUGGESTION, GH: "https://github.com/yugabyte/yb-voyager/issues/1655", DocsLink: "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#policies-on-users-in-source-require-manual-user-creation", } @@ -250,8 +250,7 @@ var beforeRowTriggerOnPartitionTableIssue = issue.Issue{ Description: BEFORE_ROW_TRIGGER_ON_PARTITION_TABLE_ISSUE_DESCRIPTION, DocsLink: "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#before-row-triggers-on-partitioned-tables", GH: "https://github.com/yugabyte/yugabyte-db/issues/24830", - Suggestion: "Create the triggers on individual partitions.", - + Suggestion: BEFORE_ROW_TRIGGER_ON_PARTITION_TABLE_ISSUE_SUGGESTION, MinimumVersionsFixedIn: map[string]*ybversion.YBVersion{ ybversion.SERIES_2_25: ybversion.V2_25_0_0, }, @@ -266,7 +265,7 @@ var alterTableAddPKOnPartitionIssue = issue.Issue{ Name: "Adding Primary Key to a partitioned table", Impact: constants.IMPACT_LEVEL_1, Description: ALTER_TABLE_ADD_PK_ON_PARTITION_ISSUE_DESCRIPTION, - Suggestion: "After export schema, the ALTER table should be merged with CREATE table for partitioned tables.", + Suggestion: ALTER_TABLE_ADD_PK_ON_PARTITION_ISSUE_SUGGESTION, DocsLink: "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#adding-primary-key-to-a-partitioned-table-results-in-an-error", GH: "https://github.com/yugabyte/yugabyte-db/issues/10074", MinimumVersionsFixedIn: map[string]*ybversion.YBVersion{ @@ -287,7 +286,7 @@ var expressionPartitionIssue = issue.Issue{ Name: "Tables partitioned using expressions containing primary or unique keys", Impact: constants.IMPACT_LEVEL_1, Description: EXPRESSION_PARTITION_ISSUE_DESCRIPTION, - Suggestion: "Remove the Constriant from the table definition", + Suggestion: EXPRESSION_PARTITION_ISSUE_SUGGESTION, GH: "https://github.com/yugabyte/yb-voyager/issues/698", DocsLink: "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/mysql/#tables-partitioned-with-expressions-cannot-contain-primary-unique-keys", } @@ -301,7 +300,7 @@ var multiColumnListPartition = issue.Issue{ Name: "Multi-column partition by list", Impact: constants.IMPACT_LEVEL_1, Description: MULTI_COLUMN_LIST_PARTITION_ISSUE_DESCRIPTION, - Suggestion: "Make it a single column partition by list or choose other supported Partitioning methods", + Suggestion: MULTI_COLUMN_LIST_PARTITION_ISSUE_SUGGESTION, GH: "https://github.com/yugabyte/yb-voyager/issues/699", DocsLink: "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/mysql/#multi-column-partition-by-list-is-not-supported", } @@ -315,7 +314,7 @@ var insufficientColumnsInPKForPartition = issue.Issue{ Name: "Partition key columns not part of Primary Key", Impact: constants.IMPACT_LEVEL_1, Description: INSUFFICIENT_COLUMNS_IN_PK_FOR_PARTITION_ISSUE_DESCRIPTION, - Suggestion: "Add all Partition columns to Primary Key", + Suggestion: INSUFFICIENT_COLUMNS_IN_PK_FOR_PARTITION_ISSUE_SUGGESTION, GH: "https://github.com/yugabyte/yb-voyager/issues/578", DocsLink: "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/oracle/#partition-key-column-not-part-of-primary-key-columns", } @@ -331,7 +330,7 @@ var xmlDatatypeIssue = issue.Issue{ Name: "Unsupported datatype - xml", Impact: constants.IMPACT_LEVEL_3, Description: XML_DATATYPE_ISSUE_DESCRIPTION, - Suggestion: "Data ingestion is not supported for this type in YugabyteDB so handle this type in different way. Refer link for more details.", + Suggestion: XML_DATATYPE_ISSUE_SUGGESTION, GH: "https://github.com/yugabyte/yugabyte-db/issues/1043", DocsLink: "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#data-ingestion-on-xml-data-type-is-not-supported", } @@ -347,7 +346,7 @@ var xidDatatypeIssue = issue.Issue{ Name: "Unsupported datatype - xid", Impact: constants.IMPACT_LEVEL_3, Description: XID_DATATYPE_ISSUE_DESCRIPTION, - Suggestion: "Functions for this type e.g. txid_current are not supported in YugabyteDB yet", + Suggestion: XID_DATATYPE_ISSUE_SUGGESTION, GH: "https://github.com/yugabyte/yugabyte-db/issues/15638", DocsLink: "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#xid-functions-is-not-supported", } @@ -424,7 +423,7 @@ var primaryOrUniqueOnUnsupportedIndexTypesIssue = issue.Issue{ Impact: constants.IMPACT_LEVEL_1, Description: PK_UK_ON_COMPLEX_DATATYPE_ISSUE_DESCRIPTION, GH: "https://github.com/yugabyte/yugabyte-db/issues/25003", - Suggestion: "Refer to the docs link for the workaround", + Suggestion: PK_UK_ON_COMPLEX_DATATYPE_ISSUE_SUGGESTION, DocsLink: "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#indexes-on-some-complex-data-types-are-not-supported", //Keeping it similar for now, will see if we need to a separate issue on docs, } @@ -443,7 +442,7 @@ var indexOnComplexDatatypesIssue = issue.Issue{ Impact: constants.IMPACT_LEVEL_1, Description: INDEX_ON_COMPLEX_DATATYPE_ISSUE_DESCRIPTION, GH: "https://github.com/yugabyte/yugabyte-db/issues/25003", - Suggestion: "Refer to the docs link for the workaround", + Suggestion: INDEX_ON_COMPLEX_DATATYPE_ISSUE_SUGGESTION, DocsLink: "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#indexes-on-some-complex-data-types-are-not-supported", } @@ -459,7 +458,7 @@ var foreignTableIssue = issue.Issue{ Impact: constants.IMPACT_LEVEL_1, Description: FOREIGN_TABLE_ISSUE_DESCRIPTION, GH: "https://github.com/yugabyte/yb-voyager/issues/1627", - Suggestion: "SERVER '%s', and USER MAPPING should be created manually on the target to create and use the foreign table", + Suggestion: FOREIGN_TABLE_ISSUE_SUGGESTION, DocsLink: "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#foreign-table-in-the-source-database-requires-server-and-user-mapping", } @@ -487,7 +486,7 @@ var percentTypeSyntax = issue.Issue{ Name: "Referencing type declaration of variables", Impact: constants.IMPACT_LEVEL_1, Description: REFERENCED_TYPE_DECLARATION_ISSUE_DESCRIPTION, - Suggestion: "Fix the syntax to include the actual type name instead of referencing the type of a column", + Suggestion: REFERENCED_TYPE_DECLARATION_ISSUE_SUGGESTION, GH: "https://github.com/yugabyte/yugabyte-db/issues/23619", DocsLink: "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#type-syntax-is-not-supported", } @@ -501,7 +500,7 @@ var loDatatypeIssue = issue.Issue{ Name: "Unsupported datatype - lo", Impact: constants.IMPACT_LEVEL_1, Description: LARGE_OBJECT_DATATYPE_ISSUE_DESCRIPTION, - Suggestion: "Large objects are not yet supported in YugabyteDB, no workaround available currently", + Suggestion: LARGE_OBJECT_DATATYPE_ISSUE_SUGGESTION, GH: "https://github.com/yugabyte/yugabyte-db/issues/25318", DocsLink: "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#large-objects-and-its-functions-are-currently-not-supported", // TODO } @@ -517,7 +516,7 @@ var multiRangeDatatypeIssue = issue.Issue{ Name: "Unsupported datatype - Multirange", Impact: constants.IMPACT_LEVEL_1, Description: MULTI_RANGE_DATATYPE_ISSUE_DESCRIPTION, - Suggestion: "Multirange data type is not yet supported in YugabyteDB, no workaround available currently", + Suggestion: MULTI_RANGE_DATATYPE_ISSUE_SUGGESTION, GH: "https://github.com/yugabyte/yugabyte-db/issues/25575", DocsLink: "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#postgresql-12-and-later-features", MinimumVersionsFixedIn: map[string]*ybversion.YBVersion{ @@ -536,7 +535,7 @@ var securityInvokerViewIssue = issue.Issue{ Name: "Security Invoker Views", Impact: constants.IMPACT_LEVEL_1, Description: SECURITY_INVOKER_VIEWS_ISSUE_DESCRIPTION, - Suggestion: "Security Invoker Views are not yet supported in YugabyteDB, no workaround available currently", + Suggestion: SECURITY_INVOKER_VIEWS_ISSUE_SUGGESTION, GH: "https://github.com/yugabyte/yugabyte-db/issues/25575", DocsLink: "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#postgresql-12-and-later-features", MinimumVersionsFixedIn: map[string]*ybversion.YBVersion{ @@ -553,7 +552,7 @@ var deterministicOptionCollationIssue = issue.Issue{ Name: DETERMINISTIC_OPTION_WITH_COLLATION_NAME, Impact: constants.IMPACT_LEVEL_1, Description: DETERMINISTIC_OPTION_WITH_COLLATION_ISSUE_DESCRIPTION, - Suggestion: "This feature is not supported in YugabyteDB yet", + Suggestion: DETERMINISTIC_OPTION_WITH_COLLATION_ISSUE_SUGGESTION, GH: "https://github.com/yugabyte/yugabyte-db/issues/25575", DocsLink: "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#postgresql-12-and-later-features", MinimumVersionsFixedIn: map[string]*ybversion.YBVersion{ @@ -571,7 +570,7 @@ var foreignKeyReferencesPartitionedTableIssue = issue.Issue{ Name: FOREIGN_KEY_REFERENCES_PARTITIONED_TABLE_NAME, Impact: constants.IMPACT_LEVEL_1, Description: FOREIGN_KEY_REFERENCES_PARTITIONED_TABLE_ISSUE_DESCRIPTION, - Suggestion: "No workaround available ", + Suggestion: FOREIGN_KEY_REFERENCES_PARTITIONED_TABLE_ISSUE_SUGGESTION, GH: "https://github.com/yugabyte/yugabyte-db/issues/25575", DocsLink: "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#postgresql-12-and-later-features", MinimumVersionsFixedIn: map[string]*ybversion.YBVersion{ @@ -591,7 +590,7 @@ var sqlBodyInFunctionIssue = issue.Issue{ Name: SQL_BODY_IN_FUNCTION_NAME, Impact: constants.IMPACT_LEVEL_1, Description: "SQL Body for sql languages in function statement is not supported in YugabyteDB", - Suggestion: "No workaround available", + Suggestion: SQL_BODY_IN_FUNCTION_ISSUE_SUGGESTION, GH: "https://github.com/yugabyte/yugabyte-db/issues/25575", DocsLink: "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#postgresql-12-and-later-features", MinimumVersionsFixedIn: map[string]*ybversion.YBVersion{