Skip to content

Commit 3b472f4

Browse files
committed
refactor: 优化sql参数替换逻辑
1 parent 68472dc commit 3b472f4

File tree

3 files changed

+741
-29
lines changed

3 files changed

+741
-29
lines changed

hsweb-easy-orm-rdb/src/main/java/org/hswebframework/ezorm/rdb/executor/reactive/r2dbc/R2dbcSqlRequest.java

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,28 +32,30 @@ public String toNativeSql() {
3232
return SqlUtils.toNativeSql(nativeSql.getSql(), parameters);
3333
}
3434

35-
private final static ThreadLocal<StringBuilder> builderRef = ThreadLocal.withInitial(StringBuilder::new);
35+
public static SqlRequest of(int firstIndex, String symbol, SqlRequest request) {
36+
Object[] parameter = request.getParameters();
37+
// 没有预编译参数,直接返回.
38+
if (parameter == null || parameter.length == 0) {
39+
return request;
40+
}
3641

37-
public static R2dbcSqlRequest of(int firstIndex, String symbol, SqlRequest request) {
3842
R2dbcSqlRequest sqlRequest = new R2dbcSqlRequest();
3943
sqlRequest.nativeSql = request;
4044
String sql = request.getSql();
41-
42-
StringBuilder builder = builderRef.get();
43-
builder.setLength(0);
44-
45-
int parameterIndex = firstIndex;
46-
for (int i = 0, sqlLen = sql.length(); i < sqlLen; i++) {
47-
char c = sql.charAt(i);
48-
//替换为 ?0,?1
49-
if (c == '?') {
50-
builder.append(symbol).append(parameterIndex++);
51-
} else {
52-
builder.append(c);
53-
}
54-
}
55-
sqlRequest.sql = builder.toString();
56-
sqlRequest.parameters = request.getParameters();
45+
int sl = symbol.length() + 1;
46+
47+
sqlRequest.sql = SqlUtils
48+
.replaceSqlParameter(
49+
sql,
50+
new StringBuilder(sql.length() + (parameter.length * sl)),
51+
(parameterIndex, builder) -> {
52+
builder
53+
.append(symbol)
54+
.append((firstIndex + parameterIndex));
55+
})
56+
.toString();
57+
58+
sqlRequest.parameters = parameter;
5759

5860
return sqlRequest;
5961
}

hsweb-easy-orm-rdb/src/main/java/org/hswebframework/ezorm/rdb/utils/SqlUtils.java

Lines changed: 196 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
import java.util.Arrays;
1414
import java.util.Date;
15+
import java.util.function.BiConsumer;
16+
import java.util.function.Function;
1517

1618
public class SqlUtils {
1719

@@ -142,27 +144,210 @@ public static String toNativeSql(String sql, Object... parameters) {
142144
} else {
143145
stringParameter[i] = "'" + parameter + "'";
144146
}
145-
len += stringParameter.length;
147+
len += stringParameter[i].length();
146148
}
147149
return sqlParameterToString(sql, len, stringParameter);
148150
}
149151

