|
12 | 12 |
|
13 | 13 | import java.util.Arrays; |
14 | 14 | import java.util.Date; |
| 15 | +import java.util.function.BiConsumer; |
| 16 | +import java.util.function.Function; |
15 | 17 |
|
16 | 18 | public class SqlUtils { |
17 | 19 |
|
@@ -142,27 +144,210 @@ public static String toNativeSql(String sql, Object... parameters) { |
142 | 144 | } else { |
143 | 145 | stringParameter[i] = "'" + parameter + "'"; |
144 | 146 | } |
145 | | - len += stringParameter.length; |
| 147 | + len += stringParameter[i].length(); |
146 | 148 | } |
147 | 149 | return sqlParameterToString(sql, len, stringParameter); |
148 | 150 | } |
149 | 151 |
|
150 | 152 | 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; |
152 | 180 |
|
153 | | - int parameterIndex = 0; |
154 | | - for (int i = 0, sqlLen = sql.length(); i < sqlLen; i++) { |
| 181 | + for (int i = 0; i < sql.length(); i++) { |
155 | 182 | 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; |
161 | 189 | } |
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) { |
163 | 248 | builder.append(c); |
| 249 | + continue; |
164 | 250 | } |
| 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); |
165 | 281 | } |
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; |
167 | 352 | } |
168 | 353 | } |
0 commit comments