feat(table-copy): 新增整表复制与自动副本命名

- 在表概览及新旧侧栏右键菜单接入复制整表入口与确认、进度和刷新反馈
- 按 source_copyN 原子创建目标表并复制列、索引、默认值及全部数据
- 处理 PostgreSQL 生成列、identity/serial 序列校准与失败清理
- 限定安全数据源与连接保护策略,阻止分区表、RLS 及引用型存储引擎
- 加固 MySQL/PostgreSQL 元数据标识符处理并补齐六语种文案
- 增加后端、能力矩阵、菜单接线和国际化回归测试
This commit is contained in:
Syngnat
2026-07-21 13:27:24 +08:00
parent 49e566e01e
commit 446707698f
27 changed files with 2061 additions and 13 deletions

View File

@@ -231,12 +231,7 @@ func (m *MariaDB) GetCreateStatement(dbName, tableName string) (string, error) {
}
func (m *MariaDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefinition, error) {
query := fmt.Sprintf("SHOW FULL COLUMNS FROM `%s`.`%s`", dbName, tableName)
if dbName == "" {
query = fmt.Sprintf("SHOW FULL COLUMNS FROM `%s`", tableName)
}
data, _, err := m.Query(query)
data, _, err := m.Query(buildMySQLShowFullColumnsQuery(dbName, tableName))
if err != nil {
return nil, err
}

View File

@@ -1071,6 +1071,10 @@ func buildMySQLShowCreateTableQuery(dbName, tableName string) string {
return "SHOW CREATE TABLE " + mysqlQualifiedTableIdentifier(dbName, tableName)
}
func buildMySQLShowFullColumnsQuery(dbName, tableName string) string {
return "SHOW FULL COLUMNS FROM " + mysqlQualifiedTableIdentifier(dbName, tableName)
}
func (m *MySQLDB) GetCreateStatement(dbName, tableName string) (string, error) {
data, _, err := m.Query(buildMySQLShowCreateTableQuery(dbName, tableName))
if err != nil {
@@ -1086,12 +1090,7 @@ func (m *MySQLDB) GetCreateStatement(dbName, tableName string) (string, error) {
}
func (m *MySQLDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefinition, error) {
query := fmt.Sprintf("SHOW FULL COLUMNS FROM `%s`.`%s`", dbName, tableName)
if dbName == "" {
query = fmt.Sprintf("SHOW FULL COLUMNS FROM `%s`", tableName)
}
data, _, err := m.Query(query)
data, _, err := m.Query(buildMySQLShowFullColumnsQuery(dbName, tableName))
if err != nil {
return nil, err
}

View File

@@ -193,3 +193,52 @@ func TestBuildMySQLShowCreateTableQueryNormalizesQuotedIdentifiers(t *testing.T)
})
}
}
func TestBuildMySQLShowFullColumnsQueryEscapesIdentifiers(t *testing.T) {
t.Parallel()
tests := []struct {
name string
dbName string
tableName string
want string
}{
{
name: "plain qualified table",
dbName: "app",
tableName: "users",
want: "SHOW FULL COLUMNS FROM `app`.`users`",
},
{
name: "backticks cannot terminate identifiers",
dbName: "app`prod",
tableName: "audit`log",
want: "SHOW FULL COLUMNS FROM `app``prod`.`audit``log`",
},
{
name: "quoted qualified table overrides database",
dbName: "ignored",
tableName: `"sales.region"."daily.order"`,
want: "SHOW FULL COLUMNS FROM `sales.region`.`daily.order`",
},
{
name: "quoted dotted table remains one identifier",
dbName: "app",
tableName: "`audit.logs`",
want: "SHOW FULL COLUMNS FROM `app`.`audit.logs`",
},
{
name: "table without database",
tableName: "standalone",
want: "SHOW FULL COLUMNS FROM `standalone`",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := buildMySQLShowFullColumnsQuery(tt.dbName, tt.tableName); got != tt.want {
t.Fatalf("buildMySQLShowFullColumnsQuery(%q,%q)=%q,want=%q", tt.dbName, tt.tableName, got, tt.want)
}
})
}
}