mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-03 16:00:25 +08:00
!88 feat: dbms表支持右键菜单:删除表、编辑表、新建表、复制表
* feat: 支持复制表 * feat: dbms表支持右键菜单:删除表、编辑表、新建表
This commit is contained in:
@@ -151,6 +151,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</Pane>
|
</Pane>
|
||||||
</Splitpanes>
|
</Splitpanes>
|
||||||
|
<db-table-op
|
||||||
|
:title="tableCreateDialog.title"
|
||||||
|
:active-name="tableCreateDialog.activeName"
|
||||||
|
:dbId="tableCreateDialog.dbId"
|
||||||
|
:db="tableCreateDialog.db"
|
||||||
|
:dbType="tableCreateDialog.dbType"
|
||||||
|
:data="tableCreateDialog.data"
|
||||||
|
v-model:visible="tableCreateDialog.visible"
|
||||||
|
@submit-sql="onSubmitEditTableSql"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -171,6 +181,7 @@ import { TagResourceTypeEnum } from '@/common/commonEnum';
|
|||||||
import { Pane, Splitpanes } from 'splitpanes';
|
import { Pane, Splitpanes } from 'splitpanes';
|
||||||
import { useEventListener } from '@vueuse/core';
|
import { useEventListener } from '@vueuse/core';
|
||||||
|
|
||||||
|
const DbTableOp = defineAsyncComponent(() => import('./component/table/DbTableOp.vue'));
|
||||||
const DbSqlEditor = defineAsyncComponent(() => import('./component/sqleditor/DbSqlEditor.vue'));
|
const DbSqlEditor = defineAsyncComponent(() => import('./component/sqleditor/DbSqlEditor.vue'));
|
||||||
const DbTableDataOp = defineAsyncComponent(() => import('./component/table/DbTableDataOp.vue'));
|
const DbTableDataOp = defineAsyncComponent(() => import('./component/table/DbTableDataOp.vue'));
|
||||||
const DbTablesOp = defineAsyncComponent(() => import('./component/table/DbTablesOp.vue'));
|
const DbTablesOp = defineAsyncComponent(() => import('./component/table/DbTablesOp.vue'));
|
||||||
@@ -258,9 +269,9 @@ const NodeTypeDb = new NodeType(SqlExecNodeType.Db)
|
|||||||
.withContextMenuItems([new ContextmenuItem('reloadTables', '刷新').withIcon('RefreshRight').withOnClick((data: any) => reloadNode(data.key))])
|
.withContextMenuItems([new ContextmenuItem('reloadTables', '刷新').withIcon('RefreshRight').withOnClick((data: any) => reloadNode(data.key))])
|
||||||
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||||
const params = parentNode.params;
|
const params = parentNode.params;
|
||||||
|
params.parentKey = parentNode.key;
|
||||||
// pg类数据库会多一层schema
|
// pg类数据库会多一层schema
|
||||||
if (params.type == DbType.postgresql || params.type === DbType.dm || params.type === DbType.oracle) {
|
if (params.type == DbType.postgresql || params.type === DbType.dm || params.type === DbType.oracle) {
|
||||||
const params = parentNode.params;
|
|
||||||
const { id, db } = params;
|
const { id, db } = params;
|
||||||
const schemaNames = await dbApi.pgSchemas.request({ id, db });
|
const schemaNames = await dbApi.pgSchemas.request({ id, db });
|
||||||
return schemaNames.map((sn: any) => {
|
return schemaNames.map((sn: any) => {
|
||||||
@@ -277,23 +288,29 @@ const NodeTypeDb = new NodeType(SqlExecNodeType.Db)
|
|||||||
.withNodeClickFunc(nodeClickChangeDb);
|
.withNodeClickFunc(nodeClickChangeDb);
|
||||||
|
|
||||||
const NodeTypeTables = (params: any) => {
|
const NodeTypeTables = (params: any) => {
|
||||||
|
let tableKey = `${params.id}.${params.db}.table-menu`;
|
||||||
|
let sqlKey = getSqlMenuNodeKey(params.id, params.db);
|
||||||
return [
|
return [
|
||||||
new TagTreeNode(`${params.id}.${params.db}.table-menu`, '表', NodeTypeTableMenu).withParams(params).withIcon(TableIcon),
|
new TagTreeNode(`${params.id}.${params.db}.table-menu`, '表', NodeTypeTableMenu).withParams({...params, key:tableKey}).withIcon(TableIcon),
|
||||||
new TagTreeNode(getSqlMenuNodeKey(params.id, params.db), 'SQL', NodeTypeSqlMenu).withParams(params).withIcon(SqlIcon),
|
new TagTreeNode(sqlKey, 'SQL', NodeTypeSqlMenu).withParams({...params, key:sqlKey}).withIcon(SqlIcon),
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
// postgres schema模式
|
// postgres schema模式
|
||||||
const NodeTypePostgresSchema = new NodeType(SqlExecNodeType.PgSchema)
|
const NodeTypePostgresSchema = new NodeType(SqlExecNodeType.PgSchema)
|
||||||
.withContextMenuItems([new ContextmenuItem('reloadTables', '刷新').withIcon('RefreshRight').withOnClick((data: any) => reloadNode(data.key))])
|
.withContextMenuItems([new ContextmenuItem('reloadTables', '刷新').withIcon('RefreshRight').withOnClick((data: any) => reloadNode(data.key))])
|
||||||
.withLoadNodesFunc(async (parentNode: TagTreeNode) => NodeTypeTables(parentNode.params))
|
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||||
|
const params = parentNode.params;
|
||||||
|
params.parentKey = parentNode.key;
|
||||||
|
return NodeTypeTables(params);
|
||||||
|
})
|
||||||
.withNodeClickFunc(nodeClickChangeDb);
|
.withNodeClickFunc(nodeClickChangeDb);
|
||||||
|
|
||||||
// 数据库表菜单节点
|
// 数据库表菜单节点
|
||||||
const NodeTypeTableMenu = new NodeType(SqlExecNodeType.TableMenu)
|
const NodeTypeTableMenu = new NodeType(SqlExecNodeType.TableMenu)
|
||||||
.withContextMenuItems([
|
.withContextMenuItems([
|
||||||
new ContextmenuItem('reloadTables', '刷新').withIcon('RefreshRight').withOnClick((data: any) => reloadNode(data.key)),
|
new ContextmenuItem('reloadTables', '刷新').withIcon('RefreshRight').withOnClick((data: any) => reloadNode(data.key)),
|
||||||
|
new ContextmenuItem('createTable', '创建表').withIcon('Plus').withOnClick((data: any) => onEditTable(data)),
|
||||||
new ContextmenuItem('tablesOp', '表操作').withIcon('Setting').withOnClick((data: any) => {
|
new ContextmenuItem('tablesOp', '表操作').withIcon('Setting').withOnClick((data: any) => {
|
||||||
const params = data.params;
|
const params = data.params;
|
||||||
addTablesOpTab({ id: params.id, db: params.db, type: params.type, nodeKey: data.key });
|
addTablesOpTab({ id: params.id, db: params.db, type: params.type, nodeKey: data.key });
|
||||||
@@ -301,7 +318,7 @@ const NodeTypeTableMenu = new NodeType(SqlExecNodeType.TableMenu)
|
|||||||
])
|
])
|
||||||
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||||
const params = parentNode.params;
|
const params = parentNode.params;
|
||||||
let { id, db } = params;
|
let { id, db, type } = params;
|
||||||
// 获取当前库的所有表信息
|
// 获取当前库的所有表信息
|
||||||
let tables = await DbInst.getInst(id).loadTables(db, state.reloadStatus);
|
let tables = await DbInst.getInst(id).loadTables(db, state.reloadStatus);
|
||||||
state.reloadStatus = false;
|
state.reloadStatus = false;
|
||||||
@@ -309,11 +326,15 @@ const NodeTypeTableMenu = new NodeType(SqlExecNodeType.TableMenu)
|
|||||||
const tablesNode = tables.map((x: any) => {
|
const tablesNode = tables.map((x: any) => {
|
||||||
const tableSize = x.dataLength + x.indexLength;
|
const tableSize = x.dataLength + x.indexLength;
|
||||||
dbTableSize += tableSize;
|
dbTableSize += tableSize;
|
||||||
return new TagTreeNode(`${id}.${db}.${x.tableName}`, x.tableName, NodeTypeTable)
|
const key = `${id}.${db}.${x.tableName}`;
|
||||||
|
return new TagTreeNode(key, x.tableName, NodeTypeTable)
|
||||||
.withIsLeaf(true)
|
.withIsLeaf(true)
|
||||||
.withParams({
|
.withParams({
|
||||||
id,
|
id,
|
||||||
db,
|
db,
|
||||||
|
type,
|
||||||
|
key: key,
|
||||||
|
parentKey: parentNode.key,
|
||||||
tableName: x.tableName,
|
tableName: x.tableName,
|
||||||
tableComment: x.tableComment,
|
tableComment: x.tableComment,
|
||||||
size: tableSize == 0 ? '' : formatByteSize(tableSize, 1),
|
size: tableSize == 0 ? '' : formatByteSize(tableSize, 1),
|
||||||
@@ -339,22 +360,23 @@ const NodeTypeSqlMenu = new NodeType(SqlExecNodeType.SqlMenu)
|
|||||||
return sqls.map((x: any) => {
|
return sqls.map((x: any) => {
|
||||||
return new TagTreeNode(`${id}.${db}.${x.name}`, x.name, NodeTypeSql)
|
return new TagTreeNode(`${id}.${db}.${x.name}`, x.name, NodeTypeSql)
|
||||||
.withIsLeaf(true)
|
.withIsLeaf(true)
|
||||||
.withParams({
|
.withParams({ id, db, dbs, sqlName: x.name })
|
||||||
id,
|
|
||||||
db,
|
|
||||||
dbs,
|
|
||||||
sqlName: x.name,
|
|
||||||
})
|
|
||||||
.withIcon(SqlIcon);
|
.withIcon(SqlIcon);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.withNodeClickFunc(nodeClickChangeDb);
|
.withNodeClickFunc(nodeClickChangeDb);
|
||||||
|
|
||||||
// 表节点类型
|
// 表节点类型
|
||||||
const NodeTypeTable = new NodeType(SqlExecNodeType.Table).withNodeClickFunc((nodeData: TagTreeNode) => {
|
const NodeTypeTable = new NodeType(SqlExecNodeType.Table)
|
||||||
const params = nodeData.params;
|
.withContextMenuItems([
|
||||||
loadTableData({ id: params.id, nodeKey: nodeData.key }, params.db, params.tableName);
|
new ContextmenuItem('copyTable', '复制表').withIcon('copyDocument').withOnClick((data: any) => onCopyTable(data)),
|
||||||
});
|
new ContextmenuItem('editTable', '编辑表').withIcon('edit').withOnClick((data: any) => onEditTable(data)),
|
||||||
|
new ContextmenuItem('delTable', '删除表').withIcon('Delete').withOnClick((data: any) => onDeleteTable(data)),
|
||||||
|
])
|
||||||
|
.withNodeClickFunc((nodeData: TagTreeNode) => {
|
||||||
|
const params = nodeData.params;
|
||||||
|
loadTableData({ id: params.id, nodeKey: nodeData.key }, params.db, params.tableName);
|
||||||
|
});
|
||||||
|
|
||||||
// sql模板节点类型
|
// sql模板节点类型
|
||||||
const NodeTypeSql = new NodeType(SqlExecNodeType.Sql)
|
const NodeTypeSql = new NodeType(SqlExecNodeType.Sql)
|
||||||
@@ -384,9 +406,19 @@ const state = reactive({
|
|||||||
loading: true,
|
loading: true,
|
||||||
version: '',
|
version: '',
|
||||||
},
|
},
|
||||||
|
tableCreateDialog: {
|
||||||
|
visible: false,
|
||||||
|
title: '',
|
||||||
|
activeName: '',
|
||||||
|
dbId: 0,
|
||||||
|
db: '',
|
||||||
|
dbType: '',
|
||||||
|
data: {},
|
||||||
|
parentKey: '',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { nowDbInst } = toRefs(state);
|
const { nowDbInst, tableCreateDialog } = toRefs(state);
|
||||||
|
|
||||||
const serverInfoReqParam = ref({
|
const serverInfoReqParam = ref({
|
||||||
instanceId: 0,
|
instanceId: 0,
|
||||||
@@ -602,6 +634,63 @@ const reloadNode = (nodeKey: string) => {
|
|||||||
tagTreeRef.value.reloadNode(nodeKey);
|
tagTreeRef.value.reloadNode(nodeKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onEditTable = async (data: any) => {
|
||||||
|
let { db, id, tableName, tableComment, type, parentKey, key } = data.params;
|
||||||
|
// data.label就是表名
|
||||||
|
if (tableName) {
|
||||||
|
state.tableCreateDialog.title = '修改表';
|
||||||
|
let indexs = await dbApi.tableIndex.request({ id, db, tableName });
|
||||||
|
let columns = await dbApi.columnMetadata.request({ id, db, tableName });
|
||||||
|
let row = { tableName, tableComment };
|
||||||
|
state.tableCreateDialog.data = { edit: true, row, indexs, columns };
|
||||||
|
state.tableCreateDialog.parentKey = parentKey;
|
||||||
|
} else {
|
||||||
|
state.tableCreateDialog.title = '创建表';
|
||||||
|
state.tableCreateDialog.data = { edit: false, row: {} };
|
||||||
|
state.tableCreateDialog.parentKey = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.tableCreateDialog.visible = true;
|
||||||
|
state.tableCreateDialog.activeName = '1';
|
||||||
|
state.tableCreateDialog.dbId = id;
|
||||||
|
state.tableCreateDialog.db = db;
|
||||||
|
state.tableCreateDialog.dbType = type;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDeleteTable = async (data: any) => {
|
||||||
|
let { db, id, tableName, parentKey } = data.params;
|
||||||
|
await ElMessageBox.confirm(`此操作是永久性且无法撤销,确定删除【${tableName}】? `, '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
});
|
||||||
|
// 执行sql
|
||||||
|
dbApi.sqlExec.request({ id, db, sql: `drop table ${tableName}` }).then(() => {
|
||||||
|
ElMessage.success('删除成功');
|
||||||
|
setTimeout(() => {
|
||||||
|
parentKey && reloadNode(parentKey);
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCopyTable = async (data: any) => {
|
||||||
|
let { db, id, tableName, parentKey } = data.params;
|
||||||
|
|
||||||
|
// 执行sql
|
||||||
|
dbApi.copyTable.request({ id, db, tableName, copyData:true }).then(() => {
|
||||||
|
ElMessage.success('复制成功');
|
||||||
|
setTimeout(() => {
|
||||||
|
parentKey && reloadNode(parentKey);
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmitEditTableSql = () => {
|
||||||
|
state.tableCreateDialog.visible = false;
|
||||||
|
state.tableCreateDialog.data = { edit: false, row: {} };
|
||||||
|
reloadNode(state.tableCreateDialog.parentKey);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前操作的数据库信息
|
* 获取当前操作的数据库信息
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export const dbApi = {
|
|||||||
tableInfos: Api.newGet('/dbs/{id}/t-infos'),
|
tableInfos: Api.newGet('/dbs/{id}/t-infos'),
|
||||||
tableIndex: Api.newGet('/dbs/{id}/t-index'),
|
tableIndex: Api.newGet('/dbs/{id}/t-index'),
|
||||||
tableDdl: Api.newGet('/dbs/{id}/t-create-ddl'),
|
tableDdl: Api.newGet('/dbs/{id}/t-create-ddl'),
|
||||||
|
copyTable: Api.newPost('/dbs/{id}/copy-table'),
|
||||||
columnMetadata: Api.newGet('/dbs/{id}/c-metadata'),
|
columnMetadata: Api.newGet('/dbs/{id}/c-metadata'),
|
||||||
pgSchemas: Api.newGet('/dbs/{id}/pg/schemas'),
|
pgSchemas: Api.newGet('/dbs/{id}/pg/schemas'),
|
||||||
// 获取表即列提示
|
// 获取表即列提示
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
import { reactive, ref, toRefs, watch } from 'vue';
|
import { reactive, ref, toRefs, watch } from 'vue';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
import SqlExecBox from '../sqleditor/SqlExecBox';
|
import SqlExecBox from '../sqleditor/SqlExecBox';
|
||||||
import { DbType, getDbDialect, IndexDefinition, RowDefinition } from '../../dialect/index';
|
import { DbDialect, DbType, getDbDialect, IndexDefinition, RowDefinition } from '../../dialect/index';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
visible: {
|
visible: {
|
||||||
@@ -158,7 +158,7 @@ const props = defineProps({
|
|||||||
//定义事件
|
//定义事件
|
||||||
const emit = defineEmits(['update:visible', 'cancel', 'val-change', 'submit-sql']);
|
const emit = defineEmits(['update:visible', 'cancel', 'val-change', 'submit-sql']);
|
||||||
|
|
||||||
const dbDialect = getDbDialect(props.dbType);
|
let dbDialect = getDbDialect(props.dbType);
|
||||||
|
|
||||||
type ColName = {
|
type ColName = {
|
||||||
prop: string;
|
prop: string;
|
||||||
@@ -272,6 +272,7 @@ const { dialogVisible, btnloading, activeName, indexTypeList, tableData } = toRe
|
|||||||
|
|
||||||
watch(props, async (newValue) => {
|
watch(props, async (newValue) => {
|
||||||
state.dialogVisible = newValue.visible;
|
state.dialogVisible = newValue.visible;
|
||||||
|
dbDialect = getDbDialect(newValue.dbType);
|
||||||
});
|
});
|
||||||
|
|
||||||
const cancel = () => {
|
const cancel = () => {
|
||||||
|
|||||||
@@ -68,9 +68,7 @@
|
|||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-link @click.prevent="showColumns(scope.row)" type="primary">字段</el-link>
|
<el-link @click.prevent="showColumns(scope.row)" type="primary">字段</el-link>
|
||||||
<el-link class="ml5" @click.prevent="showTableIndex(scope.row)" type="success">索引</el-link>
|
<el-link class="ml5" @click.prevent="showTableIndex(scope.row)" type="success">索引</el-link>
|
||||||
<el-link class="ml5" v-if="tableCreateDialog.enableEditTypes.indexOf(dbType) > -1" @click.prevent="openEditTable(scope.row)" type="warning"
|
<el-link class="ml5" v-if="editDbTypes.indexOf(dbType) > -1" @click.prevent="openEditTable(scope.row)" type="warning">编辑表</el-link>
|
||||||
>编辑表</el-link
|
|
||||||
>
|
|
||||||
<el-link class="ml5" @click.prevent="showCreateDdl(scope.row)" type="info">DDL</el-link>
|
<el-link class="ml5" @click.prevent="showCreateDdl(scope.row)" type="info">DDL</el-link>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -127,7 +125,7 @@ import SqlExecBox from '../sqleditor/SqlExecBox';
|
|||||||
import config from '@/common/config';
|
import config from '@/common/config';
|
||||||
import { joinClientParams } from '@/common/request';
|
import { joinClientParams } from '@/common/request';
|
||||||
import { isTrue } from '@/common/assert';
|
import { isTrue } from '@/common/assert';
|
||||||
import { compatibleMysql, DbType } from '../../dialect/index';
|
import { compatibleMysql, DbType, editDbTypes } from '../../dialect/index';
|
||||||
|
|
||||||
const DbTableOp = defineAsyncComponent(() => import('./DbTableOp.vue'));
|
const DbTableOp = defineAsyncComponent(() => import('./DbTableOp.vue'));
|
||||||
|
|
||||||
|
|||||||
@@ -115,6 +115,8 @@ export const DbType = {
|
|||||||
sqlite: 'sqlite',
|
sqlite: 'sqlite',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const editDbTypes = [DbType.mysql, DbType.mariadb, DbType.postgresql, DbType.dm, DbType.oracle, DbType.sqlite];
|
||||||
|
|
||||||
export const compatibleMysql = (dbType: string): boolean => {
|
export const compatibleMysql = (dbType: string): boolean => {
|
||||||
switch (dbType) {
|
switch (dbType) {
|
||||||
case DbType.mysql:
|
case DbType.mysql:
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ class PostgresqlDialect implements DbDialect {
|
|||||||
let marks = false;
|
let marks = false;
|
||||||
if (this.matchType(cl.type, ['char', 'time', 'date', 'text'])) {
|
if (this.matchType(cl.type, ['char', 'time', 'date', 'text'])) {
|
||||||
// 默认值是now()的time或date不需要加引号
|
// 默认值是now()的time或date不需要加引号
|
||||||
if (cl.value.toLowerCase() === 'pg_systimestamp()' && this.matchType(cl.type, ['time', 'date'])) {
|
if (['pg_systimestamp()', 'current_timestamp'].includes(cl.value.toLowerCase()) && this.matchType(cl.type, ['time', 'date'])) {
|
||||||
marks = false;
|
marks = false;
|
||||||
} else {
|
} else {
|
||||||
marks = true;
|
marks = true;
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ class SqliteDialect implements DbDialect {
|
|||||||
|
|
||||||
getDefaultRows(): RowDefinition[] {
|
getDefaultRows(): RowDefinition[] {
|
||||||
return [
|
return [
|
||||||
{ name: 'id', type: 'bigint', length: '20', numScale: '', value: '', notNull: true, pri: true, auto_increment: true, remark: '主键ID' },
|
{ name: 'id', type: 'integer', length: '', numScale: '', value: '', notNull: true, pri: true, auto_increment: true, remark: '主键ID' },
|
||||||
{ name: 'creator_id', type: 'bigint', length: '20', numScale: '', value: '', notNull: true, pri: false, auto_increment: false, remark: '创建人id' },
|
{ name: 'creator_id', type: 'bigint', length: '20', numScale: '', value: '', notNull: true, pri: false, auto_increment: false, remark: '创建人id' },
|
||||||
{
|
{
|
||||||
name: 'creator',
|
name: 'creator',
|
||||||
|
|||||||
@@ -462,6 +462,20 @@ func (d *Db) GetSchemas(rc *req.Ctx) {
|
|||||||
rc.ResData = res
|
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 {
|
func getDbId(g *gin.Context) uint64 {
|
||||||
dbId, _ := strconv.Atoi(g.Param("dbId"))
|
dbId, _ := strconv.Atoi(g.Param("dbId"))
|
||||||
biz.IsTrue(dbId > 0, "dbId错误")
|
biz.IsTrue(dbId > 0, "dbId错误")
|
||||||
|
|||||||
@@ -23,3 +23,11 @@ type DbSqlExecForm struct {
|
|||||||
Sql string `binding:"required" json:"sql"` // 执行sql
|
Sql string `binding:"required" json:"sql"` // 执行sql
|
||||||
Remark string `json:"remark"` // 执行备注
|
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"`
|
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 {
|
type Dialect interface {
|
||||||
@@ -97,6 +104,8 @@ type Dialect interface {
|
|||||||
GetDataType(dbColumnType string) DataType
|
GetDataType(dbColumnType string) DataType
|
||||||
|
|
||||||
FormatStrData(dbColumnValue string, dataType DataType) string
|
FormatStrData(dbColumnValue string, dataType DataType) string
|
||||||
|
|
||||||
|
CopyTable(copy *DbCopyTable) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------- 元数据sql操作 -------------------------
|
// ------------------------- 元数据sql操作 -------------------------
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/kanzihuang/vitess/go/vt/sqlparser"
|
||||||
"mayfly-go/internal/db/dbm/dbi"
|
"mayfly-go/internal/db/dbm/dbi"
|
||||||
"mayfly-go/pkg/errorx"
|
"mayfly-go/pkg/errorx"
|
||||||
"mayfly-go/pkg/logx"
|
"mayfly-go/pkg/logx"
|
||||||
"mayfly-go/pkg/utils/anyx"
|
"mayfly-go/pkg/utils/anyx"
|
||||||
"mayfly-go/pkg/utils/collx"
|
"mayfly-go/pkg/utils/collx"
|
||||||
|
"mayfly-go/pkg/utils/stringx"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -311,3 +313,44 @@ func (dd *DMDialect) FormatStrData(dbColumnValue string, dataType dbi.DataType)
|
|||||||
}
|
}
|
||||||
return dbColumnValue
|
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"
|
"mayfly-go/pkg/utils/collx"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -224,3 +225,25 @@ func (md *MysqlDialect) FormatStrData(dbColumnValue string, dataType dbi.DataTyp
|
|||||||
// mysql不需要格式化时间日期等
|
// mysql不需要格式化时间日期等
|
||||||
return dbColumnValue
|
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
|
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
|
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)
|
tables := make([]dbi.Table, 0)
|
||||||
for _, re := range res {
|
for _, re := range res {
|
||||||
tables = append(tables, dbi.Table{
|
tables = append(tables, dbi.Table{
|
||||||
TableName: re["tableName"].(string),
|
TableName: anyx.ConvString(re["tableName"]),
|
||||||
TableComment: anyx.ConvString(re["tableComment"]),
|
TableComment: anyx.ConvString(re["tableComment"]),
|
||||||
CreateTime: anyx.ConvString(re["createTime"]),
|
CreateTime: anyx.ConvString(re["createTime"]),
|
||||||
TableRows: anyx.ConvInt(re["tableRows"]),
|
TableRows: anyx.ConvInt(re["tableRows"]),
|
||||||
@@ -97,7 +97,7 @@ func (sd *SqliteDialect) GetColumns(tableNames ...string) ([]dbi.Column, error)
|
|||||||
}
|
}
|
||||||
columns = append(columns, dbi.Column{
|
columns = append(columns, dbi.Column{
|
||||||
TableName: tableName,
|
TableName: tableName,
|
||||||
ColumnName: re["name"].(string),
|
ColumnName: anyx.ConvString(re["name"]),
|
||||||
ColumnType: strings.ToLower(anyx.ConvString(re["type"])),
|
ColumnType: strings.ToLower(anyx.ConvString(re["type"])),
|
||||||
ColumnComment: "",
|
ColumnComment: "",
|
||||||
Nullable: nullable,
|
Nullable: nullable,
|
||||||
@@ -117,7 +117,7 @@ func (sd *SqliteDialect) GetPrimaryKey(tableName string) (string, error) {
|
|||||||
}
|
}
|
||||||
for _, re := range res {
|
for _, re := range res {
|
||||||
if anyx.ConvInt(re["pk"]) == 1 {
|
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)
|
indexs := make([]dbi.Index, 0)
|
||||||
for _, re := range res {
|
for _, re := range res {
|
||||||
indexSql := re["indexSql"].(string)
|
indexSql := anyx.ConvString(re["indexSql"])
|
||||||
isUnique := strings.Contains(indexSql, "CREATE UNIQUE INDEX")
|
isUnique := strings.Contains(indexSql, "CREATE UNIQUE INDEX")
|
||||||
nonUnique := 1
|
nonUnique := 1
|
||||||
if isUnique {
|
if isUnique {
|
||||||
@@ -157,7 +157,7 @@ func (sd *SqliteDialect) GetTableIndex(tableName string) ([]dbi.Index, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
indexs = append(indexs, dbi.Index{
|
indexs = append(indexs, dbi.Index{
|
||||||
IndexName: re["indexName"].(string),
|
IndexName: anyx.ConvString(re["indexName"]),
|
||||||
ColumnName: extractIndexFields(indexSql),
|
ColumnName: extractIndexFields(indexSql),
|
||||||
IndexType: anyx.ConvString(re["indexType"]),
|
IndexType: anyx.ConvString(re["indexType"]),
|
||||||
IndexComment: anyx.ConvString(re["indexComment"]),
|
IndexComment: anyx.ConvString(re["indexComment"]),
|
||||||
@@ -171,13 +171,13 @@ func (sd *SqliteDialect) GetTableIndex(tableName string) ([]dbi.Index, error) {
|
|||||||
|
|
||||||
// 获取建表ddl
|
// 获取建表ddl
|
||||||
func (sd *SqliteDialect) GetTableDDL(tableName string) (string, error) {
|
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
var builder strings.Builder
|
var builder strings.Builder
|
||||||
for _, re := range res {
|
for _, re := range res {
|
||||||
builder.WriteString(re["sql"].(string))
|
builder.WriteString(anyx.ConvString(re["sql"]) + "; \n\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.String(), nil
|
return builder.String(), nil
|
||||||
@@ -245,3 +245,35 @@ func (sd *SqliteDialect) FormatStrData(dbColumnValue string, dataType dbi.DataTy
|
|||||||
}
|
}
|
||||||
return dbColumnValue
|
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/hint-tables", d.HintTables),
|
||||||
|
|
||||||
req.NewGet(":dbId/restore-task", d.GetRestoreTask),
|
req.NewGet(":dbId/restore-task", d.GetRestoreTask),
|
||||||
|
|
||||||
req.NewPost(":dbId/restore-task", d.SaveRestoreTask).
|
req.NewPost(":dbId/restore-task", d.SaveRestoreTask).
|
||||||
Log(req.NewLogSave("db-保存数据库恢复任务")),
|
Log(req.NewLogSave("db-保存数据库恢复任务")),
|
||||||
|
|
||||||
req.NewGet(":dbId/restore-histories", d.GetRestoreHistories),
|
req.NewGet(":dbId/restore-histories", d.GetRestoreHistories),
|
||||||
|
|
||||||
|
req.NewPost(":dbId/copy-table", d.CopyTable),
|
||||||
}
|
}
|
||||||
|
|
||||||
req.BatchSetGroup(db, reqs[:])
|
req.BatchSetGroup(db, reqs[:])
|
||||||
|
|||||||
Reference in New Issue
Block a user