feat: 支持执行多条sql

This commit is contained in:
meilin.huang
2022-11-02 19:27:40 +08:00
parent 2c863a2774
commit ad616496d1
6 changed files with 70 additions and 21 deletions

View File

@@ -1,7 +1,6 @@
<template> <template>
<div> <div>
<el-dialog title="待执行SQL" v-model="dialogVisible" :show-close="false" width="600px"> <el-dialog title="待执行SQL" v-model="dialogVisible" :show-close="false" width="600px">
如需执行多条sql需要在数据库管理配置连接参数multiStatements=true
<codemirror height="350px" class="codesql" ref="cmEditor" language="sql" v-model="sqlValue" <codemirror height="350px" class="codesql" ref="cmEditor" language="sql" v-model="sqlValue"
:options="cmOptions" /> :options="cmOptions" />
<el-input ref="remarkInputRef" v-model="remark" placeholder="请输入执行备注" class="mt5" /> <el-input ref="remarkInputRef" v-model="remark" placeholder="请输入执行备注" class="mt5" />
@@ -90,14 +89,22 @@ const runSql = async () => {
try { try {
state.btnLoading = true; state.btnLoading = true;
await dbApi.sqlExec.request({ const res = await dbApi.sqlExec.request({
id: state.dbId, id: state.dbId,
db: state.db, db: state.db,
remark: state.remark, remark: state.remark,
sql: state.sqlValue.trim(), sql: state.sqlValue.trim(),
}); });
ElMessage.success('执行成功');
for (let re of res.res) {
if (re.result !== 'success') {
ElMessage.error(`${re.sql} \n执行失败: ${re.result}`);
throw new Error(re.result)
}
}
runSuccess = true; runSuccess = true;
ElMessage.success('执行成功');
} catch (e) { } catch (e) {
runSuccess = false; runSuccess = false;
} }

View File

@@ -36,7 +36,7 @@
<el-table-column label="操作" width> <el-table-column label="操作" width>
<template #default="scope"> <template #default="scope">
<el-link type="primary" @click="showDatabases(scope.row.id, scope.row)" plain size="small" <el-link type="primary" @click="showDatabases(scope.row.id)" plain size="small"
:underline="false">数据库</el-link> :underline="false">数据库</el-link>
</template> </template>
</el-table-column> </el-table-column>

View File

@@ -128,27 +128,43 @@ func (d *Db) ExecSql(rc *ctx.ReqCtx) {
dbInstance := d.DbApp.GetDbInstance(id, db) dbInstance := d.DbApp.GetDbInstance(id, db)
biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.LoginAccount.Id, dbInstance.TagPath), "%s") biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.LoginAccount.Id, dbInstance.TagPath), "%s")
rc.ReqParam = fmt.Sprintf("db: %d:%s | sql: %s", id, db, form.Sql)
biz.NotEmpty(form.Sql, "sql不能为空")
// 去除前后空格及换行符 // 去除前后空格及换行符
sql := strings.TrimFunc(form.Sql, func(r rune) bool { sql := utils.StrTrimSpaceAndBr(form.Sql)
s := string(r)
return s == " " || s == "\n"
})
rc.ReqParam = fmt.Sprintf("db: %d:%s | sql: %s", id, db, sql) execReq := &application.DbSqlExecReq{
biz.NotEmpty(sql, "sql不能为空")
execRes, err := d.DbSqlExecApp.Exec(&application.DbSqlExecReq{
DbId: id, DbId: id,
Db: db, Db: db,
Sql: sql,
Remark: form.Remark, Remark: form.Remark,
DbInstance: dbInstance, DbInstance: dbInstance,
LoginAccount: rc.LoginAccount, LoginAccount: rc.LoginAccount,
}) }
biz.ErrIsNilAppendErr(err, "执行失败: %s")
sqls := strings.Split(sql, ";\n")
isMulti := len(sqls) > 1
var execResAll *application.DbSqlExecRes
for _, s := range sqls {
s = utils.StrTrimSpaceAndBr(s)
// 多条执行,如果有查询语句,则跳过
if isMulti && strings.HasPrefix(strings.ToLower(s), "select") {
continue
}
execReq.Sql = s
execRes, err := d.DbSqlExecApp.Exec(execReq)
biz.ErrIsNilAppendErr(err, "执行失败: %s")
if execResAll == nil {
execResAll = execRes
} else {
execResAll.Merge(execRes)
}
}
colAndRes := make(map[string]interface{}) colAndRes := make(map[string]interface{})
colAndRes["colNames"] = execRes.ColNames colAndRes["colNames"] = execResAll.ColNames
colAndRes["res"] = execRes.Res colAndRes["res"] = execResAll.Res
rc.ResData = colAndRes rc.ResData = colAndRes
} }

