@@ -13,7 +13,9 @@ | |||
1. 将 SQL 的编写逻辑 `结构化` ,像写文章大纲一样编写和阅读 SQL | |||
2. 重复的 SQL 只需编写一次 ,SQL 变动时修改一处即可 | |||
3. 可以针对某部分 SQL 进行传参和调试 | |||
4. 查看 SQL 语句的引用树 | |||
4. 查看 SQL 语句的引用树和替换过程,便于分析理解 SQL | |||
![查看调用树和替换过程](https://xingqiu-tuchuang-1256524210.cos.ap-shanghai.myqcloud.com/1/image-20220514143425002.png) | |||
## 应用场景 | |||
@@ -63,7 +65,7 @@ from | |||
3. 支持参数透传,比如 @a(xx = #{yy}),yy 变量可传递给 @a 公式 | |||
4. 支持嵌套传参(将子查询作为参数),比如 @a(xx = @b(yy = 1)) | |||
5. 不限制用户在 JSON 中编写的内容,因此该工具也可以作为重复代码生成器来使用 | |||
6. 支持查看 SQL 语句的调用树,便于分析引用关系 | |||
6. 支持查看 SQL 语句的调用树和替换详情,便于分析引用关系 | |||
## 文档 | |||
@@ -1,7 +1,7 @@ | |||
<script setup lang="ts"> | |||
import { doGenerateSQL } from "./generator"; | |||
import { importExample } from "./examples"; | |||
import { onMounted, ref, toRaw, watch } from "vue"; | |||
import { onMounted, ref, toRaw } from "vue"; | |||
import * as monaco from "monaco-editor"; | |||
import { format } from "sql-formatter"; | |||
import { GithubOutlined } from "@ant-design/icons-vue"; | |||
@@ -56,6 +56,7 @@ const getSQL = () => { | |||
toRaw(outputEditor.value).setValue(result); | |||
// 获取调用树 | |||
invokeTree.value = [generateResult.invokeTree]; | |||
console.log(invokeTree.value); | |||
}; | |||
const showInvokeTree = () => { | |||
@@ -120,7 +121,7 @@ onMounted(() => { | |||
<a-button size="large" type="primary" ghost @click="showInvokeTree"> | |||
查看调用树 | |||
</a-button> | |||
<a-button size="large" type="default" @click="importExample"> | |||
<a-button size="large" type="default" @click="importExample('init')"> | |||
导入例子 | |||
</a-button> | |||
</a-space> | |||
@@ -13,13 +13,37 @@ | |||
:tree-data="tree" | |||
@expand="onExpand" | |||
> | |||
<template #title="{ title }"> | |||
<span v-if="title.indexOf(searchValue) > -1"> | |||
{{ title.substr(0, title.indexOf(searchValue)) }} | |||
<span style="color: #f50">{{ searchValue }}</span> | |||
{{ title.substr(title.indexOf(searchValue) + searchValue.length) }} | |||
</span> | |||
<span v-else>{{ title }}</span> | |||
<template #title="{ title, sql, params, resultSQL }"> | |||
<a-popover title="详情" placement="top"> | |||
<template #content> | |||
<div style="max-width: 600px"> | |||
<p> | |||
<b>替换前语句:</b> | |||
<a-typography-paragraph copyable> | |||
{{ sql }} | |||
</a-typography-paragraph> | |||
</p> | |||
<p> | |||
<b>替换参数:</b> | |||
<a-typography-paragraph copyable> | |||
{{ params ?? "无" }} | |||
</a-typography-paragraph> | |||
</p> | |||
<p> | |||
<b>替换后语句:</b> | |||
<a-typography-paragraph copyable> | |||
{{ resultSQL }} | |||
</a-typography-paragraph> | |||
</p> | |||
</div> | |||
</template> | |||
<span v-if="title.indexOf(searchValue) > -1"> | |||
{{ title.substr(0, title.indexOf(searchValue)) }} | |||
<span style="color: #f50">{{ searchValue }}</span> | |||
{{ title.substr(title.indexOf(searchValue) + searchValue.length) }} | |||
</span> | |||
<span v-else>{{ title }}</span> | |||
</a-popover> | |||
</template> | |||
</a-tree> | |||
</div> | |||
@@ -19,35 +19,55 @@ export function doGenerateSQL(json: InputJSON) { | |||
const rootInvokeTreeNode = { ...initTreeNode }; | |||
const context = json; | |||
const resultSQL = generateSQL( | |||
context.main, | |||
"main", | |||
context, | |||
context.main?.params, | |||
rootInvokeTreeNode | |||
); | |||
return { | |||
resultSQL, | |||
invokeTree: rootInvokeTreeNode, | |||
invokeTree: rootInvokeTreeNode.children[0], // 取第一个作为根节点 | |||
}; | |||
} | |||
/** | |||
* 递归生成 SQL | |||
* @param currentNode | |||
* @param key | |||
* @param context | |||
* @param params | |||
* @param invokeTreeNode | |||
*/ | |||
function generateSQL( | |||
currentNode: InputJSONValue, | |||
key: string, | |||
context: InputJSON, | |||
params?: Record<string, string>, | |||
invokeTreeNode?: InvokeTreeNode | |||
): string { | |||
const currentNode = context[key]; | |||
if (!currentNode) { | |||
return ""; | |||
} | |||
const result = replaceParams(currentNode, context, params, invokeTreeNode); | |||
return replaceSubSql(result, context, invokeTreeNode); | |||
let childInvokeTreeNode: InvokeTreeNode | undefined; | |||
if (invokeTreeNode) { | |||
childInvokeTreeNode = { | |||
title: key, | |||
sql: currentNode.sql ?? currentNode, | |||
params, | |||
children: [], | |||
}; | |||
invokeTreeNode.children?.push(childInvokeTreeNode); | |||
} | |||
const result = replaceParams( | |||
currentNode, | |||
context, | |||
params, | |||
childInvokeTreeNode | |||
); | |||
const resultSQL = replaceSubSql(result, context, childInvokeTreeNode); | |||
if (childInvokeTreeNode) { | |||
childInvokeTreeNode.resultSQL = resultSQL; | |||
} | |||
return resultSQL; | |||
} | |||
/** | |||
@@ -140,23 +160,8 @@ function replaceSubSql( | |||
const key = keyValueArray[0].trim(); | |||
params[key] = keyValueArray[1].trim(); | |||
} | |||
let childInvokeTreeNode; | |||
if (invokeTreeNode) { | |||
childInvokeTreeNode = { | |||
title: subKey, | |||
sql, | |||
params, | |||
children: [], | |||
}; | |||
invokeTreeNode.children?.push(childInvokeTreeNode); | |||
} | |||
// 递归解析被替换节点 | |||
const replacement = generateSQL( | |||
replacementNode, | |||
context, | |||
params, | |||
childInvokeTreeNode | |||
); | |||
const replacement = generateSQL(subKey, context, params, invokeTreeNode); | |||
result = result.replace(regExpMatchArray[0], replacement); | |||
regExpMatchArray = matchSubQuery(result); | |||
} | |||
@@ -16,6 +16,7 @@ interface InvokeTreeNode { | |||
sql: string; | |||
key?: string; | |||
params?: Record<string, string>; | |||
resultSQL?: string; | |||
children?: InvokeTreeNode[]; | |||
} | |||