diff --git a/go.mod b/go.mod index 40743f28ea..7d7b379b86 100644 --- a/go.mod +++ b/go.mod @@ -184,7 +184,7 @@ replace ( cloud.google.com/go/compute/metadata => cloud.google.com/go/compute/metadata v0.1.0 github.com/labstack/echo/v4 => github.com/labstack/echo/v4 v4.6.1 github.com/pingcap/log => github.com/pingcap/log v0.0.0-20191012051959-b742a5d432e9 - github.com/pingcap/parser => github.com/sjjian/parser v0.0.0-20240305095250-688ad439ef31 + github.com/pingcap/parser => github.com/sjjian/parser v0.0.0-20240704052347-b6199b7bccae github.com/swaggo/swag => github.com/swaggo/swag v1.6.7 google.golang.org/grpc => google.golang.org/grpc v1.29.0 ) diff --git a/go.sum b/go.sum index da27c5b767..b88cedc87f 100644 --- a/go.sum +++ b/go.sum @@ -793,8 +793,8 @@ github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrf github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/sjjian/parser v0.0.0-20240305095250-688ad439ef31 h1:E3JSX1FjUlg8ep8XQlLkdTFbZhM4tmecpdfZhUuubLs= -github.com/sjjian/parser v0.0.0-20240305095250-688ad439ef31/go.mod h1:Qq2tnreUXwVo7NAKAHmbWFsgqpDUkxwhJCClY+ZCudA= +github.com/sjjian/parser v0.0.0-20240704052347-b6199b7bccae h1:rTwogc7Uq0/7zMHNhpQTqjtGKdccVh8Z8vHReIlsXG0= +github.com/sjjian/parser v0.0.0-20240704052347-b6199b7bccae/go.mod h1:Qq2tnreUXwVo7NAKAHmbWFsgqpDUkxwhJCClY+ZCudA= github.com/skeema/knownhosts v1.2.0 h1:h9r9cf0+u7wSE+M183ZtMGgOJKiL96brpaz5ekfJCpM= github.com/skeema/knownhosts v1.2.0/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= diff --git a/sqle/api/app.go b/sqle/api/app.go index 819def6c5e..acf7dc6a0d 100644 --- a/sqle/api/app.go +++ b/sqle/api/app.go @@ -281,6 +281,11 @@ func StartApi(net *gracenet.Net, exitChan chan struct{}, config *config.SqleOpti v1ProjectOpRouter.POST("/:project_name/pipelines", v1.CreatePipeline) v1ProjectOpRouter.DELETE("/:project_name/pipelines/:pipeline_id/", v1.DeletePipeline) v1ProjectOpRouter.PATCH("/:project_name/pipelines/:pipeline_id/", v1.UpdatePipeline) + + // database_compare + v1ProjectOpRouter.POST("/:project_name/database_comparison/execute_comparison", v1.ExecuteDatabaseComparison) + v1ProjectOpRouter.POST("/:project_name/database_comparison/comparison_statements", v1.GetComparisonStatement) + v1ProjectOpRouter.POST("/:project_name/database_comparison/modify_sql_statements", v1.GenDatabaseDiffModifySQLs) } // project member router @@ -428,12 +433,8 @@ func StartApi(net *gracenet.Net, exitChan chan struct{}, config *config.SqleOpti v1Router.POST("/rule_templates/parse", v1.ParseProjectRuleTemplateFile) v1Router.GET("/import_rule_template", v1.GetImportRuleTemplateFile) - - // 全局 sql manage - v1Router.GET("/sql_manages", v1.GetGlobalSqlManageList) - + // 全局 workflow - v1Router.GET("/workflows", v1.GetGlobalWorkflowsV1) v1Router.GET("/rule_knowledge/db_types/:db_type/rules/:rule_name/", v1.GetRuleKnowledge) v1Router.GET("/rule_knowledge/db_types/:db_type/custom_rules/:rule_name/", v1.GetCustomRuleKnowledge) v1Router.GET("/workflows/statistic_of_instances", v1.GetWorkflowStatisticOfInstances) @@ -463,6 +464,11 @@ func StartApi(net *gracenet.Net, exitChan chan struct{}, config *config.SqleOpti // dashboard v1Router.GET("/dashboard", v1.Dashboard) + // 全局 sql manage + v1Router.GET("/dashboard/sql_manages", v1.GetGlobalSqlManageList) + v1Router.GET("/dashboard/sql_manages/statistics", v1.GetGlobalSqlManageStatistics) + v1Router.GET("/dashboard/workflows", v1.GetGlobalWorkflowsV1) + v1Router.GET("/dashboard/workflows/statistics", v1.GetGlobalWorkflowStatistics) // configurations v1Router.GET("/configurations/drivers", v1.GetDrivers) @@ -483,6 +489,7 @@ func StartApi(net *gracenet.Net, exitChan chan struct{}, config *config.SqleOpti v1Router.GET("/company_notice", v1.GetCompanyNotice) // 系统功能开关 v1Router.GET("/system/module_status", v1.GetSystemModuleStatus) + v1Router.GET("/system/module_red_dots", v1.GetSystemModuleRedDots) } // enterprise customized apis diff --git a/sqle/api/controller/v1/database_compare.go b/sqle/api/controller/v1/database_compare.go new file mode 100644 index 0000000000..2083f15271 --- /dev/null +++ b/sqle/api/controller/v1/database_compare.go @@ -0,0 +1,140 @@ +package v1 + +import ( + "github.com/actiontech/sqle/sqle/api/controller" + "github.com/actiontech/sqle/sqle/model" + "github.com/labstack/echo/v4" +) + +type GetDatabaseComparisonReqV1 struct { + BaseDBObject *DatabaseComparisonObject `json:"base_db_object" query:"base_db_object"` + ComparisonDBObject *DatabaseComparisonObject `json:"comparison_db_object" query:"comparison_db_object"` +} + +type DatabaseComparisonObject struct { + InstanceId string `json:"instance_id" query:"instance_id"` + SchemaName *string `json:"schema_name,omitempty" query:"schema_name"` +} +type DatabaseComparisonResV1 struct { + controller.BaseRes + Data []*SchemaObject `json:"data"` +} + +type SchemaObject struct { + BaseSchemaName string `json:"base_schema_name"` + ComparisonSchemaName string `json:"comparison_schema_name"` + ComparisonResult string `json:"comparison_result" enums:"same,inconsistent,base_not_exist,comparison_not_exist"` + DatabaseDiffObjects []*DatabaseDiffObject `json:"database_diff_objects,omitempty"` + InconsistentNum int `json:"inconsistent_num"` +} + +type DatabaseDiffObject struct { + InconsistentNum int `json:"inconsistent_num"` + ObjectType string `json:"object_type" enums:"TABLE,VIEW,PROCEDURE,TIGGER,EVENT,FUNCTION"` + ObjectsDiffResults []*ObjectDiffResult `json:"objects_diff_result,omitempty"` +} + +type ObjectDiffResult struct { + ComparisonResult string `json:"comparison_result" enums:"same,inconsistent,base_not_exist,comparison_not_exist"` + ObjectName string `json:"object_name"` +} + +// @Summary 执行数据库结构对比并获取结果 +// @Description get database comparison +// @Id executeDatabaseComparisonV1 +// @Tags database_comparison +// @Security ApiKeyAuth +// @Accept json +// @Param project_name path string true "project name" +// @Param database_comparison body v1.GetDatabaseComparisonReqV1 true "get database comparison request" +// @Success 200 {object} v1.DatabaseComparisonResV1 +// @router /v1/projects/{project_name}/database_comparison/execute_comparison [post] +func ExecuteDatabaseComparison(c echo.Context) error { + + return getDatabaseComparison(c) +} + +type DatabaseObject struct { + ObjectName string `json:"object_name"` + ObjectType string `json:"object_type" enums:"TABLE,VIEW,PROCEDURE,TIGGER,EVENT,FUNCTION"` +} + +type GetComparisonStatementsReqV1 struct { + DatabaseComparisonObject GetDatabaseComparisonReqV1 `json:"database_comparison_object"` + DatabaseObject *DatabaseObject `json:"database_object"` +} + +type DatabaseComparisonStatementsResV1 struct { + controller.BaseRes + Data *DatabaseComparisonStatements `json:"data"` +} + +type DatabaseComparisonStatements struct { + BaseSQL *SQLStatementWithAuditResult `json:"base_sql"` + ComparisondSQL *SQLStatementWithAuditResult `json:"comparison_sql"` +} + +type SQLStatementWithAuditResult struct { + SQLStatement string `json:"sql_statement"` + AuditResults []*SQLAuditResult `json:"audit_results"` +} + +type SQLAuditResult struct { + Level string `json:"level" example:"warn"` + Message string `json:"message" example:"避免使用不必要的内置函数md5()"` + RuleName string `json:"rule_name"` + DbType string `json:"db_type"` + I18nAuditResultInfo model.I18nAuditResultInfo `json:"i18n_audit_result_info"` +} + +// @Summary 获取对比语句 +// @Description get database comparison detail +// @Id getComparisonStatementV1 +// @Tags database_comparison +// @Security ApiKeyAuth +// @Accept json +// @Param project_name path string true "project name" +// @Param database_comparison_object body v1.GetComparisonStatementsReqV1 true "get database comparison statement request" +// @Success 200 {object} v1.DatabaseComparisonStatementsResV1 +// @router /v1/projects/{project_name}/database_comparison/comparison_statements [post] +func GetComparisonStatement(c echo.Context) error { + + return getComparisonStatement(c) +} + +type GenModifylSQLReqV1 struct { + BaseInstanceId string `json:"base_instance_id"` + ComparisonInstanceId string `json:"comparison_instance_id"` + DatabaseSchemaObjects []*DatabaseSchemaObject `json:"database_schema_objects"` +} + +type DatabaseSchemaObject struct { + BaseSchemaName string `json:"base_schema_name"` + ComparisonSchemaName string `json:"comparison_schema_name"` + DatabaseObjects []*DatabaseObject `json:"database_objects"` +} + +type GenModifySQLResV1 struct { + controller.BaseRes + Data []*DatabaseDiffModifySQL `json:"data"` +} + +type DatabaseDiffModifySQL struct { + SchemaName string `json:"schema_name"` + ModifySQLs []*SQLStatementWithAuditResult `json:"modify_sqls"` +} + +// @Summary 生成变更SQL +// @Description generate database diff modify sqls +// @Id genDatabaseDiffModifySQLsV1 +// @Tags database_comparison +// @Security ApiKeyAuth +// @Accept json +// @Param project_name path string true "project name" +// @Param gen_modify_sql body v1.GenModifylSQLReqV1 true "generate database diff modify sqls request" +// @Success 200 {object} v1.GenModifySQLResV1 +// @router /v1/projects/{project_name}/database_comparison/modify_sql_statements [post] +func GenDatabaseDiffModifySQLs(c echo.Context) error { + + return genDatabaseDiffModifySQLs(c) +} diff --git a/sqle/api/controller/v1/database_compare_ce.go b/sqle/api/controller/v1/database_compare_ce.go new file mode 100644 index 0000000000..de5e76cc33 --- /dev/null +++ b/sqle/api/controller/v1/database_compare_ce.go @@ -0,0 +1,25 @@ +//go:build !enterprise +// +build !enterprise + +package v1 + +import ( + e "errors" + + "github.com/actiontech/sqle/sqle/errors" + "github.com/labstack/echo/v4" +) + +var ErrCommunityEditionNotSupportDatabaseStructComparison = errors.New(errors.EnterpriseEditionFeatures, e.New("database struct comparison is enterprise version feature")) + +func getDatabaseComparison(c echo.Context) error { + return ErrCommunityEditionNotSupportDatabaseStructComparison +} + +func getComparisonStatement(c echo.Context) error { + return ErrCommunityEditionNotSupportDatabaseStructComparison +} + +func genDatabaseDiffModifySQLs(c echo.Context) error { + return ErrCommunityEditionNotSupportDatabaseStructComparison +} diff --git a/sqle/api/controller/v1/instance_audit_plan.go b/sqle/api/controller/v1/instance_audit_plan.go index 72ab7d3711..5f514714b2 100644 --- a/sqle/api/controller/v1/instance_audit_plan.go +++ b/sqle/api/controller/v1/instance_audit_plan.go @@ -418,6 +418,7 @@ type AuditPlanTypeResBase struct { AuditPlanId uint `json:"audit_plan_id"` AuditPlanType string `json:"type"` AuditPlanTypeDesc string `json:"desc"` + Token string `json:"token"` } type GetInstanceAuditPlansReqV1 struct { @@ -519,7 +520,7 @@ func GetInstanceAuditPlans(c echo.Context) error { typeBases := make([]AuditPlanTypeResBase, 0, len(auditPlanIds)) for _, auditPlanId := range auditPlanIds { if auditPlanId != "" { - typeBase, err := ConvertAuditPlanTypeToResByID(c.Request().Context(), auditPlanId) + typeBase, err := ConvertAuditPlanTypeToResByID(c.Request().Context(), auditPlanId, v.Token) if err != nil { return controller.JSONBaseErrorReq(c, err) } @@ -547,7 +548,7 @@ func GetInstanceAuditPlans(c echo.Context) error { }) } -func ConvertAuditPlanTypeToResByID(ctx context.Context, id string) (AuditPlanTypeResBase, error) { +func ConvertAuditPlanTypeToResByID(ctx context.Context, id string, token string) (AuditPlanTypeResBase, error) { auditPlanID, err := strconv.Atoi(id) if err != nil { return AuditPlanTypeResBase{}, err @@ -566,6 +567,7 @@ func ConvertAuditPlanTypeToResByID(ctx context.Context, id string) (AuditPlanTyp AuditPlanType: auditPlan.Type, AuditPlanTypeDesc: locale.Bundle.LocalizeMsgByCtx(ctx, meta.Desc), AuditPlanId: auditPlan.ID, + Token: token, }, nil } } @@ -822,6 +824,11 @@ func GetAuditPlanExecCmd(projectName string, iap *model.InstanceAuditPlan, ap *m return "" } + if ap.Type == "default" { + cmdTpl := "--project=%s --audit_plan_id=%d --token=%s" + return fmt.Sprintf(cmdTpl, iap.ProjectId, ap.ID, iap.Token) + } + address := config.GetOptions().SqleOptions.DMSServerAddress parsedURL, err := url.Parse(address) if err != nil { @@ -834,22 +841,32 @@ func GetAuditPlanExecCmd(projectName string, iap *model.InstanceAuditPlan, ap *m return "" } - scannerd, err := scannerCmd.GetScannerdCmd(ap.Type) - if err != nil { - logger.Infof("get scannerd %s failed %s", ap.Type, err) - return "" - } - cmd, err := scannerd.GenCommand("./scannerd", map[string]string{ - scannerCmd.FlagHost: ip, - scannerCmd.FlagPort: port, - scannerCmd.FlagProject: projectName, - scannerCmd.FlagToken: iap.Token, - scannerCmd.FlagAuditPlanID: fmt.Sprint(ap.ID), - }) - if err != nil { - logger.Infof("generate scannerd %s command failed %s", ap.Type, err) - return "" + var cmd string + switch ap.Type { + case auditplan.TypeAllAppExtract: + cmd = fmt.Sprintf(`SQLE_PROJECT_NAME=%s \ +PROJECT_APP_NAME= \ +java - \ +-jar `, projectName) + default: + scannerd, err := scannerCmd.GetScannerdCmd(ap.Type) + if err != nil { + logger.Infof("get scannerd %s failed %s", ap.Type, err) + return "" + } + cmd, err = scannerd.GenCommand("./scannerd", map[string]string{ + scannerCmd.FlagHost: ip, + scannerCmd.FlagPort: port, + scannerCmd.FlagProject: projectName, + scannerCmd.FlagToken: iap.Token, + scannerCmd.FlagAuditPlanID: fmt.Sprint(ap.ID), + }) + if err != nil { + logger.Infof("generate scannerd %s command failed %s", ap.Type, err) + return "" + } } + return cmd } diff --git a/sqle/api/controller/v1/red_dot_notification.go b/sqle/api/controller/v1/red_dot_notification.go new file mode 100644 index 0000000000..4579f814a0 --- /dev/null +++ b/sqle/api/controller/v1/red_dot_notification.go @@ -0,0 +1,85 @@ +package v1 + +import ( + "context" + + "github.com/actiontech/sqle/sqle/model" + "github.com/labstack/echo/v4" +) + +type RedDotModule interface { + Name() string + HasRedDot(ctx echo.Context) (bool, error) +} + +type RedDot struct { + ModuleName string + HasRedDot bool +} + +var redDotList []RedDotModule + +func RegisterRedDotModules(redDotModule ...RedDotModule) { + redDotList = append(redDotList, redDotModule...) +} + +func GetSystemModuleRedDotsList(ctx echo.Context) ([]*RedDot, error) { + redDots := make([]*RedDot, len(redDotList)) + for i, rd := range redDotList { + hasRedDot, err := rd.HasRedDot(ctx) + if err != nil { + return nil, err + } + redDots[i] = &RedDot{ + ModuleName: rd.Name(), + HasRedDot: hasRedDot, + } + } + return redDots, nil +} + +var statusOfGlobalWorkflowToBeFiltered []string = []string{ + model.WorkflowStatusWaitForExecution, + model.WorkflowStatusExecFailed, + model.WorkflowStatusWaitForAudit, + model.WorkflowStatusReject, +} + +// 查询待关注工单,是否有未处理的工单 +func HasOthersWorkflowToHandle(ctx context.Context, user *model.User, userVisibility GlobalDashBoardVisibility) (has bool, err error) { + filter, err := constructGlobalWorkflowBasicFilter(ctx, user, userVisibility, &globalWorkflowBasicFilter{ + FilterStatusList: statusOfGlobalWorkflowToBeFiltered, + }) + if err != nil { + return false, err + } + s := model.GetStorage() + total, err := s.GetGlobalWorkflowTotalNum(filter) + if err != nil { + return false, err + } + if total > 0 { + return true, nil + } + return false, nil +} + +// 查询创建的工单,是否有未处理的工单 +func HasMyWorkflowToHandle(ctx context.Context, user *model.User, userVisibility GlobalDashBoardVisibility) (has bool, err error) { + filter, err := constructGlobalWorkflowBasicFilter(ctx, user, userVisibility, &globalWorkflowBasicFilter{ + FilterStatusList: statusOfGlobalWorkflowToBeFiltered, + FilterCreateUserId: user.GetIDStr(), + }) + if err != nil { + return false, err + } + s := model.GetStorage() + total, err := s.GetGlobalWorkflowTotalNum(filter) + if err != nil { + return false, err + } + if total > 0 { + return true, nil + } + return false, nil +} diff --git a/sqle/api/controller/v1/red_dot_notification_ce.go b/sqle/api/controller/v1/red_dot_notification_ce.go new file mode 100644 index 0000000000..140588f6fc --- /dev/null +++ b/sqle/api/controller/v1/red_dot_notification_ce.go @@ -0,0 +1,47 @@ +//go:build !enterprise +// +build !enterprise + +package v1 + +import ( + "github.com/actiontech/dms/pkg/dms-common/dmsobject" + "github.com/actiontech/sqle/sqle/api/controller" + dms "github.com/actiontech/sqle/sqle/dms" + "github.com/labstack/echo/v4" +) + +func init() { + RegisterRedDotModules(GlobalDashBoardModule{}) +} + +type GlobalDashBoardModule struct{} + +func (m GlobalDashBoardModule) Name() string { + return "global_dashboard" +} + +func (m GlobalDashBoardModule) HasRedDot(ctx echo.Context) (bool, error) { + user, err := controller.GetCurrentUser(ctx, dms.GetUser) + if err != nil { + return false, err + } + permissions, isAdmin, err := dmsobject.GetUserOpPermission(ctx.Request().Context(), "", user.GetIDStr(), dms.GetDMSServerAddress()) + if err != nil { + return false, err + } + // 将用户权限信息,转化为全局待处理清单统一的用户可见性 + userVisibility := getGlobalDashBoardVisibilityOfUser(isAdmin, permissions) + has, err := HasOthersWorkflowToHandle(ctx.Request().Context(), user, userVisibility) + if err != nil { + return false, err + } + if has { + return true, nil + } + + has, err = HasMyWorkflowToHandle(ctx.Request().Context(), user, userVisibility) + if err != nil { + return false, err + } + return has, nil +} diff --git a/sqle/api/controller/v1/sql_manage.go b/sqle/api/controller/v1/sql_manage.go index dd7973970f..4013af8229 100644 --- a/sqle/api/controller/v1/sql_manage.go +++ b/sqle/api/controller/v1/sql_manage.go @@ -359,8 +359,8 @@ type GetGlobalSqlManageListReq struct { type GetGlobalSqlManageListResp struct { controller.BaseRes - Data []*GlobalSqlManage `json:"data"` - TotalNum uint64 `json:"total_num"` + Data []*GlobalSqlManage `json:"data"` + TotalNums uint64 `json:"total_nums"` } type GlobalSqlManage struct { @@ -390,7 +390,33 @@ type GlobalSqlManage struct { // @Param page_index query uint32 true "page index" // @Param page_size query uint32 true "size of per page" // @Success 200 {object} v1.GetGlobalSqlManageListResp -// @Router /v1/sql_manages [get] +// @Router /v1/dashboard/sql_manages [get] func GetGlobalSqlManageList(c echo.Context) error { return getGlobalSqlManageList(c) } + +type GetGlobalSqlManageStatisticsReq struct { + FilterProjectUid *string `query:"filter_project_uid" json:"filter_project_uid,omitempty"` + FilterInstanceId *string `query:"filter_instance_id" json:"filter_instance_id,omitempty"` + FilterProjectPriority *dmsV1.ProjectPriority `query:"filter_project_priority" json:"filter_project_priority,omitempty" enums:"high,medium,low"` +} + +type GetGlobalSqlManageStatisticsResp struct { + controller.BaseRes + TotalNums uint64 `json:"total_nums"` +} + +// GetGlobalSqlManageStatistics +// @Summary 获取全局管控sql统计信息 +// @Description get global sql manage statistics +// @Tags SqlManage +// @Id GetGlobalSqlManageStatistics +// @Security ApiKeyAuth +// @Param filter_project_uid query string false "project uid" +// @Param filter_instance_id query string false "instance id" +// @Param filter_project_priority query string false "project priority" Enums(high,medium,low) +// @Success 200 {object} v1.GetGlobalSqlManageStatisticsResp +// @Router /v1/dashboard/sql_manages/statistics [get] +func GetGlobalSqlManageStatistics(c echo.Context) error { + return getGlobalSqlManageStatistics(c) +} diff --git a/sqle/api/controller/v1/sql_manager_ce.go b/sqle/api/controller/v1/sql_manager_ce.go index b9ed24b5b1..49226e35cb 100644 --- a/sqle/api/controller/v1/sql_manager_ce.go +++ b/sqle/api/controller/v1/sql_manager_ce.go @@ -39,3 +39,7 @@ func getAuditPlanUnsolvedSQLCount(auditPlanId uint) (int64, error) { func getGlobalSqlManageList(c echo.Context) error { return ErrCommunityEditionNotSupportSqlManage } + +func getGlobalSqlManageStatistics(c echo.Context) error { + return ErrCommunityEditionNotSupportSqlManage +} diff --git a/sqle/api/controller/v1/statistic.go b/sqle/api/controller/v1/statistic.go index d3476ee4ac..618764a0a8 100644 --- a/sqle/api/controller/v1/statistic.go +++ b/sqle/api/controller/v1/statistic.go @@ -1,7 +1,6 @@ package v1 import ( - "context" "math" "net/http" "time" @@ -603,21 +602,12 @@ func StatisticRiskWorkflowV1(c echo.Context) error { riskWorkflows := make([]*RiskWorkflow, len(projectWorkflowStatusDetails)) for i, info := range projectWorkflowStatusDetails { - user, err := func() (*model.User, error) { - ctx, cancel := context.WithTimeout(c.Request().Context(), 5*time.Second) - defer cancel() - - return dms.GetUser(ctx, info.CreateUserId, controller.GetDMSServerAddress()) - }() - - if err != nil { - return controller.JSONBaseErrorReq(c, err) - } + userName := dms.GetUserNameWithDelTag(info.CreateUserId) riskWorkflows[i] = &RiskWorkflow{ Name: info.Subject, WorkflowID: info.WorkflowId, Status: info.Status, - CreateUser: user.Name, + CreateUser: userName, UpdateTime: info.UpdatedAt, } } diff --git a/sqle/api/controller/v1/system_module.go b/sqle/api/controller/v1/system_module.go index b9d109067c..2d9b2939b0 100644 --- a/sqle/api/controller/v1/system_module.go +++ b/sqle/api/controller/v1/system_module.go @@ -50,3 +50,44 @@ func GetSystemModuleStatus(c echo.Context) error { }, }) } + +type GetSystemModuleRedDotsRes struct { + controller.BaseRes + Data ModuleRedDots `json:"data"` +} + +type ModuleRedDots []ModuleRedDot + +type ModuleRedDot struct { + ModuleName string `json:"module_name" enums:"global_dashboard"` + HasRedDot bool `json:"has_red_dot"` +} + +// @Summary 查询系统各模块的红点提示信息 +// @Description get the red dot prompt information in the system +// @Id GetSystemModuleRedDots +// @Tags system +// @Security ApiKeyAuth +// @Success 200 {object} v1.GetSystemModuleRedDotsRes +// @router /v1/system/module_red_dots [get] +func GetSystemModuleRedDots(c echo.Context) error { + redDots, err := GetSystemModuleRedDotsList(c) + if err != nil { + return controller.JSONBaseErrorReq(c, err) + } + return c.JSON(http.StatusOK, &GetSystemModuleRedDotsRes{ + BaseRes: controller.NewBaseReq(nil), + Data: toModuleRedDots(redDots), + }) +} + +func toModuleRedDots(redDots []*RedDot) ModuleRedDots { + moduleRedDots := make(ModuleRedDots, 0, len(redDots)) + for _, redDot := range redDots { + moduleRedDots = append(moduleRedDots, ModuleRedDot{ + ModuleName: redDot.ModuleName, + HasRedDot: redDot.HasRedDot, + }) + } + return moduleRedDots +} diff --git a/sqle/api/controller/v1/workflow.go b/sqle/api/controller/v1/workflow.go index 6378b33427..2f3185e67b 100644 --- a/sqle/api/controller/v1/workflow.go +++ b/sqle/api/controller/v1/workflow.go @@ -548,31 +548,21 @@ type InstanceInfo struct { // @Tags workflow // @Id getGlobalWorkflowsV1 // @Security ApiKeyAuth -// @Param filter_subject query string false "filter subject" -// @Param filter_create_time_from query string false "filter create time from" -// @Param filter_create_time_to query string false "filter create time to" -// @Param filter_task_execute_start_time_from query string false "filter_task_execute_start_time_from" -// @Param filter_task_execute_start_time_to query string false "filter_task_execute_start_time_to" // @Param filter_create_user_id query string false "filter create user id" -// @Param filter_status query string false "filter workflow status" Enums(wait_for_audit,wait_for_execution,rejected,executing,canceled,exec_failed,finished) // @Param filter_status_list query []string false "filter by workflow status,, support using many status" Enums(wait_for_audit,wait_for_execution,rejected,executing,canceled,exec_failed,finished) // @Param filter_project_uid query string false "filter by project uid" // @Param filter_instance_id query string false "filter by instance id in project" // @Param filter_project_priority query string false "filter by project priority" Enums(high,medium,low) -// @Param filter_current_step_assignee_user_id query string false "filter current step assignee user id" -// @Param filter_task_instance_id query string false "filter instance id" // @Param page_index query uint32 true "page index" // @Param page_size query uint32 true "size of per page" // @Success 200 {object} v1.GetWorkflowsResV1 -// @router /v1/workflows [get] +// @router /v1/dashboard/workflows [get] func GetGlobalWorkflowsV1(c echo.Context) error { req := new(GetWorkflowsReqV1) if err := controller.BindAndValidateReq(c, req); err != nil { return controller.JSONBaseErrorReq(c, err) } - - // 1. 权限控制 - // 1.1. 获取用户的所有权限信息 + // 1. 获取用户权限信息 user, err := controller.GetCurrentUser(c, dms.GetUser) if err != nil { return controller.JSONBaseErrorReq(c, err) @@ -581,116 +571,108 @@ func GetGlobalWorkflowsV1(c echo.Context) error { if err != nil { return controller.JSONBaseErrorReq(c, err) } - // 1.2. 获取用户全局待关注清单的可见性 + // 2. 将用户权限信息,转化为全局待处理清单统一的用户可视范围 userVisibility := getGlobalDashBoardVisibilityOfUser(isAdmin, permissions) - if req.FilterCurrentStepAssigneeUserId != "" { - // 如果根据当前用户筛选,则筛选出用户在所有项目中的工单 - userVisibility = GlobalDashBoardVisibilityGlobal - } - // 1.3. 若用户可见性为多项目,则需要根据项目id筛选 - var projectIdsOfProjectAdmin []string - if userVisibility == GlobalDashBoardVisibilityProjects { - for _, permission := range permissions { - if permission.OpPermissionType == dmsV1.OpPermissionTypeProjectAdmin { - projectIdsOfProjectAdmin = append(projectIdsOfProjectAdmin, permission.RangeUids...) - } - } - } - // 2. 组织筛选项 - // 2.1. 基本筛选项 + // 3. 将用户可视范围、接口请求以及用户的权限范围,构造为全局工单的基础的过滤器,满足全局工单统一的过滤逻辑 + filter, err := constructGlobalWorkflowBasicFilter(c.Request().Context(), user, userVisibility, + &globalWorkflowBasicFilter{ + FilterCreateUserId: req.FilterCreateUserId, + FilterStatusList: req.FilterStatusList, + FilterProjectUid: req.FilterProjectUid, + FilterInstanceId: req.FilterInstanceId, + FilterProjectPriority: req.FilterProjectPriority, + }) + if err != nil { + return controller.JSONBaseErrorReq(c, err) + } + // 4. 过滤器增加分页 limit, offset := controller.GetLimitAndOffset(req.PageIndex, req.PageSize) - data := map[string]interface{}{ - // "filter_subject": req.FilterSubject, - // "filter_create_time_from": req.FilterCreateTimeFrom, - // "filter_create_time_to": req.FilterCreateTimeTo, - // "filter_task_execute_start_time_from": req.FilterTaskExecuteStartTimeFrom, - // "filter_task_execute_start_time_to": req.FilterTaskExecuteStartTimeTo, - // "filter_status": req.FilterStatus, - // "filter_current_step_assignee_user_id": req.FilterCurrentStepAssigneeUserId, - // "filter_task_instance_id": req.FilterTaskInstanceId, - // "current_user_id": user.GetIDStr(), - // "check_user_can_access": canViewGlobal, - "filter_create_user_id": req.FilterCreateUserId, - "limit": limit, - "offset": offset, - "filter_status_list": req.FilterStatusList, // 根据SQL工单的状态筛选多个状态的工单 - "filter_project_id": req.FilterProjectUid, // 根据项目id筛选某些一个项目下的多个工单 - "filter_instance_id": req.FilterInstanceId, // 根据工单记录的数据源id,筛选包含该数据源的工单,多数据源情况下,一旦包含该数据源,则被选中 - } - // 2.2 页面筛选项:如果根据项目优先级筛选,则先筛选出对应优先级下的项目 - var projectIdsByPriority []string - var projectMap map[string]*dmsV1.ListProject - if req.FilterProjectPriority != "" { - projectIdsByPriority, projectMap, err = loadProjectsByPriority(c.Request().Context(), req.FilterProjectPriority) - if err != nil { - return controller.JSONBaseErrorReq(c, err) - } - if len(projectMap) == 0 { - return c.JSON(http.StatusOK, GetWorkflowsResV1{ - BaseRes: controller.NewBaseReq(nil), - Data: []*WorkflowDetailResV1{}, - TotalNums: 0, - }) - } + filter["limit"] = limit + filter["offset"] = offset + // 5. 根据筛选项筛选工单信息 + s := model.GetStorage() + workflows, count, err := s.GetWorkflowsByReq(filter) + if err != nil { + return controller.JSONBaseErrorReq(c, err) } + if len(workflows) == 0 { + return c.JSON(http.StatusOK, GetWorkflowsResV1{ + BaseRes: controller.NewBaseReq(nil), + Data: []*WorkflowDetailResV1{}, + TotalNums: count, + }) + } + // 6. 从dms获取工单对应的项目信息 + var projectMap = make(ProjectMap) if req.FilterProjectPriority != "" { - // 2.2.1 若根据项目优先级筛选,则根据优先级对应的项目筛选 - data["filter_project_id_list"] = projectIdsByPriority + _, projectMap, err = loadProjectsByPriority(c.Request().Context(), req.FilterProjectPriority) + } else { + projectMap, err = loadProjectsByWorkflows(c.Request().Context(), workflows) } - - if req.FilterProjectPriority != "" && userVisibility == GlobalDashBoardVisibilityProjects { - // 2.2.2 若根据项目优先级筛选,且可以查看多项目待关注SQL,则将可查看的项目和项目优先级筛选后的项目的集合取交集 - data["filter_project_id_list"] = utils.IntersectionStringSlice(projectIdsByPriority, projectIdsOfProjectAdmin) + if err != nil { + return controller.JSONBaseErrorReq(c, err) } - // 2.3 若不根据项目优先级筛选 - if req.FilterProjectPriority == "" && userVisibility == GlobalDashBoardVisibilityProjects { - // 2.3.1 若可以查看多项目待关注SQL,则通过用户的有权限的项目进行筛选 - data["filter_project_id_list"] = projectIdsOfProjectAdmin + // 7. 从dms获取工单对应的数据源信息 + instanceMap, err := loadInstanceByWorkflows(c.Request().Context(), workflows) + if err != nil { + return controller.JSONBaseErrorReq(c, err) } - // 2.4. 若用户可见性为受让人,则可以查看在SQL管控中分配给他的SQL - if userVisibility == GlobalDashBoardVisibilityAssignee { - data["filter_current_step_assignee_user_id"] = user.GetIDStr() + return c.JSON(http.StatusOK, GetWorkflowsResV1{ + BaseRes: controller.NewBaseReq(nil), + Data: toGlobalWorkflowRes(workflows, projectMap, instanceMap), + TotalNums: count, + }) +} + +type ProjectMap map[string] /* project uid */ *dmsV1.ListProject + +func (m ProjectMap) ProjectName(projectUid string) string { + if m == nil { + return "" } + if project, exist := m[projectUid]; exist && project != nil { + return project.Name + } + return "" +} - // 3. 根据筛选项筛选SQL管控的SQL信息 - s := model.GetStorage() - workflows, count, err := s.GetWorkflowsByReq(data) - if err != nil { - return controller.JSONBaseErrorReq(c, err) +func (m ProjectMap) ProjectPriority(projectUid string) dmsV1.ProjectPriority { + if m == nil { + return dmsV1.ProjectPriorityUnknown + } + if project, exist := m[projectUid]; exist && project != nil { + return project.ProjectPriority } - /* - TODO 全局工单暂时不使用 - 1. viewable_instance_ids,check_user_can_access 调用 AddFilterInstanceAndUserAdmin 添加筛选项 - 2. 用户相关代码需要从DMS获取 - */ + return dmsV1.ProjectPriorityUnknown +} - // 从dms获取工单对应的项目信息,若该信息已经在根据项目优先级筛选时被加载,则不需要重复加载 - if req.FilterProjectPriority == "" { - projectMap, err = loadProjectsByWorkflows(c.Request().Context(), workflows) - if err != nil { - return controller.JSONBaseErrorReq(c, err) - } +type InstanceMap map[string] /* instance id */ *dmsV1.ListDBService + +func (m InstanceMap) InstanceName(instanceId string) string { + if m == nil { + return "" } - // 从dms获取工单对应的数据源信息 - instanceMap, err := loadInstanceByWorkflows(c.Request().Context(), workflows) - if err != nil { - return controller.JSONBaseErrorReq(c, err) + if instance, exist := m[instanceId]; exist && instance != nil { + return instance.Name } + return "" +} - workflowsResV1 := make([]*WorkflowDetailResV1, 0, len(workflows)) +func toGlobalWorkflowRes(workflows []*model.WorkflowListDetail, projectMap ProjectMap, instanceMap InstanceMap) (workflowsResV1 []*WorkflowDetailResV1) { + workflowsResV1 = make([]*WorkflowDetailResV1, 0, len(workflows)) for _, workflow := range workflows { instanceInfos := make([]InstanceInfo, 0, len(workflow.InstanceIds)) for _, id := range workflow.InstanceIds { instanceInfos = append(instanceInfos, InstanceInfo{ InstanceId: id, - InstanceName: instanceMap[id].Name, + InstanceName: instanceMap.InstanceName(id), }) } workflowRes := &WorkflowDetailResV1{ - ProjectName: projectMap[workflow.ProjectId].Name, + ProjectName: projectMap.ProjectName(workflow.ProjectId), ProjectUid: workflow.ProjectId, - ProjectPriority: projectMap[workflow.ProjectId].ProjectPriority, + ProjectPriority: projectMap.ProjectPriority(workflow.ProjectId), InstanceInfo: instanceInfos, Name: workflow.Subject, WorkflowId: workflow.WorkflowId, @@ -703,43 +685,182 @@ func GetGlobalWorkflowsV1(c echo.Context) error { } workflowsResV1 = append(workflowsResV1, workflowRes) } + return workflowsResV1 +} - return c.JSON(http.StatusOK, GetWorkflowsResV1{ +type GetGlobalWorkflowStatisticsReqV1 struct { + FilterCreateUserId string `json:"filter_create_user_id" query:"filter_create_user_id"` + FilterStatusList []string `json:"filter_status_list" query:"filter_status_list" validate:"dive,oneof=wait_for_audit wait_for_execution rejected canceled executing exec_failed finished"` + FilterProjectUid string `json:"filter_project_uid" query:"filter_project_uid"` + FilterInstanceId string `json:"filter_instance_id" query:"filter_instance_id"` + FilterProjectPriority dmsV1.ProjectPriority `json:"filter_project_priority" query:"filter_project_priority" valid:"omitempty,oneof=high medium low"` +} + +type GlobalWorkflowStatisticsResV1 struct { + controller.BaseRes + TotalNums uint64 `json:"total_nums"` +} + +// GetGlobalWorkflowStatistics +// @Summary 获取全局工单统计数据 +// @Description get global workflow statistics +// @Tags workflow +// @Id GetGlobalWorkflowStatistics +// @Security ApiKeyAuth +// @Param filter_create_user_id query string false "filter create user id" +// @Param filter_status_list query []string false "filter by workflow status,, support using many status" Enums(wait_for_audit,wait_for_execution,rejected,executing,canceled,exec_failed,finished) +// @Param filter_project_uid query string false "filter by project uid" +// @Param filter_instance_id query string false "filter by instance id in project" +// @Param filter_project_priority query string false "filter by project priority" Enums(high,medium,low) +// @Success 200 {object} v1.GlobalWorkflowStatisticsResV1 +// @router /v1/dashboard/workflows/statistics [get] +func GetGlobalWorkflowStatistics(c echo.Context) error { + req := new(GetGlobalWorkflowStatisticsReqV1) + if err := controller.BindAndValidateReq(c, req); err != nil { + return controller.JSONBaseErrorReq(c, err) + } + // 1. 获取用户权限信息 + user, err := controller.GetCurrentUser(c, dms.GetUser) + if err != nil { + return controller.JSONBaseErrorReq(c, err) + } + permissions, isAdmin, err := dmsobject.GetUserOpPermission(c.Request().Context(), "", user.GetIDStr(), dms.GetDMSServerAddress()) + if err != nil { + return controller.JSONBaseErrorReq(c, err) + } + // 2. 将用户权限信息,转化为全局待处理清单统一的用户可视范围 + userVisibility := getGlobalDashBoardVisibilityOfUser(isAdmin, permissions) + + // 3. 将用户可视范围、接口请求以及用户的权限范围,构造为全局工单的基础的过滤器,满足全局工单统一的过滤逻辑 + filter, err := constructGlobalWorkflowBasicFilter(c.Request().Context(), user, userVisibility, + &globalWorkflowBasicFilter{ + FilterCreateUserId: req.FilterCreateUserId, + FilterStatusList: req.FilterStatusList, + FilterProjectUid: req.FilterProjectUid, + FilterInstanceId: req.FilterInstanceId, + FilterProjectPriority: req.FilterProjectPriority, + }) + if err != nil { + return controller.JSONBaseErrorReq(c, err) + } + // 4. 根据筛选项获取工单数量 + s := model.GetStorage() + count, err := s.GetGlobalWorkflowTotalNum(filter) + if err != nil { + return controller.JSONBaseErrorReq(c, err) + } + + return c.JSON(http.StatusOK, GlobalWorkflowStatisticsResV1{ BaseRes: controller.NewBaseReq(nil), - Data: workflowsResV1, TotalNums: count, }) } -type GlobalDashBoardVisibility string +type globalWorkflowBasicFilter struct { + FilterCreateUserId string `json:"filter_create_user_id" query:"filter_create_user_id"` + FilterStatusList []string `json:"filter_status_list" query:"filter_status_list" validate:"dive,oneof=wait_for_audit wait_for_execution rejected canceled executing exec_failed finished"` + FilterProjectUid string `json:"filter_project_uid" query:"filter_project_uid"` + FilterInstanceId string `json:"filter_instance_id" query:"filter_instance_id"` + FilterProjectPriority dmsV1.ProjectPriority `json:"filter_project_priority" query:"filter_project_priority" valid:"omitempty,oneof=high medium low"` +} -const GlobalDashBoardVisibilityGlobal GlobalDashBoardVisibility = "global" -const GlobalDashBoardVisibilityProjects GlobalDashBoardVisibility = "projects" -const GlobalDashBoardVisibilityAssignee GlobalDashBoardVisibility = "assignee" +// 将用户可视范围、接口请求以及用户的权限范围,构造为全局工单的基础的过滤器,满足全局工单统一的过滤逻辑 +func constructGlobalWorkflowBasicFilter(ctx context.Context, user *model.User, userVisibility GlobalDashBoardVisibility, req *globalWorkflowBasicFilter) (map[string]interface{}, error) { + // 1. 基本筛选项 + data := map[string]interface{}{ + "filter_create_user_id": req.FilterCreateUserId, // 根据创建人ID筛选用户自己创建的工单 + "filter_status_list": req.FilterStatusList, // 根据SQL工单的状态筛选多个状态的工单 + "filter_project_id": req.FilterProjectUid, // 根据项目id筛选某些一个项目下的多个工单 + "filter_instance_id": req.FilterInstanceId, // 根据工单记录的数据源id,筛选包含该数据源的工单,多数据源情况下,一旦包含该数据源,则被选中 + } + // 1.1 页面筛选项:如果根据项目优先级筛选,则先筛选出对应优先级下的项目 + var projectIdsByPriority []string + var err error + if req.FilterProjectPriority != "" { + projectIdsByPriority, _, err = loadProjectsByPriority(ctx, req.FilterProjectPriority) + if err != nil { + return nil, err + } + data["filter_project_id_list"] = projectIdsByPriority + } + // 2. 发起的工单页面,根据当前用户筛选在所有项目下该用户创建的工单,因此将用户可视范围调整为全局 + if req.FilterCreateUserId != "" { + userVisibility.VisibilityType = GlobalDashBoardVisibilityGlobal + } + // 3. 待处理工单页面,根据当前用户的可视范围筛选 + switch userVisibility.ViewType() { + case GlobalDashBoardVisibilityProjects: + // 3.1 当用户的可视范围为多项目,则根据项目id筛选 + if req.FilterProjectPriority != "" { + // 若根据项目优先级筛选,则将可查看的项目和项目优先级筛选后的项目的集合取交集 + data["filter_project_id_list"] = utils.IntersectionStringSlice(projectIdsByPriority, userVisibility.ViewRange()) + } else { + // 若不根据项目优先级筛选,则通过用户的有权限的项目进行筛选 + data["filter_project_id_list"] = userVisibility.ViewRange() + } + case GlobalDashBoardVisibilityAssignee: + // 3.2 若用户可视范围为受让人,则查看分配给他的工单 + data["filter_current_step_assignee_user_id"] = user.GetIDStr() + } + return data, nil +} + +type VisibilityType string + +const GlobalDashBoardVisibilityGlobal VisibilityType = "global" // 全局可见 +const GlobalDashBoardVisibilityProjects VisibilityType = "projects" // 多项目可见 +const GlobalDashBoardVisibilityAssignee VisibilityType = "assignee" // 仅可见授予自己的 + +type GlobalDashBoardVisibility struct { + VisibilityType VisibilityType + VisibilityRange []string // 对于项目是项目id +} + +func (v GlobalDashBoardVisibility) ViewType() VisibilityType { + return v.VisibilityType +} +func (v GlobalDashBoardVisibility) ViewRange() []string { + return v.VisibilityRange +} + +// 将用户权限信息,转化为全局待处理清单统一的用户可视范围 func getGlobalDashBoardVisibilityOfUser(isAdmin bool, permissions []dmsV1.OpPermissionItem) GlobalDashBoardVisibility { // 角色:全局管理员,全局可查看者 if isAdmin { - return GlobalDashBoardVisibilityGlobal + return GlobalDashBoardVisibility{ + VisibilityType: GlobalDashBoardVisibilityGlobal, + } } for _, permission := range permissions { if permission.OpPermissionType == dmsV1.OpPermissionTypeGlobalView || permission.OpPermissionType == dmsV1.OpPermissionTypeGlobalManagement { - return GlobalDashBoardVisibilityGlobal + return GlobalDashBoardVisibility{ + VisibilityType: GlobalDashBoardVisibilityGlobal, + } } } // 角色:多项目管理者 + var projectRange []string for _, permission := range permissions { if permission.OpPermissionType == dmsV1.OpPermissionTypeProjectAdmin { - return GlobalDashBoardVisibilityProjects + projectRange = append(projectRange, permission.RangeUids...) + } + } + if len(projectRange) > 0 { + return GlobalDashBoardVisibility{ + VisibilityType: GlobalDashBoardVisibilityProjects, + VisibilityRange: projectRange, } } // 角色:受让人,事件处理者 - return GlobalDashBoardVisibilityAssignee + return GlobalDashBoardVisibility{ + VisibilityType: GlobalDashBoardVisibilityAssignee, + } } // 根据项目优先级从 dms 系统中获取相应的项目列表,并返回项目ID列表和项目映射 -func loadProjectsByPriority(ctx context.Context, priority dmsV1.ProjectPriority) (projectIds []string, projectMap map[string] /* project uid */ *dmsV1.ListProject, err error) { - projectMap = make(map[string]*dmsV1.ListProject) +func loadProjectsByPriority(ctx context.Context, priority dmsV1.ProjectPriority) (projectIds []string, projectMap ProjectMap, err error) { + projectMap = make(ProjectMap) // 如果根据项目优先级筛选SQL工单,则先获取项目优先级,根据优先级对应的项目ID进行筛选 projects, _, err := dmsobject.ListProjects(ctx, controller.GetDMSServerAddress(), dmsV1.ListProjectReq{ PageSize: 999, @@ -759,8 +880,8 @@ func loadProjectsByPriority(ctx context.Context, priority dmsV1.ProjectPriority) } // 根据工单列表中的项目ID从 dms 系统中获取对应的项目信息,并返回项目映射 -func loadProjectsByWorkflows(ctx context.Context, workflows []*model.WorkflowListDetail) (projectMap map[string] /* project uid */ *dmsV1.ListProject, err error) { - projectMap = make(map[string]*dmsV1.ListProject) +func loadProjectsByWorkflows(ctx context.Context, workflows []*model.WorkflowListDetail) (projectMap ProjectMap, err error) { + projectMap = make(ProjectMap) if len(workflows) == 0 { return projectMap, nil } @@ -775,7 +896,7 @@ func loadProjectsByWorkflows(ctx context.Context, workflows []*model.WorkflowLis return loadProjectsByProjectIds(ctx, projectIds) } -func loadProjectsByProjectIds(ctx context.Context, projectIds []string) (projectMap map[string] /* project uid */ *dmsV1.ListProject, err error) { +func loadProjectsByProjectIds(ctx context.Context, projectIds []string) (projectMap ProjectMap, err error) { // get project priority from dms projects, _, err := dmsobject.ListProjects(ctx, controller.GetDMSServerAddress(), dmsV1.ListProjectReq{ PageSize: uint32(len(projectIds)), @@ -793,8 +914,8 @@ func loadProjectsByProjectIds(ctx context.Context, projectIds []string) (project } // 根据工单列表中的实例ID从 dms 系统中获取对应的数据源实例信息,并返回实例映射 -func loadInstanceByWorkflows(ctx context.Context, workflows []*model.WorkflowListDetail) (instanceMap map[string] /* instance id */ *dmsV1.ListDBService, err error) { - instanceMap = make(map[string]*dmsV1.ListDBService) +func loadInstanceByWorkflows(ctx context.Context, workflows []*model.WorkflowListDetail) (instanceMap InstanceMap, err error) { + instanceMap = make(InstanceMap) if len(workflows) == 0 { return instanceMap, nil } @@ -811,9 +932,9 @@ func loadInstanceByWorkflows(ctx context.Context, workflows []*model.WorkflowLis return loadInstanceByInstanceIds(ctx, instanceIdList) } -func loadInstanceByInstanceIds(ctx context.Context, instanceIds []string) (instanceMap map[string] /* instance id */ *dmsV1.ListDBService, err error) { +func loadInstanceByInstanceIds(ctx context.Context, instanceIds []string) (instanceMap InstanceMap, err error) { // get instances from dms - instanceMap = make(map[string]*dmsV1.ListDBService) + instanceMap = make(InstanceMap) instances, _, err := dmsobject.ListDbServices(ctx, controller.GetDMSServerAddress(), dmsV1.ListDBServiceReq{ PageSize: uint32(len(instanceIds)), PageIndex: 1, diff --git a/sqle/api/controller/v2/workflow.go b/sqle/api/controller/v2/workflow.go index e371e8dec6..0a2b52dbe4 100644 --- a/sqle/api/controller/v2/workflow.go +++ b/sqle/api/controller/v2/workflow.go @@ -729,11 +729,6 @@ func CreateWorkflowV2(c echo.Context) error { return controller.JSONBaseErrorReq(c, errTaskHasBeenUsed) } - err = v1.CheckWorkflowCanCommit(workflowTemplate, tasks) - if err != nil { - return controller.JSONBaseErrorReq(c, err) - } - stepTemplates, err := s.GetWorkflowStepsByTemplateId(workflowTemplate.ID) if err != nil { return err diff --git a/sqle/cmd/scannerd/command/base.go b/sqle/cmd/scannerd/command/base.go index 21f54ce02c..eeb7b8ba5b 100644 --- a/sqle/cmd/scannerd/command/base.go +++ b/sqle/cmd/scannerd/command/base.go @@ -2,7 +2,6 @@ package command import ( "fmt" - "strconv" ) @@ -75,7 +74,8 @@ func GetScannerdCmd(scannerType string) (*scannerCmd, error) { return &rootCmd, nil case TypeMySQLMybatis: return &myBatis, nil - case TypeMySQLSlowLog: + // TDSQL Innodb of MySQL 慢日志格式与 MySQL 慢日志格式一致,复用 Mysql 慢日志 Scanner + case TypeMySQLSlowLog, TypeTDSQLInnodbSlowLog: return &slowLog, nil case TypeTiDBAuditLog: return &tidbAuditLog, nil diff --git a/sqle/cmd/scannerd/command/command.go b/sqle/cmd/scannerd/command/command.go index 647361bbe0..5d8344eeb1 100644 --- a/sqle/cmd/scannerd/command/command.go +++ b/sqle/cmd/scannerd/command/command.go @@ -1,12 +1,13 @@ package command const ( - TypeMySQLMybatis = "mysql_mybatis" - TypeMySQLSlowLog = "mysql_slow_log" - TypeSQLFile = "sql_file" - TypeTBaseSlowLog = "TBase_slow_log" - TypeTiDBAuditLog = "tidb_audit_log" - TypeRootScannerd = "root" + TypeMySQLMybatis = "mysql_mybatis" + TypeMySQLSlowLog = "mysql_slow_log" + TypeTDSQLInnodbSlowLog = "tdsql_for_innodb_slow_log" + TypeSQLFile = "sql_file" + TypeTBaseSlowLog = "TBase_slow_log" + TypeTiDBAuditLog = "tidb_audit_log" + TypeRootScannerd = "root" ) var ( @@ -58,4 +59,4 @@ func init() { sqlFile.addStringFlag(FlagInstanceName, FlagInstanceNameSort, EmptyDefaultValue, "instance name") sqlFile.addStringFlag(FlagSchemaName, FlagSchemaNameSort, EmptyDefaultValue, "schema name") sqlFile.addRequiredFlag(FlagDirectory) -} \ No newline at end of file +} diff --git a/sqle/docs/docs.go b/sqle/docs/docs.go index 72c98c5928..a99ba6240a 100644 --- a/sqle/docs/docs.go +++ b/sqle/docs/docs.go @@ -908,6 +908,274 @@ var doc = `{ } } }, + "/v1/dashboard/sql_manages": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get global sql manage list", + "tags": [ + "SqlManage" + ], + "summary": "获取全局管控sql列表", + "operationId": "GetGlobalSqlManageList", + "parameters": [ + { + "type": "string", + "description": "project uid", + "name": "filter_project_uid", + "in": "query" + }, + { + "type": "string", + "description": "instance id", + "name": "filter_instance_id", + "in": "query" + }, + { + "enum": [ + "high", + "medium", + "low" + ], + "type": "string", + "description": "project priority", + "name": "filter_project_priority", + "in": "query" + }, + { + "type": "integer", + "description": "page index", + "name": "page_index", + "in": "query", + "required": true + }, + { + "type": "integer", + "description": "size of per page", + "name": "page_size", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.GetGlobalSqlManageListResp" + } + } + } + } + }, + "/v1/dashboard/sql_manages/statistics": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get global sql manage statistics", + "tags": [ + "SqlManage" + ], + "summary": "获取全局管控sql统计信息", + "operationId": "GetGlobalSqlManageStatistics", + "parameters": [ + { + "type": "string", + "description": "project uid", + "name": "filter_project_uid", + "in": "query" + }, + { + "type": "string", + "description": "instance id", + "name": "filter_instance_id", + "in": "query" + }, + { + "enum": [ + "high", + "medium", + "low" + ], + "type": "string", + "description": "project priority", + "name": "filter_project_priority", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.GetGlobalSqlManageStatisticsResp" + } + } + } + } + }, + "/v1/dashboard/workflows": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get global workflow list", + "tags": [ + "workflow" + ], + "summary": "获取全局工单列表", + "operationId": "getGlobalWorkflowsV1", + "parameters": [ + { + "type": "string", + "description": "filter create user id", + "name": "filter_create_user_id", + "in": "query" + }, + { + "enum": [ + "wait_for_audit", + "wait_for_execution", + "rejected", + "executing", + "canceled", + "exec_failed", + "finished" + ], + "type": "array", + "items": { + "type": "string" + }, + "description": "filter by workflow status,, support using many status", + "name": "filter_status_list", + "in": "query" + }, + { + "type": "string", + "description": "filter by project uid", + "name": "filter_project_uid", + "in": "query" + }, + { + "type": "string", + "description": "filter by instance id in project", + "name": "filter_instance_id", + "in": "query" + }, + { + "enum": [ + "high", + "medium", + "low" + ], + "type": "string", + "description": "filter by project priority", + "name": "filter_project_priority", + "in": "query" + }, + { + "type": "integer", + "description": "page index", + "name": "page_index", + "in": "query", + "required": true + }, + { + "type": "integer", + "description": "size of per page", + "name": "page_size", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.GetWorkflowsResV1" + } + } + } + } + }, + "/v1/dashboard/workflows/statistics": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get global workflow statistics", + "tags": [ + "workflow" + ], + "summary": "获取全局工单统计数据", + "operationId": "GetGlobalWorkflowStatistics", + "parameters": [ + { + "type": "string", + "description": "filter create user id", + "name": "filter_create_user_id", + "in": "query" + }, + { + "enum": [ + "wait_for_audit", + "wait_for_execution", + "rejected", + "executing", + "canceled", + "exec_failed", + "finished" + ], + "type": "array", + "items": { + "type": "string" + }, + "description": "filter by workflow status,, support using many status", + "name": "filter_status_list", + "in": "query" + }, + { + "type": "string", + "description": "filter by project uid", + "name": "filter_project_uid", + "in": "query" + }, + { + "type": "string", + "description": "filter by instance id in project", + "name": "filter_instance_id", + "in": "query" + }, + { + "enum": [ + "high", + "medium", + "low" + ], + "type": "string", + "description": "filter by project priority", + "name": "filter_project_priority", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.GlobalWorkflowStatisticsResV1" + } + } + } + } + }, "/v1/import_rule_template": { "get": { "security": [ @@ -2346,6 +2614,138 @@ var doc = `{ } } }, + "/v1/projects/{project_name}/database_comparison/comparison_statements": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get database comparison detail", + "consumes": [ + "application/json" + ], + "tags": [ + "database_comparison" + ], + "summary": "获取对比语句", + "operationId": "getComparisonStatementV1", + "parameters": [ + { + "type": "string", + "description": "project name", + "name": "project_name", + "in": "path", + "required": true + }, + { + "description": "get database comparison statement request", + "name": "database_comparison_object", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1.GetComparisonStatementsReqV1" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.DatabaseComparisonStatementsResV1" + } + } + } + } + }, + "/v1/projects/{project_name}/database_comparison/execute_comparison": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get database comparison", + "consumes": [ + "application/json" + ], + "tags": [ + "database_comparison" + ], + "summary": "执行数据库结构对比并获取结果", + "operationId": "executeDatabaseComparisonV1", + "parameters": [ + { + "type": "string", + "description": "project name", + "name": "project_name", + "in": "path", + "required": true + }, + { + "description": "get database comparison request", + "name": "database_comparison", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1.GetDatabaseComparisonReqV1" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.DatabaseComparisonResV1" + } + } + } + } + }, + "/v1/projects/{project_name}/database_comparison/modify_sql_statements": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "generate database diff modify sqls", + "consumes": [ + "application/json" + ], + "tags": [ + "database_comparison" + ], + "summary": "生成变更SQL", + "operationId": "genDatabaseDiffModifySQLsV1", + "parameters": [ + { + "type": "string", + "description": "project name", + "name": "project_name", + "in": "path", + "required": true + }, + { + "description": "generate database diff modify sqls request", + "name": "gen_modify_sql", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1.GenModifylSQLReqV1" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.GenModifySQLResV1" + } + } + } + } + }, "/v1/projects/{project_name}/instance_audit_plans": { "get": { "security": [ @@ -7982,68 +8382,6 @@ var doc = `{ } } }, - "/v1/sql_manages": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "get global sql manage list", - "tags": [ - "SqlManage" - ], - "summary": "获取全局管控sql列表", - "operationId": "GetGlobalSqlManageList", - "parameters": [ - { - "type": "string", - "description": "project uid", - "name": "filter_project_uid", - "in": "query" - }, - { - "type": "string", - "description": "instance id", - "name": "filter_instance_id", - "in": "query" - }, - { - "enum": [ - "high", - "medium", - "low" - ], - "type": "string", - "description": "project priority", - "name": "filter_project_priority", - "in": "query" - }, - { - "type": "integer", - "description": "page index", - "name": "page_index", - "in": "query", - "required": true - }, - { - "type": "integer", - "description": "size of per page", - "name": "page_size", - "in": "query", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/v1.GetGlobalSqlManageListResp" - } - } - } - } - }, "/v1/statistic/instances/sql_average_execution_time": { "get": { "security": [ @@ -8421,6 +8759,29 @@ var doc = `{ } } }, + "/v1/system/module_red_dots": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get the red dot prompt information in the system", + "tags": [ + "system" + ], + "summary": "查询系统各模块的红点提示信息", + "operationId": "GetSystemModuleRedDots", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.GetSystemModuleRedDotsRes" + } + } + } + } + }, "/v1/system/module_status": { "get": { "security": [ @@ -8837,242 +9198,99 @@ var doc = `{ "responses": { "200": { "description": "OK", - "schema": { - "$ref": "#/definitions/controller.BaseRes" - } - } - } - } - }, - "/v1/tasks/audits/{task_id}/sqls/{number}/analysis": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "get SQL explain and related table metadata for analysis", - "tags": [ - "task" - ], - "summary": "获取task相关的SQL执行计划和表元数据", - "operationId": "getTaskAnalysisData", - "parameters": [ - { - "type": "string", - "description": "task id", - "name": "task_id", - "in": "path", - "required": true - }, - { - "type": "integer", - "description": "sql number", - "name": "number", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/v1.GetTaskAnalysisDataResV1" - } - } - } - } - }, - "/v1/tasks/file_order_methods": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "get file order method", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "task" - ], - "summary": "获取文件上线排序方式", - "operationId": "getSqlFileOrderMethodV1", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/v1.GetSqlFileOrderMethodResV1" - } - } - } - } - }, - "/v1/user_tips": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "get user tip list", - "tags": [ - "user" - ], - "summary": "获取用户提示列表", - "operationId": "getUserTipListV1", - "parameters": [ - { - "type": "string", - "description": "project name", - "name": "filter_project", - "in": "query", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/v1.GetUserTipsResV1" - } - } - } - } - }, - "/v1/workflows": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "get global workflow list", - "tags": [ - "workflow" - ], - "summary": "获取全局工单列表", - "operationId": "getGlobalWorkflowsV1", - "parameters": [ - { - "type": "string", - "description": "filter subject", - "name": "filter_subject", - "in": "query" - }, - { - "type": "string", - "description": "filter create time from", - "name": "filter_create_time_from", - "in": "query" - }, - { - "type": "string", - "description": "filter create time to", - "name": "filter_create_time_to", - "in": "query" - }, - { - "type": "string", - "description": "filter_task_execute_start_time_from", - "name": "filter_task_execute_start_time_from", - "in": "query" - }, - { - "type": "string", - "description": "filter_task_execute_start_time_to", - "name": "filter_task_execute_start_time_to", - "in": "query" - }, - { - "type": "string", - "description": "filter create user id", - "name": "filter_create_user_id", - "in": "query" - }, - { - "enum": [ - "wait_for_audit", - "wait_for_execution", - "rejected", - "executing", - "canceled", - "exec_failed", - "finished" - ], - "type": "string", - "description": "filter workflow status", - "name": "filter_status", - "in": "query" - }, - { - "enum": [ - "wait_for_audit", - "wait_for_execution", - "rejected", - "executing", - "canceled", - "exec_failed", - "finished" - ], - "type": "array", - "items": { - "type": "string" - }, - "description": "filter by workflow status,, support using many status", - "name": "filter_status_list", - "in": "query" - }, - { - "type": "string", - "description": "filter by project uid", - "name": "filter_project_uid", - "in": "query" - }, - { - "type": "string", - "description": "filter by instance id in project", - "name": "filter_instance_id", - "in": "query" - }, - { - "enum": [ - "high", - "medium", - "low" - ], - "type": "string", - "description": "filter by project priority", - "name": "filter_project_priority", - "in": "query" - }, + "schema": { + "$ref": "#/definitions/controller.BaseRes" + } + } + } + } + }, + "/v1/tasks/audits/{task_id}/sqls/{number}/analysis": { + "get": { + "security": [ { - "type": "string", - "description": "filter current step assignee user id", - "name": "filter_current_step_assignee_user_id", - "in": "query" - }, + "ApiKeyAuth": [] + } + ], + "description": "get SQL explain and related table metadata for analysis", + "tags": [ + "task" + ], + "summary": "获取task相关的SQL执行计划和表元数据", + "operationId": "getTaskAnalysisData", + "parameters": [ { "type": "string", - "description": "filter instance id", - "name": "filter_task_instance_id", - "in": "query" + "description": "task id", + "name": "task_id", + "in": "path", + "required": true }, { "type": "integer", - "description": "page index", - "name": "page_index", - "in": "query", + "description": "sql number", + "name": "number", + "in": "path", "required": true - }, + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.GetTaskAnalysisDataResV1" + } + } + } + } + }, + "/v1/tasks/file_order_methods": { + "get": { + "security": [ { - "type": "integer", - "description": "size of per page", - "name": "page_size", + "ApiKeyAuth": [] + } + ], + "description": "get file order method", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "task" + ], + "summary": "获取文件上线排序方式", + "operationId": "getSqlFileOrderMethodV1", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.GetSqlFileOrderMethodResV1" + } + } + } + } + }, + "/v1/user_tips": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get user tip list", + "tags": [ + "user" + ], + "summary": "获取用户提示列表", + "operationId": "getUserTipListV1", + "parameters": [ + { + "type": "string", + "description": "project name", + "name": "filter_project", "in": "query", "required": true } @@ -9081,7 +9299,7 @@ var doc = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/v1.GetWorkflowsResV1" + "$ref": "#/definitions/v1.GetUserTipsResV1" } } } @@ -10984,6 +11202,9 @@ var doc = `{ "desc": { "type": "string" }, + "token": { + "type": "string" + }, "type": { "type": "string" } @@ -11850,119 +12071,254 @@ var doc = `{ "next_stage_instance_id": { "type": "string" }, - "stage_instance_id": { - "type": "string" + "stage_instance_id": { + "type": "string" + } + } + }, + "v1.CreateWorkflowReqV1": { + "type": "object", + "properties": { + "desc": { + "type": "string" + }, + "task_ids": { + "type": "array", + "items": { + "type": "integer" + } + }, + "workflow_subject": { + "type": "string" + } + } + }, + "v1.CustomRuleResV1": { + "type": "object", + "properties": { + "annotation": { + "type": "string", + "example": "this is test rule" + }, + "db_type": { + "type": "string", + "example": "MySQL" + }, + "desc": { + "type": "string", + "example": "this is test rule" + }, + "level": { + "type": "string", + "enum": [ + "normal", + "notice", + "warn", + "error" + ], + "example": "notice" + }, + "rule_id": { + "type": "string" + }, + "rule_script": { + "type": "string" + }, + "type": { + "type": "string", + "example": "DDL规则" + } + } + }, + "v1.DBPerformanceImproveOverview": { + "type": "object", + "properties": { + "avg_performance_improve": { + "type": "number" + }, + "instance_name": { + "type": "string" + } + } + }, + "v1.DBTypeAuditPlan": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.AuditPlanCount" + } + }, + "db_type": { + "type": "string" + } + } + }, + "v1.DBTypeHealth": { + "type": "object", + "properties": { + "db_type": { + "type": "string" + }, + "health_instance_names": { + "type": "array", + "items": { + "type": "string" + } + }, + "unhealth_instance_names": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "v1.DashboardResV1": { + "type": "object", + "properties": { + "workflow_statistics": { + "type": "object", + "$ref": "#/definitions/v1.WorkflowStatisticsResV1" + } + } + }, + "v1.DatabaseComparisonObject": { + "type": "object", + "properties": { + "instance_id": { + "type": "string" + }, + "schema_name": { + "type": "string" + } + } + }, + "v1.DatabaseComparisonResV1": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 0 + }, + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SchemaObject" + } + }, + "message": { + "type": "string", + "example": "ok" + } + } + }, + "v1.DatabaseComparisonStatements": { + "type": "object", + "properties": { + "base_sql": { + "type": "object", + "$ref": "#/definitions/v1.SQLStatementWithAuditResult" + }, + "comparison_sql": { + "type": "object", + "$ref": "#/definitions/v1.SQLStatementWithAuditResult" + } + } + }, + "v1.DatabaseComparisonStatementsResV1": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 0 + }, + "data": { + "type": "object", + "$ref": "#/definitions/v1.DatabaseComparisonStatements" + }, + "message": { + "type": "string", + "example": "ok" } } }, - "v1.CreateWorkflowReqV1": { + "v1.DatabaseDiffModifySQL": { "type": "object", "properties": { - "desc": { - "type": "string" - }, - "task_ids": { + "modify_sqls": { "type": "array", "items": { - "type": "integer" + "$ref": "#/definitions/v1.SQLStatementWithAuditResult" } }, - "workflow_subject": { + "schema_name": { "type": "string" } } }, - "v1.CustomRuleResV1": { + "v1.DatabaseDiffObject": { "type": "object", "properties": { - "annotation": { - "type": "string", - "example": "this is test rule" - }, - "db_type": { - "type": "string", - "example": "MySQL" - }, - "desc": { - "type": "string", - "example": "this is test rule" + "inconsistent_num": { + "type": "integer" }, - "level": { + "object_type": { "type": "string", "enum": [ - "normal", - "notice", - "warn", - "error" - ], - "example": "notice" - }, - "rule_id": { - "type": "string" - }, - "rule_script": { - "type": "string" + "TABLE", + "VIEW", + "PROCEDURE", + "TIGGER", + "EVENT", + "FUNCTION" + ] }, - "type": { - "type": "string", - "example": "DDL规则" + "objects_diff_result": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.ObjectDiffResult" + } } } }, - "v1.DBPerformanceImproveOverview": { + "v1.DatabaseObject": { "type": "object", "properties": { - "avg_performance_improve": { - "type": "number" - }, - "instance_name": { + "object_name": { "type": "string" - } - } - }, - "v1.DBTypeAuditPlan": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/v1.AuditPlanCount" - } }, - "db_type": { - "type": "string" + "object_type": { + "type": "string", + "enum": [ + "TABLE", + "VIEW", + "PROCEDURE", + "TIGGER", + "EVENT", + "FUNCTION" + ] } } }, - "v1.DBTypeHealth": { + "v1.DatabaseSchemaObject": { "type": "object", "properties": { - "db_type": { + "base_schema_name": { "type": "string" }, - "health_instance_names": { - "type": "array", - "items": { - "type": "string" - } + "comparison_schema_name": { + "type": "string" }, - "unhealth_instance_names": { + "database_objects": { "type": "array", "items": { - "type": "string" + "$ref": "#/definitions/v1.DatabaseObject" } } } }, - "v1.DashboardResV1": { - "type": "object", - "properties": { - "workflow_statistics": { - "type": "object", - "$ref": "#/definitions/v1.WorkflowStatisticsResV1" - } - } - }, "v1.DepBetweenStageInstance": { "type": "object", "properties": { @@ -12269,6 +12625,42 @@ var doc = `{ } } }, + "v1.GenModifySQLResV1": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 0 + }, + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.DatabaseDiffModifySQL" + } + }, + "message": { + "type": "string", + "example": "ok" + } + } + }, + "v1.GenModifylSQLReqV1": { + "type": "object", + "properties": { + "base_instance_id": { + "type": "string" + }, + "comparison_instance_id": { + "type": "string" + }, + "database_schema_objects": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.DatabaseSchemaObject" + } + } + } + }, "v1.GetAuditPlanAnalysisDataResV1": { "type": "object", "properties": { @@ -12678,6 +13070,19 @@ var doc = `{ } } }, + "v1.GetComparisonStatementsReqV1": { + "type": "object", + "properties": { + "database_comparison_object": { + "type": "object", + "$ref": "#/definitions/v1.GetDatabaseComparisonReqV1" + }, + "database_object": { + "type": "object", + "$ref": "#/definitions/v1.DatabaseObject" + } + } + }, "v1.GetCustomRuleResV1": { "type": "object", "properties": { @@ -12750,6 +13155,19 @@ var doc = `{ } } }, + "v1.GetDatabaseComparisonReqV1": { + "type": "object", + "properties": { + "base_db_object": { + "type": "object", + "$ref": "#/definitions/v1.DatabaseComparisonObject" + }, + "comparison_db_object": { + "type": "object", + "$ref": "#/definitions/v1.DatabaseComparisonObject" + } + } + }, "v1.GetDepBetweenStageInstanceResV1": { "type": "object", "properties": { @@ -12837,7 +13255,23 @@ var doc = `{ "type": "string", "example": "ok" }, - "total_num": { + "total_nums": { + "type": "integer" + } + } + }, + "v1.GetGlobalSqlManageStatisticsResp": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 0 + }, + "message": { + "type": "string", + "example": "ok" + }, + "total_nums": { "type": "integer" } } @@ -13782,6 +14216,23 @@ var doc = `{ } } }, + "v1.GetSystemModuleRedDotsRes": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 0 + }, + "data": { + "type": "object", + "$ref": "#/definitions/v1.ModuleRedDots" + }, + "message": { + "type": "string", + "example": "ok" + } + } + }, "v1.GetSystemVariablesResV1": { "type": "object", "properties": { @@ -14284,6 +14735,22 @@ var doc = `{ } } }, + "v1.GlobalWorkflowStatisticsResV1": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 0 + }, + "message": { + "type": "string", + "example": "ok" + }, + "total_nums": { + "type": "integer" + } + } + }, "v1.HighPriorityConditionReq": { "type": "object", "properties": { @@ -14698,6 +15165,26 @@ var doc = `{ } } }, + "v1.ModuleRedDot": { + "type": "object", + "properties": { + "has_red_dot": { + "type": "boolean" + }, + "module_name": { + "type": "string", + "enum": [ + "global_dashboard" + ] + } + } + }, + "v1.ModuleRedDots": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.ModuleRedDot" + } + }, "v1.ModuleStatusRes": { "type": "object", "properties": { @@ -14706,6 +15193,23 @@ var doc = `{ } } }, + "v1.ObjectDiffResult": { + "type": "object", + "properties": { + "comparison_result": { + "type": "string", + "enum": [ + "same", + "inconsistent", + "base_not_exist", + "comparison_not_exist" + ] + }, + "object_name": { + "type": "string" + } + } + }, "v1.OperationActionList": { "type": "object", "properties": { @@ -15538,6 +16042,29 @@ var doc = `{ } } }, + "v1.SQLAuditResult": { + "type": "object", + "properties": { + "db_type": { + "type": "string" + }, + "i18n_audit_result_info": { + "type": "object", + "$ref": "#/definitions/model.I18nAuditResultInfo" + }, + "level": { + "type": "string", + "example": "warn" + }, + "message": { + "type": "string", + "example": "避免使用不必要的内置函数md5()" + }, + "rule_name": { + "type": "string" + } + } + }, "v1.SQLExplain": { "type": "object", "properties": { @@ -15577,6 +16104,20 @@ var doc = `{ } } }, + "v1.SQLStatementWithAuditResult": { + "type": "object", + "properties": { + "audit_results": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SQLAuditResult" + } + }, + "sql_statement": { + "type": "string" + } + } + }, "v1.ScheduleTaskDefaultOption": { "type": "object", "properties": { @@ -15606,6 +16147,35 @@ var doc = `{ } } }, + "v1.SchemaObject": { + "type": "object", + "properties": { + "base_schema_name": { + "type": "string" + }, + "comparison_result": { + "type": "string", + "enum": [ + "same", + "inconsistent", + "base_not_exist", + "comparison_not_exist" + ] + }, + "comparison_schema_name": { + "type": "string" + }, + "database_diff_objects": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.DatabaseDiffObject" + } + }, + "inconsistent_num": { + "type": "integer" + } + } + }, "v1.Source": { "type": "object", "properties": { diff --git a/sqle/docs/swagger.json b/sqle/docs/swagger.json index 7416a94edb..371f769ce4 100644 --- a/sqle/docs/swagger.json +++ b/sqle/docs/swagger.json @@ -892,6 +892,274 @@ } } }, + "/v1/dashboard/sql_manages": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get global sql manage list", + "tags": [ + "SqlManage" + ], + "summary": "获取全局管控sql列表", + "operationId": "GetGlobalSqlManageList", + "parameters": [ + { + "type": "string", + "description": "project uid", + "name": "filter_project_uid", + "in": "query" + }, + { + "type": "string", + "description": "instance id", + "name": "filter_instance_id", + "in": "query" + }, + { + "enum": [ + "high", + "medium", + "low" + ], + "type": "string", + "description": "project priority", + "name": "filter_project_priority", + "in": "query" + }, + { + "type": "integer", + "description": "page index", + "name": "page_index", + "in": "query", + "required": true + }, + { + "type": "integer", + "description": "size of per page", + "name": "page_size", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.GetGlobalSqlManageListResp" + } + } + } + } + }, + "/v1/dashboard/sql_manages/statistics": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get global sql manage statistics", + "tags": [ + "SqlManage" + ], + "summary": "获取全局管控sql统计信息", + "operationId": "GetGlobalSqlManageStatistics", + "parameters": [ + { + "type": "string", + "description": "project uid", + "name": "filter_project_uid", + "in": "query" + }, + { + "type": "string", + "description": "instance id", + "name": "filter_instance_id", + "in": "query" + }, + { + "enum": [ + "high", + "medium", + "low" + ], + "type": "string", + "description": "project priority", + "name": "filter_project_priority", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.GetGlobalSqlManageStatisticsResp" + } + } + } + } + }, + "/v1/dashboard/workflows": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get global workflow list", + "tags": [ + "workflow" + ], + "summary": "获取全局工单列表", + "operationId": "getGlobalWorkflowsV1", + "parameters": [ + { + "type": "string", + "description": "filter create user id", + "name": "filter_create_user_id", + "in": "query" + }, + { + "enum": [ + "wait_for_audit", + "wait_for_execution", + "rejected", + "executing", + "canceled", + "exec_failed", + "finished" + ], + "type": "array", + "items": { + "type": "string" + }, + "description": "filter by workflow status,, support using many status", + "name": "filter_status_list", + "in": "query" + }, + { + "type": "string", + "description": "filter by project uid", + "name": "filter_project_uid", + "in": "query" + }, + { + "type": "string", + "description": "filter by instance id in project", + "name": "filter_instance_id", + "in": "query" + }, + { + "enum": [ + "high", + "medium", + "low" + ], + "type": "string", + "description": "filter by project priority", + "name": "filter_project_priority", + "in": "query" + }, + { + "type": "integer", + "description": "page index", + "name": "page_index", + "in": "query", + "required": true + }, + { + "type": "integer", + "description": "size of per page", + "name": "page_size", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.GetWorkflowsResV1" + } + } + } + } + }, + "/v1/dashboard/workflows/statistics": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get global workflow statistics", + "tags": [ + "workflow" + ], + "summary": "获取全局工单统计数据", + "operationId": "GetGlobalWorkflowStatistics", + "parameters": [ + { + "type": "string", + "description": "filter create user id", + "name": "filter_create_user_id", + "in": "query" + }, + { + "enum": [ + "wait_for_audit", + "wait_for_execution", + "rejected", + "executing", + "canceled", + "exec_failed", + "finished" + ], + "type": "array", + "items": { + "type": "string" + }, + "description": "filter by workflow status,, support using many status", + "name": "filter_status_list", + "in": "query" + }, + { + "type": "string", + "description": "filter by project uid", + "name": "filter_project_uid", + "in": "query" + }, + { + "type": "string", + "description": "filter by instance id in project", + "name": "filter_instance_id", + "in": "query" + }, + { + "enum": [ + "high", + "medium", + "low" + ], + "type": "string", + "description": "filter by project priority", + "name": "filter_project_priority", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.GlobalWorkflowStatisticsResV1" + } + } + } + } + }, "/v1/import_rule_template": { "get": { "security": [ @@ -2330,6 +2598,138 @@ } } }, + "/v1/projects/{project_name}/database_comparison/comparison_statements": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get database comparison detail", + "consumes": [ + "application/json" + ], + "tags": [ + "database_comparison" + ], + "summary": "获取对比语句", + "operationId": "getComparisonStatementV1", + "parameters": [ + { + "type": "string", + "description": "project name", + "name": "project_name", + "in": "path", + "required": true + }, + { + "description": "get database comparison statement request", + "name": "database_comparison_object", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1.GetComparisonStatementsReqV1" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.DatabaseComparisonStatementsResV1" + } + } + } + } + }, + "/v1/projects/{project_name}/database_comparison/execute_comparison": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get database comparison", + "consumes": [ + "application/json" + ], + "tags": [ + "database_comparison" + ], + "summary": "执行数据库结构对比并获取结果", + "operationId": "executeDatabaseComparisonV1", + "parameters": [ + { + "type": "string", + "description": "project name", + "name": "project_name", + "in": "path", + "required": true + }, + { + "description": "get database comparison request", + "name": "database_comparison", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1.GetDatabaseComparisonReqV1" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.DatabaseComparisonResV1" + } + } + } + } + }, + "/v1/projects/{project_name}/database_comparison/modify_sql_statements": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "generate database diff modify sqls", + "consumes": [ + "application/json" + ], + "tags": [ + "database_comparison" + ], + "summary": "生成变更SQL", + "operationId": "genDatabaseDiffModifySQLsV1", + "parameters": [ + { + "type": "string", + "description": "project name", + "name": "project_name", + "in": "path", + "required": true + }, + { + "description": "generate database diff modify sqls request", + "name": "gen_modify_sql", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1.GenModifylSQLReqV1" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.GenModifySQLResV1" + } + } + } + } + }, "/v1/projects/{project_name}/instance_audit_plans": { "get": { "security": [ @@ -7966,68 +8366,6 @@ } } }, - "/v1/sql_manages": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "get global sql manage list", - "tags": [ - "SqlManage" - ], - "summary": "获取全局管控sql列表", - "operationId": "GetGlobalSqlManageList", - "parameters": [ - { - "type": "string", - "description": "project uid", - "name": "filter_project_uid", - "in": "query" - }, - { - "type": "string", - "description": "instance id", - "name": "filter_instance_id", - "in": "query" - }, - { - "enum": [ - "high", - "medium", - "low" - ], - "type": "string", - "description": "project priority", - "name": "filter_project_priority", - "in": "query" - }, - { - "type": "integer", - "description": "page index", - "name": "page_index", - "in": "query", - "required": true - }, - { - "type": "integer", - "description": "size of per page", - "name": "page_size", - "in": "query", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/v1.GetGlobalSqlManageListResp" - } - } - } - } - }, "/v1/statistic/instances/sql_average_execution_time": { "get": { "security": [ @@ -8405,6 +8743,29 @@ } } }, + "/v1/system/module_red_dots": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get the red dot prompt information in the system", + "tags": [ + "system" + ], + "summary": "查询系统各模块的红点提示信息", + "operationId": "GetSystemModuleRedDots", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.GetSystemModuleRedDotsRes" + } + } + } + } + }, "/v1/system/module_status": { "get": { "security": [ @@ -8821,242 +9182,99 @@ "responses": { "200": { "description": "OK", - "schema": { - "$ref": "#/definitions/controller.BaseRes" - } - } - } - } - }, - "/v1/tasks/audits/{task_id}/sqls/{number}/analysis": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "get SQL explain and related table metadata for analysis", - "tags": [ - "task" - ], - "summary": "获取task相关的SQL执行计划和表元数据", - "operationId": "getTaskAnalysisData", - "parameters": [ - { - "type": "string", - "description": "task id", - "name": "task_id", - "in": "path", - "required": true - }, - { - "type": "integer", - "description": "sql number", - "name": "number", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/v1.GetTaskAnalysisDataResV1" - } - } - } - } - }, - "/v1/tasks/file_order_methods": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "get file order method", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "task" - ], - "summary": "获取文件上线排序方式", - "operationId": "getSqlFileOrderMethodV1", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/v1.GetSqlFileOrderMethodResV1" - } - } - } - } - }, - "/v1/user_tips": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "get user tip list", - "tags": [ - "user" - ], - "summary": "获取用户提示列表", - "operationId": "getUserTipListV1", - "parameters": [ - { - "type": "string", - "description": "project name", - "name": "filter_project", - "in": "query", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/v1.GetUserTipsResV1" - } - } - } - } - }, - "/v1/workflows": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "get global workflow list", - "tags": [ - "workflow" - ], - "summary": "获取全局工单列表", - "operationId": "getGlobalWorkflowsV1", - "parameters": [ - { - "type": "string", - "description": "filter subject", - "name": "filter_subject", - "in": "query" - }, - { - "type": "string", - "description": "filter create time from", - "name": "filter_create_time_from", - "in": "query" - }, - { - "type": "string", - "description": "filter create time to", - "name": "filter_create_time_to", - "in": "query" - }, - { - "type": "string", - "description": "filter_task_execute_start_time_from", - "name": "filter_task_execute_start_time_from", - "in": "query" - }, - { - "type": "string", - "description": "filter_task_execute_start_time_to", - "name": "filter_task_execute_start_time_to", - "in": "query" - }, - { - "type": "string", - "description": "filter create user id", - "name": "filter_create_user_id", - "in": "query" - }, - { - "enum": [ - "wait_for_audit", - "wait_for_execution", - "rejected", - "executing", - "canceled", - "exec_failed", - "finished" - ], - "type": "string", - "description": "filter workflow status", - "name": "filter_status", - "in": "query" - }, - { - "enum": [ - "wait_for_audit", - "wait_for_execution", - "rejected", - "executing", - "canceled", - "exec_failed", - "finished" - ], - "type": "array", - "items": { - "type": "string" - }, - "description": "filter by workflow status,, support using many status", - "name": "filter_status_list", - "in": "query" - }, - { - "type": "string", - "description": "filter by project uid", - "name": "filter_project_uid", - "in": "query" - }, - { - "type": "string", - "description": "filter by instance id in project", - "name": "filter_instance_id", - "in": "query" - }, - { - "enum": [ - "high", - "medium", - "low" - ], - "type": "string", - "description": "filter by project priority", - "name": "filter_project_priority", - "in": "query" - }, + "schema": { + "$ref": "#/definitions/controller.BaseRes" + } + } + } + } + }, + "/v1/tasks/audits/{task_id}/sqls/{number}/analysis": { + "get": { + "security": [ { - "type": "string", - "description": "filter current step assignee user id", - "name": "filter_current_step_assignee_user_id", - "in": "query" - }, + "ApiKeyAuth": [] + } + ], + "description": "get SQL explain and related table metadata for analysis", + "tags": [ + "task" + ], + "summary": "获取task相关的SQL执行计划和表元数据", + "operationId": "getTaskAnalysisData", + "parameters": [ { "type": "string", - "description": "filter instance id", - "name": "filter_task_instance_id", - "in": "query" + "description": "task id", + "name": "task_id", + "in": "path", + "required": true }, { "type": "integer", - "description": "page index", - "name": "page_index", - "in": "query", + "description": "sql number", + "name": "number", + "in": "path", "required": true - }, + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.GetTaskAnalysisDataResV1" + } + } + } + } + }, + "/v1/tasks/file_order_methods": { + "get": { + "security": [ { - "type": "integer", - "description": "size of per page", - "name": "page_size", + "ApiKeyAuth": [] + } + ], + "description": "get file order method", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "task" + ], + "summary": "获取文件上线排序方式", + "operationId": "getSqlFileOrderMethodV1", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.GetSqlFileOrderMethodResV1" + } + } + } + } + }, + "/v1/user_tips": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "get user tip list", + "tags": [ + "user" + ], + "summary": "获取用户提示列表", + "operationId": "getUserTipListV1", + "parameters": [ + { + "type": "string", + "description": "project name", + "name": "filter_project", "in": "query", "required": true } @@ -9065,7 +9283,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/v1.GetWorkflowsResV1" + "$ref": "#/definitions/v1.GetUserTipsResV1" } } } @@ -10968,6 +11186,9 @@ "desc": { "type": "string" }, + "token": { + "type": "string" + }, "type": { "type": "string" } @@ -11834,119 +12055,254 @@ "next_stage_instance_id": { "type": "string" }, - "stage_instance_id": { - "type": "string" + "stage_instance_id": { + "type": "string" + } + } + }, + "v1.CreateWorkflowReqV1": { + "type": "object", + "properties": { + "desc": { + "type": "string" + }, + "task_ids": { + "type": "array", + "items": { + "type": "integer" + } + }, + "workflow_subject": { + "type": "string" + } + } + }, + "v1.CustomRuleResV1": { + "type": "object", + "properties": { + "annotation": { + "type": "string", + "example": "this is test rule" + }, + "db_type": { + "type": "string", + "example": "MySQL" + }, + "desc": { + "type": "string", + "example": "this is test rule" + }, + "level": { + "type": "string", + "enum": [ + "normal", + "notice", + "warn", + "error" + ], + "example": "notice" + }, + "rule_id": { + "type": "string" + }, + "rule_script": { + "type": "string" + }, + "type": { + "type": "string", + "example": "DDL规则" + } + } + }, + "v1.DBPerformanceImproveOverview": { + "type": "object", + "properties": { + "avg_performance_improve": { + "type": "number" + }, + "instance_name": { + "type": "string" + } + } + }, + "v1.DBTypeAuditPlan": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.AuditPlanCount" + } + }, + "db_type": { + "type": "string" + } + } + }, + "v1.DBTypeHealth": { + "type": "object", + "properties": { + "db_type": { + "type": "string" + }, + "health_instance_names": { + "type": "array", + "items": { + "type": "string" + } + }, + "unhealth_instance_names": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "v1.DashboardResV1": { + "type": "object", + "properties": { + "workflow_statistics": { + "type": "object", + "$ref": "#/definitions/v1.WorkflowStatisticsResV1" + } + } + }, + "v1.DatabaseComparisonObject": { + "type": "object", + "properties": { + "instance_id": { + "type": "string" + }, + "schema_name": { + "type": "string" + } + } + }, + "v1.DatabaseComparisonResV1": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 0 + }, + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SchemaObject" + } + }, + "message": { + "type": "string", + "example": "ok" + } + } + }, + "v1.DatabaseComparisonStatements": { + "type": "object", + "properties": { + "base_sql": { + "type": "object", + "$ref": "#/definitions/v1.SQLStatementWithAuditResult" + }, + "comparison_sql": { + "type": "object", + "$ref": "#/definitions/v1.SQLStatementWithAuditResult" + } + } + }, + "v1.DatabaseComparisonStatementsResV1": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 0 + }, + "data": { + "type": "object", + "$ref": "#/definitions/v1.DatabaseComparisonStatements" + }, + "message": { + "type": "string", + "example": "ok" } } }, - "v1.CreateWorkflowReqV1": { + "v1.DatabaseDiffModifySQL": { "type": "object", "properties": { - "desc": { - "type": "string" - }, - "task_ids": { + "modify_sqls": { "type": "array", "items": { - "type": "integer" + "$ref": "#/definitions/v1.SQLStatementWithAuditResult" } }, - "workflow_subject": { + "schema_name": { "type": "string" } } }, - "v1.CustomRuleResV1": { + "v1.DatabaseDiffObject": { "type": "object", "properties": { - "annotation": { - "type": "string", - "example": "this is test rule" - }, - "db_type": { - "type": "string", - "example": "MySQL" - }, - "desc": { - "type": "string", - "example": "this is test rule" + "inconsistent_num": { + "type": "integer" }, - "level": { + "object_type": { "type": "string", "enum": [ - "normal", - "notice", - "warn", - "error" - ], - "example": "notice" - }, - "rule_id": { - "type": "string" - }, - "rule_script": { - "type": "string" + "TABLE", + "VIEW", + "PROCEDURE", + "TIGGER", + "EVENT", + "FUNCTION" + ] }, - "type": { - "type": "string", - "example": "DDL规则" + "objects_diff_result": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.ObjectDiffResult" + } } } }, - "v1.DBPerformanceImproveOverview": { + "v1.DatabaseObject": { "type": "object", "properties": { - "avg_performance_improve": { - "type": "number" - }, - "instance_name": { + "object_name": { "type": "string" - } - } - }, - "v1.DBTypeAuditPlan": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/v1.AuditPlanCount" - } }, - "db_type": { - "type": "string" + "object_type": { + "type": "string", + "enum": [ + "TABLE", + "VIEW", + "PROCEDURE", + "TIGGER", + "EVENT", + "FUNCTION" + ] } } }, - "v1.DBTypeHealth": { + "v1.DatabaseSchemaObject": { "type": "object", "properties": { - "db_type": { + "base_schema_name": { "type": "string" }, - "health_instance_names": { - "type": "array", - "items": { - "type": "string" - } + "comparison_schema_name": { + "type": "string" }, - "unhealth_instance_names": { + "database_objects": { "type": "array", "items": { - "type": "string" + "$ref": "#/definitions/v1.DatabaseObject" } } } }, - "v1.DashboardResV1": { - "type": "object", - "properties": { - "workflow_statistics": { - "type": "object", - "$ref": "#/definitions/v1.WorkflowStatisticsResV1" - } - } - }, "v1.DepBetweenStageInstance": { "type": "object", "properties": { @@ -12253,6 +12609,42 @@ } } }, + "v1.GenModifySQLResV1": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 0 + }, + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.DatabaseDiffModifySQL" + } + }, + "message": { + "type": "string", + "example": "ok" + } + } + }, + "v1.GenModifylSQLReqV1": { + "type": "object", + "properties": { + "base_instance_id": { + "type": "string" + }, + "comparison_instance_id": { + "type": "string" + }, + "database_schema_objects": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.DatabaseSchemaObject" + } + } + } + }, "v1.GetAuditPlanAnalysisDataResV1": { "type": "object", "properties": { @@ -12662,6 +13054,19 @@ } } }, + "v1.GetComparisonStatementsReqV1": { + "type": "object", + "properties": { + "database_comparison_object": { + "type": "object", + "$ref": "#/definitions/v1.GetDatabaseComparisonReqV1" + }, + "database_object": { + "type": "object", + "$ref": "#/definitions/v1.DatabaseObject" + } + } + }, "v1.GetCustomRuleResV1": { "type": "object", "properties": { @@ -12734,6 +13139,19 @@ } } }, + "v1.GetDatabaseComparisonReqV1": { + "type": "object", + "properties": { + "base_db_object": { + "type": "object", + "$ref": "#/definitions/v1.DatabaseComparisonObject" + }, + "comparison_db_object": { + "type": "object", + "$ref": "#/definitions/v1.DatabaseComparisonObject" + } + } + }, "v1.GetDepBetweenStageInstanceResV1": { "type": "object", "properties": { @@ -12821,7 +13239,23 @@ "type": "string", "example": "ok" }, - "total_num": { + "total_nums": { + "type": "integer" + } + } + }, + "v1.GetGlobalSqlManageStatisticsResp": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 0 + }, + "message": { + "type": "string", + "example": "ok" + }, + "total_nums": { "type": "integer" } } @@ -13766,6 +14200,23 @@ } } }, + "v1.GetSystemModuleRedDotsRes": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 0 + }, + "data": { + "type": "object", + "$ref": "#/definitions/v1.ModuleRedDots" + }, + "message": { + "type": "string", + "example": "ok" + } + } + }, "v1.GetSystemVariablesResV1": { "type": "object", "properties": { @@ -14268,6 +14719,22 @@ } } }, + "v1.GlobalWorkflowStatisticsResV1": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 0 + }, + "message": { + "type": "string", + "example": "ok" + }, + "total_nums": { + "type": "integer" + } + } + }, "v1.HighPriorityConditionReq": { "type": "object", "properties": { @@ -14682,6 +15149,26 @@ } } }, + "v1.ModuleRedDot": { + "type": "object", + "properties": { + "has_red_dot": { + "type": "boolean" + }, + "module_name": { + "type": "string", + "enum": [ + "global_dashboard" + ] + } + } + }, + "v1.ModuleRedDots": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.ModuleRedDot" + } + }, "v1.ModuleStatusRes": { "type": "object", "properties": { @@ -14690,6 +15177,23 @@ } } }, + "v1.ObjectDiffResult": { + "type": "object", + "properties": { + "comparison_result": { + "type": "string", + "enum": [ + "same", + "inconsistent", + "base_not_exist", + "comparison_not_exist" + ] + }, + "object_name": { + "type": "string" + } + } + }, "v1.OperationActionList": { "type": "object", "properties": { @@ -15522,6 +16026,29 @@ } } }, + "v1.SQLAuditResult": { + "type": "object", + "properties": { + "db_type": { + "type": "string" + }, + "i18n_audit_result_info": { + "type": "object", + "$ref": "#/definitions/model.I18nAuditResultInfo" + }, + "level": { + "type": "string", + "example": "warn" + }, + "message": { + "type": "string", + "example": "避免使用不必要的内置函数md5()" + }, + "rule_name": { + "type": "string" + } + } + }, "v1.SQLExplain": { "type": "object", "properties": { @@ -15561,6 +16088,20 @@ } } }, + "v1.SQLStatementWithAuditResult": { + "type": "object", + "properties": { + "audit_results": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SQLAuditResult" + } + }, + "sql_statement": { + "type": "string" + } + } + }, "v1.ScheduleTaskDefaultOption": { "type": "object", "properties": { @@ -15590,6 +16131,35 @@ } } }, + "v1.SchemaObject": { + "type": "object", + "properties": { + "base_schema_name": { + "type": "string" + }, + "comparison_result": { + "type": "string", + "enum": [ + "same", + "inconsistent", + "base_not_exist", + "comparison_not_exist" + ] + }, + "comparison_schema_name": { + "type": "string" + }, + "database_diff_objects": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.DatabaseDiffObject" + } + }, + "inconsistent_num": { + "type": "integer" + } + } + }, "v1.Source": { "type": "object", "properties": { diff --git a/sqle/docs/swagger.yaml b/sqle/docs/swagger.yaml index fbf433c6ae..dcf888a631 100644 --- a/sqle/docs/swagger.yaml +++ b/sqle/docs/swagger.yaml @@ -292,6 +292,8 @@ definitions: type: integer desc: type: string + token: + type: string type: type: string type: object @@ -962,6 +964,99 @@ definitions: $ref: '#/definitions/v1.WorkflowStatisticsResV1' type: object type: object + v1.DatabaseComparisonObject: + properties: + instance_id: + type: string + schema_name: + type: string + type: object + v1.DatabaseComparisonResV1: + properties: + code: + example: 0 + type: integer + data: + items: + $ref: '#/definitions/v1.SchemaObject' + type: array + message: + example: ok + type: string + type: object + v1.DatabaseComparisonStatements: + properties: + base_sql: + $ref: '#/definitions/v1.SQLStatementWithAuditResult' + type: object + comparison_sql: + $ref: '#/definitions/v1.SQLStatementWithAuditResult' + type: object + type: object + v1.DatabaseComparisonStatementsResV1: + properties: + code: + example: 0 + type: integer + data: + $ref: '#/definitions/v1.DatabaseComparisonStatements' + type: object + message: + example: ok + type: string + type: object + v1.DatabaseDiffModifySQL: + properties: + modify_sqls: + items: + $ref: '#/definitions/v1.SQLStatementWithAuditResult' + type: array + schema_name: + type: string + type: object + v1.DatabaseDiffObject: + properties: + inconsistent_num: + type: integer + object_type: + enum: + - TABLE + - VIEW + - PROCEDURE + - TIGGER + - EVENT + - FUNCTION + type: string + objects_diff_result: + items: + $ref: '#/definitions/v1.ObjectDiffResult' + type: array + type: object + v1.DatabaseObject: + properties: + object_name: + type: string + object_type: + enum: + - TABLE + - VIEW + - PROCEDURE + - TIGGER + - EVENT + - FUNCTION + type: string + type: object + v1.DatabaseSchemaObject: + properties: + base_schema_name: + type: string + comparison_schema_name: + type: string + database_objects: + items: + $ref: '#/definitions/v1.DatabaseObject' + type: array + type: object v1.DepBetweenStageInstance: properties: next_stage_instance_id: @@ -1171,6 +1266,30 @@ definitions: $ref: '#/definitions/v1.AuditPlanSQLReqV1' type: array type: object + v1.GenModifySQLResV1: + properties: + code: + example: 0 + type: integer + data: + items: + $ref: '#/definitions/v1.DatabaseDiffModifySQL' + type: array + message: + example: ok + type: string + type: object + v1.GenModifylSQLReqV1: + properties: + base_instance_id: + type: string + comparison_instance_id: + type: string + database_schema_objects: + items: + $ref: '#/definitions/v1.DatabaseSchemaObject' + type: array + type: object v1.GetAuditPlanAnalysisDataResV1: properties: code: @@ -1452,6 +1571,15 @@ definitions: example: ok type: string type: object + v1.GetComparisonStatementsReqV1: + properties: + database_comparison_object: + $ref: '#/definitions/v1.GetDatabaseComparisonReqV1' + type: object + database_object: + $ref: '#/definitions/v1.DatabaseObject' + type: object + type: object v1.GetCustomRuleResV1: properties: code: @@ -1502,6 +1630,15 @@ definitions: example: ok type: string type: object + v1.GetDatabaseComparisonReqV1: + properties: + base_db_object: + $ref: '#/definitions/v1.DatabaseComparisonObject' + type: object + comparison_db_object: + $ref: '#/definitions/v1.DatabaseComparisonObject' + type: object + type: object v1.GetDepBetweenStageInstanceResV1: properties: code: @@ -1563,7 +1700,18 @@ definitions: message: example: ok type: string - total_num: + total_nums: + type: integer + type: object + v1.GetGlobalSqlManageStatisticsResp: + properties: + code: + example: 0 + type: integer + message: + example: ok + type: string + total_nums: type: integer type: object v1.GetInstanceAuditPlanDetailResV1: @@ -2215,6 +2363,18 @@ definitions: total_nums: type: integer type: object + v1.GetSystemModuleRedDotsRes: + properties: + code: + example: 0 + type: integer + data: + $ref: '#/definitions/v1.ModuleRedDots' + type: object + message: + example: ok + type: string + type: object v1.GetSystemVariablesResV1: properties: code: @@ -2565,6 +2725,17 @@ definitions: - manual_audited type: string type: object + v1.GlobalWorkflowStatisticsResV1: + properties: + code: + example: 0 + type: integer + message: + example: ok + type: string + total_nums: + type: integer + type: object v1.HighPriorityConditionReq: properties: key: @@ -2844,11 +3015,36 @@ definitions: $ref: '#/definitions/v1.TimeResV1' type: object type: object + v1.ModuleRedDot: + properties: + has_red_dot: + type: boolean + module_name: + enum: + - global_dashboard + type: string + type: object + v1.ModuleRedDots: + items: + $ref: '#/definitions/v1.ModuleRedDot' + type: array v1.ModuleStatusRes: properties: is_supported: type: boolean type: object + v1.ObjectDiffResult: + properties: + comparison_result: + enum: + - same + - inconsistent + - base_not_exist + - comparison_not_exist + type: string + object_name: + type: string + type: object v1.OperationActionList: properties: desc: @@ -3405,6 +3601,22 @@ definitions: $ref: '#/definitions/v1.AuditTaskResV1' type: object type: object + v1.SQLAuditResult: + properties: + db_type: + type: string + i18n_audit_result_info: + $ref: '#/definitions/model.I18nAuditResultInfo' + type: object + level: + example: warn + type: string + message: + example: 避免使用不必要的内置函数md5() + type: string + rule_name: + type: string + type: object v1.SQLExplain: properties: classic_result: @@ -3432,6 +3644,15 @@ definitions: query_timeout_second: type: integer type: object + v1.SQLStatementWithAuditResult: + properties: + audit_results: + items: + $ref: '#/definitions/v1.SQLAuditResult' + type: array + sql_statement: + type: string + type: object v1.ScheduleTaskDefaultOption: properties: default_selector: @@ -3452,6 +3673,26 @@ definitions: example: ok type: string type: object + v1.SchemaObject: + properties: + base_schema_name: + type: string + comparison_result: + enum: + - same + - inconsistent + - base_not_exist + - comparison_not_exist + type: string + comparison_schema_name: + type: string + database_diff_objects: + items: + $ref: '#/definitions/v1.DatabaseDiffObject' + type: array + inconsistent_num: + type: integer + type: object v1.Source: properties: sql_source_desc: @@ -6124,69 +6365,33 @@ paths: summary: 获取 dashboard 信息 tags: - dashboard - /v1/import_rule_template: - get: - description: get rule template file - operationId: getRuleTemplateFileV1 - parameters: - - description: instance type - in: query - name: instance_type - required: true - type: string - - description: file type - enum: - - csv - - json - in: query - name: file_type - required: true - type: string - responses: - "200": - description: sqle rule template file - schema: - type: file - security: - - ApiKeyAuth: [] - summary: 获取规则模板文件 - tags: - - rule_template - /v1/operation_records: + /v1/dashboard/sql_manages: get: - description: Get operation record list - operationId: getOperationRecordListV1 + description: get global sql manage list + operationId: GetGlobalSqlManageList parameters: - - description: filter_operate_time_from - in: query - name: filter_operate_time_from - type: string - - description: filter_operate_time_to - in: query - name: filter_operate_time_to - type: string - - description: filter_operate_project_name - in: query - name: filter_operate_project_name - type: string - - description: fuzzy_search_operate_user_name + - description: project uid in: query - name: fuzzy_search_operate_user_name + name: filter_project_uid type: string - - description: filter_operate_type_name + - description: instance id in: query - name: filter_operate_type_name + name: filter_instance_id type: string - - description: filter_operate_action + - description: project priority + enum: + - high + - medium + - low in: query - name: filter_operate_action + name: filter_project_priority type: string - - description: page_index + - description: page index in: query name: page_index required: true type: integer - - description: page_size + - description: size of per page in: query name: page_size required: true @@ -6195,44 +6400,260 @@ paths: "200": description: OK schema: - $ref: '#/definitions/v1.GetOperationRecordListResV1' + $ref: '#/definitions/v1.GetGlobalSqlManageListResp' security: - ApiKeyAuth: [] - summary: 获取操作记录列表 + summary: 获取全局管控sql列表 tags: - - OperationRecord - /v1/operation_records/exports: + - SqlManage + /v1/dashboard/sql_manages/statistics: get: - description: Export operation record list - operationId: getExportOperationRecordListV1 + description: get global sql manage statistics + operationId: GetGlobalSqlManageStatistics parameters: - - description: filter_operate_time_from - in: query - name: filter_operate_time_from - type: string - - description: filter_operate_time_to - in: query - name: filter_operate_time_to - type: string - - description: filter_operate_project_name - in: query - name: filter_operate_project_name - type: string - - description: fuzzy_search_operate_user_name + - description: project uid in: query - name: fuzzy_search_operate_user_name + name: filter_project_uid type: string - - description: filter_operate_type_name + - description: instance id in: query - name: filter_operate_type_name + name: filter_instance_id type: string - - description: filter_operate_action + - description: project priority + enum: + - high + - medium + - low in: query - name: filter_operate_action + name: filter_project_priority type: string responses: "200": - description: get export operation record list + description: OK + schema: + $ref: '#/definitions/v1.GetGlobalSqlManageStatisticsResp' + security: + - ApiKeyAuth: [] + summary: 获取全局管控sql统计信息 + tags: + - SqlManage + /v1/dashboard/workflows: + get: + description: get global workflow list + operationId: getGlobalWorkflowsV1 + parameters: + - description: filter create user id + in: query + name: filter_create_user_id + type: string + - description: filter by workflow status,, support using many status + enum: + - wait_for_audit + - wait_for_execution + - rejected + - executing + - canceled + - exec_failed + - finished + in: query + items: + type: string + name: filter_status_list + type: array + - description: filter by project uid + in: query + name: filter_project_uid + type: string + - description: filter by instance id in project + in: query + name: filter_instance_id + type: string + - description: filter by project priority + enum: + - high + - medium + - low + in: query + name: filter_project_priority + type: string + - description: page index + in: query + name: page_index + required: true + type: integer + - description: size of per page + in: query + name: page_size + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/v1.GetWorkflowsResV1' + security: + - ApiKeyAuth: [] + summary: 获取全局工单列表 + tags: + - workflow + /v1/dashboard/workflows/statistics: + get: + description: get global workflow statistics + operationId: GetGlobalWorkflowStatistics + parameters: + - description: filter create user id + in: query + name: filter_create_user_id + type: string + - description: filter by workflow status,, support using many status + enum: + - wait_for_audit + - wait_for_execution + - rejected + - executing + - canceled + - exec_failed + - finished + in: query + items: + type: string + name: filter_status_list + type: array + - description: filter by project uid + in: query + name: filter_project_uid + type: string + - description: filter by instance id in project + in: query + name: filter_instance_id + type: string + - description: filter by project priority + enum: + - high + - medium + - low + in: query + name: filter_project_priority + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/v1.GlobalWorkflowStatisticsResV1' + security: + - ApiKeyAuth: [] + summary: 获取全局工单统计数据 + tags: + - workflow + /v1/import_rule_template: + get: + description: get rule template file + operationId: getRuleTemplateFileV1 + parameters: + - description: instance type + in: query + name: instance_type + required: true + type: string + - description: file type + enum: + - csv + - json + in: query + name: file_type + required: true + type: string + responses: + "200": + description: sqle rule template file + schema: + type: file + security: + - ApiKeyAuth: [] + summary: 获取规则模板文件 + tags: + - rule_template + /v1/operation_records: + get: + description: Get operation record list + operationId: getOperationRecordListV1 + parameters: + - description: filter_operate_time_from + in: query + name: filter_operate_time_from + type: string + - description: filter_operate_time_to + in: query + name: filter_operate_time_to + type: string + - description: filter_operate_project_name + in: query + name: filter_operate_project_name + type: string + - description: fuzzy_search_operate_user_name + in: query + name: fuzzy_search_operate_user_name + type: string + - description: filter_operate_type_name + in: query + name: filter_operate_type_name + type: string + - description: filter_operate_action + in: query + name: filter_operate_action + type: string + - description: page_index + in: query + name: page_index + required: true + type: integer + - description: page_size + in: query + name: page_size + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/v1.GetOperationRecordListResV1' + security: + - ApiKeyAuth: [] + summary: 获取操作记录列表 + tags: + - OperationRecord + /v1/operation_records/exports: + get: + description: Export operation record list + operationId: getExportOperationRecordListV1 + parameters: + - description: filter_operate_time_from + in: query + name: filter_operate_time_from + type: string + - description: filter_operate_time_to + in: query + name: filter_operate_time_to + type: string + - description: filter_operate_project_name + in: query + name: filter_operate_project_name + type: string + - description: fuzzy_search_operate_user_name + in: query + name: fuzzy_search_operate_user_name + type: string + - description: filter_operate_type_name + in: query + name: filter_operate_type_name + type: string + - description: filter_operate_action + in: query + name: filter_operate_action + type: string + responses: + "200": + description: get export operation record list schema: type: file security: @@ -7059,6 +7480,90 @@ paths: summary: 更新黑名单 tags: - blacklist + /v1/projects/{project_name}/database_comparison/comparison_statements: + post: + consumes: + - application/json + description: get database comparison detail + operationId: getComparisonStatementV1 + parameters: + - description: project name + in: path + name: project_name + required: true + type: string + - description: get database comparison statement request + in: body + name: database_comparison_object + required: true + schema: + $ref: '#/definitions/v1.GetComparisonStatementsReqV1' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/v1.DatabaseComparisonStatementsResV1' + security: + - ApiKeyAuth: [] + summary: 获取对比语句 + tags: + - database_comparison + /v1/projects/{project_name}/database_comparison/execute_comparison: + post: + consumes: + - application/json + description: get database comparison + operationId: executeDatabaseComparisonV1 + parameters: + - description: project name + in: path + name: project_name + required: true + type: string + - description: get database comparison request + in: body + name: database_comparison + required: true + schema: + $ref: '#/definitions/v1.GetDatabaseComparisonReqV1' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/v1.DatabaseComparisonResV1' + security: + - ApiKeyAuth: [] + summary: 执行数据库结构对比并获取结果 + tags: + - database_comparison + /v1/projects/{project_name}/database_comparison/modify_sql_statements: + post: + consumes: + - application/json + description: generate database diff modify sqls + operationId: genDatabaseDiffModifySQLsV1 + parameters: + - description: project name + in: path + name: project_name + required: true + type: string + - description: generate database diff modify sqls request + in: body + name: gen_modify_sql + required: true + schema: + $ref: '#/definitions/v1.GenModifylSQLReqV1' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/v1.GenModifySQLResV1' + security: + - ApiKeyAuth: [] + summary: 生成变更SQL + tags: + - database_comparison /v1/projects/{project_name}/instance_audit_plans: get: description: get instance audit plan info list @@ -10750,47 +11255,6 @@ paths: summary: 直接审核SQL tags: - sql_audit - /v1/sql_manages: - get: - description: get global sql manage list - operationId: GetGlobalSqlManageList - parameters: - - description: project uid - in: query - name: filter_project_uid - type: string - - description: instance id - in: query - name: filter_instance_id - type: string - - description: project priority - enum: - - high - - medium - - low - in: query - name: filter_project_priority - type: string - - description: page index - in: query - name: page_index - required: true - type: integer - - description: size of per page - in: query - name: page_size - required: true - type: integer - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.GetGlobalSqlManageListResp' - security: - - ApiKeyAuth: [] - summary: 获取全局管控sql列表 - tags: - - SqlManage /v1/statistic/instances/sql_average_execution_time: get: description: get average execution time of sql @@ -11027,6 +11491,20 @@ paths: summary: 获取各种状态工单的数量 tags: - statistic + /v1/system/module_red_dots: + get: + description: get the red dot prompt information in the system + operationId: GetSystemModuleRedDots + responses: + "200": + description: OK + schema: + $ref: '#/definitions/v1.GetSystemModuleRedDotsRes' + security: + - ApiKeyAuth: [] + summary: 查询系统各模块的红点提示信息 + tags: + - system /v1/system/module_status: get: description: get module status for modulealities in the system @@ -11373,105 +11851,6 @@ paths: summary: 获取用户提示列表 tags: - user - /v1/workflows: - get: - description: get global workflow list - operationId: getGlobalWorkflowsV1 - parameters: - - description: filter subject - in: query - name: filter_subject - type: string - - description: filter create time from - in: query - name: filter_create_time_from - type: string - - description: filter create time to - in: query - name: filter_create_time_to - type: string - - description: filter_task_execute_start_time_from - in: query - name: filter_task_execute_start_time_from - type: string - - description: filter_task_execute_start_time_to - in: query - name: filter_task_execute_start_time_to - type: string - - description: filter create user id - in: query - name: filter_create_user_id - type: string - - description: filter workflow status - enum: - - wait_for_audit - - wait_for_execution - - rejected - - executing - - canceled - - exec_failed - - finished - in: query - name: filter_status - type: string - - description: filter by workflow status,, support using many status - enum: - - wait_for_audit - - wait_for_execution - - rejected - - executing - - canceled - - exec_failed - - finished - in: query - items: - type: string - name: filter_status_list - type: array - - description: filter by project uid - in: query - name: filter_project_uid - type: string - - description: filter by instance id in project - in: query - name: filter_instance_id - type: string - - description: filter by project priority - enum: - - high - - medium - - low - in: query - name: filter_project_priority - type: string - - description: filter current step assignee user id - in: query - name: filter_current_step_assignee_user_id - type: string - - description: filter instance id - in: query - name: filter_task_instance_id - type: string - - description: page index - in: query - name: page_index - required: true - type: integer - - description: size of per page - in: query - name: page_size - required: true - type: integer - responses: - "200": - description: OK - schema: - $ref: '#/definitions/v1.GetWorkflowsResV1' - security: - - ApiKeyAuth: [] - summary: 获取全局工单列表 - tags: - - workflow /v1/workflows/statistic_of_instances: get: description: Get Workflows Statistic Of Instances diff --git a/sqle/driver/mysql/mysql_ce.go b/sqle/driver/mysql/mysql_ce.go index f07e7cbec6..72e39e1300 100644 --- a/sqle/driver/mysql/mysql_ce.go +++ b/sqle/driver/mysql/mysql_ce.go @@ -13,3 +13,11 @@ import ( func (i *MysqlDriverImpl) Query(ctx context.Context, sql string, conf *driverV2.QueryConf) (*driverV2.QueryResult, error) { return nil, fmt.Errorf("only support Query in enterprise edition") } + +func (i *MysqlDriverImpl) GetDatabaseDiffModifySQL(ctx context.Context, calibratedDSN *driverV2.DSN, objInfos []*driverV2.DatabasCompareSchemaInfo) ([]*driverV2.DatabaseDiffModifySQLResult, error) { + return nil, fmt.Errorf("only support Query in enterprise edition") +} + +func (i *MysqlDriverImpl) GetDatabaseObjectDDL(ctx context.Context, objInfos []*driverV2.DatabasSchemaInfo) ([]*driverV2.DatabaseSchemaObjectResult, error) { + return nil, fmt.Errorf("only support Query in enterprise edition") +} diff --git a/sqle/driver/mysql/splitter/block.go b/sqle/driver/mysql/splitter/block.go new file mode 100644 index 0000000000..8e4881c556 --- /dev/null +++ b/sqle/driver/mysql/splitter/block.go @@ -0,0 +1,80 @@ +package splitter + +import ( + "github.com/pingcap/parser" + "strings" +) + +type Block interface { + MatchBegin(token *parser.Token) bool + MatchEnd(token *parser.Token) bool +} + +var allBlocks []Block = []Block{ + BeginEndBlock{}, + IfEndIfBlock{}, + CaseEndCaseBlock{}, + RepeatEndRepeatBlock{}, + WhileEndWhileBlock{}, + LoopEndLoopBlock{}, +} + +type LoopEndLoopBlock struct{} + +func (b BeginEndBlock) MatchBegin(token *parser.Token) bool { + return token.TokenType() == parser.Begin +} + +func (b BeginEndBlock) MatchEnd(token *parser.Token) bool { + return true +} + +type IfEndIfBlock struct{} + +func (b IfEndIfBlock) MatchBegin(token *parser.Token) bool { + return token.TokenType() == parser.IfKwd +} + +func (b IfEndIfBlock) MatchEnd(token *parser.Token) bool { + return token.TokenType() == parser.IfKwd +} + +type CaseEndCaseBlock struct{} + +func (b CaseEndCaseBlock) MatchBegin(token *parser.Token) bool { + return token.TokenType() == parser.CaseKwd +} + +func (b CaseEndCaseBlock) MatchEnd(token *parser.Token) bool { + return token.TokenType() == parser.CaseKwd +} + +type RepeatEndRepeatBlock struct{} + +func (b RepeatEndRepeatBlock) MatchBegin(token *parser.Token) bool { + return token.TokenType() == parser.Repeat +} + +func (b RepeatEndRepeatBlock) MatchEnd(token *parser.Token) bool { + return token.TokenType() == parser.Repeat +} + +type WhileEndWhileBlock struct{} + +func (b WhileEndWhileBlock) MatchBegin(token *parser.Token) bool { + return token.TokenType() == parser.Identifier && strings.ToUpper(token.Ident()) == "WHILE" +} + +func (b WhileEndWhileBlock) MatchEnd(token *parser.Token) bool { + return token.TokenType() == parser.Identifier && strings.ToUpper(token.Ident()) == "WHILE" +} + +type BeginEndBlock struct{} + +func (b LoopEndLoopBlock) MatchBegin(token *parser.Token) bool { + return token.TokenType() == parser.Identifier && strings.ToUpper(token.Ident()) == "LOOP" +} + +func (b LoopEndLoopBlock) MatchEnd(token *parser.Token) bool { + return token.TokenType() == parser.Identifier && strings.ToUpper(token.Ident()) == "LOOP" +} diff --git a/sqle/driver/mysql/splitter/delimiter.go b/sqle/driver/mysql/splitter/delimiter.go new file mode 100644 index 0000000000..f264837bf7 --- /dev/null +++ b/sqle/driver/mysql/splitter/delimiter.go @@ -0,0 +1,150 @@ +package splitter + +import ( + "errors" + "github.com/pingcap/parser" + "strings" +) + +const ( + BackSlash int = '\\' + BackSlashString string = "\\" + BlankSpace string = " " + DefaultDelimiterString string = ";" + DelimiterCommand string = "DELIMITER" + DelimiterCommandSort string = `\d` +) + +type Delimiter struct { + FirstTokenTypeOfDelimiter int + FirstTokenValueOfDelimiter string + DelimiterStr string + line int + startPos int +} + +func NewDelimiter() *Delimiter { + return &Delimiter{} +} + +/* + 根据传入的SQL和位置,判断当前位置开始是否是一个缩写分隔符的语法 + +判断依据:从当前位置开始是否紧跟着一个\\d +不使用Lex的原因: + 1. \\d会被识别为三个token,即: \ \ d + 2. Lex可能会跳过空格和注释,因此这里使用字符串匹配 +*/ +func (d *Delimiter) isSortDelimiterCommand(sql string, index int) bool { + return index+2 < len(sql) && sql[index:index+2] == "\\d" +} + +// DELIMITER会被识别为identifier,因此这里仅需识别其值是否相等 +func (d *Delimiter) isDelimiterCommand(token string) bool { + return strings.ToUpper(token) == DelimiterCommand +} + +// 获取下一个参数及其参数的结束位置 +// 该函数翻译自MySQL Client获取delimiter值的代码,参考:https://github.com/mysql/mysql-server/blob/824e2b4064053f7daf17d7f3f84b7a3ed92e5fb4/client/mysql.cc#L4866 +func getDelimiterValueAndEndPos(sqlAfterDelimiter string) (string, int) { + ptr := 0 + start := 0 + quoted := false + qType := byte(0) + + // 跳过开头的空格 + for ptr < len(sqlAfterDelimiter) && isWhitespace(sqlAfterDelimiter[ptr]) { + ptr++ + } + + if ptr == len(sqlAfterDelimiter) { + return "", -1 + } + + // 检查是否为引号字符串 + if sqlAfterDelimiter[ptr] == '\'' || sqlAfterDelimiter[ptr] == '"' || sqlAfterDelimiter[ptr] == '`' { + qType = sqlAfterDelimiter[ptr] + quoted = true + ptr++ + } + + start = ptr + + // 找到字符串结尾 + for ptr < len(sqlAfterDelimiter) { + if !quoted && sqlAfterDelimiter[ptr] == '\\' && ptr+1 < len(sqlAfterDelimiter) { // 跳过转义字符 + ptr += 2 + } else if (!quoted && isWhitespace(sqlAfterDelimiter[ptr])) || (quoted && sqlAfterDelimiter[ptr] == qType) { + break + } else { + ptr++ + } + } + // 结束位置为分隔符后一位 + var endPos int = ptr + if ptr < len(sqlAfterDelimiter) { + endPos++ + } + return sqlAfterDelimiter[start:ptr], endPos +} + +// 辅助函数,判断字符是否为空格 +func isWhitespace(c byte) bool { + return c == ' ' || c == '\t' || c == '\n' || c == '\r' +} + +var ErrDelimiterCanNotExtractToken = errors.New("sorry, we cannot extract any token form the delimiter you provide, please change a delimiter") +var ErrDelimiterContainsBackslash = errors.New("DELIMITER cannot contain a backslash character") +var ErrDelimiterContainsBlankSpace = errors.New("DELIMITER should not contain blank space") +var ErrDelimiterMissing = errors.New("DELIMITER must be followed by a 'delimiter' character or string") +var ErrDelimiterReservedKeyword = errors.New("delimiter should not use a reserved keyword") + +/* +该方法设置分隔符,对分隔符的内容有一定的限制: + + 1. 不允许分隔符内部包含反斜杠 + 2. 不允许分隔符为空字符串 + 3. 不允许分隔符为mysql的保留字,因为这样会被scanner扫描为其他类型的token,从而绕过判断分隔符的逻辑 + +注:其中1和2与MySQL客户端对分隔符内容一致,错误内容参考MySQL客户端源码中的com_delimiter函数 +https://github.com/mysql/mysql-server/blob/824e2b4064053f7daf17d7f3f84b7a3ed92e5fb4/client/mysql.cc#L4621 +*/ +func (d *Delimiter) setDelimiter(delimiter string) (err error) { + if delimiter == "" { + return ErrDelimiterMissing + } + if strings.Contains(delimiter, BackSlashString) { + return ErrDelimiterContainsBackslash + } + if strings.Contains(delimiter, BlankSpace) { + return ErrDelimiterContainsBlankSpace + } + if isReservedKeyWord(delimiter) { + return ErrDelimiterReservedKeyword + } + token := parser.NewScanner(delimiter).NextToken() + d.FirstTokenTypeOfDelimiter = token.TokenType() + if d.FirstTokenTypeOfDelimiter == 0 { + return ErrDelimiterCanNotExtractToken + } + d.FirstTokenValueOfDelimiter = token.Ident() + d.DelimiterStr = delimiter + return nil +} + +func isReservedKeyWord(input string) bool { + token := parser.NewScanner(input).NextToken() + tokenType := token.TokenType() + if len(token.Ident()) < len(input) { + // 如果分隔符无法识别为一个token,则一定不是关键字 + return false + } + // 如果分隔符识别为一个关键字,但不知道是哪个关键字,则为identifier,此时就非保留字 + return tokenType != parser.Identifier && tokenType > parser.YyEOFCode && tokenType < parser.YyDefault +} + +func (d *Delimiter) reset() error { + d.line = 0 + d.startPos = 0 + return d.setDelimiter(DefaultDelimiterString) +} diff --git a/sqle/driver/mysql/splitter/splitter.go b/sqle/driver/mysql/splitter/splitter.go new file mode 100644 index 0000000000..9892bf4d19 --- /dev/null +++ b/sqle/driver/mysql/splitter/splitter.go @@ -0,0 +1,284 @@ +package splitter + +import ( + "bytes" + + "strings" + + "github.com/pingcap/parser" + "github.com/pingcap/parser/ast" +) + +type splitter struct { + parser *parser.Parser + delimiter *Delimiter + scanner *parser.Scanner +} + +func NewSplitter() *splitter { + return &splitter{ + parser: parser.New(), + delimiter: NewDelimiter(), + scanner: parser.NewScanner(""), + } +} + +func (s *splitter) ParseSqlText(sqlText string) ([]ast.StmtNode, error) { + err := s.delimiter.reset() + if err != nil { + return nil, err + } + results, err := s.splitSqlText(sqlText) + if err != nil { + return nil, err + } + return s.processToExecutableNodes(results) +} + +func (s *splitter) processToExecutableNodes(results []*singleSQL) ([]ast.StmtNode, error) { + var executableNodes []ast.StmtNode + for _, result := range results { + // 根据解析结果生成得到sql的抽象语法树 + stmt, err := s.parser.ParseOneStmt(result.originSql, "", "") + if err != nil { + // 若解析结果为错误,则将分割后的SQL作为不可解析的SQL添加到executableNodes中 + unParsedStmt := &ast.UnparsedStmt{} + unParsedStmt.SetStartLine(result.lineNumber) + unParsedStmt.SetText(result.originSql) + executableNodes = append(executableNodes, unParsedStmt) + } else { + // 若能成功解析,则将解析的结果添加到executableNodes中 + stmt.SetStartLine(result.lineNumber) + executableNodes = append(executableNodes, stmt) + } + } + return executableNodes, nil +} + +type singleSQL struct { + originSql string + lineNumber int + isDelimiterCommand bool +} + +func (s singleSQL) IsEmpty() bool { + return s.originSql == "" +} + +func (s *splitter) splitSqlText(sqlText string) (results []*singleSQL, err error) { + result, err := s.getNextSql(sqlText) + if err != nil { + return nil, err + } + if !result.IsEmpty() && !result.isDelimiterCommand { + results = append(results, result) + } + // 递归切分剩余SQL + if s.scanner.Offset() < len(sqlText) { + subResults, err := s.splitSqlText(sqlText[s.scanner.Offset():]) + if err != nil { + return results, err + } + results = append(results, subResults...) + } + return results, nil +} + +func (s *splitter) getNextSql(sqlText string) (*singleSQL, error) { + matchedDelimiterCommand, err := s.matchAndSetCustomDelimiter(sqlText) + if err != nil { + return nil, err + } + // 根据分隔符匹配到SQL结尾,输出切分后的原始SQL + if matchedDelimiterCommand || s.matchSql(sqlText) { + buff := bytes.Buffer{} + buff.WriteString(sqlText[:s.scanner.Offset()]) + lineBeforeStart := strings.Count(sqlText[:s.delimiter.startPos], "\n") + originSql := s.formateOriginSql(strings.TrimSpace(buff.String())) + if originSql == "" { + // 跳过空SQL + return &singleSQL{}, nil + } + result := &singleSQL{ + originSql: originSql, + lineNumber: s.delimiter.line + lineBeforeStart + 1, + isDelimiterCommand: matchedDelimiterCommand, + } + s.delimiter.line += s.scanner.ScannedLines() // pos().Line-1表示的是该SQL中有多少换行 + return result, nil + } + // 处理剩余SQL文本 + restOfSql := s.formateOriginSql(strings.TrimSpace(sqlText)) + if restOfSql == "" { + // 跳过空SQL + return &singleSQL{}, nil + } + + return &singleSQL{ + originSql: restOfSql, + lineNumber: s.delimiter.line + strings.Count(sqlText[:s.delimiter.startPos], "\n") + 1, + isDelimiterCommand: matchedDelimiterCommand, + }, nil +} + +// 如果SQL有分隔符,则保留SQL的分隔符, +// 如果SQL没有分隔符,则返回没有分隔符的SQL +// 如果分隔符不是默认分隔符,则将其替换为默认分隔符 +// 如果去除分隔符和空白符之后SQL为空字符串,则返回空字符串,空字符串需跳过 +func (s *splitter) formateOriginSql(originSql string) string { + if strings.HasSuffix(originSql, s.delimiter.DelimiterStr) { + trimmedSql := strings.TrimSuffix(originSql, s.delimiter.DelimiterStr) + if trimmedSql == "" { + return "" + } + if s.delimiter.DelimiterStr != DefaultDelimiterString { + originSql = trimmedSql + DefaultDelimiterString + } + } + return originSql +} + +func (s *splitter) matchSql(sql string) bool { + s.scanner.Reset(sql) + token := &parser.Token{} + var isFirstToken bool = true + + for s.scanner.Offset() < len(sql) { + token = s.scanner.NextToken() + if isFirstToken { + s.delimiter.startPos = s.scanner.Offset() + isFirstToken = false + } + token = s.skipBeginEndBlock(token) + if s.isTokenMatchDelimiter(token) { + return true + } + } + return false +} + +func (s *splitter) skipBeginEndBlock(token *parser.Token) *parser.Token { + var blockStack []Block + if token.TokenType() == parser.Begin { + blockStack = append(blockStack, BeginEndBlock{}) + } + for len(blockStack) > 0 { + token = s.scanner.NextToken() + for _, block := range allBlocks { + if block.MatchBegin(token) { + blockStack = append(blockStack, block) + break + } + } + /* + begin...end语句块示例如下,语法都以END+对应开始标志为该语句块的结束,因此当匹配到END时,判断下一个TOKEN是否匹配该语句块,若匹配则弹出该语句块 + BEGIN + IF + END IF + WHILE + END WHILE + END + */ + if token.TokenType() == parser.End { + // 如果匹配到END,则需要判断END后的token是否匹配当前的Block + currentBlock := blockStack[len(blockStack)-1] + token = s.scanner.NextToken() + if currentBlock.MatchEnd(token) { + blockStack = blockStack[:len(blockStack)-1] + } + // 如果未匹配到,则为错误的begin...end语句块 + } + if len(blockStack) == 0 { + // 语句块栈全部弹出,则begin...end语句块正确匹配,返回结束循环,返回END后一个TOKEN + break + } + // 如果匹配到SQL的结尾都没有结束该begin...end语句块,返回最后一个TOKEN + if len(s.scanner.Text()) == s.scanner.Offset() { + break + } + } + return token +} + +// ref:https://dev.mysql.com/doc/refman/8.4/en/flow-control-statements.html +func (s *splitter) isTokenMatchDelimiter(token *parser.Token) bool { + switch token.TokenType() { + case s.delimiter.FirstTokenTypeOfDelimiter: + /* + 在mysql client的语法中需要跳过注释以及分隔符处于引号中的情况,由于scanner.Lex会自动跳过注释,因此,仅需要判断分隔符处于引号中的情况。对于该方法,以分隔符的第一个token作为特征仅需匹配,可能会匹配到由引号括起的情况,存在stringLit和identifier两种token需要进一步判断: + 1. 当匹配到identifier时,identifier有可能由反引号括起: + 1. 若identifier没有反引号括起,则不需要判断是否跳过 + 2. 若identifier被反引号括起,匹配的字符串会带上反引号,能在匹配字符串时能够检查出是否需要跳过 + 2. 当匹配到stringLit时,stringLit一定是由单引号或双引号括起: + 1. 当分隔符第一个token值与stringLit的token值不等,那么一定不是分隔符,则跳过 + 2. 当分隔符第一个token值与stringLit的token值相等, 如:"'abc'd" '"abc"d'会因为字符串不匹配而跳过 + */ + // 1. 当分隔符第一个token值与stringLit的token值不等,那么一定不是分隔符,则跳过 + if token.TokenType() == parser.StringLit && token.Ident() != s.delimiter.FirstTokenValueOfDelimiter { + return false + } + // 2. 定位特征的第一个字符所处的位置 + indexInToken := strings.Index(token.Ident(), s.delimiter.FirstTokenValueOfDelimiter) + if indexInToken == -1 { + return false + } + // 3. 字符串匹配 + begin := s.scanner.Offset() + indexInToken + end := begin + len(s.delimiter.DelimiterStr) + if begin < 0 || end > len(s.scanner.Text()) { + return false + } + expected := s.scanner.Text()[begin:end] + if expected != s.delimiter.DelimiterStr { + return false + } + s.scanner.SetCursor(end) + return true + + case parser.Invalid: + s.scanner.HandleInvalid() + } + return false +} + +/* +该方法检测sql文本开头是否是自定义分隔符语法,若是匹配并更新分隔符: + + 1. 分隔符语法满足:delimiter str 或者 \d str + 2. 参考链接:https://dev.mysql.com/doc/refman/5.7/en/mysql-commands.html +*/ +func (s *splitter) matchAndSetCustomDelimiter(sql string) (bool, error) { + // 重置扫描器 + s.scanner.Reset(sql) + var sqlAfterDelimiter string + // 根据token的类型判断是否是分隔符语法的开始 + token := s.scanner.NextToken() + switch token.TokenType() { + case BackSlash: + if s.delimiter.isSortDelimiterCommand(sql, s.scanner.Offset()) { + sqlAfterDelimiter = sql[s.scanner.Offset()+2:] // \d的长度是2字节 + s.delimiter.startPos = s.scanner.Offset() + s.scanner.SetCursor(s.scanner.Offset() + 2) + } + case parser.Identifier: + if s.delimiter.isDelimiterCommand(token.Ident()) { + sqlAfterDelimiter = sql[s.scanner.Offset()+9:] //DELIMITER的长度是9字节 + s.delimiter.startPos = s.scanner.Offset() + s.scanner.SetCursor(s.scanner.Offset() + 9) + } + default: + return false, nil + } + // 若定义分隔符命令后仍有文本,则需要获取和设置自定义分隔符的值,并重置游标 + if sqlAfterDelimiter != "" { + // 获取分隔符定义语法后的自定义分隔符,并设置自定义分隔符 + newDelimiterValue, endPos := getDelimiterValueAndEndPos(sqlAfterDelimiter) + if err := s.delimiter.setDelimiter(newDelimiterValue); err != nil { + return false, err + } + // 重置游标到分隔符语法后 + s.scanner.SetCursor(s.scanner.Offset() + endPos) + return true, nil + } + return false, nil +} diff --git a/sqle/driver/mysql/splitter/splitter_test.go b/sqle/driver/mysql/splitter/splitter_test.go new file mode 100644 index 0000000000..01a3fb998a --- /dev/null +++ b/sqle/driver/mysql/splitter/splitter_test.go @@ -0,0 +1,1186 @@ +package splitter + +import ( + "bytes" + "fmt" + "github.com/pingcap/parser/ast" + parser_formate "github.com/pingcap/parser/format" + _ "github.com/pingcap/tidb/types/parser_driver" + "github.com/stretchr/testify/assert" + "os" + "strings" + "testing" +) + +func TestSplitSqlText(t *testing.T) { + s := NewSplitter() + // 读取文件内容 + testCases := []struct { + filePath string + expectedLength int + }{ + {"splitter_test_1.sql", 14}, + {"splitter_test_2.sql", 14}, + {"splitter_test_3.sql", 4}, + {"splitter_test_skip_quoted_delimiter.sql", 18}, + } + for _, testCase := range testCases { + t.Run(testCase.filePath, func(t *testing.T) { + sqls, err := os.ReadFile(testCase.filePath) + if err != nil { + t.Fatalf("failed to read file: %v", err) + } + splitResults, err := s.splitSqlText(string(sqls)) + if err != nil { + t.Fatalf(err.Error()) + } + assert.Equal(t, testCase.expectedLength, len(splitResults)) + }) + } +} + +func TestSplitterProcess(t *testing.T) { + s := NewSplitter() + testCases := []struct { + filePath string + expectedLength int + }{ + {"splitter_test_1.sql", 14}, + {"splitter_test_2.sql", 14}, + {"splitter_test_3.sql", 4}, + {"splitter_test_skip_quoted_delimiter.sql", 18}, + } + for _, testCase := range testCases { + t.Run(testCase.filePath, func(t *testing.T) { + // 读取文件内容 + sqlText, err := os.ReadFile(testCase.filePath) + if err != nil { + t.Fatalf("无法读取文件: %v", err) + } + executableNodes, err := s.ParseSqlText(string(sqlText)) + if err != nil { + t.Fatalf(err.Error()) + } + assert.Equal(t, testCase.expectedLength, len(executableNodes)) + }) + } +} + +func TestIsDelimiterReservedKeyWord(t *testing.T) { + tests := []struct { + delimiter string + expected bool + }{ + // 非关键字 + {"id", false}, + {"$$", false}, + {";;", false}, + {"\\", false}, + {"Abscsd", false}, + {"%%", false}, + {"|", false}, + {"%", false}, + {"foo", false}, + {"column1", false}, + {"table_name", false}, + {"_underscore", false}, + // 关键字 + {"&&", true}, + {"=", true}, + {"!=", true}, + {"<=", true}, + {">=", true}, + {"||", true}, + {"<>", true}, + {"IN", true}, + {"AS", true}, + {"Update", true}, + {"Delete", true}, + {"not", true}, + {"Order", true}, + {"by", true}, + {"Select", true}, + {"From", true}, + {"Where", true}, + {"Join", true}, + {"Inner", true}, + {"Left", true}, + {"Right", true}, + {"Full", true}, + {"Group", true}, + {"Having", true}, + {"Insert", true}, + {"Into", true}, + {"Values", true}, + {"Create", true}, + {"Table", true}, + {"Alter", true}, + {"Drop", true}, + {"Truncate", true}, + {"Union", true}, + {"Exists", true}, + {"Like", true}, + {"Distinct", true}, + {"And", true}, + {"Or", true}, + {"Limit", true}, + {"ALL", true}, + {"ANY", true}, + {"BETWEEN", true}, + } + + for _, test := range tests { + t.Run(test.delimiter, func(t *testing.T) { + result := isReservedKeyWord(test.delimiter) + if result != test.expected { + t.Errorf("isDelimiterReservedKeyWord(%s) = %v; want %v", test.delimiter, result, test.expected) + } + }) + } +} + +func TestSkipQuotedDelimiter(t *testing.T) { + s := NewSplitter() + // 读取文件内容 + sqls, err := os.ReadFile("splitter_test_skip_quoted_delimiter.sql") + if err != nil { + t.Fatalf("无法读取文件: %v", err) + } + splitResults, err := s.splitSqlText(string(sqls)) + if err != nil { + t.Fatalf(err.Error()) + } + for _, result := range splitResults { + fmt.Print("------------------------------\n") + fmt.Printf("SQL语句在第%v行\n", result.lineNumber) + fmt.Printf("SQL语句为:\n%v\n", result.originSql) + } + if len(splitResults) != 18 { + t.FailNow() + } +} + +func TestStartLine(t *testing.T) { + // 测试用例第2个到第5个sql是解析器不能解析的sql + p := NewSplitter() + stmts, err := p.ParseSqlText(`grant all on point_trans_shard_00_part_202401 to kgoldpointapp; +create table point_trans_shard_00_part_202401(like point_trans_shard_00 including all) inherits(point_trans_shard_00); +Alter table point_trans_shard_00_part_202401 ADD CONSTRAINT chk_point_trans_shard_202401 CHECK (processedtime >= '1704038400000'::bigint AND processedtime < '1706716800000'::bigint ); +create table point_trans_source_shard_00_part_202401(like point_trans_source_shard_00 including all) inherits(point_trans_source_shard_00); +Alter table point_trans_source_shard_00_part_202401 ADD CONSTRAINT chk_point_trans_source_shard_202401 CHECK (processedtime >= '1704038400000'::bigint AND processedtime < '1706716800000'::bigint ); +grant select on point_trans_shard_00_part_202401 to prd_fin, dbsec, sec_db_scan; +grant all on point_trans_source_shard_00_part_202401 to kgoldpointapp; +grant select on point_trans_source_shard_00_part_202401 to prd_fin, dbsec, sec_db_scan; +`) + if err != nil { + t.Error(err) + return + } + if len(stmts) != 8 { + t.Errorf("expect 2 stmts, actual is %d", len(stmts)) + return + } + for i, stmt := range stmts { + if stmt.StartLine() != i+1 { + t.Errorf("expect start line is %d, actual is %d", i+1, stmt.StartLine()) + } + } + + // 所有测试用例都是可以解析的sql + stmts, err = p.ParseSqlText(`grant select on point_trans_shard_00_part_202401 to prd_fin, dbsec, sec_db_scan; +grant all on point_trans_source_shard_00_part_202401 to kgoldpointapp; +grant select on point_trans_source_shard_00_part_202401 to prd_fin, dbsec, sec_db_scan; +`) + if err != nil { + t.Error(err) + return + } + if len(stmts) != 3 { + t.Errorf("expect 3 nodes, actual is %d", len(stmts)) + return + } + for i, node := range stmts { + if node.StartLine() != i+1 { + t.Errorf("expect start line is %d, actual is %d", i+1, node.StartLine()) + } + } + + // 所有测试用例都是不可以解析的sql + stmts, err = p.ParseSqlText(`create table point_trans_shard_00_part_202401(like point_trans_shard_00 including all) inherits(point_trans_shard_00); +Alter table point_trans_shard_00_part_202401 ADD CONSTRAINT chk_point_trans_shard_202401 CHECK (processedtime >= '1704038400000'::bigint AND processedtime < '1706716800000'::bigint ); +create table point_trans_source_shard_00_part_202401(like point_trans_source_shard_00 including all) inherits(point_trans_source_shard_00);`) + if err != nil { + t.Error(err) + return + } + if len(stmts) != 3 { + t.Errorf("expect 3 stmts, actual is %d", len(stmts)) + return + } + for i, stmt := range stmts { + if stmt.StartLine() != i+1 { + t.Errorf("expect start line is %d, actual is %d", i+1, stmt.StartLine()) + } + } + + // 并排sql测试用例,备注:3个sql都不能被解析 + stmts, err = p.ParseSqlText(`create table point_trans_shard_00_part_202401(like point_trans_shard_00 including all) inherits(point_trans_shard_00); +Alter table point_trans_shard_00_part_202401 ADD CONSTRAINT chk_point_trans_shard_202401 CHECK (processedtime >= '1704038400000'::bigint AND processedtime < '1706716800000'::bigint );create table point_trans_source_shard_00_part_202401(like point_trans_source_shard_00 including all) inherits(point_trans_source_shard_00);`) + if err != nil { + t.Error(err) + return + } + if len(stmts) != 3 { + t.Errorf("expect 3 stmts, actual is %d", len(stmts)) + return + } + + for i, stmt := range stmts { + if i == 2 { + if stmt.StartLine() != 2 { + t.Errorf("expect start line is 2, actual is %d", stmt.StartLine()) + } + } else { + if stmt.StartLine() != i+1 { + t.Errorf("expect start line is %d, actual is %d", i+1, stmt.StartLine()) + } + } + } +} + +func TestPerfectParse(t *testing.T) { + parser := NewSplitter() + + stmt, err := parser.ParseSqlText("OPTIMIZE TABLE foo;") + if err != nil { + t.Error(err) + return + } + if _, ok := stmt[0].(*ast.UnparsedStmt); !ok { + t.Errorf("expect stmt type is unparsedStmt, actual is %T", stmt) + return + } + + type testCase struct { + sql string + expect []string + } + + tc := []testCase{ + { + sql: `SELECT * FROM db1.t1`, + expect: []string{ + `SELECT * FROM db1.t1`, + }, + }, + { + sql: `SELECT * FROM db1.t1;SELECT * FROM db2.t2`, + expect: []string{ + "SELECT * FROM db1.t1", + "SELECT * FROM db2.t2", + }, + }, + { + sql: "SELECT * FROM db1.t1;OPTIMIZE TABLE foo;SELECT * FROM db2.t2", + expect: []string{ + "SELECT * FROM db1.t1;", + "OPTIMIZE TABLE foo;", + "SELECT * FROM db2.t2", + }, + }, + { + sql: "OPTIMIZE TABLE foo;SELECT * FROM db1.t1;SELECT * FROM db2.t2", + expect: []string{ + "OPTIMIZE TABLE foo;", + "SELECT * FROM db1.t1;", + "SELECT * FROM db2.t2", + }, + }, + { + sql: "SELECT * FROM db1.t1;SELECT * FROM db2.t2;OPTIMIZE TABLE foo", + expect: []string{ + "SELECT * FROM db1.t1;", + "SELECT * FROM db2.t2;", + "OPTIMIZE TABLE foo", + }, + }, + { + sql: "SELECT FROM db2.t2 where a=\"asd;\"; SELECT * FROM db1.t1;", + expect: []string{ + "SELECT FROM db2.t2 where a=\"asd;\";", + " SELECT * FROM db1.t1;", + }, + }, + { + sql: "SELECT * FROM db1.t1;OPTIMIZE TABLE foo;OPTIMIZE TABLE foo;SELECT * FROM db2.t2", + expect: []string{ + "SELECT * FROM db1.t1;", + "OPTIMIZE TABLE foo;", + "OPTIMIZE TABLE foo;", + "SELECT * FROM db2.t2", + }, + }, + { + sql: "OPTIMIZE TABLE foo;SELECT * FROM db1.t1;OPTIMIZE TABLE foo;SELECT * FROM db2.t2", + expect: []string{ + "OPTIMIZE TABLE foo;", + "SELECT * FROM db1.t1;", + "OPTIMIZE TABLE foo;", + "SELECT * FROM db2.t2", + }, + }, + { + sql: "SELECT * FROM db1.t1;OPTIMIZE TABLE foo;SELECT * FROM db2.t2;OPTIMIZE TABLE foo", + expect: []string{ + "SELECT * FROM db1.t1;", + "OPTIMIZE TABLE foo;", + "SELECT * FROM db2.t2;", + "OPTIMIZE TABLE foo", + }, + }, + { + sql: ` +CREATE PROCEDURE proc1(OUT s int) +BEGIN +END; +`, + expect: []string{ + ` +CREATE PROCEDURE proc1(OUT s int) +BEGIN +END;`, + }, + }, + { + sql: ` +CREATE PROCEDURE proc1(OUT s int) +BEGIN +SELECT COUNT(*) FROM user; +END; +`, + expect: []string{ + ` +CREATE PROCEDURE proc1(OUT s int) +BEGIN +SELECT COUNT(*) FROM user; +END;`, + }, + }, + { + sql: ` +CREATE PROCEDURE proc1(OUT s int) +BEGIN +SELECT COUNT(*) FROM user; +SELECT COUNT(*) FROM user; +END; +`, + expect: []string{ + ` +CREATE PROCEDURE proc1(OUT s int) +BEGIN +SELECT COUNT(*) FROM user; +SELECT COUNT(*) FROM user; +END;`, + }, + }, + { + sql: ` +SELECT * FROM db1.t1; +CREATE PROCEDURE proc1(OUT s int) +BEGIN +END; +`, + expect: []string{ + `SELECT * FROM db1.t1;`, + ` +CREATE PROCEDURE proc1(OUT s int) +BEGIN +END;`, + }, + }, + { + sql: ` +SELECT * FROM db1.t1; +CREATE PROCEDURE proc1(OUT s int) +BEGIN + SELECT COUNT(*) FROM user; + SELECT COUNT(*) FROM user; +END; +`, + expect: []string{ + `SELECT * FROM db1.t1;`, + ` +CREATE PROCEDURE proc1(OUT s int) +BEGIN + SELECT COUNT(*) FROM user; + SELECT COUNT(*) FROM user; +END;`, + }, + }, + { + sql: ` +CREATE PROCEDURE proc1(OUT s int) +BEGIN + SELECT COUNT(*) FROM user; + SELECT COUNT(*) FROM user; +END; +SELECT * FROM db1.t1; +`, + expect: []string{ + ` +CREATE PROCEDURE proc1(OUT s int) +BEGIN + SELECT COUNT(*) FROM user; + SELECT COUNT(*) FROM user; +END;`, + `SELECT * FROM db1.t1;`, + }, + }, + { + sql: ` +SELECT * FROM db1.t1; +CREATE PROCEDURE proc1(OUT s int) +BEGIN + SELECT COUNT(*) FROM user; + SELECT COUNT(*) FROM user; +END; +CREATE PROCEDURE proc1(OUT s int) +BEGIN + SELECT COUNT(*) FROM user; + SELECT COUNT(*) FROM user; + SELECT COUNT(*) FROM user; + SELECT COUNT(*) FROM user; +END; +SELECT * FROM db1.t1; +`, + expect: []string{ + `SELECT * FROM db1.t1;`, + ` +CREATE PROCEDURE proc1(OUT s int) +BEGIN + SELECT COUNT(*) FROM user; + SELECT COUNT(*) FROM user; +END;`, + ` +CREATE PROCEDURE proc1(OUT s int) +BEGIN + SELECT COUNT(*) FROM user; + SELECT COUNT(*) FROM user; + SELECT COUNT(*) FROM user; + SELECT COUNT(*) FROM user; +END;`, + `SELECT * FROM db1.t1;`, + }, + }, + { + sql: ` +SELECT * FROM db1.t1; +CREATE PROCEDURE proc1(OUT s int) +BEGIN + SELECT COUNT(*) FROM user; + SELECT COUNT(*) FROM user; +END; +SELECT * FROM db1.t1; +CREATE PROCEDURE proc1(OUT s int) +BEGIN + SELECT COUNT(*) FROM user; + SELECT COUNT(*) FROM user; + SELECT COUNT(*) FROM user; + SELECT COUNT(*) FROM user; +END; +SELECT * FROM db1.t1; +`, + expect: []string{ + `SELECT * FROM db1.t1;`, + ` +CREATE PROCEDURE proc1(OUT s int) +BEGIN + SELECT COUNT(*) FROM user; + SELECT COUNT(*) FROM user; +END;`, + `SELECT * FROM db1.t1;`, + ` +CREATE PROCEDURE proc1(OUT s int) +BEGIN + SELECT COUNT(*) FROM user; + SELECT COUNT(*) FROM user; + SELECT COUNT(*) FROM user; + SELECT COUNT(*) FROM user; +END;`, + `SELECT * FROM db1.t1;`, + }, + }, + { // 匹配特殊字符结束 + sql: "select * from �E", + expect: []string{ + `select * from �E`, + }, + }, + { // 匹配特殊字符后是; + sql: "select * from �E;select * from t1", + expect: []string{ + `select * from �E;`, + "select * from t1", + }, + }, + { // 匹配特殊字符在中间 + sql: "select * from �E where id = 1;select * from �E ", + expect: []string{ + `select * from �E where id = 1;`, + `select * from �E `, + }, + }, + { // 匹配特殊字符在开头 + sql: " where id = 1;select * from �E ", + expect: []string{ + ` where id = 1;`, + `select * from �E `, + }, + }, + { // 匹配特殊字符在SQL开头 + sql: "select * from �E ; where id = 1", + expect: []string{ + `select * from �E ;`, + ` where id = 1`, + }, + }, + { // 匹配其他invalid场景 + sql: "@`", + expect: []string{ + "@`", + }, + }, + { // 匹配其他invalid场景 + sql: "@` ;select * from t1", + expect: []string{ + "@` ;select * from t1", + }, + }, + } + for _, c := range tc { + stmt, err := parser.splitSqlText(c.sql) + if err != nil { + t.Error(err) + return + } + if len(c.expect) != len(stmt) { + t.Errorf("expect sql length is %d, actual is %d, sql is [%s]", len(c.expect), len(stmt), c.sql) + } else { + for i, s := range stmt { + // 之前的测试用例预期对SQL的切分会保留SQL语句的前后的空格 + // 现在的切分会将SQL前后的空格去掉 + // 这里统一修改为匹配SQL语句,除去分隔符后的内容是否相等 + if strings.TrimSuffix(s.originSql, ";") != strings.TrimSuffix(strings.TrimSpace(c.expect[i]), ";") { + t.Errorf("expect sql is [%s], actual is [%s]", c.expect[i], s.originSql) + } + } + } + } +} + +func TestCharset(t *testing.T) { + parser := NewSplitter() + type testCase struct { + sql string + formatSQL string + noError bool + errMsg string + } + + tc := []testCase{ + { + sql: `create table t1(id int, name varchar(255) CHARACTER SET armscii8)`, + formatSQL: `CREATE TABLE t1 (id INT,name VARCHAR(255) CHARACTER SET ARMSCII8)`, + noError: true, + }, + { + sql: `create table t1(id int, name varchar(255) CHARACTER SET armscii8 COLLATE armscii8_general_ci)`, + formatSQL: "CREATE TABLE t1 (id INT,name VARCHAR(255) CHARACTER SET ARMSCII8 COLLATE armscii8_general_ci)", + noError: true, + }, + { + sql: `create table t1(id int, name varchar(255)) DEFAULT CHARACTER SET armscii8`, + formatSQL: "CREATE TABLE t1 (id INT,name VARCHAR(255)) DEFAULT CHARACTER SET = ARMSCII8", + noError: true, + }, + { + sql: `create table t1(id int, name varchar(255)) DEFAULT CHARACTER SET armscii8 COLLATE greek_general_ci`, + formatSQL: "CREATE TABLE t1 (id INT,name VARCHAR(255)) DEFAULT CHARACTER SET = ARMSCII8 DEFAULT COLLATE = GREEK_GENERAL_CI", + noError: true, + }, + { + sql: `create table t1(id int, name varchar(255)) DEFAULT CHARACTER SET utf8mb3`, + formatSQL: "CREATE TABLE t1 (id INT,name VARCHAR(255)) DEFAULT CHARACTER SET = UTF8", + noError: true, + }, + { + sql: `create table t1(id int, name varchar(255)) DEFAULT CHARACTER SET utf8mb3 COLLATE utf8mb3_bin`, + formatSQL: "CREATE TABLE t1 (id INT,name VARCHAR(255)) DEFAULT CHARACTER SET = UTF8 DEFAULT COLLATE = UTF8_BIN", + noError: true, + }, + { + sql: `create table t1(id int, name varchar(255)) DEFAULT CHARACTER SET utf8 COLLATE utf8mb3_bin`, + formatSQL: "CREATE TABLE t1 (id INT,name VARCHAR(255)) DEFAULT CHARACTER SET = UTF8 DEFAULT COLLATE = UTF8_BIN", + noError: true, + }, + { + sql: `create table t1(id int, name varchar(255) CHARACTER SET utf8mb3)`, + formatSQL: "CREATE TABLE t1 (id INT,name VARCHAR(255) CHARACTER SET UTF8)", + noError: true, + }, + { + sql: `create table t1(id int, name varchar(255) CHARACTER SET utf8mb3 COLLATE cp852_general_ci)`, + formatSQL: "CREATE TABLE t1 (id INT,name VARCHAR(255) CHARACTER SET UTF8 COLLATE cp852_general_ci)", + noError: true, + }, + { + sql: `create table t1(id int, name varchar(255))default character set utf8mb3 COLLATE utf8mb3_unicode_ci;`, + formatSQL: "CREATE TABLE t1 (id INT,name VARCHAR(255)) DEFAULT CHARACTER SET = UTF8 DEFAULT COLLATE = UTF8_UNICODE_CI", + noError: true, + }, + { + sql: `create table t1(id int, name varchar(255))default character set utf8mb3 COLLATE big5_chinese_ci;`, + formatSQL: "CREATE TABLE t1 (id INT,name VARCHAR(255)) DEFAULT CHARACTER SET = UTF8 DEFAULT COLLATE = BIG5_CHINESE_CI", + noError: true, + }, + { + sql: `create table t1(id int, name varchar(255)) DEFAULT CHARACTER SET aaa`, + noError: false, + errMsg: "[parser:1115]Unknown character set: 'aaa'", + }, + { + sql: `create table t1(id int, name varchar(255)) DEFAULT CHARACTER SET utf8mb3 COLLATE bbb`, + noError: false, + errMsg: "[ddl:1273]Unknown collation: 'bbb'", + }, + + // 原生测试用例,预期从报错调整为不报错。 + { + sql: `create table t (a longtext unicode);`, + formatSQL: "CREATE TABLE t (a LONGTEXT CHARACTER SET UCS2)", + noError: true, + }, + { + sql: `create table t (a long byte, b text unicode);`, + formatSQL: "CREATE TABLE t (a MEDIUMTEXT,b TEXT CHARACTER SET UCS2)", + noError: true, + }, + { + sql: `create table t (a long ascii, b long unicode);`, + formatSQL: "CREATE TABLE t (a MEDIUMTEXT CHARACTER SET LATIN1,b MEDIUMTEXT CHARACTER SET UCS2)", + noError: true, + }, + { + sql: `create table t (a text unicode, b mediumtext ascii, c int);`, + formatSQL: "CREATE TABLE t (a TEXT CHARACTER SET UCS2,b MEDIUMTEXT CHARACTER SET LATIN1,c INT)", + noError: true, + }, + } + + for _, c := range tc { + stmts, err := parser.ParseSqlText(c.sql) + if err != nil { + if c.noError { + t.Error(err) + continue + } + // 现在不会报错,而是解析为为解析节点 + // if err.Error() != c.errMsg { + // t.Errorf("expect error message: %s; actual error message: %s", c.errMsg, err.Error()) + // continue + // } + if len(stmts) > 0 { + if _, ok := stmts[0].(*ast.UnparsedStmt); !ok { + t.Errorf("expect error message: %s; actual error message: %s", c.errMsg, err.Error()) + continue + } + } + continue + } else { + if !c.noError { + if _, ok := stmts[0].(*ast.UnparsedStmt); !ok { + t.Errorf("expect error message: %s; actual error message: %s", c.errMsg, err.Error()) + continue + } + // t.Errorf("expect need error, but no error") + continue + } + buf := new(bytes.Buffer) + restoreCtx := parser_formate.NewRestoreCtx(parser_formate.RestoreKeyWordUppercase, buf) + if len(stmts) > 0 { + err = stmts[0].Restore(restoreCtx) + if nil != err { + t.Error(err) + continue + } + if buf.String() != c.formatSQL { + t.Errorf("expect sql format: %s; actual sql format: %s", c.formatSQL, buf.String()) + } + } + } + } +} + +func TestGeometryColumn(t *testing.T) { + parser := NewSplitter() + type testCase struct { + sql string + formatSQL string + noError bool + errMsg string + } + + tc := []testCase{ + { + sql: `CREATE TABLE t (id INT PRIMARY KEY,g POINT)`, + formatSQL: `CREATE TABLE t (id INT PRIMARY KEY,g POINT)`, + noError: true, + }, + { + sql: `CREATE TABLE t (id INT PRIMARY KEY, g GEOMETRY)`, + formatSQL: `CREATE TABLE t (id INT PRIMARY KEY,g GEOMETRY)`, + noError: true, + }, + { + sql: `CREATE TABLE t (id INT PRIMARY KEY, g LINESTRING)`, + formatSQL: `CREATE TABLE t (id INT PRIMARY KEY,g LINESTRING)`, + noError: true, + }, + { + sql: `CREATE TABLE t (id INT PRIMARY KEY, g POLYGON)`, + formatSQL: `CREATE TABLE t (id INT PRIMARY KEY,g POLYGON)`, + noError: true, + }, + { + sql: `CREATE TABLE t (id INT PRIMARY KEY, g MULTIPOINT)`, + formatSQL: `CREATE TABLE t (id INT PRIMARY KEY,g MULTIPOINT)`, + noError: true, + }, + { + sql: `CREATE TABLE t (id INT PRIMARY KEY, g MULTILINESTRING)`, + formatSQL: `CREATE TABLE t (id INT PRIMARY KEY,g MULTILINESTRING)`, + noError: true, + }, + { + sql: `CREATE TABLE t (id INT PRIMARY KEY, g MULTIPOLYGON)`, + formatSQL: `CREATE TABLE t (id INT PRIMARY KEY,g MULTIPOLYGON)`, + noError: true, + }, + { + sql: `CREATE TABLE t (id INT PRIMARY KEY, g GEOMETRYCOLLECTION)`, + formatSQL: `CREATE TABLE t (id INT PRIMARY KEY,g GEOMETRYCOLLECTION)`, + noError: true, + }, + { + sql: `ALTER TABLE t ADD COLUMN g GEOMETRY`, + formatSQL: `ALTER TABLE t ADD COLUMN g GEOMETRY`, + noError: true, + }, + { + sql: `ALTER TABLE t ADD COLUMN g POINT`, + formatSQL: `ALTER TABLE t ADD COLUMN g POINT`, + noError: true, + }, + { + sql: `ALTER TABLE t ADD COLUMN g LINESTRING`, + formatSQL: `ALTER TABLE t ADD COLUMN g LINESTRING`, + noError: true, + }, + { + sql: `ALTER TABLE t ADD COLUMN g POLYGON`, + formatSQL: `ALTER TABLE t ADD COLUMN g POLYGON`, + noError: true, + }, + { + sql: `ALTER TABLE t ADD COLUMN g MULTIPOINT`, + formatSQL: `ALTER TABLE t ADD COLUMN g MULTIPOINT`, + noError: true, + }, + { + sql: `ALTER TABLE t ADD COLUMN g MULTILINESTRING`, + formatSQL: `ALTER TABLE t ADD COLUMN g MULTILINESTRING`, + noError: true, + }, + { + sql: `ALTER TABLE t ADD COLUMN g MULTIPOLYGON`, + formatSQL: `ALTER TABLE t ADD COLUMN g MULTIPOLYGON`, + noError: true, + }, + { + sql: `ALTER TABLE t ADD COLUMN g GEOMETRYCOLLECTION`, + formatSQL: `ALTER TABLE t ADD COLUMN g GEOMETRYCOLLECTION`, + noError: true, + }, + } + + for _, c := range tc { + stmts, err := parser.ParseSqlText(c.sql) + if err != nil { + if c.noError { + t.Error(err) + continue + } + if err.Error() != c.errMsg { + t.Errorf("expect error message: %s; actual error message: %s", c.errMsg, err.Error()) + continue + } + continue + } else { + if !c.noError { + t.Errorf("expect need error, but no error") + continue + } + buf := new(bytes.Buffer) + restoreCtx := parser_formate.NewRestoreCtx(parser_formate.RestoreKeyWordUppercase, buf) + if len(stmts) > 0 { + err = stmts[0].Restore(restoreCtx) + if nil != err { + t.Error(err) + continue + } + if buf.String() != c.formatSQL { + t.Errorf("expect sql format: %s; actual sql format: %s", c.formatSQL, buf.String()) + } + } + } + } +} + +func TestIndexConstraint(t *testing.T) { + parser := NewSplitter() + type testCase struct { + sql string + indexConstraint interface{} + } + tc := []testCase{ + { + sql: "CREATE TABLE t (id INT PRIMARY KEY, g POINT, SPATIAL INDEX(g))", + indexConstraint: ast.ConstraintSpatial, + }, + { + sql: "ALTER TABLE geom ADD SPATIAL INDEX(g)", + indexConstraint: ast.ConstraintSpatial, + }, + { + sql: "CREATE SPATIAL INDEX g ON geom (g)", + indexConstraint: ast.IndexKeyTypeSpatial, + }, + } + + for _, c := range tc { + isRight := false + stmt, err := parser.ParseSqlText(c.sql) + if err != nil { + t.Error(err) + continue + } else { + if len(stmt) == 0 { + t.Fatalf("result is empty") + } + switch stmt := stmt[0].(type) { + case *ast.CreateTableStmt: + indexConstraint, ok := c.indexConstraint.(ast.ConstraintType) + if !ok { + t.Errorf("sql: %s, indexConstraint is not ConstraintType", c.sql) + } + for _, constraint := range stmt.Constraints { + if constraint.Tp == indexConstraint { + isRight = true + } + } + case *ast.AlterTableStmt: + indexConstraint, ok := c.indexConstraint.(ast.ConstraintType) + if !ok { + t.Errorf("sql: %s, indexConstraint is not ConstraintType", c.sql) + } + for _, spec := range stmt.Specs { + if spec.Tp != ast.AlterTableAddConstraint || spec.Constraint == nil { + continue + } + if spec.Constraint.Tp == indexConstraint { + isRight = true + } + } + case *ast.CreateIndexStmt: + indexKey, ok := c.indexConstraint.(ast.IndexKeyType) + if !ok { + t.Errorf("sql: %s, indexConstraint is not indexKey", c.sql) + } + if stmt.KeyType == indexKey { + isRight = true + } + } + } + if !isRight { + t.Errorf("sql: %s, do not get expect indexConstraint: %v", c.sql, c.indexConstraint) + } + } +} + +func TestGeometryColumnIsNotReserved(t *testing.T) { + parser := NewSplitter() + type testCase struct { + sql string + formatSQL string + noError bool + errMsg string + } + + tc := []testCase{ + // point + { + sql: `CREATE TABLE t (id INT PRIMARY KEY,point INT(8) NOT NULL)`, + formatSQL: `CREATE TABLE t (id INT PRIMARY KEY,point INT(8) NOT NULL)`, + noError: true, + }, + { + sql: `SELECT point FROM t`, + formatSQL: `SELECT point FROM t`, + noError: true, + }, + { + sql: `INSERT INTO t (point) VALUES (1)`, + formatSQL: `INSERT INTO t (point) VALUES (1)`, + noError: true, + }, + { + sql: `UPDATE t SET point=1`, + formatSQL: `UPDATE t SET point=1`, + noError: true, + }, + { + sql: `DELETE FROM t WHERE point=1`, + formatSQL: `DELETE FROM t WHERE point=1`, + noError: true, + }, + // geometry + { + sql: `CREATE TABLE t (id INT PRIMARY KEY,geometry INT(8) NOT NULL)`, + formatSQL: `CREATE TABLE t (id INT PRIMARY KEY,geometry INT(8) NOT NULL)`, + noError: true, + }, + { + sql: `SELECT geometry FROM t`, + formatSQL: `SELECT geometry FROM t`, + noError: true, + }, + { + sql: `INSERT INTO t (geometry) VALUES (1)`, + formatSQL: `INSERT INTO t (geometry) VALUES (1)`, + noError: true, + }, + { + sql: `UPDATE t SET geometry=1`, + formatSQL: `UPDATE t SET geometry=1`, + noError: true, + }, + { + sql: `DELETE FROM t WHERE geometry=1`, + formatSQL: `DELETE FROM t WHERE geometry=1`, + noError: true, + }, + // LINESTRING + { + sql: `CREATE TABLE t (id INT PRIMARY KEY,linestring INT(8) NOT NULL)`, + formatSQL: `CREATE TABLE t (id INT PRIMARY KEY,linestring INT(8) NOT NULL)`, + noError: true, + }, + { + sql: `SELECT linestring FROM t`, + formatSQL: `SELECT linestring FROM t`, + noError: true, + }, + { + sql: `INSERT INTO t (linestring) VALUES (1)`, + formatSQL: `INSERT INTO t (linestring) VALUES (1)`, + noError: true, + }, + { + sql: `UPDATE t SET linestring=1`, + formatSQL: `UPDATE t SET linestring=1`, + noError: true, + }, + { + sql: `DELETE FROM t WHERE linestring=1`, + formatSQL: `DELETE FROM t WHERE linestring=1`, + noError: true, + }, + // POLYGON + { + sql: `CREATE TABLE t (id INT PRIMARY KEY,polygon INT(8) NOT NULL)`, + formatSQL: `CREATE TABLE t (id INT PRIMARY KEY,polygon INT(8) NOT NULL)`, + noError: true, + }, + { + sql: `SELECT polygon FROM t`, + formatSQL: `SELECT polygon FROM t`, + noError: true, + }, + { + sql: `INSERT INTO t (polygon) VALUES (1)`, + formatSQL: `INSERT INTO t (polygon) VALUES (1)`, + noError: true, + }, + { + sql: `UPDATE t SET polygon=1`, + formatSQL: `UPDATE t SET polygon=1`, + noError: true, + }, + { + sql: `DELETE FROM t WHERE polygon=1`, + formatSQL: `DELETE FROM t WHERE polygon=1`, + noError: true, + }, + // MULTIPOINT + { + sql: `CREATE TABLE t (id INT PRIMARY KEY,multipoint INT(8) NOT NULL)`, + formatSQL: `CREATE TABLE t (id INT PRIMARY KEY,multipoint INT(8) NOT NULL)`, + noError: true, + }, + { + sql: `SELECT multipoint FROM t`, + formatSQL: `SELECT multipoint FROM t`, + noError: true, + }, + { + sql: `INSERT INTO t (multipoint) VALUES (1)`, + formatSQL: `INSERT INTO t (multipoint) VALUES (1)`, + noError: true, + }, + { + sql: `UPDATE t SET multipoint=1`, + formatSQL: `UPDATE t SET multipoint=1`, + noError: true, + }, + { + sql: `DELETE FROM t WHERE multipoint=1`, + formatSQL: `DELETE FROM t WHERE multipoint=1`, + noError: true, + }, + // MULTILINESTRING + { + sql: `CREATE TABLE t (id INT PRIMARY KEY,multilinestring INT(8) NOT NULL)`, + formatSQL: `CREATE TABLE t (id INT PRIMARY KEY,multilinestring INT(8) NOT NULL)`, + noError: true, + }, + { + sql: `SELECT multilinestring FROM t`, + formatSQL: `SELECT multilinestring FROM t`, + noError: true, + }, + { + sql: `INSERT INTO t (multilinestring) VALUES (1)`, + formatSQL: `INSERT INTO t (multilinestring) VALUES (1)`, + noError: true, + }, + { + sql: `UPDATE t SET multilinestring=1`, + formatSQL: `UPDATE t SET multilinestring=1`, + noError: true, + }, + { + sql: `DELETE FROM t WHERE multilinestring=1`, + formatSQL: `DELETE FROM t WHERE multilinestring=1`, + noError: true, + }, + // MULTIPOLYGON + { + sql: `CREATE TABLE t (id INT PRIMARY KEY,multipolygon INT(8) NOT NULL)`, + formatSQL: `CREATE TABLE t (id INT PRIMARY KEY,multipolygon INT(8) NOT NULL)`, + noError: true, + }, + { + sql: `SELECT multipolygon FROM t`, + formatSQL: `SELECT multipolygon FROM t`, + noError: true, + }, + { + sql: `INSERT INTO t (multipolygon) VALUES (1)`, + formatSQL: `INSERT INTO t (multipolygon) VALUES (1)`, + noError: true, + }, + { + sql: `UPDATE t SET multipolygon=1`, + formatSQL: `UPDATE t SET multipolygon=1`, + noError: true, + }, + { + sql: `DELETE FROM t WHERE multipolygon=1`, + formatSQL: `DELETE FROM t WHERE multipolygon=1`, + noError: true, + }, + // GEOMETRYCOLLECTION + { + sql: `CREATE TABLE t (id INT PRIMARY KEY,geometrycollection INT(8) NOT NULL)`, + formatSQL: `CREATE TABLE t (id INT PRIMARY KEY,geometrycollection INT(8) NOT NULL)`, + noError: true, + }, + { + sql: `SELECT geometrycollection FROM t`, + formatSQL: `SELECT geometrycollection FROM t`, + noError: true, + }, + { + sql: `INSERT INTO t (geometrycollection) VALUES (1)`, + formatSQL: `INSERT INTO t (geometrycollection) VALUES (1)`, + noError: true, + }, + { + sql: `UPDATE t SET geometrycollection=1`, + formatSQL: `UPDATE t SET geometrycollection=1`, + noError: true, + }, + { + sql: `DELETE FROM t WHERE geometrycollection=1`, + formatSQL: `DELETE FROM t WHERE geometrycollection=1`, + noError: true, + }, + } + + for _, c := range tc { + stmt, err := parser.ParseSqlText(c.sql) + if len(stmt) == 0 { + t.Fatalf("result is empty") + } + if err != nil { + if c.noError { + t.Error(err) + continue + } + // 现在不会报错,而是解析为为解析节点 + // if err.Error() != c.errMsg { + // t.Errorf("expect error message: %s; actual error message: %s", c.errMsg, err.Error()) + // continue + // } + if _, ok := stmt[0].(*ast.UnparsedStmt); !ok { + t.Errorf("expect error message: %s; actual error message: %s", c.errMsg, err.Error()) + continue + } + // if err.Error() != c.errMsg { + // t.Errorf("expect error message: %s; actual error message: %s", c.errMsg, err.Error()) + // continue + // } + continue + } else { + if !c.noError { + // t.Errorf("expect need error, but no error") + if _, ok := stmt[0].(*ast.UnparsedStmt); !ok { + t.Errorf("expect error message: %s; actual error message: %s", c.errMsg, err.Error()) + continue + } + continue + } + buf := new(bytes.Buffer) + restoreCtx := parser_formate.NewRestoreCtx(parser_formate.RestoreKeyWordUppercase, buf) + + err = stmt[0].Restore(restoreCtx) + if nil != err { + t.Error(err) + continue + } + if buf.String() != c.formatSQL { + t.Errorf("expect sql format: %s; actual sql format: %s", c.formatSQL, buf.String()) + } + } + } +} diff --git a/sqle/driver/mysql/splitter/splitter_test_1.sql b/sqle/driver/mysql/splitter/splitter_test_1.sql new file mode 100644 index 0000000000..14120b706d --- /dev/null +++ b/sqle/driver/mysql/splitter/splitter_test_1.sql @@ -0,0 +1,61 @@ +/* DELIMITER str */ +/* 注释中定义分隔符 预期不会被检测为分隔符语法 */ +DELIMITER --good +-- DELIMITER str + +/* 分隔符在注释中,预期不被分割 */ +-- --good +/* --good */ +DROP PROCEDURE IF EXISTS `_GS_GM_Check` --good +/* 分隔符在SQL语句中或注释中,预期不会被分割 */ +/* 分隔符定义语法在SQL语句中,预期不会被识别 */ +CREATE DELIMITER = `--good`@`%` PROCEDURE `DELIMITER`(vi_uid INT,vi_pwd VARCHAR(32),vi_ip VARCHAR(100),OUT vo_level INT,OUT vo_code INT) +BEGIN +/* 分隔符与语句粘连,预期会被分割 */ + END--good +/* 切换分隔符 */ +DELIMITER ; +CREATE FUNCTION hello (s CHAR(20)); +CREATE FUNCTION hello (s CHAR(20)) ; +-- 切换分隔符 +DELIMITER // +use test // +/* 多条语句合并提交,预期切分结果会包含分隔符之间的多条sql */ +SET @sql = 'SELECT * FROM employees WHERE salary = 2321.21'; +SET @result = @sql; +PREPARE stmt FROM @result; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; +CREATE DELIMITER = `//`@`%` PROCEDURE `DELIMITER`(vi_uid INT,vi_pwd VARCHAR(32),vi_ip VARCHAR(100),OUT vo_level INT,OUT vo_code INT) +BEGIN + -- 分隔符与语句粘连,预期会被分割 + END; +// +DELIMITER ; + +CREATE TABLE account (acct_num INT, amount DECIMAL(10,2)); /* ; */ + +CREATE TRIGGER ins_sum BEFORE /* ; */INSERT ON account + FOR EACH ROW SET @sum = @sum + NEW.amount; + +SET @sum = 0; + +INSERT INTO account VALUES(137,14.98)/* ; */,(141,1937.50),(97,-100.00);/* ; */ +SELECT @sum AS ';';/* ; */ +DROP TRIGGER test.ins_sum;/* ; */ +CREATE TRIGGER ins_transaction BEFORE INSERT ON account + /* ; */ FOR EACH ROW PRECEDES ins_sum + SET + @deposits = @deposits + IF(NEW.amount>0,NEW.amount,0), + @withdrawals = @withdrawals + IF(NEW.amount<0,-NEW.amount,0); +delimiter // +CREATE TRIGGER upd_check BEFORE UPDATE ON account + FOR EACH ROW + BEGIN + IF NEW.amount < 0 THEN + SET NEW.amount = 0; + ELSEIF NEW.amount > 100 THEN + SET NEW.amount = 100; + END IF; + END// +delimiter ; \ No newline at end of file diff --git a/sqle/driver/mysql/splitter/splitter_test_2.sql b/sqle/driver/mysql/splitter/splitter_test_2.sql new file mode 100644 index 0000000000..81983f57b9 --- /dev/null +++ b/sqle/driver/mysql/splitter/splitter_test_2.sql @@ -0,0 +1,61 @@ +/* \d str */ +/* 注释中定义分隔符 预期不会被检测为分隔符语法 */ +\d --good +-- \d str + +/* 分隔符在注释中,预期不被分割 */ +-- --good +/* --good */ +DROP PROCEDURE IF EXISTS `_GS_GM_Check` --good +/* 分隔符在SQL语句中或注释中,预期不会被分割 */ +/* 分隔符定义语法在SQL语句中,预期不会被识别 */ +CREATE \d = `--good`@`%` PROCEDURE `\d`(vi_uid INT,vi_pwd VARCHAR(32),vi_ip VARCHAR(100),OUT vo_level INT,OUT vo_code INT) +BEGIN +/* 分隔符与语句粘连,预期会被分割 */ + END--good +/* 切换分隔符 */ +\d ; +CREATE FUNCTION hello (s CHAR(20)); +CREATE FUNCTION hello (s CHAR(20)) ; +-- 切换分隔符 +\d // +use test // +/* 多条语句合并提交,预期切分结果会包含分隔符之间的多条sql */ +SET @sql = 'SELECT * FROM employees WHERE salary = 2321.21'; +SET @result = @sql; +PREPARE stmt FROM @result; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; +CREATE \d = `//`@`%` PROCEDURE `\d`(vi_uid INT,vi_pwd VARCHAR(32),vi_ip VARCHAR(100),OUT vo_level INT,OUT vo_code INT) +BEGIN + -- 分隔符与语句粘连,预期会被分割 + END; +// +\d ; + +CREATE TABLE account (acct_num INT, amount DECIMAL(10,2)); /* ; */ + +CREATE TRIGGER ins_sum BEFORE /* ; */INSERT ON account + FOR EACH ROW SET @sum = @sum + NEW.amount; + +SET @sum = 0; + +INSERT INTO account VALUES(137,14.98)/* ; */,(141,1937.50),(97,-100.00);/* ; */ +SELECT @sum AS ';';/* ; */ +DROP TRIGGER test.ins_sum;/* ; */ +CREATE TRIGGER ins_transaction BEFORE INSERT ON account + /* ; */ FOR EACH ROW PRECEDES ins_sum + SET + @deposits = @deposits + IF(NEW.amount>0,NEW.amount,0), + @withdrawals = @withdrawals + IF(NEW.amount<0,-NEW.amount,0); +delimiter // +CREATE TRIGGER upd_check BEFORE UPDATE ON account + FOR EACH ROW + BEGIN + IF NEW.amount < 0 THEN + SET NEW.amount = 0; + ELSEIF NEW.amount > 100 THEN + SET NEW.amount = 100; + END IF; + END// +delimiter ; \ No newline at end of file diff --git a/sqle/driver/mysql/splitter/splitter_test_3.sql b/sqle/driver/mysql/splitter/splitter_test_3.sql new file mode 100644 index 0000000000..3fdfd18216 --- /dev/null +++ b/sqle/driver/mysql/splitter/splitter_test_3.sql @@ -0,0 +1,83 @@ +-- 1 +CREATE PROCEDURE nested_example() +BEGIN + DECLARE v_outer_variable INT DEFAULT 0; + + -- 外层 BEGIN ... END 块 + BEGIN + DECLARE v_inner_variable INT DEFAULT 10; + + -- 内层 BEGIN ... END 块 + BEGIN + DECLARE v_nested_variable INT DEFAULT 20; + + IF v_outer_variable = 0 THEN + SET v_outer_variable = v_inner_variable + v_nested_variable; + END IF; + END; -- 内层块结束 + WHILE v1 > 0 DO + SET v1 = v1 - 1; + END WHILE; + IF v_outer_variable > 0 THEN + SET v_outer_variable = v_outer_variable * 2; + END IF; + END; -- 外层块结束 +END; +-- 2 +CREATE PROCEDURE example_procedure() +BEGIN + DECLARE v_variable INT DEFAULT 0; + + IF v_variable = 0 THEN + SET v_variable = 1; + ELSEIF v_variable = 1 THEN + SET v_variable = 2; + ELSE + SET v_variable = 0; + END IF; + + -- 其他处理语句 +END; +-- 3 +CREATE PROCEDURE doiterate(p1 INT) +BEGIN + label1: LOOP + SET p1 = p1 + 1; + IF p1 < 10 THEN + ITERATE label1; + END IF; + LEAVE label1; + END LOOP label1; + SET @x = p1; +END; +-- 4 +CREATE PROCEDURE complex_loops_example() +BEGIN + DECLARE v_counter INT DEFAULT 0; + DECLARE v_limit INT DEFAULT 5; + DECLARE v_sum INT DEFAULT 0; + + -- 使用 LOOP 结构 + loop_label: LOOP + SET v_counter = v_counter + 1; + + -- 使用 WHILE 结构 + WHILE v_counter <= v_limit DO + SET v_sum = v_sum + v_counter; + + -- 使用 REPEAT 结构 + REPEAT + SET v_counter = v_counter + 1; + UNTIL v_counter > v_limit + END REPEAT; + END WHILE; + + -- 退出 LOOP 的条件 + IF v_counter > v_limit THEN + LEAVE loop_label; + END IF; + END LOOP; + + -- 输出结果 + SELECT v_sum AS total_sum; +END; \ No newline at end of file diff --git a/sqle/driver/mysql/splitter/splitter_test_skip_quoted_delimiter.sql b/sqle/driver/mysql/splitter/splitter_test_skip_quoted_delimiter.sql new file mode 100644 index 0000000000..e3081c7a3d --- /dev/null +++ b/sqle/driver/mysql/splitter/splitter_test_skip_quoted_delimiter.sql @@ -0,0 +1,26 @@ +delimiter abcd +select "abcd" abcd +select 'abcd' abcd +delimiter "'efgh'" +select 'abcd' 'efgh' +select "'abcd'" 'efgh' +delimiter "`abcd`" +select '`abcd`' `abcd` +select "`abcd`" `abcd` +delimiter `"efgh"` +select '"abcd"' "efgh" +select "abcd" "efgh" +delimiter `"abcd";` +select '"abcd";' "abcd"; +select '"aacd";' "abcd"; +delimiter "`efgh`;" +select "`abcd`;" `efgh`; +select '`abcd`;' `efgh`; +delimiter "'abcd';" +select "'abcd';" 'abcd'; +select "'abcd1';" 'abcd'; +delimiter ab +select "ab'abcd';" ab +select "ab'abbd';" ab +select 'abcd'; ab +select 'ab'; ab \ No newline at end of file diff --git a/sqle/driver/mysql/util/parser_helper.go b/sqle/driver/mysql/util/parser_helper.go index cb85f6679f..a06479e314 100644 --- a/sqle/driver/mysql/util/parser_helper.go +++ b/sqle/driver/mysql/util/parser_helper.go @@ -8,6 +8,7 @@ import ( "strconv" "strings" + "github.com/actiontech/sqle/sqle/driver/mysql/splitter" "github.com/pingcap/parser" "github.com/pingcap/parser/ast" "github.com/pingcap/parser/format" @@ -19,8 +20,7 @@ import ( ) func ParseSql(sql string) ([]ast.StmtNode, error) { - p := parser.New() - stmts, _, err := p.PerfectParse(sql, "", "") + stmts, err := splitter.NewSplitter().ParseSqlText(sql) if err != nil { return nil, err } diff --git a/sqle/driver/mysql/util/parser_helper_test.go b/sqle/driver/mysql/util/parser_helper_test.go index 854668c1a3..d28bc66fe0 100644 --- a/sqle/driver/mysql/util/parser_helper_test.go +++ b/sqle/driver/mysql/util/parser_helper_test.go @@ -189,6 +189,20 @@ func TestFingerprint(t *testing.T) { input: "select * from tb1 where a='my_db' and b='test1'# this is a comment", expect: "SELECT * FROM `tb1` WHERE `a`=? AND `b`=?", }, + // not sql + { + input: "SELECT*FROM (SELECT * FROM tb values(1));", + expect: "SELECT*FROM (SELECT * FROM tb values(1));", + }, + { + input: "not sql", + expect: "not sql", + }, + // https://github.com/actiontech/sqle/issues/2603 + { + input: "insert into tb values(1)", + expect: "INSERT INTO `tb` VALUES (?)", + }, } for _, c := range cases { testFingerprint(t, c.input, c.expect) diff --git a/sqle/driver/plugin_adapter_v1.go b/sqle/driver/plugin_adapter_v1.go index 89a7d64062..3a6033f2de 100644 --- a/sqle/driver/plugin_adapter_v1.go +++ b/sqle/driver/plugin_adapter_v1.go @@ -331,3 +331,11 @@ func (p *PluginImplV1) GetTableMetaBySQL(ctx context.Context, conf *GetTableMeta func (p *PluginImplV1) EstimateSQLAffectRows(ctx context.Context, sql string) (*driverV2.EstimatedAffectRows, error) { return nil, NewErrPluginAPINotImplement(driverV2.OptionalModuleEstimateSQLAffectRows) } + +func (s *PluginImplV1) GetDatabaseObjectDDL(ctx context.Context, objInfos []*driverV2.DatabasSchemaInfo) ([]*driverV2.DatabaseSchemaObjectResult, error) { + return nil, fmt.Errorf("unimplement this method") +} + +func (s *PluginImplV1) GetDatabaseDiffModifySQL(ctx context.Context, calibratedDSN *driverV2.DSN, objInfos []*driverV2.DatabasCompareSchemaInfo) ([]*driverV2.DatabaseDiffModifySQLResult, error) { + return nil, fmt.Errorf("unimplement this method") +} diff --git a/sqle/driver/plugin_adapter_v2.go b/sqle/driver/plugin_adapter_v2.go index ac1d8b0d17..e4ceefad59 100644 --- a/sqle/driver/plugin_adapter_v2.go +++ b/sqle/driver/plugin_adapter_v2.go @@ -576,3 +576,79 @@ func (s *dbDriverResult) RowsAffected() (int64, error) { } return s.rowsAffected, nil } + +func (s *PluginImplV2) GetDatabaseObjectDDL(ctx context.Context, objInfos []*driverV2.DatabasSchemaInfo) ([]*driverV2.DatabaseSchemaObjectResult, error) { + api := "GetDatabaseObjectDDL" + s.preLog(api) + dbInfoReq := make([]*protoV2.DatabasSchemaInfo, len(objInfos)) + for i, dbSchema := range objInfos { + dbObjs := make([]*protoV2.DatabaseObject, len(dbSchema.DatabaseObjects)) + for j, dbObj := range dbSchema.DatabaseObjects { + dbObjs[j] = &protoV2.DatabaseObject{ + ObjectName: dbObj.ObjectName, + ObjectType: dbObj.ObjectType, + } + } + dbInfoReq[i] = &protoV2.DatabasSchemaInfo{ + SchemaName: dbSchema.SchemaName, + DatabaseObject: dbObjs, + } + } + resp, err := s.client.GetDatabaseObjectDDL(ctx, &protoV2.DatabaseObjectInfoRequest{ + Session: s.Session, + DatabasSchemaInfo: dbInfoReq, + }) + s.afterLog(api, err) + if err != nil { + return nil, err + } + ret := make([]*driverV2.DatabaseSchemaObjectResult, len(resp.DatabaseSchemaObject)) + for i, info := range resp.DatabaseSchemaObject { + ObjDDL := make([]*driverV2.DatabaseObjectDDL, len(info.DatabaseObjectDDL)) + for j, obj := range info.DatabaseObjectDDL { + ObjDDL[j] = &driverV2.DatabaseObjectDDL{ + DatabaseObject: &driverV2.DatabaseObject{ + ObjectName: obj.DatabaseObject.ObjectName, + ObjectType: obj.DatabaseObject.ObjectType, + }, + ObjectDDL: obj.ObjectDDL, + } + } + ret[i] = &driverV2.DatabaseSchemaObjectResult{ + SchemaName: info.SchemaName, + SchemaDDL: info.SchemaDDL, + DatabaseObjectDDLs: ObjDDL, + } + } + return ret, nil +} + +func (s *PluginImplV2) GetDatabaseDiffModifySQL(ctx context.Context, calibratedDSN *driverV2.DSN, objInfos []*driverV2.DatabasCompareSchemaInfo) ([]*driverV2.DatabaseDiffModifySQLResult, error) { + api := "GetDatabaseDiffModifySQL" + s.preLog(api) + resp, err := s.client.GetDatabaseDiffModifySQL(ctx, &protoV2.DatabaseDiffModifyRequest{ + Session: s.Session, + CalibratedDSN: &protoV2.DSN{ + Host: calibratedDSN.Host, + Port: calibratedDSN.Port, + User: calibratedDSN.User, + Password: calibratedDSN.Password, + AdditionalParams: driverV2.ConvertParamToProtoParam(calibratedDSN.AdditionalParams), + Database: calibratedDSN.DatabaseName, + }, + ObjInfos: driverV2.ConvertDatabasSchemaInfoToProto(objInfos), + }) + s.afterLog(api, err) + if err != nil { + return nil, err + } + + dbDiffSQLs := make([]*driverV2.DatabaseDiffModifySQLResult, len(resp.SchemaDiffModify)) + for i, schemaDiff := range resp.SchemaDiffModify { + dbDiffSQLs[i] = &driverV2.DatabaseDiffModifySQLResult{ + SchemaName: schemaDiff.SchemaName, + ModifySQLs: schemaDiff.ModifySQLs, + } + } + return dbDiffSQLs, nil +} diff --git a/sqle/driver/plugin_interface.go b/sqle/driver/plugin_interface.go index 31fba5447b..28d22fd1ee 100644 --- a/sqle/driver/plugin_interface.go +++ b/sqle/driver/plugin_interface.go @@ -42,6 +42,10 @@ type Plugin interface { // Introduced from v2.2304.0 EstimateSQLAffectRows(ctx context.Context, sql string) (*driverV2.EstimatedAffectRows, error) + + GetDatabaseObjectDDL(ctx context.Context, objInfos []*driverV2.DatabasSchemaInfo) ([]*driverV2.DatabaseSchemaObjectResult, error) + + GetDatabaseDiffModifySQL(ctx context.Context, calibratedDSN *driverV2.DSN, objInfos []*driverV2.DatabasCompareSchemaInfo) ([]*driverV2.DatabaseDiffModifySQLResult, error) } type PluginProcessor interface { diff --git a/sqle/driver/v2/driver_grpc_server.go b/sqle/driver/v2/driver_grpc_server.go index 7c61f327c9..d6ede6c12d 100644 --- a/sqle/driver/v2/driver_grpc_server.go +++ b/sqle/driver/v2/driver_grpc_server.go @@ -526,3 +526,83 @@ func (d *DriverGrpcServer) KillProcess(ctx context.Context, req *protoV2.KillPro ErrMessage: info.ErrMessage, }, nil } + +func (d *DriverGrpcServer) GetDatabaseObjectDDL(ctx context.Context, req *protoV2.DatabaseObjectInfoRequest) (*protoV2.DatabaseSchemaObjectResponse, error) { + driver, err := d.getDriverBySession(req.Session) + if err != nil { + return &protoV2.DatabaseSchemaObjectResponse{}, err + } + dbInfoReq := make([]*DatabasSchemaInfo, len(req.DatabasSchemaInfo)) + for i, dbSchema := range req.DatabasSchemaInfo { + dbObjs := make([]*DatabaseObject, len(dbSchema.DatabaseObject)) + for j, dbObj := range dbSchema.DatabaseObject { + dbObjs[j] = &DatabaseObject{ + ObjectName: dbObj.ObjectName, + ObjectType: dbObj.ObjectType, + } + } + dbInfoReq[i] = &DatabasSchemaInfo{ + SchemaName: dbSchema.SchemaName, + DatabaseObjects: dbObjs, + } + } + infos, err := driver.GetDatabaseObjectDDL(ctx, dbInfoReq) + if err != nil { + return &protoV2.DatabaseSchemaObjectResponse{}, err + } + ret := make([]*protoV2.DatabaseSchemaObject, len(infos)) + for i, info := range infos { + ObjDDL := make([]*protoV2.DatabaseObjectDDL, len(info.DatabaseObjectDDLs)) + for j, obj := range info.DatabaseObjectDDLs { + ObjDDL[j] = &protoV2.DatabaseObjectDDL{ + DatabaseObject: &protoV2.DatabaseObject{ + ObjectName: obj.DatabaseObject.ObjectName, + ObjectType: obj.DatabaseObject.ObjectType, + }, + ObjectDDL: obj.ObjectDDL, + } + } + ret[i] = &protoV2.DatabaseSchemaObject{ + SchemaName: info.SchemaName, + SchemaDDL: info.SchemaDDL, + DatabaseObjectDDL: ObjDDL, + } + } + return &protoV2.DatabaseSchemaObjectResponse{ + DatabaseSchemaObject: ret, + }, nil +} + +func (d *DriverGrpcServer) GetDatabaseDiffModifySQL(ctx context.Context, req *protoV2.DatabaseDiffModifyRequest) (*protoV2.DatabaseDiffModifyRponse, error) { + driver, err := d.getDriverBySession(req.Session) + if err != nil { + return &protoV2.DatabaseDiffModifyRponse{}, err + } + params, err := ConvertProtoParamToParam(req.CalibratedDSN.AdditionalParams) + if err != nil { + return nil, fmt.Errorf("DriverGrpcServer Init req rule param err: %w", err) + } + + infos, err := driver.GetDatabaseDiffModifySQL(ctx, &DSN{ + Host: req.CalibratedDSN.Host, + Port: req.CalibratedDSN.Port, + User: req.CalibratedDSN.User, + Password: req.CalibratedDSN.Password, + AdditionalParams: params, + DatabaseName: req.CalibratedDSN.Database, + }, + ConvertProtoDatabaseDiffReqToDriver(req.ObjInfos)) + if err != nil { + return &protoV2.DatabaseDiffModifyRponse{}, err + } + scheamDiff := make([]*protoV2.SchemaDiffModify, len(infos)) + for i, info := range infos { + scheamDiff[i] = &protoV2.SchemaDiffModify{ + SchemaName: info.SchemaName, + ModifySQLs: info.ModifySQLs, + } + } + return &protoV2.DatabaseDiffModifyRponse{ + SchemaDiffModify: scheamDiff, + }, nil +} diff --git a/sqle/driver/v2/driver_interface.go b/sqle/driver/v2/driver_interface.go index 366056d61f..9ee65446bd 100644 --- a/sqle/driver/v2/driver_interface.go +++ b/sqle/driver/v2/driver_interface.go @@ -91,6 +91,8 @@ type Driver interface { ExtractTableFromSQL(ctx context.Context, sql string) ([]*Table, error) EstimateSQLAffectRows(ctx context.Context, sql string) (*EstimatedAffectRows, error) KillProcess(ctx context.Context) (*KillProcessInfo, error) + GetDatabaseObjectDDL(ctx context.Context, objInfos []*DatabasSchemaInfo) ([]*DatabaseSchemaObjectResult, error) + GetDatabaseDiffModifySQL(ctx context.Context, calibratedDSN *DSN, objInfos []*DatabasCompareSchemaInfo) ([]*DatabaseDiffModifySQLResult, error) } type Node struct { @@ -340,3 +342,42 @@ func NewKillProcessInfo(errorMessage string) *KillProcessInfo { type RuleKnowledge struct { Content string } + +type DatabasSchemaInfo struct { + SchemaName string + DatabaseObjects []*DatabaseObject +} + +type DatabasCompareSchemaInfo struct { + BaseSchemaName string + ComparedSchemaName string + DatabaseObjects []*DatabaseObject +} + +const ( + ObjectType_TABLE string = "TABLE" + ObjectType_VIEW string = "VIEW" + ObjectType_PROCEDURE string = "PROCEDURE" + ObjectType_TRIGGER string = "TRIGGER" + ObjectType_EVENT string = "EVENT" + ObjectType_FUNCTION string = "FUNCTION" +) + +type DatabaseObject struct { + ObjectName string + ObjectType string +} +type DatabaseSchemaObjectResult struct { + SchemaName string + SchemaDDL string + DatabaseObjectDDLs []*DatabaseObjectDDL +} +type DatabaseObjectDDL struct { + DatabaseObject *DatabaseObject + ObjectDDL string +} + +type DatabaseDiffModifySQLResult struct { + SchemaName string + ModifySQLs []string +} diff --git a/sqle/driver/v2/proto/driver_v2.pb.go b/sqle/driver/v2/proto/driver_v2.pb.go index ae437080d1..cfb8ba253b 100644 --- a/sqle/driver/v2/proto/driver_v2.pb.go +++ b/sqle/driver/v2/proto/driver_v2.pb.go @@ -5,11 +5,9 @@ Package protoV2 is a generated protocol buffer package. It is generated from these files: - driver_v2.proto It has these top-level messages: - Empty Session Param @@ -75,6 +73,16 @@ It has these top-level messages: EstimateSQLAffectRowsRequest EstimateSQLAffectRowsResponse KillProcessResponse + DatabaseObjectInfoRequest + DatabasSchemaInfo + DatabaseObject + DatabaseSchemaObjectResponse + DatabaseSchemaObject + DatabaseObjectDDL + DatabaseDiffModifyRequest + DatabasDiffSchemaInfo + DatabaseDiffModifyRponse + SchemaDiffModify */ package protoV2 @@ -101,38 +109,44 @@ const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package type OptionalModule int32 const ( - OptionalModule_GenRollbackSQL OptionalModule = 0 - OptionalModule_Query OptionalModule = 1 - OptionalModule_Explain OptionalModule = 2 - OptionalModule_GetTableMeta OptionalModule = 3 - OptionalModule_ExtractTableFromSQL OptionalModule = 4 - OptionalModule_EstimateSQLAffectRows OptionalModule = 5 - OptionalModule_KillProcess OptionalModule = 6 - OptionalModule_ExecBatch OptionalModule = 7 - OptionalModule_I18n OptionalModule = 8 + OptionalModule_GenRollbackSQL OptionalModule = 0 + OptionalModule_Query OptionalModule = 1 + OptionalModule_Explain OptionalModule = 2 + OptionalModule_GetTableMeta OptionalModule = 3 + OptionalModule_ExtractTableFromSQL OptionalModule = 4 + OptionalModule_EstimateSQLAffectRows OptionalModule = 5 + OptionalModule_KillProcess OptionalModule = 6 + OptionalModule_ExecBatch OptionalModule = 7 + OptionalModule_I18n OptionalModule = 8 + OptionalModule_GetDatabaseObjectDDL OptionalModule = 9 + OptionalModule_GetDatabaseDiffModifySQL OptionalModule = 10 ) var OptionalModule_name = map[int32]string{ - 0: "GenRollbackSQL", - 1: "Query", - 2: "Explain", - 3: "GetTableMeta", - 4: "ExtractTableFromSQL", - 5: "EstimateSQLAffectRows", - 6: "KillProcess", - 7: "ExecBatch", - 8: "I18n", + 0: "GenRollbackSQL", + 1: "Query", + 2: "Explain", + 3: "GetTableMeta", + 4: "ExtractTableFromSQL", + 5: "EstimateSQLAffectRows", + 6: "KillProcess", + 7: "ExecBatch", + 8: "I18n", + 9: "GetDatabaseObjectDDL", + 10: "GetDatabaseDiffModifySQL", } var OptionalModule_value = map[string]int32{ - "GenRollbackSQL": 0, - "Query": 1, - "Explain": 2, - "GetTableMeta": 3, - "ExtractTableFromSQL": 4, - "EstimateSQLAffectRows": 5, - "KillProcess": 6, - "ExecBatch": 7, - "I18n": 8, + "GenRollbackSQL": 0, + "Query": 1, + "Explain": 2, + "GetTableMeta": 3, + "ExtractTableFromSQL": 4, + "EstimateSQLAffectRows": 5, + "KillProcess": 6, + "ExecBatch": 7, + "I18n": 8, + "GetDatabaseObjectDDL": 9, + "GetDatabaseDiffModifySQL": 10, } func (x OptionalModule) String() string { @@ -1652,6 +1666,254 @@ func (m *KillProcessResponse) GetErrMessage() string { return "" } +type DatabaseObjectInfoRequest struct { + Session *Session `protobuf:"bytes,1,opt,name=session" json:"session,omitempty"` + DatabasSchemaInfo []*DatabasSchemaInfo `protobuf:"bytes,2,rep,name=databasSchemaInfo" json:"databasSchemaInfo,omitempty"` +} + +func (m *DatabaseObjectInfoRequest) Reset() { *m = DatabaseObjectInfoRequest{} } +func (m *DatabaseObjectInfoRequest) String() string { return proto.CompactTextString(m) } +func (*DatabaseObjectInfoRequest) ProtoMessage() {} +func (*DatabaseObjectInfoRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{65} } + +func (m *DatabaseObjectInfoRequest) GetSession() *Session { + if m != nil { + return m.Session + } + return nil +} + +func (m *DatabaseObjectInfoRequest) GetDatabasSchemaInfo() []*DatabasSchemaInfo { + if m != nil { + return m.DatabasSchemaInfo + } + return nil +} + +type DatabasSchemaInfo struct { + SchemaName string `protobuf:"bytes,1,opt,name=schemaName" json:"schemaName,omitempty"` + DatabaseObject []*DatabaseObject `protobuf:"bytes,2,rep,name=databaseObject" json:"databaseObject,omitempty"` +} + +func (m *DatabasSchemaInfo) Reset() { *m = DatabasSchemaInfo{} } +func (m *DatabasSchemaInfo) String() string { return proto.CompactTextString(m) } +func (*DatabasSchemaInfo) ProtoMessage() {} +func (*DatabasSchemaInfo) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{66} } + +func (m *DatabasSchemaInfo) GetSchemaName() string { + if m != nil { + return m.SchemaName + } + return "" +} + +func (m *DatabasSchemaInfo) GetDatabaseObject() []*DatabaseObject { + if m != nil { + return m.DatabaseObject + } + return nil +} + +type DatabaseObject struct { + ObjectName string `protobuf:"bytes,1,opt,name=objectName" json:"objectName,omitempty"` + ObjectType string `protobuf:"bytes,2,opt,name=objectType" json:"objectType,omitempty"` +} + +func (m *DatabaseObject) Reset() { *m = DatabaseObject{} } +func (m *DatabaseObject) String() string { return proto.CompactTextString(m) } +func (*DatabaseObject) ProtoMessage() {} +func (*DatabaseObject) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{67} } + +func (m *DatabaseObject) GetObjectName() string { + if m != nil { + return m.ObjectName + } + return "" +} + +func (m *DatabaseObject) GetObjectType() string { + if m != nil { + return m.ObjectType + } + return "" +} + +type DatabaseSchemaObjectResponse struct { + DatabaseSchemaObject []*DatabaseSchemaObject `protobuf:"bytes,1,rep,name=databaseSchemaObject" json:"databaseSchemaObject,omitempty"` +} + +func (m *DatabaseSchemaObjectResponse) Reset() { *m = DatabaseSchemaObjectResponse{} } +func (m *DatabaseSchemaObjectResponse) String() string { return proto.CompactTextString(m) } +func (*DatabaseSchemaObjectResponse) ProtoMessage() {} +func (*DatabaseSchemaObjectResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{68} } + +func (m *DatabaseSchemaObjectResponse) GetDatabaseSchemaObject() []*DatabaseSchemaObject { + if m != nil { + return m.DatabaseSchemaObject + } + return nil +} + +type DatabaseSchemaObject struct { + SchemaName string `protobuf:"bytes,1,opt,name=schemaName" json:"schemaName,omitempty"` + SchemaDDL string `protobuf:"bytes,2,opt,name=schemaDDL" json:"schemaDDL,omitempty"` + DatabaseObjectDDL []*DatabaseObjectDDL `protobuf:"bytes,3,rep,name=databaseObjectDDL" json:"databaseObjectDDL,omitempty"` +} + +func (m *DatabaseSchemaObject) Reset() { *m = DatabaseSchemaObject{} } +func (m *DatabaseSchemaObject) String() string { return proto.CompactTextString(m) } +func (*DatabaseSchemaObject) ProtoMessage() {} +func (*DatabaseSchemaObject) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{69} } + +func (m *DatabaseSchemaObject) GetSchemaName() string { + if m != nil { + return m.SchemaName + } + return "" +} + +func (m *DatabaseSchemaObject) GetSchemaDDL() string { + if m != nil { + return m.SchemaDDL + } + return "" +} + +func (m *DatabaseSchemaObject) GetDatabaseObjectDDL() []*DatabaseObjectDDL { + if m != nil { + return m.DatabaseObjectDDL + } + return nil +} + +type DatabaseObjectDDL struct { + DatabaseObject *DatabaseObject `protobuf:"bytes,1,opt,name=databaseObject" json:"databaseObject,omitempty"` + ObjectDDL string `protobuf:"bytes,2,opt,name=objectDDL" json:"objectDDL,omitempty"` +} + +func (m *DatabaseObjectDDL) Reset() { *m = DatabaseObjectDDL{} } +func (m *DatabaseObjectDDL) String() string { return proto.CompactTextString(m) } +func (*DatabaseObjectDDL) ProtoMessage() {} +func (*DatabaseObjectDDL) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{70} } + +func (m *DatabaseObjectDDL) GetDatabaseObject() *DatabaseObject { + if m != nil { + return m.DatabaseObject + } + return nil +} + +func (m *DatabaseObjectDDL) GetObjectDDL() string { + if m != nil { + return m.ObjectDDL + } + return "" +} + +type DatabaseDiffModifyRequest struct { + Session *Session `protobuf:"bytes,1,opt,name=session" json:"session,omitempty"` + CalibratedDSN *DSN `protobuf:"bytes,2,opt,name=calibratedDSN" json:"calibratedDSN,omitempty"` + ObjInfos []*DatabasDiffSchemaInfo `protobuf:"bytes,3,rep,name=objInfos" json:"objInfos,omitempty"` +} + +func (m *DatabaseDiffModifyRequest) Reset() { *m = DatabaseDiffModifyRequest{} } +func (m *DatabaseDiffModifyRequest) String() string { return proto.CompactTextString(m) } +func (*DatabaseDiffModifyRequest) ProtoMessage() {} +func (*DatabaseDiffModifyRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{71} } + +func (m *DatabaseDiffModifyRequest) GetSession() *Session { + if m != nil { + return m.Session + } + return nil +} + +func (m *DatabaseDiffModifyRequest) GetCalibratedDSN() *DSN { + if m != nil { + return m.CalibratedDSN + } + return nil +} + +func (m *DatabaseDiffModifyRequest) GetObjInfos() []*DatabasDiffSchemaInfo { + if m != nil { + return m.ObjInfos + } + return nil +} + +type DatabasDiffSchemaInfo struct { + BaseSchemaName string `protobuf:"bytes,1,opt,name=baseSchemaName" json:"baseSchemaName,omitempty"` + ComparedSchemaName string `protobuf:"bytes,2,opt,name=comparedSchemaName" json:"comparedSchemaName,omitempty"` + DatabaseObject []*DatabaseObject `protobuf:"bytes,3,rep,name=databaseObject" json:"databaseObject,omitempty"` +} + +func (m *DatabasDiffSchemaInfo) Reset() { *m = DatabasDiffSchemaInfo{} } +func (m *DatabasDiffSchemaInfo) String() string { return proto.CompactTextString(m) } +func (*DatabasDiffSchemaInfo) ProtoMessage() {} +func (*DatabasDiffSchemaInfo) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{72} } + +func (m *DatabasDiffSchemaInfo) GetBaseSchemaName() string { + if m != nil { + return m.BaseSchemaName + } + return "" +} + +func (m *DatabasDiffSchemaInfo) GetComparedSchemaName() string { + if m != nil { + return m.ComparedSchemaName + } + return "" +} + +func (m *DatabasDiffSchemaInfo) GetDatabaseObject() []*DatabaseObject { + if m != nil { + return m.DatabaseObject + } + return nil +} + +type DatabaseDiffModifyRponse struct { + SchemaDiffModify []*SchemaDiffModify `protobuf:"bytes,1,rep,name=schemaDiffModify" json:"schemaDiffModify,omitempty"` +} + +func (m *DatabaseDiffModifyRponse) Reset() { *m = DatabaseDiffModifyRponse{} } +func (m *DatabaseDiffModifyRponse) String() string { return proto.CompactTextString(m) } +func (*DatabaseDiffModifyRponse) ProtoMessage() {} +func (*DatabaseDiffModifyRponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{73} } + +func (m *DatabaseDiffModifyRponse) GetSchemaDiffModify() []*SchemaDiffModify { + if m != nil { + return m.SchemaDiffModify + } + return nil +} + +type SchemaDiffModify struct { + SchemaName string `protobuf:"bytes,1,opt,name=schemaName" json:"schemaName,omitempty"` + ModifySQLs []string `protobuf:"bytes,2,rep,name=modifySQLs" json:"modifySQLs,omitempty"` +} + +func (m *SchemaDiffModify) Reset() { *m = SchemaDiffModify{} } +func (m *SchemaDiffModify) String() string { return proto.CompactTextString(m) } +func (*SchemaDiffModify) ProtoMessage() {} +func (*SchemaDiffModify) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{74} } + +func (m *SchemaDiffModify) GetSchemaName() string { + if m != nil { + return m.SchemaName + } + return "" +} + +func (m *SchemaDiffModify) GetModifySQLs() []string { + if m != nil { + return m.ModifySQLs + } + return nil +} + func init() { proto.RegisterType((*Empty)(nil), "protoV2.Empty") proto.RegisterType((*Session)(nil), "protoV2.Session") @@ -1718,6 +1980,16 @@ func init() { proto.RegisterType((*EstimateSQLAffectRowsRequest)(nil), "protoV2.EstimateSQLAffectRowsRequest") proto.RegisterType((*EstimateSQLAffectRowsResponse)(nil), "protoV2.EstimateSQLAffectRowsResponse") proto.RegisterType((*KillProcessResponse)(nil), "protoV2.KillProcessResponse") + proto.RegisterType((*DatabaseObjectInfoRequest)(nil), "protoV2.DatabaseObjectInfoRequest") + proto.RegisterType((*DatabasSchemaInfo)(nil), "protoV2.DatabasSchemaInfo") + proto.RegisterType((*DatabaseObject)(nil), "protoV2.DatabaseObject") + proto.RegisterType((*DatabaseSchemaObjectResponse)(nil), "protoV2.DatabaseSchemaObjectResponse") + proto.RegisterType((*DatabaseSchemaObject)(nil), "protoV2.DatabaseSchemaObject") + proto.RegisterType((*DatabaseObjectDDL)(nil), "protoV2.DatabaseObjectDDL") + proto.RegisterType((*DatabaseDiffModifyRequest)(nil), "protoV2.DatabaseDiffModifyRequest") + proto.RegisterType((*DatabasDiffSchemaInfo)(nil), "protoV2.DatabasDiffSchemaInfo") + proto.RegisterType((*DatabaseDiffModifyRponse)(nil), "protoV2.DatabaseDiffModifyRponse") + proto.RegisterType((*SchemaDiffModify)(nil), "protoV2.SchemaDiffModify") proto.RegisterEnum("protoV2.OptionalModule", OptionalModule_name, OptionalModule_value) } @@ -1757,6 +2029,8 @@ type DriverClient interface { GetTableMeta(ctx context.Context, in *GetTableMetaRequest, opts ...grpc.CallOption) (*GetTableMetaResponse, error) ExtractTableFromSQL(ctx context.Context, in *ExtractTableFromSQLRequest, opts ...grpc.CallOption) (*ExtractTableFromSQLResponse, error) EstimateSQLAffectRows(ctx context.Context, in *EstimateSQLAffectRowsRequest, opts ...grpc.CallOption) (*EstimateSQLAffectRowsResponse, error) + GetDatabaseObjectDDL(ctx context.Context, in *DatabaseObjectInfoRequest, opts ...grpc.CallOption) (*DatabaseSchemaObjectResponse, error) + GetDatabaseDiffModifySQL(ctx context.Context, in *DatabaseDiffModifyRequest, opts ...grpc.CallOption) (*DatabaseDiffModifyRponse, error) } type driverClient struct { @@ -1920,6 +2194,24 @@ func (c *driverClient) EstimateSQLAffectRows(ctx context.Context, in *EstimateSQ return out, nil } +func (c *driverClient) GetDatabaseObjectDDL(ctx context.Context, in *DatabaseObjectInfoRequest, opts ...grpc.CallOption) (*DatabaseSchemaObjectResponse, error) { + out := new(DatabaseSchemaObjectResponse) + err := grpc.Invoke(ctx, "/protoV2.Driver/GetDatabaseObjectDDL", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *driverClient) GetDatabaseDiffModifySQL(ctx context.Context, in *DatabaseDiffModifyRequest, opts ...grpc.CallOption) (*DatabaseDiffModifyRponse, error) { + out := new(DatabaseDiffModifyRponse) + err := grpc.Invoke(ctx, "/protoV2.Driver/GetDatabaseDiffModifySQL", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // Server API for Driver service type DriverServer interface { @@ -1948,6 +2240,8 @@ type DriverServer interface { GetTableMeta(context.Context, *GetTableMetaRequest) (*GetTableMetaResponse, error) ExtractTableFromSQL(context.Context, *ExtractTableFromSQLRequest) (*ExtractTableFromSQLResponse, error) EstimateSQLAffectRows(context.Context, *EstimateSQLAffectRowsRequest) (*EstimateSQLAffectRowsResponse, error) + GetDatabaseObjectDDL(context.Context, *DatabaseObjectInfoRequest) (*DatabaseSchemaObjectResponse, error) + GetDatabaseDiffModifySQL(context.Context, *DatabaseDiffModifyRequest) (*DatabaseDiffModifyRponse, error) } func RegisterDriverServer(s *grpc.Server, srv DriverServer) { @@ -2260,6 +2554,42 @@ func _Driver_EstimateSQLAffectRows_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } +func _Driver_GetDatabaseObjectDDL_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DatabaseObjectInfoRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DriverServer).GetDatabaseObjectDDL(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protoV2.Driver/GetDatabaseObjectDDL", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DriverServer).GetDatabaseObjectDDL(ctx, req.(*DatabaseObjectInfoRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Driver_GetDatabaseDiffModifySQL_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DatabaseDiffModifyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DriverServer).GetDatabaseDiffModifySQL(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protoV2.Driver/GetDatabaseDiffModifySQL", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DriverServer).GetDatabaseDiffModifySQL(ctx, req.(*DatabaseDiffModifyRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _Driver_serviceDesc = grpc.ServiceDesc{ ServiceName: "protoV2.Driver", HandlerType: (*DriverServer)(nil), @@ -2332,6 +2662,14 @@ var _Driver_serviceDesc = grpc.ServiceDesc{ MethodName: "EstimateSQLAffectRows", Handler: _Driver_EstimateSQLAffectRows_Handler, }, + { + MethodName: "GetDatabaseObjectDDL", + Handler: _Driver_GetDatabaseObjectDDL_Handler, + }, + { + MethodName: "GetDatabaseDiffModifySQL", + Handler: _Driver_GetDatabaseDiffModifySQL_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "driver_v2.proto", @@ -2340,141 +2678,163 @@ var _Driver_serviceDesc = grpc.ServiceDesc{ func init() { proto.RegisterFile("driver_v2.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 2174 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x59, 0xcb, 0x73, 0x13, 0xc9, - 0x19, 0x8f, 0xde, 0xd2, 0x27, 0xc9, 0x88, 0xb6, 0x0d, 0x42, 0x18, 0x70, 0x1a, 0x30, 0x2e, 0x60, - 0x0d, 0x88, 0x84, 0x65, 0x21, 0x95, 0x32, 0xd8, 0x0e, 0x78, 0x01, 0xc7, 0xb4, 0x09, 0x87, 0x54, - 0x6d, 0xb1, 0x63, 0x4d, 0xdb, 0x4c, 0x31, 0x9a, 0x91, 0xa7, 0x5b, 0xd8, 0xbe, 0xa7, 0x2a, 0x39, - 0xe6, 0x5f, 0xc8, 0x3d, 0x87, 0x5c, 0xf6, 0x92, 0xaa, 0xfc, 0x0b, 0xb9, 0xe7, 0x2f, 0xc9, 0x75, - 0xab, 0x1f, 0x33, 0xd3, 0x3d, 0x1a, 0xf1, 0x50, 0xd5, 0x9e, 0x34, 0xfd, 0xbd, 0xbf, 0xaf, 0x7f, - 0xfd, 0xf5, 0x43, 0x70, 0xc6, 0x8d, 0xbc, 0x8f, 0x34, 0x7a, 0xf7, 0xb1, 0xbf, 0x36, 0x8a, 0x42, - 0x1e, 0xa2, 0x9a, 0xfc, 0x79, 0xdb, 0xc7, 0x35, 0xa8, 0x6c, 0x0d, 0x47, 0xfc, 0x14, 0x5f, 0x80, - 0xda, 0x1e, 0x65, 0xcc, 0x0b, 0x03, 0x34, 0x07, 0x45, 0xcf, 0xed, 0x16, 0x96, 0x0b, 0xab, 0x0d, - 0x52, 0xf4, 0x5c, 0xfc, 0xdf, 0x02, 0x54, 0x76, 0x9d, 0xc8, 0x19, 0xa2, 0x0e, 0x94, 0x3e, 0xd0, - 0x53, 0xcd, 0x12, 0x9f, 0x68, 0x01, 0x2a, 0x1f, 0x1d, 0x7f, 0x4c, 0xbb, 0x45, 0x49, 0x53, 0x03, - 0x84, 0xa0, 0xec, 0x52, 0x36, 0xe8, 0x96, 0x24, 0x51, 0x7e, 0x0b, 0x1a, 0x3f, 0x1d, 0xd1, 0x6e, - 0x59, 0xd1, 0xc4, 0x37, 0x7a, 0x08, 0x75, 0xef, 0xde, 0xc3, 0x60, 0x53, 0xc8, 0x56, 0x96, 0x4b, - 0xab, 0xcd, 0xfe, 0xd2, 0x9a, 0x8e, 0x6c, 0x4d, 0x7a, 0x5c, 0xdb, 0xd6, 0xec, 0xad, 0x80, 0x47, - 0xa7, 0x24, 0x91, 0xee, 0x3d, 0x86, 0xb6, 0xc5, 0xfa, 0xd2, 0xd0, 0x1e, 0x15, 0x1f, 0x16, 0xf0, - 0x4f, 0x05, 0x28, 0x6d, 0xee, 0xed, 0x88, 0x90, 0xde, 0x87, 0x8c, 0x6b, 0x25, 0xf9, 0x2d, 0x68, - 0xa3, 0x30, 0xe2, 0x5a, 0x49, 0x7e, 0x0b, 0xda, 0x98, 0xd1, 0x28, 0x4e, 0x47, 0x7c, 0xa3, 0x1e, - 0xd4, 0x47, 0x0e, 0x63, 0xc7, 0x61, 0xe4, 0xea, 0x94, 0x92, 0xb1, 0xe0, 0xb9, 0x0e, 0x77, 0xf6, - 0x1d, 0x46, 0xbb, 0x15, 0xc5, 0x8b, 0xc7, 0xe8, 0x11, 0x74, 0x1c, 0xd7, 0xf5, 0xb8, 0x17, 0x06, - 0x8e, 0x2f, 0x73, 0x64, 0xdd, 0xaa, 0x4c, 0x7d, 0xce, 0x4e, 0x9d, 0x4c, 0xc8, 0xe1, 0xff, 0x17, - 0xa1, 0x4c, 0xc6, 0xbe, 0xac, 0x6f, 0xe0, 0x0c, 0x69, 0x1c, 0xb8, 0xf8, 0x4e, 0x6a, 0x5e, 0x34, - 0x6a, 0xbe, 0x00, 0x15, 0x9f, 0x7e, 0xa4, 0xbe, 0x8e, 0x5c, 0x0d, 0x44, 0x78, 0x03, 0x87, 0xd3, - 0xc3, 0x30, 0x3a, 0x8d, 0x43, 0x8f, 0xc7, 0x68, 0x05, 0xaa, 0x23, 0x15, 0x54, 0x25, 0x37, 0x28, - 0xcd, 0x45, 0x97, 0x01, 0x9c, 0x20, 0x08, 0xb9, 0x23, 0x02, 0xec, 0x56, 0xa5, 0x15, 0x83, 0x82, - 0xee, 0x42, 0xe3, 0x43, 0x10, 0x1e, 0xfb, 0xd4, 0x3d, 0xa4, 0xdd, 0xda, 0x72, 0x61, 0xb5, 0xd9, - 0x47, 0x89, 0xa9, 0x17, 0x31, 0x87, 0xa4, 0x42, 0x68, 0x03, 0x5a, 0x62, 0x76, 0x45, 0x7e, 0xdb, - 0xc1, 0x41, 0xd8, 0xad, 0x4b, 0xff, 0x57, 0x12, 0x25, 0xc1, 0x90, 0x70, 0x88, 0x25, 0x14, 0x24, - 0x2c, 0xa5, 0xde, 0x5b, 0x38, 0x3b, 0x21, 0x92, 0x03, 0x8d, 0x5b, 0x26, 0x34, 0x9a, 0xfd, 0xc5, - 0xc4, 0x89, 0xa9, 0x6c, 0x22, 0xe6, 0xef, 0x05, 0x68, 0x99, 0xbc, 0xa4, 0xda, 0x05, 0xa3, 0xda, - 0x66, 0x5d, 0x8b, 0x99, 0xba, 0xda, 0xf5, 0x2a, 0x7d, 0xba, 0x5e, 0xe5, 0x2f, 0xa8, 0x17, 0xbe, - 0x0e, 0x8d, 0x84, 0x8e, 0xba, 0x50, 0x1b, 0x84, 0x01, 0xa7, 0x41, 0x0c, 0xe6, 0x78, 0x88, 0x7f, - 0x2a, 0x42, 0xfb, 0x15, 0xe5, 0x0e, 0x23, 0x94, 0x8d, 0xc2, 0x80, 0x51, 0x11, 0xca, 0xc8, 0x1f, - 0x1f, 0x7a, 0xc1, 0x4e, 0x0a, 0x21, 0x83, 0x82, 0xee, 0xc2, 0x7c, 0x8c, 0xd6, 0x4d, 0x7a, 0xe0, - 0x8c, 0x7d, 0xbe, 0x1b, 0x2f, 0x88, 0x12, 0xc9, 0x63, 0xa1, 0xef, 0xa1, 0x1b, 0x93, 0x9f, 0x64, - 0xb1, 0x5d, 0xca, 0x85, 0xd1, 0x54, 0x79, 0x74, 0x15, 0x2a, 0xd1, 0xd8, 0xa7, 0xac, 0x5b, 0x96, - 0x8a, 0x6d, 0x6b, 0xfe, 0x89, 0xe2, 0xa1, 0x57, 0xb0, 0x48, 0x03, 0x67, 0xdf, 0xa7, 0xee, 0x1f, - 0x47, 0x4a, 0xfb, 0x55, 0xe8, 0x8e, 0x7d, 0x2a, 0x41, 0x3b, 0xd7, 0x3f, 0x9f, 0x28, 0xd9, 0x6c, - 0x92, 0xaf, 0x25, 0x26, 0xd3, 0x0f, 0x0f, 0x43, 0x09, 0xe3, 0x16, 0x91, 0xdf, 0x98, 0x40, 0x73, - 0x3b, 0xf0, 0x38, 0xa1, 0x47, 0x63, 0xca, 0x38, 0xba, 0x0c, 0x25, 0x97, 0x05, 0xb2, 0x5a, 0xcd, - 0x7e, 0x2b, 0xb1, 0xbf, 0xb9, 0xb7, 0x43, 0x04, 0x23, 0x0d, 0xbb, 0x38, 0x3d, 0x6c, 0xfc, 0x08, - 0x5a, 0xca, 0xa6, 0x9e, 0x89, 0x9b, 0x50, 0x63, 0xaa, 0xe7, 0x6a, 0xc3, 0x9d, 0x44, 0x4d, 0xf7, - 0x62, 0x12, 0x0b, 0x08, 0xdd, 0x0d, 0x3f, 0x64, 0x34, 0x0e, 0xe8, 0x6b, 0x74, 0xd7, 0x01, 0xbd, - 0xf0, 0x7c, 0x7f, 0x37, 0x0a, 0x07, 0x94, 0xb1, 0x59, 0x2c, 0xfc, 0x1a, 0x1a, 0xbb, 0x4e, 0xc4, - 0xa8, 0xbb, 0xf7, 0xfa, 0xa5, 0xe8, 0x2a, 0x47, 0x63, 0x1a, 0xc5, 0x2b, 0x4a, 0x0d, 0xf0, 0x8f, - 0xd0, 0x92, 0x22, 0x33, 0x98, 0x47, 0xd7, 0xa0, 0xc4, 0x8e, 0x7c, 0xbd, 0x1a, 0x91, 0x89, 0x15, - 0xe5, 0x92, 0x08, 0x36, 0xfe, 0x5b, 0x01, 0xca, 0x3b, 0xa1, 0x2b, 0xe7, 0x8b, 0xd3, 0x93, 0xa4, - 0x6f, 0x8b, 0xef, 0x64, 0x7b, 0x29, 0x1a, 0xdb, 0xcb, 0x32, 0x34, 0x0f, 0xbc, 0xe0, 0x90, 0x46, - 0xa3, 0xc8, 0x0b, 0xb8, 0x5e, 0x75, 0x26, 0x09, 0x2d, 0x41, 0x83, 0x71, 0x27, 0xe2, 0x2f, 0xbd, - 0x40, 0x2d, 0xbb, 0x32, 0x49, 0x09, 0x62, 0x55, 0xed, 0x3b, 0x7c, 0xf0, 0x7e, 0xdb, 0x95, 0x6d, - 0xbc, 0x4c, 0xe2, 0x21, 0xfe, 0x0d, 0xb4, 0x75, 0xb2, 0x7a, 0x2a, 0xaf, 0x42, 0x25, 0x08, 0x5d, - 0xca, 0xba, 0x85, 0xcc, 0xfc, 0x8b, 0x80, 0x89, 0xe2, 0xe1, 0x65, 0xa8, 0x3f, 0x19, 0xbb, 0x1e, - 0x9f, 0x5e, 0x44, 0x07, 0x5a, 0x52, 0x62, 0x96, 0x22, 0x5e, 0x87, 0x32, 0x3b, 0xf2, 0x63, 0x04, - 0x9e, 0x4d, 0x04, 0x63, 0x97, 0x44, 0xb2, 0xf1, 0x1d, 0x98, 0x17, 0x9d, 0x4c, 0xbb, 0x61, 0x63, - 0x9f, 0xcb, 0x86, 0xd6, 0x85, 0xda, 0x90, 0x32, 0xe6, 0x1c, 0xc6, 0x2d, 0x21, 0x1e, 0xe2, 0x7f, - 0x14, 0xa1, 0x69, 0x48, 0x4f, 0x97, 0x4c, 0xb7, 0x9b, 0xa2, 0xb9, 0xdd, 0x5c, 0x84, 0x86, 0x80, - 0xff, 0x3b, 0xb9, 0x63, 0xa9, 0x39, 0xa8, 0x0b, 0x82, 0x6c, 0x36, 0xef, 0x60, 0xde, 0x9b, 0x8c, - 0x46, 0x2f, 0xfe, 0x6f, 0xec, 0x1c, 0x14, 0x7f, 0x2d, 0x27, 0x7a, 0xb5, 0x15, 0xe4, 0x59, 0xea, - 0xb9, 0xd0, 0x9d, 0xa6, 0x90, 0xb3, 0x31, 0xf4, 0xed, 0x8d, 0x61, 0xc9, 0xda, 0x18, 0x32, 0x36, - 0xcc, 0xfd, 0xe1, 0xf7, 0xc9, 0xbc, 0x09, 0x2e, 0x43, 0x6b, 0x50, 0x8b, 0xd4, 0xa7, 0x06, 0xc4, - 0x42, 0x5e, 0x2a, 0x24, 0x16, 0xc2, 0xdf, 0x43, 0x3b, 0xa6, 0x2b, 0x3c, 0x7d, 0x07, 0x2d, 0xc7, - 0x30, 0xa8, 0xad, 0x2c, 0xe6, 0x59, 0x61, 0xc4, 0x12, 0xc5, 0x37, 0xe0, 0xcc, 0x0e, 0xa5, 0x2e, - 0x09, 0x7d, 0x7f, 0xdf, 0x19, 0x7c, 0x98, 0x0e, 0xb6, 0x10, 0x16, 0x9f, 0xd1, 0xc0, 0x90, 0x9b, - 0x05, 0x75, 0x37, 0xcd, 0xa5, 0xdb, 0x4d, 0x61, 0x6f, 0x47, 0xa0, 0x16, 0xb0, 0x86, 0x9e, 0x41, - 0xff, 0x0c, 0xf4, 0xfe, 0x52, 0x84, 0xe6, 0x67, 0xf3, 0x30, 0xf5, 0x8b, 0x36, 0x20, 0x35, 0xba, - 0x32, 0x0e, 0xf5, 0x9e, 0x94, 0xa2, 0xcb, 0xe0, 0xaf, 0xe5, 0x04, 0x68, 0xa0, 0x2b, 0xc3, 0x89, - 0xd1, 0x95, 0xa7, 0xf0, 0xb5, 0xe8, 0xca, 0xd8, 0x30, 0xd1, 0xb5, 0x0e, 0xe7, 0xb2, 0x13, 0xa5, - 0x61, 0xb2, 0xa2, 0xaa, 0xaf, 0x66, 0x69, 0x21, 0x2f, 0x21, 0x55, 0xf9, 0xef, 0xa0, 0xb9, 0xeb, - 0x05, 0x87, 0xb3, 0xb4, 0xfe, 0x2b, 0x50, 0xdb, 0x3a, 0xa1, 0x83, 0xe9, 0x30, 0xfa, 0x01, 0x9a, - 0x42, 0x60, 0x16, 0xf0, 0x60, 0x13, 0x3c, 0xa9, 0x9c, 0xf6, 0xa7, 0x42, 0xff, 0x57, 0x01, 0x40, - 0xd9, 0x97, 0xdd, 0x07, 0x43, 0xcb, 0x77, 0x18, 0xdf, 0x0e, 0x18, 0x8d, 0xf8, 0xb6, 0xba, 0xa6, - 0x94, 0x88, 0x45, 0x43, 0xb7, 0xe1, 0xac, 0x39, 0xde, 0x8a, 0xa2, 0x30, 0xd2, 0xd0, 0x98, 0x64, - 0x08, 0x8b, 0x51, 0x78, 0xcc, 0x9e, 0x1c, 0x1c, 0xd0, 0x01, 0xa7, 0xae, 0x6c, 0x51, 0x25, 0x62, - 0xd1, 0x84, 0x45, 0x73, 0xac, 0x2c, 0xaa, 0xb3, 0xf3, 0x24, 0x03, 0xbb, 0xd0, 0x11, 0x11, 0x3f, - 0x15, 0x9b, 0xc5, 0x6c, 0xdb, 0xa1, 0xd9, 0xc9, 0x27, 0xeb, 0xa2, 0x1a, 0xf9, 0x3a, 0x9c, 0x31, - 0xbc, 0xc8, 0xe2, 0x7c, 0x93, 0x6d, 0x3b, 0xf3, 0x96, 0x6e, 0xb6, 0xeb, 0x3c, 0x86, 0x96, 0x26, - 0x2b, 0x34, 0xdd, 0x82, 0xaa, 0x62, 0xe9, 0x10, 0x73, 0xb5, 0xb5, 0x08, 0xfe, 0x01, 0x1a, 0x6f, - 0x4e, 0x7e, 0xb9, 0xec, 0x1e, 0x03, 0x08, 0xf3, 0x3a, 0xb2, 0xaf, 0x4c, 0x6c, 0x19, 0xea, 0xaf, - 0x05, 0x36, 0xa7, 0x83, 0xf6, 0x1e, 0x34, 0xa4, 0xc4, 0x46, 0x18, 0x1c, 0xa0, 0x6b, 0xd0, 0xe6, - 0xde, 0x90, 0x86, 0x63, 0xbe, 0x47, 0x07, 0x61, 0xa0, 0x40, 0xd5, 0x26, 0x36, 0x11, 0xff, 0xb5, - 0x00, 0x2d, 0xa9, 0x33, 0x4b, 0xd2, 0x57, 0x4d, 0xa4, 0xa7, 0x7b, 0x73, 0x1c, 0xa5, 0x84, 0x3a, - 0x5a, 0x81, 0xf2, 0x20, 0x0c, 0x0e, 0x24, 0x02, 0xcd, 0x73, 0x50, 0x12, 0x29, 0x91, 0x7c, 0xec, - 0x42, 0x5b, 0x07, 0x92, 0xb4, 0x81, 0xea, 0x20, 0xf4, 0xc7, 0xc3, 0x40, 0x57, 0x67, 0xe2, 0xd6, - 0xa6, 0xb8, 0xe8, 0x16, 0x94, 0x05, 0x5a, 0x75, 0xe9, 0xcf, 0xdb, 0x0e, 0x74, 0x11, 0xc3, 0x63, - 0x22, 0x85, 0xf0, 0x06, 0xcc, 0xd9, 0x74, 0x74, 0x0f, 0xaa, 0xb2, 0x29, 0xc5, 0x93, 0x70, 0x21, - 0xcf, 0xc0, 0x5b, 0x21, 0x41, 0xb4, 0x20, 0x5e, 0x85, 0x4e, 0x96, 0x97, 0x5e, 0xcc, 0x0b, 0xc6, - 0xc5, 0x1c, 0x63, 0xb1, 0xcc, 0x47, 0xbe, 0xe3, 0x05, 0xd3, 0x67, 0x6d, 0x00, 0x73, 0x5a, 0x66, - 0xb6, 0x03, 0x92, 0x31, 0x07, 0x26, 0x80, 0x62, 0xaf, 0xaa, 0xe1, 0xbc, 0x15, 0xeb, 0x4a, 0x3b, - 0xd1, 0xf5, 0xdd, 0x80, 0xf6, 0xc0, 0x77, 0x18, 0xf3, 0x34, 0xd2, 0xb4, 0xaf, 0x4b, 0x59, 0x1b, - 0x1b, 0xa6, 0x10, 0xb1, 0x75, 0xf0, 0x3a, 0x2c, 0xe4, 0x89, 0xa1, 0x55, 0x28, 0x8b, 0xdb, 0xd0, - 0x44, 0x13, 0x7f, 0xe3, 0xec, 0x8f, 0x7d, 0x27, 0xda, 0x74, 0xb8, 0x43, 0xa4, 0x04, 0x7e, 0x02, - 0xf3, 0xcf, 0x28, 0xdf, 0xd4, 0x57, 0xa7, 0x99, 0x0e, 0xf2, 0x97, 0xa1, 0x1e, 0xeb, 0xe7, 0xbd, - 0x22, 0xe0, 0x67, 0xb0, 0x60, 0xbb, 0xd0, 0x15, 0xb8, 0x03, 0x8d, 0xf8, 0xca, 0x16, 0xcf, 0x7e, - 0x8a, 0xe2, 0x58, 0x9c, 0xa4, 0x32, 0xf8, 0x3e, 0x54, 0xde, 0x88, 0xbb, 0x56, 0xee, 0x5b, 0xc5, - 0x39, 0xa8, 0xb2, 0xc1, 0x7b, 0x3a, 0x74, 0x74, 0x57, 0xd6, 0x23, 0x7c, 0x28, 0x13, 0x94, 0x7a, - 0xe2, 0xce, 0x3a, 0x5b, 0x77, 0xa9, 0x70, 0xa1, 0xaf, 0xa7, 0x79, 0xce, 0x2c, 0xa7, 0xb8, 0x89, - 0x49, 0x26, 0x7e, 0x2e, 0xd3, 0x34, 0x1c, 0xe9, 0x34, 0xef, 0x42, 0x83, 0xc7, 0x44, 0xed, 0x0b, - 0xd9, 0x16, 0xa4, 0x78, 0x2a, 0x84, 0xff, 0x53, 0x80, 0x46, 0xc2, 0x40, 0x0f, 0xa0, 0xa9, 0x96, - 0x1a, 0x93, 0x07, 0x8d, 0xec, 0x94, 0x6e, 0xa4, 0x3c, 0x62, 0x0a, 0x0a, 0x3d, 0x2f, 0x70, 0xe9, - 0x09, 0x55, 0x7a, 0xc5, 0x8c, 0xde, 0x76, 0xca, 0x23, 0xa6, 0x20, 0x5a, 0x81, 0xb9, 0x41, 0x44, - 0x1d, 0x4e, 0x65, 0x08, 0x7b, 0xaf, 0x5f, 0xea, 0x03, 0x76, 0x86, 0x6a, 0x1e, 0x91, 0xca, 0xf6, - 0x11, 0xeb, 0x5b, 0x68, 0x1a, 0x51, 0x7d, 0x05, 0x18, 0xbf, 0x15, 0x17, 0xe4, 0x34, 0x92, 0x2f, - 0x57, 0xfc, 0x77, 0x01, 0xce, 0x18, 0xd4, 0xe7, 0xd4, 0x71, 0xbf, 0xf8, 0x41, 0xeb, 0xa9, 0xf1, - 0x60, 0xa8, 0x4e, 0x71, 0x2b, 0x79, 0x9e, 0x84, 0xcd, 0x5f, 0xe6, 0xe9, 0xf0, 0x86, 0x15, 0x3b, - 0x09, 0x8f, 0x99, 0x10, 0xf6, 0x38, 0x1d, 0xaa, 0x65, 0xd1, 0x20, 0x6a, 0x80, 0x43, 0x68, 0x1a, - 0x82, 0xa8, 0x0f, 0x35, 0x3d, 0xdf, 0x7a, 0xf5, 0x74, 0xa7, 0xc5, 0x4d, 0x62, 0x41, 0x74, 0xdb, - 0xea, 0xd6, 0xb9, 0x0a, 0x22, 0x00, 0xdd, 0xae, 0xaf, 0x89, 0xcd, 0x9c, 0x47, 0x8e, 0x38, 0x86, - 0x4c, 0xef, 0xa0, 0x47, 0xd0, 0xd3, 0x52, 0x12, 0x1b, 0x7f, 0x88, 0xc2, 0xe1, 0x8c, 0x07, 0xff, - 0x1b, 0x66, 0x37, 0x5d, 0x34, 0x3a, 0x61, 0x1a, 0x83, 0xea, 0xa7, 0x5b, 0x70, 0x31, 0xd7, 0x65, - 0xba, 0x77, 0xc9, 0xd5, 0xc4, 0x26, 0xf6, 0x2e, 0xb5, 0x62, 0x35, 0x17, 0x5f, 0x87, 0xb6, 0x3a, - 0x65, 0x89, 0x9c, 0xa7, 0x27, 0xc8, 0x61, 0x69, 0x8b, 0x71, 0x6f, 0xe8, 0x70, 0x01, 0xfc, 0x54, - 0x63, 0x96, 0x14, 0x57, 0xcd, 0x14, 0xcf, 0xa5, 0x77, 0x2f, 0x33, 0x0c, 0x95, 0xe3, 0x9f, 0xe0, - 0xd2, 0x14, 0xaf, 0x3a, 0xcb, 0x05, 0xa8, 0x0c, 0xc2, 0xb1, 0x7e, 0x9e, 0x2b, 0x11, 0x35, 0x40, - 0x97, 0x01, 0x68, 0x14, 0xbd, 0xb2, 0x2e, 0x2f, 0x06, 0x05, 0xff, 0x16, 0xe6, 0xad, 0x87, 0x9b, - 0xf4, 0x05, 0xcf, 0x50, 0x2b, 0x64, 0xd5, 0x6e, 0xfe, 0xb3, 0x00, 0x73, 0x13, 0x4f, 0x5c, 0x73, - 0xf6, 0x15, 0xa2, 0xf3, 0x2b, 0xd4, 0x80, 0x8a, 0xdc, 0x9b, 0x3b, 0x05, 0xd4, 0x14, 0x87, 0x7c, - 0xb9, 0x37, 0x75, 0x8a, 0xa8, 0x03, 0x2d, 0xb3, 0x39, 0x76, 0x4a, 0xe8, 0x3c, 0xcc, 0xe7, 0x4c, - 0x61, 0xa7, 0x8c, 0x2e, 0xc0, 0x62, 0x6e, 0xde, 0x9d, 0x0a, 0x3a, 0x03, 0x4d, 0x23, 0xf6, 0x4e, - 0x15, 0xb5, 0xa1, 0x91, 0x9c, 0x57, 0x3b, 0x35, 0x54, 0x87, 0xb2, 0x58, 0x86, 0x9d, 0x7a, 0xff, - 0x7f, 0x75, 0xa8, 0x6e, 0xca, 0x3f, 0x28, 0xd0, 0x1d, 0xa8, 0xc8, 0xc7, 0x4a, 0x94, 0xa2, 0x40, - 0xfe, 0x3d, 0xd1, 0x4b, 0xab, 0x6f, 0x3f, 0x66, 0xde, 0x87, 0xf2, 0x76, 0xe0, 0x71, 0x64, 0xf6, - 0xca, 0xe4, 0xf9, 0xa4, 0xb7, 0x98, 0xa1, 0x6a, 0xa5, 0x35, 0xa8, 0xc8, 0xb7, 0x34, 0x94, 0xf2, - 0xcd, 0xb7, 0xb5, 0x5e, 0xc6, 0x39, 0x7a, 0x6e, 0xa5, 0x82, 0x2e, 0xa6, 0x0f, 0xb3, 0x13, 0xaf, - 0x6a, 0xbd, 0xa5, 0x7c, 0xa6, 0xf6, 0xfc, 0x40, 0xfe, 0x93, 0x62, 0x79, 0x36, 0x1f, 0xcd, 0x8c, - 0x34, 0xed, 0xe7, 0xa5, 0x07, 0x50, 0x91, 0x37, 0x7e, 0x34, 0xf1, 0x02, 0x90, 0xd5, 0xb3, 0x9f, - 0x11, 0x5e, 0x67, 0xa7, 0x1d, 0x5d, 0x4e, 0x24, 0x73, 0xef, 0xfe, 0xbd, 0x2b, 0x53, 0xf9, 0xda, - 0xe4, 0x6d, 0x28, 0x8b, 0xab, 0xa4, 0x51, 0x71, 0xe3, 0x66, 0x39, 0x51, 0xba, 0xfb, 0x50, 0x16, - 0x93, 0x6e, 0x48, 0x1b, 0x77, 0xc5, 0xde, 0x62, 0xf6, 0x14, 0xaf, 0x5c, 0xac, 0x1b, 0x48, 0x41, - 0x17, 0x2c, 0x19, 0xf3, 0x4e, 0xd5, 0xeb, 0xe6, 0xb1, 0xf4, 0x45, 0xa8, 0xf8, 0xe6, 0x04, 0x19, - 0x5b, 0x77, 0x7c, 0x53, 0xe9, 0xcd, 0x5b, 0xb4, 0xb4, 0xbc, 0x72, 0x25, 0x18, 0xe5, 0x35, 0x4f, - 0xfa, 0x46, 0x79, 0xed, 0x73, 0xf7, 0xef, 0x92, 0x65, 0x83, 0xce, 0x67, 0xcf, 0x82, 0x79, 0x41, - 0xda, 0xa7, 0xca, 0x17, 0x72, 0x9d, 0x25, 0x67, 0x2d, 0xb4, 0x64, 0x94, 0x7e, 0xe2, 0x94, 0xd7, - 0xbb, 0x34, 0x85, 0x6b, 0x19, 0x4b, 0x4f, 0x22, 0x96, 0xb1, 0xec, 0x89, 0xca, 0x36, 0x36, 0x79, - 0x0c, 0xfa, 0x31, 0x77, 0xbd, 0xa3, 0xab, 0xd9, 0x2e, 0x9f, 0xb3, 0x87, 0xf4, 0xae, 0x7d, 0x5a, - 0x48, 0x7b, 0x38, 0x98, 0xd2, 0x38, 0xd0, 0xf5, 0x54, 0xfd, 0x13, 0x6d, 0xbc, 0xb7, 0xf2, 0x39, - 0x31, 0xe5, 0xe7, 0x69, 0xeb, 0xcf, 0xb0, 0x76, 0xe7, 0xb1, 0x96, 0xdd, 0xaf, 0xca, 0x8f, 0xfb, - 0x3f, 0x07, 0x00, 0x00, 0xff, 0xff, 0x5f, 0x2b, 0xbc, 0xee, 0x10, 0x1d, 0x00, 0x00, + // 2528 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x59, 0xdb, 0x72, 0x13, 0xc9, + 0x19, 0xce, 0xe8, 0x60, 0x59, 0xbf, 0x0e, 0x88, 0xb6, 0x0d, 0x83, 0x30, 0xc6, 0xdb, 0x80, 0x71, + 0x01, 0x6b, 0x40, 0x24, 0x2c, 0x0b, 0xa9, 0x04, 0xb0, 0x1c, 0xf0, 0x02, 0x5e, 0xbb, 0x45, 0xb8, + 0x48, 0xd5, 0x16, 0x3b, 0xd6, 0xb4, 0xcc, 0x2c, 0xa3, 0x19, 0x79, 0x66, 0xc4, 0xe1, 0x3e, 0x55, + 0xc9, 0x65, 0xf2, 0x02, 0x49, 0xe5, 0x0d, 0x72, 0xb3, 0x95, 0xaa, 0x54, 0xe5, 0x15, 0xf2, 0x14, + 0x79, 0x87, 0xdc, 0x6e, 0xf5, 0x61, 0x66, 0xba, 0x67, 0x46, 0x1c, 0x54, 0xb5, 0x57, 0x9a, 0xfe, + 0x0f, 0xfd, 0x1f, 0xfa, 0xeb, 0xbf, 0xbb, 0x7f, 0xc1, 0x09, 0x3b, 0x70, 0xde, 0xd0, 0xe0, 0xe5, + 0x9b, 0xde, 0xd6, 0x24, 0xf0, 0x23, 0x1f, 0xd5, 0xf8, 0xcf, 0x8b, 0x1e, 0xae, 0x41, 0x75, 0x67, + 0x3c, 0x89, 0xde, 0xe3, 0x33, 0x50, 0x1b, 0xd0, 0x30, 0x74, 0x7c, 0x0f, 0xb5, 0xa1, 0xe4, 0xd8, + 0xa6, 0xb1, 0x6e, 0x6c, 0xd6, 0x49, 0xc9, 0xb1, 0xf1, 0x7f, 0x0d, 0xa8, 0xee, 0x5b, 0x81, 0x35, + 0x46, 0x1d, 0x28, 0xbf, 0xa6, 0xef, 0x25, 0x8b, 0x7d, 0xa2, 0x65, 0xa8, 0xbe, 0xb1, 0xdc, 0x29, + 0x35, 0x4b, 0x9c, 0x26, 0x06, 0x08, 0x41, 0xc5, 0xa6, 0xe1, 0xd0, 0x2c, 0x73, 0x22, 0xff, 0x66, + 0xb4, 0xe8, 0xfd, 0x84, 0x9a, 0x15, 0x41, 0x63, 0xdf, 0xe8, 0x0e, 0x2c, 0x3a, 0x37, 0xef, 0x78, + 0x7d, 0x26, 0x5b, 0x5d, 0x2f, 0x6f, 0x36, 0x7a, 0xab, 0x5b, 0xd2, 0xb3, 0x2d, 0x6e, 0x71, 0x6b, + 0x57, 0xb2, 0x77, 0xbc, 0x28, 0x78, 0x4f, 0x12, 0xe9, 0xee, 0x3d, 0x68, 0x69, 0xac, 0x4f, 0x75, + 0xed, 0x6e, 0xe9, 0x8e, 0x81, 0x7f, 0x34, 0xa0, 0xdc, 0x1f, 0xec, 0x31, 0x97, 0x5e, 0xf9, 0x61, + 0x24, 0x95, 0xf8, 0x37, 0xa3, 0x4d, 0xfc, 0x20, 0x92, 0x4a, 0xfc, 0x9b, 0xd1, 0xa6, 0x21, 0x0d, + 0xe2, 0x70, 0xd8, 0x37, 0xea, 0xc2, 0xe2, 0xc4, 0x0a, 0xc3, 0xb7, 0x7e, 0x60, 0xcb, 0x90, 0x92, + 0x31, 0xe3, 0xd9, 0x56, 0x64, 0x1d, 0x5a, 0x21, 0x35, 0xab, 0x82, 0x17, 0x8f, 0xd1, 0x5d, 0xe8, + 0x58, 0xb6, 0xed, 0x44, 0x8e, 0xef, 0x59, 0x2e, 0x8f, 0x31, 0x34, 0x17, 0x78, 0xe8, 0x6d, 0x3d, + 0x74, 0x92, 0x93, 0xc3, 0xff, 0x2f, 0x41, 0x85, 0x4c, 0x5d, 0x9e, 0x5f, 0xcf, 0x1a, 0xd3, 0xd8, + 0x71, 0xf6, 0x9d, 0xe4, 0xbc, 0xa4, 0xe4, 0x7c, 0x19, 0xaa, 0x2e, 0x7d, 0x43, 0x5d, 0xe9, 0xb9, + 0x18, 0x30, 0xf7, 0x86, 0x56, 0x44, 0x8f, 0xfc, 0xe0, 0x7d, 0xec, 0x7a, 0x3c, 0x46, 0x1b, 0xb0, + 0x30, 0x11, 0x4e, 0x55, 0x0b, 0x9d, 0x92, 0x5c, 0xb4, 0x06, 0x60, 0x79, 0x9e, 0x1f, 0x59, 0xcc, + 0x41, 0x73, 0x81, 0xcf, 0xa2, 0x50, 0xd0, 0x0d, 0xa8, 0xbf, 0xf6, 0xfc, 0xb7, 0x2e, 0xb5, 0x8f, + 0xa8, 0x59, 0x5b, 0x37, 0x36, 0x1b, 0x3d, 0x94, 0x4c, 0xf5, 0x24, 0xe6, 0x90, 0x54, 0x08, 0x6d, + 0x43, 0x93, 0xad, 0x2e, 0x8b, 0x6f, 0xd7, 0x1b, 0xf9, 0xe6, 0x22, 0xb7, 0x7f, 0x3e, 0x51, 0x62, + 0x0c, 0x0e, 0x87, 0x58, 0x42, 0x40, 0x42, 0x53, 0xea, 0xbe, 0x80, 0x93, 0x39, 0x91, 0x02, 0x68, + 0x5c, 0x55, 0xa1, 0xd1, 0xe8, 0xad, 0x24, 0x46, 0x54, 0x65, 0x15, 0x31, 0x7f, 0x31, 0xa0, 0xa9, + 0xf2, 0x92, 0x6c, 0x1b, 0x4a, 0xb6, 0xd5, 0xbc, 0x96, 0x32, 0x79, 0xd5, 0xf3, 0x55, 0xfe, 0x70, + 0xbe, 0x2a, 0x9f, 0x90, 0x2f, 0x7c, 0x09, 0xea, 0x09, 0x1d, 0x99, 0x50, 0x1b, 0xfa, 0x5e, 0x44, + 0xbd, 0x18, 0xcc, 0xf1, 0x10, 0xff, 0x58, 0x82, 0xd6, 0x33, 0x1a, 0x59, 0x21, 0xa1, 0xe1, 0xc4, + 0xf7, 0x42, 0xca, 0x5c, 0x99, 0xb8, 0xd3, 0x23, 0xc7, 0xdb, 0x4b, 0x21, 0xa4, 0x50, 0xd0, 0x0d, + 0x58, 0x8a, 0xd1, 0xda, 0xa7, 0x23, 0x6b, 0xea, 0x46, 0xfb, 0xf1, 0x86, 0x28, 0x93, 0x22, 0x16, + 0xfa, 0x06, 0xcc, 0x98, 0xfc, 0x20, 0x8b, 0xed, 0x72, 0x21, 0x8c, 0x66, 0xca, 0xa3, 0x0b, 0x50, + 0x0d, 0xa6, 0x2e, 0x0d, 0xcd, 0x0a, 0x57, 0x6c, 0x69, 0xeb, 0x4f, 0x04, 0x0f, 0x3d, 0x83, 0x15, + 0xea, 0x59, 0x87, 0x2e, 0xb5, 0xbf, 0x9d, 0x08, 0xed, 0x67, 0xbe, 0x3d, 0x75, 0x29, 0x07, 0x6d, + 0xbb, 0x77, 0x3a, 0x51, 0xd2, 0xd9, 0xa4, 0x58, 0x8b, 0x2d, 0xa6, 0xeb, 0x1f, 0xf9, 0x1c, 0xc6, + 0x4d, 0xc2, 0xbf, 0x31, 0x81, 0xc6, 0xae, 0xe7, 0x44, 0x84, 0x1e, 0x4f, 0x69, 0x18, 0xa1, 0x35, + 0x28, 0xdb, 0xa1, 0xc7, 0xb3, 0xd5, 0xe8, 0x35, 0x93, 0xf9, 0xfb, 0x83, 0x3d, 0xc2, 0x18, 0xa9, + 0xdb, 0xa5, 0xd9, 0x6e, 0xe3, 0xbb, 0xd0, 0x14, 0x73, 0xca, 0x95, 0xb8, 0x02, 0xb5, 0x50, 0xd4, + 0x5c, 0x39, 0x71, 0x27, 0x51, 0x93, 0xb5, 0x98, 0xc4, 0x02, 0x4c, 0x77, 0xdb, 0xf5, 0x43, 0x1a, + 0x3b, 0xf4, 0x39, 0xba, 0xf7, 0x01, 0x3d, 0x71, 0x5c, 0x77, 0x3f, 0xf0, 0x87, 0x34, 0x0c, 0xe7, + 0x99, 0xe1, 0x0b, 0xa8, 0xef, 0x5b, 0x41, 0x48, 0xed, 0xc1, 0xc1, 0x53, 0x56, 0x55, 0x8e, 0xa7, + 0x34, 0x88, 0x77, 0x94, 0x18, 0xe0, 0xef, 0xa1, 0xc9, 0x45, 0xe6, 0x98, 0x1e, 0x5d, 0x84, 0x72, + 0x78, 0xec, 0xca, 0xdd, 0x88, 0x54, 0xac, 0x08, 0x93, 0x84, 0xb1, 0xf1, 0x9f, 0x0d, 0xa8, 0xec, + 0xf9, 0x36, 0x5f, 0xaf, 0x88, 0xbe, 0x4b, 0xea, 0x36, 0xfb, 0x4e, 0x8e, 0x97, 0x92, 0x72, 0xbc, + 0xac, 0x43, 0x63, 0xe4, 0x78, 0x47, 0x34, 0x98, 0x04, 0x8e, 0x17, 0xc9, 0x5d, 0xa7, 0x92, 0xd0, + 0x2a, 0xd4, 0xc3, 0xc8, 0x0a, 0xa2, 0xa7, 0x8e, 0x27, 0xb6, 0x5d, 0x85, 0xa4, 0x04, 0xb6, 0xab, + 0x0e, 0xad, 0x68, 0xf8, 0x6a, 0xd7, 0xe6, 0x65, 0xbc, 0x42, 0xe2, 0x21, 0xfe, 0x25, 0xb4, 0x64, + 0xb0, 0x72, 0x29, 0x2f, 0x40, 0xd5, 0xf3, 0x6d, 0x1a, 0x9a, 0x46, 0x66, 0xfd, 0x99, 0xc3, 0x44, + 0xf0, 0xf0, 0x3a, 0x2c, 0x3e, 0x98, 0xda, 0x4e, 0x34, 0x3b, 0x89, 0x16, 0x34, 0xb9, 0xc4, 0x3c, + 0x49, 0xbc, 0x04, 0x95, 0xf0, 0xd8, 0x8d, 0x11, 0x78, 0x32, 0x11, 0x8c, 0x4d, 0x12, 0xce, 0xc6, + 0xd7, 0x61, 0x89, 0x55, 0x32, 0x69, 0x26, 0x9c, 0xba, 0x11, 0x2f, 0x68, 0x26, 0xd4, 0xc6, 0x34, + 0x0c, 0xad, 0xa3, 0xb8, 0x24, 0xc4, 0x43, 0xfc, 0x8f, 0x12, 0x34, 0x14, 0xe9, 0xd9, 0x92, 0xe9, + 0x71, 0x53, 0x52, 0x8f, 0x9b, 0xb3, 0x50, 0x67, 0xf0, 0x7f, 0xc9, 0x4f, 0x2c, 0xb1, 0x06, 0x8b, + 0x8c, 0xc0, 0x8b, 0xcd, 0x4b, 0x58, 0x72, 0xf2, 0xde, 0xc8, 0xcd, 0xff, 0xa5, 0x1e, 0x83, 0xe0, + 0x6f, 0x15, 0x78, 0x2f, 0x8e, 0x82, 0xa2, 0x99, 0xba, 0x36, 0x98, 0xb3, 0x14, 0x0a, 0x0e, 0x86, + 0x9e, 0x7e, 0x30, 0xac, 0x6a, 0x07, 0x43, 0x66, 0x0e, 0xf5, 0x7c, 0xf8, 0x4d, 0xb2, 0x6e, 0x8c, + 0x1b, 0xa2, 0x2d, 0xa8, 0x05, 0xe2, 0x53, 0x02, 0x62, 0xb9, 0x28, 0x14, 0x12, 0x0b, 0xe1, 0x6f, + 0xa0, 0x15, 0xd3, 0x05, 0x9e, 0xbe, 0x86, 0xa6, 0xa5, 0x4c, 0x28, 0x67, 0x59, 0x29, 0x9a, 0x25, + 0x24, 0x9a, 0x28, 0xbe, 0x0c, 0x27, 0xf6, 0x28, 0xb5, 0x89, 0xef, 0xba, 0x87, 0xd6, 0xf0, 0xf5, + 0x6c, 0xb0, 0xf9, 0xb0, 0xf2, 0x88, 0x7a, 0x8a, 0xdc, 0x3c, 0xa8, 0xbb, 0xa2, 0x6e, 0x5d, 0x33, + 0x85, 0xbd, 0xee, 0x81, 0xd8, 0xc0, 0x12, 0x7a, 0x0a, 0xfd, 0x23, 0xd0, 0xfb, 0x63, 0x09, 0x1a, + 0x1f, 0x8d, 0x43, 0xd5, 0x2f, 0xe9, 0x80, 0x94, 0xe8, 0xca, 0x18, 0x94, 0x67, 0x52, 0x8a, 0x2e, + 0x85, 0xbf, 0x55, 0xe0, 0xa0, 0x82, 0xae, 0x0c, 0x27, 0x46, 0x57, 0x91, 0xc2, 0xe7, 0xa2, 0x2b, + 0x33, 0x87, 0x8a, 0xae, 0xfb, 0x70, 0x2a, 0xbb, 0x50, 0x12, 0x26, 0x1b, 0x22, 0xfb, 0x62, 0x95, + 0x96, 0x8b, 0x02, 0x12, 0x99, 0xff, 0x1a, 0x1a, 0xfb, 0x8e, 0x77, 0x34, 0x4f, 0xe9, 0x3f, 0x0f, + 0xb5, 0x9d, 0x77, 0x74, 0x38, 0x1b, 0x46, 0xdf, 0x41, 0x83, 0x09, 0xcc, 0x03, 0x1e, 0xac, 0x82, + 0x27, 0x95, 0x93, 0xf6, 0x84, 0xeb, 0xff, 0x34, 0x00, 0xc4, 0xfc, 0xbc, 0xfa, 0x60, 0x68, 0xba, + 0x56, 0x18, 0xed, 0x7a, 0x21, 0x0d, 0xa2, 0x5d, 0xf1, 0x4c, 0x29, 0x13, 0x8d, 0x86, 0xae, 0xc1, + 0x49, 0x75, 0xbc, 0x13, 0x04, 0x7e, 0x20, 0xa1, 0x91, 0x67, 0xb0, 0x19, 0x03, 0xff, 0x6d, 0xf8, + 0x60, 0x34, 0xa2, 0xc3, 0x88, 0xda, 0xbc, 0x44, 0x95, 0x89, 0x46, 0x63, 0x33, 0xaa, 0x63, 0x31, + 0xa3, 0xb8, 0x3b, 0xe7, 0x19, 0xd8, 0x86, 0x0e, 0xf3, 0xf8, 0x21, 0x3b, 0x2c, 0xe6, 0x3b, 0x0e, + 0xd5, 0x4a, 0x9e, 0xcf, 0x8b, 0x28, 0xe4, 0xf7, 0xe1, 0x84, 0x62, 0x85, 0x27, 0xe7, 0xcb, 0x6c, + 0xd9, 0x59, 0xd2, 0x74, 0xb3, 0x55, 0xe7, 0x1e, 0x34, 0x25, 0x59, 0xa0, 0xe9, 0x2a, 0x2c, 0x08, + 0x96, 0x74, 0xb1, 0x50, 0x5b, 0x8a, 0xe0, 0xef, 0xa0, 0xfe, 0xfc, 0xdd, 0xcf, 0x17, 0xdd, 0x3d, + 0x00, 0x36, 0xbd, 0xf4, 0xec, 0x33, 0x03, 0x5b, 0x87, 0xc5, 0x03, 0x86, 0xcd, 0xd9, 0xa0, 0xbd, + 0x09, 0x75, 0x2e, 0xb1, 0xed, 0x7b, 0x23, 0x74, 0x11, 0x5a, 0x91, 0x33, 0xa6, 0xfe, 0x34, 0x1a, + 0xd0, 0xa1, 0xef, 0x09, 0x50, 0xb5, 0x88, 0x4e, 0xc4, 0x7f, 0x32, 0xa0, 0xc9, 0x75, 0xe6, 0x09, + 0xfa, 0x82, 0x8a, 0xf4, 0xf4, 0x6c, 0x8e, 0xbd, 0xe4, 0x50, 0x47, 0x1b, 0x50, 0x19, 0xfa, 0xde, + 0x88, 0x23, 0x50, 0xbd, 0x07, 0x25, 0x9e, 0x12, 0xce, 0xc7, 0x36, 0xb4, 0xa4, 0x23, 0x49, 0x19, + 0x58, 0x18, 0xfa, 0xee, 0x74, 0xec, 0xc9, 0xec, 0xe4, 0x5e, 0x6d, 0x82, 0x8b, 0xae, 0x42, 0x85, + 0xa1, 0x55, 0xa6, 0xfe, 0xb4, 0x6e, 0x40, 0x26, 0xd1, 0x7f, 0x4b, 0xb8, 0x10, 0xde, 0x86, 0xb6, + 0x4e, 0x47, 0x37, 0x61, 0x81, 0x17, 0xa5, 0x78, 0x11, 0xce, 0x14, 0x4d, 0xf0, 0x82, 0x49, 0x10, + 0x29, 0x88, 0x37, 0xa1, 0x93, 0xe5, 0xa5, 0x0f, 0x73, 0x43, 0x79, 0x98, 0x63, 0xcc, 0xb6, 0xf9, + 0xc4, 0xb5, 0x1c, 0x6f, 0xf6, 0xaa, 0x0d, 0xa1, 0x2d, 0x65, 0xe6, 0xbb, 0x20, 0x29, 0x6b, 0xa0, + 0x02, 0x28, 0xb6, 0x2a, 0x0a, 0xce, 0x0b, 0xb6, 0xaf, 0xa4, 0x11, 0x99, 0xdf, 0x6d, 0x68, 0x0d, + 0x5d, 0x2b, 0x0c, 0x1d, 0x89, 0x34, 0x69, 0xeb, 0x5c, 0x76, 0x8e, 0x6d, 0x55, 0x88, 0xe8, 0x3a, + 0xf8, 0x3e, 0x2c, 0x17, 0x89, 0xa1, 0x4d, 0xa8, 0xb0, 0xd7, 0x50, 0xae, 0x88, 0x3f, 0xb7, 0x0e, + 0xa7, 0xae, 0x15, 0xf4, 0xad, 0xc8, 0x22, 0x5c, 0x02, 0x3f, 0x80, 0xa5, 0x47, 0x34, 0xea, 0xcb, + 0xa7, 0xd3, 0x5c, 0x17, 0xf9, 0x35, 0x58, 0x8c, 0xf5, 0x8b, 0xba, 0x08, 0xf8, 0x11, 0x2c, 0xeb, + 0x26, 0x64, 0x06, 0xae, 0x43, 0x3d, 0x7e, 0xb2, 0xc5, 0xab, 0x9f, 0xa2, 0x38, 0x16, 0x27, 0xa9, + 0x0c, 0xbe, 0x05, 0xd5, 0xe7, 0xec, 0xad, 0x55, 0xd8, 0xab, 0x38, 0x05, 0x0b, 0xe1, 0xf0, 0x15, + 0x1d, 0x5b, 0xb2, 0x2a, 0xcb, 0x11, 0x3e, 0xe2, 0x01, 0x72, 0x3d, 0xf6, 0x66, 0x9d, 0xaf, 0xba, + 0x54, 0x23, 0xa6, 0x2f, 0x97, 0xb9, 0xad, 0xa6, 0x93, 0xbd, 0xc4, 0x38, 0x13, 0x3f, 0xe6, 0x61, + 0x2a, 0x86, 0x64, 0x98, 0x37, 0xa0, 0x1e, 0xc5, 0x44, 0x69, 0x0b, 0xe9, 0x33, 0x70, 0xf1, 0x54, + 0x08, 0xff, 0xc7, 0x80, 0x7a, 0xc2, 0x40, 0xb7, 0xa1, 0x21, 0xb6, 0x5a, 0xc8, 0x2f, 0x1a, 0xd9, + 0x25, 0xdd, 0x4e, 0x79, 0x44, 0x15, 0x64, 0x7a, 0x8e, 0x67, 0xd3, 0x77, 0x54, 0xe8, 0x95, 0x32, + 0x7a, 0xbb, 0x29, 0x8f, 0xa8, 0x82, 0x68, 0x03, 0xda, 0xc3, 0x80, 0x5a, 0x11, 0xe5, 0x2e, 0x0c, + 0x0e, 0x9e, 0xca, 0x0b, 0x76, 0x86, 0xaa, 0x5e, 0x91, 0x2a, 0xfa, 0x15, 0xeb, 0x2b, 0x68, 0x28, + 0x5e, 0x7d, 0x06, 0x18, 0xbf, 0x62, 0x0f, 0xe4, 0xd4, 0x93, 0x4f, 0x57, 0xfc, 0xb7, 0x01, 0x27, + 0x14, 0xea, 0x63, 0x6a, 0xd9, 0x9f, 0xdc, 0xd0, 0x7a, 0xa8, 0x34, 0x0c, 0xc5, 0x2d, 0x6e, 0xa3, + 0xc8, 0x12, 0x9b, 0xf3, 0xe7, 0x69, 0x1d, 0x5e, 0xd6, 0x7c, 0x27, 0xfe, 0xdb, 0x90, 0x09, 0x3b, + 0x11, 0x1d, 0x8b, 0x6d, 0x51, 0x27, 0x62, 0x80, 0x7d, 0x68, 0x28, 0x82, 0xa8, 0x07, 0x35, 0xb9, + 0xde, 0x72, 0xf7, 0x98, 0xb3, 0xfc, 0x26, 0xb1, 0x20, 0xba, 0xa6, 0x55, 0xeb, 0x42, 0x05, 0xe6, + 0x80, 0x2c, 0xd7, 0x17, 0xd9, 0x61, 0x1e, 0x05, 0x16, 0xbb, 0x86, 0xcc, 0xae, 0xa0, 0xc7, 0xd0, + 0x95, 0x52, 0x1c, 0x1b, 0xbf, 0x0b, 0xfc, 0xf1, 0x9c, 0x17, 0xff, 0xcb, 0x6a, 0x35, 0x5d, 0x51, + 0x2a, 0x61, 0xea, 0x83, 0xa8, 0xa7, 0x3b, 0x70, 0xb6, 0xd0, 0x64, 0x7a, 0x76, 0xf1, 0xdd, 0x14, + 0xe6, 0xce, 0x2e, 0xb1, 0x63, 0x25, 0x17, 0x5f, 0x82, 0x96, 0xb8, 0x65, 0xb1, 0x98, 0x67, 0x07, + 0x18, 0xc1, 0xea, 0x4e, 0x18, 0x39, 0x63, 0x2b, 0x62, 0xc0, 0x4f, 0x35, 0xe6, 0x09, 0x71, 0x53, + 0x0d, 0xf1, 0x54, 0xfa, 0xf6, 0x52, 0xdd, 0x10, 0x31, 0xfe, 0x1e, 0xce, 0xcd, 0xb0, 0x2a, 0xa3, + 0x5c, 0x86, 0xea, 0xd0, 0x9f, 0xca, 0xf6, 0x5c, 0x99, 0x88, 0x01, 0x5a, 0x03, 0xa0, 0x41, 0xf0, + 0x4c, 0x7b, 0xbc, 0x28, 0x14, 0xfc, 0x2b, 0x58, 0xd2, 0x1a, 0x37, 0x69, 0x07, 0x4f, 0x51, 0x33, + 0x72, 0x6a, 0x7f, 0x35, 0xe0, 0x4c, 0x5c, 0x93, 0xbf, 0x3d, 0xfc, 0x81, 0x0e, 0xc5, 0x7b, 0x75, + 0x8e, 0x0c, 0x3c, 0x86, 0x93, 0xb2, 0xa4, 0x0f, 0x78, 0x85, 0x96, 0xd5, 0x89, 0xad, 0x53, 0x37, + 0x5b, 0xfe, 0x53, 0x09, 0x92, 0x57, 0xc2, 0x11, 0x9c, 0xcc, 0xc9, 0xb1, 0x40, 0x44, 0xe5, 0x57, + 0x5b, 0x91, 0x29, 0x05, 0xfd, 0x16, 0xda, 0xb6, 0x16, 0x47, 0xee, 0xe6, 0xa2, 0x87, 0x49, 0x32, + 0xe2, 0x78, 0x1f, 0xda, 0xba, 0x04, 0x33, 0xe9, 0xf3, 0x2f, 0xd5, 0x64, 0x4a, 0x49, 0xf9, 0xcf, + 0xd3, 0x6e, 0x92, 0x42, 0xc1, 0xc7, 0xb0, 0x1a, 0xcf, 0x28, 0x02, 0x91, 0x96, 0xe3, 0xb5, 0x39, + 0x80, 0x65, 0xbb, 0x80, 0x2f, 0xc1, 0x7d, 0x2e, 0xe7, 0xb8, 0x36, 0x49, 0xa1, 0x2a, 0xfe, 0x9b, + 0x01, 0xcb, 0x45, 0xe2, 0x1f, 0x4d, 0xdf, 0x2a, 0xd4, 0xc5, 0xa8, 0xdf, 0x7f, 0x2a, 0x43, 0x49, + 0x09, 0xca, 0xda, 0xca, 0xdc, 0x30, 0xa9, 0x72, 0xf1, 0xda, 0xa6, 0x12, 0x24, 0xaf, 0x84, 0x83, + 0x64, 0x6d, 0x53, 0x62, 0xc1, 0xda, 0x09, 0xb4, 0x7d, 0xea, 0xda, 0x31, 0xef, 0xfd, 0xc4, 0x2f, + 0xe9, 0x7d, 0x42, 0xc0, 0xff, 0x52, 0x30, 0xde, 0x77, 0x46, 0xa3, 0x67, 0xbe, 0xed, 0x8c, 0xe6, + 0xba, 0x9a, 0xf7, 0xa0, 0x35, 0xb4, 0x5c, 0xe7, 0x30, 0xb0, 0x22, 0x6a, 0xf7, 0x07, 0x7b, 0x72, + 0xbf, 0xeb, 0x4d, 0x5e, 0x5d, 0x04, 0xdd, 0x85, 0x45, 0xff, 0xf0, 0x07, 0x86, 0xe1, 0xb8, 0xc3, + 0xbd, 0x96, 0x0d, 0x8b, 0x39, 0xa5, 0x6c, 0x89, 0x44, 0x9e, 0x3d, 0x68, 0x57, 0x0a, 0x65, 0xd8, + 0x69, 0x9e, 0xae, 0xb1, 0xb2, 0xa6, 0x19, 0x2a, 0xda, 0x02, 0x34, 0xf4, 0xc7, 0x13, 0x2b, 0xa0, + 0xb6, 0x22, 0x2b, 0x52, 0x54, 0xc0, 0x29, 0x58, 0x8a, 0xf2, 0xe7, 0x6d, 0x23, 0x0b, 0xcc, 0x82, + 0x5c, 0x0b, 0xc0, 0xef, 0x40, 0x47, 0x62, 0x2a, 0xe1, 0xe4, 0x9e, 0x07, 0x83, 0x8c, 0x00, 0xc9, + 0xa9, 0x60, 0x02, 0x9d, 0xac, 0xd4, 0x47, 0xf1, 0xbd, 0x06, 0x30, 0xe6, 0x92, 0x83, 0x83, 0xa7, + 0xe2, 0x98, 0xac, 0x13, 0x85, 0x72, 0xe5, 0x7f, 0x06, 0xb4, 0x73, 0xad, 0xfe, 0xb6, 0xde, 0x4a, + 0xe9, 0xfc, 0x02, 0xd5, 0xa1, 0xca, 0xdf, 0x28, 0x1d, 0x03, 0x35, 0xa0, 0x26, 0xef, 0xe8, 0x9d, + 0x12, 0xea, 0x40, 0x53, 0xbd, 0x24, 0x76, 0xca, 0xe8, 0x34, 0x2c, 0x15, 0x1c, 0x65, 0x9d, 0x0a, + 0x3a, 0x03, 0x2b, 0x85, 0xf5, 0xbf, 0x53, 0x45, 0x27, 0xa0, 0xa1, 0xd4, 0xf0, 0xce, 0x02, 0x6a, + 0x41, 0x3d, 0x79, 0xb7, 0x77, 0x6a, 0x68, 0x11, 0x2a, 0xec, 0x3a, 0xd2, 0x59, 0x44, 0xa6, 0x76, + 0xf7, 0x4e, 0x76, 0x52, 0xa7, 0x8e, 0x56, 0xc1, 0x54, 0x38, 0x69, 0x86, 0x98, 0x71, 0xe8, 0xfd, + 0x1d, 0x60, 0xa1, 0xcf, 0xff, 0xe0, 0x45, 0xd7, 0xa1, 0xca, 0xff, 0xec, 0x41, 0xe9, 0x29, 0xca, + 0xff, 0xde, 0xed, 0xa6, 0xa7, 0x97, 0xfe, 0x67, 0xd0, 0x2d, 0xa8, 0xec, 0x7a, 0x4e, 0x84, 0xd4, + 0xbb, 0x66, 0xd2, 0x7e, 0xee, 0xae, 0x64, 0xa8, 0x52, 0x69, 0x0b, 0xaa, 0xfc, 0xbf, 0x08, 0x94, + 0xf2, 0xd5, 0xff, 0x26, 0xba, 0x19, 0xe3, 0xe8, 0xb1, 0x96, 0x02, 0x74, 0x36, 0xfd, 0x63, 0x2b, + 0xf7, 0xaf, 0x44, 0x77, 0xb5, 0x98, 0x29, 0x2d, 0xdf, 0xe6, 0xff, 0x44, 0x6b, 0x96, 0xd5, 0x3f, + 0x1d, 0x94, 0x30, 0xf5, 0xf6, 0xfc, 0x6d, 0xa8, 0xf2, 0x8e, 0x29, 0xca, 0x75, 0x50, 0xb3, 0x7a, + 0x7a, 0x1b, 0xf6, 0x20, 0x0b, 0x17, 0x94, 0xee, 0xf3, 0xc2, 0xde, 0x69, 0xf7, 0xfc, 0x4c, 0xbe, + 0x9c, 0xf2, 0x1a, 0x54, 0xf6, 0x1d, 0xef, 0x48, 0xc9, 0xb8, 0xd2, 0x99, 0xcb, 0xa5, 0xee, 0x16, + 0x54, 0x18, 0x58, 0x14, 0x69, 0xa5, 0xd7, 0xd6, 0x5d, 0xc9, 0x76, 0x41, 0x84, 0x89, 0xfb, 0x0a, + 0xc2, 0xd0, 0x19, 0x4d, 0x46, 0xed, 0x49, 0x75, 0xcd, 0x22, 0x96, 0x6c, 0x24, 0x95, 0x9e, 0xbf, + 0x43, 0xca, 0xd3, 0x27, 0xee, 0xf4, 0x74, 0x97, 0x34, 0x5a, 0x9a, 0x5e, 0xbe, 0x83, 0x94, 0xf4, + 0xaa, 0x9d, 0x12, 0x25, 0xbd, 0x7a, 0xdf, 0xe2, 0xd7, 0xc9, 0x76, 0x43, 0xa7, 0xb3, 0x6f, 0xe9, + 0x22, 0x27, 0xf5, 0x57, 0xf9, 0x13, 0xbe, 0x3f, 0x93, 0xb7, 0x2a, 0x5a, 0x55, 0x52, 0x9f, 0x7b, + 0x25, 0x77, 0xcf, 0xcd, 0xe0, 0x6a, 0x93, 0xa5, 0x2f, 0x39, 0x6d, 0xb2, 0xec, 0x8b, 0x54, 0x9f, + 0x2c, 0xff, 0x8c, 0xfc, 0xbe, 0xb0, 0x4e, 0xa0, 0x0b, 0xd9, 0x5b, 0x72, 0xc1, 0x1d, 0xbc, 0x7b, + 0xf1, 0xc3, 0x42, 0xd2, 0xc2, 0x68, 0x46, 0xc1, 0x41, 0x97, 0x52, 0xf5, 0x0f, 0x5c, 0x83, 0xbb, + 0x1b, 0x1f, 0x13, 0x93, 0x76, 0xac, 0xe2, 0x9a, 0x84, 0xf0, 0x8c, 0xa3, 0x43, 0xb9, 0x68, 0x76, + 0x2f, 0x7d, 0xf8, 0xb2, 0x93, 0x9a, 0x98, 0x59, 0xdc, 0x0a, 0xcc, 0xe4, 0xce, 0xfa, 0xee, 0x17, + 0x1f, 0x92, 0xe1, 0x26, 0x1e, 0x36, 0xff, 0x00, 0x5b, 0xd7, 0xef, 0x49, 0xb1, 0xc3, 0x05, 0xfe, + 0x71, 0xeb, 0xa7, 0x00, 0x00, 0x00, 0xff, 0xff, 0x3e, 0xae, 0xce, 0xe0, 0x16, 0x23, 0x00, 0x00, } diff --git a/sqle/driver/v2/proto/driver_v2.proto b/sqle/driver/v2/proto/driver_v2.proto index f738268d6f..04ffd79223 100644 --- a/sqle/driver/v2/proto/driver_v2.proto +++ b/sqle/driver/v2/proto/driver_v2.proto @@ -32,6 +32,8 @@ service Driver { rpc GetTableMeta(GetTableMetaRequest) returns (GetTableMetaResponse); rpc ExtractTableFromSQL(ExtractTableFromSQLRequest) returns (ExtractTableFromSQLResponse); rpc EstimateSQLAffectRows(EstimateSQLAffectRowsRequest) returns (EstimateSQLAffectRowsResponse); // Introduced from SQLE v2.2304.0 + rpc GetDatabaseObjectDDL(DatabaseObjectInfoRequest) returns (DatabaseSchemaObjectResponse); + rpc GetDatabaseDiffModifySQL(DatabaseDiffModifyRequest) returns (DatabaseDiffModifyRponse); } message Empty {} @@ -49,15 +51,17 @@ enum OptionalModule { EstimateSQLAffectRows = 5; KillProcess = 6; ExecBatch = 7; - I18n = 8; + I18n = 8; // 插件是否支持国际化 + GetDatabaseObjectDDL = 9; + GetDatabaseDiffModifySQL = 10; } message Param { string key = 1; string value = 2; - string desc = 3; + string desc = 3; // 不支持国际化时,sqle从此取值 string type = 4; - map i18nDesc = 5; + map i18nDesc = 5; // 支持国际化时,sqle从此取值,必须包含默认语言 } message DSN { @@ -71,13 +75,13 @@ message DSN { message Rule { string name = 1; - string desc = 2; + string desc = 2; // 不支持国际化时,sqle从此取值 string level = 3; - string category = 4; + string category = 4; // 不支持国际化时,sqle从此取值 repeated Param params = 5; - string annotation = 6; - Knowledge knowledge = 7; - map i18nRuleInfo = 8; + string annotation = 6; // 不支持国际化时,sqle从此取值 + Knowledge knowledge = 7; // 不支持国际化时,sqle从此取值 + map i18nRuleInfo = 8; // 支持国际化时,sqle从此取值,必须包含默认语言 } message I18nRuleInfo { @@ -158,10 +162,10 @@ message I18nAuditResultInfo { } message AuditResult { - string message = 1; + string message = 1; // 不支持国际化时,sqle从此取值 string level = 2; string rule_name = 3; - map i18nAuditResultInfo = 4; + map i18nAuditResultInfo = 4; // 支持国际化时,sqle从此取值,必须包含默认语言 } message AuditResults { @@ -188,8 +192,8 @@ message I18nRollbackSQLInfo { message RollbackSQL { string query = 1; - string message = 2; - map i18nRollbackSQLInfo = 3; + string message = 2; // 不支持国际化时,sqle从此取值 + map i18nRollbackSQLInfo = 3; // 支持国际化时,sqle从此取值,必须包含默认语言 } message GenRollbackSQLResponse { @@ -332,8 +336,8 @@ message IndexesInfo { message TabularDataHead { string name = 1; - string desc = 2; - map i18nDesc = 3; + string desc = 2; // 不支持国际化时,sqle从此取值 + map i18nDesc = 3; // 支持国际化时,sqle从此取值,必须包含默认语言 } message TabularDataRows { @@ -378,3 +382,51 @@ message KillProcessResponse { string errMessage = 1; // 记录执行失败原因 } +message DatabaseObjectInfoRequest { + Session session = 1; + repeated DatabasSchemaInfo databasSchemaInfo = 2; +} + +message DatabasSchemaInfo { + string schemaName = 1; + repeated DatabaseObject databaseObject = 2; +} + +message DatabaseObject { + string objectName = 1; + string objectType = 2; +} +message DatabaseSchemaObjectResponse { + repeated DatabaseSchemaObject databaseSchemaObject = 1; +} +message DatabaseSchemaObject { + string schemaName = 1; + string schemaDDL = 2; + repeated DatabaseObjectDDL databaseObjectDDL = 3; +} + +message DatabaseObjectDDL { + DatabaseObject databaseObject = 1; + string objectDDL = 2; +} + +message DatabaseDiffModifyRequest { + Session session = 1; + DSN calibratedDSN = 2; + repeated DatabasDiffSchemaInfo objInfos = 3; +} + +message DatabasDiffSchemaInfo { + string baseSchemaName = 1; + string comparedSchemaName = 2; + repeated DatabaseObject databaseObject = 3; +} + +message DatabaseDiffModifyRponse { + repeated SchemaDiffModify schemaDiffModify = 1; +} + +message SchemaDiffModify { + string schemaName = 1; + repeated string modifySQLs = 2; +} \ No newline at end of file diff --git a/sqle/driver/v2/util.go b/sqle/driver/v2/util.go index 36ab475f6e..29b5e84f10 100644 --- a/sqle/driver/v2/util.go +++ b/sqle/driver/v2/util.go @@ -437,3 +437,41 @@ func RandStr(length int) string { } return string(result) } + +func ConvertDatabasSchemaInfoToProto(infos []*DatabasCompareSchemaInfo) []*protoV2.DatabasDiffSchemaInfo { + dbInfoReq := make([]*protoV2.DatabasDiffSchemaInfo, 0, len(infos)) + for i, dbSchema := range infos { + dbObjs := make([]*protoV2.DatabaseObject, len(dbSchema.DatabaseObjects)) + for j, dbObj := range dbSchema.DatabaseObjects { + dbObjs[j] = &protoV2.DatabaseObject{ + ObjectName: dbObj.ObjectName, + ObjectType: dbObj.ObjectType, + } + } + dbInfoReq[i] = &protoV2.DatabasDiffSchemaInfo{ + BaseSchemaName: dbSchema.BaseSchemaName, + ComparedSchemaName: dbSchema.ComparedSchemaName, + DatabaseObject: dbObjs, + } + } + return dbInfoReq +} + +func ConvertProtoDatabaseDiffReqToDriver(infos []*protoV2.DatabasDiffSchemaInfo) []*DatabasCompareSchemaInfo { + dbInfoReq := make([]*DatabasCompareSchemaInfo, len(infos)) + for i, dbSchema := range infos { + dbObjs := make([]*DatabaseObject, len(dbSchema.DatabaseObject)) + for j, dbObj := range dbSchema.DatabaseObject { + dbObjs[j] = &DatabaseObject{ + ObjectName: dbObj.ObjectName, + ObjectType: dbObj.ObjectType, + } + } + dbInfoReq[i] = &DatabasCompareSchemaInfo{ + BaseSchemaName: dbSchema.BaseSchemaName, + ComparedSchemaName: dbSchema.ComparedSchemaName, + DatabaseObjects: dbObjs, + } + } + return dbInfoReq +} diff --git a/sqle/model/instance_audit_plan.go b/sqle/model/instance_audit_plan.go index 7cc4a12d32..31fecb9a97 100644 --- a/sqle/model/instance_audit_plan.go +++ b/sqle/model/instance_audit_plan.go @@ -186,7 +186,6 @@ type SQLManageRecord struct { Info JSON `gorm:"type:json"` // 慢日志的 执行时间等特殊属性 AuditLevel string `json:"audit_level" gorm:"type:varchar(255)"` AuditResults AuditResults `json:"audit_results" gorm:"type:json"` - EndPoint string `json:"endpoint" gorm:"type:varchar(255)"` SQLID string `json:"sql_id" gorm:"type:varchar(255);unique;not null"` Priority sql.NullString `json:"priority" gorm:"type:varchar(255)"` @@ -463,9 +462,7 @@ type SQLManageQueue struct { SqlFingerprint string `json:"sql_fingerprint" gorm:"type:mediumtext;not null"` SqlText string `json:"sql_text" gorm:"type:mediumtext;not null"` Info JSON `gorm:"type:json"` // 慢日志的 执行时间等特殊属性 - EndPoint string `json:"endpoint" gorm:"type:varchar(255)"` - - SQLID string `json:"sql_id" gorm:"type:varchar(255);not null"` + SQLID string `json:"sql_id" gorm:"type:varchar(255);not null"` } func (s *Storage) PushSQLToManagerSQLQueue(sqls []*SQLManageQueue) error { diff --git a/sqle/model/instance_audit_plan_list.go b/sqle/model/instance_audit_plan_list.go index 69917150df..f9e7f90f39 100644 --- a/sqle/model/instance_audit_plan_list.go +++ b/sqle/model/instance_audit_plan_list.go @@ -28,6 +28,7 @@ SELECT instance_audit_plans.active_status, instance_audit_plans.create_user_id, instance_audit_plans.created_at, + instance_audit_plans.token, audit_plans.audit_plan_ids diff --git a/sqle/model/workflow_list.go b/sqle/model/workflow_list.go index 4583e6b66b..5bf43c600b 100644 --- a/sqle/model/workflow_list.go +++ b/sqle/model/workflow_list.go @@ -190,6 +190,10 @@ func (s *Storage) GetWorkflowsByReq(data map[string]interface{}) ( return result, count, err } +func (s *Storage) GetGlobalWorkflowTotalNum(data map[string]interface{}) (count uint64, err error) { + return s.getCountResult(workflowsQueryBodyTpl, workflowsCountTpl, data) +} + func (s *Storage) GetWorkflowCountByReq(data map[string]interface{}) (uint64, error) { return s.getCountResult(workflowsQueryBodyTpl, workflowsCountTpl, data) } diff --git a/sqle/pkg/driver/impl.go b/sqle/pkg/driver/impl.go index 08835e21d7..6fe9876a44 100644 --- a/sqle/pkg/driver/impl.go +++ b/sqle/pkg/driver/impl.go @@ -7,12 +7,12 @@ import ( "fmt" "time" + "github.com/actiontech/sqle/sqle/driver/mysql/util" driverV2 "github.com/actiontech/sqle/sqle/driver/v2" "github.com/actiontech/sqle/sqle/pkg/params" "github.com/actiontech/sqle/sqle/utils" hclog "github.com/hashicorp/go-hclog" - "github.com/percona/go-mysql/query" "github.com/pkg/errors" "vitess.io/vitess/go/vt/sqlparser" ) @@ -200,10 +200,14 @@ func (p *DriverImpl) Parse(ctx context.Context, sql string) ([]driverV2.Node, er nodes := make([]driverV2.Node, 0, len(sqls)) for _, sql := range sqls { + fp, err := util.Fingerprint(sql, true) + if err != nil { + return nil, errors.Wrapf(err, "fingerprint of sql: %s", sql) + } n := driverV2.Node{ Text: sql, Type: classifySQL(sql), - Fingerprint: query.Fingerprint(sql), + Fingerprint: fp, } nodes = append(nodes, n) } @@ -285,3 +289,11 @@ func (p *DriverImpl) EstimateSQLAffectRows(ctx context.Context, sql string) (*dr func (p *DriverImpl) KillProcess(ctx context.Context) (*driverV2.KillProcessInfo, error) { return &driverV2.KillProcessInfo{}, nil } + +func (p *DriverImpl) GetDatabaseObjectDDL(ctx context.Context, objInfos []*driverV2.DatabasSchemaInfo) ([]*driverV2.DatabaseSchemaObjectResult, error) { + return []*driverV2.DatabaseSchemaObjectResult{}, nil +} + +func (p *DriverImpl) GetDatabaseDiffModifySQL(ctx context.Context, calibratedDSN *driverV2.DSN, objInfos []*driverV2.DatabasCompareSchemaInfo) ([]*driverV2.DatabaseDiffModifySQLResult, error) { + return []*driverV2.DatabaseDiffModifySQLResult{}, nil +} diff --git a/sqle/server/auditplan/job_task_handler.go b/sqle/server/auditplan/job_task_handler.go index 6999e3da35..e4fca607e2 100644 --- a/sqle/server/auditplan/job_task_handler.go +++ b/sqle/server/auditplan/job_task_handler.go @@ -187,42 +187,76 @@ func SetSQLPriority(sqlList []*model.SQLManageRecord) ([]*model.SQLManageRecord, } auditPlanMap[sourceId] = auditPlan } - - info, err := sql_.Info.OriginValue() + priority, _, err := GetSingleSQLPriorityWithReasons(auditPlan, sql_) if err != nil { return nil, err } - highPriorityConditions := auditPlan.HighPriorityParams - priority := sql.NullString{} - for _, highPriorityCondition := range highPriorityConditions { - var compareParamVale string - // 审核级别特殊处理 - if highPriorityCondition.Key == OperationParamAuditLevel { - switch sql_.AuditLevel { - case string(driverV2.RuleLevelNotice): - compareParamVale = "1" - case string(driverV2.RuleLevelWarn): - compareParamVale = "2" - case string(driverV2.RuleLevelError): - compareParamVale = "3" - default: - compareParamVale = "0" - } - } else { - infoV, ok := info[highPriorityCondition.Key] - if !ok { - continue - } - compareParamVale = fmt.Sprintf("%v", infoV) - } - if high, err := highPriorityConditions.CompareParamValue(highPriorityCondition.Key, compareParamVale); err == nil && high { - priority = sql.NullString{ - String: model.PriorityHigh, - Valid: true, - } + if priority == model.PriorityHigh { + sqlList[i].Priority = sql.NullString{ + String: model.PriorityHigh, + Valid: true, } } - sqlList[i].Priority = priority } return sqlList, nil } + +// 获取SQL的优先级以及优先级触发的原因,只有高优先级或者无优先级,若是高优先级,则返回model.PriorityHigh=high,如果无优先级则返回空字符串 +func GetSingleSQLPriorityWithReasons(auditPlan *model.AuditPlanV2, sql *model.SQLManageRecord) (priority string, reasons []string, err error) { + if auditPlan == nil || sql == nil { + return "", reasons, nil + } + info, err := sql.Info.OriginValue() + if err != nil { + return "", nil, err + } + toAuditLevel := func(valueToBeCompared string) string { + switch valueToBeCompared { + case "1": + return "提示" + case "2": + return "警告" + case "3": + return "错误" + default: + return valueToBeCompared + } + } + highPriorityConditions := auditPlan.HighPriorityParams + // 遍历优先级条件 + for _, highPriorityCondition := range highPriorityConditions { + var valueToBeCompared string + // 特殊处理审核级别 + if highPriorityCondition.Key == OperationParamAuditLevel { + switch sql.AuditLevel { + case string(driverV2.RuleLevelNotice): + valueToBeCompared = "1" + case string(driverV2.RuleLevelWarn): + valueToBeCompared = "2" + case string(driverV2.RuleLevelError): + valueToBeCompared = "3" + default: + valueToBeCompared = "0" + } + } else { + // 获取信息中的相应字段值 + infoV, ok := info[highPriorityCondition.Key] + if !ok { + continue + } + valueToBeCompared = fmt.Sprintf("%v", infoV) + } + // 检查是否为高优先级条件 + if high, err := highPriorityConditions.CompareParamValue(highPriorityCondition.Key, valueToBeCompared); err == nil && high { + // 添加匹配的条件作为原因 + if highPriorityCondition.Key == OperationParamAuditLevel { + valueToBeCompared = toAuditLevel(valueToBeCompared) + } + reasons = append(reasons, fmt.Sprintf("【%v %v %v,为:%s】", highPriorityCondition.Desc, highPriorityCondition.Operator.Value, highPriorityCondition.Param.Value, valueToBeCompared)) + } + } + if len(reasons) > 0 { + return model.PriorityHigh, reasons, nil + } + return "", reasons, nil +} diff --git a/sqle/server/auditplan/meta.go b/sqle/server/auditplan/meta.go index 2a823b1fa9..f3e8fcbcee 100644 --- a/sqle/server/auditplan/meta.go +++ b/sqle/server/auditplan/meta.go @@ -183,6 +183,8 @@ func GetMeta(typ string) (Meta, error) { var supportedCmdTypeList = map[string]struct{}{ TypeMySQLSlowLog: {}, TypeTiDBAuditLog: {}, + TypeAllAppExtract: {}, + TypeDefault: {}, } func GetSupportedScannerAuditPlanType() map[string]struct{} { diff --git a/sqle/server/auditplan/metrics.go b/sqle/server/auditplan/metrics.go index 103258052d..9f2cc208fb 100644 --- a/sqle/server/auditplan/metrics.go +++ b/sqle/server/auditplan/metrics.go @@ -1,6 +1,8 @@ package auditplan -import "time" +import ( + "time" +) const MetricNameCounter string = "counter" // 总次数 const MetricNameLastReceiveTimestamp string = "last_receive_timestamp" @@ -43,7 +45,7 @@ var ALLMetric = map[string]MetricType{ MetricNameRowExaminedAvg: MetricTypeFloat, // MySQL slow log MetricNameFirstQueryAt: MetricTypeString, // MySQL slow log, 好像没用上 | OB MySQL TOP SQL MetricNameDBUser: MetricTypeString, // MySQL slow log - MetricNameEndpoints: MetricTypeString, // MySQL slow log + MetricNameEndpoints: MetricTypeArray, // MySQL slow log MetricNameStartTimeOfLastScrapedSQL: MetricTypeString, // MySQL slow log MetricNameMetaName: MetricTypeString, // MySQL schema meta MetricNameMetaType: MetricTypeString, // MySQL schema meta @@ -94,7 +96,20 @@ func LoadMetrics(info map[string]interface{}, metrics []string) Metrics { } else { ms.SetString(metric, "") } - + case MetricTypeArray: + if ss, ok := v.([]string); ok { + ms.SetStringArray(metric, ss) + } else if ss, ok := v.([]interface{}); ok { + var valList []string + for _, s := range ss { + if val, ok := s.(string); ok { + valList = append(valList, val) + } + } + ms.SetStringArray(metric, valList) + } else { + ms.SetStringArray(metric, nil) + } case MetricTypeTime: if t, ok := v.(*time.Time); ok { ms.SetTime(metric, t) @@ -152,6 +167,7 @@ const ( MetricTypeString MetricType = 3 MetricTypeTime MetricType = 4 MetricTypeBool MetricType = 5 + MetricTypeArray MetricType = 6 ) type Metric struct { @@ -161,6 +177,7 @@ type Metric struct { f float64 t *time.Time b bool + ss []string typ MetricType } @@ -185,6 +202,13 @@ func (m *Metric) String() string { return m.s } +func (m *Metric) StringArray() []string { + if m == nil || m.typ != MetricTypeArray { + return nil + } + return m.ss +} + func (m *Metric) Time() *time.Time { if m == nil || m.typ != MetricTypeTime { return nil @@ -210,6 +234,8 @@ func (m *Metric) Value() interface{} { return m.f case MetricTypeString: return m.s + case MetricTypeArray: + return m.ss case MetricTypeTime: return m.t case MetricTypeBool: @@ -250,6 +276,13 @@ func (m Metrics) SetString(name string, s string) { } } +func (m Metrics) SetStringArray(name string, ss []string) { + m[name] = &Metric{ + typ: MetricTypeArray, + ss: ss, + } +} + func (m Metrics) SetTime(name string, t *time.Time) { m[name] = &Metric{ typ: MetricTypeTime, diff --git a/sqle/server/auditplan/sql_info.go b/sqle/server/auditplan/sql_info.go index b050f5a7fa..6c4f41d543 100644 --- a/sqle/server/auditplan/sql_info.go +++ b/sqle/server/auditplan/sql_info.go @@ -125,7 +125,6 @@ func ConvertSQLV2ToMangerSQL(sql *SQLV2) *model.SQLManageRecord { SqlFingerprint: sql.Fingerprint, SqlText: sql.SQLContent, Info: data, - EndPoint: sql.Info.Get("endpoints").String(), } } @@ -141,6 +140,5 @@ func ConvertSQLV2ToMangerSQLQueue(sql *SQLV2) *model.SQLManageQueue { SqlFingerprint: sql.Fingerprint, SqlText: sql.SQLContent, Info: data, - EndPoint: sql.Info.Get("endpoints").String(), } } diff --git a/sqle/server/auditplan/task_type_mysql_processlist.go b/sqle/server/auditplan/task_type_mysql_processlist.go index 1e963eb24a..7944520445 100644 --- a/sqle/server/auditplan/task_type_mysql_processlist.go +++ b/sqle/server/auditplan/task_type_mysql_processlist.go @@ -3,6 +3,7 @@ package auditplan import ( "context" "fmt" + "github.com/actiontech/sqle/sqle/errors" "strconv" "time" @@ -77,10 +78,13 @@ func (at *MySQLProcessListTaskV2) ExtractSQL(logger *logrus.Entry, ap *AuditPlan ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) defer cancel() - instance, _, err := dms.GetInstancesById(ctx, ap.InstanceID) + instance, exist, err := dms.GetInstancesById(ctx, ap.InstanceID) if err != nil { return nil, fmt.Errorf("get instance fail, error: %v", err) } + if !exist { + return nil, errors.NewInstanceNoExistErr() + } db, err := executor.NewExecutor(logger, &driverV2.DSN{ Host: instance.Host, diff --git a/sqle/server/auditplan/task_type_mysql_schema_meta.go b/sqle/server/auditplan/task_type_mysql_schema_meta.go index 48d14057f2..2511a65403 100644 --- a/sqle/server/auditplan/task_type_mysql_schema_meta.go +++ b/sqle/server/auditplan/task_type_mysql_schema_meta.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "github.com/actiontech/sqle/sqle/errors" "strconv" "time" @@ -47,10 +48,13 @@ func (at *BaseSchemaMetaTaskV2) extractSQL(logger *logrus.Entry, ap *AuditPlan, sqls := []*SchemaMetaSQL{} ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) defer cancel() - instance, _, err := dms.GetInstancesById(ctx, ap.InstanceID) + instance, exist, err := dms.GetInstancesById(ctx, ap.InstanceID) if err != nil { return nil, fmt.Errorf("get instance fail, error: %v", err) } + if !exist { + return nil, errors.NewInstanceNoExistErr() + } db, err := executor.NewExecutor(logger, &driverV2.DSN{ Host: instance.Host, Port: instance.Port, diff --git a/sqle/server/auditplan/task_type_oracle_topsql.go b/sqle/server/auditplan/task_type_oracle_topsql.go index 0ea86fc8c7..8891228885 100644 --- a/sqle/server/auditplan/task_type_oracle_topsql.go +++ b/sqle/server/auditplan/task_type_oracle_topsql.go @@ -3,6 +3,7 @@ package auditplan import ( "context" "fmt" + "github.com/actiontech/sqle/sqle/errors" "strconv" "time" @@ -95,10 +96,13 @@ func (at *OracleTopSQLTaskV2) ExtractSQL(logger *logrus.Entry, ap *AuditPlan, pe ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) defer cancel() - inst, _, err := dms.GetInstancesById(ctx, ap.InstanceID) + inst, exist, err := dms.GetInstancesById(ctx, ap.InstanceID) if err != nil { return nil, fmt.Errorf("get instance fail, error: %v", err) } + if !exist { + return nil, errors.NewInstanceNoExistErr() + } // This depends on: https://github.com/actiontech/sqle-oracle-plugin. // If your Oracle db plugin does not implement the parameter `service_name`, // you can only use the default service name `XE`. diff --git a/sqle/server/auditplan/task_wrap.go b/sqle/server/auditplan/task_wrap.go index bda2a50c3e..f196f31b08 100644 --- a/sqle/server/auditplan/task_wrap.go +++ b/sqle/server/auditplan/task_wrap.go @@ -320,7 +320,12 @@ func (at *TaskWrapper) filterSqlManageQueue(sqlList []*model.SQLManageQueue) (ma instanceMap[sql.InstanceID] = instName } - matchedID, isInBlacklist := FilterSQLsByBlackList(sql.EndPoint, sql.SqlText, sql.SqlFingerprint, instName, blacklist) + value, err := sql.Info.OriginValue() + if err != nil { + return nil, nil, err + } + metrics := LoadMetrics(value, []string{MetricNameEndpoints}) + matchedID, isInBlacklist := FilterSQLsByBlackList(metrics.Get(MetricNameEndpoints).StringArray(), sql.SqlText, sql.SqlFingerprint, instName, blacklist) if isInBlacklist { matchedCount[matchedID]++ continue @@ -332,14 +337,14 @@ func (at *TaskWrapper) filterSqlManageQueue(sqlList []*model.SQLManageQueue) (ma return matchedCount, SqlQueueList, nil } -func FilterSQLsByBlackList(endpoint, sqlText, sqlFp, instName string, blacklist []*model.BlackListAuditPlanSQL) (uint, bool) { +func FilterSQLsByBlackList(endpoint []string, sqlText, sqlFp, instName string, blacklist []*model.BlackListAuditPlanSQL) (uint, bool) { if len(blacklist) == 0 { return 0, false } filter := ConvertToBlackFilter(blacklist) - matchedID, hasEndpointInBlacklist := filter.HasEndpointInBlackList([]string{endpoint}) + matchedID, hasEndpointInBlacklist := filter.HasEndpointInBlackList(endpoint) if hasEndpointInBlacklist { return matchedID, true } diff --git a/sqle/server/pipeline/pipeline.go b/sqle/server/pipeline/pipeline.go index 358dde1aa0..88378d690b 100644 --- a/sqle/server/pipeline/pipeline.go +++ b/sqle/server/pipeline/pipeline.go @@ -3,6 +3,7 @@ package pipeline import ( "context" "fmt" + "github.com/actiontech/sqle/sqle/errors" "net" "net/url" "time" @@ -41,10 +42,13 @@ func (node PipelineNode) IntegrationInfo(ctx context.Context, projectName string return "", err } if node.InstanceID != 0 { - instance, _, err := dms.GetInstancesById(context.TODO(), fmt.Sprint(node.InstanceID)) + instance, exist, err := dms.GetInstancesById(context.TODO(), fmt.Sprint(node.InstanceID)) if err != nil { return "", err } + if !exist { + return "", errors.NewInstanceNoExistErr() + } node.InstanceName = instance.Name } diff --git a/sqle/server/sqled_test.go b/sqle/server/sqled_test.go index 048cb9f3bf..7719c8856c 100644 --- a/sqle/server/sqled_test.go +++ b/sqle/server/sqled_test.go @@ -106,6 +106,14 @@ func (d *mockDriver) EstimateSQLAffectRows(ctx context.Context, sql string) (*dr return nil, nil } +func (d *mockDriver) GetDatabaseObjectDDL(ctx context.Context, objInfos []*driverV2.DatabasSchemaInfo) ([]*driverV2.DatabaseSchemaObjectResult, error) { + return nil, nil +} + +func (d *mockDriver) GetDatabaseDiffModifySQL(ctx context.Context, calibratedDSN *driverV2.DSN, objInfos []*driverV2.DatabasCompareSchemaInfo) ([]*driverV2.DatabaseDiffModifySQLResult, error) { + return nil, nil +} + func TestAction_validation(t *testing.T) { actions := map[int]*action{ ActionTypeAudit: {typ: ActionTypeAudit}, diff --git a/sqle/utils/util.go b/sqle/utils/util.go index 1b50a37e8d..b1b31f9d07 100644 --- a/sqle/utils/util.go +++ b/sqle/utils/util.go @@ -12,6 +12,7 @@ import ( "math" "net/url" "regexp" + "sort" "strconv" "strings" "sync" @@ -86,6 +87,25 @@ func RemoveDuplicate(c []string) []string { return result } +func MergeAndDeduplicateSort(arr1, arr2 []string) []string { + // 合并两个数组 + merged := append(arr1, arr2...) + + // 排序 + sort.Strings(merged) + + // 去重 + i := 0 + for j := 1; j < len(merged); j++ { + if merged[i] != merged[j] { + i++ + merged[i] = merged[j] + } + } + + return merged[:i+1] +} + func RemoveDuplicatePtrUint64(c []*uint64) []*uint64 { var tmpMap = map[uint64]struct{}{} var result = []*uint64{} @@ -409,20 +429,20 @@ func TruncateAndMarkForExcelCell(s string) string { } func IntersectionStringSlice(slice1, slice2 []string) []string { - // 用 map 来存储第一个切片的元素 - elemMap := make(map[string]bool) - for _, v := range slice1 { - elemMap[v] = true - } - - // 遍历第二个切片,找到交集 - var intersection []string - for _, v := range slice2 { - if elemMap[v] { - intersection = append(intersection, v) - // 删除元素以防重复添加 - delete(elemMap, v) - } - } - return intersection -} \ No newline at end of file + // 用 map 来存储第一个切片的元素 + elemMap := make(map[string]bool) + for _, v := range slice1 { + elemMap[v] = true + } + + // 遍历第二个切片,找到交集 + var intersection []string + for _, v := range slice2 { + if elemMap[v] { + intersection = append(intersection, v) + // 删除元素以防重复添加 + delete(elemMap, v) + } + } + return intersection +} diff --git a/sqle/utils/util_test.go b/sqle/utils/util_test.go index 92d5690f29..7a24db76c6 100644 --- a/sqle/utils/util_test.go +++ b/sqle/utils/util_test.go @@ -2,6 +2,7 @@ package utils import ( "math/rand" + "reflect" "strconv" "testing" "time" @@ -34,6 +35,37 @@ func TestHasPrefix(t *testing.T) { } } +func TestMergeAndDeduplicate(t *testing.T) { + testCases := []struct { + arr1 []string + arr2 []string + expect []string + }{ + { + []string{"apple", "banana", "cherry", "apple"}, + []string{"banana", "orange", "grape"}, + []string{"apple", "banana", "cherry", "grape", "orange"}, + }, + { + []string{}, + []string{"apple", "banana"}, + []string{"apple", "banana"}, + }, + { + []string{"apple"}, + []string{}, + []string{"apple"}, + }, + } + + for _, tc := range testCases { + result := MergeAndDeduplicateSort(tc.arr1, tc.arr2) + if !reflect.DeepEqual(result, tc.expect) { + t.Errorf("expected %v, got %v", tc.expect, result) + } + } +} + func TestHasSuffix(t *testing.T) { type args struct { s string @@ -261,12 +293,12 @@ func TestFullFuzzySearchRegexp(t *testing.T) { ".*(?i)", []string{"GoLang .*(?i) awesome", "I love GO^.*(?i)SING", "GoLangGO.*(?i)Golang"}, []string{"language", "hi", "heyHelloCode", "HElLO", "Sun_hello", "HelLo_Jack"}, - },{ + }, { "ignored_service", []string{`/* this is a comment, Service: ignored_service */ select * from table_ignored where id < 123;' - `,`/* this is a comment, Service: ignored_service */ select * from table_ignored where id < 123;`}, - []string{"any sql","",`/* this is a comment, Service: ignored + `, `/* this is a comment, Service: ignored_service */ select * from table_ignored where id < 123;`}, + []string{"any sql", "", `/* this is a comment, Service: ignored _service */ select * from table_ignored where id < 123;`}, }, } diff --git a/vendor/github.com/pingcap/parser/parser_extension.go b/vendor/github.com/pingcap/parser/parser_extension.go new file mode 100644 index 0000000000..81fc193c77 --- /dev/null +++ b/vendor/github.com/pingcap/parser/parser_extension.go @@ -0,0 +1,7 @@ +package parser + +import "github.com/pingcap/parser/ast" + +func (p Parser) Result() []ast.StmtNode { + return p.result +} diff --git a/vendor/github.com/pingcap/parser/perfect_parser.go b/vendor/github.com/pingcap/parser/perfect_parser.go index e49d98acaa..31026e7d9e 100644 --- a/vendor/github.com/pingcap/parser/perfect_parser.go +++ b/vendor/github.com/pingcap/parser/perfect_parser.go @@ -23,7 +23,7 @@ func (parser *Parser) PerfectParse(sql, charset, collation string) (stmt []ast.S } stmt = append(stmt, stmts...) } - parser.startLineOffset = parser.lexer.r.pos().Line - 1 + parser.startLineOffset += parser.lexer.r.pos().Line - 1 // The origin SQL text(input args `sql`) consists of many SQL segments, // each SQL segments is a complete SQL and be parsed into `ast.StmtNode`. // diff --git a/vendor/github.com/pingcap/parser/scanner_extension .go b/vendor/github.com/pingcap/parser/scanner_extension .go new file mode 100644 index 0000000000..0dad382699 --- /dev/null +++ b/vendor/github.com/pingcap/parser/scanner_extension .go @@ -0,0 +1,68 @@ +package parser + +// 这个Reset方法在原有reset()方法上增加了将最后扫描的偏移量置零 +func (s *Scanner) Reset(sql string) { + s.reset(sql) + s.lastScanOffset = 0 +} + +// 该方法返回Scanner最后扫描到的位置 +func (s *Scanner) Offset() int { + return s.lastScanOffset +} + +// 该方法修改Scanner最后扫描到的位置 +func (s *Scanner) SetCursor(offset int) { + s.lastScanOffset = offset +} + +const ( + Identifier int = identifier + YyEOFCode int = yyEOFCode + YyDefault int = yyDefault + IfKwd int = ifKwd + CaseKwd int = caseKwd + Repeat int = repeat + Begin int = begin + End int = end + StringLit int = stringLit + Invalid int = invalid +) + +type TokenValue yySymType + +type Token struct { + tokenType int + tokenValue *yySymType +} + +func (t Token) Ident() string { + return t.tokenValue.ident +} + +func (t Token) TokenType() int { + return t.tokenType +} + +func (s *Scanner) NextToken() *Token { + tokenValue := &yySymType{} + tokenType := s.Lex(tokenValue) + return &Token{ + tokenType: tokenType, + tokenValue: tokenValue, + } +} + +func (s *Scanner) ScannedLines() int { + return s.r.pos().Line - 1 +} + +func (s *Scanner) Text() string { + return s.r.s +} + +func (s *Scanner) HandleInvalid() { + if s.lastScanOffset == s.r.p.Offset { + s.r.inc() + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 331a0a8ca1..06465c9e34 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -562,7 +562,7 @@ github.com/pingcap/errors # github.com/pingcap/log v0.0.0-20210317133921-96f4fcab92a4 => github.com/pingcap/log v0.0.0-20191012051959-b742a5d432e9 ## explicit; go 1.13 github.com/pingcap/log -# github.com/pingcap/parser v3.0.12+incompatible => github.com/sjjian/parser v0.0.0-20240305095250-688ad439ef31 +# github.com/pingcap/parser v3.0.12+incompatible => github.com/sjjian/parser v0.0.0-20240704052347-b6199b7bccae ## explicit; go 1.13 github.com/pingcap/parser github.com/pingcap/parser/ast @@ -930,6 +930,6 @@ vitess.io/vitess/go/vt/vtgate/evalengine # cloud.google.com/go/compute/metadata => cloud.google.com/go/compute/metadata v0.1.0 # github.com/labstack/echo/v4 => github.com/labstack/echo/v4 v4.6.1 # github.com/pingcap/log => github.com/pingcap/log v0.0.0-20191012051959-b742a5d432e9 -# github.com/pingcap/parser => github.com/sjjian/parser v0.0.0-20240305095250-688ad439ef31 +# github.com/pingcap/parser => github.com/sjjian/parser v0.0.0-20240704052347-b6199b7bccae # github.com/swaggo/swag => github.com/swaggo/swag v1.6.7 # google.golang.org/grpc => google.golang.org/grpc v1.29.0