001/** 002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (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 io.jboot.db.model; 017 018import com.jfinal.kit.LogKit; 019import io.jboot.utils.ArrayUtil; 020import io.jboot.utils.StrUtil; 021 022import java.util.List; 023import java.util.regex.Pattern; 024 025/** 026 * @author Michael Yang 杨福海 (fuhai999@gmail.com) 027 * @version V1.0 028 */ 029public class SqlBuilder { 030 031 private static final String OR = " OR "; 032 private static final String AND = " AND "; 033 034 public static void buildMysqlWhereSql(StringBuilder sqlBuilder, List<Column> columns) { 035 buildWhereSql(sqlBuilder, columns, '`'); 036 } 037 038 public static String forDeleteByColumns(String alias, List<Join> joins, String table, List<Column> columns, char separator) { 039 StringBuilder sqlBuilder = new StringBuilder(45); 040 sqlBuilder.append("DELETE FROM "); 041 042 appendTextWithSeparator(sqlBuilder, table, separator); 043 044 buildAlias(sqlBuilder, alias); 045 buildJoinSql(sqlBuilder, joins, separator); 046 buildWhereSql(sqlBuilder, columns, separator); 047 048 return sqlBuilder.toString(); 049 } 050 051 052 public static void buildWhereSql(StringBuilder sqlBuilder, List<Column> columns, char separator) { 053 buildWhereSql(sqlBuilder, columns, separator, true); 054 } 055 056 057 public static void buildWhereSql(StringBuilder sqlBuilder, List<Column> columns, char separator, boolean appendWhereKeyword) { 058 if (ArrayUtil.isNullOrEmpty(columns)) { 059 return; 060 } 061 062 StringBuilder whereSqlBuilder = new StringBuilder(); 063 buildByColumns(whereSqlBuilder, columns, separator); 064 065 if (whereSqlBuilder.length() > 0) { 066 if (appendWhereKeyword && !isAllGroupByColumns(columns)) { 067 sqlBuilder.append(" WHERE "); 068 } 069 sqlBuilder.append(whereSqlBuilder); 070 } 071 } 072 073 //fixed: https://gitee.com/JbootProjects/jboot/issues/I3TP7J 074 private static boolean isAllGroupByColumns(List<Column> columns) { 075 for (Column column : columns) { 076 if (!(column instanceof GroupBy)) { 077 return false; 078 } 079 } 080 return true; 081 } 082 083 084 private static void buildByColumns(StringBuilder sqlBuilder, List<Column> columns, char separator) { 085 for (int i = 0; i < columns.size(); i++) { 086 087 Column before = i > 0 ? columns.get(i - 1) : null; 088 Column current = columns.get(i); 089 090 if (current instanceof Or) { 091 continue; 092 } 093 // sqlPart 094 else if (current instanceof SqlPart) { 095 appendSqlPartLogic(sqlBuilder, before, (SqlPart) current, separator); 096 } 097 // group 098 else if (current instanceof Group) { 099 appendGroupLogic(sqlBuilder, before, (Group) current, separator); 100 } 101 // in logic 102 else if (Column.LOGIC_IN.equals(current.getLogic()) || Column.LOGIC_NOT_IN.equals(current.getLogic())) { 103 appendLinkString(sqlBuilder, before); 104 appendInLogic(sqlBuilder, current, separator); 105 } 106 // between logic 107 else if (Column.LOGIC_BETWEEN.equals(current.getLogic()) || Column.LOGIC_NOT_BETWEEN.equals(current.getLogic())) { 108 appendLinkString(sqlBuilder, before); 109 appendBetweenLogic(sqlBuilder, current, separator); 110 } 111 // others 112 else { 113 appendLinkString(sqlBuilder, before); 114 appendColumnName(sqlBuilder, current, separator); 115 116 if (current.hasPara()) { 117 sqlBuilder.append('?'); 118 } 119 } 120 } 121 } 122 123 124 private static void appendSqlPartLogic(StringBuilder sqlBuilder, Column before, SqlPart sqlPart, char separator) { 125 if (!sqlPart.isWithoutLink()) { 126 appendLinkString(sqlBuilder, before); 127 } 128 sqlPart.build(separator); 129 sqlBuilder.append(' ').append(sqlPart.getSql()).append(' '); 130 } 131 132 133 private static void appendColumnName(StringBuilder sqlBuilder, Column column, char separator) { 134 appendTextWithSeparator(sqlBuilder, column.getName(), separator); 135 sqlBuilder.append(' ') 136 .append(column.getLogic()) 137 .append(' '); 138 } 139 140 141 private static void appendLinkString(StringBuilder sqlBuilder, Column before) { 142 if (sqlBuilder.length() == 0 || before == null) { 143 return; 144 } else { 145 sqlBuilder.append(before instanceof Or ? OR : AND); 146 } 147 } 148 149 150 public static void appendGroupLogic(StringBuilder sqlBuilder, Column before, Group group, char separator) { 151 List<Column> columns = group.getColumns().getList(); 152 if (ArrayUtil.isNullOrEmpty(columns)) { 153 return; 154 } 155 156 StringBuilder groupSqlBuilder = new StringBuilder(); 157 buildByColumns(groupSqlBuilder, columns, separator); 158 159 String groupSql = groupSqlBuilder.toString(); 160 if (StrUtil.isNotBlank(groupSql)) { 161 appendLinkString(sqlBuilder, before); 162 sqlBuilder.append('('); 163 sqlBuilder.append(groupSql); 164 sqlBuilder.append(')'); 165 } 166 } 167 168 169 public static void appendInLogic(StringBuilder sqlBuilder, Column column, char separator) { 170 171 appendColumnName(sqlBuilder, column, separator); 172 173 sqlBuilder.append('('); 174 175 Object[] values = (Object[]) column.getValue(); 176 177 //in 里的参数数量 178 int paraCount = 0; 179 for (Object v : values) { 180 if (v.getClass() == int[].class) { 181 paraCount += ((int[]) v).length; 182 } else if (v.getClass() == long[].class) { 183 paraCount += ((long[]) v).length; 184 } else if (v.getClass() == short[].class) { 185 paraCount += ((short[]) v).length; 186 } else { 187 paraCount++; 188 } 189 } 190 191 for (int i = 0; i < paraCount; i++) { 192 sqlBuilder.append('?'); 193 if (i != paraCount - 1) { 194 sqlBuilder.append(','); 195 } 196 } 197 sqlBuilder.append(')'); 198 } 199 200 201 public static void appendBetweenLogic(StringBuilder sqlBuilder, Column column, char separator) { 202 appendTextWithSeparator(sqlBuilder, column.getName(), separator); 203 sqlBuilder.append(' ').append(column.getLogic()); 204 sqlBuilder.append(" ? AND ?"); 205 } 206 207 208 public static void appendTextWithSeparator(StringBuilder sqlBuilder, String text, char separator) { 209 if (text.indexOf(".") > 0) { 210 sqlBuilder.append(text); 211 } else { 212 sqlBuilder.append(separator).append(text).append(separator); 213 } 214 } 215 216 217 public static StringBuilder forFindByColumns(String alias, List<Join> joins, String table, String loadColumns, List<Column> columns, String orderBy, char separator) { 218 StringBuilder sqlBuilder = new StringBuilder("SELECT "); 219 sqlBuilder.append(loadColumns) 220 .append(" FROM "); 221 appendTextWithSeparator(sqlBuilder, table, separator); 222 223 buildAlias(sqlBuilder, alias); 224 buildJoinSql(sqlBuilder, joins, separator); 225 buildWhereSql(sqlBuilder, columns, separator); 226 227 orderBy = escapeOrderBySql(orderBy); 228 if (StrUtil.isNotBlank(orderBy)) { 229 sqlBuilder.append(" ORDER BY ").append(orderBy); 230 } 231 232 return sqlBuilder; 233 } 234 235 //来源于 @link Dialect.java 236 private static final Pattern ORDER_BY_PATTERN = Pattern.compile( 237 "order\\s+by\\s+[^,\\s]+(\\s+asc|\\s+desc)?(\\s*,\\s*[^,\\s]+(\\s+asc|\\s+desc)?)*", 238 Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); 239 240 private static String replaceOrderBy(String sql) { 241 return ORDER_BY_PATTERN.matcher(sql).replaceAll(""); 242 } 243 244 public static String forPaginateDistinctTotalRow(String select, String sqlExceptSelect, Object ext) { 245 if (ext instanceof JbootModel && CPI.hasAnyJoinEffective((JbootModel) ext)) { 246 String distinct = JbootModelExts.getDistinctColumn((JbootModel) ext); 247 if (StrUtil.isNotBlank(distinct)) { 248 return "SELECT count(DISTINCT " + distinct + ") " + replaceOrderBy(sqlExceptSelect); 249 } 250 } 251 return null; 252 } 253 254 255 public static String forPaginateFrom(String alias, List<Join> joins, String table, List<Column> columns, String orderBy, char separator) { 256 StringBuilder sqlBuilder = new StringBuilder(" FROM "); 257 appendTextWithSeparator(sqlBuilder, table, separator); 258 259 buildAlias(sqlBuilder, alias); 260 buildJoinSql(sqlBuilder, joins, separator); 261 buildWhereSql(sqlBuilder, columns, separator); 262 263 orderBy = escapeOrderBySql(orderBy); 264 265 if (StrUtil.isNotBlank(orderBy)) { 266 sqlBuilder.append(" ORDER BY ").append(orderBy); 267 } 268 269 return sqlBuilder.toString(); 270 } 271 272 273 public static void buildJoinSql(StringBuilder sqlBuilder, List<Join> joins, char separator) { 274 if (joins == null || joins.isEmpty()) { 275 return; 276 } 277 for (Join join : joins) { 278 if (!join.isEffective()) { 279 continue; 280 } 281 282 sqlBuilder.append(join.getType()); 283 appendTextWithSeparator(sqlBuilder, join.getTable(), separator); 284 285 buildAlias(sqlBuilder, join.getAs()); 286 287 sqlBuilder.append(" ON ") 288 .append(join.getOn()); 289 } 290 } 291 292 293 public static void buildAlias(StringBuilder sqlBuilder, String alias) { 294 if (StrUtil.isNotBlank(alias)) { 295 sqlBuilder.append(" AS ").append(alias); 296 } 297 } 298 299 300 public static String forFindCountByColumns(String alias, List<Join> joins, String table, String loadColumns, List<Column> columns, char separator) { 301 StringBuilder sqlBuilder = new StringBuilder("SELECT count(" + loadColumns + ") FROM "); 302 appendTextWithSeparator(sqlBuilder, table, separator); 303 304 buildAlias(sqlBuilder, alias); 305 buildJoinSql(sqlBuilder, joins, separator); 306 buildWhereSql(sqlBuilder, columns, separator); 307 308 return sqlBuilder.toString(); 309 } 310 311 312 public static String escapeOrderBySql(String orignalOrderBy) { 313 if (StrUtil.isNotBlank(orignalOrderBy) && !isValidOrderBySql(orignalOrderBy)) { 314 LogKit.warn("Sql Warn: order_by value has inject chars and be filtered, order_by value: " + orignalOrderBy); 315 return ""; 316 } 317 return orignalOrderBy; 318 } 319 320 321 /** 322 * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序) 323 */ 324 private static String SQL_ORDER_BY_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+"; 325 326 private static boolean isValidOrderBySql(String value) { 327 return value.matches(SQL_ORDER_BY_PATTERN); 328 } 329}