Skip to content

Commit 7bde7ed

Browse files
committed
feat: Support more MySQL character sets and collations
1 parent 7389aad commit 7bde7ed

File tree

24 files changed

+198
-22
lines changed

24 files changed

+198
-22
lines changed

agent/app/api/v2/database_mysql.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,22 @@ func (b *BaseApi) ListDBName(c *gin.Context) {
212212
helper.SuccessWithData(c, list)
213213
}
214214

215+
// @Tags Database Mysql
216+
// @Summary List mysql database format collation options
217+
// @Accept json
218+
// @Param request body dto.OperationWithName true "request"
219+
// @Success 200 {array} dto.MysqlFormatCollationOption
220+
// @Security ApiKeyAuth
221+
// @Security Timestamp
222+
// @Router /databases/format/options [post]
223+
func (b *BaseApi) ListDBFormatCollationOptions(c *gin.Context) {
224+
var req dto.OperationWithName
225+
if err := helper.CheckBindAndValidate(&req, c); err != nil {
226+
return
227+
}
228+
helper.SuccessWithData(c, mysqlService.LoadFormatOption(req))
229+
}
230+
215231
// @Tags Database Mysql
216232
// @Summary Load mysql database from remote
217233
// @Accept json

agent/app/dto/database.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,18 @@ type MysqlDBCreate struct {
5858
From string `json:"from" validate:"required,oneof=local remote"`
5959
Database string `json:"database" validate:"required"`
6060
Format string `json:"format" validate:"required,oneof=utf8mb4 utf8 gbk big5"`
61+
Collation string `json:"collation" validate:"required"`
6162
Username string `json:"username" validate:"required"`
6263
Password string `json:"password" validate:"required"`
6364
Permission string `json:"permission" validate:"required"`
6465
Description string `json:"description"`
6566
}
6667

