@@ -13,7 +13,9 @@ | |||||
1. 将 SQL 的编写逻辑 `结构化` ,像写文章大纲一样编写和阅读 SQL | 1. 将 SQL 的编写逻辑 `结构化` ,像写文章大纲一样编写和阅读 SQL | ||||
2. 重复的 SQL 只需编写一次 ,SQL 变动时修改一处即可 | 2. 重复的 SQL 只需编写一次 ,SQL 变动时修改一处即可 | ||||
3. 可以针对某部分 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 公式 | 3. 支持参数透传,比如 @a(xx = #{yy}),yy 变量可传递给 @a 公式 | ||||
4. 支持嵌套传参(将子查询作为参数),比如 @a(xx = @b(yy = 1)) | 4. 支持嵌套传参(将子查询作为参数),比如 @a(xx = @b(yy = 1)) | ||||
5. 不限制用户在 JSON 中编写的内容,因此该工具也可以作为重复代码生成器来使用 | 5. 不限制用户在 JSON 中编写的内容,因此该工具也可以作为重复代码生成器来使用 | ||||
6. 支持查看 SQL 语句的调用树,便于分析引用关系 | |||||
6. 支持查看 SQL 语句的调用树和替换详情,便于分析引用关系 | |||||
## 文档 | ## 文档 | ||||
@@ -1,7 +1,7 @@ | |||||
<script setup lang="ts"> | <script setup lang="ts"> | ||||
import { doGenerateSQL } from "./generator"; | import { doGenerateSQL } from "./generator"; | ||||
import { importExample } from "./examples"; | import { importExample } from "./examples"; | ||||
import { onMounted, ref, toRaw, watch } from "vue"; | |||||
import { onMounted, ref, toRaw } from "vue"; | |||||
import * as monaco from "monaco-editor"; | import * as monaco from "monaco-editor"; | ||||
import { format } from "sql-formatter"; | import { format } from "sql-formatter"; | ||||
import { GithubOutlined } from "@ant-design/icons-vue"; | import { GithubOutlined } from "@ant-design/icons-vue"; | ||||
@@ -56,6 +56,7 @@ const getSQL = () => { | |||||
toRaw(outputEditor.value).setValue(result); | toRaw(outputEditor.value).setValue(result); | ||||
// 获取调用树 | // 获取调用树 | ||||
invokeTree.value = [generateResult.invokeTree]; | invokeTree.value = [generateResult.invokeTree]; | ||||
console.log(invokeTree.value); | |||||
}; | }; | ||||
const showInvokeTree = () => { | const showInvokeTree = () => { | ||||
@@ -120,7 +121,7 @@ onMounted(() => { | |||||
<a-button size="large" type="primary" ghost @click="showInvokeTree"> | <a-button size="large" type="primary" ghost @click="showInvokeTree"> | ||||
查看调用树 | 查看调用树 | ||||
</a-button> | </a-button> | ||||
<a-button size="large" type="default" @click="importExample"> | |||||
<a-button size="large" type="default" @click="importExample('init')"> | |||||
导入例子 | 导入例子 | ||||
</a-button> | </a-button> | ||||
</a-space> | </a-space> | ||||
@@ -13,13 +13,37 @@ | |||||
:tree-data="tree" | :tree-data="tree" | ||||
@expand="onExpand" | @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> | </template> | ||||
</a-tree> | </a-tree> | ||||
</div> | </div> | ||||
@@ -19,35 +19,55 @@ export function doGenerateSQL(json: InputJSON) { | |||||
const rootInvokeTreeNode = { ...initTreeNode }; | const rootInvokeTreeNode = { ...initTreeNode }; | ||||
const context = json; | const context = json; | ||||
const resultSQL = generateSQL( | const resultSQL = generateSQL( | ||||
context.main, | |||||
"main", | |||||
context, | context, | ||||
context.main?.params, | context.main?.params, | ||||
rootInvokeTreeNode | rootInvokeTreeNode | ||||
); | ); | ||||
return { | return { | ||||
resultSQL, | resultSQL, | ||||
invokeTree: rootInvokeTreeNode, | |||||
invokeTree: rootInvokeTreeNode.children[0], // 取第一个作为根节点 | |||||
}; | }; | ||||
} | } | ||||
/** | /** | ||||
* 递归生成 SQL | * 递归生成 SQL | ||||
* @param currentNode | |||||
* @param key | |||||
* @param context | * @param context | ||||
* @param params | * @param params | ||||
* @param invokeTreeNode | * @param invokeTreeNode | ||||
*/ | */ | ||||
function generateSQL( | function generateSQL( | ||||
currentNode: InputJSONValue, | |||||
key: string, | |||||
context: InputJSON, | context: InputJSON, | ||||
params?: Record<string, string>, | params?: Record<string, string>, | ||||
invokeTreeNode?: InvokeTreeNode | invokeTreeNode?: InvokeTreeNode | ||||
): string { | ): string { | ||||
const currentNode = context[key]; | |||||
if (!currentNode) { | if (!currentNode) { | ||||
return ""; | 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(); | const key = keyValueArray[0].trim(); | ||||
params[key] = keyValueArray[1].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); | result = result.replace(regExpMatchArray[0], replacement); | ||||
regExpMatchArray = matchSubQuery(result); | regExpMatchArray = matchSubQuery(result); | ||||
} | } | ||||
@@ -16,6 +16,7 @@ interface InvokeTreeNode { | |||||
sql: string; | sql: string; | ||||
key?: string; | key?: string; | ||||
params?: Record<string, string>; | params?: Record<string, string>; | ||||
resultSQL?: string; | |||||
children?: InvokeTreeNode[]; | children?: InvokeTreeNode[]; | ||||
} | } | ||||