mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-03 16:00:25 +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",
|
"font_class": "sqlite",
|
||||||
"unicode": "e546",
|
"unicode": "e546",
|
||||||
"unicode_decimal": 58694
|
"unicode_decimal": 58694
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "29340317",
|
||||||
|
"name": "temp-mssql",
|
||||||
|
"font_class": "MSSQLNATIVE",
|
||||||
|
"unicode": "e600",
|
||||||
|
"unicode_decimal": 58880
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<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 { dbApi } from './api';
|
||||||
import { DbSqlExecTypeEnum } from './enums';
|
import { DbSqlExecTypeEnum } from './enums';
|
||||||
import PageTable from '@/components/pagetable/PageTable.vue';
|
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||||
@@ -120,6 +120,12 @@ const onShowRollbackSql = async (sqlExecLog: any) => {
|
|||||||
const primaryKey = getPrimaryKey(columns);
|
const primaryKey = getPrimaryKey(columns);
|
||||||
const oldValue = JSON.parse(sqlExecLog.oldValue);
|
const oldValue = JSON.parse(sqlExecLog.oldValue);
|
||||||
|
|
||||||
|
let schema = '';
|
||||||
|
let dbArr = sqlExecLog.db.split('/');
|
||||||
|
if (dbArr.length == 2) {
|
||||||
|
schema = dbArr[1] + '.';
|
||||||
|
}
|
||||||
|
|
||||||
const rollbackSqls = [];
|
const rollbackSqls = [];
|
||||||
if (sqlExecLog.type == DbSqlExecTypeEnum.Update.value) {
|
if (sqlExecLog.type == DbSqlExecTypeEnum.Update.value) {
|
||||||
for (let ov of oldValue) {
|
for (let ov of oldValue) {
|
||||||
@@ -130,7 +136,7 @@ const onShowRollbackSql = async (sqlExecLog: any) => {
|
|||||||
}
|
}
|
||||||
setItems.push(`${key} = ${wrapValue(ov[key])}`);
|
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) {
|
} else if (sqlExecLog.type == DbSqlExecTypeEnum.Delete.value) {
|
||||||
const columnNames = columns.map((c: any) => c.columnName);
|
const columnNames = columns.map((c: any) => c.columnName);
|
||||||
@@ -139,7 +145,7 @@ const onShowRollbackSql = async (sqlExecLog: any) => {
|
|||||||
for (let column of columnNames) {
|
for (let column of columnNames) {
|
||||||
values.push(wrapValue(ov[column]));
|
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 getPrimaryKey = (columns: any) => {
|
||||||
const col = columns.find((c: any) => c.columnKey == 'PRI');
|
const col = columns.find((c: any) => c.isPrimaryKey);
|
||||||
if (col) {
|
if (col) {
|
||||||
return col.columnName;
|
return col.columnName;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,27 +156,31 @@ const dbForm: any = ref(null);
|
|||||||
const dbTypes = [
|
const dbTypes = [
|
||||||
{
|
{
|
||||||
type: 'mysql',
|
type: 'mysql',
|
||||||
label: 'mysql',
|
label: 'MySQL',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'mariadb',
|
type: 'mariadb',
|
||||||
label: 'mariadb',
|
label: 'MariaDB',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'postgres',
|
type: 'postgres',
|
||||||
label: 'postgres',
|
label: 'PostgreSQL',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'dm',
|
type: 'dm',
|
||||||
label: '达梦',
|
label: 'DM',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'oracle',
|
type: 'oracle',
|
||||||
label: 'oracle',
|
label: 'Oracle',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'sqlite',
|
type: 'sqlite',
|
||||||
label: 'sqlite',
|
label: 'Sqlite',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'mssql',
|
||||||
|
label: 'MSSQL',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -196,17 +200,17 @@ const state = reactive({
|
|||||||
remark: '',
|
remark: '',
|
||||||
sshTunnelMachineId: null as any,
|
sshTunnelMachineId: null as any,
|
||||||
},
|
},
|
||||||
subimtForm: {},
|
submitForm: {},
|
||||||
// 原密码
|
// 原密码
|
||||||
pwd: '',
|
pwd: '',
|
||||||
// 原用户名
|
// 原用户名
|
||||||
oldUserName: null,
|
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: saveBtnLoading, execute: saveInstanceExec } = dbApi.saveInstance.useApi(submitForm);
|
||||||
const { isFetching: testConnBtnLoading, execute: testConnExec } = dbApi.testConn.useApi(subimtForm);
|
const { isFetching: testConnBtnLoading, execute: testConnExec } = dbApi.testConn.useApi(submitForm);
|
||||||
|
|
||||||
watch(props, (newValue: any) => {
|
watch(props, (newValue: any) => {
|
||||||
state.dialogVisible = newValue.visible;
|
state.dialogVisible = newValue.visible;
|
||||||
@@ -249,7 +253,7 @@ const testConn = async () => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.subimtForm = await getReqForm();
|
state.submitForm = await getReqForm();
|
||||||
await testConnExec();
|
await testConnExec();
|
||||||
ElMessage.success('连接成功');
|
ElMessage.success('连接成功');
|
||||||
});
|
});
|
||||||
@@ -270,7 +274,7 @@ const btnOk = async () => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.subimtForm = await getReqForm();
|
state.submitForm = await getReqForm();
|
||||||
await saveInstanceExec();
|
await saveInstanceExec();
|
||||||
ElMessage.success('保存成功');
|
ElMessage.success('保存成功');
|
||||||
emit('val-change', state.form);
|
emit('val-change', state.form);
|
||||||
|
|||||||
@@ -165,8 +165,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { defineAsyncComponent, onBeforeUnmount, onMounted, reactive, ref, toRefs } from 'vue';
|
import { defineAsyncComponent, h, onBeforeUnmount, onMounted, reactive, ref, toRefs } from 'vue';
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
import { ElCheckbox, ElMessage, ElMessageBox } from 'element-plus';
|
||||||
import { formatByteSize } from '@/common/utils/format';
|
import { formatByteSize } from '@/common/utils/format';
|
||||||
import { DbInst, registerDbCompletionItemProvider, TabInfo, TabType } from './db';
|
import { DbInst, registerDbCompletionItemProvider, TabInfo, TabType } from './db';
|
||||||
import { NodeType, TagTreeNode } from '../component/tag';
|
import { NodeType, TagTreeNode } from '../component/tag';
|
||||||
@@ -175,7 +175,7 @@ import { dbApi } from './api';
|
|||||||
import { dispposeCompletionItemProvider } from '@/components/monaco/completionItemProvider';
|
import { dispposeCompletionItemProvider } from '@/components/monaco/completionItemProvider';
|
||||||
import SvgIcon from '@/components/svgIcon/index.vue';
|
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||||
import { ContextmenuItem } from '@/components/contextmenu';
|
import { ContextmenuItem } from '@/components/contextmenu';
|
||||||
import { getDbDialect, schemaDbTypes} from './dialect/index'
|
import { getDbDialect, schemaDbTypes } from './dialect/index';
|
||||||
import { sleep } from '@/common/utils/loading';
|
import { sleep } from '@/common/utils/loading';
|
||||||
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
import { TagResourceTypeEnum } from '@/common/commonEnum';
|
||||||
import { Pane, Splitpanes } from 'splitpanes';
|
import { Pane, Splitpanes } from 'splitpanes';
|
||||||
@@ -229,8 +229,11 @@ const nodeClickChangeDb = (nodeData: TagTreeNode) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ContextmenuItemRefresh = new ContextmenuItem('refresh', '刷新').withIcon('RefreshRight').withOnClick((data: any) => reloadNode(data.key));
|
||||||
|
|
||||||
// tagpath 节点类型
|
// tagpath 节点类型
|
||||||
const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath)
|
||||||
|
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||||
const dbInfoRes = await dbApi.dbs.request({ tagPath: parentNode.key });
|
const dbInfoRes = await dbApi.dbs.request({ tagPath: parentNode.key });
|
||||||
const dbInfos = dbInfoRes.list;
|
const dbInfos = dbInfoRes.list;
|
||||||
if (!dbInfos) {
|
if (!dbInfos) {
|
||||||
@@ -243,7 +246,8 @@ const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(asyn
|
|||||||
x.tagPath = parentNode.key;
|
x.tagPath = parentNode.key;
|
||||||
return new TagTreeNode(`${parentNode.key}.${x.id}`, x.name, NodeTypeDbInst).withParams(x);
|
return new TagTreeNode(`${parentNode.key}.${x.id}`, x.name, NodeTypeDbInst).withParams(x);
|
||||||
});
|
});
|
||||||
});
|
})
|
||||||
|
.withContextMenuItems([ContextmenuItemRefresh]);
|
||||||
|
|
||||||
// 数据库实例节点类型
|
// 数据库实例节点类型
|
||||||
const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc((parentNode: TagTreeNode) => {
|
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)
|
const NodeTypeDb = new NodeType(SqlExecNodeType.Db)
|
||||||
.withContextMenuItems([new ContextmenuItem('reloadTables', '刷新').withIcon('RefreshRight').withOnClick((data: any) => reloadNode(data.key))])
|
.withContextMenuItems([ContextmenuItemRefresh])
|
||||||
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||||
const params = parentNode.params;
|
const params = parentNode.params;
|
||||||
params.parentKey = parentNode.key;
|
params.parentKey = parentNode.key;
|
||||||
@@ -291,14 +295,14 @@ const NodeTypeTables = (params: any) => {
|
|||||||
let tableKey = `${params.id}.${params.db}.table-menu`;
|
let tableKey = `${params.id}.${params.db}.table-menu`;
|
||||||
let sqlKey = getSqlMenuNodeKey(params.id, params.db);
|
let sqlKey = getSqlMenuNodeKey(params.id, params.db);
|
||||||
return [
|
return [
|
||||||
new TagTreeNode(`${params.id}.${params.db}.table-menu`, '表', NodeTypeTableMenu).withParams({...params, key:tableKey}).withIcon(TableIcon),
|
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(sqlKey, 'SQL', NodeTypeSqlMenu).withParams({ ...params, key: sqlKey }).withIcon(SqlIcon),
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
// postgres schema模式
|
// postgres schema模式
|
||||||
const NodeTypePostgresSchema = new NodeType(SqlExecNodeType.PgSchema)
|
const NodeTypePostgresSchema = new NodeType(SqlExecNodeType.PgSchema)
|
||||||
.withContextMenuItems([new ContextmenuItem('reloadTables', '刷新').withIcon('RefreshRight').withOnClick((data: any) => reloadNode(data.key))])
|
.withContextMenuItems([ContextmenuItemRefresh])
|
||||||
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
.withLoadNodesFunc(async (parentNode: TagTreeNode) => {
|
||||||
const params = parentNode.params;
|
const params = parentNode.params;
|
||||||
params.parentKey = parentNode.key;
|
params.parentKey = parentNode.key;
|
||||||
@@ -309,7 +313,7 @@ const NodeTypePostgresSchema = new NodeType(SqlExecNodeType.PgSchema)
|
|||||||
// 数据库表菜单节点
|
// 数据库表菜单节点
|
||||||
const NodeTypeTableMenu = new NodeType(SqlExecNodeType.TableMenu)
|
const NodeTypeTableMenu = new NodeType(SqlExecNodeType.TableMenu)
|
||||||
.withContextMenuItems([
|
.withContextMenuItems([
|
||||||
new ContextmenuItem('reloadTables', '刷新').withIcon('RefreshRight').withOnClick((data: any) => reloadNode(data.key)),
|
ContextmenuItemRefresh,
|
||||||
new ContextmenuItem('createTable', '创建表').withIcon('Plus').withOnClick((data: any) => onEditTable(data)),
|
new ContextmenuItem('createTable', '创建表').withIcon('Plus').withOnClick((data: any) => onEditTable(data)),
|
||||||
new ContextmenuItem('tablesOp', '表操作').withIcon('Setting').withOnClick((data: any) => {
|
new ContextmenuItem('tablesOp', '表操作').withIcon('Setting').withOnClick((data: any) => {
|
||||||
const params = data.params;
|
const params = data.params;
|
||||||
@@ -676,13 +680,35 @@ const onDeleteTable = async (data: any) => {
|
|||||||
const onCopyTable = async (data: any) => {
|
const onCopyTable = async (data: any) => {
|
||||||
let { db, id, tableName, parentKey } = data.params;
|
let { db, id, tableName, parentKey } = data.params;
|
||||||
|
|
||||||
|
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
|
// 执行sql
|
||||||
dbApi.copyTable.request({ id, db, tableName, copyData:true }).then(() => {
|
dbApi.copyTable.request({ id, db, tableName, copyData: checked.value }).then(() => {
|
||||||
ElMessage.success('复制成功');
|
ElMessage.success('复制成功');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
parentKey && reloadNode(parentKey);
|
parentKey && reloadNode(parentKey);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmitEditTableSql = () => {
|
const onSubmitEditTableSql = () => {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { NodeType, TagTreeNode } from '@/views/ops/component/tag';
|
|||||||
import { dbApi } from '@/views/ops/db/api';
|
import { dbApi } from '@/views/ops/db/api';
|
||||||
import { sleep } from '@/common/utils/loading';
|
import { sleep } from '@/common/utils/loading';
|
||||||
import SvgIcon from '@/components/svgIcon/index.vue';
|
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 TagTreeResourceSelect from '../../component/TagTreeResourceSelect.vue';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
@@ -90,8 +90,8 @@ const NodeTypeTagPath = new NodeType(TagTreeNode.TagPath).withLoadNodesFunc(asyn
|
|||||||
});
|
});
|
||||||
|
|
||||||
/** mysql类型的数据库,没有schema层 */
|
/** mysql类型的数据库,没有schema层 */
|
||||||
const mysqlType = (type: string) => {
|
const noSchemaType = (type: string) => {
|
||||||
return mysqlDbTypes.includes(type);
|
return noSchemaTypes.includes(type);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 数据库实例节点类型
|
// 数据库实例节点类型
|
||||||
@@ -99,7 +99,7 @@ const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc((p
|
|||||||
const params = parentNode.params;
|
const params = parentNode.params;
|
||||||
const dbs = params.database.split(' ')?.sort();
|
const dbs = params.database.split(' ')?.sort();
|
||||||
let fn: NodeType;
|
let fn: NodeType;
|
||||||
if (mysqlType(params.type)) {
|
if (noSchemaType(params.type)) {
|
||||||
fn = MysqlNodeTypes;
|
fn = MysqlNodeTypes;
|
||||||
} else {
|
} else {
|
||||||
fn = PgNodeTypes;
|
fn = PgNodeTypes;
|
||||||
@@ -117,7 +117,7 @@ const NodeTypeDbInst = new NodeType(SqlExecNodeType.DbInst).withLoadNodesFunc((p
|
|||||||
db: x,
|
db: x,
|
||||||
})
|
})
|
||||||
.withIcon(DbIcon);
|
.withIcon(DbIcon);
|
||||||
if (mysqlType(params.type)) {
|
if (noSchemaType(params.type)) {
|
||||||
tagTreeNode.isLeaf = true;
|
tagTreeNode.isLeaf = true;
|
||||||
}
|
}
|
||||||
return tagTreeNode;
|
return tagTreeNode;
|
||||||
|
|||||||
@@ -128,12 +128,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<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 { getToken } from '@/common/utils/storage';
|
||||||
import { notBlank } from '@/common/assert';
|
import { notBlank } from '@/common/assert';
|
||||||
import { format as sqlFormatter } from 'sql-formatter';
|
import { format as sqlFormatter } from 'sql-formatter';
|
||||||
import config from '@/common/config';
|
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 * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||||
import { editor } from 'monaco-editor';
|
import { editor } from 'monaco-editor';
|
||||||
@@ -146,11 +146,10 @@ import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
|
|||||||
import { joinClientParams } from '@/common/request';
|
import { joinClientParams } from '@/common/request';
|
||||||
import { buildProgressProps } from '@/components/progress-notify/progress-notify';
|
import { buildProgressProps } from '@/components/progress-notify/progress-notify';
|
||||||
import ProgressNotify from '@/components/progress-notify/progress-notify.vue';
|
import ProgressNotify from '@/components/progress-notify/progress-notify.vue';
|
||||||
import { ElNotification } from 'element-plus';
|
|
||||||
import syssocket from '@/common/syssocket';
|
import syssocket from '@/common/syssocket';
|
||||||
import SvgIcon from '@/components/svgIcon/index.vue';
|
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||||
import { getDbDialect } from '../../dialect';
|
import { getDbDialect } from '../../dialect';
|
||||||
import { Splitpanes, Pane } from 'splitpanes';
|
import { Pane, Splitpanes } from 'splitpanes';
|
||||||
|
|
||||||
const emits = defineEmits(['saveSqlSuccess']);
|
const emits = defineEmits(['saveSqlSuccess']);
|
||||||
|
|
||||||
@@ -357,6 +356,7 @@ const onRunSql = async (newTab = false) => {
|
|||||||
const colAndData: any = data.value;
|
const colAndData: any = data.value;
|
||||||
if (!colAndData.res || colAndData.res.length === 0) {
|
if (!colAndData.res || colAndData.res.length === 0) {
|
||||||
ElMessage.warning('未查询到结果集');
|
ElMessage.warning('未查询到结果集');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 要实时响应,故需要用索引改变数据才生效
|
// 要实时响应,故需要用索引改变数据才生效
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<el-input
|
<el-input
|
||||||
v-if="dataType == DataType.String"
|
v-if="dataType == DataType.String"
|
||||||
:ref="(el: any) => focus && el?.focus()"
|
:ref="(el: any) => focus && el?.focus()"
|
||||||
|
:disabled="disabled"
|
||||||
@blur="handleBlur"
|
@blur="handleBlur"
|
||||||
:class="`w100 mb4 ${showEditorIcon ? 'string-input-container-show-icon' : ''}`"
|
:class="`w100 mb4 ${showEditorIcon ? 'string-input-container-show-icon' : ''}`"
|
||||||
input-style="text-align: center; height: 26px;"
|
input-style="text-align: center; height: 26px;"
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
<el-input
|
<el-input
|
||||||
v-else-if="dataType == DataType.Number"
|
v-else-if="dataType == DataType.Number"
|
||||||
:ref="(el: any) => focus && el?.focus()"
|
:ref="(el: any) => focus && el?.focus()"
|
||||||
|
:disabled="disabled"
|
||||||
@blur="handleBlur"
|
@blur="handleBlur"
|
||||||
class="w100 mb4"
|
class="w100 mb4"
|
||||||
input-style="text-align: center; height: 26px;"
|
input-style="text-align: center; height: 26px;"
|
||||||
@@ -28,6 +30,7 @@
|
|||||||
<el-date-picker
|
<el-date-picker
|
||||||
v-else-if="dataType == DataType.Date"
|
v-else-if="dataType == DataType.Date"
|
||||||
:ref="(el: any) => focus && el?.focus()"
|
:ref="(el: any) => focus && el?.focus()"
|
||||||
|
:disabled="disabled"
|
||||||
@change="emit('blur')"
|
@change="emit('blur')"
|
||||||
@blur="handleBlur"
|
@blur="handleBlur"
|
||||||
class="edit-time-picker mb4"
|
class="edit-time-picker mb4"
|
||||||
@@ -43,6 +46,7 @@
|
|||||||
<el-date-picker
|
<el-date-picker
|
||||||
v-else-if="dataType == DataType.DateTime"
|
v-else-if="dataType == DataType.DateTime"
|
||||||
:ref="(el: any) => focus && el?.focus()"
|
:ref="(el: any) => focus && el?.focus()"
|
||||||
|
:disabled="disabled"
|
||||||
@change="handleBlur"
|
@change="handleBlur"
|
||||||
@blur="handleBlur"
|
@blur="handleBlur"
|
||||||
class="edit-time-picker mb4"
|
class="edit-time-picker mb4"
|
||||||
@@ -58,6 +62,7 @@
|
|||||||
<el-time-picker
|
<el-time-picker
|
||||||
v-else-if="dataType == DataType.Time"
|
v-else-if="dataType == DataType.Time"
|
||||||
:ref="(el: any) => focus && el?.focus()"
|
:ref="(el: any) => focus && el?.focus()"
|
||||||
|
:disabled="disabled"
|
||||||
@change="handleBlur"
|
@change="handleBlur"
|
||||||
@blur="handleBlur"
|
@blur="handleBlur"
|
||||||
class="edit-time-picker mb4"
|
class="edit-time-picker mb4"
|
||||||
@@ -71,7 +76,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Ref, ref, computed } from 'vue';
|
import { computed, ref, Ref } from 'vue';
|
||||||
import { ElInput } from 'element-plus';
|
import { ElInput } from 'element-plus';
|
||||||
import { DataType } from '../../dialect/index';
|
import { DataType } from '../../dialect/index';
|
||||||
import SvgIcon from '@/components/svgIcon/index.vue';
|
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||||
@@ -83,11 +88,13 @@ export interface ColumnFormItemProps {
|
|||||||
focus?: boolean; // 是否获取焦点
|
focus?: boolean; // 是否获取焦点
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
columnName?: string;
|
columnName?: string;
|
||||||
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<ColumnFormItemProps>(), {
|
const props = withDefaults(defineProps<ColumnFormItemProps>(), {
|
||||||
focus: false,
|
focus: false,
|
||||||
dataType: DataType.String,
|
dataType: DataType.String,
|
||||||
|
disabled: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue', 'blur']);
|
const emit = defineEmits(['update:modelValue', 'blur']);
|
||||||
|
|||||||
@@ -715,9 +715,13 @@ const submitUpdateFields = async () => {
|
|||||||
const db = state.db;
|
const db = state.db;
|
||||||
let res = '';
|
let res = '';
|
||||||
const dbDialect = getDbDialect(dbInst.type);
|
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()) {
|
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 rowData = updateRow.rowData;
|
||||||
// 主键列信息
|
// 主键列信息
|
||||||
const primaryKey = await dbInst.loadTableColumn(db, state.table);
|
const primaryKey = await dbInst.loadTableColumn(db, state.table);
|
||||||
|
|||||||
@@ -170,8 +170,8 @@
|
|||||||
:page-sizes="pageSizes"
|
:page-sizes="pageSizes"
|
||||||
></el-pagination>
|
></el-pagination>
|
||||||
</el-row>
|
</el-row>
|
||||||
<div style="font-size: 12px; padding: 0 10px; color: #606266">
|
<div style="padding: 0 10px">
|
||||||
<span>{{ state.sql }}</span>
|
<span style="color: var(--el-color-info-light-3)" class="font10 el-text el-text--small is-truncated">{{ state.sql }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-dialog v-model="conditionDialog.visible" :title="conditionDialog.title" width="420px">
|
<el-dialog v-model="conditionDialog.visible" :title="conditionDialog.title" width="420px">
|
||||||
@@ -211,13 +211,14 @@
|
|||||||
class="w100 mb5"
|
class="w100 mb5"
|
||||||
:prop="column.columnName"
|
:prop="column.columnName"
|
||||||
:label="column.columnName"
|
:label="column.columnName"
|
||||||
:required="column.nullable != 'YES' && column.columnKey != 'PRI'"
|
:required="column.nullable != 'YES' && !column.isPrimaryKey && !column.isIdentity"
|
||||||
>
|
>
|
||||||
<ColumnFormItem
|
<ColumnFormItem
|
||||||
v-model="addDataDialog.data[`${column.columnName}`]"
|
v-model="addDataDialog.data[`${column.columnName}`]"
|
||||||
:data-type="dbDialect.getDataType(column.columnType)"
|
:data-type="dbDialect.getDataType(column.columnType)"
|
||||||
:placeholder="`${column.columnType} ${column.columnComment}`"
|
:placeholder="`${column.columnType} ${column.columnComment}`"
|
||||||
:column-name="column.columnName"
|
:column-name="column.columnName"
|
||||||
|
:disabled="column.isIdentity"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
@@ -372,7 +373,7 @@ const selectData = async () => {
|
|||||||
|
|
||||||
const countRes = await dbInst.runSql(db, dbInst.getDefaultCountSql(table, state.condition));
|
const countRes = await dbInst.runSql(db, dbInst.getDefaultCountSql(table, state.condition));
|
||||||
state.count = parseInt(countRes.res[0].count || countRes.res[0].COUNT || 0);
|
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;
|
state.sql = sql;
|
||||||
if (state.count > 0) {
|
if (state.count > 0) {
|
||||||
const colAndData: any = await dbInst.runSql(db, sql);
|
const colAndData: any = await dbInst.runSql(db, sql);
|
||||||
@@ -566,7 +567,13 @@ const addRow = async () => {
|
|||||||
}
|
}
|
||||||
let columnNames = Object.keys(obj).join(',');
|
let columnNames = Object.keys(obj).join(',');
|
||||||
let values = Object.values(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, () => {
|
dbInst.promptExeSql(props.dbName, sql, null, () => {
|
||||||
closeAddDataDialog();
|
closeAddDataDialog();
|
||||||
onRefresh();
|
onRefresh();
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
v-else-if="item.prop === 'auto_increment'"
|
v-else-if="item.prop === 'auto_increment'"
|
||||||
size="small"
|
size="small"
|
||||||
v-model="scope.row.auto_increment"
|
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" />
|
<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 v-if="item.prop === 'unique'" size="small" v-model="scope.row.unique" @change="indexChanges(scope.row)">
|
||||||
</el-checkbox>
|
</el-checkbox>
|
||||||
|
|
||||||
<el-select v-if="item.prop === 'indexType'" disabled size="small" v-model="scope.row.indexType">
|
<el-input 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 === 'indexComment'" size="small" v-model="scope.row.indexComment"> </el-input>
|
<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 { reactive, ref, toRefs, watch } from 'vue';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
import SqlExecBox from '../sqleditor/SqlExecBox';
|
import SqlExecBox from '../sqleditor/SqlExecBox';
|
||||||
import { DbDialect, DbType, getDbDialect, IndexDefinition, RowDefinition } from '../../dialect/index';
|
import { DbType, getDbDialect, IndexDefinition, RowDefinition } from '../../dialect/index';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
visible: {
|
visible: {
|
||||||
@@ -172,7 +170,6 @@ const state = reactive({
|
|||||||
btnloading: false,
|
btnloading: false,
|
||||||
activeName: '1',
|
activeName: '1',
|
||||||
columnTypeList: dbDialect.getInfo().columnTypes,
|
columnTypeList: dbDialect.getInfo().columnTypes,
|
||||||
indexTypeList: ['BTREE', 'NORMAL'], // mysql索引类型详解 http://c.biancheng.net/view/7897.html
|
|
||||||
tableData: {
|
tableData: {
|
||||||
fields: {
|
fields: {
|
||||||
colNames: [
|
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) => {
|
watch(props, async (newValue) => {
|
||||||
state.dialogVisible = newValue.visible;
|
state.dialogVisible = newValue.visible;
|
||||||
@@ -408,7 +405,7 @@ const genSql = () => {
|
|||||||
} else if (state.activeName === '2') {
|
} else if (state.activeName === '2') {
|
||||||
// 修改索引
|
// 修改索引
|
||||||
let changeData = filterChangedData(state.tableData.indexs.oldIndexs, state.tableData.indexs.res, 'indexName');
|
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();
|
formRef.value.resetFields();
|
||||||
state.tableData.tableName = '';
|
state.tableData.tableName = '';
|
||||||
state.tableData.tableComment = '';
|
state.tableData.tableComment = '';
|
||||||
state.tableData.fields.res = [
|
state.tableData.fields.res = [];
|
||||||
{
|
state.tableData.indexs.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: '',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const indexChanges = (row: any) => {
|
const indexChanges = (row: any) => {
|
||||||
@@ -460,6 +437,21 @@ const indexChanges = (row: any) => {
|
|||||||
row.indexComment = `${tableData.value.tableName}表(${name.replaceAll('_', ',')})${commentSuffix}`;
|
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(
|
watch(
|
||||||
() => props.data,
|
() => props.data,
|
||||||
(newValue: any) => {
|
(newValue: any) => {
|
||||||
@@ -492,8 +484,8 @@ watch(
|
|||||||
length,
|
length,
|
||||||
numScale: a.numScale,
|
numScale: a.numScale,
|
||||||
notNull: a.nullable !== 'YES',
|
notNull: a.nullable !== 'YES',
|
||||||
pri: a.columnKey === 'PRI',
|
pri: a.isPrimaryKey,
|
||||||
auto_increment: a.columnKey === 'PRI' /*a.extra?.indexOf('auto_increment') > -1*/,
|
auto_increment: a.isIdentity /*a.extra?.indexOf('auto_increment') > -1*/,
|
||||||
remark: a.columnComment,
|
remark: a.columnComment,
|
||||||
};
|
};
|
||||||
state.tableData.fields.res.push(data);
|
state.tableData.fields.res.push(data);
|
||||||
@@ -513,7 +505,7 @@ watch(
|
|||||||
let data = {
|
let data = {
|
||||||
indexName: a.indexName,
|
indexName: a.indexName,
|
||||||
columnNames: a.columnName?.split(','),
|
columnNames: a.columnName?.split(','),
|
||||||
unique: a.nonUnique === 0 || false,
|
unique: a.isUnique || false,
|
||||||
indexType: a.indexType,
|
indexType: a.indexType,
|
||||||
indexComment: a.indexComment,
|
indexComment: a.indexComment,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ import { editor, languages, Position } from 'monaco-editor';
|
|||||||
|
|
||||||
import { registerCompletionItemProvider } from '@/components/monaco/completionItemProvider';
|
import { registerCompletionItemProvider } from '@/components/monaco/completionItemProvider';
|
||||||
import { DbDialect, EditorCompletionItem, getDbDialect } from './dialect';
|
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();
|
const dbInstCache: Map<number, DbInst> = new Map();
|
||||||
|
|
||||||
@@ -58,14 +62,15 @@ export class DbInst {
|
|||||||
if (!dbName) {
|
if (!dbName) {
|
||||||
throw new Error('dbName不能为空');
|
throw new Error('dbName不能为空');
|
||||||
}
|
}
|
||||||
let db = this.dbs.get(dbName);
|
let key = `${this.id}_${dbName}`;
|
||||||
|
let db = this.dbs.get(key);
|
||||||
if (db) {
|
if (db) {
|
||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
console.info(`new db -> dbId: ${this.id}, dbName: ${dbName}`);
|
console.info(`new db -> dbId: ${this.id}, dbName: ${dbName}`);
|
||||||
db = new Db();
|
db = new Db();
|
||||||
db.name = dbName;
|
db.name = dbName;
|
||||||
this.dbs.set(dbName, db);
|
this.dbs.set(key, db);
|
||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,17 +82,22 @@ export class DbInst {
|
|||||||
*/
|
*/
|
||||||
async loadTables(dbName: string, reload?: boolean) {
|
async loadTables(dbName: string, reload?: boolean) {
|
||||||
const db = this.getDb(dbName);
|
const db = this.getDb(dbName);
|
||||||
// 优先从 table map中获取
|
let key = this.dbTablesKey(dbName);
|
||||||
let tables = db.tables;
|
let tables = tableStorage.value.get(key);
|
||||||
|
// 优先从 table 缓存中获取
|
||||||
if (!reload && tables) {
|
if (!reload && tables) {
|
||||||
|
db.tables = tables;
|
||||||
return tables;
|
return tables;
|
||||||
}
|
}
|
||||||
// 重置列信息缓存与表提示信息
|
// 重置列信息缓存与表提示信息
|
||||||
db.columnsMap?.clear();
|
db.columnsMap?.clear();
|
||||||
db.tableHints = null;
|
|
||||||
console.log(`load tables -> dbName: ${dbName}`);
|
console.log(`load tables -> dbName: ${dbName}`);
|
||||||
tables = await dbApi.tableInfos.request({ id: this.id, db: dbName });
|
tables = await dbApi.tableInfos.request({ id: this.id, db: dbName });
|
||||||
|
tableStorage.value.set(key, tables);
|
||||||
db.tables = tables;
|
db.tables = tables;
|
||||||
|
|
||||||
|
// 异步加载表提示信息
|
||||||
|
this.loadDbHints(dbName, true).then(() => {});
|
||||||
return tables;
|
return tables;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,18 +179,30 @@ export class DbInst {
|
|||||||
return this.getDb(dbName).getColumn(table, columnName);
|
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);
|
const db = this.getDb(dbName);
|
||||||
if (db.tableHints) {
|
let key = this.dbTableHintsKey(dbName);
|
||||||
return db.tableHints;
|
let hints = hintsStorage.value.get(key);
|
||||||
|
if (!reload && hints) {
|
||||||
|
db.tableHints = hints;
|
||||||
|
return hints;
|
||||||
}
|
}
|
||||||
console.log(`load db-hits -> dbName: ${dbName}`);
|
console.log(`load db-hits -> dbName: ${dbName}`);
|
||||||
const hits = await dbApi.hintTables.request({ id: this.id, db: db.name });
|
hints = await dbApi.hintTables.request({ id: this.id, db: db.name });
|
||||||
db.tableHints = hits;
|
db.tableHints = hints;
|
||||||
return hits;
|
hintsStorage.value.set(key, hints);
|
||||||
|
return hints;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -225,8 +247,8 @@ export class DbInst {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 获取指定表的默认查询sql
|
// 获取指定表的默认查询sql
|
||||||
getDefaultSelectSql(table: string, condition: string, orderBy: string, pageNum: number, limit: number = DbInst.DefaultLimit) {
|
getDefaultSelectSql(db: string, table: string, condition: string, orderBy: string, pageNum: number, limit: number = DbInst.DefaultLimit) {
|
||||||
return getDbDialect(this.type).getDefaultSelectSql(table, condition, orderBy, pageNum, limit);
|
return getDbDialect(this.type).getDefaultSelectSql(db, table, condition, orderBy, pageNum, limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -275,6 +297,7 @@ export class DbInst {
|
|||||||
sql,
|
sql,
|
||||||
dbId: this.id,
|
dbId: this.id,
|
||||||
db,
|
db,
|
||||||
|
dbType: getDbDialect(this.type).getInfo().formatSqlDialect,
|
||||||
runSuccessCallback: successFunc,
|
runSuccessCallback: successFunc,
|
||||||
cancelCallback: cancelFunc,
|
cancelCallback: cancelFunc,
|
||||||
});
|
});
|
||||||
@@ -441,7 +464,7 @@ class Db {
|
|||||||
getColumn(table: string, columnName: string = '') {
|
getColumn(table: string, columnName: string = '') {
|
||||||
const cols = this.getColumns(table);
|
const cols = this.getColumns(table);
|
||||||
if (!columnName) {
|
if (!columnName) {
|
||||||
const col = cols.find((c: any) => c.columnKey == 'PRI');
|
const col = cols.find((c: any) => c.isPrimaryKey);
|
||||||
return col || cols[0];
|
return col || cols[0];
|
||||||
}
|
}
|
||||||
return cols.find((c: any) => c.columnName == columnName);
|
return cols.find((c: any) => c.columnName == columnName);
|
||||||
|
|||||||
@@ -375,12 +375,12 @@ class DMDialect implements DbDialect {
|
|||||||
return dmDialectInfo;
|
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)};`;
|
return `SELECT * FROM "${table}" ${condition ? 'WHERE ' + condition : ''} ${orderBy ? orderBy : ''} ${this.getPageSql(pageNum, limit)};`;
|
||||||
}
|
}
|
||||||
|
|
||||||
getPageSql(pageNum: number, limit: number) {
|
getPageSql(pageNum: number, limit: number) {
|
||||||
return ` OFFSET ${(pageNum - 1) * limit} LIMIT ${limit};`;
|
return ` OFFSET ${(pageNum - 1) * limit} LIMIT ${limit}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaultRows(): RowDefinition[] {
|
getDefaultRows(): RowDefinition[] {
|
||||||
@@ -620,7 +620,7 @@ class DMDialect implements DbDialect {
|
|||||||
return dropPkSql + modifySql + dropSql + renameSql + addPkSql + commentSql;
|
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 dropIndexNames: string[] = [];
|
||||||
let addIndexs: any[] = [];
|
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 { OracleDialect } from '@/views/ops/db/dialect/oracle_dialect';
|
||||||
import { MariadbDialect } from '@/views/ops/db/dialect/mariadb_dialect';
|
import { MariadbDialect } from '@/views/ops/db/dialect/mariadb_dialect';
|
||||||
import { SqliteDialect } from '@/views/ops/db/dialect/sqlite_dialect';
|
import { SqliteDialect } from '@/views/ops/db/dialect/sqlite_dialect';
|
||||||
|
import { MssqlDialect } from '@/views/ops/db/dialect/mssql_dialect';
|
||||||
|
|
||||||
export interface sqlColumnType {
|
export interface sqlColumnType {
|
||||||
udtName: string;
|
udtName: string;
|
||||||
@@ -113,15 +114,16 @@ export const DbType = {
|
|||||||
dm: 'dm', // 达梦
|
dm: 'dm', // 达梦
|
||||||
oracle: 'oracle',
|
oracle: 'oracle',
|
||||||
sqlite: 'sqlite',
|
sqlite: 'sqlite',
|
||||||
|
mssql: 'mssql', // ms sqlserver
|
||||||
};
|
};
|
||||||
|
|
||||||
// mysql兼容的数据库
|
// mysql兼容的数据库
|
||||||
export const mysqlDbTypes = [DbType.mysql, DbType.mariadb, DbType.sqlite];
|
export const noSchemaTypes = [DbType.mysql, DbType.mariadb, DbType.sqlite];
|
||||||
|
|
||||||
// 有schema层的数据库
|
// 有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 => {
|
export const compatibleMysql = (dbType: string): boolean => {
|
||||||
switch (dbType) {
|
switch (dbType) {
|
||||||
@@ -141,13 +143,14 @@ export interface DbDialect {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取默认查询sql
|
* 获取默认查询sql
|
||||||
|
* @param db 数据库信息
|
||||||
* @param table 表名
|
* @param table 表名
|
||||||
* @param condition 条件
|
* @param condition 条件
|
||||||
* @param orderBy 排序
|
* @param orderBy 排序
|
||||||
* @param pageNum 页数
|
* @param pageNum 页数
|
||||||
* @param limit 条数
|
* @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;
|
getPageSql(pageNum: number, limit: number): string;
|
||||||
|
|
||||||
@@ -183,10 +186,11 @@ export interface DbDialect {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成编辑索引sql
|
* 生成编辑索引sql
|
||||||
|
* @param tableData 表数据,包含表名、列数据、索引数据
|
||||||
* @param tableName 表名
|
* @param tableName 表名
|
||||||
* @param changeData 改变数据
|
* @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;
|
getDataType(columnType: string): DataType;
|
||||||
@@ -201,6 +205,7 @@ let postgresDialect = new PostgresqlDialect();
|
|||||||
let dmDialect = new DMDialect();
|
let dmDialect = new DMDialect();
|
||||||
let oracleDialect = new OracleDialect();
|
let oracleDialect = new OracleDialect();
|
||||||
let sqliteDialect = new SqliteDialect();
|
let sqliteDialect = new SqliteDialect();
|
||||||
|
let mssqlDialect = new MssqlDialect();
|
||||||
|
|
||||||
export const getDbDialect = (dbType: string | undefined): DbDialect => {
|
export const getDbDialect = (dbType: string | undefined): DbDialect => {
|
||||||
if (!dbType) {
|
if (!dbType) {
|
||||||
@@ -219,6 +224,8 @@ export const getDbDialect = (dbType: string | undefined): DbDialect => {
|
|||||||
return oracleDialect;
|
return oracleDialect;
|
||||||
case DbType.sqlite:
|
case DbType.sqlite:
|
||||||
return sqliteDialect;
|
return sqliteDialect;
|
||||||
|
case DbType.mssql:
|
||||||
|
return mssqlDialect;
|
||||||
default:
|
default:
|
||||||
throw new Error('不支持的数据库');
|
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;
|
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(
|
return `SELECT * FROM ${this.quoteIdentifier(table)} ${condition ? 'WHERE ' + condition : ''} ${orderBy ? orderBy : ''} ${this.getPageSql(
|
||||||
pageNum,
|
pageNum,
|
||||||
limit
|
limit
|
||||||
@@ -252,7 +252,7 @@ class MysqlDialect implements DbDialect {
|
|||||||
return sql + arr.join(',') + ';';
|
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
|
// 搜集修改和删除的索引,添加到drop index xx
|
||||||
// 收集新增和修改的索引,添加到ADD xx
|
// 收集新增和修改的索引,添加到ADD xx
|
||||||
// ALTER TABLE `test1`
|
// ALTER TABLE `test1`
|
||||||
|
|||||||
@@ -92,7 +92,35 @@ const replaceFunctions: EditorCompletionItem[] = [
|
|||||||
{ label: 'NVL2', insertText: 'NVL2(x,value1,value2)', description: '如果x非空,返回value1,否则返回value2' },
|
{ 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;
|
let oracleDialectInfo: DialectInfo;
|
||||||
class OracleDialect implements DbDialect {
|
class OracleDialect implements DbDialect {
|
||||||
@@ -104,6 +132,7 @@ class OracleDialect implements DbDialect {
|
|||||||
let { keywords, operators, builtinVariables } = sqlLanguage;
|
let { keywords, operators, builtinVariables } = sqlLanguage;
|
||||||
let functionNames = replaceFunctions.map((a) => a.label);
|
let functionNames = replaceFunctions.map((a) => a.label);
|
||||||
let excludeKeywords = new Set(functionNames.concat(operators));
|
let excludeKeywords = new Set(functionNames.concat(operators));
|
||||||
|
excludeKeywords.add('SELECT');
|
||||||
|
|
||||||
let editorCompletions: EditorCompletion = {
|
let editorCompletions: EditorCompletion = {
|
||||||
keywords: keywords
|
keywords: keywords
|
||||||
@@ -118,15 +147,7 @@ class OracleDialect implements DbDialect {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.concat(
|
.concat(addCustomKeywords),
|
||||||
// 加上自定义的关键字
|
|
||||||
addCustomKeywords.map(
|
|
||||||
(a): EditorCompletionItem => ({
|
|
||||||
label: a,
|
|
||||||
description: 'keyword',
|
|
||||||
})
|
|
||||||
)
|
|
||||||
),
|
|
||||||
operators: operators.map((a: string): EditorCompletionItem => ({ label: a, description: 'operator' })),
|
operators: operators.map((a: string): EditorCompletionItem => ({ label: a, description: 'operator' })),
|
||||||
functions: replaceFunctions,
|
functions: replaceFunctions,
|
||||||
variables: builtinVariables.map((a: string): EditorCompletionItem => ({ label: a, description: 'var' })),
|
variables: builtinVariables.map((a: string): EditorCompletionItem => ({ label: a, description: 'var' })),
|
||||||
@@ -142,7 +163,7 @@ class OracleDialect implements DbDialect {
|
|||||||
return oracleDialectInfo;
|
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 `
|
return `
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM (
|
FROM (
|
||||||
@@ -399,7 +420,7 @@ class OracleDialect implements DbDialect {
|
|||||||
return dropPkSql + modifySql + dropSql + renameSql + addPkSql + commentSql;
|
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 dropIndexNames: string[] = [];
|
||||||
let addIndexs: any[] = [];
|
let addIndexs: any[] = [];
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ class PostgresqlDialect implements DbDialect {
|
|||||||
return pgDialectInfo;
|
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(
|
return `SELECT * FROM ${this.quoteIdentifier(table)} ${condition ? 'WHERE ' + condition : ''} ${orderBy ? orderBy : ''} ${this.getPageSql(
|
||||||
pageNum,
|
pageNum,
|
||||||
limit
|
limit
|
||||||
@@ -365,7 +365,7 @@ class PostgresqlDialect implements DbDialect {
|
|||||||
return dropPkSql + modifySql + dropSql + renameSql + addPkSql + commentSql;
|
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 dropIndexNames: string[] = [];
|
||||||
let addIndexs: any[] = [];
|
let addIndexs: any[] = [];
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ class SqliteDialect implements DbDialect {
|
|||||||
return sqliteDialectInfo;
|
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(
|
return `SELECT * FROM ${this.quoteIdentifier(table)} ${condition ? 'WHERE ' + condition : ''} ${orderBy ? orderBy : ''} ${this.getPageSql(
|
||||||
pageNum,
|
pageNum,
|
||||||
limit
|
limit
|
||||||
@@ -284,7 +284,7 @@ class SqliteDialect implements DbDialect {
|
|||||||
return sql.join(';') + ';';
|
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创建索引需要先删除再创建
|
// sqlite创建索引需要先删除再创建
|
||||||
// CREATE INDEX "main"."aa1" ON "t_sys_resource" ( "ui_path" );
|
// CREATE INDEX "main"."aa1" ON "t_sys_resource" ( "ui_path" );
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ require (
|
|||||||
github.com/gorilla/websocket v1.5.1
|
github.com/gorilla/websocket v1.5.1
|
||||||
github.com/kanzihuang/vitess/go/vt/sqlparser v0.0.0-20231018071450-ac8d9f0167e9
|
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/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/mojocn/base64Captcha v1.3.6 // 验证码
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/pkg/sftp v1.13.6
|
github.com/pkg/sftp v1.13.6
|
||||||
@@ -54,6 +55,8 @@ require (
|
|||||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
|
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
|
||||||
github.com/goccy/go-json v0.10.2 // 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/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||||
github.com/golang/glog v1.0.0 // indirect
|
github.com/golang/glog v1.0.0 // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
|
|||||||
@@ -29,5 +29,5 @@ type DbCopyTableForm struct {
|
|||||||
Id uint64 `binding:"required" json:"id"`
|
Id uint64 `binding:"required" json:"id"`
|
||||||
Db string `binding:"required" json:"db" `
|
Db string `binding:"required" json:"db" `
|
||||||
TableName string `binding:"required" json:"tableName"`
|
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
|
checkDb := dbName
|
||||||
// 兼容pgsql/dm db/schema模式
|
// 兼容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, "/")
|
ss := strings.Split(dbName, "/")
|
||||||
if len(ss) > 1 {
|
if len(ss) > 1 {
|
||||||
checkDb = ss[0]
|
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)]
|
updFieldVal := srcRes[len(srcRes)-1][strings.ToUpper(task.UpdField)]
|
||||||
if updFieldVal == "" {
|
if updFieldVal == "" || updFieldVal == nil {
|
||||||
updFieldVal = srcRes[len(srcRes)-1][strings.ToLower(task.UpdField)]
|
updFieldVal = srcRes[len(srcRes)-1][strings.ToLower(task.UpdField)]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,12 +332,6 @@ func (app *dataSyncAppImpl) srcData2TargetDb(srcRes []map[string]any, fieldMap [
|
|||||||
return err
|
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)
|
taskParam, _ := app.GetById(new(entity.DataSyncTask), task.Id)
|
||||||
if taskParam.RunningState == entity.DataSyncTaskRunStateStop {
|
if taskParam.RunningState == entity.DataSyncTaskRunStateStop {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"mayfly-go/internal/db/domain/repository"
|
"mayfly-go/internal/db/domain/repository"
|
||||||
"mayfly-go/pkg/contextx"
|
"mayfly-go/pkg/contextx"
|
||||||
"mayfly-go/pkg/errorx"
|
"mayfly-go/pkg/errorx"
|
||||||
|
"mayfly-go/pkg/logx"
|
||||||
"mayfly-go/pkg/model"
|
"mayfly-go/pkg/model"
|
||||||
"mayfly-go/pkg/utils/jsonx"
|
"mayfly-go/pkg/utils/jsonx"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -87,8 +88,14 @@ func (d *dbSqlExecAppImpl) Exec(ctx context.Context, execSqlReq *DbSqlExecReq) (
|
|||||||
// 如果配置为0,则不校验分页参数
|
// 如果配置为0,则不校验分页参数
|
||||||
maxCount := config.GetDbQueryMaxCount()
|
maxCount := config.GetDbQueryMaxCount()
|
||||||
if maxCount != 0 {
|
if maxCount != 0 {
|
||||||
|
|
||||||
|
if !strings.Contains(lowerSql, "limit") &&
|
||||||
// 兼容oracle rownum分页
|
// 兼容oracle rownum分页
|
||||||
if !strings.Contains(lowerSql, "limit") && !strings.Contains(lowerSql, "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语句
|
// 判断是不是count语句
|
||||||
if !strings.Contains(lowerSql, "count(") {
|
if !strings.Contains(lowerSql, "count(") {
|
||||||
return nil, errorx.NewBiz("请完善分页信息后执行")
|
return nil, errorx.NewBiz("请完善分页信息后执行")
|
||||||
@@ -164,7 +171,7 @@ func doSelect(ctx context.Context, selectStmt *sqlparser.Select, execSqlReq *DbS
|
|||||||
// 如果配置为0,则不校验分页参数
|
// 如果配置为0,则不校验分页参数
|
||||||
maxCount := config.GetDbQueryMaxCount()
|
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 {
|
if maxCount != 0 && !skipped {
|
||||||
limit := selectStmt.Limit
|
limit := selectStmt.Limit
|
||||||
if limit == nil {
|
if limit == nil {
|
||||||
@@ -204,6 +211,9 @@ func doUpdate(ctx context.Context, update *sqlparser.Update, execSqlReq *DbSqlEx
|
|||||||
tableStr := sqlparser.String(update.TableExprs)
|
tableStr := sqlparser.String(update.TableExprs)
|
||||||
// 可能使用别名,故空格切割
|
// 可能使用别名,故空格切割
|
||||||
tableName := strings.Split(tableStr, " ")[0]
|
tableName := strings.Split(tableStr, " ")[0]
|
||||||
|
if strings.Index(tableName, ".") > -1 {
|
||||||
|
tableName = strings.Split(tableName, ".")[1]
|
||||||
|
}
|
||||||
where := sqlparser.String(update.Where)
|
where := sqlparser.String(update.Where)
|
||||||
if len(where) == 0 {
|
if len(where) == 0 {
|
||||||
return nil, errorx.NewBiz("SQL[%s]未执行. 请完善 where 条件后再执行", execSqlReq.Sql)
|
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
|
updateColumnsAndPrimaryKey := strings.Join(updateColumns, ",") + "," + primaryKey
|
||||||
// 查询要更新字段数据的旧值,以及主键值
|
// 查询要更新字段数据的旧值,以及主键值
|
||||||
selectSql := fmt.Sprintf("SELECT %s FROM %s %s LIMIT 200", updateColumnsAndPrimaryKey, tableStr, where)
|
selectSql := fmt.Sprintf("SELECT %s FROM %s %s", updateColumnsAndPrimaryKey, tableStr, where)
|
||||||
_, res, err := dbConn.QueryContext(ctx, selectSql)
|
|
||||||
if err == nil {
|
// WalkQuery查出最多200条数据
|
||||||
dbSqlExec.OldValue = jsonx.ToStr(res)
|
maxRec := 200
|
||||||
} else {
|
nowRec := 0
|
||||||
dbSqlExec.OldValue = err.Error()
|
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.Table = tableName
|
||||||
dbSqlExec.Type = entity.DbSqlExecTypeUpdate
|
dbSqlExec.Type = entity.DbSqlExecTypeUpdate
|
||||||
|
|
||||||
|
|||||||
@@ -173,7 +173,12 @@ func walkQueryRows(ctx context.Context, db *sql.DB, selectSql string, walkFn Wal
|
|||||||
// 这里表示一行所有列的值,用[]byte表示
|
// 这里表示一行所有列的值,用[]byte表示
|
||||||
values := make([][]byte, lenCols)
|
values := make([][]byte, lenCols)
|
||||||
for k, colType := range colTypes {
|
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引用values,把数据填充到[]byte里
|
||||||
scans[k] = &values[k]
|
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)
|
rowData := make(map[string]any, lenCols)
|
||||||
// 把values中的数据复制到row中
|
// 把values中的数据复制到row中
|
||||||
for i, v := range values {
|
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 {
|
if err = walkFn(rowData, cols); err != nil {
|
||||||
logx.Error("游标遍历查询结果集出错,退出遍历: %s", err.Error())
|
logx.Error("游标遍历查询结果集出错,退出遍历: %s", err.Error())
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const (
|
|||||||
DbTypeDM DbType = "dm"
|
DbTypeDM DbType = "dm"
|
||||||
DbTypeOracle DbType = "oracle"
|
DbTypeOracle DbType = "oracle"
|
||||||
DbTypeSqlite DbType = "sqlite"
|
DbTypeSqlite DbType = "sqlite"
|
||||||
|
DbTypeMssql DbType = "mssql"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ToDbType(dbType string) DbType {
|
func ToDbType(dbType string) DbType {
|
||||||
@@ -44,6 +45,8 @@ func (dbType DbType) QuoteIdentifier(name string) string {
|
|||||||
return quoteIdentifier(name, "`")
|
return quoteIdentifier(name, "`")
|
||||||
case DbTypePostgres:
|
case DbTypePostgres:
|
||||||
return quoteIdentifier(name, `"`)
|
return quoteIdentifier(name, `"`)
|
||||||
|
case DbTypeMssql:
|
||||||
|
return fmt.Sprintf("[%s]", name)
|
||||||
default:
|
default:
|
||||||
return quoteIdentifier(name, `"`)
|
return quoteIdentifier(name, `"`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ type Column struct {
|
|||||||
ColumnName string `json:"columnName"` // 列名
|
ColumnName string `json:"columnName"` // 列名
|
||||||
ColumnType string `json:"columnType"` // 列类型
|
ColumnType string `json:"columnType"` // 列类型
|
||||||
ColumnComment string `json:"columnComment"` // 列备注
|
ColumnComment string `json:"columnComment"` // 列备注
|
||||||
ColumnKey string `json:"columnKey"` // 是否为主键,逐渐的话值钱为PRI
|
IsPrimaryKey bool `json:"isPrimaryKey"` // 是否为主键
|
||||||
|
IsIdentity bool `json:"isIdentity"` // 是否自增
|
||||||
ColumnDefault string `json:"columnDefault"` // 默认值
|
ColumnDefault string `json:"columnDefault"` // 默认值
|
||||||
Nullable string `json:"nullable"` // 是否可为null
|
Nullable string `json:"nullable"` // 是否可为null
|
||||||
NumScale string `json:"numScale"` // 小数点
|
NumScale string `json:"numScale"` // 小数点
|
||||||
@@ -56,7 +57,7 @@ type Index struct {
|
|||||||
IndexType string `json:"indexType"` // 索引类型
|
IndexType string `json:"indexType"` // 索引类型
|
||||||
IndexComment string `json:"indexComment"` // 备注
|
IndexComment string `json:"indexComment"` // 备注
|
||||||
SeqInIndex int `json:"seqInIndex"`
|
SeqInIndex int `json:"seqInIndex"`
|
||||||
NonUnique int `json:"nonUnique"`
|
IsUnique bool `json:"isUnique"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DbCopyTable struct {
|
type DbCopyTable struct {
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ ORDER BY a.object_name
|
|||||||
select
|
select
|
||||||
a.index_name as INDEX_NAME,
|
a.index_name as INDEX_NAME,
|
||||||
a.index_type as INDEX_TYPE,
|
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,
|
indexdef(b.object_id,1) as INDEX_DEF,
|
||||||
c.column_name as COLUMN_NAME,
|
c.column_name as COLUMN_NAME,
|
||||||
c.column_position as SEQ_IN_INDEX,
|
c.column_position as SEQ_IN_INDEX,
|
||||||
@@ -64,22 +64,26 @@ select a.table_name
|
|||||||
b.comments as COLUMN_COMMENT,
|
b.comments as COLUMN_COMMENT,
|
||||||
a.data_default as COLUMN_DEFAULT,
|
a.data_default as COLUMN_DEFAULT,
|
||||||
a.data_scale as NUM_SCALE,
|
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
|
from all_tab_columns a
|
||||||
left join user_col_comments b
|
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
|
on b.owner = (SELECT SF_GET_SCHEMA_NAME_BY_ID(CURRENT_SCHID))
|
||||||
a.column_name = b.column_name
|
and b.table_name = a.table_name
|
||||||
left join (select b.owner, b.table_name, a.name COL_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,
|
from SYS.SYSCOLUMNS a,
|
||||||
all_tables b,
|
SYS.all_tables b,
|
||||||
sys.sysobjects c,
|
SYS.SYSOBJECTS c
|
||||||
sys.sysobjects d
|
|
||||||
where a.INFO2 & 0x01 = 0x01
|
where a.INFO2 & 0x01 = 0x01
|
||||||
and a.id=c.id and d.type$ = 'SCH' and d.id = c.schid
|
and a.ID = c.ID
|
||||||
and b.owner = (SELECT SF_GET_SCHEMA_NAME_BY_ID(CURRENT_SCHID))
|
and c.NAME = b.TABLE_NAME) t
|
||||||
and c.schid = ( select id from sys.sysobjects where type$ = 'SCH' and name = (SELECT SF_GET_SCHEMA_NAME_BY_ID(CURRENT_SCHID)))
|
on t.table_name = a.table_name and t.owner = a.owner
|
||||||
and c.name = b.table_name) t
|
left join (select uc.OWNER, uic.column_name, uic.table_name, uc.constraint_type
|
||||||
on t.table_name = a.table_name
|
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))
|
where a.owner = (SELECT SF_GET_SCHEMA_NAME_BY_ID(CURRENT_SCHID))
|
||||||
and a.table_name in (%s)
|
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,
|
index_name indexName,
|
||||||
column_name columnName,
|
column_name columnName,
|
||||||
index_type indexType,
|
index_type indexType,
|
||||||
non_unique nonUnique,
|
IF(non_unique, 0, 1) isUnique,
|
||||||
SEQ_IN_INDEX seqInIndex,
|
SEQ_IN_INDEX seqInIndex,
|
||||||
INDEX_COMMENT indexComment
|
INDEX_COMMENT indexComment
|
||||||
FROM
|
FROM
|
||||||
@@ -46,24 +46,25 @@ ORDER BY
|
|||||||
SEQ_IN_INDEX asc
|
SEQ_IN_INDEX asc
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
--MYSQL_COLUMN_MA 列信息元数据
|
--MYSQL_COLUMN_MA 列信息元数据
|
||||||
SELECT
|
SELECT table_name tableName,
|
||||||
table_name tableName,
|
|
||||||
column_name columnName,
|
column_name columnName,
|
||||||
column_type columnType,
|
column_type columnType,
|
||||||
column_default columnDefault,
|
column_default columnDefault,
|
||||||
column_comment columnComment,
|
column_comment columnComment,
|
||||||
column_key columnKey,
|
CASE
|
||||||
extra extra,
|
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,
|
is_nullable nullable,
|
||||||
NUMERIC_SCALE numScale
|
NUMERIC_SCALE numScale
|
||||||
from
|
FROM information_schema.COLUMNS
|
||||||
information_schema.columns
|
WHERE table_schema = (SELECT DATABASE())
|
||||||
WHERE
|
AND table_name IN (%s)
|
||||||
table_schema = (
|
ORDER BY table_name,
|
||||||
SELECT
|
|
||||||
database ()
|
|
||||||
)
|
|
||||||
AND table_name in (%s)
|
|
||||||
ORDER BY
|
|
||||||
tableName,
|
|
||||||
ordinal_position
|
ordinal_position
|
||||||
@@ -21,9 +21,9 @@ ORDER BY a.TABLE_NAME
|
|||||||
SELECT ai.INDEX_NAME AS INDEX_NAME,
|
SELECT ai.INDEX_NAME AS INDEX_NAME,
|
||||||
ai.INDEX_TYPE AS INDEX_TYPE,
|
ai.INDEX_TYPE AS INDEX_TYPE,
|
||||||
CASE
|
CASE
|
||||||
WHEN ai.uniqueness = 'UNIQUE' THEN 'NO'
|
WHEN ai.uniqueness = 'UNIQUE' THEN 1
|
||||||
ELSE 'YES'
|
ELSE 0
|
||||||
END AS NON_UNIQUE,
|
END AS IS_UNIQUE,
|
||||||
(SELECT LISTAGG(column_name, ', ') WITHIN GROUP (ORDER BY column_position)
|
(SELECT LISTAGG(column_name, ', ') WITHIN GROUP (ORDER BY column_position)
|
||||||
FROM ALL_IND_COLUMNS aic
|
FROM ALL_IND_COLUMNS aic
|
||||||
WHERE aic.INDEX_NAME = ai.INDEX_NAME
|
WHERE aic.INDEX_NAME = ai.INDEX_NAME
|
||||||
@@ -53,9 +53,8 @@ SELECT a.TABLE_NAME as TABLE_NAME,
|
|||||||
b.COMMENTS as COLUMN_COMMENT,
|
b.COMMENTS as COLUMN_COMMENT,
|
||||||
a.DATA_DEFAULT as COLUMN_DEFAULT,
|
a.DATA_DEFAULT as COLUMN_DEFAULT,
|
||||||
a.DATA_SCALE as NUM_SCALE,
|
a.DATA_SCALE as NUM_SCALE,
|
||||||
CASE
|
CASE WHEN d.pri IS NOT NULL THEN 1 ELSE 0 END as IS_PRIMARY_KEY,
|
||||||
WHEN d.pri IS NOT NULL THEN 'PRI'
|
CASE WHEN a.IDENTITY_COLUMN = 'YES' THEN 1 ELSE 0 END as IS_IDENTITY
|
||||||
END as COLUMN_KEY
|
|
||||||
FROM all_tab_columns a
|
FROM all_tab_columns a
|
||||||
LEFT JOIN all_col_comments b
|
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
|
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
|
SELECT
|
||||||
indexname AS "indexName",
|
indexname AS "indexName",
|
||||||
'BTREE' AS "IndexType",
|
'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",
|
obj_description(b.oid, 'pg_class') AS "indexComment",
|
||||||
indexdef AS "indexDef",
|
indexdef AS "indexDef",
|
||||||
c.attname AS "columnName",
|
c.attname AS "columnName",
|
||||||
@@ -47,18 +47,21 @@ WHERE a.schemaname = (select current_schema())
|
|||||||
AND a.tablename = '%s';
|
AND a.tablename = '%s';
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
--PGSQL_COLUMN_MA 表列信息
|
--PGSQL_COLUMN_MA 表列信息
|
||||||
SELECT
|
SELECT a.table_name AS "tableName",
|
||||||
table_name AS "tableName",
|
a.column_name AS "columnName",
|
||||||
column_name AS "columnName",
|
a.is_nullable AS "nullable",
|
||||||
is_nullable AS "nullable",
|
|
||||||
case when character_maximum_length > 0 then concat(udt_name, '(',character_maximum_length,')') else udt_name end AS "columnType",
|
case when character_maximum_length > 0 then concat(udt_name, '(',character_maximum_length,')') else udt_name end AS "columnType",
|
||||||
column_default as "columnDefault",
|
a.column_default as "columnDefault",
|
||||||
numeric_scale AS "numScale",
|
a.numeric_scale AS "numScale",
|
||||||
case when column_default like 'nextval%%' then 'PRI' else '' end "columnKey",
|
case when a.column_default like 'nextval%%' then 1 else 0 end "isIdentity",
|
||||||
col_description((table_schema || '.' || table_name)::regclass, ordinal_position) AS "columnComment"
|
case when b.column_name is not null then 1 else 0 end "isPrimaryKey",
|
||||||
FROM information_schema.columns
|
col_description((a.table_schema || '.' || a.table_name)::regclass, a.ordinal_position) AS "columnComment"
|
||||||
WHERE table_schema = (select current_schema()) and table_name in (%s)
|
FROM information_schema.columns a
|
||||||
order by table_name, ordinal_position
|
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函数
|
--PGSQL_TABLE_DDL_FUNC 表ddl函数
|
||||||
CREATE OR REPLACE FUNCTION showcreatetable(namespace character varying, tablename character varying)
|
CREATE OR REPLACE FUNCTION showcreatetable(namespace character varying, tablename character varying)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"mayfly-go/internal/common/consts"
|
"mayfly-go/internal/common/consts"
|
||||||
"mayfly-go/internal/db/dbm/dbi"
|
"mayfly-go/internal/db/dbm/dbi"
|
||||||
_ "mayfly-go/internal/db/dbm/dm"
|
_ "mayfly-go/internal/db/dbm/dm"
|
||||||
|
_ "mayfly-go/internal/db/dbm/mssql"
|
||||||
_ "mayfly-go/internal/db/dbm/mysql"
|
_ "mayfly-go/internal/db/dbm/mysql"
|
||||||
_ "mayfly-go/internal/db/dbm/oracle"
|
_ "mayfly-go/internal/db/dbm/oracle"
|
||||||
_ "mayfly-go/internal/db/dbm/postgres"
|
_ "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"]),
|
ColumnType: anyx.ConvString(re["COLUMN_TYPE"]),
|
||||||
ColumnComment: anyx.ConvString(re["COLUMN_COMMENT"]),
|
ColumnComment: anyx.ConvString(re["COLUMN_COMMENT"]),
|
||||||
Nullable: anyx.ConvString(re["NULLABLE"]),
|
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"]),
|
ColumnDefault: anyx.ConvString(re["COLUMN_DEFAULT"]),
|
||||||
NumScale: anyx.ConvString(re["NUM_SCALE"]),
|
NumScale: anyx.ConvString(re["NUM_SCALE"]),
|
||||||
})
|
})
|
||||||
@@ -118,7 +119,7 @@ func (dd *DMDialect) GetPrimaryKey(tablename string) (string, error) {
|
|||||||
return "", errorx.NewBiz("[%s] 表不存在", tablename)
|
return "", errorx.NewBiz("[%s] 表不存在", tablename)
|
||||||
}
|
}
|
||||||
for _, v := range columns {
|
for _, v := range columns {
|
||||||
if v.ColumnKey == "PRI" {
|
if v.IsPrimaryKey {
|
||||||
return v.ColumnName, nil
|
return v.ColumnName, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,7 +141,7 @@ func (dd *DMDialect) GetTableIndex(tableName string) ([]dbi.Index, error) {
|
|||||||
ColumnName: anyx.ConvString(re["COLUMN_NAME"]),
|
ColumnName: anyx.ConvString(re["COLUMN_NAME"]),
|
||||||
IndexType: anyx.ConvString(re["INDEX_TYPE"]),
|
IndexType: anyx.ConvString(re["INDEX_TYPE"]),
|
||||||
IndexComment: anyx.ConvString(re["INDEX_COMMENT"]),
|
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"]),
|
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"]),
|
ColumnType: anyx.ConvString(re["columnType"]),
|
||||||
ColumnComment: anyx.ConvString(re["columnComment"]),
|
ColumnComment: anyx.ConvString(re["columnComment"]),
|
||||||
Nullable: anyx.ConvString(re["nullable"]),
|
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"]),
|
ColumnDefault: anyx.ConvString(re["columnDefault"]),
|
||||||
NumScale: anyx.ConvString(re["numScale"]),
|
NumScale: anyx.ConvString(re["numScale"]),
|
||||||
})
|
})
|
||||||
@@ -109,7 +110,7 @@ func (md *MysqlDialect) GetPrimaryKey(tablename string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range columns {
|
for _, v := range columns {
|
||||||
if v.ColumnKey == "PRI" {
|
if v.IsPrimaryKey {
|
||||||
return v.ColumnName, nil
|
return v.ColumnName, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,7 +132,7 @@ func (md *MysqlDialect) GetTableIndex(tableName string) ([]dbi.Index, error) {
|
|||||||
ColumnName: anyx.ConvString(re["columnName"]),
|
ColumnName: anyx.ConvString(re["columnName"]),
|
||||||
IndexType: anyx.ConvString(re["indexType"]),
|
IndexType: anyx.ConvString(re["indexType"]),
|
||||||
IndexComment: anyx.ConvString(re["indexComment"]),
|
IndexComment: anyx.ConvString(re["indexComment"]),
|
||||||
NonUnique: anyx.ConvInt(re["nonUnique"]),
|
IsUnique: anyx.ConvInt(re["isUnique"]) == 1,
|
||||||
SeqInIndex: anyx.ConvInt(re["seqInIndex"]),
|
SeqInIndex: anyx.ConvInt(re["seqInIndex"]),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,7 +103,8 @@ func (od *OracleDialect) GetColumns(tableNames ...string) ([]dbi.Column, error)
|
|||||||
ColumnType: anyx.ConvString(re["COLUMN_TYPE"]),
|
ColumnType: anyx.ConvString(re["COLUMN_TYPE"]),
|
||||||
ColumnComment: anyx.ConvString(re["COLUMN_COMMENT"]),
|
ColumnComment: anyx.ConvString(re["COLUMN_COMMENT"]),
|
||||||
Nullable: anyx.ConvString(re["NULLABLE"]),
|
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,
|
ColumnDefault: defaultVal,
|
||||||
NumScale: anyx.ConvString(re["NUM_SCALE"]),
|
NumScale: anyx.ConvString(re["NUM_SCALE"]),
|
||||||
})
|
})
|
||||||
@@ -120,7 +121,7 @@ func (od *OracleDialect) GetPrimaryKey(tablename string) (string, error) {
|
|||||||
return "", errorx.NewBiz("[%s] 表不存在", tablename)
|
return "", errorx.NewBiz("[%s] 表不存在", tablename)
|
||||||
}
|
}
|
||||||
for _, v := range columns {
|
for _, v := range columns {
|
||||||
if v.ColumnKey == "PRI" {
|
if v.IsPrimaryKey {
|
||||||
return v.ColumnName, nil
|
return v.ColumnName, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,7 +143,7 @@ func (od *OracleDialect) GetTableIndex(tableName string) ([]dbi.Index, error) {
|
|||||||
ColumnName: anyx.ConvString(re["COLUMN_NAME"]),
|
ColumnName: anyx.ConvString(re["COLUMN_NAME"]),
|
||||||
IndexType: anyx.ConvString(re["INDEX_TYPE"]),
|
IndexType: anyx.ConvString(re["INDEX_TYPE"]),
|
||||||
IndexComment: anyx.ConvString(re["INDEX_COMMENT"]),
|
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"]),
|
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"]),
|
ColumnType: anyx.ConvString(re["columnType"]),
|
||||||
ColumnComment: anyx.ConvString(re["columnComment"]),
|
ColumnComment: anyx.ConvString(re["columnComment"]),
|
||||||
Nullable: anyx.ConvString(re["nullable"]),
|
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"]),
|
ColumnDefault: anyx.ConvString(re["columnDefault"]),
|
||||||
NumScale: anyx.ConvString(re["numScale"]),
|
NumScale: anyx.ConvString(re["numScale"]),
|
||||||
})
|
})
|
||||||
@@ -108,7 +109,7 @@ func (md *PgsqlDialect) GetPrimaryKey(tablename string) (string, error) {
|
|||||||
return "", errorx.NewBiz("[%s] 表不存在", tablename)
|
return "", errorx.NewBiz("[%s] 表不存在", tablename)
|
||||||
}
|
}
|
||||||
for _, v := range columns {
|
for _, v := range columns {
|
||||||
if v.ColumnKey == "PRI" {
|
if v.IsPrimaryKey {
|
||||||
return v.ColumnName, nil
|
return v.ColumnName, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,7 +131,7 @@ func (md *PgsqlDialect) GetTableIndex(tableName string) ([]dbi.Index, error) {
|
|||||||
ColumnName: anyx.ConvString(re["columnName"]),
|
ColumnName: anyx.ConvString(re["columnName"]),
|
||||||
IndexType: anyx.ConvString(re["IndexType"]),
|
IndexType: anyx.ConvString(re["IndexType"]),
|
||||||
IndexComment: anyx.ConvString(re["indexComment"]),
|
IndexComment: anyx.ConvString(re["indexComment"]),
|
||||||
NonUnique: anyx.ConvInt(re["nonUnique"]),
|
IsUnique: anyx.ConvInt(re["isUnique"]) == 1,
|
||||||
SeqInIndex: anyx.ConvInt(re["seqInIndex"]),
|
SeqInIndex: anyx.ConvInt(re["seqInIndex"]),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,17 +90,14 @@ func (sd *SqliteDialect) GetColumns(tableNames ...string) ([]dbi.Column, error)
|
|||||||
if strings.Contains(defaultValue, "'") {
|
if strings.Contains(defaultValue, "'") {
|
||||||
defaultValue = strings.ReplaceAll(defaultValue, "'", "")
|
defaultValue = strings.ReplaceAll(defaultValue, "'", "")
|
||||||
}
|
}
|
||||||
columnKey := ""
|
|
||||||
if anyx.ConvInt(re["pk"]) == 1 {
|
|
||||||
columnKey = "PRI"
|
|
||||||
}
|
|
||||||
columns = append(columns, dbi.Column{
|
columns = append(columns, dbi.Column{
|
||||||
TableName: tableName,
|
TableName: tableName,
|
||||||
ColumnName: anyx.ConvString(re["name"]),
|
ColumnName: anyx.ConvString(re["name"]),
|
||||||
ColumnType: strings.ToLower(anyx.ConvString(re["type"])),
|
ColumnType: strings.ToLower(anyx.ConvString(re["type"])),
|
||||||
ColumnComment: "",
|
ColumnComment: "",
|
||||||
Nullable: nullable,
|
Nullable: nullable,
|
||||||
ColumnKey: columnKey,
|
IsPrimaryKey: anyx.ConvInt(re["pk"]) == 1,
|
||||||
|
IsIdentity: anyx.ConvInt(re["pk"]) == 1,
|
||||||
ColumnDefault: defaultValue,
|
ColumnDefault: defaultValue,
|
||||||
NumScale: "0",
|
NumScale: "0",
|
||||||
})
|
})
|
||||||
@@ -150,17 +147,13 @@ func (sd *SqliteDialect) GetTableIndex(tableName string) ([]dbi.Index, error) {
|
|||||||
for _, re := range res {
|
for _, re := range res {
|
||||||
indexSql := anyx.ConvString(re["indexSql"])
|
indexSql := anyx.ConvString(re["indexSql"])
|
||||||
isUnique := strings.Contains(indexSql, "CREATE UNIQUE INDEX")
|
isUnique := strings.Contains(indexSql, "CREATE UNIQUE INDEX")
|
||||||
nonUnique := 1
|
|
||||||
if isUnique {
|
|
||||||
nonUnique = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
indexs = append(indexs, dbi.Index{
|
indexs = append(indexs, dbi.Index{
|
||||||
IndexName: anyx.ConvString(re["indexName"]),
|
IndexName: anyx.ConvString(re["indexName"]),
|
||||||
ColumnName: extractIndexFields(indexSql),
|
ColumnName: extractIndexFields(indexSql),
|
||||||
IndexType: anyx.ConvString(re["indexType"]),
|
IndexType: anyx.ConvString(re["indexType"]),
|
||||||
IndexComment: anyx.ConvString(re["indexComment"]),
|
IndexComment: anyx.ConvString(re["indexComment"]),
|
||||||
NonUnique: nonUnique,
|
IsUnique: isUnique,
|
||||||
SeqInIndex: 1,
|
SeqInIndex: 1,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ type MachineInfo struct {
|
|||||||
Passphrase string `json:"-"` // 私钥口令
|
Passphrase string `json:"-"` // 私钥口令
|
||||||
|
|
||||||
SshTunnelMachine *MachineInfo `json:"-"` // ssh隧道机器
|
SshTunnelMachine *MachineInfo `json:"-"` // ssh隧道机器
|
||||||
|
TempSshMachineId uint64 `json:"-"` // ssh隧道机器id,用于记录隧道机器id,连接出错后关闭隧道
|
||||||
EnableRecorder int8 `json:"-"` // 是否启用终端回放记录
|
EnableRecorder int8 `json:"-"` // 是否启用终端回放记录
|
||||||
TagPath []string `json:"tagPath"`
|
TagPath []string `json:"tagPath"`
|
||||||
}
|
}
|
||||||
@@ -47,10 +48,10 @@ func (mi *MachineInfo) Conn() (*Cli, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cli := &Cli{Info: mi}
|
cli := &Cli{Info: mi}
|
||||||
sshClient, err := GetSshClient(mi)
|
sshClient, err := GetSshClient(mi, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if mi.UseSshTunnel() {
|
if mi.UseSshTunnel() {
|
||||||
CloseSshTunnelMachine(int(mi.SshTunnelMachine.Id), mi.GetTunnelId())
|
CloseSshTunnelMachine(int(mi.TempSshMachineId), mi.GetTunnelId())
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -83,10 +84,29 @@ func (me *MachineInfo) IfUseSshTunnelChangeIpPort() error {
|
|||||||
// 修改机器ip地址
|
// 修改机器ip地址
|
||||||
me.Ip = exposeIp
|
me.Ip = exposeIp
|
||||||
me.Port = exposePort
|
me.Port = exposePort
|
||||||
|
// 代理之后置空跳板机信息,防止重复跳
|
||||||
|
me.TempSshMachineId = me.SshTunnelMachine.Id
|
||||||
|
me.SshTunnelMachine = nil
|
||||||
return 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{
|
config := &ssh.ClientConfig{
|
||||||
User: m.Username,
|
User: m.Username,
|
||||||
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
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,
|
Timeout: 5 * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.AuthMethod == entity.AuthCertAuthMethodPassword {
|
if m.AuthMethod == entity.AuthCertAuthMethodPassword {
|
||||||
config.Auth = []ssh.AuthMethod{ssh.Password(m.Password)}
|
config.Auth = []ssh.AuthMethod{ssh.Password(m.Password)}
|
||||||
} else if m.AuthMethod == entity.MachineAuthMethodPublicKey {
|
} 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)
|
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)
|
sshClient, err := ssh.Dial("tcp", addr, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ func GetSshTunnelMachine(machineId int, getMachine func(uint64) (*MachineInfo, e
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sshClient, err := GetSshClient(me)
|
sshClient, err := GetSshClient(me, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user