SqlIllegalInterceptor.java 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802
  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 willenfoo
  61. * @since 2018-03-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 any to verify, just return.
  113. if (!illegalRule.verifiable()) {
  114. illegalRule.error();
  115. return invocation.proceed();
  116. }
  117. // if the sql is legal, cache the sql encode.
  118. if (illegalRule.verify()) {
  119. legalSqlCacheSet.add(sqlEncode);
  120. } else {
  121. if (devMode) {
  122. illegalRule.warn();
  123. } else {
  124. illegalRule.error();
  125. }
  126. }
  127. return invocation.proceed();
  128. }
  129. @Override
  130. public Object plugin(Object target) {
  131. if (target instanceof StatementHandler) {
  132. return Plugin.wrap(target, this);
  133. }
  134. return target;
  135. }
  136. @Override
  137. public void setProperties(Properties prop) {
  138. String devMode = prop.getProperty("devMode");
  139. String sqlWrite = prop.getProperty("sqlWrite");
  140. if (SqlUtils.isBoolean(devMode)) {
  141. this.devMode = Boolean.valueOf(devMode);
  142. }
  143. if (SqlUtils.isBoolean(devMode)) {
  144. this.sqlWrite = Boolean.valueOf(sqlWrite);
  145. }
  146. }
  147. /**
  148. * the rules of illegal sql.
  149. */
  150. private static class IllegalRule {
  151. // the list of where exp parse from the sql to verify.
  152. private List<Expression> wheres = new ArrayList<>();
  153. // the list of index info parse from the sql to verify.
  154. private List<IndexInfo> indexInfos = new ArrayList<>();
  155. // the verify info parse from sql, include WHERE & TABLE & JOINS.
  156. private List<VerifyInfo> verifyInfos = new ArrayList<>();
  157. // the verify message.
  158. private List<String> verifyMessage = new ArrayList<>();
  159. private DatabaseMetaData databaseMetaData;
  160. private String catalog;
  161. private String schema;
  162. /**
  163. * the constructor with a original sql.
  164. *
  165. * @param originalSql the original sql string.
  166. */
  167. private IllegalRule(DatabaseMetaData databaseMetaData, String catalog, String schema, String originalSql) {
  168. this.databaseMetaData = databaseMetaData;
  169. this.catalog = catalog;
  170. this.schema = schema;
  171. // parse original sql
  172. parseSql(originalSql);
  173. }
  174. private void addMessage(String message, Object... values){
  175. if(message.contains("%s") && null != values && values.length > 0){
  176. verifyMessage.add(String.format(message, values));
  177. }else {
  178. verifyMessage.add(message);
  179. }
  180. }
  181. /**
  182. * verify no use WHERE.
  183. *
  184. * @param where a where {@link Expression}
  185. */
  186. private int whereNoUse(Expression where) {
  187. if (SqlUtils.isNull(where)) {
  188. addMessage("illegal sql. not have WHERE condition.");
  189. return 0;
  190. }
  191. return 1;
  192. }
  193. /**
  194. * verify use OR expression in WHERE.
  195. *
  196. * @param where a where {@link Expression}
  197. */
  198. private int whereHasOr(Expression where) {
  199. if (where instanceof OrExpression) {
  200. OrExpression or = (OrExpression) where;
  201. addMessage("illegal use [%s] in WHERE, will cause full table scan, try UNION ALL. sql: %s", or.getStringExpression(), or.toString());
  202. return 0;
  203. }
  204. return 1;
  205. }
  206. /**
  207. * verify use NotEqualsTo expression in WHERE.
  208. *
  209. * @param where a where {@link Expression}
  210. */
  211. private int whereHasNotEqualsTo(Expression where) {
  212. if (where instanceof NotEqualsTo) {
  213. NotEqualsTo notEqualsTo = (NotEqualsTo) where;
  214. addMessage("illegal use [%s] in WHERE, will cause full table scan. sql: %s", notEqualsTo.getStringExpression(), notEqualsTo.toString());
  215. return 0;
  216. }
  217. return 1;
  218. }
  219. /**
  220. * verify use binary expression in WHERE.
  221. * 1. NOT binary expression
  222. * 2. the left of binary is FUNCTION.
  223. * 3. the right of binary is SubSelect.
  224. *
  225. * @param where a where {@link Expression}
  226. */
  227. private int whereHasNotBinary(Expression where) {
  228. if (where instanceof BinaryExpression) {
  229. BinaryExpression binary = (BinaryExpression) where;
  230. if (binary.isNot()) {
  231. addMessage("illegal use [%s] in WHERE, will cause full table scan. Sql: %s", binary.getStringExpression(), binary.toString());
  232. return 0;
  233. }
  234. Expression leftExpression = binary.getLeftExpression();
  235. if (leftExpression instanceof Function) {
  236. Function function = (Function) leftExpression;
  237. addMessage("illegal use db function in WHERE, will cause full table scan. sql: %s", function.toString());
  238. return 0;
  239. }
  240. Expression rightExpression = binary.getRightExpression();
  241. if (rightExpression instanceof SubSelect) {
  242. SubSelect subSelect = (SubSelect) rightExpression;
  243. addMessage("illegal use subSelect in WHERE, will cause full table scan. sql: %s", subSelect.toString());
  244. return 0;
  245. }
  246. }
  247. return 1;
  248. }
  249. /**
  250. * verify use IN expression and the rightItem of IN is SubSelect in WHERE.
  251. *
  252. * @param where a where {@link Expression}
  253. */
  254. private int whereHasInSubSelect(Expression where) {
  255. if (where instanceof InExpression) {
  256. InExpression in = (InExpression) where;
  257. ItemsList rightItemsList = in.getRightItemsList();
  258. if (rightItemsList instanceof SubSelect) {
  259. SubSelect subSelect = (SubSelect) rightItemsList;
  260. addMessage("illegal use IN (subSelect) in WHERE, Replacing IN with EXISTS(subSelect) is better. sql: %s", subSelect.toString());
  261. return 0;
  262. }
  263. }
  264. return 1;
  265. }
  266. /**
  267. * verify check NULL in WHERE.
  268. *
  269. * @param where a where {@link Expression}
  270. */
  271. private int whereCheckNull(Expression where) {
  272. String lower = where.toString().toLowerCase();
  273. boolean checkNull = lower.contains("is null") || lower.contains("is not null");
  274. if (checkNull) {
  275. addMessage("illegal check null in WHERE, will cause full table scan, set a default value on column and equals it. sql: %s", where);
  276. return 0;
  277. }
  278. return 1;
  279. }
  280. /**
  281. * verify whether use index on the column.
  282. *
  283. * @param indexInfo include catalog & schema & tableName & columnName to Specify a unique index.
  284. */
  285. private int columnNoUseIndex(IndexInfo indexInfo) {
  286. String fullyQualifiedColumn = indexInfo.fullyQualifiedColumn();
  287. //if the column has set. and the cache contains the column.
  288. if (indexInfo.fullyColumn() && indexInfoCacheSet.contains(fullyQualifiedColumn)) {
  289. return 1;
  290. }
  291. //if the table has set. and the cache do not contains the table.
  292. String fullyQualifiedTable = indexInfo.fullyQualifiedTable();
  293. // if new create index on the column. must to reload index,
  294. if (indexInfo.fullyTable() && tableCacheSet.contains(fullyQualifiedTable)) {
  295. addMessage("Index not use on %s. Example sql: CREATE [UNIQUE|CLUSTERED] INDEX idx_%s ON %s(%s)", fullyQualifiedColumn, indexInfo.columnName, indexInfo.tableName, indexInfo.columnName);
  296. return 0;
  297. }
  298. //get index info from the connection.
  299. try (ResultSet resultSet = databaseMetaData.getIndexInfo(indexInfo.catalog, indexInfo.schema, indexInfo.tableName, false, true)) {
  300. int indexCount = 0;
  301. while (resultSet.next()) {
  302. //索引中的列序列号等于1,才有效, index: 8, label: ORDINAL_POSITION
  303. if (resultSet.getShort("ORDINAL_POSITION") == 1) {
  304. //在索引中的列名列 index:9, label: COLUMN_NAME
  305. IndexInfo index = IndexInfo.newInstance();
  306. //TABLE_CAT
  307. index.setCatalog(indexInfo.catalog);
  308. //TABLE_SCHEM
  309. index.setSchema(indexInfo.schema);
  310. //TABLE_NAME
  311. index.setTableName(indexInfo.tableName);
  312. //COLUMN_NAME
  313. index.setColumnName(resultSet.getString("COLUMN_NAME"));
  314. indexInfoCacheSet.add(index.fullyQualifiedColumn());
  315. indexCount++;
  316. //cache table, no reload index every time.
  317. tableCacheSet.add(index.fullyQualifiedTable());
  318. }
  319. }
  320. if (indexCount > 6) {
  321. addMessage("Too many indexes (%s > 6) use on table (%s).", indexCount, fullyQualifiedTable);
  322. }
  323. log.info("Cache index: {}, table: {}", indexInfoCacheSet.size(), tableCacheSet.size());
  324. } catch (SQLException e) {
  325. log.error("get index info from DatabaseMetaData fail.");
  326. }
  327. if (indexInfo.fullyColumn() && indexInfoCacheSet.contains(fullyQualifiedColumn)) {
  328. return 1;
  329. }
  330. addMessage("Index not use on %s. Example sql: CREATE [UNIQUE|CLUSTERED] INDEX idx_%s ON %s(%s)", fullyQualifiedColumn, indexInfo.columnName, indexInfo.tableName, indexInfo.columnName);
  331. return 0;
  332. }
  333. /**
  334. * parse original sql to WHERE & TABLE & JOIN TABLES.
  335. */
  336. private void parseSql(String originalSql) {
  337. try {
  338. log.info("Parsing original sql.");
  339. if(null == originalSql || originalSql.trim().isEmpty()){
  340. addMessage("The original sql is null or empty.");
  341. return;
  342. }
  343. Statement statement = CCJSqlParserUtil.parse(originalSql);
  344. //select <SelectItem> from <FromItem> where <Expression>
  345. if (isSelect(statement)) {
  346. //SELECT t.* FROM (SELECT * FROM t1 LEFT JOIN t2 ON t1.c1 = t2.c1 WHERE ...) t WHERE t....
  347. log.info("original sql is SELECT.");
  348. SelectBody selectBody = ((Select) statement).getSelectBody();
  349. if(isPlainSelect(selectBody)){
  350. parseSelect((PlainSelect) selectBody);
  351. }
  352. } else if (isUpdate(statement)) {
  353. //UPDATE table SET column1 = v1 WHERE column2 = v2
  354. log.info("original sql is UPDATE.");
  355. verifyInfos.add(VerifyInfo.newInstance().parse((Update) statement));
  356. } else if (isDelete(statement)) {
  357. //DELETE FROM table WHERE column = v
  358. log.info("original sql is DELETE.");
  359. verifyInfos.add(VerifyInfo.newInstance().parse((Delete) statement));
  360. }
  361. } catch (Exception e) {
  362. verifyInfos.clear();
  363. addMessage("Parse original sql fail. %s", e.getMessage());
  364. }
  365. }
  366. /**
  367. * parse plain select to WHERE & TABLE & JOIN TABLES.
  368. *
  369. * @param plainSelect a plain select.
  370. */
  371. private void parseSelect(PlainSelect plainSelect) {
  372. verifyInfos.add(VerifyInfo.newInstance().parse(plainSelect));
  373. FromItem fromItem = plainSelect.getFromItem();
  374. //if is SubSelect, recursion parse select.
  375. if (isSubSelect(fromItem)) {
  376. SelectBody selectBody = ((SubSelect) fromItem).getSelectBody();
  377. if (isPlainSelect(selectBody)) {
  378. parseSelect((PlainSelect) selectBody);
  379. }
  380. }
  381. }
  382. /**
  383. * parse a join to WHERE & INDEX
  384. */
  385. private void parseJoin(VerifyInfo verifyInfo) {
  386. Expression on = verifyInfo.getWhere();
  387. Table table = verifyInfo.getTable();
  388. List<Table> joinTables = verifyInfo.getJoinTables();
  389. wheres.add(on);
  390. if (!isBinary(on)) {
  391. return;
  392. }
  393. BinaryExpression binary = (BinaryExpression) on;
  394. //获得左边表达式
  395. Expression leftExp = binary.getLeftExpression();
  396. //获得右边表达式
  397. Expression rightExp = binary.getRightExpression();
  398. wheres.add(rightExp);
  399. if (isBinary(leftExp)) {
  400. verifyInfo.setWhere(leftExp);
  401. parseJoin(verifyInfo);
  402. } else if (isColumn(leftExp) && isColumn(rightExp)) {
  403. // the alias of primary table
  404. Alias alias = table.getAlias();
  405. Column leftCol = (Column) leftExp;
  406. String leftColumn = leftCol.getColumnName();
  407. Table leftTable = leftCol.getTable();
  408. Column rightCol = (Column) rightExp;
  409. String rightColumn = rightCol.getColumnName();
  410. Table rightTable = rightCol.getTable();
  411. // if left or right belong to the main table, that verify the index info.
  412. // if the alias or the table name equals.
  413. if (SqlUtils.notNull(leftTable, rightTable)) {
  414. String leftTableName = leftTable.getName();
  415. String rightTableName = rightTable.getName();
  416. if (SqlUtils.notNull(alias) && Objects.equals(leftTableName, alias.getName())) {
  417. parseTable(table, leftColumn);
  418. } else if (Objects.equals(leftTableName, table.getName())) {
  419. parseTable(table, leftColumn);
  420. } else if (SqlUtils.notNull(alias) && Objects.equals(rightTableName, alias.getName())) {
  421. parseTable(table, rightColumn);
  422. } else if (Objects.equals(rightTableName, table.getName())) {
  423. parseTable(table, rightColumn);
  424. } else if (verifyInfo.hasJoinTables()) {
  425. // if left and right both don't belong to main table, find in the join tables.
  426. addMessage("Join column does not contain primary table (%s). sql: %s", table, binary);
  427. // the column is belong to the join tables.
  428. for (Table joinTable : joinTables) {
  429. Alias joinTableAlias = joinTable.getAlias();
  430. if (SqlUtils.notNull(joinTableAlias) && Objects.equals(joinTableAlias.getName(), leftTableName)) {
  431. parseTable(joinTable, leftColumn);
  432. } else if (SqlUtils.notNull(joinTableAlias) && Objects.equals(joinTableAlias.getName(), rightTableName)) {
  433. parseTable(joinTable, rightColumn);
  434. } else if (Objects.equals(joinTable.getName(), leftTableName)) {
  435. parseTable(joinTable, leftColumn);
  436. } else if (Objects.equals(joinTable.getName(), rightTableName)) {
  437. parseTable(joinTable, rightColumn);
  438. }
  439. }
  440. }
  441. }
  442. }
  443. }
  444. /**
  445. * parse where exp to WHERE & INDEX.
  446. */
  447. private void parseWhere(VerifyInfo verifyInfo) {
  448. Expression where = verifyInfo.getWhere();
  449. Table table = verifyInfo.getTable();
  450. List<Table> joinTables = verifyInfo.getJoinTables();
  451. wheres.add(where);
  452. if (!isBinary(where)) {
  453. return;
  454. }
  455. BinaryExpression binary = (BinaryExpression) where;
  456. //获得右边表达式
  457. Expression rightExp = binary.getRightExpression();
  458. wheres.add(rightExp);
  459. //获得左边表达式
  460. Expression leftExp = binary.getLeftExpression();
  461. if (isBinary(leftExp)) {
  462. verifyInfo.setWhere(leftExp);
  463. parseWhere(verifyInfo);
  464. } else if (isColumn(leftExp)) {
  465. Column leftCol = (Column) leftExp;
  466. Table leftTable = leftCol.getTable();
  467. Alias alias = table.getAlias();
  468. String leftColumn = leftCol.getColumnName();
  469. // with table alias
  470. if (SqlUtils.notNull(leftTable, alias) && Objects.equals(leftTable.getName(), alias.getName())) {
  471. parseTable(table, leftColumn);
  472. // single table select without alias
  473. } else if (SqlUtils.isNull(leftTable, alias)) {
  474. parseTable(table, leftColumn);
  475. } else if (verifyInfo.hasJoinTables()) {
  476. // the column is belong to join tables.
  477. addMessage("Column in the WHERE is not of the primary table (%s). sql: %s", table, binary);
  478. for (Table joinTable : joinTables) {
  479. Alias joinTableAlias = joinTable.getAlias();
  480. if (SqlUtils.notNull(joinTableAlias) && Objects.equals(joinTableAlias.getName(), leftTable.getName())) {
  481. parseTable(joinTable, leftColumn);
  482. } else if (Objects.equals(joinTable.getName(), leftTable.getName())) {
  483. parseTable(joinTable, leftColumn);
  484. }
  485. }
  486. }
  487. }
  488. }
  489. /**
  490. * parse joins exp to to WHERE & INDEX
  491. */
  492. private void parseJoins(VerifyInfo verifyInfo) {
  493. //允许执行join,验证join是否使用索引等等
  494. if (verifyInfo.hasJoins()) {
  495. for (Join join : verifyInfo.getJoins()) {
  496. verifyInfo.setWhere(join.getOnExpression());
  497. parseJoin(verifyInfo);
  498. }
  499. }
  500. }
  501. private void parseOrderBy(VerifyInfo verifyInfo) {
  502. List<OrderByElement> orderByElements = verifyInfo.getOrderByElements();
  503. Table table = verifyInfo.getTable();
  504. List<Table> joinTables = verifyInfo.getJoinTables();
  505. if (SqlUtils.notEmpty(orderByElements)) {
  506. for (OrderByElement orderByElement : orderByElements) {
  507. Expression orderBy = orderByElement.getExpression();
  508. if (!isColumn(orderBy)) {
  509. continue;
  510. }
  511. Column orderByCol = (Column) orderBy;
  512. String orderByColumnName = orderByCol.getColumnName();
  513. Table orderByTable = orderByCol.getTable();
  514. Alias alias = table.getAlias();
  515. if (SqlUtils.notNull(orderByTable, alias) && Objects.equals(orderByTable.getName(), alias.getName())) {
  516. parseTable(table, orderByColumnName);
  517. } else if (SqlUtils.isNull(orderByTable, alias)) {
  518. parseTable(table, orderByColumnName);
  519. } else if (verifyInfo.hasJoinTables()) {
  520. // the column is belong to join tables.
  521. addMessage("Column in ORDER BY is not of the primary table (%s). sql: %s", table, orderByElement);
  522. for (Table joinTable : joinTables) {
  523. Alias joinTableAlias = joinTable.getAlias();
  524. if (SqlUtils.notNull(joinTableAlias) && Objects.equals(joinTableAlias.getName(), orderByTable.getName())) {
  525. parseTable(joinTable, orderByColumnName);
  526. } else if (Objects.equals(joinTable.getName(), orderByTable.getName())) {
  527. parseTable(joinTable, orderByColumnName);
  528. }
  529. }
  530. }
  531. }
  532. }
  533. }
  534. /**
  535. * parse table and it's column to get index.
  536. *
  537. * @param table a table.
  538. * @param columnName the column belong to the table.
  539. */
  540. private void parseTable(Table table, String columnName) {
  541. String tableInfo = table.getName();
  542. //table : db.tableName
  543. IndexInfo indexInfo = new IndexInfo();
  544. String[] tableArray = tableInfo.split("\\.");
  545. if (tableArray.length == 1) {
  546. indexInfo.setTableName(tableArray[0]);
  547. } else {
  548. indexInfo.setTableName(tableArray[1]);
  549. }
  550. indexInfo.setCatalog(catalog);
  551. indexInfo.setSchema(schema);
  552. indexInfo.setColumnName(columnName);
  553. indexInfos.add(indexInfo);
  554. }
  555. /**
  556. * check whether the sql is verifiable.
  557. */
  558. private boolean verifiable() {
  559. return !verifyInfos.isEmpty();
  560. }
  561. /**
  562. * verify the where exp & index info parse from the sql. return finally.
  563. */
  564. private boolean verify() {
  565. if (verifyInfos.isEmpty()) {
  566. return false;
  567. }
  568. log.info("Parsing all expressions of sql.");
  569. for (VerifyInfo verifyInfo : verifyInfos) {
  570. if (verifyInfo.hasTable()) {
  571. log.info("===============================");
  572. parseWhere(verifyInfo);
  573. parseJoins(verifyInfo);
  574. parseOrderBy(verifyInfo);
  575. } else {
  576. wheres.add(verifyInfo.getWhere());
  577. }
  578. }
  579. int whereTotal = wheres.size();
  580. int indexTotal = indexInfos.size();
  581. log.info("parse expression complete, WHERE: {}, INDEX: {}.", whereTotal, indexTotal);
  582. int verifyCount = 0;
  583. if(!wheres.isEmpty()){
  584. log.info("Verifying all WHERE expressions.");
  585. for (int i = 0; i < whereTotal; i++) {
  586. Expression where = wheres.get(i);
  587. log.info("{}. where: {}", (i + 1), where);
  588. int whereNoUse = whereNoUse(where);
  589. verifyCount += whereNoUse;
  590. if (whereNoUse > 0) {
  591. verifyCount += whereHasOr(where);
  592. verifyCount += whereHasNotEqualsTo(where);
  593. verifyCount += whereHasNotBinary(where);
  594. verifyCount += whereHasInSubSelect(where);
  595. verifyCount += whereCheckNull(where);
  596. }
  597. }
  598. }
  599. if(!indexInfos.isEmpty()){
  600. log.info("Verifying all INDEX on column.");
  601. for (int i = 0; i < indexTotal; i++) {
  602. IndexInfo indexInfo = indexInfos.get(i);
  603. log.info("{}. indexInfo: {}", (i + 1), indexInfo.fullyQualifiedColumn());
  604. verifyCount += columnNoUseIndex(indexInfo);
  605. }
  606. }
  607. return verifyCount == whereTotal * 6 + indexTotal;
  608. }
  609. public void warn() {
  610. if (verifyMessage.isEmpty()) {
  611. log.warn("no any message.");
  612. } else {
  613. verifyMessage.forEach(m -> {
  614. log.warn("Warn: {}", m);
  615. });
  616. }
  617. }
  618. public void error() {
  619. if (verifyMessage.isEmpty()) {
  620. log.error("no any message.");
  621. } else {
  622. verifyMessage.forEach(m -> {
  623. log.error("Error: {}", m);
  624. });
  625. }
  626. }
  627. private boolean isSelect(Statement statement) {
  628. return (statement instanceof Select);
  629. }
  630. private boolean isUpdate(Statement statement) {
  631. return (statement instanceof Update);
  632. }
  633. private boolean isDelete(Statement statement) {
  634. return (statement instanceof Delete);
  635. }
  636. private boolean isPlainSelect(Object object) {
  637. return (object instanceof PlainSelect);
  638. }
  639. private boolean isSubSelect(Object object) {
  640. return (object instanceof SubSelect);
  641. }
  642. private boolean isBinary(Expression expression) {
  643. return (expression instanceof BinaryExpression);
  644. }
  645. private boolean isColumn(Expression expression) {
  646. return (expression instanceof Column);
  647. }
  648. }
  649. @Data
  650. @Accessors(chain = true)
  651. @ToString
  652. private static class VerifyInfo {
  653. private Expression where;
  654. private Table table;
  655. private List<Join> joins;
  656. private List<OrderByElement> orderByElements;
  657. // the list tables in the JOIN condition.
  658. private List<Table> joinTables = new ArrayList<>();
  659. private static VerifyInfo newInstance(){
  660. return new VerifyInfo();
  661. }
  662. private void parseJoinTables() {
  663. if (hasJoins()) {
  664. for (Join join : joins) {
  665. FromItem rightItem = join.getRightItem();
  666. if(rightItem instanceof Table){
  667. joinTables.add((Table) rightItem);
  668. }
  669. }
  670. }
  671. }
  672. private VerifyInfo parse(PlainSelect plainSelect) {
  673. this.setWhere(plainSelect.getWhere());
  674. this.setJoins(plainSelect.getJoins());
  675. this.setOrderByElements(plainSelect.getOrderByElements());
  676. FromItem fromItem = plainSelect.getFromItem();
  677. // if is Table
  678. if (fromItem instanceof Table) {
  679. this.setTable((Table) fromItem);
  680. }
  681. parseJoinTables();
  682. return this;
  683. }
  684. private VerifyInfo parse(Update update) {
  685. this.setWhere(update.getWhere());
  686. this.setJoins(update.getJoins());
  687. List<Table> tables = update.getTables();
  688. if (!tables.isEmpty()) {
  689. this.setTable(tables.get(0));
  690. }
  691. parseJoinTables();
  692. return this;
  693. }
  694. private VerifyInfo parse(Delete delete) {
  695. this.setWhere(delete.getWhere());
  696. this.setJoins(delete.getJoins());
  697. this.setTable(delete.getTable());
  698. parseJoinTables();
  699. return this;
  700. }
  701. private boolean hasWhere() {
  702. return null != where;
  703. }
  704. private boolean hasTable() {
  705. return null != table;
  706. }
  707. private boolean hasJoins() {
  708. return null != joins && !joins.isEmpty();
  709. }
  710. private boolean hasJoinTables() {
  711. return !joinTables.isEmpty();
  712. }
  713. }
  714. /**
  715. * the information of the index.
  716. */
  717. @Data
  718. @Accessors(chain = true)
  719. @ToString
  720. private static class IndexInfo {
  721. private String catalog;
  722. private String schema;
  723. private String tableName;
  724. private String columnName;
  725. private static IndexInfo newInstance() {
  726. return new IndexInfo();
  727. }
  728. private boolean fullyColumn() {
  729. return SqlUtils.notNull(getTableName(), getColumnName());
  730. }
  731. private boolean fullyTable() {
  732. return SqlUtils.notNull(getTableName());
  733. }
  734. private String fullyQualifiedColumn() {
  735. return fullyColumn() ? String.format("%s.%s.%s.%s", fixCatalog(), fixSchema(), getTableName(), getColumnName()) : null;
  736. }
  737. private String fullyQualifiedTable() {
  738. return fullyTable() ? String.format("%s.%s.%s", fixCatalog(), fixSchema(), getTableName()) : null;
  739. }
  740. private String fixCatalog() {
  741. return null == this.catalog ? "<catalog>" : this.catalog;
  742. }
  743. private String fixSchema() {
  744. return null == this.schema ? "<schema>" : this.schema;
  745. }
  746. }
  747. }