mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-02 15:30:25 +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';
|
||||
const propsData = props.data as any;
|
||||
if (!propsData?.id) {
|
||||
state.form = basicFormData;
|
||||
let d = {} as FormData;
|
||||
Object.assign(d, basicFormData);
|
||||
state.form = d;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -83,6 +83,14 @@ export const dbApi = {
|
||||
runDatasyncTask: Api.newPost('/datasync/tasks/{taskId}/run'),
|
||||
stopDatasyncTask: Api.newPost('/datasync/tasks/{taskId}/stop'),
|
||||
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 = {
|
||||
|
||||
@@ -426,7 +426,7 @@ export class DbInst {
|
||||
* @returns
|
||||
*/
|
||||
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 值都会自动转换为真
|
||||
{ udtName: 'BIT', dataType: 'BIT', desc: '用于存储整数数据 1、0 或 NULL', space: '1', range: '1' },
|
||||
// 一般日期时间数据类型 DATE TIME TIMESTAMP 默认精度 6
|
||||
// 多媒体数据类型 TEXT/LONG/LONGVARCHAR 类型:变长字符串类型 IMAGE/LONGVARBINARY 类型 BLOB CLOB BFILE 100G-1
|
||||
{ udtName: 'DATE', dataType: 'DATE', desc: '年、月、日', space: '', range: '' },
|
||||
{ udtName: 'TIME', dataType: 'TIME', desc: '时、分、秒', space: '', range: '' },
|
||||
{
|
||||
@@ -45,6 +44,7 @@ const DM_TYPE_LIST: sqlColumnType[] = [
|
||||
space: '',
|
||||
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: 'LONG', dataType: 'LONG', 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 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 baseSql = ` ${this.quoteIdentifier(name)} ${cl.type}${length} ${incr}`;
|
||||
@@ -329,10 +334,10 @@ class OracleDialect implements DbDialect {
|
||||
// 主键语句
|
||||
let prisql = '';
|
||||
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) {
|
||||
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) {
|
||||
taskId := getTaskId(rc)
|
||||
taskId := d.getTaskId(rc)
|
||||
rc.ReqParam = taskId
|
||||
_ = d.DataSyncTaskApp.RunCronJob(taskId)
|
||||
}
|
||||
|
||||
func (d *DataSyncTask) Stop(rc *req.Ctx) {
|
||||
taskId := getTaskId(rc)
|
||||
taskId := d.getTaskId(rc)
|
||||
rc.ReqParam = taskId
|
||||
|
||||
task := new(entity.DataSyncTask)
|
||||
@@ -91,12 +91,12 @@ func (d *DataSyncTask) Stop(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)
|
||||
rc.ResData = dbEntity
|
||||
}
|
||||
|
||||
func getTaskId(rc *req.Ctx) uint64 {
|
||||
func (d *DataSyncTask) getTaskId(rc *req.Ctx) uint64 {
|
||||
instanceId := rc.PathParamInt("taskId")
|
||||
biz.IsTrue(instanceId > 0, "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(dbSqlAppImpl), ioc.WithComponentName("DbSqlApp"))
|
||||
ioc.Register(new(dataSyncAppImpl), ioc.WithComponentName("DbDataSyncTaskApp"))
|
||||
ioc.Register(new(dbTransferAppImpl), ioc.WithComponentName("DbTransferTaskApp"))
|
||||
|
||||
ioc.Register(newDbScheduler(), ioc.WithComponentName("DbScheduler"))
|
||||
ioc.Register(new(DbBackupApp), ioc.WithComponentName("DbBackupApp"))
|
||||
@@ -31,6 +32,7 @@ func Init() {
|
||||
panic(fmt.Sprintf("初始化 DbBinlogApp 失败: %v", err))
|
||||
}
|
||||
GetDataSyncTaskApp().InitCronJob()
|
||||
GetDbTransferTaskApp().InitJob()
|
||||
InitDbFlowHandler()
|
||||
})()
|
||||
}
|
||||
@@ -54,3 +56,6 @@ func GetDbBinlogApp() *DbBinlogApp {
|
||||
func GetDataSyncTaskApp() DataSyncTask {
|
||||
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])
|
||||
}
|
||||
if err = walkFn(rowData, cols); err != nil {
|
||||
logx.Error("游标遍历查询结果集出错,退出遍历: %s", err.Error())
|
||||
logx.Errorf("游标遍历查询结果集出错,退出遍历: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,33 @@ import (
|
||||
"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 (
|
||||
// -1. 无操作
|
||||
DuplicateStrategyNone = -1
|
||||
@@ -32,4 +59,14 @@ type Dialect interface {
|
||||
|
||||
// 拷贝表
|
||||
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)
|
||||
|
||||
// 获取表信息
|
||||
GetTables() ([]Table, error)
|
||||
GetTables(tableNames ...string) ([]Table, error)
|
||||
|
||||
// 获取指定表名的所有列元信息
|
||||
GetColumns(tableNames ...string) ([]Column, error)
|
||||
|
||||
@@ -19,19 +19,44 @@ SELECT a.object_name as TABLE_NAME,
|
||||
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'
|
||||
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 表索引信息
|
||||
select
|
||||
a.index_name as INDEX_NAME,
|
||||
|
||||
@@ -34,6 +34,21 @@ FROM sys.tables t
|
||||
where ss.name = ?
|
||||
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 索引信息
|
||||
SELECT ind.name AS indexName,
|
||||
col.name AS columnName,
|
||||
|
||||
@@ -25,6 +25,25 @@ WHERE
|
||||
)
|
||||
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 索引信息
|
||||
SELECT
|
||||
index_name indexName,
|
||||
|
||||
@@ -31,6 +31,26 @@ where
|
||||
and c.reltype > 0
|
||||
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 表索引信息
|
||||
SELECT
|
||||
indexname AS "indexName",
|
||||
|
||||
@@ -10,6 +10,19 @@ WHERE type = 'table'
|
||||
and name not like 'sqlite_%'
|
||||
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 表索引信息
|
||||
select name as indexName,
|
||||
`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, ","))
|
||||
|
||||
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
|
||||
// 设置允许填充自增列之后,显示指定列名可以插入自增列
|
||||
for _, value := range values {
|
||||
// 达梦数据库只能一条条的执行insert
|
||||
res, err := dd.dc.TxExec(tx, sqlTemp, value...)
|
||||
if err != nil {
|
||||
logx.Errorf("执行sql失败:%s, sql: [ %s ]", err.Error(), sqlTemp)
|
||||
return 0, err
|
||||
}
|
||||
effRows += int(res)
|
||||
}
|
||||
@@ -147,8 +151,8 @@ func (dd *DMDialect) CopyTable(copy *dbi.DbCopyTable) error {
|
||||
// 复制数据
|
||||
if copy.CopyData {
|
||||
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)
|
||||
columnArr := make([]string, 0)
|
||||
@@ -157,12 +161,168 @@ func (dd *DMDialect) CopyTable(copy *dbi.DbCopyTable) error {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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 (
|
||||
DM_META_FILE = "metasql/dm_meta.sql"
|
||||
DM_DB_SCHEMAS = "DM_DB_SCHEMAS"
|
||||
DM_TABLE_INFO_KEY = "DM_TABLE_INFO"
|
||||
DM_INDEX_INFO_KEY = "DM_INDEX_INFO"
|
||||
DM_COLUMN_MA_KEY = "DM_COLUMN_MA"
|
||||
DM_META_FILE = "metasql/dm_meta.sql"
|
||||
DM_DB_SCHEMAS = "DM_DB_SCHEMAS"
|
||||
DM_TABLE_INFO_KEY = "DM_TABLE_INFO"
|
||||
DM_INDEX_INFO_KEY = "DM_INDEX_INFO"
|
||||
DM_COLUMN_MA_KEY = "DM_COLUMN_MA"
|
||||
DM_TABLE_INFO_BY_NAMES_KEY = "DM_TABLE_INFO_BY_NAMES"
|
||||
)
|
||||
|
||||
type DMMetaData struct {
|
||||
@@ -50,14 +51,19 @@ func (dd *DMMetaData) GetDbNames() ([]string, error) {
|
||||
return databases, nil
|
||||
}
|
||||
|
||||
// 获取表基础元信息, 如表名等
|
||||
func (dd *DMMetaData) GetTables() ([]dbi.Table, error) {
|
||||
func (dd *DMMetaData) GetTables(tableNames ...string) ([]dbi.Table, error) {
|
||||
names := strings.Join(collx.ArrayMap[string, string](tableNames, func(val string) string {
|
||||
return fmt.Sprintf("'%s'", dbi.RemoveQuote(dd, val))
|
||||
}), ",")
|
||||
|
||||
// 首先执行更新统计信息sql 这个统计信息在数据量比较大的时候就比较耗时,所以最好定时执行
|
||||
// _, _, err := pd.dc.Query("dbms_stats.GATHER_SCHEMA_stats(SELECT SF_GET_SCHEMA_NAME_BY_ID(CURRENT_SCHID))")
|
||||
var res []map[string]any
|
||||
var err error
|
||||
|
||||
// 查询表信息
|
||||
_, res, err := dd.dc.Query(dbi.GetLocalSql(DM_META_FILE, DM_TABLE_INFO_KEY))
|
||||
if tableNames != nil && len(tableNames) > 0 {
|
||||
_, 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 {
|
||||
return nil, err
|
||||
}
|
||||
@@ -254,8 +260,64 @@ var (
|
||||
dateRegexp = regexp.MustCompile(`(?i)date`)
|
||||
// 时间类型
|
||||
timeRegexp = regexp.MustCompile(`(?i)time`)
|
||||
// 定义正则表达式,匹配括号内的数字
|
||||
bracketsRegexp = regexp.MustCompile(`\((\d+)\)`)
|
||||
|
||||
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 {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"mayfly-go/internal/db/dbm/dbi"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/utils/anyx"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -14,6 +15,17 @@ type MssqlDialect struct {
|
||||
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 获取数据库程序模块,用于数据库备份与恢复
|
||||
func (md *MssqlDialect) GetDbProgram() (dbi.DbProgram, error) {
|
||||
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) {
|
||||
// 把二维数组转为一维数组
|
||||
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()
|
||||
schema := md.dc.Info.CurrentSchema()
|
||||
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)
|
||||
// 执行批量insert sql
|
||||
// 把二维数组转为一维数组
|
||||
var args []any
|
||||
for _, v := range values {
|
||||
args = append(args, v...)
|
||||
}
|
||||
|
||||
// 设置允许填充自增列之后,显示指定列名可以插入自增列
|
||||
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
|
||||
}
|
||||
|
||||
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 (
|
||||
MSSQL_META_FILE = "metasql/mssql_meta.sql"
|
||||
MSSQL_DBS_KEY = "MSSQL_DBS"
|
||||
MSSQL_DB_SCHEMAS_KEY = "MSSQL_DB_SCHEMAS"
|
||||
MSSQL_TABLE_INFO_KEY = "MSSQL_TABLE_INFO"
|
||||
MSSQL_INDEX_INFO_KEY = "MSSQL_INDEX_INFO"
|
||||
MSSQL_COLUMN_MA_KEY = "MSSQL_COLUMN_MA"
|
||||
MSSQL_TABLE_DETAIL_KEY = "MSSQL_TABLE_DETAIL"
|
||||
MSSQL_TABLE_INDEX_DDL_KEY = "MSSQL_TABLE_INDEX_DDL"
|
||||
MSSQL_META_FILE = "metasql/mssql_meta.sql"
|
||||
MSSQL_DBS_KEY = "MSSQL_DBS"
|
||||
MSSQL_DB_SCHEMAS_KEY = "MSSQL_DB_SCHEMAS"
|
||||
MSSQL_TABLE_INFO_KEY = "MSSQL_TABLE_INFO"
|
||||
MSSQL_TABLE_INFO_BY_NAMES_KEY = "MSSQL_TABLE_INFO_BY_NAMES"
|
||||
MSSQL_INDEX_INFO_KEY = "MSSQL_INDEX_INFO"
|
||||
MSSQL_COLUMN_MA_KEY = "MSSQL_COLUMN_MA"
|
||||
MSSQL_TABLE_DETAIL_KEY = "MSSQL_TABLE_DETAIL"
|
||||
MSSQL_TABLE_INDEX_DDL_KEY = "MSSQL_TABLE_INDEX_DDL"
|
||||
)
|
||||
|
||||
type MssqlMetaData struct {
|
||||
@@ -54,8 +55,21 @@ func (md *MssqlMetaData) GetDbNames() ([]string, error) {
|
||||
}
|
||||
|
||||
// 获取表基础元信息, 如表名等
|
||||
func (md *MssqlMetaData) GetTables() ([]dbi.Table, error) {
|
||||
_, res, err := md.dc.Query(dbi.GetLocalSql(MSSQL_META_FILE, MSSQL_TABLE_INFO_KEY), md.dc.Info.CurrentSchema())
|
||||
func (md *MssqlMetaData) GetTables(tableNames ...string) ([]dbi.Table, error) {
|
||||
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 {
|
||||
return nil, err
|
||||
@@ -77,8 +91,9 @@ func (md *MssqlMetaData) GetTables() ([]dbi.Table, 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 {
|
||||
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())
|
||||
@@ -176,6 +191,8 @@ func (md *MssqlMetaData) CopyTableDDL(tableName string, newTableName string) (st
|
||||
newTableName = tableName
|
||||
}
|
||||
|
||||
meta := md.dc.GetMetaData()
|
||||
|
||||
// 根据列信息生成建表语句
|
||||
var builder 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)
|
||||
@@ -294,6 +311,71 @@ var (
|
||||
timeRegexp = regexp.MustCompile(`(?i)time`)
|
||||
|
||||
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 {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"mayfly-go/internal/db/dbm/dbi"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -49,6 +50,10 @@ func (md *MysqlDialect) BatchInsert(tx *sql.Tx, tableName string, columns []stri
|
||||
return md.dc.TxExec(tx, sqlStr, args...)
|
||||
}
|
||||
|
||||
func (md *MysqlDialect) GetDataConverter() dbi.DataConverter {
|
||||
return converter
|
||||
}
|
||||
|
||||
func (md *MysqlDialect) CopyTable(copy *dbi.DbCopyTable) error {
|
||||
|
||||
tableName := copy.TableName
|
||||
@@ -70,3 +75,143 @@ func (md *MysqlDialect) CopyTable(copy *dbi.DbCopyTable) error {
|
||||
}
|
||||
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 (
|
||||
MYSQL_META_FILE = "metasql/mysql_meta.sql"
|
||||
MYSQL_DBS = "MYSQL_DBS"
|
||||
MYSQL_TABLE_INFO_KEY = "MYSQL_TABLE_INFO"
|
||||
MYSQL_INDEX_INFO_KEY = "MYSQL_INDEX_INFO"
|
||||
MYSQL_COLUMN_MA_KEY = "MYSQL_COLUMN_MA"
|
||||
MYSQL_META_FILE = "metasql/mysql_meta.sql"
|
||||
MYSQL_DBS = "MYSQL_DBS"
|
||||
MYSQL_TABLE_INFO_KEY = "MYSQL_TABLE_INFO"
|
||||
MYSQL_TABLE_INFO_BY_NAMES_KEY = "MYSQL_TABLE_INFO_BY_NAMES"
|
||||
MYSQL_INDEX_INFO_KEY = "MYSQL_INDEX_INFO"
|
||||
MYSQL_COLUMN_MA_KEY = "MYSQL_COLUMN_MA"
|
||||
)
|
||||
|
||||
type MysqlMetaData struct {
|
||||
@@ -52,9 +53,21 @@ func (md *MysqlMetaData) GetDbNames() ([]string, error) {
|
||||
return databases, nil
|
||||
}
|
||||
|
||||
// 获取表基础元信息, 如表名等
|
||||
func (md *MysqlMetaData) GetTables() ([]dbi.Table, error) {
|
||||
_, res, err := md.dc.Query(dbi.GetLocalSql(MYSQL_META_FILE, MYSQL_TABLE_INFO_KEY))
|
||||
func (md *MysqlMetaData) GetTables(tableNames ...string) ([]dbi.Table, error) {
|
||||
meta := md.dc.GetMetaData()
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@@ -75,8 +88,9 @@ func (md *MysqlMetaData) GetTables() ([]dbi.Table, 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 {
|
||||
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))
|
||||
@@ -89,7 +103,7 @@ func (md *MysqlMetaData) GetColumns(tableNames ...string) ([]dbi.Column, error)
|
||||
columns = append(columns, dbi.Column{
|
||||
TableName: anyx.ConvString(re["tableName"]),
|
||||
ColumnName: anyx.ConvString(re["columnName"]),
|
||||
ColumnType: anyx.ConvString(re["columnType"]),
|
||||
ColumnType: strings.Replace(anyx.ConvString(re["columnType"]), " unsigned", "", 1),
|
||||
ColumnComment: anyx.ConvString(re["columnComment"]),
|
||||
Nullable: anyx.ConvString(re["nullable"]),
|
||||
IsPrimaryKey: anyx.ConvInt(re["isPrimaryKey"]) == 1,
|
||||
@@ -199,6 +213,59 @@ var (
|
||||
timeRegexp = regexp.MustCompile(`(?i)time`)
|
||||
|
||||
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 {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"mayfly-go/internal/db/dbm/dbi"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/utils/anyx"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"strings"
|
||||
"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))
|
||||
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元数据 -----------------------------------
|
||||
const (
|
||||
ORACLE_META_FILE = "metasql/oracle_meta.sql"
|
||||
ORACLE_DB_SCHEMAS = "ORACLE_DB_SCHEMAS"
|
||||
ORACLE_TABLE_INFO_KEY = "ORACLE_TABLE_INFO"
|
||||
ORACLE_INDEX_INFO_KEY = "ORACLE_INDEX_INFO"
|
||||
ORACLE_COLUMN_MA_KEY = "ORACLE_COLUMN_MA"
|
||||
ORACLE_META_FILE = "metasql/oracle_meta.sql"
|
||||
ORACLE_DB_SCHEMAS = "ORACLE_DB_SCHEMAS"
|
||||
ORACLE_TABLE_INFO_KEY = "ORACLE_TABLE_INFO"
|
||||
ORACLE_TABLE_INFO_BY_NAMES_KEY = "ORACLE_TABLE_INFO_BY_NAMES"
|
||||
ORACLE_INDEX_INFO_KEY = "ORACLE_INDEX_INFO"
|
||||
ORACLE_COLUMN_MA_KEY = "ORACLE_COLUMN_MA"
|
||||
)
|
||||
|
||||
type OracleMetaData struct {
|
||||
@@ -51,14 +52,20 @@ func (od *OracleMetaData) GetDbNames() ([]string, error) {
|
||||
return databases, nil
|
||||
}
|
||||
|
||||
// 获取表基础元信息, 如表名等
|
||||
func (od *OracleMetaData) GetTables() ([]dbi.Table, error) {
|
||||
func (od *OracleMetaData) GetTables(tableNames ...string) ([]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 这个统计信息在数据量比较大的时候就比较耗时,所以最好定时执行
|
||||
// _, _, err := pd.dc.Query("dbms_stats.GATHER_SCHEMA_stats(SELECT SF_GET_SCHEMA_NAME_BY_ID(CURRENT_SCHID))")
|
||||
var res []map[string]any
|
||||
var err error
|
||||
|
||||
// 查询表信息
|
||||
_, res, err := od.dc.Query(dbi.GetLocalSql(ORACLE_META_FILE, ORACLE_TABLE_INFO_KEY))
|
||||
if tableNames != nil || len(tableNames) > 0 {
|
||||
_, 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 {
|
||||
return nil, err
|
||||
}
|
||||
@@ -79,8 +86,9 @@ func (od *OracleMetaData) GetTables() ([]dbi.Table, 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 {
|
||||
return fmt.Sprintf("'%s'", dbi.RemoveQuote(od, val))
|
||||
return fmt.Sprintf("'%s'", meta.RemoveQuote(val))
|
||||
}), ",")
|
||||
|
||||
// 如果表数量超过了1000,需要分批查询
|
||||
@@ -273,7 +281,57 @@ var (
|
||||
// 日期时间类型
|
||||
datetimeTypeRegexp = regexp.MustCompile(`(?i)date|timestamp`)
|
||||
|
||||
bracketsRegexp = regexp.MustCompile(`\((\d+)\)`)
|
||||
|
||||
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 {
|
||||
|
||||
@@ -58,7 +58,7 @@ func (pd *PgsqlDialect) BatchInsert(tx *sql.Tx, tableName string, columns []stri
|
||||
}
|
||||
|
||||
// pgsql默认唯一键冲突策略
|
||||
func (pd PgsqlDialect) pgsqlOnDuplicateStrategySql(duplicateStrategy int, tableName string, columns []string) string {
|
||||
func (pd *PgsqlDialect) pgsqlOnDuplicateStrategySql(duplicateStrategy int, tableName string, columns []string) string {
|
||||
suffix := ""
|
||||
if duplicateStrategy == dbi.DuplicateStrategyIgnore {
|
||||
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
|
||||
func (pd PgsqlDialect) gaussOnDuplicateStrategySql(duplicateStrategy int, tableName string, columns []string) string {
|
||||
func (pd *PgsqlDialect) gaussOnDuplicateStrategySql(duplicateStrategy int, tableName string, columns []string) string {
|
||||
suffix := ""
|
||||
metadata := pd.dc.GetMetaData()
|
||||
if duplicateStrategy == dbi.DuplicateStrategyIgnore {
|
||||
@@ -122,17 +122,7 @@ func (pd PgsqlDialect) gaussOnDuplicateStrategySql(duplicateStrategy int, tableN
|
||||
|
||||
// 从连接信息中获取数据库和schema信息
|
||||
func (pd *PgsqlDialect) currentSchema() string {
|
||||
dbName := pd.dc.Info.Database
|
||||
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")
|
||||
return pd.dc.Info.CurrentSchema()
|
||||
}
|
||||
|
||||
func (pd *PgsqlDialect) CopyTable(copy *dbi.DbCopyTable) error {
|
||||
@@ -192,3 +182,221 @@ func (pd *PgsqlDialect) CopyTable(copy *dbi.DbCopyTable) error {
|
||||
}
|
||||
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 (
|
||||
PGSQL_META_FILE = "metasql/pgsql_meta.sql"
|
||||
PGSQL_DB_SCHEMAS = "PGSQL_DB_SCHEMAS"
|
||||
PGSQL_TABLE_INFO_KEY = "PGSQL_TABLE_INFO"
|
||||
PGSQL_INDEX_INFO_KEY = "PGSQL_INDEX_INFO"
|
||||
PGSQL_COLUMN_MA_KEY = "PGSQL_COLUMN_MA"
|
||||
PGSQL_TABLE_DDL_KEY = "PGSQL_TABLE_DDL_FUNC"
|
||||
PGSQL_META_FILE = "metasql/pgsql_meta.sql"
|
||||
PGSQL_DB_SCHEMAS = "PGSQL_DB_SCHEMAS"
|
||||
PGSQL_TABLE_INFO_KEY = "PGSQL_TABLE_INFO"
|
||||
PGSQL_INDEX_INFO_KEY = "PGSQL_INDEX_INFO"
|
||||
PGSQL_COLUMN_MA_KEY = "PGSQL_COLUMN_MA"
|
||||
PGSQL_TABLE_DDL_KEY = "PGSQL_TABLE_DDL_FUNC"
|
||||
PGSQL_TABLE_INFO_BY_NAMES_KEY = "PGSQL_TABLE_INFO_BY_NAMES"
|
||||
)
|
||||
|
||||
type PgsqlMetaData struct {
|
||||
@@ -51,13 +52,23 @@ func (pd *PgsqlMetaData) GetDbNames() ([]string, error) {
|
||||
return databases, nil
|
||||
}
|
||||
|
||||
// 获取表基础元信息, 如表名等
|
||||
func (pd *PgsqlMetaData) GetTables() ([]dbi.Table, error) {
|
||||
_, res, err := pd.dc.Query(dbi.GetLocalSql(PGSQL_META_FILE, PGSQL_TABLE_INFO_KEY))
|
||||
func (pd *PgsqlMetaData) GetTables(tableNames ...string) ([]dbi.Table, error) {
|
||||
meta := pd.dc.GetMetaData()
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tables := make([]dbi.Table, 0)
|
||||
for _, re := range res {
|
||||
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) {
|
||||
meta := pd.dc.GetMetaData()
|
||||
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))
|
||||
@@ -210,8 +222,58 @@ var (
|
||||
dateRegexp = regexp.MustCompile(`(?i)date`)
|
||||
// 时间类型
|
||||
timeRegexp = regexp.MustCompile(`(?i)time`)
|
||||
// 定义正则表达式,匹配括号内的数字
|
||||
bracketsRegexp = regexp.MustCompile(`\((\d+)\)`)
|
||||
|
||||
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 {
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"mayfly-go/internal/db/dbm/dbi"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"strings"
|
||||
"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) {
|
||||
_, _ = sd.dc.Exec("PRAGMA foreign_keys = false")
|
||||
// 执行批量insert sql,跟mysql一样 支持批量insert语法
|
||||
// 生成占位符字符串:如:(?,?)
|
||||
// 重复字符串并用逗号连接
|
||||
@@ -44,9 +47,16 @@ func (sd *SqliteDialect) BatchInsert(tx *sql.Tx, tableName string, columns []str
|
||||
for _, v := range values {
|
||||
args = append(args, v...)
|
||||
}
|
||||
exec, err := sd.dc.TxExec(tx, sqlStr, args...)
|
||||
|
||||
_, _ = sd.dc.Exec("PRAGMA foreign_keys = true;")
|
||||
|
||||
// 执行批量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 {
|
||||
@@ -80,3 +90,130 @@ func (sd *SqliteDialect) CopyTable(copy *dbi.DbCopyTable) error {
|
||||
|
||||
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 {
|
||||
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 {
|
||||
|
||||
@@ -6,15 +6,17 @@ import (
|
||||
"mayfly-go/internal/db/dbm/dbi"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/utils/anyx"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
SQLITE_META_FILE = "metasql/sqlite_meta.sql"
|
||||
SQLITE_TABLE_INFO_KEY = "SQLITE_TABLE_INFO"
|
||||
SQLITE_INDEX_INFO_KEY = "SQLITE_INDEX_INFO"
|
||||
SQLITE_META_FILE = "metasql/sqlite_meta.sql"
|
||||
SQLITE_TABLE_INFO_KEY = "SQLITE_TABLE_INFO"
|
||||
SQLITE_INDEX_INFO_KEY = "SQLITE_INDEX_INFO"
|
||||
SQLITE_TABLE_INFO_BY_NAMES_KEY = "SQLITE_TABLE_INFO_BY_NAMES"
|
||||
)
|
||||
|
||||
type SqliteMetaData struct {
|
||||
@@ -48,13 +50,22 @@ func (sd *SqliteMetaData) GetDbNames() ([]string, error) {
|
||||
}
|
||||
|
||||
// 获取表基础元信息, 如表名等
|
||||
func (sd *SqliteMetaData) GetTables() ([]dbi.Table, error) {
|
||||
_, res, err := sd.dc.Query(dbi.GetLocalSql(SQLITE_META_FILE, SQLITE_TABLE_INFO_KEY))
|
||||
//cols, res, err := sd.dc.Query("SELECT datetime(1092941466, 'unixepoch')")
|
||||
func (sd *SqliteMetaData) GetTables(tableNames ...string) ([]dbi.Table, error) {
|
||||
names := strings.Join(collx.ArrayMap[string, string](tableNames, func(val string) string {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tables := make([]dbi.Table, 0)
|
||||
for _, re := range res {
|
||||
tables = append(tables, dbi.Table{
|
||||
@@ -191,6 +202,61 @@ var (
|
||||
datetimeRegexp = regexp.MustCompile(`(?i)datetime`)
|
||||
|
||||
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 {
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
type DbTransferTaskQuery struct {
|
||||
}
|
||||
|
||||
type DbTransferLogQuery struct {
|
||||
TaskId uint64 `json:"task_id" form:"taskId"`
|
||||
}
|
||||
|
||||
// 数据库查询实体,不与数据库表字段一一对应
|
||||
type DbQuery struct {
|
||||
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(newDataSyncTaskRepo(), ioc.WithComponentName("DbDataSyncTaskRepo"))
|
||||
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(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)
|
||||
InitDbRestoreRouter(router)
|
||||
InitDbDataSyncRouter(router)
|
||||
InitDbTransferRouter(router)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package collx
|
||||
|
||||
import "strings"
|
||||
|
||||
// 数组比较
|
||||
// 依次返回,新增值,删除值,以及不变值
|
||||
func ArrayCompare[T comparable](newArr []T, oldArr []T) ([]T, []T, []T) {
|
||||
@@ -143,3 +145,13 @@ func ArrayDeduplicate[T comparable](arr []T) []T {
|
||||
|
||||
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 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