001/* 002 * Copyright (c) 2022-2025, Mybatis-Flex (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package com.mybatisflex.core.util; 017 018import com.mybatisflex.core.audit.AuditMessage; 019import com.mybatisflex.core.mybatis.TypeHandlerObject; 020 021import java.lang.reflect.Array; 022import java.lang.reflect.Proxy; 023import java.sql.PreparedStatement; 024import java.sql.SQLException; 025import java.time.LocalDateTime; 026import java.time.format.DateTimeFormatter; 027import java.util.Date; 028import java.util.StringJoiner; 029import java.util.regex.Matcher; 030 031import static com.mybatisflex.core.constant.SqlConsts.*; 032 033public class SqlUtil { 034 035 private SqlUtil() { 036 } 037 038 public static void keepColumnSafely(String column) { 039 if (StringUtil.noText(column)) { 040 throw new IllegalArgumentException("Column must not be empty"); 041 } else { 042 column = column.trim(); 043 } 044 045 int strLen = column.length(); 046 for (int i = 0; i < strLen; ++i) { 047 char ch = column.charAt(i); 048 if (Character.isWhitespace(ch)) { 049 throw new IllegalArgumentException("Column must not has space char."); 050 } 051 if (isUnSafeChar(ch)) { 052 throw new IllegalArgumentException("Column has unsafe char: [" + ch + "]."); 053 } 054 } 055 } 056 057 058 /** 059 * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序) 060 */ 061 private static final String SQL_ORDER_BY_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+"; 062 063 public static void keepOrderBySqlSafely(String value) { 064 if (!value.matches(SQL_ORDER_BY_PATTERN)) { 065 throw new IllegalArgumentException("Order By sql not safe, order by string: " + value); 066 } 067 } 068 069 070 private static final char[] UN_SAFE_CHARS = "'`\"<>&+=#-;".toCharArray(); 071 072 private static boolean isUnSafeChar(char ch) { 073 for (char c : UN_SAFE_CHARS) { 074 if (c == ch) { 075 return true; 076 } 077 } 078 return false; 079 } 080 081 082 /** 083 * 根据数据库响应结果判断数据库操作是否成功。 084 * 085 * @param result 数据库操作返回影响条数 086 * @return {@code true} 操作成功,{@code false} 操作失败。 087 */ 088 public static boolean toBool(int result) { 089 return result > 0 || result == -2; 090 } 091 092 public static boolean toBool(long result) { 093 return result > 0; 094 } 095 096 097 /** 098 * 根据数据库响应结果判断数据库操作是否成功。 099 * 有 1 条数据成功便算成功 100 * 101 * @param results 操作数据的响应成功条数 102 * @return {@code true} 操作成功,{@code false} 操作失败。 103 */ 104 public static boolean toBool(int[] results) { 105 for (int result : results) { 106 if (toBool(result)) { 107 return true; 108 } 109 } 110 return false; 111 } 112 113 114 /** 115 * 替换 sql 中的问号 ? 116 * 117 * @param sql sql 内容 118 * @param params 参数 119 * @return 完整的 sql 120 */ 121 public static String replaceSqlParams(String sql, Object[] params) { 122 if (params == null || params.length == 0) { 123 return sql; 124 } 125 126 StringBuilder sqlBuilder = new StringBuilder(); 127 char quote = 0; 128 int index = 0; 129 for (int i = 0; i < sql.length(); ++i) { 130 char ch = sql.charAt(i); 131 if (ch == '\'') { 132 if (quote == 0) { 133 quote = ch; 134 } else if (quote == '\'') { 135 quote = 0; 136 } 137 } else if (ch == '"') { 138 if (quote == 0) { 139 quote = ch; 140 } else if (quote == '"') { 141 quote = 0; 142 } 143 } 144 if (quote == 0 && ch == '?' && index < params.length) { 145 sqlBuilder.append(getParamString(params, index++)); 146 } else { 147 sqlBuilder.append(ch); 148 } 149 } 150 151 return sqlBuilder.toString(); 152 } 153 154 155 private static String getParamString(Object[] params, int index) { 156 Object value = params[index]; 157 if (value == null) { 158 return "null"; 159 } 160 // number or bool 161 else if (value instanceof Number || value instanceof Boolean) { 162 return value.toString(); 163 } 164 // array 165 else if (ClassUtil.isArray(value.getClass())) { 166 StringJoiner joiner = new StringJoiner(",", "[", "]"); 167 for (int i = 0; i < Array.getLength(value); i++) { 168 joiner.add(String.valueOf(Array.get(value, i))); 169 } 170 return joiner.toString(); 171 } 172 // TypeHandlerObject 173 else if (value instanceof TypeHandlerObject) { 174 TypeHandlerObject handlerObject = (TypeHandlerObject) value; 175 String[] paramArray = new String[1]; 176 PreparedStatement preparedStatement = createPreparedStatement(paramArray); 177 try { 178 handlerObject.setParameter(preparedStatement, 0); 179 } catch (SQLException e) { 180 throw new RuntimeException(e); 181 } 182 return paramArray[0]; 183 } 184 185 // enum 186 else if (value.getClass().isEnum()) { 187 EnumWrapper<?> ew = EnumWrapper.of(value.getClass()); 188 Object enumValue = ew.getEnumValue(value); 189 return enumValue.toString(); 190 } 191 192 // other 193 else { 194 StringBuilder sb = new StringBuilder(); 195 sb.append("'"); 196 if (value instanceof Date) { 197 sb.append(DateUtil.toDateTimeString((Date) value)); 198 } else if (value instanceof LocalDateTime) { 199 sb.append(((LocalDateTime) value).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); 200 } else { 201 sb.append(value); 202 } 203 sb.append("'"); 204 return Matcher.quoteReplacement(sb.toString()); 205 } 206 } 207 208 209 public static String buildSqlParamPlaceholder(int count) { 210 StringBuilder sb = new StringBuilder(BRACKET_LEFT); 211 for (int i = 0; i < count; i++) { 212 sb.append(PLACEHOLDER); 213 if (i != count - 1) { 214 sb.append(DELIMITER); 215 } 216 } 217 sb.append(BRACKET_RIGHT); 218 return sb.toString(); 219 } 220 221 222 private static PreparedStatement createPreparedStatement(String[] params) { 223 return (PreparedStatement) Proxy.newProxyInstance( 224 AuditMessage.class.getClassLoader(), 225 new Class[]{PreparedStatement.class}, (proxy, method, args) -> { 226 if (args != null && (args.length == 2 || args.length == 3)) { 227 params[0] = args[1].toString(); 228 } 229 return null; 230 }); 231 } 232}