68+
type MysqlFormatCollationOption struct {
69+
Format string `json:"format"`
70+
Collations []string `json:"collations"`
71+
}
72+
6773
type BindUser struct {
6874
Database string `json:"database" validate:"required"`
6975
DB string `json:"db" validate:"required"`

agent/app/model/database_mysql.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ type DatabaseMysql struct {
66
From string `json:"from" gorm:"not null;default:local"`
77
MysqlName string `json:"mysqlName" gorm:"not null"`
88
Format string `json:"format" gorm:"not null"`
9+
Collation string `json:"collation" gorm:"not null"`
910
Username string `json:"username" gorm:"not null"`
1011
Password string `json:"password" gorm:"not null"`
1112
Permission string `json:"permission" gorm:"not null"`

agent/app/service/database_mysql.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ import (
2323
"github.com/1Panel-dev/1Panel/agent/utils/compose"
2424
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
2525
"github.com/1Panel-dev/1Panel/agent/utils/mysql"
26-
"github.com/1Panel-dev/1Panel/agent/utils/re"
2726
"github.com/1Panel-dev/1Panel/agent/utils/mysql/client"
27+
"github.com/1Panel-dev/1Panel/agent/utils/re"
2828
_ "github.com/go-sql-driver/mysql"
2929
"github.com/jinzhu/copier"
3030
"github.com/pkg/errors"
@@ -45,6 +45,7 @@ type IMysqlService interface {
4545
DeleteCheck(req dto.MysqlDBDeleteCheck) ([]dto.DBResource, error)
4646
Delete(ctx context.Context, req dto.MysqlDBDelete) error
4747

48+
LoadFormatOption(req dto.OperationWithName) []dto.MysqlFormatCollationOption
4849
LoadStatus(req dto.OperationWithNameAndType) (*dto.MysqlStatus, error)
4950
LoadVariables(req dto.OperationWithNameAndType) (*dto.MysqlVariables, error)
5051
LoadRemoteAccess(req dto.OperationWithNameAndType) (bool, error)
@@ -126,6 +127,7 @@ func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*mode
126127
if err := cli.Create(client.CreateInfo{
127128
Name: req.Name,
128129
Format: req.Format,
130+
Collation: req.Collation,
129131
Username: req.Username,
130132
Password: req.Password,
131133
Permission: req.Permission,
@@ -584,6 +586,19 @@ func (u *MysqlService) LoadStatus(req dto.OperationWithNameAndType) (*dto.MysqlS
584586
return &info, nil
585587
}
586588

589+
func (u *MysqlService) LoadFormatOption(req dto.OperationWithName) []dto.MysqlFormatCollationOption {
590+
defaultList := []dto.MysqlFormatCollationOption{{Format: "utf8mb4"}, {Format: "utf8mb3"}, {Format: "gbk"}, {Format: "big5"}}
591+
client, _, err := LoadMysqlClientByFrom(req.Name)
592+
if err != nil {
593+
return defaultList
594+
}
595+
options, err := client.LoadFormatCollation(3)
596+
if err != nil {
597+
return defaultList
598+
}
599+
return options
600+
}
601+
587602
func executeSqlForMaps(containerName, dbType, password, command string) (map[string]string, error) {
588603
if dbType == "mysql-cluster" {
589604
dbType = "mysql"

agent/init/migration/migrate.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ func InitAgentDB() {
5353
migrations.UpdateTensorrtLLM,
5454
migrations.AddIptablesFilterRuleTable,
5555
migrations.UpdateDatabase,
56+
migrations.UpdateDatabaseMysql,
5657
})
5758
if err := m.Migrate(); err != nil {
5859
global.LOG.Error(err)

agent/init/migration/migrations/init.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,3 +712,9 @@ var UpdateDatabase = &gormigrate.Migration{
712712
return tx.AutoMigrate(&model.Database{})
713713
},
714714
}
715+
var UpdateDatabaseMysql = &gormigrate.Migration{
716+
ID: "20251124-update-database-mysql",
717+
Migrate: func(tx *gorm.DB) error {
718+
return tx.AutoMigrate(&model.Database{})
719+
},
720+
}

agent/router/ro_database.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func (s *DatabaseRouter) InitRouter(Router *gin.RouterGroup) {
2828
cmdRouter.POST("/variables", baseApi.LoadVariables)
2929
cmdRouter.POST("/status", baseApi.LoadStatus)
3030
cmdRouter.POST("/remote", baseApi.LoadRemoteAccess)
31-
cmdRouter.GET("/options", baseApi.ListDBName)
31+
cmdRouter.POST("/format/options", baseApi.ListDBFormatCollationOptions)
3232

3333
cmdRouter.POST("/redis/persistence/conf", baseApi.LoadPersistenceConf)
3434
cmdRouter.POST("/redis/status", baseApi.LoadRedisStatus)

agent/utils/mysql/client.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"strings"
99
"time"
1010

11+
"github.com/1Panel-dev/1Panel/agent/app/dto"
1112
"github.com/1Panel-dev/1Panel/agent/buserr"
1213
"github.com/1Panel-dev/1Panel/agent/global"
1314
"github.com/1Panel-dev/1Panel/agent/utils/mysql/client"
@@ -24,6 +25,7 @@ type MysqlClient interface {
2425
Backup(info client.BackupInfo) error
2526
Recover(info client.RecoverInfo) error
2627

28+
LoadFormatCollation(timeout uint) ([]dto.MysqlFormatCollationOption, error)
2729
SyncDB(version string) ([]client.SyncDBInfo, error)
2830
Close()
2931
}

agent/utils/mysql/client/info.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type DBInfo struct {
3030
type CreateInfo struct {
3131
Name string `json:"name"`
3232
Format string `json:"format"`
33+
Collation string `json:"collation"`
3334
Version string `json:"version"`
3435
Username string `json:"userName"`
3536
Password string `json:"password"`
@@ -80,6 +81,11 @@ type BackupInfo struct {
8081
Timeout uint `json:"timeout"` // second
8182
}
8283

84+
type FormatCollation struct {
85+
Format string `json:"format" gorm:"column:CHARACTER_SET_NAME"`
86+
Collation string `json:"collation" gorm:"column:COLLATION_NAME"`
87+
}
88+
8389
type RecoverInfo struct {
8490
Name string `json:"name"`
8591
Type string `json:"type"`
@@ -100,13 +106,6 @@ type SyncDBInfo struct {
100106
Permission string `json:"permission"`
101107
}
102108

103-
var formatMap = map[string]string{
104-
"utf8": "utf8_general_ci",
105-
"utf8mb4": "utf8mb4_general_ci",
106-
"gbk": "gbk_chinese_ci",
107-
"big5": "big5_chinese_ci",
108-
}
109-
110109
func ConnWithSSL(ssl, skipVerify bool, clientKey, clientCert, rootCert string) (string, error) {
111110
if !ssl {
112111
return "", nil

agent/utils/mysql/client/local.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"strings"
1313
"time"
1414

15+
"github.com/1Panel-dev/1Panel/agent/app/dto"
1516
"github.com/1Panel-dev/1Panel/agent/buserr"
1617
"github.com/1Panel-dev/1Panel/agent/constant"
1718
"github.com/1Panel-dev/1Panel/agent/global"
@@ -31,7 +32,7 @@ func NewLocal(command []string, dbType, containerName, password, database string
3132
}
3233

3334
func (r *Local) Create(info CreateInfo) error {
34-
createSql := fmt.Sprintf("create database `%s` default character set %s collate %s", info.Name, info.Format, formatMap[info.Format])
35+
createSql := fmt.Sprintf("create database `%s` default character set %s collate %s", info.Name, info.Format, info.Collation)
3536
if err := r.ExecSQL(createSql, info.Timeout); err != nil {
3637
if strings.Contains(strings.ToLower(err.Error()), "error 1007") {
3738
return buserr.New("ErrDatabaseIsExist")
@@ -385,3 +386,33 @@ func (r *Local) ExecSQLForRows(command string, timeout uint) ([]string, error) {
385386
}
386387
return strings.Split(stdStr, "\n"), nil
387388
}
389+
390+
func (r *Local) LoadFormatCollation(timeout uint) ([]dto.MysqlFormatCollationOption, error) {
391+
std, err := r.ExecSQLForRows("SELECT CHARACTER_SET_NAME, COLLATION_NAME FROM INFORMATION_SCHEMA.COLLATIONS ORDER BY CHARACTER_SET_NAME, COLLATION_NAME;", timeout)
392+
if err != nil {
393+
return nil, err
394+
}
395+
formatMap := make(map[string][]string)
396+
for _, item := range std {
397+
if strings.ToLower(item) == "character_set_name\tcollation_name" {
398+
continue
399+
}
400+
parts := strings.Split(item, "\t")
401+
if len(parts) != 2 {
402+
continue
403+
}
404+
if _, ok := formatMap[parts[0]]; !ok {
405+
formatMap[parts[0]] = []string{parts[1]}
406+
} else {
407+
formatMap[parts[0]] = append(formatMap[parts[0]], parts[1])
408+
}
409+
}
410+
options := []dto.MysqlFormatCollationOption{}
411+
for key, val := range formatMap {
412+
options = append(options, dto.MysqlFormatCollationOption{
413+
Format: key,
414+
Collations: val,
415+
})
416+
}
417+
return options, nil
418+
}

0 commit comments

Comments
 (0)