mirror of
				https://gitee.com/dromara/mayfly-go
				synced 2025-11-04 08:20:25 +08:00 
			
		
		
		
	!88 feat: dbms表支持右键菜单:删除表、编辑表、新建表、复制表
* feat: 支持复制表 * feat: dbms表支持右键菜单:删除表、编辑表、新建表
This commit is contained in:
		@@ -462,6 +462,20 @@ func (d *Db) GetSchemas(rc *req.Ctx) {
 | 
			
		||||
	rc.ResData = res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *Db) CopyTable(rc *req.Ctx) {
 | 
			
		||||
	form := &form.DbCopyTableForm{}
 | 
			
		||||
	copy := ginx.BindJsonAndCopyTo[*dbi.DbCopyTable](rc.GinCtx, form, new(dbi.DbCopyTable))
 | 
			
		||||
 | 
			
		||||
	conn, err := d.DbApp.GetDbConn(form.Id, form.Db)
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "拷贝表失败: %s")
 | 
			
		||||
 | 
			
		||||
	err = conn.GetDialect().CopyTable(copy)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logx.Errorf("拷贝表失败: %s", err.Error())
 | 
			
		||||
	}
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "拷贝表失败: %s")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getDbId(g *gin.Context) uint64 {
 | 
			
		||||
	dbId, _ := strconv.Atoi(g.Param("dbId"))
 | 
			
		||||
	biz.IsTrue(dbId > 0, "dbId错误")
 | 
			
		||||
 
 | 
			
		||||
@@ -23,3 +23,11 @@ type DbSqlExecForm struct {
 | 
			
		||||
	Sql    string `binding:"required" json:"sql"` // 执行sql
 | 
			
		||||
	Remark string `json:"remark"`                 // 执行备注
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 数据库复制表
 | 
			
		||||
type DbCopyTableForm struct {
 | 
			
		||||
	Id        uint64 `binding:"required" json:"id"`
 | 
			
		||||
	Db        string `binding:"required" json:"db" `
 | 
			
		||||
	TableName string `binding:"required" json:"tableName"`
 | 
			
		||||
	CopyData  bool   `binding:"required" json:"copyData"` // 是否复制数据
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -59,6 +59,13 @@ type Index struct {
 | 
			
		||||
	NonUnique    int    `json:"nonUnique"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DbCopyTable struct {
 | 
			
		||||
	Id        uint64 `json:"id"`
 | 
			
		||||
	Db        string `json:"db" `
 | 
			
		||||
	TableName string `json:"tableName"`
 | 
			
		||||
	CopyData  bool   `json:"copyData"` // 是否复制数据
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// -----------------------------------元数据接口定义------------------------------------------
 | 
			
		||||
// 数据库方言、元信息接口(表、列、获取表数据等元信息)
 | 
			
		||||
type Dialect interface {
 | 
			
		||||
@@ -97,6 +104,8 @@ type Dialect interface {
 | 
			
		||||
	GetDataType(dbColumnType string) DataType
 | 
			
		||||
 | 
			
		||||
	FormatStrData(dbColumnValue string, dataType DataType) string
 | 
			
		||||
 | 
			
		||||
	CopyTable(copy *DbCopyTable) error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ------------------------- 元数据sql操作 -------------------------
 | 
			
		||||
 
 | 
			
		||||
@@ -4,11 +4,13 @@ import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"database/sql"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/kanzihuang/vitess/go/vt/sqlparser"
 | 
			
		||||
	"mayfly-go/internal/db/dbm/dbi"
 | 
			
		||||
	"mayfly-go/pkg/errorx"
 | 
			
		||||
	"mayfly-go/pkg/logx"
 | 
			
		||||
	"mayfly-go/pkg/utils/anyx"
 | 
			
		||||
	"mayfly-go/pkg/utils/collx"
 | 
			
		||||
	"mayfly-go/pkg/utils/stringx"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
@@ -311,3 +313,44 @@ func (dd *DMDialect) FormatStrData(dbColumnValue string, dataType dbi.DataType)
 | 
			
		||||
	}
 | 
			
		||||
	return dbColumnValue
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (dd *DMDialect) CopyTable(copy *dbi.DbCopyTable) error {
 | 
			
		||||
	tableName := copy.TableName
 | 
			
		||||
	ddl, err := dd.GetTableDDL(tableName)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	// 生成新表名,为老表明+_copy_时间戳
 | 
			
		||||
	newTableName := tableName + "_copy_" + time.Now().Format("20060102150405")
 | 
			
		||||
 | 
			
		||||
	// 替换新表名
 | 
			
		||||
	ddl = strings.ReplaceAll(ddl, fmt.Sprintf("\"%s\"", strings.ToUpper(tableName)), fmt.Sprintf("\"%s\"", strings.ToUpper(newTableName)))
 | 
			
		||||
	// 去除空格换行
 | 
			
		||||
	ddl = stringx.TrimSpaceAndBr(ddl)
 | 
			
		||||
	sqls, err := sqlparser.SplitStatementToPieces(ddl, sqlparser.WithDialect(dd.dc.Info.Type.Dialect()))
 | 
			
		||||
	for _, sql := range sqls {
 | 
			
		||||
		_, _ = dd.dc.Exec(sql)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 复制数据
 | 
			
		||||
	if copy.CopyData {
 | 
			
		||||
		go func() {
 | 
			
		||||
			// 设置允许填充自增列之后,显示指定列名可以插入自增列
 | 
			
		||||
			_, _ = dd.dc.Exec(fmt.Sprintf("set identity_insert \"%s\" on", newTableName))
 | 
			
		||||
			// 获取列名
 | 
			
		||||
			columns, _ := dd.GetColumns(tableName)
 | 
			
		||||
			columnArr := make([]string, 0)
 | 
			
		||||
			for _, column := range columns {
 | 
			
		||||
				columnArr = append(columnArr, fmt.Sprintf("\"%s\"", column.ColumnName))
 | 
			
		||||
			}
 | 
			
		||||
			columnStr := strings.Join(columnArr, ",")
 | 
			
		||||
			// 插入新数据并显示指定列
 | 
			
		||||
			_, _ = dd.dc.Exec(fmt.Sprintf("insert into \"%s\" (%s) select %s from \"%s\"", newTableName, columnStr, columnStr, tableName))
 | 
			
		||||
 | 
			
		||||
			// 执行完成后关闭允许填充自增列
 | 
			
		||||
			_, _ = dd.dc.Exec(fmt.Sprintf("set identity_insert \"%s\" off", newTableName))
 | 
			
		||||
		}()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ import (
 | 
			
		||||
	"mayfly-go/pkg/utils/collx"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
@@ -224,3 +225,25 @@ func (md *MysqlDialect) FormatStrData(dbColumnValue string, dataType dbi.DataTyp
 | 
			
		||||
	// mysql不需要格式化时间日期等
 | 
			
		||||
	return dbColumnValue
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (md *MysqlDialect) CopyTable(copy *dbi.DbCopyTable) error {
 | 
			
		||||
 | 
			
		||||
	tableName := copy.TableName
 | 
			
		||||
 | 
			
		||||
	// 生成新表名,为老表明+_copy_时间戳
 | 
			
		||||
	newTableName := tableName + "_copy_" + time.Now().Format("20060102150405")
 | 
			
		||||
 | 
			
		||||
	// 复制表结构创建表
 | 
			
		||||
	_, err := md.dc.Exec(fmt.Sprintf("create table %s like %s", newTableName, tableName))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 复制数据
 | 
			
		||||
	if copy.CopyData {
 | 
			
		||||
		go func() {
 | 
			
		||||
			_, _ = md.dc.Exec(fmt.Sprintf("insert into %s select * from %s", newTableName, tableName))
 | 
			
		||||
		}()
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -340,3 +340,14 @@ func (od *OracleDialect) FormatStrData(dbColumnValue string, dataType dbi.DataTy
 | 
			
		||||
	}
 | 
			
		||||
	return dbColumnValue
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (od *OracleDialect) CopyTable(copy *dbi.DbCopyTable) error {
 | 
			
		||||
	// 生成新表名,为老表明+_copy_时间戳
 | 
			
		||||
	newTableName := strings.ToUpper(copy.TableName + "_copy_" + time.Now().Format("20060102150405"))
 | 
			
		||||
	condition := ""
 | 
			
		||||
	if copy.CopyData {
 | 
			
		||||
		condition = " where 1 = 2"
 | 
			
		||||
	}
 | 
			
		||||
	_, err := od.dc.Exec(fmt.Sprintf("create table \"%s\" as select * from \"%s\" %s", newTableName, copy.TableName, condition))
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -255,3 +255,65 @@ func (pd *PgsqlDialect) FormatStrData(dbColumnValue string, dataType dbi.DataTyp
 | 
			
		||||
	}
 | 
			
		||||
	return dbColumnValue
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (pd *PgsqlDialect) IsGauss() bool {
 | 
			
		||||
	return strings.Contains(pd.dc.Info.Params, "gauss")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (pd *PgsqlDialect) CopyTable(copy *dbi.DbCopyTable) error {
 | 
			
		||||
	tableName := copy.TableName
 | 
			
		||||
	// 生成新表名,为老表明+_copy_时间戳
 | 
			
		||||
	newTableName := tableName + "_copy_" + time.Now().Format("20060102150405")
 | 
			
		||||
	// 执行根据旧表创建新表
 | 
			
		||||
	_, err := pd.dc.Exec(fmt.Sprintf("create table %s (like %s)", newTableName, tableName))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 复制数据
 | 
			
		||||
	if copy.CopyData {
 | 
			
		||||
		go func() {
 | 
			
		||||
			_, _ = pd.dc.Exec(fmt.Sprintf("insert into %s select * from %s", newTableName, tableName))
 | 
			
		||||
		}()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 查询旧表的自增字段名 重新设置新表的序列序列器
 | 
			
		||||
	_, res, err := pd.dc.Query(fmt.Sprintf("select column_name from information_schema.columns where table_name = '%s' and column_default like 'nextval%%'", tableName))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, re := range res {
 | 
			
		||||
		colName := anyx.ConvString(re["column_name"])
 | 
			
		||||
		if colName != "" {
 | 
			
		||||
 | 
			
		||||
			// 查询自增列当前最大值
 | 
			
		||||
			_, maxRes, err := pd.dc.Query(fmt.Sprintf("select max(%s) max_val from %s", colName, tableName))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			maxVal := anyx.ConvInt(maxRes[0]["max_val"])
 | 
			
		||||
			// 序列起始值为1或当前最大值+1
 | 
			
		||||
			if maxVal <= 0 {
 | 
			
		||||
				maxVal = 1
 | 
			
		||||
			} else {
 | 
			
		||||
				maxVal += 1
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// 之所以不用tableName_colName_seq是因为gauss会自动创建同名的序列,且无法修改序列起始值,所以直接使用新序列值
 | 
			
		||||
			newSeqName := fmt.Sprintf("%s_%s_copy_seq", newTableName, colName)
 | 
			
		||||
 | 
			
		||||
			// 创建自增序列,当前最大值为旧表最大值
 | 
			
		||||
			_, err = pd.dc.Exec(fmt.Sprintf("CREATE SEQUENCE %s START %d INCREMENT 1", newSeqName, maxVal))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			// 将新表的自增主键序列与主键列相关联
 | 
			
		||||
			_, err = pd.dc.Exec(fmt.Sprintf("alter table %s alter column %s set default nextval('%s')", newTableName, colName, newSeqName))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -58,7 +58,7 @@ func (sd *SqliteDialect) GetTables() ([]dbi.Table, error) {
 | 
			
		||||
	tables := make([]dbi.Table, 0)
 | 
			
		||||
	for _, re := range res {
 | 
			
		||||
		tables = append(tables, dbi.Table{
 | 
			
		||||
			TableName:    re["tableName"].(string),
 | 
			
		||||
			TableName:    anyx.ConvString(re["tableName"]),
 | 
			
		||||
			TableComment: anyx.ConvString(re["tableComment"]),
 | 
			
		||||
			CreateTime:   anyx.ConvString(re["createTime"]),
 | 
			
		||||
			TableRows:    anyx.ConvInt(re["tableRows"]),
 | 
			
		||||
@@ -97,7 +97,7 @@ func (sd *SqliteDialect) GetColumns(tableNames ...string) ([]dbi.Column, error)
 | 
			
		||||
			}
 | 
			
		||||
			columns = append(columns, dbi.Column{
 | 
			
		||||
				TableName:     tableName,
 | 
			
		||||
				ColumnName:    re["name"].(string),
 | 
			
		||||
				ColumnName:    anyx.ConvString(re["name"]),
 | 
			
		||||
				ColumnType:    strings.ToLower(anyx.ConvString(re["type"])),
 | 
			
		||||
				ColumnComment: "",
 | 
			
		||||
				Nullable:      nullable,
 | 
			
		||||
@@ -117,7 +117,7 @@ func (sd *SqliteDialect) GetPrimaryKey(tableName string) (string, error) {
 | 
			
		||||
	}
 | 
			
		||||
	for _, re := range res {
 | 
			
		||||
		if anyx.ConvInt(re["pk"]) == 1 {
 | 
			
		||||
			return re["name"].(string), nil
 | 
			
		||||
			return anyx.ConvString(re["name"]), nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -149,7 +149,7 @@ func (sd *SqliteDialect) GetTableIndex(tableName string) ([]dbi.Index, error) {
 | 
			
		||||
 | 
			
		||||
	indexs := make([]dbi.Index, 0)
 | 
			
		||||
	for _, re := range res {
 | 
			
		||||
		indexSql := re["indexSql"].(string)
 | 
			
		||||
		indexSql := anyx.ConvString(re["indexSql"])
 | 
			
		||||
		isUnique := strings.Contains(indexSql, "CREATE UNIQUE INDEX")
 | 
			
		||||
		nonUnique := 1
 | 
			
		||||
		if isUnique {
 | 
			
		||||
@@ -157,7 +157,7 @@ func (sd *SqliteDialect) GetTableIndex(tableName string) ([]dbi.Index, error) {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		indexs = append(indexs, dbi.Index{
 | 
			
		||||
			IndexName:    re["indexName"].(string),
 | 
			
		||||
			IndexName:    anyx.ConvString(re["indexName"]),
 | 
			
		||||
			ColumnName:   extractIndexFields(indexSql),
 | 
			
		||||
			IndexType:    anyx.ConvString(re["indexType"]),
 | 
			
		||||
			IndexComment: anyx.ConvString(re["indexComment"]),
 | 
			
		||||
@@ -171,13 +171,13 @@ func (sd *SqliteDialect) GetTableIndex(tableName string) ([]dbi.Index, error) {
 | 
			
		||||
 | 
			
		||||
// 获取建表ddl
 | 
			
		||||
func (sd *SqliteDialect) GetTableDDL(tableName string) (string, error) {
 | 
			
		||||
	_, res, err := sd.dc.Query("select sql from sqlite_master WHERE name=? order by type desc", tableName)
 | 
			
		||||
	_, res, err := sd.dc.Query("select sql from sqlite_master WHERE tbl_name=? order by type desc", tableName)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	var builder strings.Builder
 | 
			
		||||
	for _, re := range res {
 | 
			
		||||
		builder.WriteString(re["sql"].(string))
 | 
			
		||||
		builder.WriteString(anyx.ConvString(re["sql"]) + "; \n\n")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return builder.String(), nil
 | 
			
		||||
@@ -245,3 +245,35 @@ func (sd *SqliteDialect) FormatStrData(dbColumnValue string, dataType dbi.DataTy
 | 
			
		||||
	}
 | 
			
		||||
	return dbColumnValue
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sd *SqliteDialect) CopyTable(copy *dbi.DbCopyTable) error {
 | 
			
		||||
	tableName := copy.TableName
 | 
			
		||||
 | 
			
		||||
	// 生成新表名,为老表明+_copy_时间戳
 | 
			
		||||
	newTableName := tableName + "_copy_" + time.Now().Format("20060102150405")
 | 
			
		||||
	ddl, err := sd.GetTableDDL(tableName)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	// 生成建表语句
 | 
			
		||||
	// 替换表名
 | 
			
		||||
	ddl = strings.ReplaceAll(ddl, fmt.Sprintf("CREATE TABLE \"%s\"", tableName), fmt.Sprintf("CREATE TABLE \"%s\"", newTableName))
 | 
			
		||||
	// 替换索引名,索引名为按照规范生成的,才能替换,否则未知索引名,无法替换
 | 
			
		||||
	ddl = strings.ReplaceAll(ddl, fmt.Sprintf("CREATE INDEX \"%s", tableName), fmt.Sprintf("CREATE INDEX \"%s", newTableName))
 | 
			
		||||
 | 
			
		||||
	// 执行建表语句
 | 
			
		||||
	_, err = sd.dc.Exec(ddl)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 使用异步线程插入数据
 | 
			
		||||
	if copy.CopyData {
 | 
			
		||||
		go func() {
 | 
			
		||||
			// 执行插入语句
 | 
			
		||||
			_, _ = sd.dc.Exec(fmt.Sprintf("INSERT INTO \"%s\" SELECT * FROM \"%s\"", newTableName, tableName))
 | 
			
		||||
		}()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -42,9 +42,13 @@ func InitDbRouter(router *gin.RouterGroup) {
 | 
			
		||||
		req.NewGet(":dbId/hint-tables", d.HintTables),
 | 
			
		||||
 | 
			
		||||
		req.NewGet(":dbId/restore-task", d.GetRestoreTask),
 | 
			
		||||
 | 
			
		||||
		req.NewPost(":dbId/restore-task", d.SaveRestoreTask).
 | 
			
		||||
			Log(req.NewLogSave("db-保存数据库恢复任务")),
 | 
			
		||||
 | 
			
		||||
		req.NewGet(":dbId/restore-histories", d.GetRestoreHistories),
 | 
			
		||||
 | 
			
		||||
		req.NewPost(":dbId/copy-table", d.CopyTable),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	req.BatchSetGroup(db, reqs[:])
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user