Merge remote-tracking branch 'upstream/dev' into dev_1207_dm

This commit is contained in:
刘宗洋
2023-12-07 14:32:07 +08:00
14 changed files with 117 additions and 74 deletions

File diff suppressed because one or more lines are too long

View File

@@ -26,6 +26,13 @@
"unicode": "e8b7", "unicode": "e8b7",
"unicode_decimal": 59575 "unicode_decimal": 59575
}, },
{
"icon_id": "12295203",
"name": "达梦数据库",
"font_class": "db-dm",
"unicode": "e6f0",
"unicode_decimal": 59120
},
{ {
"icon_id": "10055634", "icon_id": "10055634",
"name": "云数据库MongoDB", "name": "云数据库MongoDB",

View File

@@ -8,10 +8,11 @@
<el-input v-model.trim="form.name" placeholder="请输入数据库别名" auto-complete="off"></el-input> <el-input v-model.trim="form.name" placeholder="请输入数据库别名" auto-complete="off"></el-input>
</el-form-item> </el-form-item>
<el-form-item prop="type" label="类型" required> <el-form-item prop="type" label="类型" required>
<el-select style="width: 100%" v-model="form.type" placeholder="请选择数据库类型"> <el-select @change="changeDbType" style="width: 100%" v-model="form.type" placeholder="请选择数据库类型">
<el-option key="item.id" label="mysql" value="mysql"> </el-option> <el-option v-for="dt in dbTypes" :key="dt.type" :value="dt.type">
<el-option key="item.id" label="postgres" value="postgres"> </el-option> <SvgIcon :name="getDbDialect(dt.type).getInfo().icon" :size="18" />
<el-option key="item.id" label="达梦(暂不支持ssh)" value="dm"> </el-option> {{ dt.label }}
</el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item prop="host" label="host" required> <el-form-item prop="host" label="host" required>
@@ -86,6 +87,8 @@ import { ElMessage } from 'element-plus';
import { notBlank } from '@/common/assert'; import { notBlank } from '@/common/assert';
import { RsaEncrypt } from '@/common/rsa'; import { RsaEncrypt } from '@/common/rsa';
import SshTunnelSelect from '../component/SshTunnelSelect.vue'; import SshTunnelSelect from '../component/SshTunnelSelect.vue';
import { getDbDialect } from './dialect';
import SvgIcon from '@/components/svgIcon/index.vue';
const props = defineProps({ const props = defineProps({
visible: { visible: {
@@ -121,7 +124,7 @@ const rules = {
{ {
required: true, required: true,
message: '请输入主机ip和port', message: '请输入主机ip和port',
trigger: ['change', 'blur'], trigger: ['blur'],
}, },
], ],
username: [ username: [
@@ -135,6 +138,21 @@ const rules = {
const dbForm: any = ref(null); const dbForm: any = ref(null);
const dbTypes = [
{
type: 'mysql',
label: 'mysql',
},
{
type: 'postgres',
label: 'postgres',
},
{
type: 'dm',
label: '达梦(暂不支持ssh)',
},
];
const state = reactive({ const state = reactive({
dialogVisible: false, dialogVisible: false,
tabActiveName: 'basic', tabActiveName: 'basic',
@@ -143,7 +161,7 @@ const state = reactive({
type: null, type: null,
name: null, name: null,
host: '', host: '',
port: 3306, port: null,
username: null, username: null,
password: null, password: null,
params: null, params: null,
@@ -170,11 +188,17 @@ watch(props, (newValue: any) => {
state.form = { ...newValue.data }; state.form = { ...newValue.data };
state.oldUserName = state.form.username; state.oldUserName = state.form.username;
} else { } else {
state.form = { port: 3306 } as any; state.form = { port: null } as any;
state.oldUserName = null; state.oldUserName = null;
} }
}); });
const changeDbType = (val: string) => {
if (!state.form.id) {
state.form.port = getDbDialect(val).getInfo().defaultPort as any;
}
};
const getDbPwd = async () => { const getDbPwd = async () => {
state.pwd = await dbApi.getInstancePwd.request({ id: state.form.id }); state.pwd = await dbApi.getInstancePwd.request({ id: state.form.id });
}; };

View File

@@ -7,7 +7,7 @@
<span v-if="data.type.value == SqlExecNodeType.DbInst"> <span v-if="data.type.value == SqlExecNodeType.DbInst">
<el-popover :show-after="500" placement="right-start" title="数据库实例信息" trigger="hover" :width="250"> <el-popover :show-after="500" placement="right-start" title="数据库实例信息" trigger="hover" :width="250">
<template #reference> <template #reference>
<SvgIcon :name="getDbDialect(data.params.type).getIcon()" :size="18" /> <SvgIcon :name="getDbDialect(data.params.type).getInfo().icon" :size="18" />
</template> </template>
<template #default> <template #default>
<el-descriptions :column="1" size="small"> <el-descriptions :column="1" size="small">
@@ -66,7 +66,7 @@
<el-descriptions-item label-align="right"> <el-descriptions-item label-align="right">
<template #label> <template #label>
<div> <div>
<SvgIcon :name="getDbDialect(nowDbInst.type).getIcon()" :size="18" /> <SvgIcon :name="getDbDialect(nowDbInst.type).getInfo().icon" :size="18" />
实例 实例
</div> </div>
</template> </template>
@@ -104,7 +104,7 @@
{{ dt.params.name }} {{ dt.params.name }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="host"> <el-descriptions-item label="host">
<SvgIcon :name="getDbDialect(dt.params.type).getIcon()" :size="18" /> <SvgIcon :name="getDbDialect(dt.params.type).getInfo().icon" :size="18" />
{{ dt.params.host }} {{ dt.params.host }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="库名"> <el-descriptions-item label="库名">

View File

@@ -332,7 +332,7 @@ const selectData = async () => {
const table = props.tableName; const table = props.tableName;
try { try {
const countRes = await dbInst.runSql(db, dbInst.getDefaultCountSql(table, state.condition)); const countRes = await dbInst.runSql(db, dbInst.getDefaultCountSql(table, state.condition));
state.count = countRes.res[0].count || countRes.res[0].COUNT; state.count = 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(table, state.condition, state.orderBy, state.pageNum, state.pageSize);
state.sql = sql; state.sql = sql;
if (state.count > 0) { if (state.count > 0) {

View File

@@ -167,7 +167,7 @@ const state = reactive({
dialogVisible: false, dialogVisible: false,
btnloading: false, btnloading: false,
activeName: '1', activeName: '1',
columnTypeList: dbDialect.getColumnTypes(), columnTypeList: dbDialect.getInfo().columnTypes,
indexTypeList: ['BTREE'], // mysql索引类型详解 http://c.biancheng.net/view/7897.html indexTypeList: ['BTREE'], // mysql索引类型详解 http://c.biancheng.net/view/7897.html
tableData: { tableData: {
fields: { fields: {
@@ -425,7 +425,7 @@ const submit = async () => {
sql: sql, sql: sql,
dbId: props.dbId as any, dbId: props.dbId as any,
db: props.db as any, db: props.db as any,
dbType: dbDialect.getFormatDialect(), dbType: dbDialect.getInfo().formatSqlDialect,
runSuccessCallback: () => { runSuccessCallback: () => {
emit('submit-sql', { tableName: state.tableData.tableName }); emit('submit-sql', { tableName: state.tableData.tableName });
// cancel(); // cancel();

View File

@@ -1,5 +1,4 @@
import { DbDialect, sqlColumnType } from './index'; import { DbDialect, sqlColumnType, DialectInfo } from './index';
import { SqlLanguage } from 'sql-formatter/lib/src/sqlFormatter';
export { DMDialect, GAUSS_TYPE_LIST }; export { DMDialect, GAUSS_TYPE_LIST };
@@ -83,13 +82,16 @@ const GAUSS_TYPE_LIST: sqlColumnType[] = [
{ udtName: 'macaddr', dataType: 'macaddr', desc: 'MAC地址', space: '6字节' }, { udtName: 'macaddr', dataType: 'macaddr', desc: 'MAC地址', space: '6字节' },
]; ];
class DMDialect implements DbDialect { const dmDialectInfo: DialectInfo = {
getFormatDialect(): SqlLanguage { icon: 'iconfont icon-db-dm',
return 'postgresql'; defaultPort: 5236,
} formatSqlDialect: 'postgresql',
columnTypes: GAUSS_TYPE_LIST.sort((a, b) => a.udtName.localeCompare(b.udtName)),
};
getIcon() { class DMDialect implements DbDialect {
return 'iconfont icon-op-postgres'; getInfo() {
return dmDialectInfo;
} }
getDefaultSelectSql(table: string, condition: string, orderBy: string, pageNum: number, limit: number) { getDefaultSelectSql(table: string, condition: string, orderBy: string, pageNum: number, limit: number) {
@@ -102,10 +104,6 @@ class DMDialect implements DbDialect {
return name; return name;
}; };
getColumnTypes(): sqlColumnType[] {
return GAUSS_TYPE_LIST.sort((a, b) => a.udtName.localeCompare(b.udtName));
}
matchType(text: string, arr: string[]): boolean { matchType(text: string, arr: string[]): boolean {
if (!text || !arr || arr.length === 0) { if (!text || !arr || arr.length === 0) {
return false; return false;

View File

@@ -11,6 +11,29 @@ export interface sqlColumnType {
range?: string; range?: string;
} }
// 数据库基础信息
export interface DialectInfo {
/**
* 图标
*/
icon: string;
/**
* 默认端口
*/
defaultPort: number;
/**
* 格式化sql的方言
*/
formatSqlDialect: SqlLanguage;
/**
* 列字段类型
*/
columnTypes: sqlColumnType[];
}
export const DbType = { export const DbType = {
mysql: 'mysql', mysql: 'mysql',
postgresql: 'postgres', postgresql: 'postgres',
@@ -19,14 +42,9 @@ export const DbType = {
export interface DbDialect { export interface DbDialect {
/** /**
* 获取格式化sql对应的dialect名称 * 获取一些数据库默认信息
*/ */
getFormatDialect(): SqlLanguage; getInfo(): DialectInfo;
/**
* 获取图标信息
*/
getIcon(): string;
/** /**
* 获取默认查询sql * 获取默认查询sql
@@ -44,11 +62,6 @@ export interface DbDialect {
*/ */
wrapName(name: string): string; wrapName(name: string): string;
/**
* 生成字段类型列表
* */
getColumnTypes(): sqlColumnType[];
/** /**
* 生成创建表sql * 生成创建表sql
* @param tableData 建表数据 * @param tableData 建表数据

View File

@@ -1,5 +1,4 @@
import { DbDialect, sqlColumnType } from './index'; import { DbDialect, DialectInfo } from './index';
import { SqlLanguage } from 'sql-formatter/lib/src/sqlFormatter';
export { MYSQL_TYPE_LIST, MysqlDialect, language }; export { MYSQL_TYPE_LIST, MysqlDialect, language };
@@ -30,13 +29,16 @@ const MYSQL_TYPE_LIST = [
'varchar', 'varchar',
]; ];
class MysqlDialect implements DbDialect { const mysqlDialectInfo: DialectInfo = {
getFormatDialect(): SqlLanguage { icon: 'iconfont icon-op-mysql',
return 'mysql'; defaultPort: 3306,
} formatSqlDialect: 'mysql',
columnTypes: MYSQL_TYPE_LIST.map((a) => ({ udtName: a, dataType: a, desc: '', space: '' })),
};
getIcon() { class MysqlDialect implements DbDialect {
return 'iconfont icon-op-mysql'; getInfo(): DialectInfo {
return mysqlDialectInfo;
} }
getDefaultSelectSql(table: string, condition: string, orderBy: string, pageNum: number, limit: number) { getDefaultSelectSql(table: string, condition: string, orderBy: string, pageNum: number, limit: number) {
@@ -49,10 +51,6 @@ class MysqlDialect implements DbDialect {
return `\`${name}\``; return `\`${name}\``;
}; };
getColumnTypes(): sqlColumnType[] {
return MYSQL_TYPE_LIST.map((a) => ({ udtName: a, dataType: a, desc: '', space: '' }));
}
genColumnBasicSql(cl: any): string { genColumnBasicSql(cl: any): string {
let val = cl.value ? (cl.value === 'CURRENT_TIMESTAMP' ? cl.value : `'${cl.value}'`) : ''; let val = cl.value ? (cl.value === 'CURRENT_TIMESTAMP' ? cl.value : `'${cl.value}'`) : '';
let defVal = val ? `DEFAULT ${val}` : ''; let defVal = val ? `DEFAULT ${val}` : '';

View File

@@ -1,5 +1,4 @@
import { DbDialect, sqlColumnType } from './index'; import { DbDialect, DialectInfo, sqlColumnType } from './index';
import { SqlLanguage } from 'sql-formatter/lib/src/sqlFormatter';
export { PostgresqlDialect, GAUSS_TYPE_LIST }; export { PostgresqlDialect, GAUSS_TYPE_LIST };
@@ -83,13 +82,16 @@ const GAUSS_TYPE_LIST: sqlColumnType[] = [
{ udtName: 'macaddr', dataType: 'macaddr', desc: 'MAC地址', space: '6字节' }, { udtName: 'macaddr', dataType: 'macaddr', desc: 'MAC地址', space: '6字节' },
]; ];
class PostgresqlDialect implements DbDialect { const postgresDialectInfo: DialectInfo = {
getFormatDialect(): SqlLanguage { icon: 'iconfont icon-op-postgres',
return 'postgresql'; defaultPort: 5432,
} formatSqlDialect: 'postgresql',
columnTypes: GAUSS_TYPE_LIST.sort((a, b) => a.udtName.localeCompare(b.udtName)),
};
getIcon() { class PostgresqlDialect implements DbDialect {
return 'iconfont icon-op-postgres'; getInfo(): DialectInfo {
return postgresDialectInfo;
} }
getDefaultSelectSql(table: string, condition: string, orderBy: string, pageNum: number, limit: number) { getDefaultSelectSql(table: string, condition: string, orderBy: string, pageNum: number, limit: number) {
@@ -102,10 +104,6 @@ class PostgresqlDialect implements DbDialect {
return name; return name;
}; };
getColumnTypes(): sqlColumnType[] {
return GAUSS_TYPE_LIST.sort((a, b) => a.udtName.localeCompare(b.udtName));
}
matchType(text: string, arr: string[]): boolean { matchType(text: string, arr: string[]): boolean {
if (!text || !arr || arr.length === 0) { if (!text || !arr || arr.length === 0) {
return false; return false;

View File

@@ -34,8 +34,6 @@ require (
gorm.io/gorm v1.25.5 gorm.io/gorm v1.25.5
) )
require golang.org/x/exp v0.0.0-20230519143937-03e91628a987
require ( require (
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
@@ -79,6 +77,7 @@ require (
github.com/xdg-go/stringprep v1.0.4 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
golang.org/x/arch v0.3.0 // indirect golang.org/x/arch v0.3.0 // indirect
golang.org/x/exp v0.0.0-20230519143937-03e91628a987
golang.org/x/image v0.0.0-20220302094943-723b81ca9867 // indirect golang.org/x/image v0.0.0-20220302094943-723b81ca9867 // indirect
golang.org/x/net v0.18.0 // indirect golang.org/x/net v0.18.0 // indirect
golang.org/x/sync v0.1.0 // indirect golang.org/x/sync v0.1.0 // indirect

View File

@@ -115,7 +115,7 @@ func (d *Db) ExecSql(rc *req.Ctx) {
ctx := rc.MetaCtx ctx := rc.MetaCtx
// 如果存在执行id则保存取消函数用于后续可能的取消操作 // 如果存在执行id则保存取消函数用于后续可能的取消操作
if form.ExecId != "" { if form.ExecId != "" {
cancelCtx, cancel := context.WithCancel(rc.MetaCtx) cancelCtx, cancel := context.WithTimeout(rc.MetaCtx, 55*time.Second)
ctx = cancelCtx ctx = cancelCtx
cancelExecSqlMap.Store(form.ExecId, cancel) cancelExecSqlMap.Store(form.ExecId, cancel)
defer cancelExecSqlMap.Delete(form.ExecId) defer cancelExecSqlMap.Delete(form.ExecId)

View File

@@ -33,10 +33,7 @@ func (d *DbConn) QueryContext(ctx context.Context, querySql string) ([]string, [
result = append(result, record) result = append(result, record)
}) })
if err != nil { if err != nil {
if err == context.Canceled { return nil, nil, wrapSqlError(err)
return nil, nil, errorx.NewBiz("取消执行")
}
return nil, nil, err
} }
return columns, result, nil return columns, result, nil
} }
@@ -74,10 +71,7 @@ func (d *DbConn) Exec(sql string) (int64, error) {
func (d *DbConn) ExecContext(ctx context.Context, sql string) (int64, error) { func (d *DbConn) ExecContext(ctx context.Context, sql string) (int64, error) {
res, err := d.db.ExecContext(ctx, sql) res, err := d.db.ExecContext(ctx, sql)
if err != nil { if err != nil {
if err == context.Canceled { return 0, wrapSqlError(err)
return 0, errorx.NewBiz("取消执行")
}
return 0, err
} }
return res.RowsAffected() return res.RowsAffected()
} }
@@ -202,3 +196,14 @@ func valueConvert(data []byte, colType *sql.ColumnType) any {
return stringV return stringV
} }
// 包装sql执行相关错误
func wrapSqlError(err error) error {
if err == context.Canceled {
return errorx.NewBiz("取消执行")
}
if err == context.DeadlineExceeded {
return errorx.NewBiz("执行超时")
}
return err
}

View File

@@ -4,10 +4,11 @@ import (
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
_ "gitee.com/chunanyong/dm"
"mayfly-go/pkg/errorx" "mayfly-go/pkg/errorx"
"mayfly-go/pkg/utils/anyx" "mayfly-go/pkg/utils/anyx"
"strings" "strings"
_ "gitee.com/chunanyong/dm"
) )
func getDmDB(d *DbInfo) (*sql.DB, error) { func getDmDB(d *DbInfo) (*sql.DB, error) {