mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-12-25 09:06:34 +08:00
331
mayfly_go_web/src/views/ops/db/DbTransferEdit.vue
Normal file
331
mayfly_go_web/src/views/ops/db/DbTransferEdit.vue
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
<template>
|
||||||
|
<div class="db-transfer-edit">
|
||||||
|
<el-dialog
|
||||||
|
:title="title"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:before-close="cancel"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
width="850px"
|
||||||
|
>
|
||||||
|
<el-form :model="form" ref="dbForm" :rules="rules" label-width="auto">
|
||||||
|
<el-tabs v-model="tabActiveName">
|
||||||
|
<el-tab-pane label="基本信息" :name="basicTab">
|
||||||
|
<el-form-item prop="srcDbId" label="源数据库" required>
|
||||||
|
<db-select-tree
|
||||||
|
placeholder="请选择源数据库"
|
||||||
|
v-model:db-id="form.srcDbId"
|
||||||
|
v-model:inst-name="form.srcInstName"
|
||||||
|
v-model:db-name="form.srcDbName"
|
||||||
|
v-model:tag-path="form.srcTagPath"
|
||||||
|
v-model:db-type="form.srcDbType"
|
||||||
|
@select-db="onSelectSrcDb"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item prop="targetDbId" label="目标数据库" required>
|
||||||
|
<db-select-tree
|
||||||
|
placeholder="请选择目标数据库"
|
||||||
|
v-model:db-id="form.targetDbId"
|
||||||
|
v-model:inst-name="form.targetInstName"
|
||||||
|
v-model:db-name="form.targetDbName"
|
||||||
|
v-model:tag-path="form.targetTagPath"
|
||||||
|
v-model:db-type="form.targetDbType"
|
||||||
|
@select-db="onSelectTargetDb"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item prop="strategy" label="迁移策略" required>
|
||||||
|
<el-select v-model="form.strategy" filterable placeholder="迁移策略">
|
||||||
|
<el-option label="全量" :value="1" />
|
||||||
|
<el-option label="增量(暂不可用)" disabled :value="2" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item prop="nameCase" label="转换表、字段名" required>
|
||||||
|
<el-select v-model="form.nameCase">
|
||||||
|
<el-option label="无" :value="1" />
|
||||||
|
<el-option label="大写" :value="2" />
|
||||||
|
<el-option label="小写" :value="3" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="deleteTable" label="创建前删除表" required>
|
||||||
|
<el-select v-model="form.deleteTable">
|
||||||
|
<el-option label="是" :value="1" />
|
||||||
|
<el-option label="否" :value="2" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="数据库对象" :name="tableTab" :disabled="!baseFieldCompleted">
|
||||||
|
<el-form-item>
|
||||||
|
<el-input v-model="state.filterSrcTableText" style="width: 240px" placeholder="过滤表" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-tree
|
||||||
|
ref="srcTreeRef"
|
||||||
|
style="width: 760px; max-height: 400px; overflow-y: auto"
|
||||||
|
default-expand-all
|
||||||
|
:expand-on-click-node="false"
|
||||||
|
:data="state.srcTableTree"
|
||||||
|
node-key="id"
|
||||||
|
show-checkbox
|
||||||
|
@check-change="handleSrcTableCheckChange"
|
||||||
|
:filter-node-method="filterSrcTableTreeNode"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div>
|
||||||
|
<el-button @click="cancel()">取 消</el-button>
|
||||||
|
<el-button type="primary" :loading="saveBtnLoading" @click="btnOk">确 定</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, nextTick, reactive, ref, toRefs, watch } from 'vue';
|
||||||
|
import { dbApi } from './api';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
import DbSelectTree from '@/views/ops/db/component/DbSelectTree.vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: [Boolean, Object],
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
//定义事件
|
||||||
|
const emit = defineEmits(['update:visible', 'cancel', 'val-change']);
|
||||||
|
|
||||||
|
const dialogVisible = defineModel<boolean>('visible', { default: false });
|
||||||
|
|
||||||
|
const rules = {};
|
||||||
|
|
||||||
|
const dbForm: any = ref(null);
|
||||||
|
|
||||||
|
const basicTab = 'basic';
|
||||||
|
const tableTab = 'table';
|
||||||
|
|
||||||
|
type FormData = {
|
||||||
|
id?: number;
|
||||||
|
srcDbId?: number;
|
||||||
|
srcDbName?: string;
|
||||||
|
srcDbType?: string;
|
||||||
|
srcInstName?: string;
|
||||||
|
srcTagPath?: string;
|
||||||
|
srcTableNames?: string;
|
||||||
|
targetDbId?: number;
|
||||||
|
targetInstName?: string;
|
||||||
|
targetDbName?: string;
|
||||||
|
targetTagPath?: string;
|
||||||
|
targetDbType?: string;
|
||||||
|
strategy: 1 | 2;
|
||||||
|
nameCase: 1 | 2 | 3;
|
||||||
|
deleteTable?: 1 | 2;
|
||||||
|
checkedKeys: string;
|
||||||
|
runningState: 1 | 2;
|
||||||
|
};
|
||||||
|
|
||||||
|
const basicFormData = {
|
||||||
|
strategy: 1,
|
||||||
|
nameCase: 1,
|
||||||
|
deleteTable: 1,
|
||||||
|
checkedKeys: '',
|
||||||
|
runningState: 1,
|
||||||
|
} as FormData;
|
||||||
|
|
||||||
|
const srcTableList = ref<{ tableName: string; tableComment: string }[]>([]);
|
||||||
|
const srcTableListDisabled = ref(false);
|
||||||
|
|
||||||
|
const defaultKeys = ['tab-check', 'all', 'table-list'];
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
tabActiveName: 'basic',
|
||||||
|
form: basicFormData,
|
||||||
|
submitForm: {} as any,
|
||||||
|
srcTableFields: [] as string[],
|
||||||
|
targetColumnList: [] as any[],
|
||||||
|
filterSrcTableText: '',
|
||||||
|
srcTableTree: [
|
||||||
|
{
|
||||||
|
id: 'tab-check',
|
||||||
|
label: '表',
|
||||||
|
children: [
|
||||||
|
{ id: 'all', label: '全部表(*)' },
|
||||||
|
{
|
||||||
|
id: 'table-list',
|
||||||
|
label: '自定义',
|
||||||
|
disabled: srcTableListDisabled,
|
||||||
|
children: [] as any[],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { tabActiveName, form, submitForm } = toRefs(state);
|
||||||
|
|
||||||
|
const { isFetching: saveBtnLoading, execute: saveExec } = dbApi.saveDbTransferTask.useApi(submitForm);
|
||||||
|
|
||||||
|
// 基础字段信息是否填写完整
|
||||||
|
const baseFieldCompleted = computed(() => {
|
||||||
|
return state.form.srcDbId && state.form.targetDbId && state.form.targetDbName;
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(dialogVisible, async (newValue: boolean) => {
|
||||||
|
if (!newValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.tabActiveName = 'basic';
|
||||||
|
const propsData = props.data as any;
|
||||||
|
if (!propsData?.id) {
|
||||||
|
let d = {} as FormData;
|
||||||
|
Object.assign(d, basicFormData);
|
||||||
|
state.form = d;
|
||||||
|
await nextTick(() => {
|
||||||
|
srcTreeRef.value.setCheckedKeys([]);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.form = props.data as FormData;
|
||||||
|
let { srcDbId, targetDbId } = state.form;
|
||||||
|
|
||||||
|
// 初始化src数据源
|
||||||
|
if (srcDbId) {
|
||||||
|
// 通过tagPath查询实例列表
|
||||||
|
const dbInfoRes = await dbApi.dbs.request({ id: srcDbId });
|
||||||
|
const db = dbInfoRes.list[0];
|
||||||
|
// 初始化实例
|
||||||
|
db.databases = db.database?.split(' ').sort() || [];
|
||||||
|
|
||||||
|
if (srcDbId && state.form.srcDbName) {
|
||||||
|
await loadDbTables(srcDbId, state.form.srcDbName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化target数据源
|
||||||
|
if (targetDbId) {
|
||||||
|
// 通过tagPath查询实例列表
|
||||||
|
const dbInfoRes = await dbApi.dbs.request({ id: targetDbId });
|
||||||
|
const db = dbInfoRes.list[0];
|
||||||
|
// 初始化实例
|
||||||
|
db.databases = db.database?.split(' ').sort() || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化勾选迁移表
|
||||||
|
srcTreeRef.value.setCheckedKeys(state.form.checkedKeys.split(','));
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => state.filterSrcTableText,
|
||||||
|
(val) => {
|
||||||
|
srcTreeRef.value!.filter(val);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const onSelectSrcDb = async (params: any) => {
|
||||||
|
// 初始化数据源
|
||||||
|
params.databases = params.dbs; // 数据源里需要这个值
|
||||||
|
await loadDbTables(params.id, params.db);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSelectTargetDb = async (params: any) => {
|
||||||
|
console.log(params);
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadDbTables = async (dbId: number, db: string) => {
|
||||||
|
// 加载db下的表
|
||||||
|
srcTableList.value = await dbApi.tableInfos.request({ id: dbId, db });
|
||||||
|
handleLoadSrcTableTree();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSrcTableCheckChange = (data: { id: string; name: string }, checked: boolean) => {
|
||||||
|
if (data.id === 'all') {
|
||||||
|
srcTableListDisabled.value = checked;
|
||||||
|
if (checked) {
|
||||||
|
state.form.checkedKeys = 'all';
|
||||||
|
} else {
|
||||||
|
state.form.checkedKeys = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.id && (data.id + '').startsWith('list-item')) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterSrcTableTreeNode = (value: string, data: any) => {
|
||||||
|
if (!value) return true;
|
||||||
|
return data.label.includes(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLoadSrcTableTree = () => {
|
||||||
|
state.srcTableTree[0].children[1].children = srcTableList.value.map((item) => {
|
||||||
|
return {
|
||||||
|
id: item.tableName,
|
||||||
|
label: item.tableName + (item.tableComment && '-' + item.tableComment),
|
||||||
|
disabled: srcTableListDisabled,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getReqForm = async () => {
|
||||||
|
return { ...state.form };
|
||||||
|
};
|
||||||
|
|
||||||
|
const srcTreeRef = ref();
|
||||||
|
|
||||||
|
const getCheckedKeys = () => {
|
||||||
|
let checks = srcTreeRef.value!.getCheckedKeys(false);
|
||||||
|
if (checks.indexOf('all') >= 0) {
|
||||||
|
return ['all'];
|
||||||
|
}
|
||||||
|
return checks.filter((item: any) => !defaultKeys.includes(item));
|
||||||
|
};
|
||||||
|
|
||||||
|
const btnOk = async () => {
|
||||||
|
dbForm.value.validate(async (valid: boolean) => {
|
||||||
|
if (!valid) {
|
||||||
|
ElMessage.error('请正确填写信息');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.submitForm = await getReqForm();
|
||||||
|
|
||||||
|
let checkedKeys = getCheckedKeys();
|
||||||
|
if (checkedKeys.length > 0) {
|
||||||
|
state.submitForm.checkedKeys = checkedKeys.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.submitForm.checkedKeys) {
|
||||||
|
ElMessage.error('请选择需要迁移的表');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await saveExec();
|
||||||
|
ElMessage.success('保存成功');
|
||||||
|
emit('val-change', state.form);
|
||||||
|
cancel();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
|
dialogVisible.value = false;
|
||||||
|
emit('cancel');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
.db-transfer-edit {
|
||||||
|
.el-select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
180
mayfly_go_web/src/views/ops/db/DbTransferList.vue
Normal file
180
mayfly_go_web/src/views/ops/db/DbTransferList.vue
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
<template>
|
||||||
|
<div class="db-list">
|
||||||
|
<page-table
|
||||||
|
ref="pageTableRef"
|
||||||
|
:page-api="dbApi.dbTransferTasks"
|
||||||
|
:searchItems="searchItems"
|
||||||
|
v-model:query-form="query"
|
||||||
|
:show-selection="true"
|
||||||
|
v-model:selection-data="state.selectionData"
|
||||||
|
:columns="columns"
|
||||||
|
>
|
||||||
|
<template #tableHeader>
|
||||||
|
<el-button v-auth="perms.save" type="primary" icon="plus" @click="edit(false)">添加</el-button>
|
||||||
|
<el-button v-auth="perms.del" :disabled="selectionData.length < 1" @click="del()" type="danger" icon="delete">删除</el-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #srcDb="{ data }">
|
||||||
|
<el-tooltip :content="`${data.srcTagPath} > ${data.srcInstName} > ${data.srcDbName}`">
|
||||||
|
<span>
|
||||||
|
<SvgIcon :name="getDbDialect(data.srcDbType).getInfo().icon" :size="18" />
|
||||||
|
{{ data.srcInstName }}
|
||||||
|
</span>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
<template #targetDb="{ data }">
|
||||||
|
<el-tooltip :content="`${data.targetTagPath} > ${data.targetInstName} > ${data.targetDbName}`">
|
||||||
|
<span>
|
||||||
|
<SvgIcon :name="getDbDialect(data.targetDbType).getInfo().icon" :size="18" />
|
||||||
|
{{ data.targetInstName }}
|
||||||
|
</span>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #action="{ data }">
|
||||||
|
<!-- 删除、启停用、编辑 -->
|
||||||
|
<el-button v-if="actionBtns[perms.save]" @click="edit(data)" type="primary" link>编辑</el-button>
|
||||||
|
<el-button v-if="actionBtns[perms.log]" type="primary" link @click="log(data)">日志</el-button>
|
||||||
|
<el-button v-if="data.runningState === 1" @click="stop(data.id)" type="danger" link>停止</el-button>
|
||||||
|
<el-button v-if="actionBtns[perms.run] && data.runningState !== 1" type="primary" link @click="reRun(data)">运行</el-button>
|
||||||
|
</template>
|
||||||
|
</page-table>
|
||||||
|
|
||||||
|
<db-transfer-edit @val-change="search" :title="editDialog.title" v-model:visible="editDialog.visible" v-model:data="editDialog.data" />
|
||||||
|
|
||||||
|
<db-transfer-log v-model:visible="logsDialog.visible" v-model:taskId="logsDialog.taskId" :running="state.logsDialog.running" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { defineAsyncComponent, onMounted, reactive, ref, Ref, toRefs } from 'vue';
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
|
import { dbApi } from './api';
|
||||||
|
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||||
|
import { TableColumn } from '@/components/pagetable';
|
||||||
|
import { hasPerms } from '@/components/auth/auth';
|
||||||
|
import { SearchItem } from '@/components/SearchForm';
|
||||||
|
import { getDbDialect } from '@/views/ops/db/dialect';
|
||||||
|
|
||||||
|
const DbTransferEdit = defineAsyncComponent(() => import('./DbTransferEdit.vue'));
|
||||||
|
const DbTransferLog = defineAsyncComponent(() => import('./DbTransferLog.vue'));
|
||||||
|
|
||||||
|
const perms = {
|
||||||
|
save: 'db:transfer:save',
|
||||||
|
del: 'db:transfer:del',
|
||||||
|
status: 'db:transfer:status',
|
||||||
|
log: 'db:transfer:log',
|
||||||
|
run: 'db:transfer:run',
|
||||||
|
};
|
||||||
|
|
||||||
|
const searchItems = [SearchItem.input('name', '名称')];
|
||||||
|
|
||||||
|
const columns = ref([
|
||||||
|
TableColumn.new('srcDb', '源库').setMinWidth(250).isSlot(),
|
||||||
|
TableColumn.new('targetDb', '目标库').setMinWidth(250).isSlot(),
|
||||||
|
TableColumn.new('modifier', '修改人').alignCenter(),
|
||||||
|
TableColumn.new('updateTime', '修改时间').alignCenter().isTime(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 该用户拥有的的操作列按钮权限
|
||||||
|
const actionBtns = hasPerms([perms.save, perms.del, perms.status, perms.log, perms.run]);
|
||||||
|
const actionWidth = ((actionBtns[perms.save] ? 1 : 0) + (actionBtns[perms.log] ? 1 : 0) + (actionBtns[perms.run] ? 1 : 0)) * 55;
|
||||||
|
const actionColumn = TableColumn.new('action', '操作').isSlot().setMinWidth(actionWidth).fixedRight().alignCenter();
|
||||||
|
const pageTableRef: Ref<any> = ref(null);
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
row: {},
|
||||||
|
dbId: 0,
|
||||||
|
db: '',
|
||||||
|
/**
|
||||||
|
* 选中的数据
|
||||||
|
*/
|
||||||
|
selectionData: [],
|
||||||
|
/**
|
||||||
|
* 查询条件
|
||||||
|
*/
|
||||||
|
query: {
|
||||||
|
name: null,
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 0,
|
||||||
|
},
|
||||||
|
editDialog: {
|
||||||
|
visible: false,
|
||||||
|
data: null as any,
|
||||||
|
title: '新增数据数据迁移任务',
|
||||||
|
},
|
||||||
|
logsDialog: {
|
||||||
|
taskId: 0,
|
||||||
|
visible: false,
|
||||||
|
data: null as any,
|
||||||
|
running: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { selectionData, query, editDialog, logsDialog } = toRefs(state);
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
if (Object.keys(actionBtns).length > 0) {
|
||||||
|
columns.value.push(actionColumn);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const search = () => {
|
||||||
|
pageTableRef.value.search();
|
||||||
|
};
|
||||||
|
|
||||||
|
const edit = async (data: any) => {
|
||||||
|
if (!data) {
|
||||||
|
state.editDialog.data = null;
|
||||||
|
state.editDialog.title = '新增数据库迁移任务';
|
||||||
|
} else {
|
||||||
|
state.editDialog.data = data;
|
||||||
|
state.editDialog.title = '修改数据库迁移任务';
|
||||||
|
}
|
||||||
|
state.editDialog.visible = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const stop = async (id: any) => {
|
||||||
|
await ElMessageBox.confirm(`确定停止?`, '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
});
|
||||||
|
await dbApi.stopDbTransferTask.request({ taskId: id });
|
||||||
|
ElMessage.success(`停止成功`);
|
||||||
|
search();
|
||||||
|
};
|
||||||
|
|
||||||
|
const log = async (data: any) => {
|
||||||
|
state.logsDialog.taskId = data.id;
|
||||||
|
state.logsDialog.visible = true;
|
||||||
|
state.logsDialog.running = data.state === 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const reRun = async (data: any) => {
|
||||||
|
await ElMessageBox.confirm(`确定运行?`, '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
});
|
||||||
|
await dbApi.runDbTransferTask.request({ taskId: data.id });
|
||||||
|
ElMessage.success('运行成功');
|
||||||
|
search();
|
||||||
|
};
|
||||||
|
|
||||||
|
const del = async () => {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(`确定删除任务?`, '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
});
|
||||||
|
await dbApi.deleteDbTransferTask.request({ taskId: state.selectionData.map((x: any) => x.id).join(',') });
|
||||||
|
ElMessage.success('删除成功');
|
||||||
|
search();
|
||||||
|
} catch (err) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss"></style>
|
||||||
117
mayfly_go_web/src/views/ops/db/DbTransferLog.vue
Normal file
117
mayfly_go_web/src/views/ops/db/DbTransferLog.vue
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
<template>
|
||||||
|
<div class="sync-task-logs">
|
||||||
|
<el-dialog v-model="dialogVisible" :before-close="cancel" :destroy-on-close="false" width="1120px">
|
||||||
|
<template #header>
|
||||||
|
<span class="mr10">任务执行日志</span>
|
||||||
|
<el-switch v-model="realTime" @change="watchPolling" inline-prompt active-text="实时" inactive-text="非实时" />
|
||||||
|
<el-button @click="search" icon="Refresh" circle size="small" :loading="realTime" class="ml10"></el-button>
|
||||||
|
</template>
|
||||||
|
<page-table
|
||||||
|
ref="logTableRef"
|
||||||
|
:page-api="dbApi.dbTransferTaskLogs"
|
||||||
|
v-model:query-form="query"
|
||||||
|
:tool-button="false"
|
||||||
|
:columns="columns"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { reactive, Ref, ref, toRefs, watch } from 'vue';
|
||||||
|
import { dbApi } from '@/views/ops/db/api';
|
||||||
|
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||||
|
import { TableColumn } from '@/components/pagetable';
|
||||||
|
import { DbDataSyncLogStatusEnum } from './enums';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
taskId: {
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
|
running: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const dialogVisible = defineModel<boolean>('visible', { default: false });
|
||||||
|
|
||||||
|
const columns = ref([
|
||||||
|
// 状态:1.成功 -1.失败
|
||||||
|
TableColumn.new('status', '状态').alignCenter().typeTag(DbDataSyncLogStatusEnum),
|
||||||
|
TableColumn.new('createTime', '时间').alignCenter().isTime(),
|
||||||
|
TableColumn.new('errText', '日志'),
|
||||||
|
TableColumn.new('dataSqlFull', 'SQL').alignCenter(),
|
||||||
|
TableColumn.new('resNum', '数据条数'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
watch(dialogVisible, (newValue: any) => {
|
||||||
|
if (!newValue) {
|
||||||
|
state.polling = false;
|
||||||
|
watchPolling(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.query.taskId = props.taskId!;
|
||||||
|
search();
|
||||||
|
state.realTime = props.running;
|
||||||
|
watchPolling(props.running);
|
||||||
|
});
|
||||||
|
|
||||||
|
const startPolling = () => {
|
||||||
|
if (!state.polling) {
|
||||||
|
state.polling = true;
|
||||||
|
state.pollingIndex = setInterval(search, 1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const stopPolling = () => {
|
||||||
|
if (state.polling) {
|
||||||
|
state.polling = false;
|
||||||
|
clearInterval(state.pollingIndex);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const watchPolling = (polling: boolean) => {
|
||||||
|
if (polling) {
|
||||||
|
startPolling();
|
||||||
|
} else {
|
||||||
|
stopPolling();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const logTableRef: Ref<any> = ref(null);
|
||||||
|
|
||||||
|
const search = () => {
|
||||||
|
try {
|
||||||
|
logTableRef.value.search();
|
||||||
|
} catch (e) {
|
||||||
|
/* empty */
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:visible', 'cancel', 'val-change']);
|
||||||
|
//定义事件
|
||||||
|
const cancel = () => {
|
||||||
|
dialogVisible.value = false;
|
||||||
|
emit('cancel');
|
||||||
|
watchPolling(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
polling: false,
|
||||||
|
pollingIndex: 0 as any,
|
||||||
|
realTime: props.running,
|
||||||
|
/**
|
||||||
|
* 查询条件
|
||||||
|
*/
|
||||||
|
query: {
|
||||||
|
taskId: 0,
|
||||||
|
name: null,
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { query, realTime } = toRefs(state);
|
||||||
|
</script>
|
||||||
@@ -301,7 +301,9 @@ watch(dialogVisible, async (newValue: boolean) => {
|
|||||||
state.tabActiveName = 'basic';
|
state.tabActiveName = 'basic';
|
||||||
const propsData = props.data as any;
|
const propsData = props.data as any;
|
||||||
if (!propsData?.id) {
|
if (!propsData?.id) {
|
||||||
state.form = basicFormData;
|
let d = {} as FormData;
|
||||||
|
Object.assign(d, basicFormData);
|
||||||
|
state.form = d;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,14 @@ export const dbApi = {
|
|||||||
runDatasyncTask: Api.newPost('/datasync/tasks/{taskId}/run'),
|
runDatasyncTask: Api.newPost('/datasync/tasks/{taskId}/run'),
|
||||||
stopDatasyncTask: Api.newPost('/datasync/tasks/{taskId}/stop'),
|
stopDatasyncTask: Api.newPost('/datasync/tasks/{taskId}/stop'),
|
||||||
datasyncLogs: Api.newGet('/datasync/tasks/{taskId}/logs'),
|
datasyncLogs: Api.newGet('/datasync/tasks/{taskId}/logs'),
|
||||||
|
|
||||||
|
// 数据库迁移相关
|
||||||
|
dbTransferTasks: Api.newGet('/dbTransfer'),
|
||||||
|
saveDbTransferTask: Api.newPost('/dbTransfer/save'),
|
||||||
|
deleteDbTransferTask: Api.newDelete('/dbTransfer/{taskId}/del'),
|
||||||
|
runDbTransferTask: Api.newPost('/dbTransfer/{taskId}/run'),
|
||||||
|
stopDbTransferTask: Api.newPost('/dbTransfer/{taskId}/stop'),
|
||||||
|
dbTransferTaskLogs: Api.newGet('/dbTransfer/{taskId}/logs'),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dbSqlExecApi = {
|
export const dbSqlExecApi = {
|
||||||
|
|||||||
@@ -426,7 +426,7 @@ export class DbInst {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
static isNumber(columnType: string) {
|
static isNumber(columnType: string) {
|
||||||
return columnType.match(/int|double|float|number|decimal|byte|bit/gi);
|
return columnType && columnType.match(/int|double|float|number|decimal|byte|bit/gi);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ const DM_TYPE_LIST: sqlColumnType[] = [
|
|||||||
// 位串数据类型 BIT 用于存储整数数据 1、0 或 NULL,只有 0 才转换为假,其他非空、非 0 值都会自动转换为真
|
// 位串数据类型 BIT 用于存储整数数据 1、0 或 NULL,只有 0 才转换为假,其他非空、非 0 值都会自动转换为真
|
||||||
{ udtName: 'BIT', dataType: 'BIT', desc: '用于存储整数数据 1、0 或 NULL', space: '1', range: '1' },
|
{ udtName: 'BIT', dataType: 'BIT', desc: '用于存储整数数据 1、0 或 NULL', space: '1', range: '1' },
|
||||||
// 一般日期时间数据类型 DATE TIME TIMESTAMP 默认精度 6
|
// 一般日期时间数据类型 DATE TIME TIMESTAMP 默认精度 6
|
||||||
// 多媒体数据类型 TEXT/LONG/LONGVARCHAR 类型:变长字符串类型 IMAGE/LONGVARBINARY 类型 BLOB CLOB BFILE 100G-1
|
|
||||||
{ udtName: 'DATE', dataType: 'DATE', desc: '年、月、日', space: '', range: '' },
|
{ udtName: 'DATE', dataType: 'DATE', desc: '年、月、日', space: '', range: '' },
|
||||||
{ udtName: 'TIME', dataType: 'TIME', desc: '时、分、秒', space: '', range: '' },
|
{ udtName: 'TIME', dataType: 'TIME', desc: '时、分、秒', space: '', range: '' },
|
||||||
{
|
{
|
||||||
@@ -45,6 +44,7 @@ const DM_TYPE_LIST: sqlColumnType[] = [
|
|||||||
space: '',
|
space: '',
|
||||||
range: '-4712-01-01 00:00:00.000000000 ~ 9999-12-31 23:59:59.999999999',
|
range: '-4712-01-01 00:00:00.000000000 ~ 9999-12-31 23:59:59.999999999',
|
||||||
},
|
},
|
||||||
|
// 多媒体数据类型 TEXT/LONG/LONGVARCHAR 类型:变长字符串类型 IMAGE/LONGVARBINARY 类型 BLOB CLOB BFILE 100G-1
|
||||||
{ udtName: 'TEXT', dataType: 'TEXT', desc: '变长字符串', space: '', range: '100G-1' },
|
{ udtName: 'TEXT', dataType: 'TEXT', desc: '变长字符串', space: '', range: '100G-1' },
|
||||||
{ udtName: 'LONG', dataType: 'LONG', desc: '同TEXT', space: '', range: '100G-1' },
|
{ udtName: 'LONG', dataType: 'LONG', desc: '同TEXT', space: '', range: '100G-1' },
|
||||||
{ udtName: 'LONGVARCHAR', dataType: 'LONGVARCHAR', desc: '同TEXT', space: '', range: '100G-1' },
|
{ udtName: 'LONGVARCHAR', dataType: 'LONGVARCHAR', desc: '同TEXT', space: '', range: '100G-1' },
|
||||||
|
|||||||
@@ -296,7 +296,12 @@ class OracleDialect implements DbDialect {
|
|||||||
let length = this.getTypeLengthSql(cl);
|
let length = this.getTypeLengthSql(cl);
|
||||||
// 默认值
|
// 默认值
|
||||||
let defVal = this.getDefaultValueSql(cl);
|
let defVal = this.getDefaultValueSql(cl);
|
||||||
let incr = cl.auto_increment && create ? 'generated by default as IDENTITY' : '';
|
let incr = '';
|
||||||
|
if (cl.auto_increment && create) {
|
||||||
|
cl.type = 'number';
|
||||||
|
length = '';
|
||||||
|
incr = 'generated by default as IDENTITY';
|
||||||
|
}
|
||||||
// 如果有原名以原名为准
|
// 如果有原名以原名为准
|
||||||
let name = cl.oldName && cl.name !== cl.oldName ? cl.oldName : cl.name;
|
let name = cl.oldName && cl.name !== cl.oldName ? cl.oldName : cl.name;
|
||||||
let baseSql = ` ${this.quoteIdentifier(name)} ${cl.type}${length} ${incr}`;
|
let baseSql = ` ${this.quoteIdentifier(name)} ${cl.type}${length} ${incr}`;
|
||||||
@@ -329,10 +334,10 @@ class OracleDialect implements DbDialect {
|
|||||||
// 主键语句
|
// 主键语句
|
||||||
let prisql = '';
|
let prisql = '';
|
||||||
if (pris.length > 0) {
|
if (pris.length > 0) {
|
||||||
prisql = ` CONSTRAINT "PK_${data.tableName}" PRIMARY KEY (${pris.join(',')});`;
|
prisql = ` PRIMARY KEY (${pris.join(',')})`;
|
||||||
}
|
}
|
||||||
// 建表
|
// 建表
|
||||||
createSql = `CREATE TABLE ${dbTable} ( ${fields.join(',')} ) ${prisql ? ',' + prisql : ''};`;
|
createSql = `CREATE TABLE ${dbTable} ( ${fields.join(',')} ${prisql ? ',' + prisql : ''} ) ;`;
|
||||||
// 表注释
|
// 表注释
|
||||||
if (data.tableComment) {
|
if (data.tableComment) {
|
||||||
tableCommentSql = ` COMMENT ON TABLE ${dbTable} is '${data.tableComment}'; `;
|
tableCommentSql = ` COMMENT ON TABLE ${dbTable} is '${data.tableComment}'; `;
|
||||||
|
|||||||
@@ -75,13 +75,13 @@ func (d *DataSyncTask) ChangeStatus(rc *req.Ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *DataSyncTask) Run(rc *req.Ctx) {
|
func (d *DataSyncTask) Run(rc *req.Ctx) {
|
||||||
taskId := getTaskId(rc)
|
taskId := d.getTaskId(rc)
|
||||||
rc.ReqParam = taskId
|
rc.ReqParam = taskId
|
||||||
_ = d.DataSyncTaskApp.RunCronJob(taskId)
|
_ = d.DataSyncTaskApp.RunCronJob(taskId)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DataSyncTask) Stop(rc *req.Ctx) {
|
func (d *DataSyncTask) Stop(rc *req.Ctx) {
|
||||||
taskId := getTaskId(rc)
|
taskId := d.getTaskId(rc)
|
||||||
rc.ReqParam = taskId
|
rc.ReqParam = taskId
|
||||||
|
|
||||||
task := new(entity.DataSyncTask)
|
task := new(entity.DataSyncTask)
|
||||||
@@ -91,12 +91,12 @@ func (d *DataSyncTask) Stop(rc *req.Ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *DataSyncTask) GetTask(rc *req.Ctx) {
|
func (d *DataSyncTask) GetTask(rc *req.Ctx) {
|
||||||
taskId := getTaskId(rc)
|
taskId := d.getTaskId(rc)
|
||||||
dbEntity, _ := d.DataSyncTaskApp.GetById(new(entity.DataSyncTask), taskId)
|
dbEntity, _ := d.DataSyncTaskApp.GetById(new(entity.DataSyncTask), taskId)
|
||||||
rc.ResData = dbEntity
|
rc.ResData = dbEntity
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTaskId(rc *req.Ctx) uint64 {
|
func (d *DataSyncTask) getTaskId(rc *req.Ctx) uint64 {
|
||||||
instanceId := rc.PathParamInt("taskId")
|
instanceId := rc.PathParamInt("taskId")
|
||||||
biz.IsTrue(instanceId > 0, "instanceId 错误")
|
biz.IsTrue(instanceId > 0, "instanceId 错误")
|
||||||
return uint64(instanceId)
|
return uint64(instanceId)
|
||||||
|
|||||||
85
server/internal/db/api/db_transfer.go
Normal file
85
server/internal/db/api/db_transfer.go
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"mayfly-go/internal/db/api/form"
|
||||||
|
"mayfly-go/internal/db/api/vo"
|
||||||
|
"mayfly-go/internal/db/application"
|
||||||
|
"mayfly-go/internal/db/domain/entity"
|
||||||
|
"mayfly-go/pkg/biz"
|
||||||
|
"mayfly-go/pkg/logx"
|
||||||
|
"mayfly-go/pkg/req"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DbTransferTask struct {
|
||||||
|
DbTransferTask application.DbTransferTask `inject:"DbTransferTaskApp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DbTransferTask) Tasks(rc *req.Ctx) {
|
||||||
|
queryCond, page := req.BindQueryAndPage[*entity.DbTransferTaskQuery](rc, new(entity.DbTransferTaskQuery))
|
||||||
|
res, err := d.DbTransferTask.GetPageList(queryCond, page, new([]vo.DbTransferTaskListVO))
|
||||||
|
biz.ErrIsNil(err)
|
||||||
|
rc.ResData = res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DbTransferTask) Logs(rc *req.Ctx) {
|
||||||
|
queryCond, page := req.BindQueryAndPage[*entity.DbTransferLogQuery](rc, new(entity.DbTransferLogQuery))
|
||||||
|
res, err := d.DbTransferTask.GetTaskLogList(queryCond, page, new([]vo.DbTransferLogListVO))
|
||||||
|
biz.ErrIsNil(err)
|
||||||
|
rc.ResData = res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DbTransferTask) SaveTask(rc *req.Ctx) {
|
||||||
|
reqForm := &form.DbTransferTaskForm{}
|
||||||
|
task := req.BindJsonAndCopyTo[*entity.DbTransferTask](rc, reqForm, new(entity.DbTransferTask))
|
||||||
|
|
||||||
|
rc.ReqParam = reqForm
|
||||||
|
biz.ErrIsNil(d.DbTransferTask.Save(rc.MetaCtx, task))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DbTransferTask) DeleteTask(rc *req.Ctx) {
|
||||||
|
taskId := rc.PathParam("taskId")
|
||||||
|
rc.ReqParam = taskId
|
||||||
|
ids := strings.Split(taskId, ",")
|
||||||
|
|
||||||
|
for _, v := range ids {
|
||||||
|
value, err := strconv.Atoi(v)
|
||||||
|
biz.ErrIsNilAppendErr(err, "string类型转换为int异常: %s")
|
||||||
|
biz.ErrIsNil(d.DbTransferTask.Delete(rc.MetaCtx, uint64(value)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DbTransferTask) Run(rc *req.Ctx) {
|
||||||
|
taskId := d.changeState(rc, entity.DbTransferTaskRunStateRunning)
|
||||||
|
go d.DbTransferTask.Run(taskId, func(msg string, err error) {
|
||||||
|
// 修改状态为停止
|
||||||
|
if err != nil {
|
||||||
|
logx.Error(msg, err)
|
||||||
|
} else {
|
||||||
|
logx.Info(fmt.Sprintf("执行迁移完成,%s", msg))
|
||||||
|
}
|
||||||
|
// 修改任务状态
|
||||||
|
task := new(entity.DbTransferTask)
|
||||||
|
task.Id = taskId
|
||||||
|
task.RunningState = entity.DbTransferTaskRunStateStop
|
||||||
|
biz.ErrIsNil(d.DbTransferTask.UpdateById(context.Background(), task))
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DbTransferTask) Stop(rc *req.Ctx) {
|
||||||
|
taskId := d.changeState(rc, entity.DbTransferTaskRunStateStop)
|
||||||
|
d.DbTransferTask.Stop(taskId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DbTransferTask) changeState(rc *req.Ctx, RunningState int) uint64 {
|
||||||
|
reqForm := &form.DbTransferTaskStatusForm{RunningState: RunningState}
|
||||||
|
task := req.BindJsonAndCopyTo[*entity.DbTransferTask](rc, reqForm, new(entity.DbTransferTask))
|
||||||
|
biz.ErrIsNil(d.DbTransferTask.UpdateById(rc.MetaCtx, task))
|
||||||
|
// 记录请求日志
|
||||||
|
rc.ReqParam = reqForm
|
||||||
|
return task.Id
|
||||||
|
}
|
||||||
24
server/internal/db/api/form/db_transfer.go
Normal file
24
server/internal/db/api/form/db_transfer.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package form
|
||||||
|
|
||||||
|
type DbTransferTaskForm struct {
|
||||||
|
Id uint64 `json:"id"`
|
||||||
|
CheckedKeys string `binding:"required" json:"checkedKeys"` // 选中需要迁移的表
|
||||||
|
DeleteTable int `binding:"required" json:"deleteTable"` // 创建表前是否删除表 1是 2否
|
||||||
|
NameCase int `binding:"required" json:"nameCase"` // 表名、字段大小写转换 1无 2大写 3小写
|
||||||
|
Strategy int `binding:"required" json:"strategy"` // 迁移策略 1全量 2增量
|
||||||
|
SrcDbId int `binding:"required" json:"srcDbId"` // 源库id
|
||||||
|
SrcDbName string `binding:"required" json:"srcDbName"` // 源库名
|
||||||
|
SrcDbType string `binding:"required" json:"srcDbType"` // 源库类型
|
||||||
|
SrcInstName string `binding:"required" json:"srcInstName"` // 源库实例名
|
||||||
|
SrcTagPath string `binding:"required" json:"srcTagPath"` // 源库tagPath
|
||||||
|
TargetDbId int `binding:"required" json:"targetDbId"` // 目标库id
|
||||||
|
TargetDbName string `binding:"required" json:"targetDbName"` // 目标库名
|
||||||
|
TargetDbType string `binding:"required" json:"targetDbType"` // 目标库类型
|
||||||
|
TargetInstName string `binding:"required" json:"targetInstName"` // 目标库实例名
|
||||||
|
TargetTagPath string `binding:"required" json:"targetTagPath"` // 目标库tagPath
|
||||||
|
}
|
||||||
|
|
||||||
|
type DbTransferTaskStatusForm struct {
|
||||||
|
Id uint64 `binding:"required" json:"taskId"`
|
||||||
|
RunningState int `json:"status"`
|
||||||
|
}
|
||||||
37
server/internal/db/api/vo/db_transfer.go
Normal file
37
server/internal/db/api/vo/db_transfer.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package vo
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type DbTransferTaskListVO struct {
|
||||||
|
Id *int64 `json:"id"`
|
||||||
|
|
||||||
|
UpdateTime *time.Time `json:"updateTime"`
|
||||||
|
Modifier string `json:"modifier"`
|
||||||
|
|
||||||
|
RunningState int `json:"runningState"`
|
||||||
|
|
||||||
|
CheckedKeys string `json:"checkedKeys"` // 选中需要迁移的表
|
||||||
|
DeleteTable int `json:"deleteTable"` // 创建表前是否删除表
|
||||||
|
NameCase int `json:"nameCase"` // 表名、字段大小写转换 1无 2大写 3小写
|
||||||
|
Strategy int `json:"strategy"` // 迁移策略 1全量 2增量
|
||||||
|
|
||||||
|
SrcDbId int64 `json:"srcDbId"` // 源库id
|
||||||
|
SrcDbName string `json:"srcDbName"` // 源库名
|
||||||
|
SrcTagPath string `json:"srcTagPath"` // 源库tagPath
|
||||||
|
SrcDbType string `json:"srcDbType"` // 源库类型
|
||||||
|
SrcInstName string `json:"srcInstName"` // 源库实例名
|
||||||
|
|
||||||
|
TargetDbId int `json:"targetDbId"` // 目标库id
|
||||||
|
TargetDbName string `json:"targetDbName"` // 目标库名
|
||||||
|
TargetDbType string `json:"targetDbType"` // 目标库类型
|
||||||
|
TargetInstName string `json:"targetInstName"` // 目标库实例名
|
||||||
|
TargetTagPath string `json:"targetTagPath"` // 目标库tagPath
|
||||||
|
}
|
||||||
|
|
||||||
|
type DbTransferLogListVO struct {
|
||||||
|
CreateTime *time.Time `json:"createTime"`
|
||||||
|
DataSqlFull string `json:"dataSqlFull"`
|
||||||
|
ResNum string `json:"resNum"`
|
||||||
|
ErrText string `json:"errText"`
|
||||||
|
Status *int `json:"status"`
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ func InitIoc() {
|
|||||||
ioc.Register(new(dbSqlExecAppImpl), ioc.WithComponentName("DbSqlExecApp"))
|
ioc.Register(new(dbSqlExecAppImpl), ioc.WithComponentName("DbSqlExecApp"))
|
||||||
ioc.Register(new(dbSqlAppImpl), ioc.WithComponentName("DbSqlApp"))
|
ioc.Register(new(dbSqlAppImpl), ioc.WithComponentName("DbSqlApp"))
|
||||||
ioc.Register(new(dataSyncAppImpl), ioc.WithComponentName("DbDataSyncTaskApp"))
|
ioc.Register(new(dataSyncAppImpl), ioc.WithComponentName("DbDataSyncTaskApp"))
|
||||||
|
ioc.Register(new(dbTransferAppImpl), ioc.WithComponentName("DbTransferTaskApp"))
|
||||||
|
|
||||||
ioc.Register(newDbScheduler(), ioc.WithComponentName("DbScheduler"))
|
ioc.Register(newDbScheduler(), ioc.WithComponentName("DbScheduler"))
|
||||||
ioc.Register(new(DbBackupApp), ioc.WithComponentName("DbBackupApp"))
|
ioc.Register(new(DbBackupApp), ioc.WithComponentName("DbBackupApp"))
|
||||||
@@ -31,6 +32,7 @@ func Init() {
|
|||||||
panic(fmt.Sprintf("初始化 DbBinlogApp 失败: %v", err))
|
panic(fmt.Sprintf("初始化 DbBinlogApp 失败: %v", err))
|
||||||
}
|
}
|
||||||
GetDataSyncTaskApp().InitCronJob()
|
GetDataSyncTaskApp().InitCronJob()
|
||||||
|
GetDbTransferTaskApp().InitJob()
|
||||||
InitDbFlowHandler()
|
InitDbFlowHandler()
|
||||||
})()
|
})()
|
||||||
}
|
}
|
||||||
@@ -54,3 +56,6 @@ func GetDbBinlogApp() *DbBinlogApp {
|
|||||||
func GetDataSyncTaskApp() DataSyncTask {
|
func GetDataSyncTaskApp() DataSyncTask {
|
||||||
return ioc.Get[DataSyncTask]("DbDataSyncTaskApp")
|
return ioc.Get[DataSyncTask]("DbDataSyncTaskApp")
|
||||||
}
|
}
|
||||||
|
func GetDbTransferTaskApp() DbTransferTask {
|
||||||
|
return ioc.Get[DbTransferTask]("DbTransferTaskApp")
|
||||||
|
}
|
||||||
|
|||||||
302
server/internal/db/application/db_transfer.go
Normal file
302
server/internal/db/application/db_transfer.go
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"mayfly-go/internal/db/dbm/dbi"
|
||||||
|
"mayfly-go/internal/db/domain/entity"
|
||||||
|
"mayfly-go/internal/db/domain/repository"
|
||||||
|
"mayfly-go/pkg/base"
|
||||||
|
"mayfly-go/pkg/gormx"
|
||||||
|
"mayfly-go/pkg/logx"
|
||||||
|
"mayfly-go/pkg/model"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DbTransferTask interface {
|
||||||
|
base.App[*entity.DbTransferTask]
|
||||||
|
|
||||||
|
// GetPageList 分页获取数据库实例
|
||||||
|
GetPageList(condition *entity.DbTransferTaskQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||||
|
|
||||||
|
Save(ctx context.Context, instanceEntity *entity.DbTransferTask) error
|
||||||
|
|
||||||
|
Delete(ctx context.Context, id uint64) error
|
||||||
|
|
||||||
|
InitJob()
|
||||||
|
|
||||||
|
GetTaskLogList(condition *entity.DbTransferLogQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||||
|
|
||||||
|
Run(taskId uint64, end func(msg string, err error))
|
||||||
|
|
||||||
|
Stop(taskId uint64)
|
||||||
|
}
|
||||||
|
|
||||||
|
type dbTransferAppImpl struct {
|
||||||
|
base.AppImpl[*entity.DbTransferTask, repository.DbTransferTask]
|
||||||
|
|
||||||
|
dbTransferLogRepo repository.DbTransferLog `inject:"DbTransferLogRepo"`
|
||||||
|
|
||||||
|
dbApp Db `inject:"DbApp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *dbTransferAppImpl) InjectDbTransferTaskRepo(repo repository.DbTransferTask) {
|
||||||
|
app.Repo = repo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *dbTransferAppImpl) GetPageList(condition *entity.DbTransferTaskQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||||
|
return app.GetRepo().GetTaskList(condition, pageParam, toEntity, orderBy...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *dbTransferAppImpl) Save(ctx context.Context, taskEntity *entity.DbTransferTask) error {
|
||||||
|
var err error
|
||||||
|
if taskEntity.Id == 0 {
|
||||||
|
err = app.Insert(ctx, taskEntity)
|
||||||
|
} else {
|
||||||
|
err = app.UpdateById(ctx, taskEntity)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *dbTransferAppImpl) Delete(ctx context.Context, id uint64) error {
|
||||||
|
if err := app.DeleteById(ctx, id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *dbTransferAppImpl) InitJob() {
|
||||||
|
// 修改执行状态为待执行
|
||||||
|
updateMap := map[string]interface{}{
|
||||||
|
"running_state": entity.DbTransferTaskRunStateStop,
|
||||||
|
}
|
||||||
|
taskParam := new(entity.DbTransferTask)
|
||||||
|
taskParam.RunningState = 1
|
||||||
|
_ = gormx.Updates(taskParam, taskParam, updateMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *dbTransferAppImpl) GetTaskLogList(condition *entity.DbTransferLogQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||||
|
return app.dbTransferLogRepo.GetTaskLogList(condition, pageParam, toEntity, orderBy...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *dbTransferAppImpl) Run(taskId uint64, end func(msg string, err error)) {
|
||||||
|
task, err := app.GetById(new(entity.DbTransferTask), taskId)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 获取源库连接、目标库连接,判断连接可用性,否则记录日志:xx连接不可用
|
||||||
|
// 获取源库表信息
|
||||||
|
srcConn, err := app.dbApp.GetDbConn(uint64(task.SrcDbId), task.SrcDbName)
|
||||||
|
if err != nil {
|
||||||
|
end("获取源库连接失败", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 获取目标库表信息
|
||||||
|
targetConn, err := app.dbApp.GetDbConn(uint64(task.TargetDbId), task.TargetDbName)
|
||||||
|
if err != nil {
|
||||||
|
end("获取目标库连接失败", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 查询出源库表信息
|
||||||
|
srcDialect := srcConn.GetDialect()
|
||||||
|
targetDialect := targetConn.GetDialect()
|
||||||
|
|
||||||
|
var tables []dbi.Table
|
||||||
|
|
||||||
|
if task.CheckedKeys == "all" {
|
||||||
|
tables, err = srcConn.GetMetaData().GetTables()
|
||||||
|
if err != nil {
|
||||||
|
end("获取源表信息失败", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tableNames := strings.Split(task.CheckedKeys, ",")
|
||||||
|
tables, err = srcConn.GetMetaData().GetTables(tableNames...)
|
||||||
|
if err != nil {
|
||||||
|
end("获取源表信息失败", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 迁移表
|
||||||
|
app.transferTables(task, srcConn, srcDialect, targetConn, targetDialect, tables, end)
|
||||||
|
|
||||||
|
end(fmt.Sprintf("执行迁移任务完成:[%d]", task.Id), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *dbTransferAppImpl) Stop(taskId uint64) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *dbTransferAppImpl) recLog(taskId uint64) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 迁移表
|
||||||
|
func (app *dbTransferAppImpl) transferTables(task *entity.DbTransferTask, srcConn *dbi.DbConn, srcDialect dbi.Dialect, targetConn *dbi.DbConn, targetDialect dbi.Dialect, tables []dbi.Table, end func(msg string, err error)) {
|
||||||
|
|
||||||
|
tableNames := make([]string, 0)
|
||||||
|
tableMap := make(map[string]dbi.Table) // 以表名分组,存放表信息
|
||||||
|
for _, table := range tables {
|
||||||
|
tableNames = append(tableNames, table.TableName)
|
||||||
|
tableMap[table.TableName] = table
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tableNames) == 0 {
|
||||||
|
end("没有需要迁移的表", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询源表列信息
|
||||||
|
columns, err := srcConn.GetMetaData().GetColumns(tableNames...)
|
||||||
|
if err != nil {
|
||||||
|
end("获取源表列信息失败", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 以表名分组,存放每个表的列信息
|
||||||
|
columnMap := make(map[string][]dbi.Column)
|
||||||
|
for _, column := range columns {
|
||||||
|
columnMap[column.TableName] = append(columnMap[column.TableName], column)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
for tbName, cols := range columnMap {
|
||||||
|
// 在目标库建表
|
||||||
|
// 把源列信息转化成公共列信息
|
||||||
|
commonColumns := srcDialect.TransColumns(cols)
|
||||||
|
// 通过公共列信息生成目标库的建表语句,并执行目标库建表
|
||||||
|
logx.Infof("开始创建目标表: 表名:%s", tbName)
|
||||||
|
_, err := targetDialect.CreateTable(commonColumns, tableMap[tbName], true)
|
||||||
|
if err != nil {
|
||||||
|
end(fmt.Sprintf("创建目标表失败: 表名:%s, error: %s", tbName, err.Error()), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logx.Infof("创建目标表成功: 表名:%s", tbName)
|
||||||
|
|
||||||
|
// 迁移数据
|
||||||
|
logx.Infof("开始迁移数据: 表名:%s", tbName)
|
||||||
|
total, err := app.transferData(ctx, tbName, srcConn, srcDialect, targetConn, targetDialect)
|
||||||
|
if err != nil {
|
||||||
|
end(fmt.Sprintf("迁移数据失败: 表名:%s, error: %s", tbName, err.Error()), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logx.Infof("迁移数据成功: 表名:%s, 数据:%d 条", tbName, total)
|
||||||
|
|
||||||
|
// 有些数据库迁移完数据之后,需要更新表自增序列为当前表最大值
|
||||||
|
targetDialect.UpdateSequence(tbName, commonColumns)
|
||||||
|
|
||||||
|
// 迁移索引信息
|
||||||
|
logx.Infof("开始迁移索引: 表名:%s", tbName)
|
||||||
|
err = app.transferIndex(ctx, tableMap[tbName], srcConn, srcDialect, targetConn, targetDialect)
|
||||||
|
if err != nil {
|
||||||
|
end(fmt.Sprintf("迁移索引失败: 表名:%s, error: %s", tbName, err.Error()), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logx.Infof("迁移索引成功: 表名:%s", tbName)
|
||||||
|
|
||||||
|
// 记录任务执行日志
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改任务状态
|
||||||
|
taskParam := &entity.DbTransferTask{}
|
||||||
|
taskParam.Id = task.Id
|
||||||
|
taskParam.RunningState = entity.DbTransferTaskRunStateStop
|
||||||
|
|
||||||
|
if err := app.UpdateById(ctx, task); err != nil {
|
||||||
|
end("修改任务状态失败", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *dbTransferAppImpl) transferData(ctx context.Context, tableName string, srcConn *dbi.DbConn, srcDialect dbi.Dialect, targetConn *dbi.DbConn, targetDialect dbi.Dialect) (int, error) {
|
||||||
|
result := make([]map[string]any, 0)
|
||||||
|
total := 0 // 总条数
|
||||||
|
batchSize := 1000 // 每次查询并迁移1000条数据
|
||||||
|
var queryColumns []*dbi.QueryColumn
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// 游标查询源表数据,并批量插入目标表
|
||||||
|
err = srcConn.WalkTableRows(ctx, tableName, func(row map[string]any, columns []*dbi.QueryColumn) error {
|
||||||
|
if len(queryColumns) == 0 {
|
||||||
|
|
||||||
|
for _, col := range columns {
|
||||||
|
queryColumns = append(queryColumns, &dbi.QueryColumn{
|
||||||
|
Name: targetConn.GetMetaData().QuoteIdentifier(col.Name),
|
||||||
|
Type: col.Type,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
total++
|
||||||
|
result = append(result, row)
|
||||||
|
if total%batchSize == 0 {
|
||||||
|
err = app.transfer2Target(targetConn, queryColumns, result, targetDialect, tableName)
|
||||||
|
if err != nil {
|
||||||
|
logx.Error("批量插入目标表数据失败", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
result = result[:0]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
// 处理剩余的数据
|
||||||
|
if len(result) > 0 {
|
||||||
|
err = app.transfer2Target(targetConn, queryColumns, result, targetDialect, tableName)
|
||||||
|
if err != nil {
|
||||||
|
logx.Error(fmt.Sprintf("批量插入目标表数据失败,表名:%s", tableName), err)
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *dbTransferAppImpl) transfer2Target(targetConn *dbi.DbConn, cols []*dbi.QueryColumn, result []map[string]any, targetDialect dbi.Dialect, tbName string) error {
|
||||||
|
tx, err := targetConn.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// 收集字段名
|
||||||
|
var columnNames []string
|
||||||
|
for _, col := range cols {
|
||||||
|
columnNames = append(columnNames, col.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从目标库数据中取出源库字段对应的值
|
||||||
|
values := make([][]any, 0)
|
||||||
|
for _, record := range result {
|
||||||
|
rawValue := make([]any, 0)
|
||||||
|
for _, cn := range columnNames {
|
||||||
|
rawValue = append(rawValue, record[targetConn.GetMetaData().RemoveQuote(cn)])
|
||||||
|
}
|
||||||
|
values = append(values, rawValue)
|
||||||
|
}
|
||||||
|
// 批量插入
|
||||||
|
_, err = targetDialect.BatchInsert(tx, tbName, columnNames, values, -1)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
logx.Error("批量插入目标表数据失败", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
_ = tx.Commit()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *dbTransferAppImpl) transferIndex(ctx context.Context, tableInfo dbi.Table, srcConn *dbi.DbConn, srcDialect dbi.Dialect, targetConn *dbi.DbConn, targetDialect dbi.Dialect) error {
|
||||||
|
|
||||||
|
// 查询源表索引信息
|
||||||
|
indexs, err := srcConn.GetMetaData().GetTableIndex(tableInfo.TableName)
|
||||||
|
if err != nil {
|
||||||
|
logx.Error("获取索引信息失败", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(indexs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通过表名、索引信息生成建索引语句,并执行到目标表
|
||||||
|
return targetDialect.CreateIndex(tableInfo, indexs)
|
||||||
|
}
|
||||||
@@ -200,7 +200,7 @@ func walkQueryRows(ctx context.Context, db *sql.DB, selectSql string, walkFn Wal
|
|||||||
rowData[cols[i].Name] = valueConvert(v, colTypes[i])
|
rowData[cols[i].Name] = valueConvert(v, colTypes[i])
|
||||||
}
|
}
|
||||||
if err = walkFn(rowData, cols); err != nil {
|
if err = walkFn(rowData, cols); err != nil {
|
||||||
logx.Error("游标遍历查询结果集出错,退出遍历: %s", err.Error())
|
logx.Errorf("游标遍历查询结果集出错,退出遍历: %s", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,33 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CommonTypeVarchar string = "varchar"
|
||||||
|
CommonTypeChar string = "char"
|
||||||
|
CommonTypeText string = "text"
|
||||||
|
CommonTypeBlob string = "blob"
|
||||||
|
CommonTypeLongblob string = "longblob"
|
||||||
|
CommonTypeLongtext string = "longtext"
|
||||||
|
CommonTypeBinary string = "binary"
|
||||||
|
CommonTypeMediumblob string = "mediumblob"
|
||||||
|
CommonTypeMediumtext string = "mediumtext"
|
||||||
|
CommonTypeVarbinary string = "varbinary"
|
||||||
|
|
||||||
|
CommonTypeInt string = "int"
|
||||||
|
CommonTypeSmallint string = "smallint"
|
||||||
|
CommonTypeTinyint string = "tinyint"
|
||||||
|
CommonTypeNumber string = "number"
|
||||||
|
CommonTypeBigint string = "bigint"
|
||||||
|
|
||||||
|
CommonTypeDatetime string = "datetime"
|
||||||
|
CommonTypeDate string = "date"
|
||||||
|
CommonTypeTime string = "time"
|
||||||
|
CommonTypeTimestamp string = "timestamp"
|
||||||
|
|
||||||
|
CommonTypeEnum string = "enum"
|
||||||
|
CommonTypeJSON string = "json"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// -1. 无操作
|
// -1. 无操作
|
||||||
DuplicateStrategyNone = -1
|
DuplicateStrategyNone = -1
|
||||||
@@ -32,4 +59,14 @@ type Dialect interface {
|
|||||||
|
|
||||||
// 拷贝表
|
// 拷贝表
|
||||||
CopyTable(copy *DbCopyTable) error
|
CopyTable(copy *DbCopyTable) error
|
||||||
|
|
||||||
|
CreateTable(commonColumns []Column, tableInfo Table, dropOldTable bool) (int, error)
|
||||||
|
|
||||||
|
CreateIndex(tableInfo Table, indexs []Index) error
|
||||||
|
|
||||||
|
// 把方言类型转换为通用类型
|
||||||
|
TransColumns(columns []Column) []Column
|
||||||
|
|
||||||
|
// 有些数据库迁移完数据之后,需要更新表自增序列为当前表最大值
|
||||||
|
UpdateSequence(tableName string, columns []Column)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ type MetaData interface {
|
|||||||
GetDbNames() ([]string, error)
|
GetDbNames() ([]string, error)
|
||||||
|
|
||||||
// 获取表信息
|
// 获取表信息
|
||||||
GetTables() ([]Table, error)
|
GetTables(tableNames ...string) ([]Table, error)
|
||||||
|
|
||||||
// 获取指定表名的所有列元信息
|
// 获取指定表名的所有列元信息
|
||||||
GetColumns(tableNames ...string) ([]Column, error)
|
GetColumns(tableNames ...string) ([]Column, error)
|
||||||
|
|||||||
@@ -19,19 +19,44 @@ SELECT a.object_name as TABLE_NAME,
|
|||||||
WHERE OWNER = 'wxb'
|
WHERE OWNER = 'wxb'
|
||||||
AND TABLE_NAME = a.object_name)) as INDEX_LENGTH,
|
AND TABLE_NAME = a.object_name)) as INDEX_LENGTH,
|
||||||
c.num_rows as TABLE_ROWS
|
c.num_rows as TABLE_ROWS
|
||||||
|
|
||||||
FROM all_objects a
|
FROM all_objects a
|
||||||
LEFT JOIN ALL_TAB_COMMENTS b ON b.TABLE_TYPE = 'TABLE'
|
LEFT JOIN ALL_TAB_COMMENTS b ON b.TABLE_TYPE = 'TABLE'
|
||||||
AND a.object_name = b.TABLE_NAME
|
AND a.object_name = b.TABLE_NAME
|
||||||
AND b.owner = a.owner
|
AND b.owner = a.owner
|
||||||
LEFT JOIN (SELECT a.owner, a.table_name, a.num_rows FROM all_tables a) c
|
LEFT JOIN (SELECT a.owner, a.table_name, a.num_rows FROM all_tables a) c
|
||||||
ON c.owner = a.owner AND c.table_name = a.object_name
|
ON c.owner = a.owner AND c.table_name = a.object_name
|
||||||
|
|
||||||
WHERE a.owner = (SELECT SF_GET_SCHEMA_NAME_BY_ID(CURRENT_SCHID))
|
WHERE a.owner = (SELECT SF_GET_SCHEMA_NAME_BY_ID(CURRENT_SCHID))
|
||||||
AND a.object_type = 'TABLE'
|
AND a.object_type = 'TABLE'
|
||||||
AND a.status = 'VALID'
|
AND a.status = 'VALID'
|
||||||
ORDER BY a.object_name
|
ORDER BY a.object_name
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
|
--DM_TABLE_INFO_BY_NAMES 表详细信息
|
||||||
|
SELECT a.object_name as TABLE_NAME,
|
||||||
|
b.comments as TABLE_COMMENT,
|
||||||
|
a.created as CREATE_TIME,
|
||||||
|
TABLE_USED_SPACE(
|
||||||
|
(SELECT SF_GET_SCHEMA_NAME_BY_ID(CURRENT_SCHID)),
|
||||||
|
a.object_name
|
||||||
|
) * page() as DATA_LENGTH,
|
||||||
|
(SELECT sum(INDEX_USED_PAGES(id))* page()
|
||||||
|
FROM SYSOBJECTS
|
||||||
|
WHERE NAME IN (SELECT INDEX_NAME
|
||||||
|
FROM ALL_INDEXES
|
||||||
|
WHERE OWNER = 'wxb'
|
||||||
|
AND TABLE_NAME = a.object_name)) as INDEX_LENGTH,
|
||||||
|
c.num_rows as TABLE_ROWS
|
||||||
|
FROM all_objects a
|
||||||
|
LEFT JOIN ALL_TAB_COMMENTS b ON b.TABLE_TYPE = 'TABLE'
|
||||||
|
AND a.object_name = b.TABLE_NAME
|
||||||
|
AND b.owner = a.owner
|
||||||
|
LEFT JOIN (SELECT a.owner, a.table_name, a.num_rows FROM all_tables a) c
|
||||||
|
ON c.owner = a.owner AND c.table_name = a.object_name
|
||||||
|
WHERE a.owner = (SELECT SF_GET_SCHEMA_NAME_BY_ID(CURRENT_SCHID))
|
||||||
|
AND a.object_type = 'TABLE'
|
||||||
|
AND a.status = 'VALID'
|
||||||
|
AND a.object_name in (%s)
|
||||||
|
ORDER BY a.object_name
|
||||||
|
---------------------------------------
|
||||||
--DM_INDEX_INFO 表索引信息
|
--DM_INDEX_INFO 表索引信息
|
||||||
select
|
select
|
||||||
a.index_name as INDEX_NAME,
|
a.index_name as INDEX_NAME,
|
||||||
|
|||||||
@@ -34,6 +34,21 @@ FROM sys.tables t
|
|||||||
where ss.name = ?
|
where ss.name = ?
|
||||||
ORDER BY t.name DESC;
|
ORDER BY t.name DESC;
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
|
--MSSQL_TABLE_INFO_BY_NAMES 表详细信息
|
||||||
|
SELECT t.name AS tableName,
|
||||||
|
ss.name AS tableSchema,
|
||||||
|
c.value AS tableComment,
|
||||||
|
p.rows AS tableRows,
|
||||||
|
0 AS dataLength,
|
||||||
|
0 AS indexLength,
|
||||||
|
t.create_date AS createTime
|
||||||
|
FROM sys.tables t
|
||||||
|
left OUTER JOIN sys.schemas ss on t.schema_id = ss.schema_id
|
||||||
|
left OUTER JOIN sys.partitions p ON t.object_id = p.object_id AND p.index_id = 1
|
||||||
|
left OUTER JOIN sys.extended_properties c ON t.object_id = c.major_id AND c.minor_id = 0 AND c.class = 1
|
||||||
|
where ss.name = ? and t.name in (%s)
|
||||||
|
ORDER BY t.name DESC;
|
||||||
|
---------------------------------------
|
||||||
--MSSQL_INDEX_INFO 索引信息
|
--MSSQL_INDEX_INFO 索引信息
|
||||||
SELECT ind.name AS indexName,
|
SELECT ind.name AS indexName,
|
||||||
col.name AS columnName,
|
col.name AS columnName,
|
||||||
|
|||||||
@@ -25,6 +25,25 @@ WHERE
|
|||||||
)
|
)
|
||||||
ORDER BY table_name
|
ORDER BY table_name
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
|
--MYSQL_TABLE_INFO_BY_NAMES 表详细信息
|
||||||
|
SELECT
|
||||||
|
table_name tableName,
|
||||||
|
table_comment tableComment,
|
||||||
|
table_rows tableRows,
|
||||||
|
data_length dataLength,
|
||||||
|
index_length indexLength,
|
||||||
|
create_time createTime
|
||||||
|
FROM
|
||||||
|
information_schema.tables
|
||||||
|
WHERE
|
||||||
|
table_type = 'BASE TABLE'
|
||||||
|
AND table_name IN (%s)
|
||||||
|
AND table_schema = (
|
||||||
|
SELECT
|
||||||
|
database ()
|
||||||
|
)
|
||||||
|
ORDER BY table_name
|
||||||
|
---------------------------------------
|
||||||
--MYSQL_INDEX_INFO 索引信息
|
--MYSQL_INDEX_INFO 索引信息
|
||||||
SELECT
|
SELECT
|
||||||
index_name indexName,
|
index_name indexName,
|
||||||
|
|||||||
@@ -31,6 +31,26 @@ where
|
|||||||
and c.reltype > 0
|
and c.reltype > 0
|
||||||
order by c.relname
|
order by c.relname
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
|
--PGSQL_TABLE_INFO_BY_NAMES 表详细信息
|
||||||
|
select
|
||||||
|
c.relname as "tableName",
|
||||||
|
obj_description (c.oid) as "tableComment",
|
||||||
|
pg_table_size ('"' || n.nspname || '"."' || c.relname || '"') as "dataLength",
|
||||||
|
pg_indexes_size ('"' || n.nspname || '"."' || c.relname || '"') as "indexLength",
|
||||||
|
psut.n_live_tup as "tableRows"
|
||||||
|
from
|
||||||
|
pg_class c
|
||||||
|
join pg_namespace n on
|
||||||
|
c.relnamespace = n.oid
|
||||||
|
join pg_stat_user_tables psut on
|
||||||
|
psut.relid = c.oid
|
||||||
|
where
|
||||||
|
has_table_privilege(CAST(c.oid AS regclass), 'SELECT')
|
||||||
|
and n.nspname = current_schema()
|
||||||
|
and c.reltype > 0
|
||||||
|
and c.relname in (%s)
|
||||||
|
order by c.relname
|
||||||
|
---------------------------------------
|
||||||
--PGSQL_INDEX_INFO 表索引信息
|
--PGSQL_INDEX_INFO 表索引信息
|
||||||
SELECT
|
SELECT
|
||||||
indexname AS "indexName",
|
indexname AS "indexName",
|
||||||
|
|||||||
@@ -10,6 +10,19 @@ WHERE type = 'table'
|
|||||||
and name not like 'sqlite_%'
|
and name not like 'sqlite_%'
|
||||||
ORDER BY tbl_name
|
ORDER BY tbl_name
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
|
--SQLITE_TABLE_INFO_BY_NAMES 表详细信息
|
||||||
|
select tbl_name as tableName,
|
||||||
|
'' as tableComment,
|
||||||
|
'' as createTime,
|
||||||
|
0 as dataLength,
|
||||||
|
0 as indexLength,
|
||||||
|
0 as tableRows
|
||||||
|
FROM sqlite_master
|
||||||
|
WHERE type = 'table'
|
||||||
|
and name not like 'sqlite_%'
|
||||||
|
and tbl_name in (%s)
|
||||||
|
ORDER BY tbl_name
|
||||||
|
---------------------------------------
|
||||||
--SQLITE_INDEX_INFO 表索引信息
|
--SQLITE_INDEX_INFO 表索引信息
|
||||||
select name as indexName,
|
select name as indexName,
|
||||||
`sql` as indexSql,
|
`sql` as indexSql,
|
||||||
|
|||||||
@@ -43,13 +43,17 @@ func (dd *DMDialect) batchInsertSimple(tx *sql.Tx, tableName string, columns []s
|
|||||||
// 去除最后一个逗号,占位符由括号包裹
|
// 去除最后一个逗号,占位符由括号包裹
|
||||||
placeholder := fmt.Sprintf("(%s)", strings.TrimSuffix(repeated, ","))
|
placeholder := fmt.Sprintf("(%s)", strings.TrimSuffix(repeated, ","))
|
||||||
|
|
||||||
sqlTemp := fmt.Sprintf("insert into %s (%s) values %s", dd.dc.GetMetaData().QuoteIdentifier(tableName), strings.Join(columns, ","), placeholder)
|
identityInsert := fmt.Sprintf("set identity_insert \"%s\" on;", tableName)
|
||||||
|
|
||||||
|
sqlTemp := fmt.Sprintf("%s insert into %s (%s) values %s", identityInsert, dd.dc.GetMetaData().QuoteIdentifier(tableName), strings.Join(columns, ","), placeholder)
|
||||||
effRows := 0
|
effRows := 0
|
||||||
|
// 设置允许填充自增列之后,显示指定列名可以插入自增列
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
// 达梦数据库只能一条条的执行insert
|
// 达梦数据库只能一条条的执行insert
|
||||||
res, err := dd.dc.TxExec(tx, sqlTemp, value...)
|
res, err := dd.dc.TxExec(tx, sqlTemp, value...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logx.Errorf("执行sql失败:%s, sql: [ %s ]", err.Error(), sqlTemp)
|
logx.Errorf("执行sql失败:%s, sql: [ %s ]", err.Error(), sqlTemp)
|
||||||
|
return 0, err
|
||||||
}
|
}
|
||||||
effRows += int(res)
|
effRows += int(res)
|
||||||
}
|
}
|
||||||
@@ -147,8 +151,8 @@ func (dd *DMDialect) CopyTable(copy *dbi.DbCopyTable) error {
|
|||||||
// 复制数据
|
// 复制数据
|
||||||
if copy.CopyData {
|
if copy.CopyData {
|
||||||
go func() {
|
go func() {
|
||||||
// 设置允许填充自增列之后,显示指定列名可以插入自增列
|
// 设置允许填充自增列之后,显示指定列名可以插入自增列\
|
||||||
_, _ = dd.dc.Exec(fmt.Sprintf("set identity_insert \"%s\" on", newTableName))
|
identityInsert := fmt.Sprintf("set identity_insert \"%s\" on", newTableName)
|
||||||
// 获取列名
|
// 获取列名
|
||||||
columns, _ := metadata.GetColumns(tableName)
|
columns, _ := metadata.GetColumns(tableName)
|
||||||
columnArr := make([]string, 0)
|
columnArr := make([]string, 0)
|
||||||
@@ -157,12 +161,168 @@ func (dd *DMDialect) CopyTable(copy *dbi.DbCopyTable) error {
|
|||||||
}
|
}
|
||||||
columnStr := strings.Join(columnArr, ",")
|
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("%s insert into \"%s\" (%s) select %s from \"%s\"", identityInsert, newTableName, columnStr, columnStr, tableName))
|
||||||
|
|
||||||
// 执行完成后关闭允许填充自增列
|
|
||||||
_, _ = dd.dc.Exec(fmt.Sprintf("set identity_insert \"%s\" off", newTableName))
|
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (dd *DMDialect) TransColumns(columns []dbi.Column) []dbi.Column {
|
||||||
|
var commonColumns []dbi.Column
|
||||||
|
for _, column := range columns {
|
||||||
|
// 取出当前数据库类型
|
||||||
|
arr := strings.Split(column.ColumnType, "(")
|
||||||
|
ctype := arr[0]
|
||||||
|
|
||||||
|
// 翻译为通用数据库类型
|
||||||
|
t1 := commonColumnMap[ctype]
|
||||||
|
if t1 == "" {
|
||||||
|
ctype = "VARCHAR(2000)"
|
||||||
|
} else {
|
||||||
|
// 回写到列信息
|
||||||
|
if len(arr) > 1 {
|
||||||
|
ctype = t1 + "(" + arr[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
column.ColumnType = ctype
|
||||||
|
commonColumns = append(commonColumns, column)
|
||||||
|
}
|
||||||
|
return commonColumns
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dd *DMDialect) CreateTable(commonColumns []dbi.Column, tableInfo dbi.Table, dropOldTable bool) (int, error) {
|
||||||
|
meta := dd.dc.GetMetaData()
|
||||||
|
replacer := strings.NewReplacer(";", "", "'", "")
|
||||||
|
tbName := meta.QuoteIdentifier(tableInfo.TableName)
|
||||||
|
|
||||||
|
if dropOldTable {
|
||||||
|
_, _ = dd.dc.Exec(fmt.Sprintf("drop table if exists %s", tbName))
|
||||||
|
}
|
||||||
|
// 组装建表语句
|
||||||
|
createSql := fmt.Sprintf("create table %s (", tbName)
|
||||||
|
fields := make([]string, 0)
|
||||||
|
pks := make([]string, 0)
|
||||||
|
columnComments := make([]string, 0)
|
||||||
|
// 把通用类型转换为达梦类型
|
||||||
|
for _, column := range commonColumns {
|
||||||
|
// 取出当前数据库类型
|
||||||
|
arr := strings.Split(column.ColumnType, "(")
|
||||||
|
ctype := arr[0]
|
||||||
|
// 翻译为通用数据库类型
|
||||||
|
t1 := dmColumnMap[ctype]
|
||||||
|
if t1 == "" {
|
||||||
|
ctype = "VARCHAR(2000)"
|
||||||
|
} else {
|
||||||
|
// 回写到列信息
|
||||||
|
if len(arr) > 1 {
|
||||||
|
ctype = t1 + "(" + arr[1]
|
||||||
|
} else {
|
||||||
|
ctype = t1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
column.ColumnType = ctype
|
||||||
|
|
||||||
|
if column.IsPrimaryKey {
|
||||||
|
pks = append(pks, meta.QuoteIdentifier(column.ColumnName))
|
||||||
|
}
|
||||||
|
fields = append(fields, dd.genColumnBasicSql(column))
|
||||||
|
// 防止注释内含有特殊字符串导致sql出错
|
||||||
|
comment := replacer.Replace(column.ColumnComment)
|
||||||
|
columnComments = append(columnComments, fmt.Sprintf("comment on column %s.%s is '%s'", tbName, meta.QuoteIdentifier(column.ColumnName), comment))
|
||||||
|
}
|
||||||
|
createSql += strings.Join(fields, ",")
|
||||||
|
if len(pks) > 0 {
|
||||||
|
createSql += fmt.Sprintf(", PRIMARY KEY (%s)", strings.Join(pks, ","))
|
||||||
|
}
|
||||||
|
createSql += ")"
|
||||||
|
|
||||||
|
tableCommentSql := ""
|
||||||
|
if tableInfo.TableComment != "" {
|
||||||
|
// 防止注释内含有特殊字符串导致sql出错
|
||||||
|
comment := replacer.Replace(tableInfo.TableComment)
|
||||||
|
tableCommentSql = fmt.Sprintf(" comment on table %s is '%s'", tbName, comment)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 达梦需要分开执行sql
|
||||||
|
var err error
|
||||||
|
if createSql != "" {
|
||||||
|
_, err = dd.dc.Exec(createSql)
|
||||||
|
}
|
||||||
|
if tableCommentSql != "" {
|
||||||
|
_, err = dd.dc.Exec(tableCommentSql)
|
||||||
|
}
|
||||||
|
if len(columnComments) > 0 {
|
||||||
|
for _, commentSql := range columnComments {
|
||||||
|
_, err = dd.dc.Exec(commentSql)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dd *DMDialect) genColumnBasicSql(column dbi.Column) string {
|
||||||
|
meta := dd.dc.GetMetaData()
|
||||||
|
colName := meta.QuoteIdentifier(column.ColumnName)
|
||||||
|
|
||||||
|
incr := ""
|
||||||
|
if column.IsIdentity {
|
||||||
|
incr = " IDENTITY"
|
||||||
|
}
|
||||||
|
|
||||||
|
nullAble := ""
|
||||||
|
if column.Nullable == "NO" {
|
||||||
|
nullAble = " NOT NULL"
|
||||||
|
}
|
||||||
|
|
||||||
|
defVal := "" // 默认值需要判断引号,如函数是不需要引号的 // 为了防止跨源函数不支持 当默认值是函数时,不需要设置默认值
|
||||||
|
if column.ColumnDefault != "" && !strings.Contains(column.ColumnDefault, "(") {
|
||||||
|
// 哪些字段类型默认值需要加引号
|
||||||
|
mark := false
|
||||||
|
if collx.ArrayAnyMatches([]string{"char", "text", "date", "time", "lob"}, strings.ToLower(column.ColumnType)) {
|
||||||
|
// 当数据类型是日期时间,默认值是日期时间函数时,默认值不需要引号
|
||||||
|
if collx.ArrayAnyMatches([]string{"date", "time"}, strings.ToLower(column.ColumnType)) &&
|
||||||
|
collx.ArrayAnyMatches([]string{"DATE", "TIME"}, strings.ToUpper(column.ColumnDefault)) {
|
||||||
|
mark = false
|
||||||
|
} else {
|
||||||
|
mark = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mark {
|
||||||
|
defVal = fmt.Sprintf(" DEFAULT '%s'", column.ColumnDefault)
|
||||||
|
} else {
|
||||||
|
defVal = fmt.Sprintf(" DEFAULT %s", column.ColumnDefault)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
columnSql := fmt.Sprintf(" %s %s %s %s %s", colName, column.ColumnType, incr, nullAble, defVal)
|
||||||
|
return columnSql
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dd *DMDialect) CreateIndex(tableInfo dbi.Table, indexs []dbi.Index) error {
|
||||||
|
meta := dd.dc.GetMetaData()
|
||||||
|
sqls := make([]string, 0)
|
||||||
|
for _, index := range indexs {
|
||||||
|
// 通过字段、表名拼接索引名
|
||||||
|
columnName := strings.ReplaceAll(index.ColumnName, "-", "")
|
||||||
|
columnName = strings.ReplaceAll(columnName, "_", "")
|
||||||
|
colName := strings.ReplaceAll(columnName, ",", "_")
|
||||||
|
|
||||||
|
keyType := "normal"
|
||||||
|
unique := ""
|
||||||
|
if index.IsUnique {
|
||||||
|
keyType = "unique"
|
||||||
|
unique = "unique"
|
||||||
|
}
|
||||||
|
indexName := fmt.Sprintf("%s_key_%s_%s", keyType, tableInfo.TableName, colName)
|
||||||
|
|
||||||
|
sqls = append(sqls, fmt.Sprintf("create %s index %s on %s(%s)", unique, indexName, meta.QuoteIdentifier(tableInfo.TableName), index.ColumnName))
|
||||||
|
}
|
||||||
|
_, err := dd.dc.Exec(strings.Join(sqls, ";"))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dd *DMDialect) UpdateSequence(tableName string, columns []dbi.Column) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,11 +12,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DM_META_FILE = "metasql/dm_meta.sql"
|
DM_META_FILE = "metasql/dm_meta.sql"
|
||||||
DM_DB_SCHEMAS = "DM_DB_SCHEMAS"
|
DM_DB_SCHEMAS = "DM_DB_SCHEMAS"
|
||||||
DM_TABLE_INFO_KEY = "DM_TABLE_INFO"
|
DM_TABLE_INFO_KEY = "DM_TABLE_INFO"
|
||||||
DM_INDEX_INFO_KEY = "DM_INDEX_INFO"
|
DM_INDEX_INFO_KEY = "DM_INDEX_INFO"
|
||||||
DM_COLUMN_MA_KEY = "DM_COLUMN_MA"
|
DM_COLUMN_MA_KEY = "DM_COLUMN_MA"
|
||||||
|
DM_TABLE_INFO_BY_NAMES_KEY = "DM_TABLE_INFO_BY_NAMES"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DMMetaData struct {
|
type DMMetaData struct {
|
||||||
@@ -50,14 +51,19 @@ func (dd *DMMetaData) GetDbNames() ([]string, error) {
|
|||||||
return databases, nil
|
return databases, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取表基础元信息, 如表名等
|
func (dd *DMMetaData) GetTables(tableNames ...string) ([]dbi.Table, error) {
|
||||||
func (dd *DMMetaData) GetTables() ([]dbi.Table, error) {
|
names := strings.Join(collx.ArrayMap[string, string](tableNames, func(val string) string {
|
||||||
|
return fmt.Sprintf("'%s'", dbi.RemoveQuote(dd, val))
|
||||||
|
}), ",")
|
||||||
|
|
||||||
// 首先执行更新统计信息sql 这个统计信息在数据量比较大的时候就比较耗时,所以最好定时执行
|
var res []map[string]any
|
||||||
// _, _, err := pd.dc.Query("dbms_stats.GATHER_SCHEMA_stats(SELECT SF_GET_SCHEMA_NAME_BY_ID(CURRENT_SCHID))")
|
var err error
|
||||||
|
|
||||||
// 查询表信息
|
if tableNames != nil && len(tableNames) > 0 {
|
||||||
_, res, err := dd.dc.Query(dbi.GetLocalSql(DM_META_FILE, DM_TABLE_INFO_KEY))
|
_, res, err = dd.dc.Query(fmt.Sprintf(dbi.GetLocalSql(DM_META_FILE, DM_TABLE_INFO_BY_NAMES_KEY), names))
|
||||||
|
} else {
|
||||||
|
_, res, err = dd.dc.Query(dbi.GetLocalSql(DM_META_FILE, DM_TABLE_INFO_KEY))
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -254,8 +260,64 @@ var (
|
|||||||
dateRegexp = regexp.MustCompile(`(?i)date`)
|
dateRegexp = regexp.MustCompile(`(?i)date`)
|
||||||
// 时间类型
|
// 时间类型
|
||||||
timeRegexp = regexp.MustCompile(`(?i)time`)
|
timeRegexp = regexp.MustCompile(`(?i)time`)
|
||||||
|
// 定义正则表达式,匹配括号内的数字
|
||||||
|
bracketsRegexp = regexp.MustCompile(`\((\d+)\)`)
|
||||||
|
|
||||||
converter = new(DataConverter)
|
converter = new(DataConverter)
|
||||||
|
|
||||||
|
// 达梦数据类型 对应 公共数据类型
|
||||||
|
commonColumnMap = map[string]string{
|
||||||
|
|
||||||
|
"CHAR": dbi.CommonTypeChar, // 字符数据类型
|
||||||
|
"VARCHAR": dbi.CommonTypeVarchar,
|
||||||
|
"TEXT": dbi.CommonTypeText,
|
||||||
|
"LONG": dbi.CommonTypeText,
|
||||||
|
"LONGVARCHAR": dbi.CommonTypeLongtext,
|
||||||
|
"IMAGE": dbi.CommonTypeLongtext,
|
||||||
|
"LONGVARBINARY": dbi.CommonTypeLongtext,
|
||||||
|
"BLOB": dbi.CommonTypeBlob,
|
||||||
|
"CLOB": dbi.CommonTypeText,
|
||||||
|
"NUMERIC": dbi.CommonTypeNumber, // 精确数值数据类型
|
||||||
|
"DECIMAL": dbi.CommonTypeNumber,
|
||||||
|
"NUMBER": dbi.CommonTypeNumber,
|
||||||
|
"INTEGER": dbi.CommonTypeInt,
|
||||||
|
"INT": dbi.CommonTypeInt,
|
||||||
|
"BIGINT": dbi.CommonTypeBigint,
|
||||||
|
"TINYINT": dbi.CommonTypeTinyint,
|
||||||
|
"BYTE": dbi.CommonTypeTinyint,
|
||||||
|
"SMALLINT": dbi.CommonTypeSmallint,
|
||||||
|
"BIT": dbi.CommonTypeTinyint,
|
||||||
|
"DOUBLE": dbi.CommonTypeNumber, // 近似数值类型
|
||||||
|
"FLOAT": dbi.CommonTypeNumber,
|
||||||
|
"DATE": dbi.CommonTypeDate, // 一般日期时间数据类型
|
||||||
|
"TIME": dbi.CommonTypeTime,
|
||||||
|
"TIMESTAMP": dbi.CommonTypeTimestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 公共数据类型 对应 达梦数据类型
|
||||||
|
dmColumnMap = map[string]string{
|
||||||
|
dbi.CommonTypeVarchar: "VARCHAR",
|
||||||
|
dbi.CommonTypeChar: "CHAR",
|
||||||
|
dbi.CommonTypeText: "TEXT",
|
||||||
|
dbi.CommonTypeBlob: "BLOB",
|
||||||
|
dbi.CommonTypeLongblob: "TEXT",
|
||||||
|
dbi.CommonTypeLongtext: "TEXT",
|
||||||
|
dbi.CommonTypeBinary: "TEXT",
|
||||||
|
dbi.CommonTypeMediumblob: "TEXT",
|
||||||
|
dbi.CommonTypeMediumtext: "TEXT",
|
||||||
|
dbi.CommonTypeVarbinary: "TEXT",
|
||||||
|
dbi.CommonTypeInt: "INT",
|
||||||
|
dbi.CommonTypeSmallint: "SMALLINT",
|
||||||
|
dbi.CommonTypeTinyint: "TINYINT",
|
||||||
|
dbi.CommonTypeNumber: "NUMBER",
|
||||||
|
dbi.CommonTypeBigint: "BIGINT",
|
||||||
|
dbi.CommonTypeDatetime: "TIMESTAMP",
|
||||||
|
dbi.CommonTypeDate: "DATE",
|
||||||
|
dbi.CommonTypeTime: "DATE",
|
||||||
|
dbi.CommonTypeTimestamp: "TIMESTAMP",
|
||||||
|
dbi.CommonTypeEnum: "TEXT",
|
||||||
|
dbi.CommonTypeJSON: "TEXT",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
type DataConverter struct {
|
type DataConverter struct {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"mayfly-go/internal/db/dbm/dbi"
|
"mayfly-go/internal/db/dbm/dbi"
|
||||||
"mayfly-go/pkg/logx"
|
"mayfly-go/pkg/logx"
|
||||||
|
"mayfly-go/pkg/utils/anyx"
|
||||||
"mayfly-go/pkg/utils/collx"
|
"mayfly-go/pkg/utils/collx"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -14,6 +15,17 @@ type MssqlDialect struct {
|
|||||||
dc *dbi.DbConn
|
dc *dbi.DbConn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从连接信息中获取数据库和schema信息
|
||||||
|
func (md *MssqlDialect) currentSchema() string {
|
||||||
|
dbName := md.dc.Info.Database
|
||||||
|
schema := ""
|
||||||
|
arr := strings.Split(dbName, "/")
|
||||||
|
if len(arr) == 2 {
|
||||||
|
schema = arr[1]
|
||||||
|
}
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
// GetDbProgram 获取数据库程序模块,用于数据库备份与恢复
|
// GetDbProgram 获取数据库程序模块,用于数据库备份与恢复
|
||||||
func (md *MssqlDialect) GetDbProgram() (dbi.DbProgram, error) {
|
func (md *MssqlDialect) GetDbProgram() (dbi.DbProgram, error) {
|
||||||
return nil, fmt.Errorf("该数据库类型不支持数据库备份与恢复: %v", md.dc.Info.Type)
|
return nil, fmt.Errorf("该数据库类型不支持数据库备份与恢复: %v", md.dc.Info.Type)
|
||||||
@@ -29,6 +41,43 @@ func (md *MssqlDialect) BatchInsert(tx *sql.Tx, tableName string, columns []stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (md *MssqlDialect) batchInsertSimple(tx *sql.Tx, tableName string, columns []string, values [][]any, duplicateStrategy int) (int64, error) {
|
func (md *MssqlDialect) batchInsertSimple(tx *sql.Tx, tableName string, columns []string, values [][]any, duplicateStrategy int) (int64, error) {
|
||||||
|
// 把二维数组转为一维数组
|
||||||
|
var args []any
|
||||||
|
var singleSize int // 一条数据的参数个数
|
||||||
|
for i, v := range values {
|
||||||
|
if i == 0 {
|
||||||
|
singleSize = len(v)
|
||||||
|
}
|
||||||
|
args = append(args, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断如果参数超过2000,则分批次执行,mssql允许最大参数为2100,保险起见,这里限制到2000
|
||||||
|
if len(args) > 2000 {
|
||||||
|
|
||||||
|
rows := 2000 / singleSize // 每批次最大数据条数
|
||||||
|
mp := make(map[any][][]any)
|
||||||
|
|
||||||
|
// 把values拆成多份,每份不能超过rows条
|
||||||
|
length := len(values)
|
||||||
|
for i := 0; i < length; i += rows {
|
||||||
|
if i+rows <= length {
|
||||||
|
mp[i] = values[i : i+rows]
|
||||||
|
} else {
|
||||||
|
mp[i] = values[i:length]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var count int64
|
||||||
|
for _, v := range mp {
|
||||||
|
res, err := md.batchInsertSimple(tx, tableName, columns, v, duplicateStrategy)
|
||||||
|
if err != nil {
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
count += res
|
||||||
|
}
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
msMetadata := md.dc.GetMetaData()
|
msMetadata := md.dc.GetMetaData()
|
||||||
schema := md.dc.Info.CurrentSchema()
|
schema := md.dc.Info.CurrentSchema()
|
||||||
ignoreDupSql := ""
|
ignoreDupSql := ""
|
||||||
@@ -69,11 +118,6 @@ func (md *MssqlDialect) batchInsertSimple(tx *sql.Tx, tableName string, columns
|
|||||||
|
|
||||||
sqlStr := fmt.Sprintf("insert into %s (%s) values %s", baseTable, strings.Join(columns, ","), placeholder)
|
sqlStr := fmt.Sprintf("insert into %s (%s) values %s", baseTable, strings.Join(columns, ","), placeholder)
|
||||||
// 执行批量insert sql
|
// 执行批量insert sql
|
||||||
// 把二维数组转为一维数组
|
|
||||||
var args []any
|
|
||||||
for _, v := range values {
|
|
||||||
args = append(args, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置允许填充自增列之后,显示指定列名可以插入自增列
|
// 设置允许填充自增列之后,显示指定列名可以插入自增列
|
||||||
identityInsertOn := fmt.Sprintf("SET IDENTITY_INSERT [%s].[%s] ON", schema, tableName)
|
identityInsertOn := fmt.Sprintf("SET IDENTITY_INSERT [%s].[%s] ON", schema, tableName)
|
||||||
@@ -206,3 +250,204 @@ func (md *MssqlDialect) CopyTable(copy *dbi.DbCopyTable) error {
|
|||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (md *MssqlDialect) TransColumns(columns []dbi.Column) []dbi.Column {
|
||||||
|
var commonColumns []dbi.Column
|
||||||
|
for _, column := range columns {
|
||||||
|
// 取出当前数据库类型
|
||||||
|
arr := strings.Split(column.ColumnType, "(")
|
||||||
|
ctype := arr[0]
|
||||||
|
// 翻译为通用数据库类型
|
||||||
|
t1 := commonColumnTypeMap[ctype]
|
||||||
|
if t1 == "" {
|
||||||
|
ctype = "nvarchar(2000)"
|
||||||
|
} else {
|
||||||
|
// 回写到列信息
|
||||||
|
if len(arr) > 1 {
|
||||||
|
ctype = t1 + "(" + arr[1]
|
||||||
|
} else {
|
||||||
|
ctype = t1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
column.ColumnType = ctype
|
||||||
|
commonColumns = append(commonColumns, column)
|
||||||
|
}
|
||||||
|
return commonColumns
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MssqlDialect) CreateTable(commonColumns []dbi.Column, tableInfo dbi.Table, dropOldTable bool) (int, error) {
|
||||||
|
meta := md.dc.GetMetaData()
|
||||||
|
replacer := strings.NewReplacer(";", "", "'", "")
|
||||||
|
if dropOldTable {
|
||||||
|
_, _ = md.dc.Exec(fmt.Sprintf("DROP TABLE IF EXISTS %s", meta.QuoteIdentifier(tableInfo.TableName)))
|
||||||
|
}
|
||||||
|
// 组装建表语句
|
||||||
|
createSql := fmt.Sprintf("CREATE TABLE %s (\n", meta.QuoteIdentifier(tableInfo.TableName))
|
||||||
|
fields := make([]string, 0)
|
||||||
|
pks := make([]string, 0)
|
||||||
|
columnComments := make([]string, 0)
|
||||||
|
// 把通用类型转换为达梦类型
|
||||||
|
for _, column := range commonColumns {
|
||||||
|
// 取出当前数据库类型
|
||||||
|
arr := strings.Split(column.ColumnType, "(")
|
||||||
|
ctype := arr[0]
|
||||||
|
// 翻译为通用数据库类型
|
||||||
|
t1 := mssqlColumnTypeMap[ctype]
|
||||||
|
if t1 == "" {
|
||||||
|
ctype = "nvarchar(2000)"
|
||||||
|
} else {
|
||||||
|
// 回写到列信息
|
||||||
|
if len(arr) > 1 {
|
||||||
|
// 如果是int类型不需要指定长度
|
||||||
|
if strings.Contains(strings.ToLower(t1), "int") {
|
||||||
|
ctype = t1
|
||||||
|
} else if collx.ArrayAnyMatches([]string{"float", "number", "decimal"}, strings.ToLower(t1)) {
|
||||||
|
// 如果是float,最大长度为38
|
||||||
|
match := bracketsRegexp.FindStringSubmatch(column.ColumnType)
|
||||||
|
if len(match) > 1 {
|
||||||
|
// size翻倍, 防止数据超长报错
|
||||||
|
size := anyx.ConvInt(match[1])
|
||||||
|
if size >= 38 { // 如果长度超过38
|
||||||
|
ctype = t1 + "(38)"
|
||||||
|
} else {
|
||||||
|
ctype = fmt.Sprintf("%s(%d)", t1, size)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctype = t1 + "(38)"
|
||||||
|
}
|
||||||
|
} else if strings.Contains(strings.ToLower(t1), "char") {
|
||||||
|
// 如果是字符串类型,长度最大4000,否则修改字段类型为text
|
||||||
|
match := bracketsRegexp.FindStringSubmatch(column.ColumnType)
|
||||||
|
if len(match) > 1 {
|
||||||
|
// size翻倍, 防止数据超长报错
|
||||||
|
size := anyx.ConvInt(match[1]) * 2
|
||||||
|
|
||||||
|
if size >= 4000 { // 如果长度超过4000,则替换为text类型
|
||||||
|
ctype = "text"
|
||||||
|
} else {
|
||||||
|
ctype = fmt.Sprintf("%s(%d)", t1, size)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctype = t1 + "(1000)"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctype = t1 + "(" + arr[1]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctype = t1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
column.ColumnType = ctype
|
||||||
|
|
||||||
|
if column.IsPrimaryKey {
|
||||||
|
pks = append(pks, meta.QuoteIdentifier(column.ColumnName))
|
||||||
|
}
|
||||||
|
fields = append(fields, md.genColumnBasicSql(column))
|
||||||
|
commentTmp := "EXECUTE sp_addextendedproperty N'MS_Description', N'%s', N'SCHEMA', N'%s', N'TABLE', N'%s', N'COLUMN', N'%s'"
|
||||||
|
|
||||||
|
// 防止注释内含有特殊字符串导致sql出错
|
||||||
|
comment := replacer.Replace(column.ColumnComment)
|
||||||
|
columnComments = append(columnComments, fmt.Sprintf(commentTmp, comment, md.currentSchema(), column.TableName, column.ColumnName))
|
||||||
|
}
|
||||||
|
createSql += strings.Join(fields, ",")
|
||||||
|
if len(pks) > 0 {
|
||||||
|
createSql += fmt.Sprintf(", PRIMARY KEY CLUSTERED (%s)", strings.Join(pks, ","))
|
||||||
|
}
|
||||||
|
createSql += ")"
|
||||||
|
tableCommentSql := ""
|
||||||
|
if tableInfo.TableComment != "" {
|
||||||
|
commentTmp := "EXECUTE sp_addextendedproperty N'MS_Description', N'%s', N'SCHEMA', N'%s', N'TABLE', N'%s'"
|
||||||
|
tableCommentSql = fmt.Sprintf(commentTmp, replacer.Replace(tableInfo.TableComment), md.currentSchema(), tableInfo.TableName)
|
||||||
|
}
|
||||||
|
|
||||||
|
columnCommentSql := strings.Join(columnComments, ";")
|
||||||
|
|
||||||
|
sqls := make([]string, 0)
|
||||||
|
|
||||||
|
if createSql != "" {
|
||||||
|
sqls = append(sqls, createSql)
|
||||||
|
}
|
||||||
|
if tableCommentSql != "" {
|
||||||
|
sqls = append(sqls, tableCommentSql)
|
||||||
|
}
|
||||||
|
if columnCommentSql != "" {
|
||||||
|
sqls = append(sqls, columnCommentSql)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := md.dc.Exec(strings.Join(sqls, ";"))
|
||||||
|
|
||||||
|
return 1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MssqlDialect) genColumnBasicSql(column dbi.Column) string {
|
||||||
|
meta := md.dc.GetMetaData()
|
||||||
|
colName := meta.QuoteIdentifier(column.ColumnName)
|
||||||
|
|
||||||
|
incr := ""
|
||||||
|
if column.IsIdentity {
|
||||||
|
incr = " IDENTITY(1,1)"
|
||||||
|
}
|
||||||
|
|
||||||
|
nullAble := ""
|
||||||
|
if column.Nullable == "NO" {
|
||||||
|
nullAble = " NOT NULL"
|
||||||
|
}
|
||||||
|
|
||||||
|
defVal := "" // 默认值需要判断引号,如函数是不需要引号的 // 为了防止跨源函数不支持 当默认值是函数时,不需要设置默认值
|
||||||
|
if column.ColumnDefault != "" && !strings.Contains(column.ColumnDefault, "(") {
|
||||||
|
// 哪些字段类型默认值需要加引号
|
||||||
|
mark := false
|
||||||
|
if collx.ArrayAnyMatches([]string{"char", "text", "date", "time", "lob"}, strings.ToLower(column.ColumnType)) {
|
||||||
|
// 当数据类型是日期时间,默认值是日期时间函数时,默认值不需要引号
|
||||||
|
if collx.ArrayAnyMatches([]string{"date", "time"}, strings.ToLower(column.ColumnType)) &&
|
||||||
|
collx.ArrayAnyMatches([]string{"DATE", "TIME"}, strings.ToUpper(column.ColumnDefault)) {
|
||||||
|
mark = false
|
||||||
|
} else {
|
||||||
|
mark = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if mark {
|
||||||
|
defVal = fmt.Sprintf(" DEFAULT '%s'", column.ColumnDefault)
|
||||||
|
} else {
|
||||||
|
defVal = fmt.Sprintf(" DEFAULT %s", column.ColumnDefault)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
columnSql := fmt.Sprintf(" %s %s %s %s %s", colName, column.ColumnType, incr, nullAble, defVal)
|
||||||
|
return columnSql
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MssqlDialect) CreateIndex(tableInfo dbi.Table, indexs []dbi.Index) error {
|
||||||
|
sqls := make([]string, 0)
|
||||||
|
comments := make([]string, 0)
|
||||||
|
for _, index := range indexs {
|
||||||
|
// 通过字段、表名拼接索引名
|
||||||
|
columnName := strings.ReplaceAll(index.ColumnName, "-", "")
|
||||||
|
columnName = strings.ReplaceAll(columnName, "_", "")
|
||||||
|
colName := strings.ReplaceAll(columnName, ",", "_")
|
||||||
|
|
||||||
|
keyType := "normal"
|
||||||
|
unique := ""
|
||||||
|
if index.IsUnique {
|
||||||
|
keyType = "unique"
|
||||||
|
unique = "unique"
|
||||||
|
}
|
||||||
|
indexName := fmt.Sprintf("%s_key_%s_%s", keyType, tableInfo.TableName, colName)
|
||||||
|
|
||||||
|
sqls = append(sqls, fmt.Sprintf("create %s NONCLUSTERED index %s on %s.%s(%s)", unique, indexName, md.currentSchema(), tableInfo.TableName, index.ColumnName))
|
||||||
|
if index.IndexComment != "" {
|
||||||
|
comments = append(comments, fmt.Sprintf("EXECUTE sp_addextendedproperty N'MS_Description', N'%s', N'SCHEMA', N'%s', N'TABLE', N'%s', N'INDEX', N'%s'", index.IndexComment, md.currentSchema(), tableInfo.TableName, indexName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err := md.dc.Exec(strings.Join(sqls, ";"))
|
||||||
|
// 添加注释
|
||||||
|
if len(comments) > 0 {
|
||||||
|
_, err = md.dc.Exec(strings.Join(comments, ";"))
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MssqlDialect) UpdateSequence(tableName string, columns []dbi.Column) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,14 +12,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MSSQL_META_FILE = "metasql/mssql_meta.sql"
|
MSSQL_META_FILE = "metasql/mssql_meta.sql"
|
||||||
MSSQL_DBS_KEY = "MSSQL_DBS"
|
MSSQL_DBS_KEY = "MSSQL_DBS"
|
||||||
MSSQL_DB_SCHEMAS_KEY = "MSSQL_DB_SCHEMAS"
|
MSSQL_DB_SCHEMAS_KEY = "MSSQL_DB_SCHEMAS"
|
||||||
MSSQL_TABLE_INFO_KEY = "MSSQL_TABLE_INFO"
|
MSSQL_TABLE_INFO_KEY = "MSSQL_TABLE_INFO"
|
||||||
MSSQL_INDEX_INFO_KEY = "MSSQL_INDEX_INFO"
|
MSSQL_TABLE_INFO_BY_NAMES_KEY = "MSSQL_TABLE_INFO_BY_NAMES"
|
||||||
MSSQL_COLUMN_MA_KEY = "MSSQL_COLUMN_MA"
|
MSSQL_INDEX_INFO_KEY = "MSSQL_INDEX_INFO"
|
||||||
MSSQL_TABLE_DETAIL_KEY = "MSSQL_TABLE_DETAIL"
|
MSSQL_COLUMN_MA_KEY = "MSSQL_COLUMN_MA"
|
||||||
MSSQL_TABLE_INDEX_DDL_KEY = "MSSQL_TABLE_INDEX_DDL"
|
MSSQL_TABLE_DETAIL_KEY = "MSSQL_TABLE_DETAIL"
|
||||||
|
MSSQL_TABLE_INDEX_DDL_KEY = "MSSQL_TABLE_INDEX_DDL"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MssqlMetaData struct {
|
type MssqlMetaData struct {
|
||||||
@@ -54,8 +55,21 @@ func (md *MssqlMetaData) GetDbNames() ([]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取表基础元信息, 如表名等
|
// 获取表基础元信息, 如表名等
|
||||||
func (md *MssqlMetaData) GetTables() ([]dbi.Table, error) {
|
func (md *MssqlMetaData) GetTables(tableNames ...string) ([]dbi.Table, error) {
|
||||||
_, res, err := md.dc.Query(dbi.GetLocalSql(MSSQL_META_FILE, MSSQL_TABLE_INFO_KEY), md.dc.Info.CurrentSchema())
|
meta := md.dc.GetMetaData()
|
||||||
|
schema := md.dc.Info.CurrentSchema()
|
||||||
|
names := strings.Join(collx.ArrayMap[string, string](tableNames, func(val string) string {
|
||||||
|
return fmt.Sprintf("'%s'", meta.RemoveQuote(val))
|
||||||
|
}), ",")
|
||||||
|
|
||||||
|
var res []map[string]any
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if tableNames != nil || len(tableNames) > 0 {
|
||||||
|
_, res, err = md.dc.Query(fmt.Sprintf(dbi.GetLocalSql(MSSQL_META_FILE, MSSQL_TABLE_INFO_BY_NAMES_KEY), names), schema)
|
||||||
|
} else {
|
||||||
|
_, res, err = md.dc.Query(dbi.GetLocalSql(MSSQL_META_FILE, MSSQL_TABLE_INFO_KEY), schema)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -77,8 +91,9 @@ func (md *MssqlMetaData) GetTables() ([]dbi.Table, error) {
|
|||||||
|
|
||||||
// 获取列元信息, 如列名等
|
// 获取列元信息, 如列名等
|
||||||
func (md *MssqlMetaData) GetColumns(tableNames ...string) ([]dbi.Column, error) {
|
func (md *MssqlMetaData) GetColumns(tableNames ...string) ([]dbi.Column, error) {
|
||||||
|
meta := md.dc.GetMetaData()
|
||||||
tableName := strings.Join(collx.ArrayMap[string, string](tableNames, func(val string) string {
|
tableName := strings.Join(collx.ArrayMap[string, string](tableNames, func(val string) string {
|
||||||
return fmt.Sprintf("'%s'", dbi.RemoveQuote(md, val))
|
return fmt.Sprintf("'%s'", meta.RemoveQuote(val))
|
||||||
}), ",")
|
}), ",")
|
||||||
|
|
||||||
_, res, err := md.dc.Query(fmt.Sprintf(dbi.GetLocalSql(MSSQL_META_FILE, MSSQL_COLUMN_MA_KEY), tableName), md.dc.Info.CurrentSchema())
|
_, res, err := md.dc.Query(fmt.Sprintf(dbi.GetLocalSql(MSSQL_META_FILE, MSSQL_COLUMN_MA_KEY), tableName), md.dc.Info.CurrentSchema())
|
||||||
@@ -176,6 +191,8 @@ func (md *MssqlMetaData) CopyTableDDL(tableName string, newTableName string) (st
|
|||||||
newTableName = tableName
|
newTableName = tableName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
meta := md.dc.GetMetaData()
|
||||||
|
|
||||||
// 根据列信息生成建表语句
|
// 根据列信息生成建表语句
|
||||||
var builder strings.Builder
|
var builder strings.Builder
|
||||||
var commentBuilder strings.Builder
|
var commentBuilder strings.Builder
|
||||||
@@ -195,7 +212,7 @@ func (md *MssqlMetaData) CopyTableDDL(tableName string, newTableName string) (st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
baseTable := fmt.Sprintf("%s.%s", dbi.QuoteIdentifier(md, md.dc.Info.CurrentSchema()), dbi.QuoteIdentifier(md, newTableName))
|
baseTable := fmt.Sprintf("%s.%s", meta.QuoteIdentifier(md.dc.Info.CurrentSchema()), meta.QuoteIdentifier(newTableName))
|
||||||
|
|
||||||
// 查询列信息
|
// 查询列信息
|
||||||
columns, err := md.GetColumns(tableName)
|
columns, err := md.GetColumns(tableName)
|
||||||
@@ -294,6 +311,71 @@ var (
|
|||||||
timeRegexp = regexp.MustCompile(`(?i)time`)
|
timeRegexp = regexp.MustCompile(`(?i)time`)
|
||||||
|
|
||||||
converter = new(DataConverter)
|
converter = new(DataConverter)
|
||||||
|
// 定义正则表达式,匹配括号内的数字
|
||||||
|
bracketsRegexp = regexp.MustCompile(`\((\d+)\)`)
|
||||||
|
// mssql数据类型 对应 公共数据类型
|
||||||
|
commonColumnTypeMap = map[string]string{
|
||||||
|
"bigint": dbi.CommonTypeBigint,
|
||||||
|
"numeric": dbi.CommonTypeNumber,
|
||||||
|
"bit": dbi.CommonTypeInt,
|
||||||
|
"smallint": dbi.CommonTypeSmallint,
|
||||||
|
"decimal": dbi.CommonTypeNumber,
|
||||||
|
"smallmoney": dbi.CommonTypeNumber,
|
||||||
|
"int": dbi.CommonTypeInt,
|
||||||
|
"tinyint": dbi.CommonTypeSmallint, // mssql tinyint不支持负数
|
||||||
|
"money": dbi.CommonTypeNumber,
|
||||||
|
"float": dbi.CommonTypeNumber, // 近似数字
|
||||||
|
"real": dbi.CommonTypeVarchar,
|
||||||
|
"date": dbi.CommonTypeDate, // 日期和时间
|
||||||
|
"datetimeoffset": dbi.CommonTypeDatetime,
|
||||||
|
"datetime2": dbi.CommonTypeDatetime,
|
||||||
|
"smalldatetime": dbi.CommonTypeDatetime,
|
||||||
|
"datetime": dbi.CommonTypeDatetime,
|
||||||
|
"time": dbi.CommonTypeTime,
|
||||||
|
"char": dbi.CommonTypeChar, // 字符串
|
||||||
|
"varchar": dbi.CommonTypeVarchar,
|
||||||
|
"text": dbi.CommonTypeText,
|
||||||
|
"nchar": dbi.CommonTypeChar,
|
||||||
|
"nvarchar": dbi.CommonTypeVarchar,
|
||||||
|
"ntext": dbi.CommonTypeText,
|
||||||
|
"binary": dbi.CommonTypeBinary,
|
||||||
|
"varbinary": dbi.CommonTypeBinary,
|
||||||
|
"cursor": dbi.CommonTypeVarchar, // 其他
|
||||||
|
"rowversion": dbi.CommonTypeVarchar,
|
||||||
|
"hierarchyid": dbi.CommonTypeVarchar,
|
||||||
|
"uniqueidentifier": dbi.CommonTypeVarchar,
|
||||||
|
"sql_variant": dbi.CommonTypeVarchar,
|
||||||
|
"xml": dbi.CommonTypeText,
|
||||||
|
"table": dbi.CommonTypeText,
|
||||||
|
"geometry": dbi.CommonTypeText, // 空间几何类型
|
||||||
|
"geography": dbi.CommonTypeText, // 空间地理类型
|
||||||
|
}
|
||||||
|
|
||||||
|
// 公共数据类型 对应 mssql数据类型
|
||||||
|
|
||||||
|
mssqlColumnTypeMap = map[string]string{
|
||||||
|
dbi.CommonTypeVarchar: "nvarchar",
|
||||||
|
dbi.CommonTypeChar: "nchar",
|
||||||
|
dbi.CommonTypeText: "ntext",
|
||||||
|
dbi.CommonTypeBlob: "ntext",
|
||||||
|
dbi.CommonTypeLongblob: "ntext",
|
||||||
|
dbi.CommonTypeLongtext: "ntext",
|
||||||
|
dbi.CommonTypeBinary: "varbinary",
|
||||||
|
dbi.CommonTypeMediumblob: "ntext",
|
||||||
|
dbi.CommonTypeMediumtext: "ntext",
|
||||||
|
dbi.CommonTypeVarbinary: "varbinary",
|
||||||
|
dbi.CommonTypeInt: "int",
|
||||||
|
dbi.CommonTypeSmallint: "smallint",
|
||||||
|
dbi.CommonTypeTinyint: "smallint",
|
||||||
|
dbi.CommonTypeNumber: "decimal",
|
||||||
|
dbi.CommonTypeBigint: "bigint",
|
||||||
|
dbi.CommonTypeDatetime: "datetime2",
|
||||||
|
dbi.CommonTypeDate: "date",
|
||||||
|
dbi.CommonTypeTime: "time",
|
||||||
|
dbi.CommonTypeTimestamp: "timestamp",
|
||||||
|
dbi.CommonTypeEnum: "nvarchar",
|
||||||
|
dbi.CommonTypeJSON: "nvarchar",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
type DataConverter struct {
|
type DataConverter struct {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"mayfly-go/internal/db/dbm/dbi"
|
"mayfly-go/internal/db/dbm/dbi"
|
||||||
|
"mayfly-go/pkg/utils/collx"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -49,6 +50,10 @@ func (md *MysqlDialect) BatchInsert(tx *sql.Tx, tableName string, columns []stri
|
|||||||
return md.dc.TxExec(tx, sqlStr, args...)
|
return md.dc.TxExec(tx, sqlStr, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (md *MysqlDialect) GetDataConverter() dbi.DataConverter {
|
||||||
|
return converter
|
||||||
|
}
|
||||||
|
|
||||||
func (md *MysqlDialect) CopyTable(copy *dbi.DbCopyTable) error {
|
func (md *MysqlDialect) CopyTable(copy *dbi.DbCopyTable) error {
|
||||||
|
|
||||||
tableName := copy.TableName
|
tableName := copy.TableName
|
||||||
@@ -70,3 +75,143 @@ func (md *MysqlDialect) CopyTable(copy *dbi.DbCopyTable) error {
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (md *MysqlDialect) TransColumns(columns []dbi.Column) []dbi.Column {
|
||||||
|
var commonColumns []dbi.Column
|
||||||
|
for _, column := range columns {
|
||||||
|
// 取出当前数据库类型
|
||||||
|
arr := strings.Split(column.ColumnType, "(")
|
||||||
|
ctype := arr[0]
|
||||||
|
// 翻译为通用数据库类型
|
||||||
|
t1 := commonColumnTypeMap[ctype]
|
||||||
|
if t1 == "" {
|
||||||
|
ctype = "varchar(2000)"
|
||||||
|
} else {
|
||||||
|
// 回写到列信息
|
||||||
|
if len(arr) > 1 {
|
||||||
|
ctype = t1 + "(" + arr[1]
|
||||||
|
} else {
|
||||||
|
ctype = t1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
column.ColumnType = ctype
|
||||||
|
commonColumns = append(commonColumns, column)
|
||||||
|
}
|
||||||
|
return commonColumns
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MysqlDialect) CreateTable(commonColumns []dbi.Column, tableInfo dbi.Table, dropOldTable bool) (int, error) {
|
||||||
|
if dropOldTable {
|
||||||
|
_, _ = md.dc.Exec(fmt.Sprintf("DROP TABLE IF EXISTS %s", tableInfo.TableName))
|
||||||
|
}
|
||||||
|
// 组装建表语句
|
||||||
|
createSql := fmt.Sprintf("CREATE TABLE %s (\n", tableInfo.TableName)
|
||||||
|
fields := make([]string, 0)
|
||||||
|
pks := make([]string, 0)
|
||||||
|
// 把通用类型转换为达梦类型
|
||||||
|
for _, column := range commonColumns {
|
||||||
|
// 取出当前数据库类型
|
||||||
|
arr := strings.Split(column.ColumnType, "(")
|
||||||
|
ctype := arr[0]
|
||||||
|
// 翻译为通用数据库类型
|
||||||
|
t1 := mysqlColumnTypeMap[ctype]
|
||||||
|
if t1 == "" {
|
||||||
|
ctype = "varchar(2000)"
|
||||||
|
} else {
|
||||||
|
// 回写到列信息
|
||||||
|
if len(arr) > 1 {
|
||||||
|
ctype = t1 + "(" + arr[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
column.ColumnType = ctype
|
||||||
|
|
||||||
|
if column.IsPrimaryKey {
|
||||||
|
pks = append(pks, column.ColumnName)
|
||||||
|
}
|
||||||
|
fields = append(fields, md.genColumnBasicSql(column))
|
||||||
|
}
|
||||||
|
createSql += strings.Join(fields, ",")
|
||||||
|
if len(pks) > 0 {
|
||||||
|
createSql += fmt.Sprintf(", PRIMARY KEY (%s)", strings.Join(pks, ","))
|
||||||
|
}
|
||||||
|
createSql += fmt.Sprintf(") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ")
|
||||||
|
if tableInfo.TableComment != "" {
|
||||||
|
replacer := strings.NewReplacer(";", "", "'", "")
|
||||||
|
createSql += fmt.Sprintf(" COMMENT '%s'", replacer.Replace(tableInfo.TableComment))
|
||||||
|
}
|
||||||
|
_, err := md.dc.Exec(createSql)
|
||||||
|
|
||||||
|
return 1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MysqlDialect) genColumnBasicSql(column dbi.Column) string {
|
||||||
|
|
||||||
|
incr := ""
|
||||||
|
if column.IsIdentity {
|
||||||
|
incr = " AUTO_INCREMENT"
|
||||||
|
}
|
||||||
|
|
||||||
|
nullAble := ""
|
||||||
|
if column.Nullable == "NO" {
|
||||||
|
nullAble = " NOT NULL"
|
||||||
|
}
|
||||||
|
|
||||||
|
defVal := "" // 默认值需要判断引号,如函数是不需要引号的 // 为了防止跨源函数不支持 当默认值是函数时,不需要设置默认值
|
||||||
|
if column.ColumnDefault != "" && !strings.Contains(column.ColumnDefault, "(") {
|
||||||
|
// 哪些字段类型默认值需要加引号
|
||||||
|
mark := false
|
||||||
|
if collx.ArrayAnyMatches([]string{"char", "text", "date", "time", "lob"}, strings.ToLower(column.ColumnType)) {
|
||||||
|
// 当数据类型是日期时间,默认值是日期时间函数时,默认值不需要引号
|
||||||
|
if collx.ArrayAnyMatches([]string{"date", "time"}, strings.ToLower(column.ColumnType)) &&
|
||||||
|
collx.ArrayAnyMatches([]string{"DATE", "TIME"}, strings.ToUpper(column.ColumnDefault)) {
|
||||||
|
mark = false
|
||||||
|
} else {
|
||||||
|
mark = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mark {
|
||||||
|
defVal = fmt.Sprintf(" DEFAULT '%s'", column.ColumnDefault)
|
||||||
|
} else {
|
||||||
|
defVal = fmt.Sprintf(" DEFAULT %s", column.ColumnDefault)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
comment := ""
|
||||||
|
if column.ColumnComment != "" {
|
||||||
|
// 防止注释内含有特殊字符串导致sql出错
|
||||||
|
replacer := strings.NewReplacer(";", "", "'", "")
|
||||||
|
commentStr := replacer.Replace(column.ColumnComment)
|
||||||
|
comment = fmt.Sprintf(" COMMENT '%s'", commentStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
columnSql := fmt.Sprintf(" %s %s %s %s %s %s", md.dc.GetMetaData().QuoteIdentifier(column.ColumnName), column.ColumnType, nullAble, incr, defVal, comment)
|
||||||
|
return columnSql
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MysqlDialect) CreateIndex(tableInfo dbi.Table, indexs []dbi.Index) error {
|
||||||
|
meta := md.dc.GetMetaData()
|
||||||
|
for _, index := range indexs {
|
||||||
|
// 通过字段、表名拼接索引名
|
||||||
|
columnName := strings.ReplaceAll(index.ColumnName, "-", "")
|
||||||
|
columnName = strings.ReplaceAll(columnName, "_", "")
|
||||||
|
colName := strings.ReplaceAll(columnName, ",", "_")
|
||||||
|
|
||||||
|
keyType := "normal"
|
||||||
|
unique := ""
|
||||||
|
if index.IsUnique {
|
||||||
|
keyType = "unique"
|
||||||
|
unique = "unique"
|
||||||
|
}
|
||||||
|
indexName := fmt.Sprintf("%s_key_%s_%s", keyType, tableInfo.TableName, colName)
|
||||||
|
sqlTmp := "ALTER TABLE %s ADD %s INDEX %s(%s) USING BTREE COMMENT '%s'"
|
||||||
|
replacer := strings.NewReplacer(";", "", "'", "")
|
||||||
|
_, err := md.dc.Exec(fmt.Sprintf(sqlTmp, meta.QuoteIdentifier(tableInfo.TableName), unique, indexName, index.ColumnName, replacer.Replace(index.IndexComment)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MysqlDialect) UpdateSequence(tableName string, columns []dbi.Column) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,11 +15,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MYSQL_META_FILE = "metasql/mysql_meta.sql"
|
MYSQL_META_FILE = "metasql/mysql_meta.sql"
|
||||||
MYSQL_DBS = "MYSQL_DBS"
|
MYSQL_DBS = "MYSQL_DBS"
|
||||||
MYSQL_TABLE_INFO_KEY = "MYSQL_TABLE_INFO"
|
MYSQL_TABLE_INFO_KEY = "MYSQL_TABLE_INFO"
|
||||||
MYSQL_INDEX_INFO_KEY = "MYSQL_INDEX_INFO"
|
MYSQL_TABLE_INFO_BY_NAMES_KEY = "MYSQL_TABLE_INFO_BY_NAMES"
|
||||||
MYSQL_COLUMN_MA_KEY = "MYSQL_COLUMN_MA"
|
MYSQL_INDEX_INFO_KEY = "MYSQL_INDEX_INFO"
|
||||||
|
MYSQL_COLUMN_MA_KEY = "MYSQL_COLUMN_MA"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MysqlMetaData struct {
|
type MysqlMetaData struct {
|
||||||
@@ -52,9 +53,21 @@ func (md *MysqlMetaData) GetDbNames() ([]string, error) {
|
|||||||
return databases, nil
|
return databases, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取表基础元信息, 如表名等
|
func (md *MysqlMetaData) GetTables(tableNames ...string) ([]dbi.Table, error) {
|
||||||
func (md *MysqlMetaData) GetTables() ([]dbi.Table, error) {
|
meta := md.dc.GetMetaData()
|
||||||
_, res, err := md.dc.Query(dbi.GetLocalSql(MYSQL_META_FILE, MYSQL_TABLE_INFO_KEY))
|
names := strings.Join(collx.ArrayMap[string, string](tableNames, func(val string) string {
|
||||||
|
return fmt.Sprintf("'%s'", meta.RemoveQuote(val))
|
||||||
|
}), ",")
|
||||||
|
|
||||||
|
var res []map[string]any
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if tableNames != nil || len(tableNames) > 0 {
|
||||||
|
_, res, err = md.dc.Query(fmt.Sprintf(dbi.GetLocalSql(MYSQL_META_FILE, MYSQL_TABLE_INFO_BY_NAMES_KEY), names))
|
||||||
|
} else {
|
||||||
|
_, res, err = md.dc.Query(dbi.GetLocalSql(MYSQL_META_FILE, MYSQL_TABLE_INFO_KEY))
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -75,8 +88,9 @@ func (md *MysqlMetaData) GetTables() ([]dbi.Table, error) {
|
|||||||
|
|
||||||
// 获取列元信息, 如列名等
|
// 获取列元信息, 如列名等
|
||||||
func (md *MysqlMetaData) GetColumns(tableNames ...string) ([]dbi.Column, error) {
|
func (md *MysqlMetaData) GetColumns(tableNames ...string) ([]dbi.Column, error) {
|
||||||
|
meta := md.dc.GetMetaData()
|
||||||
tableName := strings.Join(collx.ArrayMap[string, string](tableNames, func(val string) string {
|
tableName := strings.Join(collx.ArrayMap[string, string](tableNames, func(val string) string {
|
||||||
return fmt.Sprintf("'%s'", dbi.RemoveQuote(md, val))
|
return fmt.Sprintf("'%s'", meta.RemoveQuote(val))
|
||||||
}), ",")
|
}), ",")
|
||||||
|
|
||||||
_, res, err := md.dc.Query(fmt.Sprintf(dbi.GetLocalSql(MYSQL_META_FILE, MYSQL_COLUMN_MA_KEY), tableName))
|
_, res, err := md.dc.Query(fmt.Sprintf(dbi.GetLocalSql(MYSQL_META_FILE, MYSQL_COLUMN_MA_KEY), tableName))
|
||||||
@@ -89,7 +103,7 @@ func (md *MysqlMetaData) GetColumns(tableNames ...string) ([]dbi.Column, error)
|
|||||||
columns = append(columns, dbi.Column{
|
columns = append(columns, dbi.Column{
|
||||||
TableName: anyx.ConvString(re["tableName"]),
|
TableName: anyx.ConvString(re["tableName"]),
|
||||||
ColumnName: anyx.ConvString(re["columnName"]),
|
ColumnName: anyx.ConvString(re["columnName"]),
|
||||||
ColumnType: anyx.ConvString(re["columnType"]),
|
ColumnType: strings.Replace(anyx.ConvString(re["columnType"]), " unsigned", "", 1),
|
||||||
ColumnComment: anyx.ConvString(re["columnComment"]),
|
ColumnComment: anyx.ConvString(re["columnComment"]),
|
||||||
Nullable: anyx.ConvString(re["nullable"]),
|
Nullable: anyx.ConvString(re["nullable"]),
|
||||||
IsPrimaryKey: anyx.ConvInt(re["isPrimaryKey"]) == 1,
|
IsPrimaryKey: anyx.ConvInt(re["isPrimaryKey"]) == 1,
|
||||||
@@ -199,6 +213,59 @@ var (
|
|||||||
timeRegexp = regexp.MustCompile(`(?i)time`)
|
timeRegexp = regexp.MustCompile(`(?i)time`)
|
||||||
|
|
||||||
converter = new(DataConverter)
|
converter = new(DataConverter)
|
||||||
|
|
||||||
|
// mysql数据类型 映射 公共数据类型
|
||||||
|
commonColumnTypeMap = map[string]string{
|
||||||
|
"bigint": dbi.CommonTypeBigint,
|
||||||
|
"binary": dbi.CommonTypeBinary,
|
||||||
|
"blob": dbi.CommonTypeBlob,
|
||||||
|
"char": dbi.CommonTypeChar,
|
||||||
|
"datetime": dbi.CommonTypeDatetime,
|
||||||
|
"date": dbi.CommonTypeDate,
|
||||||
|
"decimal": dbi.CommonTypeNumber,
|
||||||
|
"double": dbi.CommonTypeNumber,
|
||||||
|
"enum": dbi.CommonTypeEnum,
|
||||||
|
"float": dbi.CommonTypeNumber,
|
||||||
|
"int": dbi.CommonTypeInt,
|
||||||
|
"json": dbi.CommonTypeJSON,
|
||||||
|
"longblob": dbi.CommonTypeLongblob,
|
||||||
|
"longtext": dbi.CommonTypeLongtext,
|
||||||
|
"mediumblob": dbi.CommonTypeBlob,
|
||||||
|
"mediumtext": dbi.CommonTypeText,
|
||||||
|
"set": dbi.CommonTypeVarchar,
|
||||||
|
"smallint": dbi.CommonTypeSmallint,
|
||||||
|
"text": dbi.CommonTypeText,
|
||||||
|
"time": dbi.CommonTypeTime,
|
||||||
|
"timestamp": dbi.CommonTypeTimestamp,
|
||||||
|
"tinyint": dbi.CommonTypeTinyint,
|
||||||
|
"varbinary": dbi.CommonTypeVarbinary,
|
||||||
|
"varchar": dbi.CommonTypeVarchar,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 公共数据类型 映射 mysql数据类型
|
||||||
|
mysqlColumnTypeMap = map[string]string{
|
||||||
|
dbi.CommonTypeVarchar: "varchar",
|
||||||
|
dbi.CommonTypeChar: "char",
|
||||||
|
dbi.CommonTypeText: "text",
|
||||||
|
dbi.CommonTypeBlob: "blob",
|
||||||
|
dbi.CommonTypeLongblob: "longblob",
|
||||||
|
dbi.CommonTypeLongtext: "longtext",
|
||||||
|
dbi.CommonTypeBinary: "binary",
|
||||||
|
dbi.CommonTypeMediumblob: "blob",
|
||||||
|
dbi.CommonTypeMediumtext: "text",
|
||||||
|
dbi.CommonTypeVarbinary: "varbinary",
|
||||||
|
dbi.CommonTypeInt: "int",
|
||||||
|
dbi.CommonTypeSmallint: "smallint",
|
||||||
|
dbi.CommonTypeTinyint: "tinyint",
|
||||||
|
dbi.CommonTypeNumber: "decimal",
|
||||||
|
dbi.CommonTypeBigint: "bigint",
|
||||||
|
dbi.CommonTypeDatetime: "datetime",
|
||||||
|
dbi.CommonTypeDate: "date",
|
||||||
|
dbi.CommonTypeTime: "time",
|
||||||
|
dbi.CommonTypeTimestamp: "timestamp",
|
||||||
|
dbi.CommonTypeEnum: "enum",
|
||||||
|
dbi.CommonTypeJSON: "json",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
type DataConverter struct {
|
type DataConverter struct {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"mayfly-go/internal/db/dbm/dbi"
|
"mayfly-go/internal/db/dbm/dbi"
|
||||||
"mayfly-go/pkg/logx"
|
"mayfly-go/pkg/logx"
|
||||||
|
"mayfly-go/pkg/utils/anyx"
|
||||||
"mayfly-go/pkg/utils/collx"
|
"mayfly-go/pkg/utils/collx"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -157,3 +158,217 @@ func (od *OracleDialect) CopyTable(copy *dbi.DbCopyTable) error {
|
|||||||
_, err := od.dc.Exec(fmt.Sprintf("create table \"%s\" as select * from \"%s\" %s", newTableName, copy.TableName, condition))
|
_, err := od.dc.Exec(fmt.Sprintf("create table \"%s\" as select * from \"%s\" %s", newTableName, copy.TableName, condition))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (od *OracleDialect) TransColumns(columns []dbi.Column) []dbi.Column {
|
||||||
|
var commonColumns []dbi.Column
|
||||||
|
for _, column := range columns {
|
||||||
|
// 取出当前数据库类型
|
||||||
|
arr := strings.Split(column.ColumnType, "(")
|
||||||
|
ctype := arr[0]
|
||||||
|
|
||||||
|
// 翻译为通用数据库类型
|
||||||
|
t1 := commonColumnTypeMap[ctype]
|
||||||
|
if t1 == "" {
|
||||||
|
ctype = "NVARCHAR2(2000)"
|
||||||
|
} else {
|
||||||
|
// 回写到列信息
|
||||||
|
if t1 == "NUMBER" {
|
||||||
|
// 如果是转number类型,需要根据公共类型加上长度, 如 bigint 需要转换为number(19,0)
|
||||||
|
if column.ColumnType == dbi.CommonTypeBigint {
|
||||||
|
ctype = t1 + "(19, 0)"
|
||||||
|
} else {
|
||||||
|
ctype = t1
|
||||||
|
}
|
||||||
|
} else if t1 != "NUMBER" && len(arr) > 1 {
|
||||||
|
ctype = t1 + "(" + arr[1]
|
||||||
|
} else {
|
||||||
|
ctype = t1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
column.ColumnType = ctype
|
||||||
|
commonColumns = append(commonColumns, column)
|
||||||
|
}
|
||||||
|
return commonColumns
|
||||||
|
}
|
||||||
|
|
||||||
|
func (od *OracleDialect) CreateTable(commonColumns []dbi.Column, tableInfo dbi.Table, dropOldTable bool) (int, error) {
|
||||||
|
meta := od.dc.GetMetaData()
|
||||||
|
replacer := strings.NewReplacer(";", "", "'", "")
|
||||||
|
quoteTableName := meta.QuoteIdentifier(tableInfo.TableName)
|
||||||
|
if dropOldTable {
|
||||||
|
// 如果表存在,先删除表
|
||||||
|
dropSqlTmp := `
|
||||||
|
declare
|
||||||
|
num number;
|
||||||
|
begin
|
||||||
|
select count(1) into num from user_tables where table_name = '%s' and owner = (SELECT sys_context('USERENV', 'CURRENT_SCHEMA') FROM dual) ;
|
||||||
|
if num > 0 then
|
||||||
|
execute immediate 'drop table "%s"' ;
|
||||||
|
end if;
|
||||||
|
end;
|
||||||
|
`
|
||||||
|
_, _ = od.dc.Exec(fmt.Sprintf(dropSqlTmp, tableInfo.TableName, tableInfo.TableName))
|
||||||
|
}
|
||||||
|
// 组装建表语句
|
||||||
|
createSql := fmt.Sprintf("CREATE TABLE %s (", quoteTableName)
|
||||||
|
fields := make([]string, 0)
|
||||||
|
pks := make([]string, 0)
|
||||||
|
columnComments := make([]string, 0)
|
||||||
|
// 把通用类型转换为达梦类型
|
||||||
|
for _, column := range commonColumns {
|
||||||
|
// 取出当前数据库类型
|
||||||
|
arr := strings.Split(column.ColumnType, "(")
|
||||||
|
ctype := arr[0]
|
||||||
|
// 翻译为通用数据库类型
|
||||||
|
t1 := oracleColumnTypeMap[ctype]
|
||||||
|
if t1 == "" {
|
||||||
|
ctype = "NVARCHAR2(2000)"
|
||||||
|
} else {
|
||||||
|
// 回写到列信息
|
||||||
|
if len(arr) > 1 {
|
||||||
|
// 如果是字符串类型,长度最大4000,否则修改字段类型为clob
|
||||||
|
if strings.Contains(strings.ToLower(t1), "char") {
|
||||||
|
match := bracketsRegexp.FindStringSubmatch(column.ColumnType)
|
||||||
|
if len(match) > 1 {
|
||||||
|
size := anyx.ConvInt(match[1])
|
||||||
|
if size >= 4000 { // 如果长度超过4000,则替换为text类型
|
||||||
|
ctype = "CLOB"
|
||||||
|
} else {
|
||||||
|
ctype = fmt.Sprintf("%s(%d)", t1, size)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctype = t1 + "(2000)"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctype = t1 + "(" + arr[1]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctype = t1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
column.ColumnType = ctype
|
||||||
|
|
||||||
|
if column.IsPrimaryKey {
|
||||||
|
pks = append(pks, meta.QuoteIdentifier(column.ColumnName))
|
||||||
|
}
|
||||||
|
fields = append(fields, od.genColumnBasicSql(column))
|
||||||
|
// 防止注释内含有特殊字符串导致sql出错
|
||||||
|
comment := replacer.Replace(column.ColumnComment)
|
||||||
|
if comment != "" {
|
||||||
|
columnComments = append(columnComments, fmt.Sprintf("COMMENT ON COLUMN %s.%s IS '%s'", quoteTableName, meta.QuoteIdentifier(column.ColumnName), comment))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createSql += strings.Join(fields, ",")
|
||||||
|
if len(pks) > 0 {
|
||||||
|
createSql += fmt.Sprintf(", PRIMARY KEY (%s)", strings.Join(pks, ","))
|
||||||
|
}
|
||||||
|
createSql += ")"
|
||||||
|
|
||||||
|
tableCommentSql := ""
|
||||||
|
if tableInfo.TableComment != "" {
|
||||||
|
tableCommentSql = fmt.Sprintf(" COMMENT ON TABLE %s is '%s'", meta.QuoteIdentifier(tableInfo.TableName), replacer.Replace(tableInfo.TableComment))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 需要分开执行sql
|
||||||
|
var err error
|
||||||
|
if createSql != "" {
|
||||||
|
_, err = od.dc.Exec(createSql)
|
||||||
|
}
|
||||||
|
if tableCommentSql != "" {
|
||||||
|
_, err = od.dc.Exec(tableCommentSql)
|
||||||
|
}
|
||||||
|
if len(columnComments) > 0 {
|
||||||
|
for _, commentSql := range columnComments {
|
||||||
|
_, err = od.dc.Exec(commentSql)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (od *OracleDialect) genColumnBasicSql(column dbi.Column) string {
|
||||||
|
meta := od.dc.GetMetaData()
|
||||||
|
colName := meta.QuoteIdentifier(column.ColumnName)
|
||||||
|
|
||||||
|
if column.IsIdentity {
|
||||||
|
// 如果是自增,不需要设置默认值和空值,自增列数据类型必须是number
|
||||||
|
return fmt.Sprintf(" %s NUMBER generated by default as IDENTITY", colName)
|
||||||
|
}
|
||||||
|
|
||||||
|
nullAble := ""
|
||||||
|
if column.Nullable == "NO" {
|
||||||
|
nullAble = " NOT NULL"
|
||||||
|
}
|
||||||
|
|
||||||
|
defVal := "" // 默认值需要判断引号,如函数是不需要引号的
|
||||||
|
if column.ColumnDefault != "" {
|
||||||
|
mark := false
|
||||||
|
// 哪些字段类型默认值需要加引号
|
||||||
|
if collx.ArrayAnyMatches([]string{"CHAR", "LONG", "DATE", "TIME", "CLOB", "BLOB", "BFILE"}, column.ColumnType) {
|
||||||
|
// 默认值是时间日期函数的必须要加引号
|
||||||
|
val := strings.ToUpper(column.ColumnDefault)
|
||||||
|
if collx.ArrayAnyMatches([]string{"DATE", "TIMESTAMP"}, column.ColumnType) && val == "CURRENT_DATE" || val == "CURRENT_TIMESTAMP" {
|
||||||
|
mark = false
|
||||||
|
} else {
|
||||||
|
mark = true
|
||||||
|
}
|
||||||
|
if mark {
|
||||||
|
defVal = fmt.Sprintf(" DEFAULT '%s'", column.ColumnDefault)
|
||||||
|
} else {
|
||||||
|
defVal = fmt.Sprintf(" DEFAULT %s", column.ColumnDefault)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果是数字,默认值提取数字
|
||||||
|
if collx.ArrayAnyMatches([]string{"NUM", "INT"}, column.ColumnType) {
|
||||||
|
match := bracketsRegexp.FindStringSubmatch(column.ColumnType)
|
||||||
|
if len(match) > 1 {
|
||||||
|
length := anyx.ConvInt(match[1])
|
||||||
|
defVal = fmt.Sprintf(" DEFAULT %d", length)
|
||||||
|
} else {
|
||||||
|
defVal = fmt.Sprintf(" DEFAULT 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defVal = fmt.Sprintf(" DEFAULT %s", column.ColumnDefault)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
columnSql := fmt.Sprintf(" %s %s %s %s", colName, column.ColumnType, defVal, nullAble)
|
||||||
|
return columnSql
|
||||||
|
}
|
||||||
|
|
||||||
|
func (od *OracleDialect) CreateIndex(tableInfo dbi.Table, indexs []dbi.Index) error {
|
||||||
|
meta := od.dc.GetMetaData()
|
||||||
|
sqls := make([]string, 0)
|
||||||
|
comments := make([]string, 0)
|
||||||
|
for _, index := range indexs {
|
||||||
|
// 通过字段、表名拼接索引名
|
||||||
|
columnName := strings.ReplaceAll(index.ColumnName, "-", "")
|
||||||
|
columnName = strings.ReplaceAll(columnName, "_", "")
|
||||||
|
colName := strings.ReplaceAll(columnName, ",", "_")
|
||||||
|
|
||||||
|
keyType := "normal"
|
||||||
|
unique := ""
|
||||||
|
if index.IsUnique {
|
||||||
|
keyType = "unique"
|
||||||
|
unique = "unique"
|
||||||
|
}
|
||||||
|
indexName := fmt.Sprintf("%s_key_%s_%s", keyType, tableInfo.TableName, colName)
|
||||||
|
|
||||||
|
sqls = append(sqls, fmt.Sprintf("CREATE %s INDEX %s ON %s(%s)", unique, indexName, meta.QuoteIdentifier(tableInfo.TableName), index.ColumnName))
|
||||||
|
if index.IndexComment != "" {
|
||||||
|
comments = append(comments, fmt.Sprintf("COMMENT ON INDEX %s IS '%s'", indexName, index.IndexComment))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err := od.dc.Exec(strings.Join(sqls, ";"))
|
||||||
|
|
||||||
|
// 添加注释
|
||||||
|
if len(comments) > 0 {
|
||||||
|
_, err = od.dc.Exec(strings.Join(comments, ";"))
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (od *OracleDialect) UpdateSequence(tableName string, columns []dbi.Column) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,11 +13,12 @@ import (
|
|||||||
|
|
||||||
// ---------------------------------- DM元数据 -----------------------------------
|
// ---------------------------------- DM元数据 -----------------------------------
|
||||||
const (
|
const (
|
||||||
ORACLE_META_FILE = "metasql/oracle_meta.sql"
|
ORACLE_META_FILE = "metasql/oracle_meta.sql"
|
||||||
ORACLE_DB_SCHEMAS = "ORACLE_DB_SCHEMAS"
|
ORACLE_DB_SCHEMAS = "ORACLE_DB_SCHEMAS"
|
||||||
ORACLE_TABLE_INFO_KEY = "ORACLE_TABLE_INFO"
|
ORACLE_TABLE_INFO_KEY = "ORACLE_TABLE_INFO"
|
||||||
ORACLE_INDEX_INFO_KEY = "ORACLE_INDEX_INFO"
|
ORACLE_TABLE_INFO_BY_NAMES_KEY = "ORACLE_TABLE_INFO_BY_NAMES"
|
||||||
ORACLE_COLUMN_MA_KEY = "ORACLE_COLUMN_MA"
|
ORACLE_INDEX_INFO_KEY = "ORACLE_INDEX_INFO"
|
||||||
|
ORACLE_COLUMN_MA_KEY = "ORACLE_COLUMN_MA"
|
||||||
)
|
)
|
||||||
|
|
||||||
type OracleMetaData struct {
|
type OracleMetaData struct {
|
||||||
@@ -51,14 +52,20 @@ func (od *OracleMetaData) GetDbNames() ([]string, error) {
|
|||||||
return databases, nil
|
return databases, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取表基础元信息, 如表名等
|
func (od *OracleMetaData) GetTables(tableNames ...string) ([]dbi.Table, error) {
|
||||||
func (od *OracleMetaData) GetTables() ([]dbi.Table, error) {
|
meta := od.dc.GetMetaData()
|
||||||
|
names := strings.Join(collx.ArrayMap[string, string](tableNames, func(val string) string {
|
||||||
|
return fmt.Sprintf("'%s'", meta.RemoveQuote(val))
|
||||||
|
}), ",")
|
||||||
|
|
||||||
// 首先执行更新统计信息sql 这个统计信息在数据量比较大的时候就比较耗时,所以最好定时执行
|
var res []map[string]any
|
||||||
// _, _, err := pd.dc.Query("dbms_stats.GATHER_SCHEMA_stats(SELECT SF_GET_SCHEMA_NAME_BY_ID(CURRENT_SCHID))")
|
var err error
|
||||||
|
|
||||||
// 查询表信息
|
if tableNames != nil || len(tableNames) > 0 {
|
||||||
_, res, err := od.dc.Query(dbi.GetLocalSql(ORACLE_META_FILE, ORACLE_TABLE_INFO_KEY))
|
_, res, err = od.dc.Query(fmt.Sprintf(dbi.GetLocalSql(ORACLE_META_FILE, ORACLE_TABLE_INFO_BY_NAMES_KEY), names))
|
||||||
|
} else {
|
||||||
|
_, res, err = od.dc.Query(dbi.GetLocalSql(ORACLE_META_FILE, ORACLE_TABLE_INFO_KEY))
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -79,8 +86,9 @@ func (od *OracleMetaData) GetTables() ([]dbi.Table, error) {
|
|||||||
|
|
||||||
// 获取列元信息, 如列名等
|
// 获取列元信息, 如列名等
|
||||||
func (od *OracleMetaData) GetColumns(tableNames ...string) ([]dbi.Column, error) {
|
func (od *OracleMetaData) GetColumns(tableNames ...string) ([]dbi.Column, error) {
|
||||||
|
meta := od.dc.GetMetaData()
|
||||||
tableName := strings.Join(collx.ArrayMap[string, string](tableNames, func(val string) string {
|
tableName := strings.Join(collx.ArrayMap[string, string](tableNames, func(val string) string {
|
||||||
return fmt.Sprintf("'%s'", dbi.RemoveQuote(od, val))
|
return fmt.Sprintf("'%s'", meta.RemoveQuote(val))
|
||||||
}), ",")
|
}), ",")
|
||||||
|
|
||||||
// 如果表数量超过了1000,需要分批查询
|
// 如果表数量超过了1000,需要分批查询
|
||||||
@@ -273,7 +281,57 @@ var (
|
|||||||
// 日期时间类型
|
// 日期时间类型
|
||||||
datetimeTypeRegexp = regexp.MustCompile(`(?i)date|timestamp`)
|
datetimeTypeRegexp = regexp.MustCompile(`(?i)date|timestamp`)
|
||||||
|
|
||||||
|
bracketsRegexp = regexp.MustCompile(`\((\d+)\)`)
|
||||||
|
|
||||||
converter = new(DataConverter)
|
converter = new(DataConverter)
|
||||||
|
|
||||||
|
// oracle数据类型 映射 公共数据类型
|
||||||
|
commonColumnTypeMap = map[string]string{
|
||||||
|
"CHAR": dbi.CommonTypeChar,
|
||||||
|
"NCHAR": dbi.CommonTypeChar,
|
||||||
|
"VARCHAR2": dbi.CommonTypeVarchar,
|
||||||
|
"NVARCHAR2": dbi.CommonTypeVarchar,
|
||||||
|
"NUMBER": dbi.CommonTypeNumber,
|
||||||
|
"INTEGER": dbi.CommonTypeInt,
|
||||||
|
"INT": dbi.CommonTypeInt,
|
||||||
|
"DECIMAL": dbi.CommonTypeNumber,
|
||||||
|
"FLOAT": dbi.CommonTypeNumber,
|
||||||
|
"REAL": dbi.CommonTypeNumber,
|
||||||
|
"BINARY_FLOAT": dbi.CommonTypeNumber,
|
||||||
|
"BINARY_DOUBLE": dbi.CommonTypeNumber,
|
||||||
|
"DATE": dbi.CommonTypeDate,
|
||||||
|
"TIMESTAMP": dbi.CommonTypeDatetime,
|
||||||
|
"LONG": dbi.CommonTypeLongtext,
|
||||||
|
"BLOB": dbi.CommonTypeLongtext,
|
||||||
|
"CLOB": dbi.CommonTypeLongtext,
|
||||||
|
"NCLOB": dbi.CommonTypeLongtext,
|
||||||
|
"BFILE": dbi.CommonTypeBinary,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 公共数据类型 映射 oracle数据类型
|
||||||
|
oracleColumnTypeMap = map[string]string{
|
||||||
|
dbi.CommonTypeVarchar: "NVARCHAR2",
|
||||||
|
dbi.CommonTypeChar: "NCHAR",
|
||||||
|
dbi.CommonTypeText: "CLOB",
|
||||||
|
dbi.CommonTypeBlob: "CLOB",
|
||||||
|
dbi.CommonTypeLongblob: "CLOB",
|
||||||
|
dbi.CommonTypeLongtext: "CLOB",
|
||||||
|
dbi.CommonTypeBinary: "BFILE",
|
||||||
|
dbi.CommonTypeMediumblob: "CLOB",
|
||||||
|
dbi.CommonTypeMediumtext: "CLOB",
|
||||||
|
dbi.CommonTypeVarbinary: "BFILE",
|
||||||
|
dbi.CommonTypeInt: "INT",
|
||||||
|
dbi.CommonTypeSmallint: "INT",
|
||||||
|
dbi.CommonTypeTinyint: "INT",
|
||||||
|
dbi.CommonTypeNumber: "NUMBER",
|
||||||
|
dbi.CommonTypeBigint: "NUMBER",
|
||||||
|
dbi.CommonTypeDatetime: "DATE",
|
||||||
|
dbi.CommonTypeDate: "DATE",
|
||||||
|
dbi.CommonTypeTime: "DATE",
|
||||||
|
dbi.CommonTypeTimestamp: "TIMESTAMP",
|
||||||
|
dbi.CommonTypeEnum: "CLOB",
|
||||||
|
dbi.CommonTypeJSON: "CLOB",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
type DataConverter struct {
|
type DataConverter struct {
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func (pd *PgsqlDialect) BatchInsert(tx *sql.Tx, tableName string, columns []stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// pgsql默认唯一键冲突策略
|
// pgsql默认唯一键冲突策略
|
||||||
func (pd PgsqlDialect) pgsqlOnDuplicateStrategySql(duplicateStrategy int, tableName string, columns []string) string {
|
func (pd *PgsqlDialect) pgsqlOnDuplicateStrategySql(duplicateStrategy int, tableName string, columns []string) string {
|
||||||
suffix := ""
|
suffix := ""
|
||||||
if duplicateStrategy == dbi.DuplicateStrategyIgnore {
|
if duplicateStrategy == dbi.DuplicateStrategyIgnore {
|
||||||
suffix = " \n on conflict do nothing"
|
suffix = " \n on conflict do nothing"
|
||||||
@@ -83,7 +83,7 @@ func (pd PgsqlDialect) pgsqlOnDuplicateStrategySql(duplicateStrategy int, tableN
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 高斯db唯一键冲突策略,使用ON DUPLICATE KEY UPDATE 参考:https://support.huaweicloud.com/distributed-devg-v3-gaussdb/gaussdb-12-0607.html#ZH-CN_TOPIC_0000001633948138
|
// 高斯db唯一键冲突策略,使用ON DUPLICATE KEY UPDATE 参考:https://support.huaweicloud.com/distributed-devg-v3-gaussdb/gaussdb-12-0607.html#ZH-CN_TOPIC_0000001633948138
|
||||||
func (pd PgsqlDialect) gaussOnDuplicateStrategySql(duplicateStrategy int, tableName string, columns []string) string {
|
func (pd *PgsqlDialect) gaussOnDuplicateStrategySql(duplicateStrategy int, tableName string, columns []string) string {
|
||||||
suffix := ""
|
suffix := ""
|
||||||
metadata := pd.dc.GetMetaData()
|
metadata := pd.dc.GetMetaData()
|
||||||
if duplicateStrategy == dbi.DuplicateStrategyIgnore {
|
if duplicateStrategy == dbi.DuplicateStrategyIgnore {
|
||||||
@@ -122,17 +122,7 @@ func (pd PgsqlDialect) gaussOnDuplicateStrategySql(duplicateStrategy int, tableN
|
|||||||
|
|
||||||
// 从连接信息中获取数据库和schema信息
|
// 从连接信息中获取数据库和schema信息
|
||||||
func (pd *PgsqlDialect) currentSchema() string {
|
func (pd *PgsqlDialect) currentSchema() string {
|
||||||
dbName := pd.dc.Info.Database
|
return pd.dc.Info.CurrentSchema()
|
||||||
schema := ""
|
|
||||||
arr := strings.Split(dbName, "/")
|
|
||||||
if len(arr) == 2 {
|
|
||||||
schema = arr[1]
|
|
||||||
}
|
|
||||||
return schema
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pd *PgsqlDialect) IsGauss() bool {
|
|
||||||
return strings.Contains(pd.dc.Info.Params, "gauss")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pd *PgsqlDialect) CopyTable(copy *dbi.DbCopyTable) error {
|
func (pd *PgsqlDialect) CopyTable(copy *dbi.DbCopyTable) error {
|
||||||
@@ -192,3 +182,221 @@ func (pd *PgsqlDialect) CopyTable(copy *dbi.DbCopyTable) error {
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pd *PgsqlDialect) TransColumns(columns []dbi.Column) []dbi.Column {
|
||||||
|
var commonColumns []dbi.Column
|
||||||
|
for _, column := range columns {
|
||||||
|
// 取出当前数据库类型
|
||||||
|
arr := strings.Split(column.ColumnType, "(")
|
||||||
|
ctype := arr[0]
|
||||||
|
// 翻译为通用数据库类型
|
||||||
|
t1 := commonColumnTypeMap[ctype]
|
||||||
|
if t1 == "" {
|
||||||
|
ctype = "varchar(2000)"
|
||||||
|
} else {
|
||||||
|
// 回写到列信息
|
||||||
|
if len(arr) > 1 {
|
||||||
|
ctype = t1 + "(" + arr[1]
|
||||||
|
} else {
|
||||||
|
ctype = t1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
column.ColumnType = ctype
|
||||||
|
commonColumns = append(commonColumns, column)
|
||||||
|
}
|
||||||
|
return commonColumns
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pd *PgsqlDialect) CreateTable(commonColumns []dbi.Column, tableInfo dbi.Table, dropOldTable bool) (int, error) {
|
||||||
|
meta := pd.dc.GetMetaData()
|
||||||
|
replacer := strings.NewReplacer(";", "", "'", "")
|
||||||
|
if dropOldTable {
|
||||||
|
_, _ = pd.dc.Exec(fmt.Sprintf("DROP TABLE IF EXISTS %s", tableInfo.TableName))
|
||||||
|
}
|
||||||
|
// 组装建表语句
|
||||||
|
createSql := fmt.Sprintf("CREATE TABLE %s (\n", meta.QuoteIdentifier(tableInfo.TableName))
|
||||||
|
fields := make([]string, 0)
|
||||||
|
pks := make([]string, 0)
|
||||||
|
columnComments := make([]string, 0)
|
||||||
|
// 把通用类型转换为达梦类型
|
||||||
|
for _, column := range commonColumns {
|
||||||
|
// 取出当前数据库类型
|
||||||
|
arr := strings.Split(column.ColumnType, "(")
|
||||||
|
ctype := arr[0]
|
||||||
|
// 翻译为通用数据库类型
|
||||||
|
t1 := pgsqlColumnTypeMap[ctype]
|
||||||
|
if t1 == "" {
|
||||||
|
ctype = "varchar(2000)"
|
||||||
|
} else {
|
||||||
|
// 回写到列信息
|
||||||
|
if len(arr) > 1 {
|
||||||
|
if strings.Contains(strings.ToLower(t1), "int") {
|
||||||
|
// 如果是数字,类型后不需要带长度
|
||||||
|
ctype = t1
|
||||||
|
} else if strings.Contains(strings.ToLower(t1), "char") {
|
||||||
|
// 如果是字符串,长度翻倍
|
||||||
|
match := bracketsRegexp.FindStringSubmatch(column.ColumnType)
|
||||||
|
if len(match) > 1 {
|
||||||
|
ctype = fmt.Sprintf("%s(%d)", t1, anyx.ConvInt(match[1])*2)
|
||||||
|
} else {
|
||||||
|
ctype = t1 + "(1000)"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctype = t1 + "(" + arr[1]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctype = t1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
column.ColumnType = ctype
|
||||||
|
|
||||||
|
if column.IsPrimaryKey {
|
||||||
|
pks = append(pks, meta.QuoteIdentifier(column.ColumnName))
|
||||||
|
}
|
||||||
|
fields = append(fields, pd.genColumnBasicSql(column))
|
||||||
|
commentTmp := "comment on column %s.%s is '%s'"
|
||||||
|
// 防止注释内含有特殊字符串导致sql出错
|
||||||
|
comment := replacer.Replace(column.ColumnComment)
|
||||||
|
columnComments = append(columnComments, fmt.Sprintf(commentTmp, column.TableName, column.ColumnName, comment))
|
||||||
|
}
|
||||||
|
createSql += strings.Join(fields, ",")
|
||||||
|
if len(pks) > 0 {
|
||||||
|
createSql += fmt.Sprintf(", PRIMARY KEY (%s)", strings.Join(pks, ","))
|
||||||
|
}
|
||||||
|
createSql += ")"
|
||||||
|
tableCommentSql := ""
|
||||||
|
if tableInfo.TableComment != "" {
|
||||||
|
commentTmp := "comment on table %s is '%s'"
|
||||||
|
tableCommentSql = fmt.Sprintf(commentTmp, tableInfo.TableName, replacer.Replace(tableInfo.TableComment))
|
||||||
|
}
|
||||||
|
|
||||||
|
columnCommentSql := strings.Join(columnComments, ";")
|
||||||
|
|
||||||
|
sqls := make([]string, 0)
|
||||||
|
|
||||||
|
if createSql != "" {
|
||||||
|
sqls = append(sqls, createSql)
|
||||||
|
}
|
||||||
|
if tableCommentSql != "" {
|
||||||
|
sqls = append(sqls, tableCommentSql)
|
||||||
|
}
|
||||||
|
if columnCommentSql != "" {
|
||||||
|
sqls = append(sqls, columnCommentSql)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := pd.dc.Exec(strings.Join(sqls, ";"))
|
||||||
|
|
||||||
|
return 1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pd *PgsqlDialect) genColumnBasicSql(column dbi.Column) string {
|
||||||
|
meta := pd.dc.GetMetaData()
|
||||||
|
colName := meta.QuoteIdentifier(column.ColumnName)
|
||||||
|
|
||||||
|
// 如果是自增类型,需要转换为serial
|
||||||
|
if column.IsIdentity {
|
||||||
|
if column.ColumnType == "int4" {
|
||||||
|
column.ColumnType = "serial"
|
||||||
|
} else if column.ColumnType == "int2" {
|
||||||
|
column.ColumnType = "smallserial"
|
||||||
|
} else if column.ColumnType == "int8" {
|
||||||
|
column.ColumnType = "bigserial"
|
||||||
|
} else {
|
||||||
|
column.ColumnType = "bigserial"
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(" %s %s NOT NULL", colName, column.ColumnType)
|
||||||
|
}
|
||||||
|
|
||||||
|
nullAble := ""
|
||||||
|
if column.Nullable == "NO" {
|
||||||
|
nullAble = " NOT NULL"
|
||||||
|
// 如果字段不能为空,则设置默认值
|
||||||
|
if column.ColumnDefault == "" {
|
||||||
|
if collx.ArrayAnyMatches([]string{"char", "text", "lob"}, strings.ToLower(column.ColumnType)) {
|
||||||
|
// 文本默认值为空字符串
|
||||||
|
column.ColumnDefault = " "
|
||||||
|
} else if collx.ArrayAnyMatches([]string{"int", "num"}, strings.ToLower(column.ColumnType)) {
|
||||||
|
// 数字默认值为0
|
||||||
|
column.ColumnDefault = "0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defVal := "" // 默认值需要判断引号,如函数是不需要引号的 // 为了防止跨源函数不支持 当默认值是函数时,不需要设置默认值
|
||||||
|
if column.ColumnDefault != "" && !strings.Contains(column.ColumnDefault, "(") {
|
||||||
|
// 哪些字段类型默认值需要加引号
|
||||||
|
mark := false
|
||||||
|
if collx.ArrayAnyMatches([]string{"char", "text", "date", "time", "lob"}, strings.ToLower(column.ColumnType)) {
|
||||||
|
// 如果是文本类型,则默认值不能带括号
|
||||||
|
if collx.ArrayAnyMatches([]string{"char", "text", "lob"}, strings.ToLower(column.ColumnType)) {
|
||||||
|
column.ColumnDefault = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当数据类型是日期时间,默认值是日期时间函数时,默认值不需要引号
|
||||||
|
if collx.ArrayAnyMatches([]string{"date", "time"}, strings.ToLower(column.ColumnType)) &&
|
||||||
|
collx.ArrayAnyMatches([]string{"DATE", "TIME"}, strings.ToUpper(column.ColumnDefault)) {
|
||||||
|
mark = false
|
||||||
|
} else {
|
||||||
|
mark = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果数据类型是日期时间,则写死默认值函数
|
||||||
|
if collx.ArrayAnyMatches([]string{"date", "time"}, strings.ToLower(column.ColumnType)) {
|
||||||
|
column.ColumnDefault = "CURRENT_TIMESTAMP"
|
||||||
|
}
|
||||||
|
|
||||||
|
if column.ColumnDefault != "" {
|
||||||
|
if mark {
|
||||||
|
defVal = fmt.Sprintf(" DEFAULT '%s'", column.ColumnDefault)
|
||||||
|
} else {
|
||||||
|
defVal = fmt.Sprintf(" DEFAULT %s", column.ColumnDefault)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
columnSql := fmt.Sprintf(" %s %s %s %s ", colName, column.ColumnType, nullAble, defVal)
|
||||||
|
return columnSql
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pd *PgsqlDialect) CreateIndex(tableInfo dbi.Table, indexs []dbi.Index) error {
|
||||||
|
sqls := make([]string, 0)
|
||||||
|
comments := make([]string, 0)
|
||||||
|
for _, index := range indexs {
|
||||||
|
// 通过字段、表名拼接索引名
|
||||||
|
columnName := strings.ReplaceAll(index.ColumnName, "-", "")
|
||||||
|
columnName = strings.ReplaceAll(columnName, "_", "")
|
||||||
|
colName := strings.ReplaceAll(columnName, ",", "_")
|
||||||
|
|
||||||
|
keyType := "normal"
|
||||||
|
unique := ""
|
||||||
|
if index.IsUnique {
|
||||||
|
keyType = "unique"
|
||||||
|
unique = "unique"
|
||||||
|
}
|
||||||
|
indexName := fmt.Sprintf("%s_key_%s_%s", keyType, tableInfo.TableName, colName)
|
||||||
|
|
||||||
|
// 如果索引名存在,先删除索引
|
||||||
|
sqls = append(sqls, fmt.Sprintf("drop index if exists %s.%s", pd.currentSchema(), indexName))
|
||||||
|
|
||||||
|
// 创建索引
|
||||||
|
sqls = append(sqls, fmt.Sprintf("CREATE %s INDEX %s on %s.%s(%s)", unique, indexName, pd.currentSchema(), tableInfo.TableName, index.ColumnName))
|
||||||
|
if index.IndexComment != "" {
|
||||||
|
comments = append(comments, fmt.Sprintf("COMMENT ON INDEX %s.%s IS '%s'", pd.currentSchema(), indexName, index.IndexComment))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err := pd.dc.Exec(strings.Join(sqls, ";"))
|
||||||
|
// 添加注释
|
||||||
|
if len(comments) > 0 {
|
||||||
|
_, err = pd.dc.Exec(strings.Join(comments, ";"))
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pd *PgsqlDialect) UpdateSequence(tableName string, columns []dbi.Column) {
|
||||||
|
for _, column := range columns {
|
||||||
|
if column.IsIdentity {
|
||||||
|
_, _ = pd.dc.Exec(fmt.Sprintf("select setval('%s_%s_seq', (SELECT max(%s) from %s))", tableName, column.ColumnName, column.ColumnName, tableName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,12 +12,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
PGSQL_META_FILE = "metasql/pgsql_meta.sql"
|
PGSQL_META_FILE = "metasql/pgsql_meta.sql"
|
||||||
PGSQL_DB_SCHEMAS = "PGSQL_DB_SCHEMAS"
|
PGSQL_DB_SCHEMAS = "PGSQL_DB_SCHEMAS"
|
||||||
PGSQL_TABLE_INFO_KEY = "PGSQL_TABLE_INFO"
|
PGSQL_TABLE_INFO_KEY = "PGSQL_TABLE_INFO"
|
||||||
PGSQL_INDEX_INFO_KEY = "PGSQL_INDEX_INFO"
|
PGSQL_INDEX_INFO_KEY = "PGSQL_INDEX_INFO"
|
||||||
PGSQL_COLUMN_MA_KEY = "PGSQL_COLUMN_MA"
|
PGSQL_COLUMN_MA_KEY = "PGSQL_COLUMN_MA"
|
||||||
PGSQL_TABLE_DDL_KEY = "PGSQL_TABLE_DDL_FUNC"
|
PGSQL_TABLE_DDL_KEY = "PGSQL_TABLE_DDL_FUNC"
|
||||||
|
PGSQL_TABLE_INFO_BY_NAMES_KEY = "PGSQL_TABLE_INFO_BY_NAMES"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PgsqlMetaData struct {
|
type PgsqlMetaData struct {
|
||||||
@@ -51,13 +52,23 @@ func (pd *PgsqlMetaData) GetDbNames() ([]string, error) {
|
|||||||
return databases, nil
|
return databases, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取表基础元信息, 如表名等
|
func (pd *PgsqlMetaData) GetTables(tableNames ...string) ([]dbi.Table, error) {
|
||||||
func (pd *PgsqlMetaData) GetTables() ([]dbi.Table, error) {
|
meta := pd.dc.GetMetaData()
|
||||||
_, res, err := pd.dc.Query(dbi.GetLocalSql(PGSQL_META_FILE, PGSQL_TABLE_INFO_KEY))
|
names := strings.Join(collx.ArrayMap[string, string](tableNames, func(val string) string {
|
||||||
|
return fmt.Sprintf("'%s'", meta.RemoveQuote(val))
|
||||||
|
}), ",")
|
||||||
|
|
||||||
|
var res []map[string]any
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if tableNames != nil || len(tableNames) > 0 {
|
||||||
|
_, res, err = pd.dc.Query(fmt.Sprintf(dbi.GetLocalSql(PGSQL_META_FILE, PGSQL_TABLE_INFO_BY_NAMES_KEY), names))
|
||||||
|
} else {
|
||||||
|
_, res, err = pd.dc.Query(dbi.GetLocalSql(PGSQL_META_FILE, PGSQL_TABLE_INFO_KEY))
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
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{
|
||||||
@@ -74,8 +85,9 @@ func (pd *PgsqlMetaData) GetTables() ([]dbi.Table, error) {
|
|||||||
|
|
||||||
// 获取列元信息, 如列名等
|
// 获取列元信息, 如列名等
|
||||||
func (pd *PgsqlMetaData) GetColumns(tableNames ...string) ([]dbi.Column, error) {
|
func (pd *PgsqlMetaData) GetColumns(tableNames ...string) ([]dbi.Column, error) {
|
||||||
|
meta := pd.dc.GetMetaData()
|
||||||
tableName := strings.Join(collx.ArrayMap[string, string](tableNames, func(val string) string {
|
tableName := strings.Join(collx.ArrayMap[string, string](tableNames, func(val string) string {
|
||||||
return fmt.Sprintf("'%s'", dbi.RemoveQuote(pd, val))
|
return fmt.Sprintf("'%s'", meta.RemoveQuote(val))
|
||||||
}), ",")
|
}), ",")
|
||||||
|
|
||||||
_, res, err := pd.dc.Query(fmt.Sprintf(dbi.GetLocalSql(PGSQL_META_FILE, PGSQL_COLUMN_MA_KEY), tableName))
|
_, res, err := pd.dc.Query(fmt.Sprintf(dbi.GetLocalSql(PGSQL_META_FILE, PGSQL_COLUMN_MA_KEY), tableName))
|
||||||
@@ -210,8 +222,58 @@ var (
|
|||||||
dateRegexp = regexp.MustCompile(`(?i)date`)
|
dateRegexp = regexp.MustCompile(`(?i)date`)
|
||||||
// 时间类型
|
// 时间类型
|
||||||
timeRegexp = regexp.MustCompile(`(?i)time`)
|
timeRegexp = regexp.MustCompile(`(?i)time`)
|
||||||
|
// 定义正则表达式,匹配括号内的数字
|
||||||
|
bracketsRegexp = regexp.MustCompile(`\((\d+)\)`)
|
||||||
|
|
||||||
converter = new(DataConverter)
|
converter = new(DataConverter)
|
||||||
|
|
||||||
|
// pgsql数据类型 映射 公共数据类型
|
||||||
|
commonColumnTypeMap = map[string]string{
|
||||||
|
"int2": dbi.CommonTypeSmallint,
|
||||||
|
"int4": dbi.CommonTypeInt,
|
||||||
|
"int8": dbi.CommonTypeBigint,
|
||||||
|
"numeric": dbi.CommonTypeNumber,
|
||||||
|
"decimal": dbi.CommonTypeNumber,
|
||||||
|
"smallserial": dbi.CommonTypeSmallint,
|
||||||
|
"serial": dbi.CommonTypeInt,
|
||||||
|
"bigserial": dbi.CommonTypeBigint,
|
||||||
|
"largeserial": dbi.CommonTypeBigint,
|
||||||
|
"money": dbi.CommonTypeNumber,
|
||||||
|
"bool": dbi.CommonTypeTinyint,
|
||||||
|
"char": dbi.CommonTypeChar,
|
||||||
|
"character": dbi.CommonTypeChar,
|
||||||
|
"nchar": dbi.CommonTypeChar,
|
||||||
|
"varchar": dbi.CommonTypeVarchar,
|
||||||
|
"text": dbi.CommonTypeText,
|
||||||
|
"bytea": dbi.CommonTypeBinary,
|
||||||
|
"date": dbi.CommonTypeDate,
|
||||||
|
"time": dbi.CommonTypeTime,
|
||||||
|
"timestamp": dbi.CommonTypeTimestamp,
|
||||||
|
}
|
||||||
|
// 公共数据类型 映射 pgsql数据类型
|
||||||
|
pgsqlColumnTypeMap = map[string]string{
|
||||||
|
dbi.CommonTypeVarchar: "varchar",
|
||||||
|
dbi.CommonTypeChar: "char",
|
||||||
|
dbi.CommonTypeText: "text",
|
||||||
|
dbi.CommonTypeBlob: "text",
|
||||||
|
dbi.CommonTypeLongblob: "text",
|
||||||
|
dbi.CommonTypeLongtext: "text",
|
||||||
|
dbi.CommonTypeBinary: "bytea",
|
||||||
|
dbi.CommonTypeMediumblob: "text",
|
||||||
|
dbi.CommonTypeMediumtext: "text",
|
||||||
|
dbi.CommonTypeVarbinary: "bytea",
|
||||||
|
dbi.CommonTypeInt: "int4",
|
||||||
|
dbi.CommonTypeSmallint: "int2",
|
||||||
|
dbi.CommonTypeTinyint: "int2",
|
||||||
|
dbi.CommonTypeNumber: "numeric",
|
||||||
|
dbi.CommonTypeBigint: "int8",
|
||||||
|
dbi.CommonTypeDatetime: "timestamp",
|
||||||
|
dbi.CommonTypeDate: "date",
|
||||||
|
dbi.CommonTypeTime: "time",
|
||||||
|
dbi.CommonTypeTimestamp: "timestamp",
|
||||||
|
dbi.CommonTypeEnum: "varchar(2000)",
|
||||||
|
dbi.CommonTypeJSON: "varchar(2000)",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
type DataConverter struct {
|
type DataConverter struct {
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"mayfly-go/internal/db/dbm/dbi"
|
"mayfly-go/internal/db/dbm/dbi"
|
||||||
|
"mayfly-go/pkg/logx"
|
||||||
|
"mayfly-go/pkg/utils/collx"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -18,6 +20,7 @@ func (sd *SqliteDialect) GetDbProgram() (dbi.DbProgram, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sd *SqliteDialect) BatchInsert(tx *sql.Tx, tableName string, columns []string, values [][]any, duplicateStrategy int) (int64, error) {
|
func (sd *SqliteDialect) BatchInsert(tx *sql.Tx, tableName string, columns []string, values [][]any, duplicateStrategy int) (int64, error) {
|
||||||
|
_, _ = sd.dc.Exec("PRAGMA foreign_keys = false")
|
||||||
// 执行批量insert sql,跟mysql一样 支持批量insert语法
|
// 执行批量insert sql,跟mysql一样 支持批量insert语法
|
||||||
// 生成占位符字符串:如:(?,?)
|
// 生成占位符字符串:如:(?,?)
|
||||||
// 重复字符串并用逗号连接
|
// 重复字符串并用逗号连接
|
||||||
@@ -44,9 +47,16 @@ func (sd *SqliteDialect) BatchInsert(tx *sql.Tx, tableName string, columns []str
|
|||||||
for _, v := range values {
|
for _, v := range values {
|
||||||
args = append(args, v...)
|
args = append(args, v...)
|
||||||
}
|
}
|
||||||
|
exec, err := sd.dc.TxExec(tx, sqlStr, args...)
|
||||||
|
|
||||||
|
_, _ = sd.dc.Exec("PRAGMA foreign_keys = true;")
|
||||||
|
|
||||||
// 执行批量insert sql
|
// 执行批量insert sql
|
||||||
return sd.dc.TxExec(tx, sqlStr, args...)
|
return exec, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sd *SqliteDialect) GetDataConverter() dbi.DataConverter {
|
||||||
|
return converter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sd *SqliteDialect) CopyTable(copy *dbi.DbCopyTable) error {
|
func (sd *SqliteDialect) CopyTable(copy *dbi.DbCopyTable) error {
|
||||||
@@ -80,3 +90,130 @@ func (sd *SqliteDialect) CopyTable(copy *dbi.DbCopyTable) error {
|
|||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sd *SqliteDialect) TransColumns(columns []dbi.Column) []dbi.Column {
|
||||||
|
var commonColumns []dbi.Column
|
||||||
|
for _, column := range columns {
|
||||||
|
// 取出当前数据库类型
|
||||||
|
arr := strings.Split(column.ColumnType, "(")
|
||||||
|
ctype := arr[0]
|
||||||
|
// 翻译为通用数据库类型
|
||||||
|
t1 := commonColumnTypeMap[ctype]
|
||||||
|
if t1 == "" {
|
||||||
|
ctype = "varchar(2000)"
|
||||||
|
} else {
|
||||||
|
// 回写到列信息
|
||||||
|
if len(arr) > 1 {
|
||||||
|
ctype = t1 + "(" + arr[1]
|
||||||
|
} else {
|
||||||
|
ctype = t1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
column.ColumnType = ctype
|
||||||
|
commonColumns = append(commonColumns, column)
|
||||||
|
}
|
||||||
|
return commonColumns
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sd *SqliteDialect) CreateTable(commonColumns []dbi.Column, tableInfo dbi.Table, dropOldTable bool) (int, error) {
|
||||||
|
tbName := sd.dc.GetMetaData().QuoteIdentifier(tableInfo.TableName)
|
||||||
|
if dropOldTable {
|
||||||
|
_, err := sd.dc.Exec(fmt.Sprintf("DROP TABLE IF EXISTS %s", tbName))
|
||||||
|
if err != nil {
|
||||||
|
logx.Error("删除表失败", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组装建表语句
|
||||||
|
createSql := fmt.Sprintf("CREATE TABLE %s (\n", tbName)
|
||||||
|
fields := make([]string, 0)
|
||||||
|
// 把通用类型转换为达梦类型
|
||||||
|
for _, column := range commonColumns {
|
||||||
|
// 取出当前数据库类型
|
||||||
|
arr := strings.Split(column.ColumnType, "(")
|
||||||
|
ctype := arr[0]
|
||||||
|
// 翻译为通用数据库类型
|
||||||
|
t1 := sqliteColumnTypeMap[ctype]
|
||||||
|
if t1 == "" {
|
||||||
|
ctype = "nvarchar(2000)"
|
||||||
|
} else {
|
||||||
|
// 回写到列信息
|
||||||
|
if len(arr) > 1 {
|
||||||
|
ctype = t1 + "(" + arr[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
column.ColumnType = ctype
|
||||||
|
fields = append(fields, sd.genColumnBasicSql(column))
|
||||||
|
}
|
||||||
|
createSql += strings.Join(fields, ",")
|
||||||
|
createSql += fmt.Sprintf(") ")
|
||||||
|
_, err := sd.dc.Exec(createSql)
|
||||||
|
|
||||||
|
return 1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sd *SqliteDialect) CreateIndex(tableInfo dbi.Table, indexs []dbi.Index) error {
|
||||||
|
sqls := make([]string, 0)
|
||||||
|
for _, index := range indexs {
|
||||||
|
// 通过字段、表名拼接索引名
|
||||||
|
columnName := strings.ReplaceAll(index.ColumnName, "-", "")
|
||||||
|
columnName = strings.ReplaceAll(columnName, "_", "")
|
||||||
|
colName := strings.ReplaceAll(columnName, ",", "_")
|
||||||
|
|
||||||
|
keyType := "normal"
|
||||||
|
unique := ""
|
||||||
|
if index.IsUnique {
|
||||||
|
keyType = "unique"
|
||||||
|
unique = "unique"
|
||||||
|
}
|
||||||
|
indexName := fmt.Sprintf("%s_key_%s_%s", keyType, tableInfo.TableName, colName)
|
||||||
|
sqlTmp := "CREATE %s INDEX %s ON \"%s\" (%s) "
|
||||||
|
sqls = append(sqls, fmt.Sprintf(sqlTmp, unique, indexName, tableInfo.TableName, index.ColumnName))
|
||||||
|
}
|
||||||
|
_, err := sd.dc.Exec(strings.Join(sqls, ";"))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sd *SqliteDialect) genColumnBasicSql(column dbi.Column) string {
|
||||||
|
|
||||||
|
incr := ""
|
||||||
|
if column.IsIdentity {
|
||||||
|
incr = " AUTOINCREMENT"
|
||||||
|
}
|
||||||
|
|
||||||
|
nullAble := ""
|
||||||
|
if column.Nullable == "NO" {
|
||||||
|
nullAble = " NOT NULL"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是主键,则直接返回,不判断默认值
|
||||||
|
if column.IsPrimaryKey {
|
||||||
|
return fmt.Sprintf(" %s integer PRIMARY KEY %s %s", column.ColumnName, incr, nullAble)
|
||||||
|
}
|
||||||
|
|
||||||
|
defVal := "" // 默认值需要判断引号,如函数是不需要引号的 // 为了防止跨源函数不支持 当默认值是函数时,不需要设置默认值
|
||||||
|
if column.ColumnDefault != "" && !strings.Contains(column.ColumnDefault, "(") {
|
||||||
|
// 哪些字段类型默认值需要加引号
|
||||||
|
mark := false
|
||||||
|
if collx.ArrayAnyMatches([]string{"char", "text", "date", "time", "lob"}, strings.ToLower(column.ColumnType)) {
|
||||||
|
// 当数据类型是日期时间,默认值是日期时间函数时,默认值不需要引号
|
||||||
|
if collx.ArrayAnyMatches([]string{"date", "time"}, strings.ToLower(column.ColumnType)) &&
|
||||||
|
collx.ArrayAnyMatches([]string{"DATE", "TIME"}, strings.ToUpper(column.ColumnDefault)) {
|
||||||
|
mark = false
|
||||||
|
} else {
|
||||||
|
mark = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mark {
|
||||||
|
defVal = fmt.Sprintf(" DEFAULT '%s'", column.ColumnDefault)
|
||||||
|
} else {
|
||||||
|
defVal = fmt.Sprintf(" DEFAULT %s", column.ColumnDefault)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(" %s %s %s %s", sd.dc.GetMetaData().QuoteIdentifier(column.ColumnName), column.ColumnType, nullAble, defVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sd *SqliteDialect) UpdateSequence(tableName string, columns []dbi.Column) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,7 +20,13 @@ func (md *SqliteMeta) GetSqlDb(d *dbi.DbInfo) (*sql.DB, error) {
|
|||||||
if _, err := os.Stat(d.Host); err != nil {
|
if _, err := os.Stat(d.Host); err != nil {
|
||||||
return nil, errors.New("数据库文件不存在")
|
return nil, errors.New("数据库文件不存在")
|
||||||
}
|
}
|
||||||
return sql.Open("sqlite", d.Host)
|
|
||||||
|
db, err := sql.Open("sqlite", d.Host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = db.Exec("PRAGMA busy_timeout = 50000;")
|
||||||
|
return db, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sm *SqliteMeta) GetDialect(conn *dbi.DbConn) dbi.Dialect {
|
func (sm *SqliteMeta) GetDialect(conn *dbi.DbConn) dbi.Dialect {
|
||||||
|
|||||||
@@ -6,15 +6,17 @@ import (
|
|||||||
"mayfly-go/internal/db/dbm/dbi"
|
"mayfly-go/internal/db/dbm/dbi"
|
||||||
"mayfly-go/pkg/logx"
|
"mayfly-go/pkg/logx"
|
||||||
"mayfly-go/pkg/utils/anyx"
|
"mayfly-go/pkg/utils/anyx"
|
||||||
|
"mayfly-go/pkg/utils/collx"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SQLITE_META_FILE = "metasql/sqlite_meta.sql"
|
SQLITE_META_FILE = "metasql/sqlite_meta.sql"
|
||||||
SQLITE_TABLE_INFO_KEY = "SQLITE_TABLE_INFO"
|
SQLITE_TABLE_INFO_KEY = "SQLITE_TABLE_INFO"
|
||||||
SQLITE_INDEX_INFO_KEY = "SQLITE_INDEX_INFO"
|
SQLITE_INDEX_INFO_KEY = "SQLITE_INDEX_INFO"
|
||||||
|
SQLITE_TABLE_INFO_BY_NAMES_KEY = "SQLITE_TABLE_INFO_BY_NAMES"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SqliteMetaData struct {
|
type SqliteMetaData struct {
|
||||||
@@ -48,13 +50,22 @@ func (sd *SqliteMetaData) GetDbNames() ([]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取表基础元信息, 如表名等
|
// 获取表基础元信息, 如表名等
|
||||||
func (sd *SqliteMetaData) GetTables() ([]dbi.Table, error) {
|
func (sd *SqliteMetaData) GetTables(tableNames ...string) ([]dbi.Table, error) {
|
||||||
_, res, err := sd.dc.Query(dbi.GetLocalSql(SQLITE_META_FILE, SQLITE_TABLE_INFO_KEY))
|
names := strings.Join(collx.ArrayMap[string, string](tableNames, func(val string) string {
|
||||||
//cols, res, err := sd.dc.Query("SELECT datetime(1092941466, 'unixepoch')")
|
return fmt.Sprintf("'%s'", dbi.RemoveQuote(sd, val))
|
||||||
|
}), ",")
|
||||||
|
|
||||||
|
var res []map[string]any
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if tableNames != nil || len(tableNames) > 0 {
|
||||||
|
_, res, err = sd.dc.Query(fmt.Sprintf(dbi.GetLocalSql(SQLITE_META_FILE, SQLITE_TABLE_INFO_BY_NAMES_KEY), names))
|
||||||
|
} else {
|
||||||
|
_, res, err = sd.dc.Query(dbi.GetLocalSql(SQLITE_META_FILE, SQLITE_TABLE_INFO_KEY))
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
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{
|
||||||
@@ -191,6 +202,61 @@ var (
|
|||||||
datetimeRegexp = regexp.MustCompile(`(?i)datetime`)
|
datetimeRegexp = regexp.MustCompile(`(?i)datetime`)
|
||||||
|
|
||||||
converter = new(DataConverter)
|
converter = new(DataConverter)
|
||||||
|
|
||||||
|
// sqlite数据类型 映射 公共数据类型
|
||||||
|
commonColumnTypeMap = map[string]string{
|
||||||
|
"int": dbi.CommonTypeInt,
|
||||||
|
"integer": dbi.CommonTypeInt,
|
||||||
|
"tinyint": dbi.CommonTypeTinyint,
|
||||||
|
"smallint": dbi.CommonTypeSmallint,
|
||||||
|
"mediumint": dbi.CommonTypeSmallint,
|
||||||
|
"bigint": dbi.CommonTypeBigint,
|
||||||
|
"int2": dbi.CommonTypeInt,
|
||||||
|
"int8": dbi.CommonTypeInt,
|
||||||
|
"character": dbi.CommonTypeChar,
|
||||||
|
"varchar": dbi.CommonTypeVarchar,
|
||||||
|
"varying character": dbi.CommonTypeVarchar,
|
||||||
|
"nchar": dbi.CommonTypeChar,
|
||||||
|
"native character": dbi.CommonTypeVarchar,
|
||||||
|
"nvarchar": dbi.CommonTypeVarchar,
|
||||||
|
"text": dbi.CommonTypeText,
|
||||||
|
"clob": dbi.CommonTypeBlob,
|
||||||
|
"blob": dbi.CommonTypeBlob,
|
||||||
|
"real": dbi.CommonTypeNumber,
|
||||||
|
"double": dbi.CommonTypeNumber,
|
||||||
|
"double precision": dbi.CommonTypeNumber,
|
||||||
|
"float": dbi.CommonTypeNumber,
|
||||||
|
"numeric": dbi.CommonTypeNumber,
|
||||||
|
"decimal": dbi.CommonTypeNumber,
|
||||||
|
"boolean": dbi.CommonTypeTinyint,
|
||||||
|
"date": dbi.CommonTypeDate,
|
||||||
|
"datetime": dbi.CommonTypeDatetime,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 公共数据类型 映射 sqlite数据类型
|
||||||
|
sqliteColumnTypeMap = map[string]string{
|
||||||
|
dbi.CommonTypeVarchar: "nvarchar",
|
||||||
|
dbi.CommonTypeChar: "nchar",
|
||||||
|
dbi.CommonTypeText: "text",
|
||||||
|
dbi.CommonTypeBlob: "blob",
|
||||||
|
dbi.CommonTypeLongblob: "blob",
|
||||||
|
dbi.CommonTypeLongtext: "text",
|
||||||
|
dbi.CommonTypeBinary: "text",
|
||||||
|
dbi.CommonTypeMediumblob: "blob",
|
||||||
|
dbi.CommonTypeMediumtext: "text",
|
||||||
|
dbi.CommonTypeVarbinary: "text",
|
||||||
|
dbi.CommonTypeInt: "int",
|
||||||
|
dbi.CommonTypeSmallint: "smallint",
|
||||||
|
dbi.CommonTypeTinyint: "tinyint",
|
||||||
|
dbi.CommonTypeNumber: "number",
|
||||||
|
dbi.CommonTypeBigint: "bigint",
|
||||||
|
dbi.CommonTypeDatetime: "datetime",
|
||||||
|
dbi.CommonTypeDate: "date",
|
||||||
|
dbi.CommonTypeTime: "datetime",
|
||||||
|
dbi.CommonTypeTimestamp: "datetime",
|
||||||
|
dbi.CommonTypeEnum: "nvarchar(2000)",
|
||||||
|
dbi.CommonTypeJSON: "nvarchar(2000)",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
type DataConverter struct {
|
type DataConverter struct {
|
||||||
|
|||||||
60
server/internal/db/domain/entity/db_transfer.go
Normal file
60
server/internal/db/domain/entity/db_transfer.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"mayfly-go/pkg/model"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DbTransferTask struct {
|
||||||
|
model.Model
|
||||||
|
|
||||||
|
RunningState int `orm:"column(running_state)" json:"runningState"` // 运行状态 1运行中 2待运行
|
||||||
|
|
||||||
|
CheckedKeys string `orm:"column(checked_keys)" json:"checkedKeys"` // 选中需要迁移的表
|
||||||
|
DeleteTable int `orm:"column(delete_table)" json:"deleteTable"` // 创建表前是否删除表
|
||||||
|
NameCase int `orm:"column(name_case)" json:"nameCase"` // 表名、字段大小写转换 1无 2大写 3小写
|
||||||
|
Strategy int `orm:"column(strategy)" json:"strategy"` // 迁移策略 1全量 2增量
|
||||||
|
|
||||||
|
SrcDbId int64 `orm:"column(src_db_id)" json:"srcDbId"` // 源库id
|
||||||
|
SrcDbName string `orm:"column(src_db_name)" json:"srcDbName"` // 源库名
|
||||||
|
SrcTagPath string `orm:"column(src_tag_path)" json:"srcTagPath"` // 源库tagPath
|
||||||
|
SrcDbType string `orm:"column(src_db_type)" json:"srcDbType"` // 源库类型
|
||||||
|
SrcInstName string `orm:"column(src_inst_name)" json:"srcInstName"` // 源库实例名
|
||||||
|
|
||||||
|
TargetDbId int `orm:"column(target_db_id)" json:"targetDbId"` // 目标库id
|
||||||
|
TargetDbName string `orm:"column(target_db_name)" json:"targetDbName"` // 目标库名
|
||||||
|
TargetDbType string `orm:"column(target_tag_path)" json:"targetDbType"` // 目标库类型
|
||||||
|
TargetInstName string `orm:"column(target_db_type)" json:"targetInstName"` // 目标库实例名
|
||||||
|
TargetTagPath string `orm:"column(target_inst_name)" json:"targetTagPath"` // 目标库tagPath
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DbTransferTask) TableName() string {
|
||||||
|
return "t_db_transfer_task"
|
||||||
|
}
|
||||||
|
|
||||||
|
type DbTransferLog struct {
|
||||||
|
model.IdModel
|
||||||
|
TaskId uint64 `orm:"column(task_id)" json:"taskId"` // 任务表id
|
||||||
|
CreateTime *time.Time `orm:"column(create_time)" json:"createTime"`
|
||||||
|
DataSqlFull string `orm:"column(data_sql_full)" json:"dataSqlFull"` // 执行的完整sql
|
||||||
|
ResNum int `orm:"column(res_num)" json:"resNum"` // 收到数据条数
|
||||||
|
ErrText string `orm:"column(err_text)" json:"errText"` // 错误日志
|
||||||
|
Status int8 `orm:"column(status)" json:"status"` // 状态:1.成功 -1.失败
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DbTransferLog) TableName() string {
|
||||||
|
return "t_db_transfer_log"
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
DbTransferTaskStatusEnable int = 1 // 启用状态
|
||||||
|
DbTransferTaskStatusDisable int = -1 // 禁用状态
|
||||||
|
|
||||||
|
DbTransferTaskStateSuccess int = 1 // 执行成功状态
|
||||||
|
DbTransferTaskStateRunning int = 2 // 执行成功状态
|
||||||
|
DbTransferTaskStateFail int = -1 // 执行失败状态
|
||||||
|
|
||||||
|
DbTransferTaskRunStateRunning int = 1 // 运行中状态
|
||||||
|
DbTransferTaskRunStateStop int = 2 // 手动停止状态
|
||||||
|
)
|
||||||
@@ -15,6 +15,13 @@ type DataSyncLogQuery struct {
|
|||||||
TaskId uint64 `json:"task_id" form:"taskId"`
|
TaskId uint64 `json:"task_id" form:"taskId"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DbTransferTaskQuery struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type DbTransferLogQuery struct {
|
||||||
|
TaskId uint64 `json:"task_id" form:"taskId"`
|
||||||
|
}
|
||||||
|
|
||||||
// 数据库查询实体,不与数据库表字段一一对应
|
// 数据库查询实体,不与数据库表字段一一对应
|
||||||
type DbQuery struct {
|
type DbQuery struct {
|
||||||
Id uint64 `form:"id"`
|
Id uint64 `form:"id"`
|
||||||
|
|||||||
21
server/internal/db/domain/repository/db_transfer.go
Normal file
21
server/internal/db/domain/repository/db_transfer.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"mayfly-go/internal/db/domain/entity"
|
||||||
|
"mayfly-go/pkg/base"
|
||||||
|
"mayfly-go/pkg/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DbTransferTask interface {
|
||||||
|
base.Repo[*entity.DbTransferTask]
|
||||||
|
|
||||||
|
// 分页获取数据库实例信息列表
|
||||||
|
GetTaskList(condition *entity.DbTransferTaskQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DbTransferLog interface {
|
||||||
|
base.Repo[*entity.DbTransferLog]
|
||||||
|
|
||||||
|
// 分页获取数据库实例信息列表
|
||||||
|
GetTaskLogList(condition *entity.DbTransferLogQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||||
|
}
|
||||||
40
server/internal/db/infrastructure/persistence/db_transfer.go
Normal file
40
server/internal/db/infrastructure/persistence/db_transfer.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package persistence
|
||||||
|
|
||||||
|
import (
|
||||||
|
"mayfly-go/internal/db/domain/entity"
|
||||||
|
"mayfly-go/internal/db/domain/repository"
|
||||||
|
"mayfly-go/pkg/base"
|
||||||
|
"mayfly-go/pkg/gormx"
|
||||||
|
"mayfly-go/pkg/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type dbTransferTaskRepoImpl struct {
|
||||||
|
base.RepoImpl[*entity.DbTransferTask]
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDbTransferTaskRepo() repository.DbTransferTask {
|
||||||
|
return &dbTransferTaskRepoImpl{base.RepoImpl[*entity.DbTransferTask]{M: new(entity.DbTransferTask)}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页获取数据库信息列表
|
||||||
|
func (d *dbTransferTaskRepoImpl) GetTaskList(condition *entity.DbTransferTaskQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||||
|
qd := gormx.NewQuery(new(entity.DbTransferTask))
|
||||||
|
//Like("task_name", condition.Name).
|
||||||
|
//Eq("status", condition.Status)
|
||||||
|
return gormx.PageQuery(qd, pageParam, toEntity)
|
||||||
|
}
|
||||||
|
|
||||||
|
type dbTransferLogRepoImpl struct {
|
||||||
|
base.RepoImpl[*entity.DbTransferLog]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页获取数据库信息列表
|
||||||
|
func (d *dbTransferLogRepoImpl) GetTaskLogList(condition *entity.DbTransferLogQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||||
|
qd := gormx.NewQuery(new(entity.DbTransferLog)).
|
||||||
|
Eq("task_id", condition.TaskId)
|
||||||
|
return gormx.PageQuery(qd, pageParam, toEntity)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDbTransferLogRepo() repository.DbTransferLog {
|
||||||
|
return &dbTransferLogRepoImpl{base.RepoImpl[*entity.DbTransferLog]{M: new(entity.DbTransferLog)}}
|
||||||
|
}
|
||||||
@@ -11,6 +11,8 @@ func InitIoc() {
|
|||||||
ioc.Register(newDbSqlExecRepo(), ioc.WithComponentName("DbSqlExecRepo"))
|
ioc.Register(newDbSqlExecRepo(), ioc.WithComponentName("DbSqlExecRepo"))
|
||||||
ioc.Register(newDataSyncTaskRepo(), ioc.WithComponentName("DbDataSyncTaskRepo"))
|
ioc.Register(newDataSyncTaskRepo(), ioc.WithComponentName("DbDataSyncTaskRepo"))
|
||||||
ioc.Register(newDataSyncLogRepo(), ioc.WithComponentName("DbDataSyncLogRepo"))
|
ioc.Register(newDataSyncLogRepo(), ioc.WithComponentName("DbDataSyncLogRepo"))
|
||||||
|
ioc.Register(newDbTransferTaskRepo(), ioc.WithComponentName("DbTransferTaskRepo"))
|
||||||
|
ioc.Register(newDbTransferLogRepo(), ioc.WithComponentName("DbTransferLogRepo"))
|
||||||
|
|
||||||
ioc.Register(NewDbBackupRepo(), ioc.WithComponentName("DbBackupRepo"))
|
ioc.Register(NewDbBackupRepo(), ioc.WithComponentName("DbBackupRepo"))
|
||||||
ioc.Register(NewDbBackupHistoryRepo(), ioc.WithComponentName("DbBackupHistoryRepo"))
|
ioc.Register(NewDbBackupHistoryRepo(), ioc.WithComponentName("DbBackupHistoryRepo"))
|
||||||
|
|||||||
38
server/internal/db/router/db_transfer.go
Normal file
38
server/internal/db/router/db_transfer.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"mayfly-go/internal/db/api"
|
||||||
|
"mayfly-go/pkg/biz"
|
||||||
|
"mayfly-go/pkg/ioc"
|
||||||
|
"mayfly-go/pkg/req"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func InitDbTransferRouter(router *gin.RouterGroup) {
|
||||||
|
instances := router.Group("/dbTransfer")
|
||||||
|
|
||||||
|
d := new(api.DbTransferTask)
|
||||||
|
biz.ErrIsNil(ioc.Inject(d))
|
||||||
|
|
||||||
|
reqs := [...]*req.Conf{
|
||||||
|
// 获取任务列表 /datasync
|
||||||
|
req.NewGet("", d.Tasks),
|
||||||
|
|
||||||
|
req.NewGet(":taskId/logs", d.Logs).RequiredPermissionCode("db:transfer:log"),
|
||||||
|
|
||||||
|
// 保存任务 /datasync/save
|
||||||
|
req.NewPost("save", d.SaveTask).Log(req.NewLogSave("datasync-保存数据迁移任务信息")).RequiredPermissionCode("db:transfer:save"),
|
||||||
|
|
||||||
|
// 删除任务 /datasync/:taskId/del
|
||||||
|
req.NewDelete(":taskId/del", d.DeleteTask).Log(req.NewLogSave("datasync-删除数据迁移任务信息")).RequiredPermissionCode("db:transfer:del"),
|
||||||
|
|
||||||
|
// 立即执行任务 /datasync/run
|
||||||
|
req.NewPost(":taskId/run", d.Run).Log(req.NewLogSave("datasync-运行数据迁移任务")).RequiredPermissionCode("db:transfer:run"),
|
||||||
|
|
||||||
|
// 停止正在执行中的任务
|
||||||
|
req.NewPost(":taskId/stop", d.Stop),
|
||||||
|
}
|
||||||
|
|
||||||
|
req.BatchSetGroup(instances, reqs[:])
|
||||||
|
}
|
||||||
@@ -10,4 +10,5 @@ func Init(router *gin.RouterGroup) {
|
|||||||
InitDbBackupRouter(router)
|
InitDbBackupRouter(router)
|
||||||
InitDbRestoreRouter(router)
|
InitDbRestoreRouter(router)
|
||||||
InitDbDataSyncRouter(router)
|
InitDbDataSyncRouter(router)
|
||||||
|
InitDbTransferRouter(router)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package collx
|
package collx
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
// 数组比较
|
// 数组比较
|
||||||
// 依次返回,新增值,删除值,以及不变值
|
// 依次返回,新增值,删除值,以及不变值
|
||||||
func ArrayCompare[T comparable](newArr []T, oldArr []T) ([]T, []T, []T) {
|
func ArrayCompare[T comparable](newArr []T, oldArr []T) ([]T, []T, []T) {
|
||||||
@@ -143,3 +145,13 @@ func ArrayDeduplicate[T comparable](arr []T) []T {
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ArrayAnyMatches 给定字符串是否包含指定数组中的任意字符串, 如:["time", "date"] , substr : timestamp,返回true
|
||||||
|
func ArrayAnyMatches(arr []string, subStr string) bool {
|
||||||
|
for _, itm := range arr {
|
||||||
|
if strings.Contains(subStr, itm) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,3 +7,44 @@ INSERT INTO `t_sys_config` (`name`, `key`, `params`, `value`, `remark`, `permiss
|
|||||||
|
|
||||||
ALTER TABLE t_db_instance CHANGE sid extra varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL COMMENT '连接需要的额外参数,如oracle数据库需要sid等';
|
ALTER TABLE t_db_instance CHANGE sid extra varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL COMMENT '连接需要的额外参数,如oracle数据库需要sid等';
|
||||||
ALTER TABLE t_db_instance MODIFY COLUMN extra varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL COMMENT '连接需要的额外参数,如oracle数据库需要sid等';
|
ALTER TABLE t_db_instance MODIFY COLUMN extra varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL COMMENT '连接需要的额外参数,如oracle数据库需要sid等';
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE `t_db_transfer_task` (
|
||||||
|
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||||
|
`creator_id` bigint(20) NOT NULL COMMENT '创建人id',
|
||||||
|
`creator` varchar(100) NOT NULL COMMENT '创建人姓名',
|
||||||
|
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`modifier_id` bigint(20) NOT NULL COMMENT '修改人id',
|
||||||
|
`modifier` varchar(100) NOT NULL COMMENT '修改人姓名',
|
||||||
|
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||||
|
`is_deleted` tinyint(1) DEFAULT '0' COMMENT '是否删除',
|
||||||
|
`delete_time` datetime DEFAULT NULL COMMENT '删除时间',
|
||||||
|
`checked_keys` text NOT NULL COMMENT '选中需要迁移的表',
|
||||||
|
`delete_table` tinyint(4) NOT NULL COMMENT '创建表前是否删除表 1是 -1否',
|
||||||
|
`name_case` tinyint(4) NOT NULL COMMENT '表名、字段大小写转换 1无 2大写 3小写',
|
||||||
|
`strategy` tinyint(4) NOT NULL COMMENT '迁移策略 1全量 2增量',
|
||||||
|
`running_state` tinyint(1) DEFAULT '2' COMMENT '运行状态 1运行中 2待运行',
|
||||||
|
`src_db_id` bigint(20) NOT NULL COMMENT '源库id',
|
||||||
|
`src_db_name` varchar(200) NOT NULL COMMENT '源库名',
|
||||||
|
`src_tag_path` varchar(200) NOT NULL COMMENT '源库tagPath',
|
||||||
|
`src_db_type` varchar(200) NOT NULL COMMENT '源库类型',
|
||||||
|
`src_inst_name` varchar(200) NOT NULL COMMENT '源库实例名',
|
||||||
|
`target_db_id` bigint(20) NOT NULL COMMENT '目标库id',
|
||||||
|
`target_db_name` varchar(200) NOT NULL COMMENT '目标库名',
|
||||||
|
`target_tag_path` varchar(200) NOT NULL COMMENT '目标库类型',
|
||||||
|
`target_db_type` varchar(200) NOT NULL COMMENT '目标库实例名',
|
||||||
|
`target_inst_name` varchar(200) NOT NULL COMMENT '目标库tagPath',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) COMMENT='数据库迁移任务表';
|
||||||
|
|
||||||
|
INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `ui_path`, `is_deleted`, `delete_time`) VALUES(1709194669, 36, 1, 1, '数据库迁移', 'transfer', 1709194669, '{"component":"ops/db/DbTransferList","icon":"Switch","isKeepAlive":true,"routeName":"DbTransferList"}', 12, 'liuzongyang', 12, 'liuzongyang', '2024-02-29 16:17:50', '2024-02-29 16:24:59', 'SmLcpu6c/', 0, NULL);
|
||||||
|
INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `ui_path`, `is_deleted`, `delete_time`) VALUES(1709194694, 1709194669, 2, 1, '基本权限', 'db:transfer', 1709194694, 'null', 12, 'liuzongyang', 12, 'liuzongyang', '2024-02-29 16:18:14', '2024-02-29 16:18:14', 'SmLcpu6c/A9vAm4J8/', 0, NULL);
|
||||||
|
INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `ui_path`, `is_deleted`, `delete_time`) VALUES(1709196697, 1709194669, 2, 1, '编辑', 'db:transfer:save', 1709196697, 'null', 12, 'liuzongyang', 12, 'liuzongyang', '2024-02-29 16:51:37', '2024-02-29 16:51:37', 'SmLcpu6c/5oJwPzNb/', 0, NULL);
|
||||||
|
INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `ui_path`, `is_deleted`, `delete_time`) VALUES(1709196707, 1709194669, 2, 1, '删除', 'db:transfer:del', 1709196707, 'null', 12, 'liuzongyang', 12, 'liuzongyang', '2024-02-29 16:51:47', '2024-02-29 16:51:47', 'SmLcpu6c/L3ybnAEW/', 0, NULL);
|
||||||
|
INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `ui_path`, `is_deleted`, `delete_time`) VALUES(1709196723, 1709194669, 2, 1, '启停', 'db:transfer:status', 1709196723, 'null', 12, 'liuzongyang', 12, 'liuzongyang', '2024-02-29 16:52:04', '2024-02-29 16:52:04', 'SmLcpu6c/hGiLN1VT/', 0, NULL);
|
||||||
|
INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `ui_path`, `is_deleted`, `delete_time`) VALUES(1709196737, 1709194669, 2, 1, '日志', 'db:transfer:log', 1709196737, 'null', 12, 'liuzongyang', 12, 'liuzongyang', '2024-02-29 16:52:17', '2024-02-29 16:52:17', 'SmLcpu6c/CZhNIbWg/', 0, NULL);
|
||||||
|
INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `ui_path`, `is_deleted`, `delete_time`) VALUES(1709196755, 1709194669, 2, 1, '运行', 'db:transfer:run', 1709196755, 'null', 12, 'liuzongyang', 12, 'liuzongyang', '2024-02-29 16:52:36', '2024-02-29 16:52:36', 'SmLcpu6c/b6yHt6V2/', 0, NULL);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user