View File

@@ -358,7 +358,7 @@ func getDsn(d *entity.Db, db string) string {
var dsn string var dsn string
if d.Type == entity.DbTypeMysql { if d.Type == entity.DbTypeMysql {
// 更多参数参考https://github.com/go-sql-driver/mysql#dsn-data-source-name // 更多参数参考https://github.com/go-sql-driver/mysql#dsn-data-source-name
dsn = fmt.Sprintf("%s:%s@%s(%s:%d)/%s?timeout=8s&multiStatements=true", d.Username, d.Password, d.Network, d.Host, d.Port, db) dsn = fmt.Sprintf("%s:%s@%s(%s:%d)/%s?timeout=8s", d.Username, d.Password, d.Network, d.Host, d.Port, db)
if d.Params != "" { if d.Params != "" {
dsn = fmt.Sprintf("%s&%s", dsn, d.Params) dsn = fmt.Sprintf("%s&%s", dsn, d.Params)
} }

View File

@@ -25,6 +25,21 @@ type DbSqlExecRes struct {
Res []map[string]interface{} Res []map[string]interface{}
} }
// 合并执行结果主要用于执行多条sql使用
func (d *DbSqlExecRes) Merge(execRes *DbSqlExecRes) {
canMerge := len(d.ColNames) == len(execRes.ColNames)
if !canMerge {
return
}
// 列名不一致,则不合并
for i, colName := range d.ColNames {
if execRes.ColNames[i] != colName {
return
}
}
d.Res = append(d.Res, execRes.Res...)
}
type DbSqlExec interface { type DbSqlExec interface {
// 执行sql // 执行sql
Exec(execSqlReq *DbSqlExecReq) (*DbSqlExecRes, error) Exec(execSqlReq *DbSqlExecReq) (*DbSqlExecRes, error)
@@ -187,16 +202,19 @@ func doInsert(insert *sqlparser.Insert, execSqlReq *DbSqlExecReq, dbSqlExec *ent
func doExec(sql string, dbInstance *DbInstance) (*DbSqlExecRes, error) { func doExec(sql string, dbInstance *DbInstance) (*DbSqlExecRes, error) {
rowsAffected, err := dbInstance.Exec(sql) rowsAffected, err := dbInstance.Exec(sql)
execRes := "success"
if err != nil { if err != nil {
return nil, err execRes = err.Error()
} }
res := make([]map[string]interface{}, 0) res := make([]map[string]interface{}, 0)
resData := make(map[string]interface{}) resData := make(map[string]interface{})
resData["影响条数"] = rowsAffected resData["rowsAffected"] = rowsAffected
resData["sql"] = sql
resData["result"] = execRes
res = append(res, resData) res = append(res, resData)
return &DbSqlExecRes{ return &DbSqlExecRes{
ColNames: []string{"影响条数"}, ColNames: []string{"sql", "rowsAffected", "result"},
Res: res, Res: res,
}, nil }, nil
} }

View File

@@ -18,6 +18,14 @@ func StrTrim(str string) string {
return strings.Trim(str, " ") return strings.Trim(str, " ")
} }
// 去除字符串左右空字符与\n换行
func StrTrimSpaceAndBr(str string) string {
return strings.TrimFunc(str, func(r rune) bool {
s := string(r)
return s == " " || s == "\n"
})
}
func SubString(str string, begin, end int) (substr string) { func SubString(str string, begin, end int) (substr string) {
// 将字符串的转换成[]rune // 将字符串的转换成[]rune
rs := []rune(str) rs := []rune(str)