SqlIllegalInterceptor.java 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789
  1. package cn.com.ty.lift.common.sql;
  2. import com.baomidou.mybatisplus.core.parser.SqlParserHelper;
  3. import lombok.Data;
  4. import lombok.Getter;
  5. import lombok.Setter;
  6. import lombok.ToString;
  7. import lombok.experimental.Accessors;
  8. import lombok.extern.slf4j.Slf4j;
  9. import net.sf.jsqlparser.expression.Alias;
  10. import net.sf.jsqlparser.expression.BinaryExpression;
  11. import net.sf.jsqlparser.expression.Expression;
  12. import net.sf.jsqlparser.expression.Function;
  13. import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
  14. import net.sf.jsqlparser.expression.operators.relational.InExpression;
  15. import net.sf.jsqlparser.expression.operators.relational.ItemsList;
  16. import net.sf.jsqlparser.expression.operators.relational.NotEqualsTo;
  17. import net.sf.jsqlparser.parser.CCJSqlParserUtil;
  18. import net.sf.jsqlparser.schema.Column;
  19. import net.sf.jsqlparser.schema.Table;
  20. import net.sf.jsqlparser.statement.Statement;
  21. import net.sf.jsqlparser.statement.delete.Delete;
  22. import net.sf.jsqlparser.statement.select.*;
  23. import net.sf.jsqlparser.statement.update.Update;
  24. import org.apache.ibatis.executor.statement.StatementHandler;
  25. import org.apache.ibatis.mapping.MappedStatement;
  26. import org.apache.ibatis.mapping.SqlCommandType;
  27. import org.apache.ibatis.plugin.*;
  28. import org.apache.ibatis.reflection.MetaObject;
  29. import org.apache.ibatis.reflection.SystemMetaObject;
  30. import java.sql.Connection;
  31. import java.sql.DatabaseMetaData;
  32. import java.sql.ResultSet;
  33. import java.sql.SQLException;
  34. import java.util.*;
  35. import java.util.concurrent.CopyOnWriteArraySet;
  36. /**
  37. * 由于开发人员水平参差不齐,即使订了开发规范很多人也不遵守
  38. * <p>SQL是影响系统性能最重要的因素,所以拦截掉垃圾SQL语句</p>
  39. * <br>
  40. * <p>拦截SQL类型的场景</p>
  41. * <p>1.必须使用到索引,包含left join连接字段,符合索引最左原则</p>
  42. * <p>必须使用索引好处,</p>
  43. * <p>1.1 如果因为动态SQL,bug导致update的where条件没有带上,全表更新上万条数据</p>
  44. * <p>1.2 如果检查到使用了索引,SQL性能基本不会太差</p>
  45. * <br>
  46. * <p>2.SQL尽量单表执行,有查询left join的语句,必须在注释里面允许该SQL运行,否则会被拦截,有left join的语句,如果不能拆成单表执行的SQL,请leader商量在做</p>
  47. * <p>https://gaoxianglong.github.io/shark</p>
  48. * <p>SQL尽量单表执行的好处</p>
  49. * <p>2.1 查询条件简单、易于开理解和维护;</p>
  50. * <p>2.2 扩展性极强;(可为分库分表做准备)</p>
  51. * <p>2.3 缓存利用率高;</p>
  52. * <p>2.在字段上使用函数</p>
  53. * <br>
  54. * <p>3.where条件为空</p>
  55. * <p>4.where条件使用了 !=</p>
  56. * <p>5.where条件使用了 not 关键字</p>
  57. * <p>6.where条件使用了 or 关键字</p>
  58. * <p>7.where条件使用了 使用子查询</p>
  59. *
  60. * @author wcz
  61. * @since 2020-01-22
  62. */
  63. @Slf4j
  64. @Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
  65. public class SqlIllegalInterceptor implements Interceptor {
  66. /**
  67. * cache the legal sql result.
  68. */
  69. private final Set<String> legalSqlCacheSet = new HashSet<>();
  70. /**
  71. * cache the index info exist on the column.
  72. */
  73. private static final Set<String> indexInfoCacheSet = new CopyOnWriteArraySet<>();
  74. /**
  75. * cache the table had load index info from the connection.
  76. */
  77. private static final Set<String> tableCacheSet = new HashSet<>();
  78. @Getter
  79. @Setter
  80. private boolean devMode = true;
  81. @Getter
  82. @Setter
  83. private boolean sqlWrite = false;
  84. @Override
  85. public Object intercept(Invocation invocation) throws Throwable {
  86. StatementHandler statementHandler = SqlUtils.realTarget(invocation.getTarget());
  87. MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
  88. // 如果是insert操作, 或者 @SqlParser(filter = true) 跳过该方法解析 , 不进行验证
  89. MappedStatement mappedStatement = SqlUtils.getMappedStatement(metaObject);
  90. if (SqlCommandType.INSERT.equals(mappedStatement.getSqlCommandType()) || SqlParserHelper.getSqlParserInfo(metaObject)) {
  91. return invocation.proceed();
  92. }
  93. String originalSql = statementHandler.getBoundSql().getSql();
  94. log.info("Verifying SQL ID: {}", mappedStatement.getId());
  95. if (sqlWrite) {
  96. log.info("Verifying SQL:");
  97. log.info(originalSql);
  98. }
  99. String sqlEncode = SqlUtils.sqlEncode(originalSql);
  100. if (legalSqlCacheSet.contains(sqlEncode)) {
  101. log.info("Original Sql is legal, No need verify.");
  102. return invocation.proceed();
  103. }
  104. //get databaseMetaData & catalog & schema from connection, and put to local thread.
  105. Object firstArg = invocation.getArgs()[0];
  106. if(!(firstArg instanceof Connection)){
  107. return invocation.proceed();
  108. }
  109. Connection con = (Connection) firstArg;
  110. //the illegal rule.
  111. IllegalRule illegalRule = new IllegalRule(con.getMetaData(), con.getCatalog(), con.getSchema(), originalSql);
  112. // if the sql is legal, cache the sql encode.
  113. if (illegalRule.verify()) {
  114. legalSqlCacheSet.add(sqlEncode);
  115. } else {
  116. if (devMode) {
  117. illegalRule.warn();
  118. } else {
  119. illegalRule.error();
  120. }
  121. }
  122. return invocation.proceed();
  123. }
  124. @Override
  125. public Object plugin(Object target) {
  126. if (target instanceof StatementHandler) {
  127. return Plugin.wrap(target, this);
  128. }
  129. return target;
  130. }
  131. @Override
  132. public void setProperties(Properties prop) {
  133. String devMode = prop.getProperty("devMode");
  134. String sqlWrite = prop.getProperty("sqlWrite");
  135. if (SqlUtils.isBoolean(devMode)) {
  136. this.devMode = Boolean.valueOf(devMode);
  137. }
  138. if (SqlUtils.isBoolean(devMode)) {
  139. this.sqlWrite = Boolean.valueOf(sqlWrite);
  140. }
  141. }
  142. /**
  143. * the rules of illegal sql.
  144. */
  145. private static class IllegalRule {
  146. // the list of where exp parse from the sql to verify.
  147. private List<Expression> wheres = new ArrayList<>();
  148. // the list of index info parse from the sql to verify.
  149. private List<IndexInfo> indexInfos = new ArrayList<>();
  150. // the verify info parse from sql, include WHERE & TABLE & JOINS.
  151. private List<VerifyInfo> verifyInfos = new ArrayList<>();
  152. // the verify message.
  153. private List<String> verifyMessage = new ArrayList<>();
  154. private DatabaseMetaData databaseMetaData;
  155. private String catalog;
  156. private String schema;
  157. /**
  158. * the constructor with a original sql.
  159. *
  160. * @param originalSql the original sql string.
  161. */
  162. private IllegalRule(DatabaseMetaData databaseMetaData, String catalog, String schema, String originalSql) {
  163. this.databaseMetaData = databaseMetaData;
  164. this.catalog = catalog;
  165. this.schema = schema;
  166. // parse original sql
  167. parseSql(originalSql);
  168. }
  169. private void addMessage(String message, Object... values){
  170. if(message.contains("%s") && null != values && values.length > 0){
  171. verifyMessage.add(String.format(message, values));
  172. }else {
  173. verifyMessage.add(message);
  174. }
  175. }
  176. /**
  177. * verify no use WHERE.
  178. *
  179. * @param where a where {@link Expression}
  180. */
  181. private int whereNoUse(Expression where) {
  182. if (SqlUtils.isNull(where)) {
  183. addMessage("illegal sql. not have WHERE condition.");
  184. return 0;
  185. }
  186. return 1;
  187. }
  188. /**
  189. * verify use OR expression in WHERE.
  190. *
  191. * @param where a where {@link Expression}
  192. */
  193. private int whereHasOr(Expression where) {
  194. if (where instanceof OrExpression) {
  195. OrExpression or = (OrExpression) where;
  196. addMessage("illegal use [%s] in WHERE, will cause full table scan, try UNION ALL. sql: %s", or.getStringExpression(), or.toString());
  197. return 0;
  198. }
  199. return 1;
  200. }
  201. /**
  202. * verify use NotEqualsTo expression in WHERE.
  203. *
  204. * @param where a where {@link Expression}
  205. */
  206. private int whereHasNotEqualsTo(Expression where) {
  207. if (where instanceof NotEqualsTo) {
  208. NotEqualsTo notEqualsTo = (NotEqualsTo) where;
  209. addMessage("illegal use [%s] in WHERE, will cause full table scan. sql: %s", notEqualsTo.getStringExpression(), notEqualsTo.toString());
  210. return 0;
  211. }
  212. return 1;
  213. }
  214. /**
  215. * verify use binary expression in WHERE.
  216. * 1. NOT binary expression
  217. * 2. the left of binary is FUNCTION.
  218. * 3. the right of binary is SubSelect.
  219. *
  220. * @param where a where {@link Expression}
  221. */
  222. private int whereHasNotBinary(Expression where) {
  223. if (where instanceof BinaryExpression) {
  224. BinaryExpression binary = (BinaryExpression) where;
  225. if (binary.isNot()) {
  226. addMessage("illegal use [%s] in WHERE, will cause full table scan. Sql: %s", binary.getStringExpression(), binary.toString());
  227. return 0;
  228. }
  229. Expression leftExpression = binary.getLeftExpression();
  230. if (leftExpression instanceof Function) {
  231. Function function = (Function) leftExpression;
  232. addMessage("illegal use db function in WHERE, will cause full table scan. sql: %s", function.toString());
  233. return 0;
  234. }
  235. Expression rightExpression = binary.getRightExpression();
  236. if (rightExpression instanceof SubSelect) {
  237. SubSelect subSelect = (SubSelect) rightExpression;
  238. addMessage("illegal use subSelect in WHERE, will cause full table scan. sql: %s", subSelect.toString());
  239. return 0;
  240. }
  241. }
  242. return 1;
  243. }
  244. /**
  245. * verify use IN expression and the rightItem of IN is SubSelect in WHERE.
  246. *
  247. * @param where a where {@link Expression}
  248. */
  249. private int whereHasInSubSelect(Expression where) {
  250. if (where instanceof InExpression) {
  251. InExpression in = (InExpression) where;
  252. ItemsList rightItemsList = in.getRightItemsList();
  253. if (rightItemsList instanceof SubSelect) {
  254. SubSelect subSelect = (SubSelect) rightItemsList;
  255. addMessage("illegal use IN (subSelect) in WHERE, Replacing IN with EXISTS(subSelect) is better. sql: %s", subSelect.toString());
  256. return 0;
  257. }
  258. }
  259. return 1;
  260. }
  261. /**
  262. * verify check NULL in WHERE.
  263. *
  264. * @param where a where {@link Expression}
  265. */
  266. private int whereCheckNull(Expression where) {
  267. String lower = where.toString().toLowerCase();
  268. boolean checkNull = lower.contains("is null") || lower.contains("is not null");
  269. if (checkNull) {
  270. addMessage("illegal check null in WHERE, will cause full table scan, set a default value on column and equals it. sql: %s", where);
  271. return 0;
  272. }
  273. return 1;
  274. }
  275. /**
  276. * verify whether use index on the column.
  277. *
  278. * @param indexInfo include catalog & schema & tableName & columnName to Specify a unique index.
  279. */
  280. private int columnNoUseIndex(IndexInfo indexInfo) {
  281. String fullyQualifiedColumn = indexInfo.fullyQualifiedColumn();
  282. //if the column has set. and the cache contains the column.
  283. if (indexInfo.fullyColumn() && indexInfoCacheSet.contains(fullyQualifiedColumn)) {
  284. return 1;
  285. }
  286. //if the table has set. and the cache do not contains the table.
  287. String fullyQualifiedTable = indexInfo.fullyQualifiedTable();
  288. // if new create index on the column. must to reload index,
  289. if (indexInfo.fullyTable() && tableCacheSet.contains(fullyQualifiedTable)) {
  290. addMessage("Index not use on %s. Example sql: CREATE [UNIQUE|FULLTEXT|SPATIAL] INDEX idx_%s ON %s(%s)", fullyQualifiedColumn, indexInfo.columnName, indexInfo.tableName, indexInfo.columnName);
  291. return 0;
  292. }
  293. //get index info from the connection.
  294. try (ResultSet resultSet = databaseMetaData.getIndexInfo(indexInfo.catalog, indexInfo.schema, indexInfo.tableName, false, true)) {
  295. int indexCount = 0;
  296. while (resultSet.next()) {
  297. //索引中的列序列号等于1,才有效, index: 8, label: ORDINAL_POSITION
  298. if (resultSet.getShort("ORDINAL_POSITION") == 1) {
  299. //在索引中的列名列 index:9, label: COLUMN_NAME
  300. IndexInfo index = IndexInfo.newInstance();
  301. //TABLE_CAT
  302. index.setCatalog(indexInfo.catalog);
  303. //TABLE_SCHEM
  304. index.setSchema(indexInfo.schema);
  305. //TABLE_NAME
  306. index.setTableName(indexInfo.tableName);
  307. //COLUMN_NAME
  308. index.setColumnName(resultSet.getString("COLUMN_NAME"));
  309. indexInfoCacheSet.add(index.fullyQualifiedColumn());
  310. indexCount++;
  311. //cache table, no reload index every time.
  312. tableCacheSet.add(index.fullyQualifiedTable());
  313. }
  314. }
  315. if (indexCount > 6) {
  316. addMessage("Too many indexes (%s > 6) use on table (%s).", indexCount, fullyQualifiedTable);
  317. }
  318. log.info("Cache index: {}, table: {}", indexInfoCacheSet.size(), tableCacheSet.size());
  319. } catch (SQLException e) {
  320. log.error("get index info from DatabaseMetaData fail.");
  321. }
  322. if (indexInfo.fullyColumn() && indexInfoCacheSet.contains(fullyQualifiedColumn)) {
  323. return 1;
  324. }
  325. addMessage("Index not use on %s. Example sql: CREATE [UNIQUE|CLUSTERED] INDEX idx_%s ON %s(%s)", fullyQualifiedColumn, indexInfo.columnName, indexInfo.tableName, indexInfo.columnName);
  326. return 0;
  327. }
  328. /**
  329. * parse original sql to WHERE & TABLE & JOIN TABLES.
  330. */
  331. private void parseSql(String originalSql) {
  332. try {
  333. log.info("Parsing original sql.");
  334. if(null == originalSql || originalSql.trim().isEmpty()){
  335. addMessage("The original sql is null or empty.");
  336. return;
  337. }
  338. Statement statement = CCJSqlParserUtil.parse(originalSql);
  339. //select <SelectItem> from <FromItem> where <Expression>
  340. if (isSelect(statement)) {
  341. //SELECT t.* FROM (SELECT * FROM t1 LEFT JOIN t2 ON t1.c1 = t2.c1 WHERE ...) t WHERE t....
  342. log.info("original sql is SELECT.");
  343. SelectBody selectBody = ((Select) statement).getSelectBody();
  344. if(isPlainSelect(selectBody)){
  345. parseSelect((PlainSelect) selectBody);
  346. }
  347. } else if (isUpdate(statement)) {
  348. //UPDATE table SET column1 = v1 WHERE column2 = v2
  349. log.info("original sql is UPDATE.");
  350. verifyInfos.add(VerifyInfo.newInstance().parse((Update) statement));
  351. } else if (isDelete(statement)) {
  352. //DELETE FROM table WHERE column = v
  353. log.info("original sql is DELETE.");
  354. verifyInfos.add(VerifyInfo.newInstance().parse((Delete) statement));
  355. }
  356. } catch (Exception e) {
  357. verifyInfos.clear();
  358. addMessage("Parse original sql fail. %s", e.getMessage());
  359. }
  360. }
  361. /**
  362. * parse plain select to WHERE & TABLE & JOIN TABLES.
  363. *
  364. * @param plainSelect a plain select.
  365. */
  366. private void parseSelect(PlainSelect plainSelect) {
  367. verifyInfos.add(VerifyInfo.newInstance().parse(plainSelect));
  368. FromItem fromItem = plainSelect.getFromItem();
  369. //if is SubSelect, recursion parse select.
  370. if (isSubSelect(fromItem)) {
  371. SelectBody selectBody = ((SubSelect) fromItem).getSelectBody();
  372. if (isPlainSelect(selectBody)) {
  373. parseSelect((PlainSelect) selectBody);
  374. }
  375. }
  376. }
  377. /**
  378. * parse a join to WHERE & INDEX
  379. */
  380. private void parseJoin(VerifyInfo verifyInfo) {
  381. Expression on = verifyInfo.getWhere();
  382. Table table = verifyInfo.getTable();
  383. List<Table> joinTables = verifyInfo.getJoinTables();
  384. wheres.add(on);
  385. if (!isBinary(on)) {
  386. return;
  387. }
  388. BinaryExpression binary = (BinaryExpression) on;
  389. //获得左边表达式
  390. Expression leftExp = binary.getLeftExpression();
  391. //获得右边表达式
  392. Expression rightExp = binary.getRightExpression();
  393. wheres.add(rightExp);
  394. if (isBinary(leftExp)) {
  395. verifyInfo.setWhere(leftExp);
  396. parseJoin(verifyInfo);
  397. } else if (isColumn(leftExp) && isColumn(rightExp)) {
  398. // the alias of primary table
  399. Alias alias = table.getAlias();
  400. Column leftCol = (Column) leftExp;
  401. String leftColumn = leftCol.getColumnName();
  402. Table leftTable = leftCol.getTable();
  403. Column rightCol = (Column) rightExp;
  404. String rightColumn = rightCol.getColumnName();
  405. Table rightTable = rightCol.getTable();
  406. // if left or right belong to the main table, that verify the index info.
  407. // if the alias or the table name equals.
  408. if (SqlUtils.notNull(leftTable, rightTable)) {
  409. String leftTableName = leftTable.getName();
  410. String rightTableName = rightTable.getName();
  411. if (SqlUtils.notNull(alias) && Objects.equals(leftTableName, alias.getName())) {
  412. parseTable(table, leftColumn);
  413. } else if (Objects.equals(leftTableName, table.getName())) {
  414. parseTable(table, leftColumn);
  415. } else if (SqlUtils.notNull(alias) && Objects.equals(rightTableName, alias.getName())) {
  416. parseTable(table, rightColumn);
  417. } else if (Objects.equals(rightTableName, table.getName())) {
  418. parseTable(table, rightColumn);
  419. } else if (verifyInfo.hasJoinTables()) {
  420. // if left and right both don't belong to main table, find in the join tables.
  421. addMessage("Join column does not contain primary table (%s). sql: %s", table, binary);
  422. // the column is belong to the join tables.
  423. for (Table joinTable : joinTables) {
  424. Alias joinTableAlias = joinTable.getAlias();
  425. if (SqlUtils.notNull(joinTableAlias) && Objects.equals(joinTableAlias.getName(), leftTableName)) {
  426. parseTable(joinTable, leftColumn);
  427. } else if (SqlUtils.notNull(joinTableAlias) && Objects.equals(joinTableAlias.getName(), rightTableName)) {
  428. parseTable(joinTable, rightColumn);
  429. } else if (Objects.equals(joinTable.getName(), leftTableName)) {
  430. parseTable(joinTable, leftColumn);
  431. } else if (Objects.equals(joinTable.getName(), rightTableName)) {
  432. parseTable(joinTable, rightColumn);
  433. }
  434. }
  435. }
  436. }
  437. }
  438. }
  439. /**
  440. * parse where exp to WHERE & INDEX.
  441. */
  442. private void parseWhere(VerifyInfo verifyInfo) {
  443. Expression where = verifyInfo.getWhere();
  444. Table table = verifyInfo.getTable();
  445. List<Table> joinTables = verifyInfo.getJoinTables();
  446. wheres.add(where);
  447. if (!isBinary(where)) {
  448. return;
  449. }
  450. BinaryExpression binary = (BinaryExpression) where;
  451. //获得右边表达式
  452. Expression rightExp = binary.getRightExpression();
  453. wheres.add(rightExp);
  454. //获得左边表达式
  455. Expression leftExp = binary.getLeftExpression();
  456. if (isBinary(leftExp)) {
  457. verifyInfo.setWhere(leftExp);
  458. parseWhere(verifyInfo);
  459. } else if (isColumn(leftExp)) {
  460. Column leftCol = (Column) leftExp;
  461. Table leftTable = leftCol.getTable();
  462. Alias alias = table.getAlias();
  463. String leftColumn = leftCol.getColumnName();
  464. // with table alias
  465. if (SqlUtils.notNull(leftTable, alias) && Objects.equals(leftTable.getName(), alias.getName())) {
  466. parseTable(table, leftColumn);
  467. // single table select without alias
  468. } else if (SqlUtils.isNull(leftTable, alias)) {
  469. parseTable(table, leftColumn);
  470. } else if (verifyInfo.hasJoinTables()) {
  471. // the column is belong to join tables.
  472. addMessage("Column in the WHERE is not of the primary table (%s). sql: %s", table, binary);
  473. for (Table joinTable : joinTables) {
  474. Alias joinTableAlias = joinTable.getAlias();
  475. if (SqlUtils.notNull(joinTableAlias) && Objects.equals(joinTableAlias.getName(), leftTable.getName())) {
  476. parseTable(joinTable, leftColumn);
  477. } else if (Objects.equals(joinTable.getName(), leftTable.getName())) {
  478. parseTable(joinTable, leftColumn);
  479. }
  480. }
  481. }
  482. }
  483. }
  484. /**
  485. * parse joins exp to to WHERE & INDEX
  486. */
  487. private void parseJoins(VerifyInfo verifyInfo) {
  488. //允许执行join,验证join是否使用索引等等
  489. if (verifyInfo.hasJoins()) {
  490. for (Join join : verifyInfo.getJoins()) {
  491. verifyInfo.setWhere(join.getOnExpression());
  492. parseJoin(verifyInfo);
  493. }
  494. }
  495. }
  496. private void parseOrderBy(VerifyInfo verifyInfo) {
  497. List<OrderByElement> orderByElements = verifyInfo.getOrderByElements();
  498. Table table = verifyInfo.getTable();
  499. List<Table> joinTables = verifyInfo.getJoinTables();
  500. if (SqlUtils.notEmpty(orderByElements)) {
  501. for (OrderByElement orderByElement : orderByElements) {
  502. Expression orderBy = orderByElement.getExpression();
  503. if (!isColumn(orderBy)) {
  504. continue;
  505. }
  506. Column orderByCol = (Column) orderBy;
  507. String orderByColumnName = orderByCol.getColumnName();
  508. Table orderByTable = orderByCol.getTable();
  509. Alias alias = table.getAlias();
  510. if (SqlUtils.notNull(orderByTable, alias) && Objects.equals(orderByTable.getName(), alias.getName())) {
  511. parseTable(table, orderByColumnName);
  512. } else if (SqlUtils.isNull(orderByTable, alias)) {
  513. parseTable(table, orderByColumnName);
  514. } else if (verifyInfo.hasJoinTables()) {
  515. // the column is belong to join tables.
  516. addMessage("Column in ORDER BY is not of the primary table (%s). sql: %s", table, orderByElement);
  517. for (Table joinTable : joinTables) {
  518. Alias joinTableAlias = joinTable.getAlias();
  519. if (SqlUtils.notNull(joinTableAlias) && Objects.equals(joinTableAlias.getName(), orderByTable.getName())) {
  520. parseTable(joinTable, orderByColumnName);
  521. } else if (Objects.equals(joinTable.getName(), orderByTable.getName())) {
  522. parseTable(joinTable, orderByColumnName);
  523. }
  524. }
  525. }
  526. }
  527. }
  528. }
  529. /**
  530. * parse table and it's column to get index.
  531. *
  532. * @param table a table.
  533. * @param columnName the column belong to the table.
  534. */
  535. private void parseTable(Table table, String columnName) {
  536. String tableInfo = table.getName();
  537. //table : db.tableName
  538. IndexInfo indexInfo = new IndexInfo();
  539. String[] tableArray = tableInfo.split("\\.");
  540. if (tableArray.length == 1) {
  541. indexInfo.setTableName(tableArray[0]);
  542. } else {
  543. indexInfo.setTableName(tableArray[1]);
  544. }
  545. indexInfo.setCatalog(catalog);
  546. indexInfo.setSchema(schema);
  547. indexInfo.setColumnName(columnName);
  548. indexInfos.add(indexInfo);
  549. }
  550. /**
  551. * verify the where exp & index info parse from the sql. return finally.
  552. */
  553. private boolean verify() {
  554. if (verifyInfos.isEmpty()) {
  555. return false;
  556. }
  557. log.info("Parsing all expressions of sql.");
  558. for (VerifyInfo verifyInfo : verifyInfos) {
  559. if (verifyInfo.hasTable()) {
  560. log.info("===============================");
  561. parseWhere(verifyInfo);
  562. parseJoins(verifyInfo);
  563. parseOrderBy(verifyInfo);
  564. } else {
  565. wheres.add(verifyInfo.getWhere());
  566. }
  567. }
  568. int whereTotal = wheres.size();
  569. int indexTotal = indexInfos.size();
  570. log.info("parse expression complete, WHERE: {}, INDEX: {}.", whereTotal, indexTotal);
  571. int verifyCount = 0;
  572. if(!wheres.isEmpty()){
  573. log.info("Verifying all WHERE expressions.");
  574. for (int i = 0; i < whereTotal; i++) {
  575. Expression where = wheres.get(i);
  576. log.info("{}. where: {}", (i + 1), where);
  577. int whereNoUse = whereNoUse(where);
  578. verifyCount += whereNoUse;
  579. if (whereNoUse > 0) {
  580. verifyCount += whereHasOr(where);
  581. verifyCount += whereHasNotEqualsTo(where);
  582. verifyCount += whereHasNotBinary(where);
  583. verifyCount += whereHasInSubSelect(where);
  584. verifyCount += whereCheckNull(where);
  585. }
  586. }
  587. }
  588. if(!indexInfos.isEmpty()){
  589. log.info("Verifying all INDEX on column.");
  590. for (int i = 0; i < indexTotal; i++) {
  591. IndexInfo indexInfo = indexInfos.get(i);
  592. log.info("{}. indexInfo: {}", (i + 1), indexInfo.fullyQualifiedColumn());
  593. verifyCount += columnNoUseIndex(indexInfo);
  594. }
  595. }
  596. return verifyCount == whereTotal * 6 + indexTotal;
  597. }
  598. public void warn() {
  599. if (verifyMessage.isEmpty()) {
  600. log.warn("no any message.");
  601. } else {
  602. verifyMessage.forEach(m -> {
  603. log.warn("Warn: {}", m);
  604. });
  605. }
  606. }
  607. public void error() {
  608. if (verifyMessage.isEmpty()) {
  609. log.error("no any message.");
  610. } else {
  611. verifyMessage.forEach(m -> {
  612. log.error("Error: {}", m);
  613. });
  614. }
  615. }
  616. private boolean isSelect(Statement statement) {
  617. return (statement instanceof Select);
  618. }
  619. private boolean isUpdate(Statement statement) {
  620. return (statement instanceof Update);
  621. }
  622. private boolean isDelete(Statement statement) {
  623. return (statement instanceof Delete);
  624. }
  625. private boolean isPlainSelect(Object object) {
  626. return (object instanceof PlainSelect);
  627. }
  628. private boolean isSubSelect(Object object) {
  629. return (object instanceof SubSelect);
  630. }
  631. private boolean isBinary(Expression expression) {
  632. return (expression instanceof BinaryExpression);
  633. }
  634. private boolean isColumn(Expression expression) {
  635. return (expression instanceof Column);
  636. }
  637. }
  638. @Data
  639. @Accessors(chain = true)
  640. @ToString
  641. private static class VerifyInfo {
  642. private Expression where;
  643. private Table table;
  644. private List<Join> joins;
  645. private List<OrderByElement> orderByElements;
  646. // the list tables in the JOIN condition.
  647. private List<Table> joinTables = new ArrayList<>();
  648. private static VerifyInfo newInstance(){
  649. return new VerifyInfo();
  650. }
  651. private void parseJoinTables() {
  652. if (hasJoins()) {
  653. for (Join join : joins) {
  654. FromItem rightItem = join.getRightItem();
  655. if(rightItem instanceof Table){
  656. joinTables.add((Table) rightItem);
  657. }
  658. }
  659. }
  660. }
  661. private VerifyInfo parse(PlainSelect plainSelect) {
  662. this.setWhere(plainSelect.getWhere());
  663. this.setJoins(plainSelect.getJoins());
  664. this.setOrderByElements(plainSelect.getOrderByElements());
  665. FromItem fromItem = plainSelect.getFromItem();
  666. // if is Table
  667. if (fromItem instanceof Table) {
  668. this.setTable((Table) fromItem);
  669. }
  670. parseJoinTables();
  671. return this;
  672. }
  673. private VerifyInfo parse(Update update) {
  674. this.setWhere(update.getWhere());
  675. this.setJoins(update.getJoins());
  676. List<Table> tables = update.getTables();
  677. if (!tables.isEmpty()) {
  678. this.setTable(tables.get(0));
  679. }
  680. parseJoinTables();
  681. return this;
  682. }
  683. private VerifyInfo parse(Delete delete) {
  684. this.setWhere(delete.getWhere());
  685. this.setJoins(delete.getJoins());
  686. this.setTable(delete.getTable());
  687. parseJoinTables();
  688. return this;
  689. }
  690. private boolean hasWhere() {
  691. return null != where;
  692. }
  693. private boolean hasTable() {
  694. return null != table;
  695. }
  696. private boolean hasJoins() {
  697. return null != joins && !joins.isEmpty();
  698. }
  699. private boolean hasJoinTables() {
  700. return !joinTables.isEmpty();
  701. }
  702. }
  703. /**
  704. * the information of the index.
  705. */
  706. @Data
  707. @Accessors(chain = true)
  708. @ToString
  709. private static class IndexInfo {
  710. private String catalog;
  711. private String schema;
  712. private String tableName;
  713. private String columnName;
  714. private static IndexInfo newInstance() {
  715. return new IndexInfo();
  716. }
  717. private boolean fullyColumn() {
  718. return SqlUtils.notNull(getTableName(), getColumnName());
  719. }
  720. private boolean fullyTable() {
  721. return SqlUtils.notNull(getTableName());
  722. }
  723. private String fullyQualifiedColumn() {
  724. return fullyColumn() ? String.format("%s.%s.%s.%s", fixCatalog(), fixSchema(), getTableName(), getColumnName()) : null;
  725. }
  726. private String fullyQualifiedTable() {
  727. return fullyTable() ? String.format("%s.%s.%s", fixCatalog(), fixSchema(), getTableName()) : null;
  728. }
  729. private String fixCatalog() {
  730. return Objects.isNull(this.catalog) ? "<catalog>" : this.catalog;
  731. }
  732. private String fixSchema() {
  733. return Objects.isNull(this.schema) ? "<schema>" : this.schema;
  734. }
  735. }
  736. }