mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-02 07:20:24 +08:00
Merge branch 'master' into dev
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -74,6 +74,13 @@
|
||||
"font_class": "sqlite",
|
||||
"unicode": "e546",
|
||||
"unicode_decimal": 58694
|
||||
},
|
||||
{
|
||||
"icon_id": "29340317",
|
||||
"name": "temp-mssql",
|
||||
"font_class": "MSSQLNATIVE",
|
||||
"unicode": "e600",
|
||||
"unicode_decimal": 58880
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { toRefs, watch, reactive, onMounted, Ref, ref } from 'vue';
|
||||
import { onMounted, reactive, Ref, ref, toRefs, watch } from 'vue';
|
||||
import { dbApi } from './api';
|
||||
import { DbSqlExecTypeEnum } from './enums';
|
||||
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||
@@ -120,6 +120,12 @@ const onShowRollbackSql = async (sqlExecLog: any) => {
|
||||
const primaryKey = getPrimaryKey(columns);
|
||||
const oldValue = JSON.parse(sqlExecLog.oldValue);
|
||||
|
||||
let schema = '';
|
||||
let dbArr = sqlExecLog.db.split('/');
|
||||
if (dbArr.length == 2) {
|
||||
schema = dbArr[1] + '.';
|
||||
}
|
||||
|
||||
const rollbackSqls = [];
|
||||
if (sqlExecLog.type == DbSqlExecTypeEnum.Update.value) {
|
||||
for (let ov of oldValue) {
|
||||
@@ -130,7 +136,7 @@ const onShowRollbackSql = async (sqlExecLog: any) => {
|
||||
}
|
||||
setItems.push(`${key} = ${wrapValue(ov[key])}`);
|
||||
}
|
||||
rollbackSqls.push(`UPDATE ${sqlExecLog.table} SET ${setItems.join(', ')} WHERE ${primaryKey} = ${wrapValue(ov[primaryKey])};`);
|
||||
rollbackSqls.push(`UPDATE ${schema}${sqlExecLog.table} SET ${setItems.join(', ')} WHERE ${primaryKey} = ${wrapValue(ov[primaryKey])};`);
|
||||
}
|
||||
} else if (sqlExecLog.type == DbSqlExecTypeEnum.Delete.value) {
|
||||
const columnNames = columns.map((c: any) => c.columnName);
|
||||
@@ -139,7 +145,7 @@ const onShowRollbackSql = async (sqlExecLog: any) => {
|
||||
for (let column of columnNames) {
|
||||
values.push(wrapValue(ov[column]));
|
||||
}
|
||||
rollbackSqls.push(`INSERT INTO ${sqlExecLog.table} (${columnNames.join(', ')}) VALUES (${values.join(', ')});`);
|
||||
rollbackSqls.push(`INSERT INTO ${schema}${sqlExecLog.table} (${columnNames.join(', ')}) VALUES (${values.join(', ')});`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +154,7 @@ const onShowRollbackSql = async (sqlExecLog: any) => {
|
||||
};
|
||||
|
||||
const getPrimaryKey = (columns: any) => {
|
||||
const col = columns.find((c: any) => c.columnKey == 'PRI');
|
||||
const col = columns.find((c: any) => c.isPrimaryKey);
|
||||
if (col) {
|
||||
return col.columnName;
|
||||
}
|
||||
|
||||
@@ -156,27 +156,31 @@ const dbForm: any = ref(null);
|
||||
const dbTypes = [
|
||||
{
|
||||
type: 'mysql',
|
||||
label: 'mysql',
|
||||
label: 'MySQL',
|
||||
},
|
||||
{
|
||||
type: 'mariadb',
|
||||
label: 'mariadb',
|
||||
label: 'MariaDB',
|
||||
},
|
||||
{
|
||||
type: 'postgres',
|
||||
label: 'postgres',
|
||||
label: 'PostgreSQL',
|
||||
},
|
||||
{
|
||||
type: 'dm',
|
||||
label: '达梦',
|
||||
label: 'DM',
|
||||
},
|
||||
{
|
||||
type: 'oracle',
|
||||
label: 'oracle',
|
||||
label: 'Oracle',
|
||||
},
|
||||
{
|
||||
type: 'sqlite',
|
||||
label: 'sqlite',
|
||||
label: 'Sqlite',
|
||||
},
|
||||
{
|
||||
type: 'mssql',
|
||||
label: 'MSSQL',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -196,17 +200,17 @@ const state = reactive({
|
||||
remark: '',
|
||||
sshTunnelMachineId: null as any,
|
||||
},
|
||||
subimtForm: {},
|
||||
submitForm: {},
|
||||
// 原密码
|
||||
pwd: '',
|
||||
// 原用户名
|
||||
oldUserName: null,
|
||||
});
|
||||
|
||||
const { dialogVisible, tabActiveName, form, subimtForm, pwd } = toRefs(state);
|
||||
const { dialogVisible, tabActiveName, form, submitForm, pwd } = toRefs(state);
|
||||
|
||||
const { isFetching: saveBtnLoading, execute: saveInstanceExec } = dbApi.saveInstance.useApi(subimtForm);
|
||||
const { isFetching: testConnBtnLoading, execute: testConnExec } = dbApi.testConn.useApi(subimtForm);
|
||||
const { isFetching: saveBtnLoading, execute: saveInstanceExec } = dbApi.saveInstance.useApi(submitForm);
|
||||
const { isFetching: testConnBtnLoading, execute: testConnExec } = dbApi.testConn.useApi(submitForm);
|
||||
|
||||
watch(props, (newValue: any) => {
|
||||
state.dialogVisible = newValue.visible;
|
||||
@@ -249,7 +253,7 @@ const testConn = async () => {
|
||||
return false;
|
||||
}
|
||||
|
||||
state.subimtForm = await getReqForm();
|
||||
state.submitForm = await getReqForm();
|
||||
await testConnExec();
|
||||
ElMessage.success('连接成功');
|
||||
});
|
||||
@@ -270,7 +274,7 @@ const btnOk = async () => {
|
||||
return false;
|
||||
}
|
||||
|
||||
state.subimtForm = await getReqForm();
|
||||
state.submitForm = await getReqForm();
|
||||
await saveInstanceExec();
|
||||
ElMessage.success('保存成功');
|
||||
emit('val-change', state.form);
|
||||
|
||||
@@ -165,8 +165,8 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent, onBeforeUnmount, onMounted, reactive, ref, toRefs } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { defineAsyncComponent, h, onBeforeUnmount, onMounted, reactive, ref, toRefs } from 'vue';
|
||||
import { ElCheckbox, ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { formatByteSize } from '@/common/utils/format';
|
||||
import { DbInst, registerDbCompletionItemProvider, TabInfo, TabType } from './db';
|
||||
import { NodeType, TagTreeNode } from '../component/tag';
|
||||
@@ -175,7 +175,7 @@ import { dbApi } from './api';
|
||||
import { dispposeCompletionItemProvider } from '@/components/monaco/completionItemProvider';
|
||||
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||
import { ContextmenuItem } from '@/components/contextmenu';
|
||||
import { getDbDialect, schemaDbTypes} from './dialect/index'
|
||||
import { getDbDialect, schemaDbTypes } from './dialect/index';
|
||||
import { sleep } from '@/common/utils/loading';
|
||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||
import { Pane, Splitpanes } from 'splitpanes';
|
||||
@@ -229,21 +229,25 @@ const nodeClickChangeDb = (nodeData: TagTreeNode) => {
|
||||
}
|
||||
};
|
||||
|
||||
// tagpath 节点类型
|
||||
const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||
const dbInfoRes = await dbApi.dbs.request({ tagPath: parentNode.key });
|
||||
const dbInfos = dbInfoRes.list;
|
||||
if (!dbInfos) {
|
||||
return [];
|
||||
}
|
||||
const ContextmenuItemRefresh = new ContextmenuItem('refresh', '刷新').withIcon('RefreshRight').withOnClick((data: any) => reloadNode(data.key));
|
||||
|
||||
// 防止过快加载会出现一闪而过,对眼睛不好
|
||||
await sleep(100);
|
||||
return dbInfos?.map((x: any) => {
|
||||
x.tagPath = parentNode.key;
|
||||
return new TagTreeNode(`${parentNode.key}.${x.id}`, x.name, NodeTypeDbInst).withParams(x);
|
||||
});
|
||||
});
|
||||
// tagpath 节点类型
|
||||
const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath)
|
||||
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||
const dbInfoRes = await dbApi.dbs.request({ tagPath: parentNode.key });
|
||||
const dbInfos = dbInfoRes.list;
|
||||
if (!dbInfos) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 防止过快加载会出现一闪而过,对眼睛不好
|
||||
await sleep(100);
|
||||
return dbInfos?.map((x: any) => {
|
||||
x.tagPath = parentNode.key;
|
||||
return new TagTreeNode(`${parentNode.key}.${x.id}`, x.name, NodeTypeDbInst).withParams(x);
|
||||
});
|
||||
})
|
||||
.withContextMenuItems([ContextmenuItemRefresh]);
|
||||
|
||||
// 数据库实例节点类型
|
||||
const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc((parentNode: TagTreeNode) => {
|
||||
@@ -266,7 +270,7 @@ const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc((p
|
||||
|
||||
// 数据库节点
|
||||
const NodeTypeDb = new NodeType(SqlExecNodeType.Db)
|
||||
.withContextMenuItems([new ContextmenuItem('reloadTables', '刷新').withIcon('RefreshRight').withOnClick((data: any) => reloadNode(data.key))])
|
||||
.withContextMenuItems([ContextmenuItemRefresh])
|
||||
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||
const params = parentNode.params;
|
||||
params.parentKey = parentNode.key;
|
||||
@@ -288,17 +292,17 @@ const NodeTypeDb = new NodeType(SqlExecNodeType.Db)
|
||||
.withNodeClickFunc(nodeClickChangeDb);
|
||||
|
||||
const NodeTypeTables = (params: any) => {
|
||||
let tableKey = `${params.id}.${params.db}.table-menu`;
|
||||
let sqlKey = getSqlMenuNodeKey(params.id, params.db);
|
||||
let tableKey = `${params.id}.${params.db}.table-menu`;
|
||||
let sqlKey = getSqlMenuNodeKey(params.id, params.db);
|
||||
return [
|
||||
new TagTreeNode(`${params.id}.${params.db}.table-menu`, '表', NodeTypeTableMenu).withParams({...params, key:tableKey}).withIcon(TableIcon),
|
||||
new TagTreeNode(sqlKey, 'SQL', NodeTypeSqlMenu).withParams({...params, key:sqlKey}).withIcon(SqlIcon),
|
||||
new TagTreeNode(`${params.id}.${params.db}.table-menu`, '表', NodeTypeTableMenu).withParams({ ...params, key: tableKey }).withIcon(TableIcon),
|
||||
new TagTreeNode(sqlKey, 'SQL', NodeTypeSqlMenu).withParams({ ...params, key: sqlKey }).withIcon(SqlIcon),
|
||||
];
|
||||
};
|
||||
|
||||
// postgres schema模式
|
||||
const NodeTypePostgresSchema = new NodeType(SqlExecNodeType.PgSchema)
|
||||
.withContextMenuItems([new ContextmenuItem('reloadTables', '刷新').withIcon('RefreshRight').withOnClick((data: any) => reloadNode(data.key))])
|
||||
.withContextMenuItems([ContextmenuItemRefresh])
|
||||
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||
const params = parentNode.params;
|
||||
params.parentKey = parentNode.key;
|
||||
@@ -309,7 +313,7 @@ const NodeTypePostgresSchema = new NodeType(SqlExecNodeType.PgSchema)
|
||||
// 数据库表菜单节点
|
||||
const NodeTypeTableMenu = new NodeType(SqlExecNodeType.TableMenu)
|
||||
.withContextMenuItems([
|
||||
new ContextmenuItem('reloadTables', '刷新').withIcon('RefreshRight').withOnClick((data: any) => reloadNode(data.key)),
|
||||
ContextmenuItemRefresh,
|
||||
new ContextmenuItem('createTable', '创建表').withIcon('Plus').withOnClick((data: any) => onEditTable(data)),
|
||||
new ContextmenuItem('tablesOp', '表操作').withIcon('Setting').withOnClick((data: any) => {
|
||||
const params = data.params;
|
||||
@@ -676,12 +680,34 @@ const onDeleteTable = async (data: any) => {
|
||||
const onCopyTable = async (data: any) => {
|
||||
let { db, id, tableName, parentKey } = data.params;
|
||||
|
||||
// 执行sql
|
||||
dbApi.copyTable.request({ id, db, tableName, copyData:true }).then(() => {
|
||||
ElMessage.success('复制成功');
|
||||
setTimeout(() => {
|
||||
parentKey && reloadNode(parentKey);
|
||||
}, 1000);
|
||||
let checked = ref(false);
|
||||
|
||||
// 弹出确认框,并选择是否复制数据
|
||||
await ElMessageBox({
|
||||
title: `复制表【${tableName}】`,
|
||||
type: 'warning',
|
||||
// icon: markRaw(Delete),
|
||||
message: () =>
|
||||
h(ElCheckbox, {
|
||||
label: '是否复制数据?',
|
||||
modelValue: checked.value,
|
||||
'onUpdate:modelValue': (val: boolean | string | number) => {
|
||||
if (typeof val === 'boolean') {
|
||||
checked.value = val;
|
||||
}
|
||||
},
|
||||
}),
|
||||
callback: (action: string) => {
|
||||
if (action === 'confirm') {
|
||||
// 执行sql
|
||||
dbApi.copyTable.request({ id, db, tableName, copyData: checked.value }).then(() => {
|
||||
ElMessage.success('复制成功');
|
||||
setTimeout(() => {
|
||||
parentKey && reloadNode(parentKey);
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import { NodeType, TagTreeNode } from '@/views/ops/component/tag';
|
||||
import { dbApi } from '@/views/ops/db/api';
|
||||
import { sleep } from '@/common/utils/loading';
|
||||
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||
import { getDbDialect, mysqlDbTypes} from '@/views/ops/db/dialect'
|
||||
import { getDbDialect, noSchemaTypes } from '@/views/ops/db/dialect';
|
||||
import TagTreeResourceSelect from '../../component/TagTreeResourceSelect.vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
@@ -90,8 +90,8 @@ const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(asyn
|
||||
});
|
||||
|
||||
/** mysql类型的数据库,没有schema层 */
|
||||
const mysqlType = (type: string) => {
|
||||
return mysqlDbTypes.includes(type);
|
||||
const noSchemaType = (type: string) => {
|
||||
return noSchemaTypes.includes(type);
|
||||
};
|
||||
|
||||
// 数据库实例节点类型
|
||||
@@ -99,7 +99,7 @@ const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc((p
|
||||
const params = parentNode.params;
|
||||
const dbs = params.database.split(' ')?.sort();
|
||||
let fn: NodeType;
|
||||
if (mysqlType(params.type)) {
|
||||
if (noSchemaType(params.type)) {
|
||||
fn = MysqlNodeTypes;
|
||||
} else {
|
||||
fn = PgNodeTypes;
|
||||
@@ -117,7 +117,7 @@ const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc((p
|
||||
db: x,
|
||||
})
|
||||
.withIcon(DbIcon);
|
||||
if (mysqlType(params.type)) {
|
||||
if (noSchemaType(params.type)) {
|
||||
tagTreeNode.isLeaf = true;
|
||||
}
|
||||
return tagTreeNode;
|
||||
|
||||
@@ -128,12 +128,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, nextTick, onMounted, reactive, toRefs, ref, unref } from 'vue';
|
||||
import { h, nextTick, onMounted, reactive, ref, toRefs, unref } from 'vue';
|
||||
import { getToken } from '@/common/utils/storage';
|
||||
import { notBlank } from '@/common/assert';
|
||||
import { format as sqlFormatter } from 'sql-formatter';
|
||||
import config from '@/common/config';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { ElMessage, ElMessageBox, ElNotification } from 'element-plus';
|
||||
|
||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
import { editor } from 'monaco-editor';
|
||||
@@ -146,11 +146,10 @@ import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
|
||||
import { joinClientParams } from '@/common/request';
|
||||
import { buildProgressProps } from '@/components/progress-notify/progress-notify';
|
||||
import ProgressNotify from '@/components/progress-notify/progress-notify.vue';
|
||||
import { ElNotification } from 'element-plus';
|
||||
import syssocket from '@/common/syssocket';
|
||||
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||
import { getDbDialect } from '../../dialect';
|
||||
import { Splitpanes, Pane } from 'splitpanes';
|
||||
import { Pane, Splitpanes } from 'splitpanes';
|
||||
|
||||
const emits = defineEmits(['saveSqlSuccess']);
|
||||
|
||||
@@ -357,6 +356,7 @@ const onRunSql = async (newTab = false) => {
|
||||
const colAndData: any = data.value;
|
||||
if (!colAndData.res || colAndData.res.length === 0) {
|
||||
ElMessage.warning('未查询到结果集');
|
||||
return;
|
||||
}
|
||||
|
||||
// 要实时响应,故需要用索引改变数据才生效
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<el-input
|
||||
v-if="dataType == DataType.String"
|
||||
:ref="(el: any) => focus && el?.focus()"
|
||||
:disabled="disabled"
|
||||
@blur="handleBlur"
|
||||
:class="`w100 mb4 ${showEditorIcon ? 'string-input-container-show-icon' : ''}`"
|
||||
input-style="text-align: center; height: 26px;"
|
||||
@@ -16,6 +17,7 @@
|
||||
<el-input
|
||||
v-else-if="dataType == DataType.Number"
|
||||
:ref="(el: any) => focus && el?.focus()"
|
||||
:disabled="disabled"
|
||||
@blur="handleBlur"
|
||||
class="w100 mb4"
|
||||
input-style="text-align: center; height: 26px;"
|
||||
@@ -28,6 +30,7 @@
|
||||
<el-date-picker
|
||||
v-else-if="dataType == DataType.Date"
|
||||
:ref="(el: any) => focus && el?.focus()"
|
||||
:disabled="disabled"
|
||||
@change="emit('blur')"
|
||||
@blur="handleBlur"
|
||||
class="edit-time-picker mb4"
|
||||
@@ -43,6 +46,7 @@
|
||||
<el-date-picker
|
||||
v-else-if="dataType == DataType.DateTime"
|
||||
:ref="(el: any) => focus && el?.focus()"
|
||||
:disabled="disabled"
|
||||
@change="handleBlur"
|
||||
@blur="handleBlur"
|
||||
class="edit-time-picker mb4"
|
||||
@@ -58,6 +62,7 @@
|
||||
<el-time-picker
|
||||
v-else-if="dataType == DataType.Time"
|
||||
:ref="(el: any) => focus && el?.focus()"
|
||||
:disabled="disabled"
|
||||
@change="handleBlur"
|
||||
@blur="handleBlur"
|
||||
class="edit-time-picker mb4"
|
||||
@@ -71,7 +76,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Ref, ref, computed } from 'vue';
|
||||
import { computed, ref, Ref } from 'vue';
|
||||
import { ElInput } from 'element-plus';
|
||||
import { DataType } from '../../dialect/index';
|
||||
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||
@@ -83,11 +88,13 @@ export interface ColumnFormItemProps {
|
||||
focus?: boolean; // 是否获取焦点
|
||||
placeholder?: string;
|
||||
columnName?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<ColumnFormItemProps>(), {
|
||||
focus: false,
|
||||
dataType: DataType.String,
|
||||
disabled: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'blur']);
|
||||
|
||||
@@ -715,9 +715,13 @@ const submitUpdateFields = async () => {
|
||||
const db = state.db;
|
||||
let res = '';
|
||||
const dbDialect = getDbDialect(dbInst.type);
|
||||
|
||||
let schema = '';
|
||||
let dbArr = db.split('/');
|
||||
if (dbArr.length == 2) {
|
||||
schema = dbInst.wrapName(dbArr[1]) + '.';
|
||||
}
|
||||
for (let updateRow of cellUpdateMap.values()) {
|
||||
let sql = `UPDATE ${dbInst.wrapName(state.table)} SET `;
|
||||
let sql = `UPDATE ${schema}${dbInst.wrapName(state.table)} SET `;
|
||||
const rowData = updateRow.rowData;
|
||||
// 主键列信息
|
||||
const primaryKey = await dbInst.loadTableColumn(db, state.table);
|
||||
|
||||
@@ -170,8 +170,8 @@
|
||||
:page-sizes="pageSizes"
|
||||
></el-pagination>
|
||||
</el-row>
|
||||
<div style="font-size: 12px; padding: 0 10px; color: #606266">
|
||||
<span>{{ state.sql }}</span>
|
||||
<div style="padding: 0 10px">
|
||||
<span style="color: var(--el-color-info-light-3)" class="font10 el-text el-text--small is-truncated">{{ state.sql }}</span>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="conditionDialog.visible" :title="conditionDialog.title" width="420px">
|
||||
@@ -211,13 +211,14 @@
|
||||
class="w100 mb5"
|
||||
:prop="column.columnName"
|
||||
:label="column.columnName"
|
||||
:required="column.nullable != 'YES' && column.columnKey != 'PRI'"
|
||||
:required="column.nullable != 'YES' && !column.isPrimaryKey && !column.isIdentity"
|
||||
>
|
||||
<ColumnFormItem
|
||||
v-model="addDataDialog.data[`${column.columnName}`]"
|
||||
:data-type="dbDialect.getDataType(column.columnType)"
|
||||
:placeholder="`${column.columnType} ${column.columnComment}`"
|
||||
:column-name="column.columnName"
|
||||
:disabled="column.isIdentity"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@@ -372,7 +373,7 @@ const selectData = async () => {
|
||||
|
||||
const countRes = await dbInst.runSql(db, dbInst.getDefaultCountSql(table, state.condition));
|
||||
state.count = parseInt(countRes.res[0].count || countRes.res[0].COUNT || 0);
|
||||
let sql = dbInst.getDefaultSelectSql(table, state.condition, state.orderBy, state.pageNum, state.pageSize);
|
||||
let sql = dbInst.getDefaultSelectSql(db, table, state.condition, state.orderBy, state.pageNum, state.pageSize);
|
||||
state.sql = sql;
|
||||
if (state.count > 0) {
|
||||
const colAndData: any = await dbInst.runSql(db, sql);
|
||||
@@ -566,7 +567,13 @@ const addRow = async () => {
|
||||
}
|
||||
let columnNames = Object.keys(obj).join(',');
|
||||
let values = Object.values(obj).join(',');
|
||||
let sql = `INSERT INTO ${dbInst.wrapName(props.tableName)} (${columnNames}) VALUES (${values});`;
|
||||
// 获取schema
|
||||
let schema = '';
|
||||
let arr = props.dbName?.split('/');
|
||||
if (arr && arr.length > 1) {
|
||||
schema = dbInst.wrapName(arr[1]) + '.';
|
||||
}
|
||||
let sql = `INSERT INTO ${schema}${dbInst.wrapName(props.tableName)} (${columnNames}) VALUES (${values});`;
|
||||
dbInst.promptExeSql(props.dbName, sql, null, () => {
|
||||
closeAddDataDialog();
|
||||
onRefresh();
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
v-else-if="item.prop === 'auto_increment'"
|
||||
size="small"
|
||||
v-model="scope.row.auto_increment"
|
||||
:disabled="dbType === DbType.postgresql"
|
||||
:disabled="disableEditIncr()"
|
||||
/>
|
||||
|
||||
<el-input v-else-if="item.prop === 'remark'" size="small" v-model="scope.row.remark" />
|
||||
@@ -99,9 +99,7 @@
|
||||
<el-checkbox v-if="item.prop === 'unique'" size="small" v-model="scope.row.unique" @change="indexChanges(scope.row)">
|
||||
</el-checkbox>
|
||||
|
||||
<el-select v-if="item.prop === 'indexType'" disabled size="small" v-model="scope.row.indexType">
|
||||
<el-option v-for="typeValue in indexTypeList" :key="typeValue" :value="typeValue">{{ typeValue }}</el-option>
|
||||
</el-select>
|
||||
<el-input v-if="item.prop === 'indexType'" disabled size="small" v-model="scope.row.indexType" />
|
||||
|
||||
<el-input v-if="item.prop === 'indexComment'" size="small" v-model="scope.row.indexComment"> </el-input>
|
||||
|
||||
@@ -132,7 +130,7 @@
|
||||
import { reactive, ref, toRefs, watch } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import SqlExecBox from '../sqleditor/SqlExecBox';
|
||||
import { DbDialect, DbType, getDbDialect, IndexDefinition, RowDefinition } from '../../dialect/index';
|
||||
import { DbType, getDbDialect, IndexDefinition, RowDefinition } from '../../dialect/index';
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
@@ -172,7 +170,6 @@ const state = reactive({
|
||||
btnloading: false,
|
||||
activeName: '1',
|
||||
columnTypeList: dbDialect.getInfo().columnTypes,
|
||||
indexTypeList: ['BTREE', 'NORMAL'], // mysql索引类型详解 http://c.biancheng.net/view/7897.html
|
||||
tableData: {
|
||||
fields: {
|
||||
colNames: [
|
||||
@@ -268,7 +265,7 @@ const state = reactive({
|
||||
},
|
||||
});
|
||||
|
||||
const { dialogVisible, btnloading, activeName, indexTypeList, tableData } = toRefs(state);
|
||||
const { dialogVisible, btnloading, activeName, tableData } = toRefs(state);
|
||||
|
||||
watch(props, async (newValue) => {
|
||||
state.dialogVisible = newValue.visible;
|
||||
@@ -408,7 +405,7 @@ const genSql = () => {
|
||||
} else if (state.activeName === '2') {
|
||||
// 修改索引
|
||||
let changeData = filterChangedData(state.tableData.indexs.oldIndexs, state.tableData.indexs.res, 'indexName');
|
||||
return dbDialect.getModifyIndexSql(data.tableName, changeData);
|
||||
return dbDialect.getModifyIndexSql(data, data.tableName, changeData);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -418,28 +415,8 @@ const reset = () => {
|
||||
formRef.value.resetFields();
|
||||
state.tableData.tableName = '';
|
||||
state.tableData.tableComment = '';
|
||||
state.tableData.fields.res = [
|
||||
{
|
||||
name: '',
|
||||
type: '',
|
||||
value: '',
|
||||
length: '',
|
||||
numScale: '',
|
||||
notNull: false,
|
||||
pri: false,
|
||||
auto_increment: false,
|
||||
remark: '',
|
||||
},
|
||||
];
|
||||
state.tableData.indexs.res = [
|
||||
{
|
||||
indexName: '',
|
||||
columnNames: [],
|
||||
unique: false,
|
||||
indexType: 'BTREE',
|
||||
indexComment: '',
|
||||
},
|
||||
];
|
||||
state.tableData.fields.res = [];
|
||||
state.tableData.indexs.res = [];
|
||||
};
|
||||
|
||||
const indexChanges = (row: any) => {
|
||||
@@ -460,6 +437,21 @@ const indexChanges = (row: any) => {
|
||||
row.indexComment = `${tableData.value.tableName}表(${name.replaceAll('_', ',')})${commentSuffix}`;
|
||||
};
|
||||
|
||||
const disableEditIncr = () => {
|
||||
if (DbType.postgresql === props.dbType) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果是mssql则不能修改自增
|
||||
if (props.data?.edit) {
|
||||
if (DbType.mssql === props.dbType) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(newValue: any) => {
|
||||
@@ -492,8 +484,8 @@ watch(
|
||||
length,
|
||||
numScale: a.numScale,
|
||||
notNull: a.nullable !== 'YES',
|
||||
pri: a.columnKey === 'PRI',
|
||||
auto_increment: a.columnKey === 'PRI' /*a.extra?.indexOf('auto_increment') > -1*/,
|
||||
pri: a.isPrimaryKey,
|
||||
auto_increment: a.isIdentity /*a.extra?.indexOf('auto_increment') > -1*/,
|
||||
remark: a.columnComment,
|
||||
};
|
||||
state.tableData.fields.res.push(data);
|
||||
@@ -513,7 +505,7 @@ watch(
|
||||
let data = {
|
||||
indexName: a.indexName,
|
||||
columnNames: a.columnName?.split(','),
|
||||
unique: a.nonUnique === 0 || false,
|
||||
unique: a.isUnique || false,
|
||||
indexType: a.indexType,
|
||||
indexComment: a.indexComment,
|
||||
};
|
||||
|
||||
@@ -7,6 +7,10 @@ import { editor, languages, Position } from 'monaco-editor';
|
||||
|
||||
import { registerCompletionItemProvider } from '@/components/monaco/completionItemProvider';
|
||||
import { DbDialect, EditorCompletionItem, getDbDialect } from './dialect';
|
||||
import { type RemovableRef, useLocalStorage } from '@vueuse/core';
|
||||
|
||||
const hintsStorage: RemovableRef<Map<string, any>> = useLocalStorage('db-table-hints', new Map());
|
||||
const tableStorage: RemovableRef<Map<string, any>> = useLocalStorage('db-tables', new Map());
|
||||
|
||||
const dbInstCache: Map<number, DbInst> = new Map();
|
||||
|
||||
@@ -58,14 +62,15 @@ export class DbInst {
|
||||
if (!dbName) {
|
||||
throw new Error('dbName不能为空');
|
||||
}
|
||||
let db = this.dbs.get(dbName);
|
||||
let key = `${this.id}_${dbName}`;
|
||||
let db = this.dbs.get(key);
|
||||
if (db) {
|
||||
return db;
|
||||
}
|
||||
console.info(`new db -> dbId: ${this.id}, dbName: ${dbName}`);
|
||||
db = new Db();
|
||||
db.name = dbName;
|
||||
this.dbs.set(dbName, db);
|
||||
this.dbs.set(key, db);
|
||||
return db;
|
||||
}
|
||||
|
||||
@@ -77,17 +82,22 @@ export class DbInst {
|
||||
*/
|
||||
async loadTables(dbName: string, reload?: boolean) {
|
||||
const db = this.getDb(dbName);
|
||||
// 优先从 table map中获取
|
||||
let tables = db.tables;
|
||||
let key = this.dbTablesKey(dbName);
|
||||
let tables = tableStorage.value.get(key);
|
||||
// 优先从 table 缓存中获取
|
||||
if (!reload && tables) {
|
||||
db.tables = tables;
|
||||
return tables;
|
||||
}
|
||||
// 重置列信息缓存与表提示信息
|
||||
db.columnsMap?.clear();
|
||||
db.tableHints = null;
|
||||
console.log(`load tables -> dbName: ${dbName}`);
|
||||
tables = await dbApi.tableInfos.request({ id: this.id, db: dbName });
|
||||
tableStorage.value.set(key, tables);
|
||||
db.tables = tables;
|
||||
|
||||
// 异步加载表提示信息
|
||||
this.loadDbHints(dbName, true).then(() => {});
|
||||
return tables;
|
||||
}
|
||||
|
||||
@@ -169,18 +179,30 @@ export class DbInst {
|
||||
return this.getDb(dbName).getColumn(table, columnName);
|
||||
}
|
||||
|
||||
dbTableHintsKey(dbName: string) {
|
||||
return `db-table-hints_${this.id}_${dbName}`;
|
||||
}
|
||||
|
||||
dbTablesKey(dbName: string) {
|
||||
return `db-tables_${this.id}_${dbName}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取库信息提示
|
||||
*/
|
||||
async loadDbHints(dbName: string) {
|
||||
async loadDbHints(dbName: string, reload?: boolean) {
|
||||
const db = this.getDb(dbName);
|
||||
if (db.tableHints) {
|
||||
return db.tableHints;
|
||||
let key = this.dbTableHintsKey(dbName);
|
||||
let hints = hintsStorage.value.get(key);
|
||||
if (!reload && hints) {
|
||||
db.tableHints = hints;
|
||||
return hints;
|
||||
}
|
||||
console.log(`load db-hits -> dbName: ${dbName}`);
|
||||
const hits = await dbApi.hintTables.request({ id: this.id, db: db.name });
|
||||
db.tableHints = hits;
|
||||
return hits;
|
||||
hints = await dbApi.hintTables.request({ id: this.id, db: db.name });
|
||||
db.tableHints = hints;
|
||||
hintsStorage.value.set(key, hints);
|
||||
return hints;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -225,8 +247,8 @@ export class DbInst {
|
||||
};
|
||||
|
||||
// 获取指定表的默认查询sql
|
||||
getDefaultSelectSql(table: string, condition: string, orderBy: string, pageNum: number, limit: number = DbInst.DefaultLimit) {
|
||||
return getDbDialect(this.type).getDefaultSelectSql(table, condition, orderBy, pageNum, limit);
|
||||
getDefaultSelectSql(db: string, table: string, condition: string, orderBy: string, pageNum: number, limit: number = DbInst.DefaultLimit) {
|
||||
return getDbDialect(this.type).getDefaultSelectSql(db, table, condition, orderBy, pageNum, limit);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -275,6 +297,7 @@ export class DbInst {
|
||||
sql,
|
||||
dbId: this.id,
|
||||
db,
|
||||
dbType: getDbDialect(this.type).getInfo().formatSqlDialect,
|
||||
runSuccessCallback: successFunc,
|
||||
cancelCallback: cancelFunc,
|
||||
});
|
||||
@@ -441,7 +464,7 @@ class Db {
|
||||
getColumn(table: string, columnName: string = '') {
|
||||
const cols = this.getColumns(table);
|
||||
if (!columnName) {
|
||||
const col = cols.find((c: any) => c.columnKey == 'PRI');
|
||||
const col = cols.find((c: any) => c.isPrimaryKey);
|
||||
return col || cols[0];
|
||||
}
|
||||
return cols.find((c: any) => c.columnName == columnName);
|
||||
|
||||
@@ -375,12 +375,12 @@ class DMDialect implements DbDialect {
|
||||
return dmDialectInfo;
|
||||
}
|
||||
|
||||
getDefaultSelectSql(table: string, condition: string, orderBy: string, pageNum: number, limit: number) {
|
||||
getDefaultSelectSql(db: string, table: string, condition: string, orderBy: string, pageNum: number, limit: number) {
|
||||
return `SELECT * FROM "${table}" ${condition ? 'WHERE ' + condition : ''} ${orderBy ? orderBy : ''} ${this.getPageSql(pageNum, limit)};`;
|
||||
}
|
||||
|
||||
getPageSql(pageNum: number, limit: number) {
|
||||
return ` OFFSET ${(pageNum - 1) * limit} LIMIT ${limit};`;
|
||||
return ` OFFSET ${(pageNum - 1) * limit} LIMIT ${limit}`;
|
||||
}
|
||||
|
||||
getDefaultRows(): RowDefinition[] {
|
||||
@@ -620,7 +620,7 @@ class DMDialect implements DbDialect {
|
||||
return dropPkSql + modifySql + dropSql + renameSql + addPkSql + commentSql;
|
||||
}
|
||||
|
||||
getModifyIndexSql(tableName: string, changeData: { del: any[]; add: any[]; upd: any[] }): string {
|
||||
getModifyIndexSql(tableData: any, tableName: string, changeData: { del: any[]; add: any[]; upd: any[] }): string {
|
||||
// 不能直接修改索引名或字段、需要先删后加
|
||||
let dropIndexNames: string[] = [];
|
||||
let addIndexs: any[] = [];
|
||||
|
||||
@@ -4,6 +4,7 @@ import { DMDialect } from '@/views/ops/db/dialect/dm_dialect';
|
||||
import { OracleDialect } from '@/views/ops/db/dialect/oracle_dialect';
|
||||
import { MariadbDialect } from '@/views/ops/db/dialect/mariadb_dialect';
|
||||
import { SqliteDialect } from '@/views/ops/db/dialect/sqlite_dialect';
|
||||
import { MssqlDialect } from '@/views/ops/db/dialect/mssql_dialect';
|
||||
|
||||
export interface sqlColumnType {
|
||||
udtName: string;
|
||||
@@ -113,15 +114,16 @@ export const DbType = {
|
||||
dm: 'dm', // 达梦
|
||||
oracle: 'oracle',
|
||||
sqlite: 'sqlite',
|
||||
mssql: 'mssql', // ms sqlserver
|
||||
};
|
||||
|
||||
// mysql兼容的数据库
|
||||
export const mysqlDbTypes = [DbType.mysql, DbType.mariadb, DbType.sqlite];
|
||||
export const noSchemaTypes = [DbType.mysql, DbType.mariadb, DbType.sqlite];
|
||||
|
||||
// 有schema层的数据库
|
||||
export const schemaDbTypes = [DbType.postgresql, DbType.dm, DbType.oracle];
|
||||
export const schemaDbTypes = [DbType.postgresql, DbType.dm, DbType.oracle, DbType.mssql];
|
||||
|
||||
export const editDbTypes = [...mysqlDbTypes, ...schemaDbTypes];
|
||||
export const editDbTypes = [...noSchemaTypes, ...schemaDbTypes];
|
||||
|
||||
export const compatibleMysql = (dbType: string): boolean => {
|
||||
switch (dbType) {
|
||||
@@ -141,13 +143,14 @@ export interface DbDialect {
|
||||
|
||||
/**
|
||||
* 获取默认查询sql
|
||||
* @param db 数据库信息
|
||||
* @param table 表名
|
||||
* @param condition 条件
|
||||
* @param orderBy 排序
|
||||
* @param pageNum 页数
|
||||
* @param limit 条数
|
||||
*/
|
||||
getDefaultSelectSql(table: string, condition: string, orderBy: string, pageNum: number, limit: number): string;
|
||||
getDefaultSelectSql(db: string, table: string, condition: string, orderBy: string, pageNum: number, limit: number): string;
|
||||
|
||||
getPageSql(pageNum: number, limit: number): string;
|
||||
|
||||
@@ -183,10 +186,11 @@ export interface DbDialect {
|
||||
|
||||
/**
|
||||
* 生成编辑索引sql
|
||||
* @param tableData 表数据,包含表名、列数据、索引数据
|
||||
* @param tableName 表名
|
||||
* @param changeData 改变数据
|
||||
*/
|
||||
getModifyIndexSql(tableName: string, changeData: { del: any[]; add: any[]; upd: any[] }): string;
|
||||
getModifyIndexSql(tableData: any, tableName: string, changeData: { del: any[]; add: any[]; upd: any[] }): string;
|
||||
|
||||
/** 通过数据库字段类型,返回基本数据类型 */
|
||||
getDataType(columnType: string): DataType;
|
||||
@@ -201,6 +205,7 @@ let postgresDialect = new PostgresqlDialect();
|
||||
let dmDialect = new DMDialect();
|
||||
let oracleDialect = new OracleDialect();
|
||||
let sqliteDialect = new SqliteDialect();
|
||||
let mssqlDialect = new MssqlDialect();
|
||||
|
||||
export const getDbDialect = (dbType: string | undefined): DbDialect => {
|
||||
if (!dbType) {
|
||||
@@ -219,6 +224,8 @@ export const getDbDialect = (dbType: string | undefined): DbDialect => {
|
||||
return oracleDialect;
|
||||
case DbType.sqlite:
|
||||
return sqliteDialect;
|
||||
case DbType.mssql:
|
||||
return mssqlDialect;
|
||||
default:
|
||||
throw new Error('不支持的数据库');
|
||||
}
|
||||
|
||||
404
mayfly_go_web/src/views/ops/db/dialect/mssql_dialect.ts
Normal file
404
mayfly_go_web/src/views/ops/db/dialect/mssql_dialect.ts
Normal file
@@ -0,0 +1,404 @@
|
||||
import { DbInst } from '../db';
|
||||
import { commonCustomKeywords, DataType, DbDialect, DialectInfo, EditorCompletion, EditorCompletionItem, IndexDefinition, RowDefinition } from './index';
|
||||
import { language as sqlLanguage } from 'monaco-editor/esm/vs/basic-languages/sql/sql.js';
|
||||
|
||||
export { MSSQL_TYPE_LIST, MssqlDialect };
|
||||
|
||||
// 参考官方文档:https://docs.microsoft.com/zh-cn/sql/t-sql/data-types/data-types-transact-sql?view=sql-server-ver15
|
||||
const MSSQL_TYPE_LIST = [
|
||||
//精确数字
|
||||
'bigint',
|
||||
'numeric',
|
||||
'bit',
|
||||
'smallint',
|
||||
'decimal',
|
||||
'smallmoney',
|
||||
'int',
|
||||
'tinyint',
|
||||
'money',
|
||||
// 近似数字
|
||||
'float',
|
||||
'real',
|
||||
// 日期和时间
|
||||
'date',
|
||||
'datetimeoffset',
|
||||
'datetime2',
|
||||
'smalldatetime',
|
||||
'datetime',
|
||||
'time',
|
||||
// 字符串
|
||||
'char',
|
||||
'varchar',
|
||||
'text',
|
||||
'nchar',
|
||||
'nvarchar',
|
||||
'ntext',
|
||||
'binary',
|
||||
'varbinary',
|
||||
|
||||
// 其他
|
||||
'cursor',
|
||||
'rowversion',
|
||||
'hierarchyid',
|
||||
'uniqueidentifier',
|
||||
'sql_variant',
|
||||
'xml',
|
||||
'table',
|
||||
// 空间几何类型 参照 https://learn.microsoft.com/zh-cn/sql/t-sql/spatial-geometry/spatial-types-geometry-transact-sql?view=sql-server-ver15
|
||||
'geometry',
|
||||
// 空间地理类型 参照 https://learn.microsoft.com/zh-cn/sql/t-sql/spatial-geography/spatial-types-geography?view=sql-server-ver15
|
||||
'geography',
|
||||
];
|
||||
// 函数参考官方文档 https://learn.microsoft.com/zh-cn/sql/t-sql/functions/functions?view=sql-server-ver15
|
||||
|
||||
let mssqlDialectInfo: DialectInfo;
|
||||
|
||||
const customKeywords: EditorCompletionItem[] = [
|
||||
{
|
||||
label: 'select top ',
|
||||
description: 'keyword',
|
||||
insertText: 'select top 100 * from',
|
||||
},
|
||||
{
|
||||
label: 'select page ',
|
||||
description: 'keyword',
|
||||
insertText: 'SELECT *, 0 AS _ORDER_F_ FROM table_name \n ORDER BY _ORDER_F_ \n OFFSET 0 ROWS FETCH NEXT 25 ROWS ONLY;',
|
||||
},
|
||||
];
|
||||
|
||||
const fixedLengthTypes = [
|
||||
'int',
|
||||
'bigint',
|
||||
'smallint',
|
||||
'tinyint',
|
||||
'float',
|
||||
'real',
|
||||
'datetime',
|
||||
'smalldatetime',
|
||||
'date',
|
||||
'time',
|
||||
'datetime2',
|
||||
'datetimeoffset',
|
||||
'bit',
|
||||
'uniqueidentifier',
|
||||
'geometry',
|
||||
'geography',
|
||||
];
|
||||
|
||||
class MssqlDialect implements DbDialect {
|
||||
getInfo(): DialectInfo {
|
||||
if (mssqlDialectInfo) {
|
||||
return mssqlDialectInfo;
|
||||
}
|
||||
|
||||
let { keywords, operators, builtinVariables, builtinFunctions } = sqlLanguage;
|
||||
let functions = builtinFunctions.map((a: string): EditorCompletionItem => ({ label: a, insertText: `${a}()`, description: 'func' }));
|
||||
|
||||
let excludeKeywords = new Set(operators);
|
||||
let editorCompletions: EditorCompletion = {
|
||||
keywords: keywords
|
||||
.filter((a: string) => !excludeKeywords.has(a)) // 移除已存在的operator、function
|
||||
.map((a: string): EditorCompletionItem => ({ label: a, description: 'keyword' }))
|
||||
.concat(customKeywords)
|
||||
.concat(commonCustomKeywords.map((a): EditorCompletionItem => ({ label: a, description: 'keyword' }))),
|
||||
operators: operators.map((a: string): EditorCompletionItem => ({ label: a, description: 'operator' })),
|
||||
functions,
|
||||
variables: builtinVariables.map((a: string): EditorCompletionItem => ({ label: a, description: 'var' })),
|
||||
};
|
||||
|
||||
mssqlDialectInfo = {
|
||||
icon: 'iconfont icon-MSSQLNATIVE',
|
||||
defaultPort: 1433,
|
||||
formatSqlDialect: 'transactsql',
|
||||
columnTypes: MSSQL_TYPE_LIST.map((a) => ({ udtName: a, dataType: a, desc: '', space: '' })),
|
||||
editorCompletions,
|
||||
};
|
||||
return mssqlDialectInfo;
|
||||
}
|
||||
|
||||
getDefaultSelectSql(db: string, table: string, condition: string, orderBy: string, pageNum: number, limit: number) {
|
||||
let schema = db.split('/')[1];
|
||||
return `SELECT *, 0 AS _MAY_ORDER_F_ FROM ${this.quoteIdentifier(schema)}.${this.quoteIdentifier(table)} ${condition ? 'WHERE ' + condition : ''} ${
|
||||
orderBy ? orderBy + ', _MAY_ORDER_F_' : 'order by _MAY_ORDER_F_'
|
||||
} ${this.getPageSql(pageNum, limit)};`.toUpperCase();
|
||||
}
|
||||
|
||||
getPageSql(pageNum: number, limit: number) {
|
||||
return ` offset ${(pageNum - 1) * limit} rows fetch next ${limit} rows only`.toUpperCase();
|
||||
}
|
||||
|
||||
getDefaultRows(): RowDefinition[] {
|
||||
return [
|
||||
{ name: 'id', type: 'bigint', length: '', numScale: '', value: '', notNull: true, pri: true, auto_increment: true, remark: '主键ID' },
|
||||
{ name: 'creator_id', type: 'bigint', length: '20', numScale: '', value: '', notNull: true, pri: false, auto_increment: false, remark: '创建人id' },
|
||||
{
|
||||
name: 'creator',
|
||||
type: 'varchar',
|
||||
length: '100',
|
||||
numScale: '',
|
||||
value: '',
|
||||
notNull: true,
|
||||
pri: false,
|
||||
auto_increment: false,
|
||||
remark: '创建人姓名',
|
||||
},
|
||||
{
|
||||
name: 'create_time',
|
||||
type: 'datetime2',
|
||||
length: '',
|
||||
numScale: '',
|
||||
value: 'CURRENT_TIMESTAMP',
|
||||
notNull: true,
|
||||
pri: false,
|
||||
auto_increment: false,
|
||||
remark: '创建时间',
|
||||
},
|
||||
{ name: 'updator_id', type: 'bigint', length: '20', numScale: '', value: '', notNull: true, pri: false, auto_increment: false, remark: '修改人id' },
|
||||
{
|
||||
name: 'updator',
|
||||
type: 'varchar',
|
||||
length: '100',
|
||||
numScale: '',
|
||||
value: '',
|
||||
notNull: true,
|
||||
pri: false,
|
||||
auto_increment: false,
|
||||
remark: '修改人姓名',
|
||||
},
|
||||
{
|
||||
name: 'update_time',
|
||||
type: 'datetime2',
|
||||
length: '',
|
||||
numScale: '',
|
||||
value: 'CURRENT_TIMESTAMP',
|
||||
notNull: true,
|
||||
pri: false,
|
||||
auto_increment: false,
|
||||
remark: '修改时间',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getDefaultIndex(): IndexDefinition {
|
||||
return {
|
||||
indexName: '',
|
||||
columnNames: [],
|
||||
unique: false,
|
||||
indexType: 'NONCLUSTERED',
|
||||
indexComment: '',
|
||||
};
|
||||
}
|
||||
|
||||
quoteIdentifier = (name: string) => {
|
||||
return `[${name}]`;
|
||||
};
|
||||
|
||||
genColumnBasicSql(cl: any): string {
|
||||
let val = cl.value ? (cl.value === 'CURRENT_TIMESTAMP' ? cl.value : `'${cl.value}'`) : '';
|
||||
let defVal = val ? `DEFAULT ${val}` : '';
|
||||
// mssql哪些字段允许有长度
|
||||
let length = '';
|
||||
if (!fixedLengthTypes.includes(cl.type)) {
|
||||
length = cl.length ? `(${cl.length})` : '';
|
||||
}
|
||||
return ` ${this.quoteIdentifier(cl.name)} ${cl.type}${length} ${cl.auto_increment ? 'IDENTITY(1,1)' : ''} ${defVal} ${cl.notNull ? 'NOT NULL' : 'NULL'} `;
|
||||
}
|
||||
getCreateTableSql(data: any): string {
|
||||
let schema = data.db.split('/')[1];
|
||||
|
||||
// 创建表结构
|
||||
let pks = [] as string[];
|
||||
let fields: string[] = [];
|
||||
let fieldComments: string[] = [];
|
||||
data.fields.res.forEach((item: any) => {
|
||||
item.name && fields.push(this.genColumnBasicSql(item));
|
||||
item.remark &&
|
||||
fieldComments.push(
|
||||
`EXECUTE sp_addextendedproperty N'MS_Description', N'${item.remark}', N'SCHEMA', N'${schema}', N'TABLE', N'${data.tableName}', N'COLUMN', N'${item.name}'`
|
||||
);
|
||||
if (item.pri) {
|
||||
pks.push(`${this.quoteIdentifier(item.name)}`);
|
||||
}
|
||||
});
|
||||
|
||||
let baseTable = `${this.quoteIdentifier(schema)}.${this.quoteIdentifier(data.tableName)}`;
|
||||
|
||||
// 建表语句
|
||||
let createTable = `CREATE TABLE ${baseTable}
|
||||
( ${fields.join(',')}
|
||||
${pks.length > 0 ? `, PRIMARY KEY CLUSTERED (${pks.join(',')})` : ''}
|
||||
);`;
|
||||
|
||||
let createIndexSql = this.getCreateIndexSql(data);
|
||||
|
||||
// 表注释
|
||||
if (data.tableComment) {
|
||||
createTable += ` EXECUTE sp_addextendedproperty N'MS_Description', N'${data.tableComment}', N'SCHEMA', N'${schema}', N'TABLE', N'${data.tableName}';`;
|
||||
}
|
||||
|
||||
return createTable + createIndexSql + fieldComments.join(';');
|
||||
}
|
||||
|
||||
getCreateIndexSql(data: any): string {
|
||||
// CREATE UNIQUE NONCLUSTERED INDEX [aaa]
|
||||
// ON [dbo].[无标题] (
|
||||
// [id],
|
||||
// [name]
|
||||
// )
|
||||
let schema = data.db.split('/')[1];
|
||||
let baseTable = `${this.quoteIdentifier(schema)}.${this.quoteIdentifier(data.tableName)}`;
|
||||
|
||||
let indexComment = [] as string[];
|
||||
|
||||
// 创建索引
|
||||
let sql: string[] = [];
|
||||
data.indexs.res.forEach((a: any) => {
|
||||
let columnNames = a.columnNames.map((b: string) => `${this.quoteIdentifier(b)}`);
|
||||
sql.push(` CREATE ${a.unique ? 'UNIQUE' : ''} NONCLUSTERED INDEX ${this.quoteIdentifier(a.indexName)} on ${baseTable} (${columnNames.join(',')})`);
|
||||
if (a.indexComment) {
|
||||
indexComment.push(
|
||||
`EXECUTE sp_addextendedproperty N'MS_Description', N'${a.indexComment}', N'SCHEMA', N'${schema}', N'TABLE', N'${data.tableName}', N'INDEX', N'${a.indexName}'`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return sql.join(';') + ';' + indexComment.join(';');
|
||||
}
|
||||
|
||||
getModifyColumnSql(tableData: any, tableName: string, changeData: { del: RowDefinition[]; add: RowDefinition[]; upd: RowDefinition[] }): string {
|
||||
// sql执行顺序
|
||||
// 1. 删除字段
|
||||
// 2. 添加字段
|
||||
// 3. 修改字段名字
|
||||
// 4. 修改字段类型
|
||||
// 5. 修改字段注释
|
||||
// 6. 添加字段注释
|
||||
|
||||
let schema = tableData.db.split('/')[1];
|
||||
let baseTable = `${this.quoteIdentifier(schema)}.${this.quoteIdentifier(tableName)}`;
|
||||
|
||||
let delSql = '';
|
||||
let addArr = [] as string[];
|
||||
let renameArr = [] as string[];
|
||||
let updArr = [] as string[];
|
||||
let changeCommentArr = [] as string[];
|
||||
let addCommentArr = [] as string[];
|
||||
|
||||
if (changeData.del.length > 0) {
|
||||
delSql = `ALTER TABLE ${baseTable} DROP ${changeData.del.map((a) => 'COLUMN ' + this.quoteIdentifier(a.name)).join(',')};`;
|
||||
}
|
||||
if (changeData.add.length > 0) {
|
||||
changeData.add.forEach((a) => {
|
||||
addArr.push(` ALTER TABLE ${baseTable} ADD COLUMN ${this.genColumnBasicSql(a)}`);
|
||||
if (a.remark) {
|
||||
addCommentArr.push(
|
||||
`EXECUTE sp_addextendedproperty N'MS_Description', N'${a.remark}', N'SCHEMA', N'${schema}', N'TABLE', N'${tableName}', N'COLUMN', N'${a.name}'`
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (changeData.upd.length > 0) {
|
||||
changeData.upd.forEach((a) => {
|
||||
if (a.oldName && a.name !== a.oldName) {
|
||||
renameArr.push(` EXEC sp_rename '${baseTable}.${this.quoteIdentifier(a.oldName)}', '${a.name}', 'COLUMN' `);
|
||||
} else {
|
||||
updArr.push(` ALTER TABLE ${baseTable} ALTER COLUMN ${this.genColumnBasicSql(a)} `);
|
||||
}
|
||||
if (a.remark) {
|
||||
changeCommentArr.push(`IF ((SELECT COUNT(*) FROM fn_listextendedproperty('MS_Description',
|
||||
'SCHEMA', N'${schema}',
|
||||
'TABLE', N'${tableName}',
|
||||
'COLUMN', N'${a.name}')) > 0)
|
||||
EXEC sp_updateextendedproperty
|
||||
'MS_Description', N'${a.remark}',
|
||||
'SCHEMA', N'${schema}',
|
||||
'TABLE', N'${tableName}',
|
||||
'COLUMN', N'${a.name}'
|
||||
ELSE
|
||||
EXEC sp_addextendedproperty
|
||||
'MS_Description', N'${a.remark}',
|
||||
'SCHEMA', N'${schema}',
|
||||
'TABLE', N'${tableName}',
|
||||
'COLUMN',N'${a.name}'`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
delSql +
|
||||
(addArr.length > 0 ? addArr.join(';') + ';' : '') +
|
||||
(renameArr.length > 0 ? renameArr.join(';') + ';' : '') +
|
||||
(updArr.length > 0 ? updArr.join(';') + ';' : '') +
|
||||
(changeCommentArr.length > 0 ? changeCommentArr.join(';') + ';' : '') +
|
||||
(addCommentArr.length > 0 ? addCommentArr.join(';') + ';' : '')
|
||||
);
|
||||
}
|
||||
|
||||
getModifyIndexSql(tableData: any, tableName: string, changeData: { del: any[]; add: any[]; upd: any[] }): string {
|
||||
let schema = tableData.db.split('/')[1];
|
||||
let baseTable = `${this.quoteIdentifier(schema)}.${this.quoteIdentifier(tableName)}`;
|
||||
|
||||
let dropArr = [] as string[];
|
||||
let addArr = [] as string[];
|
||||
let commentArr = [] as string[];
|
||||
|
||||
const pushDrop = (a: any) => {
|
||||
dropArr.push(` DROP INDEX ${this.quoteIdentifier(a.indexName)} ON ${baseTable} `);
|
||||
};
|
||||
const pushAdd = (a: any) => {
|
||||
addArr.push(
|
||||
` CREATE ${a.unique ? 'UNIQUE' : ''} NONCLUSTERED INDEX ${this.quoteIdentifier(a.indexName)} ON ${baseTable} (${a.columnNames.map((b: string) => this.quoteIdentifier(b)).join(',')}) `
|
||||
);
|
||||
if (a.indexComment) {
|
||||
commentArr.push(
|
||||
` EXEC sp_addextendedproperty N'MS_Description', N'${a.indexComment}', N'SCHEMA', N'${schema}', N'TABLE', N'${tableName}', N'INDEX', N'${a.indexName}' `
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if (changeData.upd.length > 0) {
|
||||
changeData.upd.forEach((a) => {
|
||||
pushDrop(a);
|
||||
pushAdd(a);
|
||||
});
|
||||
}
|
||||
|
||||
if (changeData.del.length > 0) {
|
||||
changeData.del.forEach((a) => {
|
||||
pushDrop(a);
|
||||
});
|
||||
}
|
||||
|
||||
if (changeData.add.length > 0) {
|
||||
changeData.add.forEach((a) => pushAdd(a));
|
||||
}
|
||||
let dropSql = dropArr.join(';');
|
||||
let addSql = addArr.join(';');
|
||||
let commentSql = commentArr.join(';');
|
||||
return dropSql + ';' + addSql + ';' + commentSql + ';';
|
||||
}
|
||||
|
||||
getDataType(columnType: string): DataType {
|
||||
if (DbInst.isNumber(columnType)) {
|
||||
return DataType.Number;
|
||||
}
|
||||
// 日期时间类型
|
||||
if (/datetime|timestamp/gi.test(columnType)) {
|
||||
return DataType.DateTime;
|
||||
}
|
||||
// 日期类型
|
||||
if (/date/gi.test(columnType)) {
|
||||
return DataType.Date;
|
||||
}
|
||||
// 时间类型
|
||||
if (/time/gi.test(columnType)) {
|
||||
return DataType.Time;
|
||||
}
|
||||
return DataType.String;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
|
||||
wrapStrValue(columnType: string, value: string): string {
|
||||
return `'${value}'`;
|
||||
}
|
||||
}
|
||||
@@ -113,7 +113,7 @@ class MysqlDialect implements DbDialect {
|
||||
return mysqlDialectInfo;
|
||||
}
|
||||
|
||||
getDefaultSelectSql(table: string, condition: string, orderBy: string, pageNum: number, limit: number) {
|
||||
getDefaultSelectSql(db: string, table: string, condition: string, orderBy: string, pageNum: number, limit: number) {
|
||||
return `SELECT * FROM ${this.quoteIdentifier(table)} ${condition ? 'WHERE ' + condition : ''} ${orderBy ? orderBy : ''} ${this.getPageSql(
|
||||
pageNum,
|
||||
limit
|
||||
@@ -252,7 +252,7 @@ class MysqlDialect implements DbDialect {
|
||||
return sql + arr.join(',') + ';';
|
||||
}
|
||||
|
||||
getModifyIndexSql(tableName: string, changeData: { del: any[]; add: any[]; upd: any[] }): string {
|
||||
getModifyIndexSql(tableData: any, tableName: string, changeData: { del: any[]; add: any[]; upd: any[] }): string {
|
||||
// 搜集修改和删除的索引,添加到drop index xx
|
||||
// 收集新增和修改的索引,添加到ADD xx
|
||||
// ALTER TABLE `test1`
|
||||
|
||||
@@ -92,7 +92,35 @@ const replaceFunctions: EditorCompletionItem[] = [
|
||||
{ label: 'NVL2', insertText: 'NVL2(x,value1,value2)', description: '如果x非空,返回value1,否则返回value2' },
|
||||
];
|
||||
|
||||
const addCustomKeywords = ['ROWNUM', 'DUAL'];
|
||||
const addCustomKeywords: EditorCompletionItem[] = [
|
||||
{
|
||||
label: 'ROWNUM',
|
||||
description: 'keyword',
|
||||
insertText: 'ROWNUM',
|
||||
},
|
||||
{
|
||||
label: 'DUAL',
|
||||
description: 'keyword',
|
||||
insertText: 'DUAL',
|
||||
},
|
||||
// 分页代码块
|
||||
{
|
||||
label: 'SELECT ROWNUM',
|
||||
description: 'code block',
|
||||
insertText: 'SELECT * from table_name where rownum <= 10',
|
||||
},
|
||||
{
|
||||
label: 'SELECT PAGE',
|
||||
description: 'code block',
|
||||
insertText: ` SELECT * FROM
|
||||
(
|
||||
SELECT t.*, ROWNUM AS rn
|
||||
FROM table_name t
|
||||
WHERE ROWNUM <= 25
|
||||
)
|
||||
WHERE rn > 0 \n`,
|
||||
},
|
||||
];
|
||||
|
||||
let oracleDialectInfo: DialectInfo;
|
||||
class OracleDialect implements DbDialect {
|
||||
@@ -104,6 +132,7 @@ class OracleDialect implements DbDialect {
|
||||
let { keywords, operators, builtinVariables } = sqlLanguage;
|
||||
let functionNames = replaceFunctions.map((a) => a.label);
|
||||
let excludeKeywords = new Set(functionNames.concat(operators));
|
||||
excludeKeywords.add('SELECT');
|
||||
|
||||
let editorCompletions: EditorCompletion = {
|
||||
keywords: keywords
|
||||
@@ -118,15 +147,7 @@ class OracleDialect implements DbDialect {
|
||||
})
|
||||
)
|
||||
)
|
||||
.concat(
|
||||
// 加上自定义的关键字
|
||||
addCustomKeywords.map(
|
||||
(a): EditorCompletionItem => ({
|
||||
label: a,
|
||||
description: 'keyword',
|
||||
})
|
||||
)
|
||||
),
|
||||
.concat(addCustomKeywords),
|
||||
operators: operators.map((a: string): EditorCompletionItem => ({ label: a, description: 'operator' })),
|
||||
functions: replaceFunctions,
|
||||
variables: builtinVariables.map((a: string): EditorCompletionItem => ({ label: a, description: 'var' })),
|
||||
@@ -142,7 +163,7 @@ class OracleDialect implements DbDialect {
|
||||
return oracleDialectInfo;
|
||||
}
|
||||
|
||||
getDefaultSelectSql(table: string, condition: string, orderBy: string, pageNum: number, limit: number) {
|
||||
getDefaultSelectSql(db: string, table: string, condition: string, orderBy: string, pageNum: number, limit: number) {
|
||||
return `
|
||||
SELECT *
|
||||
FROM (
|
||||
@@ -399,7 +420,7 @@ class OracleDialect implements DbDialect {
|
||||
return dropPkSql + modifySql + dropSql + renameSql + addPkSql + commentSql;
|
||||
}
|
||||
|
||||
getModifyIndexSql(tableName: string, changeData: { del: any[]; add: any[]; upd: any[] }): string {
|
||||
getModifyIndexSql(tableData: any, tableName: string, changeData: { del: any[]; add: any[]; upd: any[] }): string {
|
||||
// 不能直接修改索引名或字段、需要先删后加
|
||||
let dropIndexNames: string[] = [];
|
||||
let addIndexs: any[] = [];
|
||||
|
||||
@@ -132,7 +132,7 @@ class PostgresqlDialect implements DbDialect {
|
||||
return pgDialectInfo;
|
||||
}
|
||||
|
||||
getDefaultSelectSql(table: string, condition: string, orderBy: string, pageNum: number, limit: number) {
|
||||
getDefaultSelectSql(db: string, table: string, condition: string, orderBy: string, pageNum: number, limit: number) {
|
||||
return `SELECT * FROM ${this.quoteIdentifier(table)} ${condition ? 'WHERE ' + condition : ''} ${orderBy ? orderBy : ''} ${this.getPageSql(
|
||||
pageNum,
|
||||
limit
|
||||
@@ -365,7 +365,7 @@ class PostgresqlDialect implements DbDialect {
|
||||
return dropPkSql + modifySql + dropSql + renameSql + addPkSql + commentSql;
|
||||
}
|
||||
|
||||
getModifyIndexSql(tableName: string, changeData: { del: any[]; add: any[]; upd: any[] }): string {
|
||||
getModifyIndexSql(tableData: any, tableName: string, changeData: { del: any[]; add: any[]; upd: any[] }): string {
|
||||
// 不能直接修改索引名或字段、需要先删后加
|
||||
let dropIndexNames: string[] = [];
|
||||
let addIndexs: any[] = [];
|
||||
|
||||
@@ -132,7 +132,7 @@ class SqliteDialect implements DbDialect {
|
||||
return sqliteDialectInfo;
|
||||
}
|
||||
|
||||
getDefaultSelectSql(table: string, condition: string, orderBy: string, pageNum: number, limit: number) {
|
||||
getDefaultSelectSql(db: string, table: string, condition: string, orderBy: string, pageNum: number, limit: number) {
|
||||
return `SELECT * FROM ${this.quoteIdentifier(table)} ${condition ? 'WHERE ' + condition : ''} ${orderBy ? orderBy : ''} ${this.getPageSql(
|
||||
pageNum,
|
||||
limit
|
||||
@@ -284,7 +284,7 @@ class SqliteDialect implements DbDialect {
|
||||
return sql.join(';') + ';';
|
||||
}
|
||||
|
||||
getModifyIndexSql(tableName: string, changeData: { del: any[]; add: any[]; upd: any[] }): string {
|
||||
getModifyIndexSql(tableData: any, tableName: string, changeData: { del: any[]; add: any[]; upd: any[] }): string {
|
||||
// sqlite创建索引需要先删除再创建
|
||||
// CREATE INDEX "main"."aa1" ON "t_sys_resource" ( "ui_path" );
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ require (
|
||||
github.com/gorilla/websocket v1.5.1
|
||||
github.com/kanzihuang/vitess/go/vt/sqlparser v0.0.0-20231018071450-ac8d9f0167e9
|
||||
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20230712084735-068dc2aee82d
|
||||
github.com/microsoft/go-mssqldb v1.6.0
|
||||
github.com/mojocn/base64Captcha v1.3.6 // 验证码
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pkg/sftp v1.13.6
|
||||
@@ -54,6 +55,8 @@ require (
|
||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/golang/glog v1.0.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
|
||||
@@ -29,5 +29,5 @@ type DbCopyTableForm struct {
|
||||
Id uint64 `binding:"required" json:"id"`
|
||||
Db string `binding:"required" json:"db" `
|
||||
TableName string `binding:"required" json:"tableName"`
|
||||
CopyData bool `binding:"required" json:"copyData"` // 是否复制数据
|
||||
CopyData bool `json:"copyData"` // 是否复制数据
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ func (d *dbAppImpl) GetDbConn(dbId uint64, dbName string) (*dbi.DbConn, error) {
|
||||
|
||||
checkDb := dbName
|
||||
// 兼容pgsql/dm db/schema模式
|
||||
if dbi.DbTypePostgres.Equal(instance.Type) || dbi.DbTypeDM.Equal(instance.Type) || dbi.DbTypeOracle.Equal(instance.Type) {
|
||||
if dbi.DbTypePostgres.Equal(instance.Type) || dbi.DbTypeDM.Equal(instance.Type) || dbi.DbTypeOracle.Equal(instance.Type) || dbi.DbTypeMssql.Equal(instance.Type) {
|
||||
ss := strings.Split(dbName, "/")
|
||||
if len(ss) > 1 {
|
||||
checkDb = ss[0]
|
||||
|
||||
@@ -295,7 +295,7 @@ func (app *dataSyncAppImpl) srcData2TargetDb(srcRes []map[string]any, fieldMap [
|
||||
}
|
||||
// 解决字段大小写问题
|
||||
updFieldVal := srcRes[len(srcRes)-1][strings.ToUpper(task.UpdField)]
|
||||
if updFieldVal == "" {
|
||||
if updFieldVal == "" || updFieldVal == nil {
|
||||
updFieldVal = srcRes[len(srcRes)-1][strings.ToLower(task.UpdField)]
|
||||
}
|
||||
|
||||
@@ -332,12 +332,6 @@ func (app *dataSyncAppImpl) srcData2TargetDb(srcRes []map[string]any, fieldMap [
|
||||
return err
|
||||
}
|
||||
|
||||
// 运行完成一轮就记录一下修改字段最大值
|
||||
taskParam1 := new(entity.DataSyncTask)
|
||||
taskParam1.Id = task.Id
|
||||
taskParam1.UpdFieldVal = task.UpdFieldVal
|
||||
_ = app.UpdateById(context.Background(), taskParam1)
|
||||
|
||||
// 运行过程中,判断状态是否为已关闭,是则结束运行,否则继续运行
|
||||
taskParam, _ := app.GetById(new(entity.DataSyncTask), task.Id)
|
||||
if taskParam.RunningState == entity.DataSyncTaskRunStateStop {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"mayfly-go/internal/db/domain/repository"
|
||||
"mayfly-go/pkg/contextx"
|
||||
"mayfly-go/pkg/errorx"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/utils/jsonx"
|
||||
"strconv"
|
||||
@@ -87,8 +88,14 @@ func (d *dbSqlExecAppImpl) Exec(ctx context.Context, execSqlReq *DbSqlExecReq) (
|
||||
// 如果配置为0,则不校验分页参数
|
||||
maxCount := config.GetDbQueryMaxCount()
|
||||
if maxCount != 0 {
|
||||
// 兼容oracle rownum分页
|
||||
if !strings.Contains(lowerSql, "limit") && !strings.Contains(lowerSql, "rownum") {
|
||||
|
||||
if !strings.Contains(lowerSql, "limit") &&
|
||||
// 兼容oracle rownum分页
|
||||
!strings.Contains(lowerSql, "rownum") &&
|
||||
// 兼容mssql offset分页
|
||||
!strings.Contains(lowerSql, "offset") &&
|
||||
// 兼容mssql top 分页 with result as ({query sql}) select top 100 * from result
|
||||
!strings.Contains(lowerSql, " top ") {
|
||||
// 判断是不是count语句
|
||||
if !strings.Contains(lowerSql, "count(") {
|
||||
return nil, errorx.NewBiz("请完善分页信息后执行")
|
||||
@@ -164,7 +171,7 @@ func doSelect(ctx context.Context, selectStmt *sqlparser.Select, execSqlReq *DbS
|
||||
// 如果配置为0,则不校验分页参数
|
||||
maxCount := config.GetDbQueryMaxCount()
|
||||
// 哪些数据库跳过校验
|
||||
skipped := dbi.DbTypeOracle == execSqlReq.DbConn.Info.Type
|
||||
skipped := dbi.DbTypeOracle == execSqlReq.DbConn.Info.Type || dbi.DbTypeMssql == execSqlReq.DbConn.Info.Type
|
||||
if maxCount != 0 && !skipped {
|
||||
limit := selectStmt.Limit
|
||||
if limit == nil {
|
||||
@@ -204,6 +211,9 @@ func doUpdate(ctx context.Context, update *sqlparser.Update, execSqlReq *DbSqlEx
|
||||
tableStr := sqlparser.String(update.TableExprs)
|
||||
// 可能使用别名,故空格切割
|
||||
tableName := strings.Split(tableStr, " ")[0]
|
||||
if strings.Index(tableName, ".") > -1 {
|
||||
tableName = strings.Split(tableName, ".")[1]
|
||||
}
|
||||
where := sqlparser.String(update.Where)
|
||||
if len(where) == 0 {
|
||||
return nil, errorx.NewBiz("SQL[%s]未执行. 请完善 where 条件后再执行", execSqlReq.Sql)
|
||||
@@ -223,14 +233,26 @@ func doUpdate(ctx context.Context, update *sqlparser.Update, execSqlReq *DbSqlEx
|
||||
|
||||
updateColumnsAndPrimaryKey := strings.Join(updateColumns, ",") + "," + primaryKey
|
||||
// 查询要更新字段数据的旧值,以及主键值
|
||||
selectSql := fmt.Sprintf("SELECT %s FROM %s %s LIMIT 200", updateColumnsAndPrimaryKey, tableStr, where)
|
||||
_, res, err := dbConn.QueryContext(ctx, selectSql)
|
||||
if err == nil {
|
||||
dbSqlExec.OldValue = jsonx.ToStr(res)
|
||||
} else {
|
||||
dbSqlExec.OldValue = err.Error()
|
||||
selectSql := fmt.Sprintf("SELECT %s FROM %s %s", updateColumnsAndPrimaryKey, tableStr, where)
|
||||
|
||||
// WalkQuery查出最多200条数据
|
||||
maxRec := 200
|
||||
nowRec := 0
|
||||
res := make([]map[string]any, 0)
|
||||
err = dbConn.WalkQueryRows(ctx, selectSql, func(row map[string]any, columns []*dbi.QueryColumn) error {
|
||||
nowRec++
|
||||
res = append(res, row)
|
||||
if nowRec == maxRec {
|
||||
return errorx.NewBiz(fmt.Sprintf("超出更新最大查询条数限制: %d", maxRec))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logx.Warn(err.Error())
|
||||
}
|
||||
|
||||
dbSqlExec.OldValue = jsonx.ToStr(res)
|
||||
dbSqlExec.Table = tableName
|
||||
dbSqlExec.Type = entity.DbSqlExecTypeUpdate
|
||||
|
||||
|
||||
@@ -173,7 +173,12 @@ func walkQueryRows(ctx context.Context, db *sql.DB, selectSql string, walkFn Wal
|
||||
// 这里表示一行所有列的值,用[]byte表示
|
||||
values := make([][]byte, lenCols)
|
||||
for k, colType := range colTypes {
|
||||
cols[k] = &QueryColumn{Name: colType.Name(), Type: colType.DatabaseTypeName()}
|
||||
// 处理字段名,如果为空,则命名为匿名列
|
||||
colName := colType.Name()
|
||||
if colName == "" {
|
||||
colName = fmt.Sprintf("<anonymous%d>", k+1)
|
||||
}
|
||||
cols[k] = &QueryColumn{Name: colName, Type: colType.DatabaseTypeName()}
|
||||
// 这里scans引用values,把数据填充到[]byte里
|
||||
scans[k] = &values[k]
|
||||
}
|
||||
@@ -187,7 +192,7 @@ func walkQueryRows(ctx context.Context, db *sql.DB, selectSql string, walkFn Wal
|
||||
rowData := make(map[string]any, lenCols)
|
||||
// 把values中的数据复制到row中
|
||||
for i, v := range values {
|
||||
rowData[colTypes[i].Name()] = valueConvert(v, colTypes[i])
|
||||
rowData[cols[i].Name] = valueConvert(v, colTypes[i])
|
||||
}
|
||||
if err = walkFn(rowData, cols); err != nil {
|
||||
logx.Error("游标遍历查询结果集出错,退出遍历: %s", err.Error())
|
||||
|
||||
@@ -17,6 +17,7 @@ const (
|
||||
DbTypeDM DbType = "dm"
|
||||
DbTypeOracle DbType = "oracle"
|
||||
DbTypeSqlite DbType = "sqlite"
|
||||
DbTypeMssql DbType = "mssql"
|
||||
)
|
||||
|
||||
func ToDbType(dbType string) DbType {
|
||||
@@ -44,6 +45,8 @@ func (dbType DbType) QuoteIdentifier(name string) string {
|
||||
return quoteIdentifier(name, "`")
|
||||
case DbTypePostgres:
|
||||
return quoteIdentifier(name, `"`)
|
||||
case DbTypeMssql:
|
||||
return fmt.Sprintf("[%s]", name)
|
||||
default:
|
||||
return quoteIdentifier(name, `"`)
|
||||
}
|
||||
|
||||
@@ -42,7 +42,8 @@ type Column struct {
|
||||
ColumnName string `json:"columnName"` // 列名
|
||||
ColumnType string `json:"columnType"` // 列类型
|
||||
ColumnComment string `json:"columnComment"` // 列备注
|
||||
ColumnKey string `json:"columnKey"` // 是否为主键,逐渐的话值钱为PRI
|
||||
IsPrimaryKey bool `json:"isPrimaryKey"` // 是否为主键
|
||||
IsIdentity bool `json:"isIdentity"` // 是否自增
|
||||
ColumnDefault string `json:"columnDefault"` // 默认值
|
||||
Nullable string `json:"nullable"` // 是否可为null
|
||||
NumScale string `json:"numScale"` // 小数点
|
||||
@@ -56,7 +57,7 @@ type Index struct {
|
||||
IndexType string `json:"indexType"` // 索引类型
|
||||
IndexComment string `json:"indexComment"` // 备注
|
||||
SeqInIndex int `json:"seqInIndex"`
|
||||
NonUnique int `json:"nonUnique"`
|
||||
IsUnique bool `json:"isUnique"`
|
||||
}
|
||||
|
||||
type DbCopyTable struct {
|
||||
|
||||
@@ -36,7 +36,7 @@ ORDER BY a.object_name
|
||||
select
|
||||
a.index_name as INDEX_NAME,
|
||||
a.index_type as INDEX_TYPE,
|
||||
case when a.uniqueness = 'UNIQUE' then 1 else 0 end as NON_UNIQUE,
|
||||
case when a.uniqueness = 'UNIQUE' then 1 else 0 end as IS_UNIQUE,
|
||||
indexdef(b.object_id,1) as INDEX_DEF,
|
||||
c.column_name as COLUMN_NAME,
|
||||
c.column_position as SEQ_IN_INDEX,
|
||||
@@ -64,22 +64,26 @@ select a.table_name
|
||||
b.comments as COLUMN_COMMENT,
|
||||
a.data_default as COLUMN_DEFAULT,
|
||||
a.data_scale as NUM_SCALE,
|
||||
case when t.COL_NAME = a.column_name then 'PRI' else '' end as COLUMN_KEY
|
||||
case when t.COL_NAME = a.column_name then 1 else 0 end as IS_IDENTITY,
|
||||
case when t2.constraint_type = 'P' then 1 else 0 end as IS_PRIMARY_KEY
|
||||
from all_tab_columns a
|
||||
left join user_col_comments b
|
||||
on b.owner = (SELECT SF_GET_SCHEMA_NAME_BY_ID(CURRENT_SCHID)) and b.table_name = a.table_name and
|
||||
a.column_name = b.column_name
|
||||
left join (select b.owner, b.table_name, a.name COL_NAME
|
||||
on b.owner = (SELECT SF_GET_SCHEMA_NAME_BY_ID(CURRENT_SCHID))
|
||||
and b.table_name = a.table_name
|
||||
and a.column_name = b.column_name
|
||||
left join (select b.owner, b.TABLE_NAME, a.NAME as COL_NAME
|
||||
from SYS.SYSCOLUMNS a,
|
||||
all_tables b,
|
||||
sys.sysobjects c,
|
||||
sys.sysobjects d
|
||||
SYS.all_tables b,
|
||||
SYS.SYSOBJECTS c
|
||||
where a.INFO2 & 0x01 = 0x01
|
||||
and a.id=c.id and d.type$ = 'SCH' and d.id = c.schid
|
||||
and b.owner = (SELECT SF_GET_SCHEMA_NAME_BY_ID(CURRENT_SCHID))
|
||||
and c.schid = ( select id from sys.sysobjects where type$ = 'SCH' and name = (SELECT SF_GET_SCHEMA_NAME_BY_ID(CURRENT_SCHID)))
|
||||
and c.name = b.table_name) t
|
||||
on t.table_name = a.table_name
|
||||
and a.ID = c.ID
|
||||
and c.NAME = b.TABLE_NAME) t
|
||||
on t.table_name = a.table_name and t.owner = a.owner
|
||||
left join (select uc.OWNER, uic.column_name, uic.table_name, uc.constraint_type
|
||||
from user_ind_columns uic
|
||||
left join user_constraints uc on uic.index_name = uc.index_name) t2
|
||||
on t2.table_name = t.table_name and a.column_name = t2.column_name
|
||||
where a.owner = (SELECT SF_GET_SCHEMA_NAME_BY_ID(CURRENT_SCHID))
|
||||
and a.table_name in (%s)
|
||||
order by a.table_name, a.column_id
|
||||
order by a.table_name,
|
||||
a.column_id
|
||||
209
server/internal/db/dbm/dbi/metasql/mssql_meta.sql
Normal file
209
server/internal/db/dbm/dbi/metasql/mssql_meta.sql
Normal file
@@ -0,0 +1,209 @@
|
||||
--MSSQL_DBS 数据库名信息
|
||||
SELECT name AS dbname
|
||||
FROM sys.databases
|
||||
WHERE owner_sid = SUSER_SID()
|
||||
and name not in ('master', 'tempdb', 'model', 'msdb')
|
||||
---------------------------------------
|
||||
--MSSQL_TABLE_DETAIL 查询表名和表注释
|
||||
SELECT t.name AS tableName,
|
||||
ep.value AS tableComment
|
||||
FROM sys.tables t
|
||||
left OUTER JOIN sys.schemas ss on t.schema_id = ss.schema_id
|
||||
LEFT OUTER JOIN
|
||||
sys.extended_properties ep ON ep.major_id = t.object_id AND ep.minor_id = 0 AND ep.class = 1
|
||||
WHERE ss.name = ?
|
||||
and t.name = ?
|
||||
---------------------------------------
|
||||
--MSSQL_DB_SCHEMAS 数据库下所有schema
|
||||
SELECT a.SCHEMA_NAME
|
||||
FROM information_schema.schemata a
|
||||
where a.catalog_name = DB_NAME()
|
||||
and (a.SCHEMA_NAME in ('dbo', 'guest') or a.SCHEMA_NAME not like 'db_%')
|
||||
and a.SCHEMA_NAME not in ('sys', 'INFORMATION_SCHEMA')
|
||||
---------------------------------------
|
||||
--MSSQL_TABLE_INFO 表详细信息
|
||||
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 = ?
|
||||
ORDER BY t.name DESC;
|
||||
---------------------------------------
|
||||
--MSSQL_INDEX_INFO 索引信息
|
||||
SELECT ind.name AS indexName,
|
||||
col.name AS columnName,
|
||||
CASE
|
||||
WHEN ind.is_primary_key = 1 THEN 'CLUSTERED'
|
||||
ELSE 'NON-CLUSTERED'
|
||||
END AS indexType,
|
||||
IIF(ind.is_unique = 'true', 1, 0) AS isUnique,
|
||||
ic.key_ordinal AS seqInIndex,
|
||||
idx.value AS indexComment
|
||||
FROM sys.indexes ind
|
||||
LEFT JOIN sys.tables t on t.object_id = ind.object_id
|
||||
LEFT JOIN sys.schemas ss on t.schema_id = ss.schema_id
|
||||
LEFT JOIN
|
||||
sys.index_columns ic ON ind.object_id = ic.object_id AND ind.index_id = ic.index_id
|
||||
LEFT JOIN
|
||||
sys.columns col ON ind.object_id = col.object_id AND ic.column_id = col.column_id
|
||||
LEFT JOIN
|
||||
sys.extended_properties idx ON ind.object_id = idx.major_id AND ind.index_id = idx.minor_id AND idx.class = 7
|
||||
WHERE ss.name = ?
|
||||
and ind.name is not null
|
||||
and t.name = ?
|
||||
---------------------------------------
|
||||
--MSSQL_COLUMN_MA 列信息元数据
|
||||
SELECT t.name AS TABLE_NAME,
|
||||
c.name AS COLUMN_NAME,
|
||||
CASE
|
||||
WHEN c.is_nullable = 1 THEN 'YES'
|
||||
ELSE 'NO'
|
||||
END AS NULLABLE,
|
||||
tp.name +
|
||||
CASE
|
||||
WHEN tp.name IN ('char', 'varchar', 'nchar', 'nvarchar') THEN '(' + CASE
|
||||
WHEN c.max_length = -1 THEN 'max'
|
||||
ELSE CAST(c.max_length AS NVARCHAR(255)) END +
|
||||
')'
|
||||
WHEN tp.name IN ('numeric', 'decimal') THEN '(' + CAST(c.precision AS NVARCHAR(255)) + ',' +
|
||||
CAST(c.scale AS NVARCHAR(255)) + ')'
|
||||
ELSE ''
|
||||
END AS COLUMN_TYPE,
|
||||
ep.value AS COLUMN_COMMENT,
|
||||
COLUMN_DEFAULT = CASE
|
||||
WHEN c.default_object_id IS NOT NULL THEN object_definition(c.default_object_id)
|
||||
ELSE ''
|
||||
END,
|
||||
c.scale AS NUM_SCALE,
|
||||
IS_IDENTITY = COLUMNPROPERTY(c.object_id, c.name, 'IsIdentity'),
|
||||
IS_PRIMARY_KEY = CASE
|
||||
WHEN (SELECT COUNT(*)
|
||||
FROM sys.index_columns ic
|
||||
INNER JOIN sys.indexes i
|
||||
ON ic.index_id = i.index_id AND ic.object_id = i.object_id
|
||||
WHERE ic.object_id = c.object_id
|
||||
AND ic.column_id = c.column_id
|
||||
AND i.is_primary_key = 1) > 0 THEN 1
|
||||
ELSE 0
|
||||
END
|
||||
FROM sys.tables t
|
||||
INNER JOIN sys.schemas ss on t.schema_id = ss.schema_id
|
||||
INNER JOIN
|
||||
sys.columns c ON t.object_id = c.object_id
|
||||
INNER JOIN
|
||||
sys.types tp ON c.system_type_id = tp.system_type_id AND c.user_type_id = tp.user_type_id
|
||||
LEFT JOIN
|
||||
sys.extended_properties ep ON t.object_id = ep.major_id AND c.column_id = ep.minor_id AND ep.class = 1
|
||||
WHERE ss.name = ?
|
||||
and t.name in (%s)
|
||||
ORDER BY t.name, c.column_id
|
||||
---------------------------------------
|
||||
--MSSQL_TABLE_DDL 建表ddl
|
||||
declare
|
||||
@tabname varchar(50)
|
||||
set @tabname= ? --表名
|
||||
if ( object_id('tempdb.dbo.#t') is not null)
|
||||
begin
|
||||
DROP TABLE #t
|
||||
end
|
||||
select 'create table [' + so.name + '] (' + o.list + ')'
|
||||
+ CASE
|
||||
WHEN tc.Constraint_Name IS NULL THEN ''
|
||||
ELSE 'ALTER TABLE ' + so.Name + ' ADD CONSTRAINT ' + tc.Constraint_Name + ' PRIMARY KEY ' +
|
||||
' (' + LEFT(j.List, Len(j.List)-1) + ')' END
|
||||
TABLE_DDL
|
||||
into #t
|
||||
from sysobjects so
|
||||
cross apply
|
||||
(SELECT
|
||||
' \n ['+ column_name +'] ' +
|
||||
data_type + case data_type
|
||||
when 'sql_variant' then ''
|
||||
when 'text' then ''
|
||||
when 'ntext' then ''
|
||||
when 'xml' then ''
|
||||
when 'decimal' then '(' + cast (numeric_precision as varchar) + ', ' + cast (numeric_scale as varchar) + ')'
|
||||
else coalesce ('('+ case when character_maximum_length = -1 then 'MAX' else cast (character_maximum_length as varchar) end +')', '') end + ' ' +
|
||||
case when exists (
|
||||
select id from syscolumns
|
||||
where object_name(id)=so.name
|
||||
and name = column_name
|
||||
and columnproperty(id, name, 'IsIdentity') = 1
|
||||
) then
|
||||
'IDENTITY(' +
|
||||
cast (ident_seed(so.name) as varchar) + ',' +
|
||||
cast (ident_incr(so.name) as varchar) + ')'
|
||||
else ''
|
||||
end + ' ' +
|
||||
(case when IS_NULLABLE = 'No' then 'NOT ' else '' end ) + 'NULL ' +
|
||||
case when information_schema.columns.COLUMN_DEFAULT IS NOT NULL THEN 'DEFAULT '+ information_schema.columns.COLUMN_DEFAULT ELSE '' END + ', '
|
||||
from information_schema.columns where table_name = so.name
|
||||
order by ordinal_position
|
||||
FOR XML PATH ('')) o (list)
|
||||
left join
|
||||
information_schema.table_constraints tc
|
||||
on tc.Table_name = so.Name
|
||||
AND tc.Constraint_Type = 'PRIMARY KEY'
|
||||
cross apply
|
||||
(select '[' + Column_Name + '], '
|
||||
FROM information_schema.key_column_usage kcu
|
||||
WHERE kcu.Constraint_Name = tc.Constraint_Name
|
||||
ORDER BY
|
||||
ORDINAL_POSITION
|
||||
FOR XML PATH ('')) j (list)
|
||||
where xtype = 'U'
|
||||
AND name =@tabname
|
||||
|
||||
select (
|
||||
case
|
||||
when (select count(a.constraint_type)
|
||||
from information_schema.table_constraints a
|
||||
inner join information_schema.constraint_column_usage b
|
||||
on a.constraint_name = b.constraint_name
|
||||
where a.constraint_type = 'PRIMARY KEY'--主键
|
||||
and a.table_name = @tabname) = 1 then replace(table_ddl
|
||||
, ', )ALTER TABLE'
|
||||
, ')' + CHAR (13)+'ALTER TABLE')
|
||||
else SUBSTRING(table_ddl
|
||||
, 1
|
||||
, len(table_ddl) - 3) + ')' end
|
||||
) as TableDDL
|
||||
from #t
|
||||
|
||||
drop table #t
|
||||
---------------------------------------
|
||||
--MSSQL_TABLE_INDEX_DDL 建索引ddl
|
||||
DECLARE
|
||||
@TableName NVARCHAR(255)
|
||||
SET @TableName = ?;
|
||||
|
||||
SELECT 'CREATE ' +
|
||||
CASE
|
||||
WHEN i.is_primary_key = 1 THEN 'CLUSTERED '
|
||||
WHEN i.type_desc = 'HEAP' THEN ''
|
||||
ELSE 'NONCLUSTERED '
|
||||
END +
|
||||
'INDEX ' + i.name + ' ON ' + t.name + ' (' +
|
||||
STUFF((SELECT ',' + c.name +
|
||||
CASE
|
||||
WHEN ic.is_descending_key = 1 THEN ' DESC'
|
||||
ELSE ' ASC'
|
||||
END
|
||||
FROM sys.index_columns ic
|
||||
INNER JOIN
|
||||
sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
|
||||
WHERE ic.object_id = i.object_id
|
||||
AND ic.index_id = i.index_id
|
||||
ORDER BY ic.key_ordinal
|
||||
FOR XML PATH (''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '') + ');' AS IndexDDL
|
||||
FROM sys.tables t
|
||||
INNER JOIN
|
||||
sys.indexes i ON t.object_id = i.object_id
|
||||
WHERE t.name = @TableName;
|
||||
@@ -30,7 +30,7 @@ SELECT
|
||||
index_name indexName,
|
||||
column_name columnName,
|
||||
index_type indexType,
|
||||
non_unique nonUnique,
|
||||
IF(non_unique, 0, 1) isUnique,
|
||||
SEQ_IN_INDEX seqInIndex,
|
||||
INDEX_COMMENT indexComment
|
||||
FROM
|
||||
@@ -46,24 +46,25 @@ ORDER BY
|
||||
SEQ_IN_INDEX asc
|
||||
---------------------------------------
|
||||
--MYSQL_COLUMN_MA 列信息元数据
|
||||
SELECT
|
||||
table_name tableName,
|
||||
column_name columnName,
|
||||
column_type columnType,
|
||||
column_default columnDefault,
|
||||
column_comment columnComment,
|
||||
column_key columnKey,
|
||||
extra extra,
|
||||
is_nullable nullable,
|
||||
NUMERIC_SCALE numScale
|
||||
from
|
||||
information_schema.columns
|
||||
WHERE
|
||||
table_schema = (
|
||||
SELECT
|
||||
database ()
|
||||
)
|
||||
AND table_name in (%s)
|
||||
ORDER BY
|
||||
tableName,
|
||||
ordinal_position
|
||||
SELECT table_name tableName,
|
||||
column_name columnName,
|
||||
column_type columnType,
|
||||
column_default columnDefault,
|
||||
column_comment columnComment,
|
||||
CASE
|
||||
WHEN column_key = 'PRI' THEN
|
||||
1
|
||||
ELSE 0
|
||||
END AS isPrimaryKey,
|
||||
CASE
|
||||
WHEN extra LIKE '%%auto_increment%%' THEN
|
||||
1
|
||||
ELSE 0
|
||||
END AS isIdentity,
|
||||
is_nullable nullable,
|
||||
NUMERIC_SCALE numScale
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE table_schema = (SELECT DATABASE())
|
||||
AND table_name IN (%s)
|
||||
ORDER BY table_name,
|
||||
ordinal_position
|
||||
@@ -21,9 +21,9 @@ ORDER BY a.TABLE_NAME
|
||||
SELECT ai.INDEX_NAME AS INDEX_NAME,
|
||||
ai.INDEX_TYPE AS INDEX_TYPE,
|
||||
CASE
|
||||
WHEN ai.uniqueness = 'UNIQUE' THEN 'NO'
|
||||
ELSE 'YES'
|
||||
END AS NON_UNIQUE,
|
||||
WHEN ai.uniqueness = 'UNIQUE' THEN 1
|
||||
ELSE 0
|
||||
END AS IS_UNIQUE,
|
||||
(SELECT LISTAGG(column_name, ', ') WITHIN GROUP (ORDER BY column_position)
|
||||
FROM ALL_IND_COLUMNS aic
|
||||
WHERE aic.INDEX_NAME = ai.INDEX_NAME
|
||||
@@ -53,9 +53,8 @@ SELECT a.TABLE_NAME as TABLE_NAME,
|
||||
b.COMMENTS as COLUMN_COMMENT,
|
||||
a.DATA_DEFAULT as COLUMN_DEFAULT,
|
||||
a.DATA_SCALE as NUM_SCALE,
|
||||
CASE
|
||||
WHEN d.pri IS NOT NULL THEN 'PRI'
|
||||
END as COLUMN_KEY
|
||||
CASE WHEN d.pri IS NOT NULL THEN 1 ELSE 0 END as IS_PRIMARY_KEY,
|
||||
CASE WHEN a.IDENTITY_COLUMN = 'YES' THEN 1 ELSE 0 END as IS_IDENTITY
|
||||
FROM all_tab_columns a
|
||||
LEFT JOIN all_col_comments b
|
||||
on a.OWNER = b.OWNER AND a.TABLE_NAME = b.TABLE_NAME AND a.COLUMN_NAME = b.COLUMN_NAME
|
||||
|
||||
@@ -35,7 +35,7 @@ order by c.relname
|
||||
SELECT
|
||||
indexname AS "indexName",
|
||||
'BTREE' AS "IndexType",
|
||||
case when indexdef like 'CREATE UNIQUE INDEX%%' then 0 else 1 end as "nonUnique",
|
||||
case when indexdef like 'CREATE UNIQUE INDEX%%' then 1 else 0 end as "isUnique",
|
||||
obj_description(b.oid, 'pg_class') AS "indexComment",
|
||||
indexdef AS "indexDef",
|
||||
c.attname AS "columnName",
|
||||
@@ -47,18 +47,21 @@ WHERE a.schemaname = (select current_schema())
|
||||
AND a.tablename = '%s';
|
||||
---------------------------------------
|
||||
--PGSQL_COLUMN_MA 表列信息
|
||||
SELECT
|
||||
table_name AS "tableName",
|
||||
column_name AS "columnName",
|
||||
is_nullable AS "nullable",
|
||||
SELECT a.table_name AS "tableName",
|
||||
a.column_name AS "columnName",
|
||||
a.is_nullable AS "nullable",
|
||||
case when character_maximum_length > 0 then concat(udt_name, '(',character_maximum_length,')') else udt_name end AS "columnType",
|
||||
column_default as "columnDefault",
|
||||
numeric_scale AS "numScale",
|
||||
case when column_default like 'nextval%%' then 'PRI' else '' end "columnKey",
|
||||
col_description((table_schema || '.' || table_name)::regclass, ordinal_position) AS "columnComment"
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = (select current_schema()) and table_name in (%s)
|
||||
order by table_name, ordinal_position
|
||||
a.column_default as "columnDefault",
|
||||
a.numeric_scale AS "numScale",
|
||||
case when a.column_default like 'nextval%%' then 1 else 0 end "isIdentity",
|
||||
case when b.column_name is not null then 1 else 0 end "isPrimaryKey",
|
||||
col_description((a.table_schema || '.' || a.table_name)::regclass, a.ordinal_position) AS "columnComment"
|
||||
FROM information_schema.columns a
|
||||
left join information_schema.key_column_usage b
|
||||
on a.table_schema = b.table_schema and b.table_name = a.table_name and b.column_name = a.column_name
|
||||
WHERE a.table_schema = (select current_schema())
|
||||
and a.table_name in (%s)
|
||||
order by a.table_name, a.ordinal_position
|
||||
---------------------------------------
|
||||
--PGSQL_TABLE_DDL_FUNC 表ddl函数
|
||||
CREATE OR REPLACE FUNCTION showcreatetable(namespace character varying, tablename character varying)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"mayfly-go/internal/common/consts"
|
||||
"mayfly-go/internal/db/dbm/dbi"
|
||||
_ "mayfly-go/internal/db/dbm/dm"
|
||||
_ "mayfly-go/internal/db/dbm/mssql"
|
||||
_ "mayfly-go/internal/db/dbm/mysql"
|
||||
_ "mayfly-go/internal/db/dbm/oracle"
|
||||
_ "mayfly-go/internal/db/dbm/postgres"
|
||||
|
||||
@@ -101,7 +101,8 @@ func (dd *DMDialect) GetColumns(tableNames ...string) ([]dbi.Column, error) {
|
||||
ColumnType: anyx.ConvString(re["COLUMN_TYPE"]),
|
||||
ColumnComment: anyx.ConvString(re["COLUMN_COMMENT"]),
|
||||
Nullable: anyx.ConvString(re["NULLABLE"]),
|
||||
ColumnKey: anyx.ConvString(re["COLUMN_KEY"]),
|
||||
IsPrimaryKey: anyx.ConvInt(re["IS_PRIMARY_KEY"]) == 1,
|
||||
IsIdentity: anyx.ConvInt(re["IS_IDENTITY"]) == 1,
|
||||
ColumnDefault: anyx.ConvString(re["COLUMN_DEFAULT"]),
|
||||
NumScale: anyx.ConvString(re["NUM_SCALE"]),
|
||||
})
|
||||
@@ -118,7 +119,7 @@ func (dd *DMDialect) GetPrimaryKey(tablename string) (string, error) {
|
||||
return "", errorx.NewBiz("[%s] 表不存在", tablename)
|
||||
}
|
||||
for _, v := range columns {
|
||||
if v.ColumnKey == "PRI" {
|
||||
if v.IsPrimaryKey {
|
||||
return v.ColumnName, nil
|
||||
}
|
||||
}
|
||||
@@ -140,7 +141,7 @@ func (dd *DMDialect) GetTableIndex(tableName string) ([]dbi.Index, error) {
|
||||
ColumnName: anyx.ConvString(re["COLUMN_NAME"]),
|
||||
IndexType: anyx.ConvString(re["INDEX_TYPE"]),
|
||||
IndexComment: anyx.ConvString(re["INDEX_COMMENT"]),
|
||||
NonUnique: anyx.ConvInt(re["NON_UNIQUE"]),
|
||||
IsUnique: anyx.ConvInt(re["IS_UNIQUE"]) == 1,
|
||||
SeqInIndex: anyx.ConvInt(re["SEQ_IN_INDEX"]),
|
||||
})
|
||||
}
|
||||
|
||||
425
server/internal/db/dbm/mssql/dialect.go
Normal file
425
server/internal/db/dbm/mssql/dialect.go
Normal file
@@ -0,0 +1,425 @@
|
||||
package mssql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"mayfly-go/internal/db/dbm/dbi"
|
||||
"mayfly-go/pkg/errorx"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/utils/anyx"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
type MssqlDialect struct {
|
||||
dc *dbi.DbConn
|
||||
}
|
||||
|
||||
func (md *MssqlDialect) GetDbServer() (*dbi.DbServer, error) {
|
||||
_, res, err := md.dc.Query("SELECT @@VERSION as version")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ds := &dbi.DbServer{
|
||||
Version: anyx.ConvString(res[0]["version"]),
|
||||
}
|
||||
return ds, nil
|
||||
}
|
||||
|
||||
func (md *MssqlDialect) GetDbNames() ([]string, error) {
|
||||
_, res, err := md.dc.Query(dbi.GetLocalSql(MSSQL_META_FILE, MSSQL_DBS_KEY))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
databases := make([]string, 0)
|
||||
for _, re := range res {
|
||||
databases = append(databases, anyx.ConvString(re["dbname"]))
|
||||
}
|
||||
|
||||
return databases, nil
|
||||
}
|
||||
|
||||
// 从连接信息中获取数据库和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
|
||||
}
|
||||
|
||||
// 获取表基础元信息, 如表名等
|
||||
func (md *MssqlDialect) GetTables() ([]dbi.Table, error) {
|
||||
_, res, err := md.dc.Query(dbi.GetLocalSql(MSSQL_META_FILE, MSSQL_TABLE_INFO_KEY), md.currentSchema())
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tables := make([]dbi.Table, 0)
|
||||
for _, re := range res {
|
||||
tables = append(tables, dbi.Table{
|
||||
TableName: anyx.ConvString(re["tableName"]),
|
||||
TableComment: anyx.ConvString(re["tableComment"]),
|
||||
CreateTime: anyx.ConvString(re["createTime"]),
|
||||
TableRows: anyx.ConvInt(re["tableRows"]),
|
||||
DataLength: anyx.ConvInt64(re["dataLength"]),
|
||||
IndexLength: anyx.ConvInt64(re["indexLength"]),
|
||||
})
|
||||
}
|
||||
return tables, nil
|
||||
}
|
||||
|
||||
// 获取列元信息, 如列名等
|
||||
func (md *MssqlDialect) GetColumns(tableNames ...string) ([]dbi.Column, error) {
|
||||
dbType := md.dc.Info.Type
|
||||
tableName := strings.Join(collx.ArrayMap[string, string](tableNames, func(val string) string {
|
||||
return fmt.Sprintf("'%s'", dbType.RemoveQuote(val))
|
||||
}), ",")
|
||||
|
||||
_, res, err := md.dc.Query(fmt.Sprintf(dbi.GetLocalSql(MSSQL_META_FILE, MSSQL_COLUMN_MA_KEY), tableName), md.currentSchema())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
columns := make([]dbi.Column, 0)
|
||||
for _, re := range res {
|
||||
columns = append(columns, dbi.Column{
|
||||
TableName: anyx.ToString(re["TABLE_NAME"]),
|
||||
ColumnName: anyx.ToString(re["COLUMN_NAME"]),
|
||||
ColumnType: anyx.ToString(re["COLUMN_TYPE"]),
|
||||
ColumnComment: anyx.ToString(re["COLUMN_COMMENT"]),
|
||||
Nullable: anyx.ToString(re["NULLABLE"]),
|
||||
IsPrimaryKey: anyx.ConvInt(re["IS_PRIMARY_KEY"]) == 1,
|
||||
IsIdentity: anyx.ConvInt(re["IS_IDENTITY"]) == 1,
|
||||
ColumnDefault: anyx.ToString(re["COLUMN_DEFAULT"]),
|
||||
NumScale: anyx.ToString(re["NUM_SCALE"]),
|
||||
})
|
||||
}
|
||||
return columns, nil
|
||||
}
|
||||
|
||||
// 获取表主键字段名,不存在主键标识则默认第一个字段
|
||||
func (md *MssqlDialect) GetPrimaryKey(tablename string) (string, error) {
|
||||
columns, err := md.GetColumns(tablename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(columns) == 0 {
|
||||
return "", errorx.NewBiz("[%s] 表不存在", tablename)
|
||||
}
|
||||
|
||||
for _, v := range columns {
|
||||
if v.IsPrimaryKey {
|
||||
return v.ColumnName, nil
|
||||
}
|
||||
}
|
||||
|
||||
return columns[0].ColumnName, nil
|
||||
}
|
||||
|
||||
// 获取表索引信息
|
||||
func (md *MssqlDialect) GetTableIndex(tableName string) ([]dbi.Index, error) {
|
||||
_, res, err := md.dc.Query(dbi.GetLocalSql(MSSQL_META_FILE, MSSQL_INDEX_INFO_KEY), md.currentSchema(), tableName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
indexs := make([]dbi.Index, 0)
|
||||
for _, re := range res {
|
||||
indexs = append(indexs, dbi.Index{
|
||||
IndexName: anyx.ConvString(re["indexName"]),
|
||||
ColumnName: anyx.ConvString(re["columnName"]),
|
||||
IndexType: anyx.ConvString(re["indexType"]),
|
||||
IndexComment: anyx.ConvString(re["indexComment"]),
|
||||
IsUnique: anyx.ConvInt(re["isUnique"]) == 1,
|
||||
SeqInIndex: anyx.ConvInt(re["seqInIndex"]),
|
||||
})
|
||||
}
|
||||
// 把查询结果以索引名分组,索引字段以逗号连接
|
||||
result := make([]dbi.Index, 0)
|
||||
key := ""
|
||||
for _, v := range indexs {
|
||||
// 当前的索引名
|
||||
in := v.IndexName
|
||||
// 过滤掉主键索引,主键索引名为PK__开头的
|
||||
if strings.HasPrefix(in, "PK__") {
|
||||
continue
|
||||
}
|
||||
if key == in {
|
||||
// 索引字段已根据名称和顺序排序,故取最后一个即可
|
||||
i := len(result) - 1
|
||||
// 同索引字段以逗号连接
|
||||
result[i].ColumnName = result[i].ColumnName + "," + v.ColumnName
|
||||
} else {
|
||||
key = in
|
||||
result = append(result, v)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (md MssqlDialect) CopyTableDDL(tableName string, newTableName string) (string, error) {
|
||||
if newTableName == "" {
|
||||
newTableName = tableName
|
||||
}
|
||||
|
||||
// 根据列信息生成建表语句
|
||||
var builder strings.Builder
|
||||
var commentBuilder strings.Builder
|
||||
|
||||
// 查询表名和表注释, 设置表注释
|
||||
_, res, err := md.dc.Query(dbi.GetLocalSql(MSSQL_META_FILE, MSSQL_TABLE_DETAIL_KEY), md.currentSchema(), tableName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
tableComment := ""
|
||||
if len(res) > 0 {
|
||||
tableComment = anyx.ToString(res[0]["tableComment"])
|
||||
if tableComment != "" {
|
||||
// 注释转义单引号
|
||||
tableComment = strings.ReplaceAll(tableComment, "'", "\\'")
|
||||
commentBuilder.WriteString(fmt.Sprintf("\nEXEC sp_addextendedproperty N'MS_Description', N'%s', N'SCHEMA', N'%s', N'TABLE',N'%s';\n", tableComment, md.currentSchema(), newTableName))
|
||||
}
|
||||
}
|
||||
|
||||
baseTable := fmt.Sprintf("%s.%s", md.dc.Info.Type.QuoteIdentifier(md.currentSchema()), md.dc.Info.Type.QuoteIdentifier(newTableName))
|
||||
|
||||
// 查询列信息
|
||||
columns, err := md.GetColumns(tableName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
builder.WriteString(fmt.Sprintf("CREATE TABLE %s (\n", baseTable))
|
||||
pks := make([]string, 0)
|
||||
for i, v := range columns {
|
||||
nullAble := "NULL"
|
||||
if v.Nullable == "NO" {
|
||||
nullAble = "NOT NULL"
|
||||
}
|
||||
builder.WriteString(fmt.Sprintf("\t[%s] %s %s", v.ColumnName, v.ColumnType, nullAble))
|
||||
if v.IsIdentity {
|
||||
builder.WriteString(" IDENTITY(1,11)")
|
||||
}
|
||||
if v.ColumnDefault != "" {
|
||||
builder.WriteString(fmt.Sprintf(" DEFAULT %s", v.ColumnDefault))
|
||||
}
|
||||
if v.IsPrimaryKey {
|
||||
pks = append(pks, fmt.Sprintf("[%s]", v.ColumnName))
|
||||
}
|
||||
if i < len(columns)-1 {
|
||||
builder.WriteString(",")
|
||||
}
|
||||
builder.WriteString("\n")
|
||||
}
|
||||
// 设置主键
|
||||
if len(pks) > 0 {
|
||||
builder.WriteString(fmt.Sprintf("\tCONSTRAINT PK_%s PRIMARY KEY ( %s )", newTableName, strings.Join(pks, ",")))
|
||||
}
|
||||
builder.WriteString("\n);\n")
|
||||
|
||||
// 设置字段注释
|
||||
for _, v := range columns {
|
||||
if v.ColumnComment != "" {
|
||||
// 注释转义单引号
|
||||
v.ColumnComment = strings.ReplaceAll(v.ColumnComment, "'", "\\'")
|
||||
commentBuilder.WriteString(fmt.Sprintf("\nEXEC sp_addextendedproperty N'MS_Description', N'%s', N'SCHEMA', N'%s', N'TABLE',N'%s', N'COLUMN', N'%s';\n", v.ColumnComment, md.currentSchema(), newTableName, v.ColumnName))
|
||||
}
|
||||
}
|
||||
|
||||
// 设置索引
|
||||
indexs, err := md.GetTableIndex(tableName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, v := range indexs {
|
||||
builder.WriteString(fmt.Sprintf("\nCREATE NONCLUSTERED INDEX [%s] ON %s (%s);\n", v.IndexName, baseTable, v.ColumnName))
|
||||
// 设置索引注释
|
||||
if v.IndexComment != "" {
|
||||
// 注释转义单引号
|
||||
v.IndexComment = strings.ReplaceAll(v.IndexComment, "'", "\\'")
|
||||
commentBuilder.WriteString(fmt.Sprintf("\nEXEC sp_addextendedproperty N'MS_Description', N'%s', N'SCHEMA', N'%s', N'TABLE',N'%s', N'INDEX', N'%s';\n", v.IndexComment, md.currentSchema(), newTableName, v.IndexName))
|
||||
}
|
||||
}
|
||||
return builder.String() + commentBuilder.String(), nil
|
||||
}
|
||||
|
||||
// 获取建表ddl
|
||||
func (md *MssqlDialect) GetTableDDL(tableName string) (string, error) {
|
||||
return md.CopyTableDDL(tableName, "")
|
||||
}
|
||||
|
||||
func (md *MssqlDialect) WalkTableRecord(tableName string, walkFn dbi.WalkQueryRowsFunc) error {
|
||||
return md.dc.WalkQueryRows(context.Background(), fmt.Sprintf("SELECT * FROM %s", tableName), walkFn)
|
||||
}
|
||||
|
||||
func (md *MssqlDialect) GetSchemas() ([]string, error) {
|
||||
_, res, err := md.dc.Query(dbi.GetLocalSql(MSSQL_META_FILE, MSSQL_DB_SCHEMAS_KEY))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
schemas := make([]string, 0)
|
||||
for _, re := range res {
|
||||
schemas = append(schemas, anyx.ConvString(re["SCHEMA_NAME"]))
|
||||
}
|
||||
return schemas, nil
|
||||
}
|
||||
|
||||
// GetDbProgram 获取数据库程序模块,用于数据库备份与恢复
|
||||
func (md *MssqlDialect) GetDbProgram() dbi.DbProgram {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (md *MssqlDialect) BatchInsert(tx *sql.Tx, tableName string, columns []string, values [][]any) (int64, error) {
|
||||
schema := md.currentSchema()
|
||||
|
||||
// 生成占位符字符串:如:(?,?)
|
||||
// 重复字符串并用逗号连接
|
||||
repeated := strings.Repeat("?,", len(columns))
|
||||
// 去除最后一个逗号,占位符由括号包裹
|
||||
placeholder := fmt.Sprintf("(%s)", strings.TrimSuffix(repeated, ","))
|
||||
|
||||
// 重复占位符字符串n遍
|
||||
repeated = strings.Repeat(placeholder+",", len(values))
|
||||
// 去除最后一个逗号
|
||||
placeholder = strings.TrimSuffix(repeated, ",")
|
||||
|
||||
baseTable := fmt.Sprintf("%s.%s", md.dc.Info.Type.QuoteIdentifier(schema), md.dc.Info.Type.QuoteIdentifier(tableName))
|
||||
|
||||
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...)
|
||||
}
|
||||
// 设置允许填充自增列之后,显示指定列名可以插入自增列
|
||||
_, _ = md.dc.Exec(fmt.Sprintf("set identity_insert \"%s\" on", tableName))
|
||||
|
||||
exec, err := md.dc.TxExec(tx, sqlStr, args...)
|
||||
|
||||
_, _ = md.dc.Exec(fmt.Sprintf("set identity_insert \"%s\" off", tableName))
|
||||
|
||||
return exec, err
|
||||
}
|
||||
|
||||
func (md *MssqlDialect) GetDataConverter() dbi.DataConverter {
|
||||
return new(DataConverter)
|
||||
}
|
||||
|
||||
var (
|
||||
// 数字类型
|
||||
numberRegexp = regexp.MustCompile(`(?i)int|double|float|number|decimal|byte|bit`)
|
||||
// 日期时间类型
|
||||
datetimeRegexp = regexp.MustCompile(`(?i)datetime|timestamp`)
|
||||
// 日期类型
|
||||
dateRegexp = regexp.MustCompile(`(?i)date`)
|
||||
// 时间类型
|
||||
timeRegexp = regexp.MustCompile(`(?i)time`)
|
||||
)
|
||||
|
||||
type DataConverter struct {
|
||||
}
|
||||
|
||||
func (dc *DataConverter) GetDataType(dbColumnType string) dbi.DataType {
|
||||
if numberRegexp.MatchString(dbColumnType) {
|
||||
return dbi.DataTypeNumber
|
||||
}
|
||||
// 日期时间类型
|
||||
if datetimeRegexp.MatchString(dbColumnType) {
|
||||
return dbi.DataTypeDateTime
|
||||
}
|
||||
// 日期类型
|
||||
if dateRegexp.MatchString(dbColumnType) {
|
||||
return dbi.DataTypeDate
|
||||
}
|
||||
// 时间类型
|
||||
if timeRegexp.MatchString(dbColumnType) {
|
||||
return dbi.DataTypeTime
|
||||
}
|
||||
return dbi.DataTypeString
|
||||
}
|
||||
|
||||
func (dc *DataConverter) FormatData(dbColumnValue any, dataType dbi.DataType) string {
|
||||
return anyx.ToString(dbColumnValue)
|
||||
}
|
||||
|
||||
func (dc *DataConverter) ParseData(dbColumnValue any, dataType dbi.DataType) any {
|
||||
return dbColumnValue
|
||||
}
|
||||
|
||||
func (md *MssqlDialect) CopyTable(copy *dbi.DbCopyTable) error {
|
||||
|
||||
schema := md.currentSchema()
|
||||
|
||||
// 生成新表名,为老表明+_copy_时间戳
|
||||
newTableName := copy.TableName + "_copy_" + time.Now().Format("20060102150405")
|
||||
|
||||
// 复制建表语句
|
||||
ddl, err := md.CopyTableDDL(copy.TableName, newTableName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 执行建表
|
||||
_, err = md.dc.Exec(ddl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 复制数据
|
||||
if copy.CopyData {
|
||||
go func() {
|
||||
// 查询所有的列
|
||||
columns, err := md.GetColumns(copy.TableName)
|
||||
if err != nil {
|
||||
logx.Warnf("复制表[%s]数据失败: %s", copy.TableName, err.Error())
|
||||
return
|
||||
}
|
||||
// 取出每列名, 需要显示指定列名插入数据
|
||||
columnNames := make([]string, 0)
|
||||
hasIdentity := false
|
||||
for _, v := range columns {
|
||||
columnNames = append(columnNames, fmt.Sprintf("[%s]", v.ColumnName))
|
||||
if v.IsIdentity {
|
||||
hasIdentity = true
|
||||
}
|
||||
|
||||
}
|
||||
columnsSql := strings.Join(columnNames, ",")
|
||||
|
||||
// 复制数据
|
||||
// 设置允许填充自增列之后,显示指定列名可以插入自增列
|
||||
identityInsertOn := ""
|
||||
identityInsertOff := ""
|
||||
if hasIdentity {
|
||||
identityInsertOn = fmt.Sprintf("SET IDENTITY_INSERT [%s].[%s] ON", schema, newTableName)
|
||||
identityInsertOff = fmt.Sprintf("SET IDENTITY_INSERT [%s].[%s] OFF", schema, newTableName)
|
||||
}
|
||||
_, err = md.dc.Exec(fmt.Sprintf(" %s INSERT INTO [%s].[%s] (%s) SELECT * FROM [%s].[%s] %s", identityInsertOn, schema, newTableName, columnsSql, schema, copy.TableName, identityInsertOff))
|
||||
if err != nil {
|
||||
logx.Warnf("复制表[%s]数据失败: %s", copy.TableName, err.Error())
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
48
server/internal/db/dbm/mssql/meta.go
Normal file
48
server/internal/db/dbm/mssql/meta.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package mssql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
_ "github.com/microsoft/go-mssqldb"
|
||||
"mayfly-go/internal/db/dbm/dbi"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func init() {
|
||||
meta := new(Meta)
|
||||
dbi.Register(dbi.DbTypeMssql, meta)
|
||||
}
|
||||
|
||||
type Meta struct {
|
||||
}
|
||||
|
||||
func (md *Meta) GetSqlDb(d *dbi.DbInfo) (*sql.DB, error) {
|
||||
err := d.IfUseSshTunnelChangeIpPort()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query := url.Values{}
|
||||
// The application name (default is go-mssqldb)
|
||||
query.Add("app name", "mayfly")
|
||||
// 指定与服务器协商加密的最低TLS版本
|
||||
query.Add("tlsmin", "1.0")
|
||||
// 连接超时时间10秒
|
||||
query.Add("connection timeout", "10")
|
||||
if d.Database != "" {
|
||||
ss := strings.Split(d.Database, "/")
|
||||
if len(ss) > 1 {
|
||||
query.Add("database", ss[0])
|
||||
query.Add("schema", ss[1])
|
||||
} else {
|
||||
query.Add("database", d.Database)
|
||||
}
|
||||
}
|
||||
const driverName = "mssql"
|
||||
dsn := fmt.Sprintf("sqlserver://%s:%s@%s:%d?%s", url.PathEscape(d.Username), url.PathEscape(d.Password), d.Host, d.Port, query.Encode())
|
||||
return sql.Open(driverName, dsn)
|
||||
}
|
||||
|
||||
func (md *Meta) GetDialect(conn *dbi.DbConn) dbi.Dialect {
|
||||
return &MssqlDialect{conn}
|
||||
}
|
||||
@@ -90,7 +90,8 @@ func (md *MysqlDialect) GetColumns(tableNames ...string) ([]dbi.Column, error) {
|
||||
ColumnType: anyx.ConvString(re["columnType"]),
|
||||
ColumnComment: anyx.ConvString(re["columnComment"]),
|
||||
Nullable: anyx.ConvString(re["nullable"]),
|
||||
ColumnKey: anyx.ConvString(re["columnKey"]),
|
||||
IsPrimaryKey: anyx.ConvInt(re["isPrimaryKey"]) == 1,
|
||||
IsIdentity: anyx.ConvInt(re["isIdentity"]) == 1,
|
||||
ColumnDefault: anyx.ConvString(re["columnDefault"]),
|
||||
NumScale: anyx.ConvString(re["numScale"]),
|
||||
})
|
||||
@@ -109,7 +110,7 @@ func (md *MysqlDialect) GetPrimaryKey(tablename string) (string, error) {
|
||||
}
|
||||
|
||||
for _, v := range columns {
|
||||
if v.ColumnKey == "PRI" {
|
||||
if v.IsPrimaryKey {
|
||||
return v.ColumnName, nil
|
||||
}
|
||||
}
|
||||
@@ -131,7 +132,7 @@ func (md *MysqlDialect) GetTableIndex(tableName string) ([]dbi.Index, error) {
|
||||
ColumnName: anyx.ConvString(re["columnName"]),
|
||||
IndexType: anyx.ConvString(re["indexType"]),
|
||||
IndexComment: anyx.ConvString(re["indexComment"]),
|
||||
NonUnique: anyx.ConvInt(re["nonUnique"]),
|
||||
IsUnique: anyx.ConvInt(re["isUnique"]) == 1,
|
||||
SeqInIndex: anyx.ConvInt(re["seqInIndex"]),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -103,7 +103,8 @@ func (od *OracleDialect) GetColumns(tableNames ...string) ([]dbi.Column, error)
|
||||
ColumnType: anyx.ConvString(re["COLUMN_TYPE"]),
|
||||
ColumnComment: anyx.ConvString(re["COLUMN_COMMENT"]),
|
||||
Nullable: anyx.ConvString(re["NULLABLE"]),
|
||||
ColumnKey: anyx.ConvString(re["COLUMN_KEY"]),
|
||||
IsPrimaryKey: anyx.ConvInt(re["IS_PRIMARY_KEY"]) == 1,
|
||||
IsIdentity: anyx.ConvInt(re["IS_IDENTITY"]) == 1,
|
||||
ColumnDefault: defaultVal,
|
||||
NumScale: anyx.ConvString(re["NUM_SCALE"]),
|
||||
})
|
||||
@@ -120,7 +121,7 @@ func (od *OracleDialect) GetPrimaryKey(tablename string) (string, error) {
|
||||
return "", errorx.NewBiz("[%s] 表不存在", tablename)
|
||||
}
|
||||
for _, v := range columns {
|
||||
if v.ColumnKey == "PRI" {
|
||||
if v.IsPrimaryKey {
|
||||
return v.ColumnName, nil
|
||||
}
|
||||
}
|
||||
@@ -142,7 +143,7 @@ func (od *OracleDialect) GetTableIndex(tableName string) ([]dbi.Index, error) {
|
||||
ColumnName: anyx.ConvString(re["COLUMN_NAME"]),
|
||||
IndexType: anyx.ConvString(re["INDEX_TYPE"]),
|
||||
IndexComment: anyx.ConvString(re["INDEX_COMMENT"]),
|
||||
NonUnique: anyx.ConvInt(re["NON_UNIQUE"]),
|
||||
IsUnique: anyx.ConvInt(re["IS_UNIQUE"]) == 1,
|
||||
SeqInIndex: anyx.ConvInt(re["SEQ_IN_INDEX"]),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -91,7 +91,8 @@ func (md *PgsqlDialect) GetColumns(tableNames ...string) ([]dbi.Column, error) {
|
||||
ColumnType: anyx.ConvString(re["columnType"]),
|
||||
ColumnComment: anyx.ConvString(re["columnComment"]),
|
||||
Nullable: anyx.ConvString(re["nullable"]),
|
||||
ColumnKey: anyx.ConvString(re["columnKey"]),
|
||||
IsPrimaryKey: anyx.ConvInt(re["isPrimaryKey"]) == 1,
|
||||
IsIdentity: anyx.ConvInt(re["isIdentity"]) == 1,
|
||||
ColumnDefault: anyx.ConvString(re["columnDefault"]),
|
||||
NumScale: anyx.ConvString(re["numScale"]),
|
||||
})
|
||||
@@ -108,7 +109,7 @@ func (md *PgsqlDialect) GetPrimaryKey(tablename string) (string, error) {
|
||||
return "", errorx.NewBiz("[%s] 表不存在", tablename)
|
||||
}
|
||||
for _, v := range columns {
|
||||
if v.ColumnKey == "PRI" {
|
||||
if v.IsPrimaryKey {
|
||||
return v.ColumnName, nil
|
||||
}
|
||||
}
|
||||
@@ -130,7 +131,7 @@ func (md *PgsqlDialect) GetTableIndex(tableName string) ([]dbi.Index, error) {
|
||||
ColumnName: anyx.ConvString(re["columnName"]),
|
||||
IndexType: anyx.ConvString(re["IndexType"]),
|
||||
IndexComment: anyx.ConvString(re["indexComment"]),
|
||||
NonUnique: anyx.ConvInt(re["nonUnique"]),
|
||||
IsUnique: anyx.ConvInt(re["isUnique"]) == 1,
|
||||
SeqInIndex: anyx.ConvInt(re["seqInIndex"]),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -90,17 +90,14 @@ func (sd *SqliteDialect) GetColumns(tableNames ...string) ([]dbi.Column, error)
|
||||
if strings.Contains(defaultValue, "'") {
|
||||
defaultValue = strings.ReplaceAll(defaultValue, "'", "")
|
||||
}
|
||||
columnKey := ""
|
||||
if anyx.ConvInt(re["pk"]) == 1 {
|
||||
columnKey = "PRI"
|
||||
}
|
||||
columns = append(columns, dbi.Column{
|
||||
TableName: tableName,
|
||||
ColumnName: anyx.ConvString(re["name"]),
|
||||
ColumnType: strings.ToLower(anyx.ConvString(re["type"])),
|
||||
ColumnComment: "",
|
||||
Nullable: nullable,
|
||||
ColumnKey: columnKey,
|
||||
IsPrimaryKey: anyx.ConvInt(re["pk"]) == 1,
|
||||
IsIdentity: anyx.ConvInt(re["pk"]) == 1,
|
||||
ColumnDefault: defaultValue,
|
||||
NumScale: "0",
|
||||
})
|
||||
@@ -150,17 +147,13 @@ func (sd *SqliteDialect) GetTableIndex(tableName string) ([]dbi.Index, error) {
|
||||
for _, re := range res {
|
||||
indexSql := anyx.ConvString(re["indexSql"])
|
||||
isUnique := strings.Contains(indexSql, "CREATE UNIQUE INDEX")
|
||||
nonUnique := 1
|
||||
if isUnique {
|
||||
nonUnique = 0
|
||||
}
|
||||
|
||||
indexs = append(indexs, dbi.Index{
|
||||
IndexName: anyx.ConvString(re["indexName"]),
|
||||
ColumnName: extractIndexFields(indexSql),
|
||||
IndexType: anyx.ConvString(re["indexType"]),
|
||||
IndexComment: anyx.ConvString(re["indexComment"]),
|
||||
NonUnique: nonUnique,
|
||||
IsUnique: isUnique,
|
||||
SeqInIndex: 1,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ type MachineInfo struct {
|
||||
Passphrase string `json:"-"` // 私钥口令
|
||||
|
||||
SshTunnelMachine *MachineInfo `json:"-"` // ssh隧道机器
|
||||
TempSshMachineId uint64 `json:"-"` // ssh隧道机器id,用于记录隧道机器id,连接出错后关闭隧道
|
||||
EnableRecorder int8 `json:"-"` // 是否启用终端回放记录
|
||||
TagPath []string `json:"tagPath"`
|
||||
}
|
||||
@@ -47,10 +48,10 @@ func (mi *MachineInfo) Conn() (*Cli, error) {
|
||||
}
|
||||
|
||||
cli := &Cli{Info: mi}
|
||||
sshClient, err := GetSshClient(mi)
|
||||
sshClient, err := GetSshClient(mi, nil)
|
||||
if err != nil {
|
||||
if mi.UseSshTunnel() {
|
||||
CloseSshTunnelMachine(int(mi.SshTunnelMachine.Id), mi.GetTunnelId())
|
||||
CloseSshTunnelMachine(int(mi.TempSshMachineId), mi.GetTunnelId())
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
@@ -83,10 +84,29 @@ func (me *MachineInfo) IfUseSshTunnelChangeIpPort() error {
|
||||
// 修改机器ip地址
|
||||
me.Ip = exposeIp
|
||||
me.Port = exposePort
|
||||
// 代理之后置空跳板机信息,防止重复跳
|
||||
me.TempSshMachineId = me.SshTunnelMachine.Id
|
||||
me.SshTunnelMachine = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetSshClient(m *MachineInfo) (*ssh.Client, error) {
|
||||
func GetSshClient(m *MachineInfo, jumpClient *ssh.Client) (*ssh.Client, error) {
|
||||
// 递归一直取到底层没有跳板机的机器信息
|
||||
if m.SshTunnelMachine != nil {
|
||||
jumpClient, _ = GetSshClient(m.SshTunnelMachine, jumpClient)
|
||||
// 新建一个没有跳板机的机器信息
|
||||
m1 := &MachineInfo{
|
||||
Ip: m.Ip,
|
||||
Port: m.Port,
|
||||
AuthMethod: m.AuthMethod,
|
||||
Username: m.Username,
|
||||
Password: m.Password,
|
||||
Passphrase: m.Passphrase,
|
||||
}
|
||||
// 使用跳板机连接目标机器
|
||||
return GetSshClient(m1, jumpClient)
|
||||
}
|
||||
// 配置 SSH 客户端
|
||||
config := &ssh.ClientConfig{
|
||||
User: m.Username,
|
||||
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||
@@ -94,7 +114,6 @@ func GetSshClient(m *MachineInfo) (*ssh.Client, error) {
|
||||
},
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
if m.AuthMethod == entity.AuthCertAuthMethodPassword {
|
||||
config.Auth = []ssh.AuthMethod{ssh.Password(m.Password)}
|
||||
} else if m.AuthMethod == entity.MachineAuthMethodPublicKey {
|
||||
@@ -113,6 +132,19 @@ func GetSshClient(m *MachineInfo) (*ssh.Client, error) {
|
||||
}
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", m.Ip, m.Port)
|
||||
if jumpClient != nil {
|
||||
// 连接目标服务器
|
||||
netConn, err := jumpClient.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn, channel, reqs, err := ssh.NewClientConn(netConn, addr, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 创建目标服务器的 SSH 客户端
|
||||
return ssh.NewClient(conn, channel, reqs), nil
|
||||
}
|
||||
sshClient, err := ssh.Dial("tcp", addr, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -156,7 +156,7 @@ func GetSshTunnelMachine(machineId int, getMachine func(uint64) (*MachineInfo, e
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sshClient, err := GetSshClient(me)
|
||||
sshClient, err := GetSshClient(me, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user