150152
private static @NonNull String sqlParameterToString(String sql, int len, String[] stringParameter) {
151-
StringBuilder builder = new StringBuilder(sql.length() + len + 16);
153+
return replaceSqlParameter(sql, len, (parameterIndex) -> {
154+
if (stringParameter.length > parameterIndex) {
155+
return stringParameter[parameterIndex];
156+
} else {
157+
return "unbound";
158+
}
159+
});
160+
}
161+
162+
public static String replaceSqlParameter(String sql, int estimatedExtraLen, Function<Integer, String> replacer) {
163+
return replaceSqlParameter(
164+
sql,
165+
new StringBuilder(sql.length() + estimatedExtraLen),
166+
(integer, builder) -> {
167+
builder.append(replacer.apply(integer));
168+
})
169+
.toString();
170+
}
171+
172+
public static StringBuilder replaceSqlParameter(String sql,
173+
StringBuilder builder,
174+
BiConsumer<Integer, StringBuilder> replacer) {
175+
int index = 0;
176+
boolean inSingleQuote = false;
177+
boolean inDoubleQuote = false;
178+
boolean inLineComment = false;
179+
boolean inBlockComment = false;
152180

153-
int parameterIndex = 0;
154-
for (int i = 0, sqlLen = sql.length(); i < sqlLen; i++) {
181+
for (int i = 0; i < sql.length(); i++) {
155182
char c = sql.charAt(i);
156-
if (c == '?') {
157-
if (stringParameter.length > parameterIndex) {
158-
builder.append(stringParameter[parameterIndex++]);
159-
} else {
160-
builder.append("unbound");
183+
184+
// --- 处理注释 -----------------------------------------------------
185+
if (inLineComment) {
186+
builder.append(c);
187+
if (c == '\n') {
188+
inLineComment = false;
161189
}
162-
} else {
190+
continue;
191+
}
192+
193+
if (inBlockComment) {
194+
builder.append(c);
195+
if (c == '*' && i + 1 < sql.length() && sql.charAt(i + 1) == '/') {
196+
builder.append('/');
197+
i++;
198+
inBlockComment = false;
199+
}
200+
continue;
201+
}
202+
203+
// 进入注释(仅当不在字符串中)
204+
if (!inSingleQuote && !inDoubleQuote) {
205+
if (c == '-' && i + 1 < sql.length() && sql.charAt(i + 1) == '-') {
206+
builder.append("--");
207+
i++;
208+
inLineComment = true;
209+
continue;
210+
}
211+
if (c == '/' && i + 1 < sql.length() && sql.charAt(i + 1) == '*') {
212+
builder.append("/*");
213+
i++;
214+
inBlockComment = true;
215+
continue;
216+
}
217+
}
218+
219+
// --- 处理字符串和双引号 -------------------------------------------
220+
// 处理单引号字符串(支持转义:'' 表示一个单引号字符)
221+
if (!inDoubleQuote && c == '\'') {
222+
if (inSingleQuote && i + 1 < sql.length() && sql.charAt(i + 1) == '\'') {
223+
// 在单引号字符串内遇到 '',这是转义的单引号,不是字符串结束
224+
builder.append("''");
225+
i++; // 跳过下一个单引号
226+
continue;
227+
}
228+
inSingleQuote = !inSingleQuote;
229+
builder.append(c);
230+
continue;
231+
}
232+
233+
// 处理双引号字符串(支持转义:"" 表示一个双引号字符)
234+
if (!inSingleQuote && c == '\"') {
235+
if (inDoubleQuote && i + 1 < sql.length() && sql.charAt(i + 1) == '\"') {
236+
// 在双引号字符串内遇到 "",这是转义的双引号,不是字符串结束
237+
builder.append("\"\"");
238+
i++; // 跳过下一个双引号
239+
continue;
240+
}
241+
inDoubleQuote = !inDoubleQuote;
242+
builder.append(c);
243+
continue;
244+
}
245+
246+
// 在字符串/标识符内不替换 '?'
247+
if (inSingleQuote || inDoubleQuote) {
163248
builder.append(c);
249+
continue;
164250
}
251+
252+
// --- 跳过 PostgreSQL 操作符 ------------------------------------
253+
if (c == '?') {
254+
// 检查多字符操作符:?| ?& ?!
255+
if (i + 1 < sql.length()) {
256+
char next = sql.charAt(i + 1);
257+
if (next == '|' || next == '&' || next == '!' || next == '?') {
258+
builder.append('?').append(next);
259+
i++;
260+
continue;
261+
}
262+
}
263+
264+
// 检查单独的 ? 操作符(PostgreSQL JSONB/数组操作符)
265+
// 格式:column ? 'key' 或 column ? array[...]
266+
// 判断条件:前面是标识符字符,后面是空格+单引号或 array
267+
if (isPostgresOperator(sql, i)) {
268+
builder.append('?');
269+
continue;
270+
}
271+
}
272+
273+
// --- 在这里替换 '?' 参数 -------------------------------------------
274+
if (c == '?') {
275+
replacer.accept(index++, builder);
276+
continue;
277+
}
278+
279+
// 默认追加
280+
builder.append(c);
165281
}
166-
return builder.toString();
282+
283+
return builder;
284+
}
285+
286+
/**
287+
* 判断当前位置的 '?' 是否是 PostgreSQL 操作符(如 JSONB 的 ? 操作符)
288+
* <p>
289+
* PostgreSQL 操作符格式:
290+
* - jsonb_column ? 'key'
291+
* - jsonb_column ? array['key1', 'key2']
292+
* <p>
293+
* 判断逻辑:
294+
* 1. 前面(跳过空格)必须是标识符字符(字母、数字、下划线、右括号、右方括号)
295+
* 2. 后面(跳过空格)必须是单引号字符串或 array[
296+
*
297+
* @param sql SQL 语句
298+
* @param index '?' 的位置
299+
* @return 如果是操作符返回 true,否则返回 false
300+
*/
301+
private static boolean isPostgresOperator(String sql, int index) {
302+
// 检查前面是否有标识符字符(跳过空格)
303+
int prevIndex = index - 1;
304+
while (prevIndex >= 0 && Character.isWhitespace(sql.charAt(prevIndex))) {
305+
prevIndex--;
306+
}
307+
308+
if (prevIndex < 0) {
309+
// 如果 ? 在开头或前面只有空格,不可能是操作符
310+
return false;
311+
}
312+
313+
char prev = sql.charAt(prevIndex);
314+
// 标识符字符:字母、数字、下划线、右括号、右方括号
315+
// 如果不是这些字符,则不是操作符(可能是 =, >, < 等操作符后的参数占位符)
316+
if (!(Character.isLetterOrDigit(prev)
317+
|| prev == '_'
318+
|| prev == ')'
319+
|| prev == ']')) {
320+
return false;
321+
}
322+
323+
// 检查后面是否是操作符格式
324+
if (index + 1 >= sql.length()) {
325+
return false;
326+
}
327+
328+
// 跳过空格
329+
int nextIndex = index + 1;
330+
while (nextIndex < sql.length() && Character.isWhitespace(sql.charAt(nextIndex))) {
331+
nextIndex++;
332+
}
333+
334+
if (nextIndex >= sql.length()) {
335+
return false;
336+
}
337+
338+
char next = sql.charAt(nextIndex);
339+
340+
// 检查是否是单引号字符串('key')
341+
if (next == '\'') {
342+
return true;
343+
}
344+
345+
// 检查是否是 array[...]
346+
if (nextIndex + 5 <= sql.length()) {
347+
String nextStr = sql.substring(nextIndex, Math.min(nextIndex + 5, sql.length()));
348+
return nextStr.toLowerCase().startsWith("array");
349+
}
350+
351+
return false;
167352
}
168353
}

0 commit comments

Comments
 (0)