diff --git a/backend/internal/handler/ghost.go b/backend/internal/handler/ghost.go index 7f49db5..8ff4964 100644 --- a/backend/internal/handler/ghost.go +++ b/backend/internal/handler/ghost.go @@ -143,6 +143,49 @@ func GhOstGetConfig(c *gin.Context) { c.JSON(http.StatusOK, getGhostConfig()) } +// getMySQLVersion returns the MySQL version string for an instance +func getMySQLVersion(instanceID int) string { + var inst model.Instance + if model.DB.First(&inst, instanceID).Error != nil { + return "" + } + if inst.DBType != "mysql" { + return "" + } + db, err := getDBConnection(&inst, "") + if err != nil { + return "" + } + defer db.Close() + var version string + db.QueryRow("SELECT VERSION()").Scan(&version) + return version +} + +// supportsInstantDDL checks if MySQL version supports INSTANT DDL (8.0.12+) +func supportsInstantDDL(version string) bool { + // Parse "8.0.32" or "5.7.40-log" etc + parts := strings.SplitN(version, "-", 2) + v := parts[0] + segments := strings.Split(v, ".") + if len(segments) < 3 { + return false + } + major, _ := strconv.Atoi(segments[0]) + minor, _ := strconv.Atoi(segments[1]) + patch, _ := strconv.Atoi(segments[2]) + if major > 8 { + return true + } + if major == 8 && minor > 0 { + return true + } + if major == 8 && minor == 0 && patch >= 12 { + return true + } + return false +} + // POST /api/v1/ghost/analyze — analyze DDL and return OSC recommendation func GhOstAnalyzeDDL(c *gin.Context) { var req struct { @@ -158,29 +201,49 @@ func GhOstAnalyzeDDL(c *gin.Context) { analysis := analyzeDDL(req.Alter) + // Get MySQL version for INSTANT DDL check + mysqlVersion := "" + instantDDL := false + if req.InstanceID > 0 { + mysqlVersion = getMySQLVersion(req.InstanceID) + instantDDL = supportsInstantDDL(mysqlVersion) + } + + // Cloud databases: prefer Native DDL + cloudType := getSetting("ghost_cloud_type", "self_hosted") + if cloudType != "self_hosted" && analysis.NeedsOSC { + analysis.NeedsOSC = false + analysis.RecommendedTool = "native" + analysis.Reason = fmt.Sprintf("云数据库(%s)建议使用原生DDL,无需gh-ost", cloudType) + } + // Also check table row count if instance_id provided rowCount := -1 if req.InstanceID > 0 { rowCount = getTableRowCount(req.InstanceID, req.Database, req.Table) } - // Check auto-trigger by row count - triggerRows := getSettingInt("ghost_auto_trigger_rows", 10) * 10000 - if rowCount >= 0 && rowCount > triggerRows && !analysis.NeedsOSC { - analysis.NeedsOSC = true - analysis.RecommendedTool = "gh-ost" - analysis.Reason = fmt.Sprintf("表行数(%d)超过阈值(%d),推荐使用 gh-ost", rowCount, triggerRows) + // Check auto-trigger by row count (only for self-hosted) + if cloudType == "self_hosted" { + triggerRows := getSettingInt("ghost_auto_trigger_rows", 10) * 10000 + if rowCount >= 0 && rowCount > triggerRows && !analysis.NeedsOSC { + analysis.NeedsOSC = true + analysis.RecommendedTool = "gh-ost" + analysis.Reason = fmt.Sprintf("表行数(%d)超过阈值(%d),推荐使用 gh-ost", rowCount, triggerRows) + } } c.JSON(http.StatusOK, gin.H{ - "enabled": isGhostEnabled(), - "ddl_types": analysis.DDLTypes, - "needs_osc": analysis.NeedsOSC, - "recommended_tool": analysis.RecommendedTool, - "reason": analysis.Reason, - "table_rows": rowCount, - "trigger_rows": triggerRows, - "config": getGhostConfig(), + "enabled": isGhostEnabled(), + "ddl_types": analysis.DDLTypes, + "needs_osc": analysis.NeedsOSC, + "recommended_tool": analysis.RecommendedTool, + "reason": analysis.Reason, + "table_rows": rowCount, + "mysql_version": mysqlVersion, + "supports_instant": instantDDL, + "cloud_type": cloudType, + "config": getGhostConfig(), }) }