我想在前端实现一个格式化的SQL函数。其中,SQL可能包含一些mybatis特有的语法,例如'#{var}'。 输入被 SQL 压缩为一行;输出是格式化的 SQL。 下图是输入输出的例子: 输入示例:
select distinct a.name_order as "nameorder", #{authId} as "authid", trim(authType) as "authType", concat('org-', a.org_icon) as "iconCls" from table_user a left join (select b.obj_type, b.obj_id from table_object a left join table_auth_data b on a.auth_id = b.id where a.auth = aa) b on (b.obj_type = trim(a.auth_type) and b.object_id = aa) where a.parent is null and trim(c.org_id) = #{org_Id} order by a.order asc
但是,当我在SQL语句的where部分添加嵌套的concat()函数时,解析就会失败,如:
错误1: 输入
select distinct a.name_order as "nameorder", #{authId} as "authid", trim(authType) as "authType", concat('org-', a.org_icon) as "iconCls" from table_user a left join (select b.obj_type, b.obj_id from table_object a left join table_auth_data b on a.auth_id = b.id where a.auth = concat('a', concat('b', 'c'))) b on (b.obj_type = trim(a.auth_type) and b.object_id = aa) where a.parent is null and trim(c.org_id) = #{org_Id} order by a.order asc
错误信息很长:
nearley.js:343 Uncaught Error: Parse error at token: ) at line 1 column 309
Unexpected CLOSE_PAREN token: {"type":"CLOSE_PAREN","raw":")","text":")","start":308}. Instead, I was expecting to see one of the following:
A PROPERTY_ACCESS_OPERATOR token based on:
property_access → atomic_expression _ ● %PROPERTY_ACCESS_OPERATOR _ property_access$subexpression$1
atomic_expression$subexpression$1 → ● property_access
atomic_expression → ● atomic_expression$subexpression$1
asteriskless_andless_expression$subexpression$1 → ● atomic_expression
asteriskless_andless_expression → ● asteriskless_andless_expression$subexpression$1
asteriskless_free_form_sql$subexpression$1 → ● asteriskless_andless_expression
asteriskless_free_form_sql → ● asteriskless_free_form_sql$subexpression$1
free_form_sql$subexpression$1 → ● asteriskless_free_form_sql
free_form_sql → ● free_form_sql$subexpression$1
other_clause$ebnf$1 → other_clause$ebnf$1 ● free_form_sql
other_clause → %RESERVED_CLAUSE ● other_clause$ebnf$1
clause$subexpression$1 → ● other_clause
clause → ● clause$subexpression$1
expressions_or_clauses$ebnf$2 → expressions_or_clauses$ebnf$2 ● clause
expressions_or_clauses → expressions_or_clauses$ebnf$1 ● expressions_or_clauses$ebnf$2
statement → ● expressions_or_clauses statement$subexpression$1
main$ebnf$1 → main$ebnf$1 ● statement
main → ● main$ebnf$1
A LINE_COMMENT token based on:
comment → ● %LINE_COMMENT
_$ebnf$1 → _$ebnf$1 ● comment
_ → ● _$ebnf$1
property_access → atomic_expression ● _ %PROPERTY_ACCESS_OPERATOR _ property_access$subexpression$1
......Due to character limit, some error messages are omitted here.
A RESERVED_JOIN token based on:
keyword$subexpression$1 → ● %RESERVED_JOIN
keyword → ● keyword$subexpression$1
atomic_expression$subexpression$1 → ● keyword
atomic_expression → ● atomic_expression$subexpression$1
asteriskless_andless_expression$subexpression$1 → ● atomic_expression
asteriskless_andless_expression → ● asteriskless_andless_expression$subexpression$1
asteriskless_free_form_sql$subexpression$1 → ● asteriskless_andless_expression
asteriskless_free_form_sql → ● asteriskless_free_form_sql$subexpression$1
free_form_sql$subexpression$1 → ● asteriskless_free_form_sql
free_form_sql → ● free_form_sql$subexpression$1
other_clause$ebnf$1 → other_clause$ebnf$1 ● free_form_sql
other_clause → %RESERVED_CLAUSE ● other_clause$ebnf$1
clause$subexpression$1 → ● other_clause
clause → ● clause$subexpression$1
expressions_or_clauses$ebnf$2 → expressions_or_clauses$ebnf$2 ● clause
expressions_or_clauses → expressions_or_clauses$ebnf$1 ● expressions_or_clauses$ebnf$2
statement → ● expressions_or_clauses statement$subexpression$1
main$ebnf$1 → main$ebnf$1 ● statement
main → ● main$ebnf$1
at T.O.feed (nearley.js:343:27)
at Object.parse (createParser.ts:33:34)
at CA.parse (Formatter.ts:37:49)
at CA.format (Formatter.ts:31:22)
at tA (sqlFormatter.ts:96:57)
at Module.sA (sqlFormatter.ts:69:10)
at formatSQL (mybatis-xml - 副本.html:68:33)
at HTMLButtonElement.<anonymous> (mybatis-xml - 副本.html:62:30)
============================================== 错误2: 输入
select distinct a.name_order as "nameorder", #{authId} as "authid", trim(authType) as "authType", concat('org-', a.org_icon) as "iconCls" from table_user a left join (select b.obj_type, b.obj_id from table_object a left join table_auth_data b on a.auth_id = b.id where a.auth = concat(trim(a), concat('b', 'c'))) b on (b.obj_type = trim(a.auth_type) and b.object_id = aa) where a.parent is null and trim(c.org_id) = #{org_Id} order by a.order asc
错误信息:
nearley.js:343 Uncaught Error: Parse error at token: ) at line 1 column 313
Unexpected CLOSE_PAREN token: {"type":"CLOSE_PAREN","raw":")","text":")","start":312}. Instead, I was expecting to see one of the following:
A PROPERTY_ACCESS_OPERATOR token based on:
property_access → atomic_expression _ ● %PROPERTY_ACCESS_OPERATOR _ property_access$subexpression$1
atomic_expression$subexpression$1 → ● property_access
atomic_expression → ● atomic_expression$subexpression$1
asteriskless_andless_expression$subexpression$1 → ● atomic_expression
asteriskless_andless_expression → ● asteriskless_andless_expression$subexpression$1
asteriskless_free_form_sql$subexpression$1 → ● asteriskless_andless_expression
asteriskless_free_form_sql → ● asteriskless_free_form_sql$subexpression$1
free_form_sql$subexpression$1 → ● asteriskless_free_form_sql
free_form_sql → ● free_form_sql$subexpression$1
other_clause$ebnf$1 → other_clause$ebnf$1 ● free_form_sql
other_clause → %RESERVED_CLAUSE ● other_clause$ebnf$1
clause$subexpression$1 → ● other_clause
clause → ● clause$subexpression$1
expressions_or_clauses$ebnf$2 → expressions_or_clauses$ebnf$2 ● clause
expressions_or_clauses → expressions_or_clauses$ebnf$1 ● expressions_or_clauses$ebnf$2
statement → ● expressions_or_clauses statement$subexpression$1
main$ebnf$1 → main$ebnf$1 ● statement
main → ● main$ebnf$1
A LINE_COMMENT token based on:
comment → ● %LINE_COMMENT
_$ebnf$1 → _$ebnf$1 ● comment
_ → ● _$ebnf$1
property_access → atomic_expression ● _ %PROPERTY_ACCESS_OPERATOR _ property_access$subexpression$1
atomic_expression$subexpression$1 → ● property_access
atomic_expression → ● atomic_expression$subexpression$1
asteriskless_andless_expression$subexpression$1 → ● atomic_expression
asteriskless_andless_expression → ● asteriskless_andless_expression$subexpression$1
asteriskless_free_form_sql$subexpression$1 → ● asteriskless_andless_expression
asteriskless_free_form_sql → ● asteriskless_free_form_sql$subexpression$1
free_form_sql$subexpression$1 → ● asteriskless_free_form_sql
free_form_sql → ● free_form_sql$subexpression$1
other_clause$ebnf$1 → other_clause$ebnf$1 ● free_form_sql
other_clause → %RESERVED_CLAUSE ● other_clause$ebnf$1
clause$subexpression$1 → ● other_clause
clause → ● clause$subexpression$1
expressions_or_clauses$ebnf$2 → expressions_or_clauses$ebnf$2 ● clause
expressions_or_clauses → expressions_or_clauses$ebnf$1 ● expressions_or_clauses$ebnf$2
statement → ● expressions_or_clauses statement$subexpression$1
main$ebnf$1 → main$ebnf$1 ● statement
main → ● main$ebnf$1
......Due to character limit, some error messages are omitted here.
at T.O.feed (nearley.js:343:27)
at Object.parse (createParser.ts:33:34)
at CA.parse (Formatter.ts:37:49)
at CA.format (Formatter.ts:31:22)
at tA (sqlFormatter.ts:96:57)
at Module.sA (sqlFormatter.ts:69:10)
at formatSQL (mybatis-xml - 副本.html:68:33)
at HTMLButtonElement.<anonymous> (mybatis-xml - 副本.html:62:30)
T.O.feed @ nearley.js:343
parse @ createParser.ts:33
parse @ Formatter.ts:37
format @ Formatter.ts:31
tA @ sqlFormatter.ts:96
sA @ sqlFormatter.ts:69
formatSQL @ mybatis-xml - 副本.html:68
====================================================== ==========
完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SQL parse</title>
<style>
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
margin-top: 10px;
}
.xml-container {
padding: 10px;
width: 75%;
overflow: auto;
}
.xml-container pre {
font-size: 18px;
min-height: 40px;
border: 1px solid #ccc;
padding: 10px;
overflow-y: auto;
}
</style>
</head>
<body>
<div class="container">
<div class="xml-container">
<pre id="originalXml" contenteditable></pre>
</div>
<button id="parseButton">parse SQL</button>
<div class="xml-container">
<h3>parsed data</h3>
<pre id="parsedData" style="font-size: 18px;"></pre>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/sql-formatter/dist/sql-formatter.min.js"></script>
<script>
var parseButton = document.getElementById("parseButton");
var originalXmlPre = document.getElementById("originalXml");
var loopParsedDataPre = document.getElementById("parsedData");
parseButton.addEventListener("click", function () {
var xmlString = originalXmlPre.textContent;
var parsedData = formatSQL(xmlString);
loopParsedDataPre.textContent = parsedData;
});
function formatSQL(sql) {
return sqlFormatter.format(sql, {
language: 'sql',
paramTypes: {
custom: [
{ regex: String.raw`#\{(.+?)\}`},
{ regex: String.raw`trim\(.+?\)`, flags: 'i'},
{ regex: String.raw`concat\(.+?\)`, flags: 'i'},
{ regex: String.raw`concat(.+?)`, flags: 'i'},
{ regex: String.raw`concat.+?concat.+?\)\)`, flags: 'i'},
{ regex: String.raw`concat(.+?)concat(.+?)`, flags: 'i'},
],
},
});
}
</script>
</body>
</html>
我的尝试:
我尝试使用sqlFormatter的自定义正则函数来解决问题,但最多只能解决一个简单的concat()。对于嵌套的 concat() 和包含 trim() 的 concat() 来说,它完全失败。 正则定义代码:
function formatSQL(sql) {
return sqlFormatter.format(sql, {
language: 'sql',
paramTypes: {
custom: [
{ regex: String.raw`#\{(.+?)\}`},
{ regex: String.raw`trim\(.+?\)`, flags: 'i'},
{ regex: String.raw`concat\(.+?\)`, flags: 'i'},
{ regex: String.raw`concat(.+?)`, flags: 'i'},
{ regex: String.raw`concat.+?concat.+?\)\)`, flags: 'i'},
{ regex: String.raw`concat(.+?)concat(.+?)`, flags: 'i'},
],
},
});
}
在评论区的帮助下,我已经解决了问题。只需要保留第一条正则规则
regex: String.raw`#\{(.+?)\}`
,删除其余的正则规则即可。
此外,我将代码发布到了 GitHub 上。如果您需要在前端使用 mybatis XML 标签格式化 SQL 语句,那么这个存储库也许可以帮助您:https://github.com/hikari-He/format-SQL-with-Mybatis-label/ blob /main/readme-EN.md